Official
claude
Claude Code agent provider: runs the Claude Code CLI headless for each account.
The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/claude@0.2.0
Permissions in 0.2.0
Files
options.ts10.8 KB
// Config options (the Rust `options.rs`). The model list and each model's
// effort levels come from `models.json`; the slash commands and which
// toggles exist at all come from the CLI's `initialize` control response.
import * as wire from "./wire.ts";
import type { ConfigOption, ConfigChoice, SlashCommand, Values } from "./types.ts";
import { permissionMode } from "../sdk/agent.ts";
import { manifest as loadManifest, modelMatches, modelValue } from "./models.ts";
export const MODEL = "model";
export const EFFORT = "effort";
export const PERMISSION_MODE = permissionMode.OPTION_ID;
export const THINKING = "thinking";
export const FAST_MODE = "fast_mode";
/// The value that means "leave this to Claude Code", so no flag is passed.
export const DEFAULT = "default";
/// The effort level above `xhigh`: `xhigh` plus standing dynamic-workflow
/// orchestration. The CLI takes it as `--effort ultracode` and as the
/// session setting `ultracode`; it needs a model that offers `xhigh`.
export const ULTRACODE = "ultracode";
/// The effort level ultracode runs at, and needs.
const XHIGH = "xhigh";
/// The mark of the ultracode choice: the `workflow` icon.
const ULTRACODE_ICON =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="8" x="3" y="3" rx="2"/><path d="M7 11v4a2 2 0 0 0 2 2h4"/><rect width="8" height="8" x="13" y="13" rx="2"/></svg>';
/// The `--permission-mode` value for one of the four shared modes. Claude
/// has its own reviewer, so `auto` is a real mode. The mode is always
/// sent: the composer shows a mode, and it must be the one in force.
export function permissionFlag(mode: string) {
switch (mode) {
case permissionMode.AUTO_EDITS:
return "acceptEdits";
case permissionMode.AUTO:
return "auto";
case permissionMode.FULL:
return "bypassPermissions";
default:
return "default";
}
}
/// Why fast mode cannot serve, in words the user can act on. The reasons
/// the toggle itself clears, and the ones that pass on their own, give
/// nothing: "unavailable" next to the switch that turns it on is wrong.
export function fastModeNote(reason: string) {
switch (reason) {
case "not_first_party":
return "Only an Anthropic account can use fast mode.";
case "model_not_allowed":
return "Your organization does not allow fast mode for this model.";
case "free":
case "extra_usage_disabled":
return "Fast mode needs usage credits.";
case "network_error":
return "Fast mode could not be reached.";
case "disabled_by_env":
return "Fast mode is switched off on this machine.";
default:
return null;
}
}
const EFFORT_NAMES: Record<string, string> = {
low: "Low",
medium: "Medium",
high: "High",
xhigh: "Extra high",
max: "Max",
};
export function effortChoice(level: string): ConfigChoice {
if (level === ULTRACODE) {
return {
value: ULTRACODE,
name: "Ultracode",
description: "Extra high effort, and Claude plans dynamic workflows: many agents in parallel.",
icon: ULTRACODE_ICON,
// The CLI never persists it either: every workflow costs many
// agents' worth of tokens, so each chat opts in again.
transient: true,
};
}
return { value: level, name: EFFORT_NAMES[level] ?? level };
}
/// The `apply_flag_settings` layer for an effort choice, which changes a
/// running session without a restart. Ultracode is a setting of its own,
/// so every other level turns it off again; `null` drops the layer's
/// effort and the CLI falls back to the user's configured level.
export function effortSettings(value: string | null | undefined) {
if (value === ULTRACODE) return { effortLevel: XHIGH, ultracode: true };
if (value === DEFAULT || value === "" || value === null || value === undefined)
return { effortLevel: null, ultracode: false };
return { effortLevel: value, ultracode: false };
}
/// A string option value, ignoring the "leave it to Claude Code"
/// placeholder.
export function selected(values: Values | null | undefined, id: string) {
const value = values?.[id];
return typeof value === "string" && value !== "" && value !== DEFAULT ? value : null;
}
/// A toggle value.
export function toggle(values: Values | null | undefined, id: string, fallback: boolean) {
const value = values?.[id];
return typeof value === "boolean" ? value : fallback;
}
/// The parts of the `initialize` control response the plugin uses.
export class Catalog {
models: wire.Model[];
commands: wire.Command[];
fastModeState: string | null;
fastModeDisabledReason: string | null;
manifest: ReturnType<typeof loadManifest>;
workflowsOff: boolean;
constructor({
models = [],
commands = [],
fastModeState = null,
fastModeDisabledReason = null,
manifest = loadManifest(),
workflowsOff = false,
}: {
models?: wire.Model[];
commands?: wire.Command[];
fastModeState?: string | null;
fastModeDisabledReason?: string | null;
manifest?: ReturnType<typeof loadManifest>;
workflowsOff?: boolean;
} = {}) {
this.models = models;
this.commands = commands;
/// `off`, `cooldown` or `on`. Absent on a CLI without fast mode.
this.fastModeState = fastModeState;
this.fastModeDisabledReason = fastModeDisabledReason;
this.manifest = manifest;
/// The user's settings turn dynamic workflows off: no ultracode.
this.workflowsOff = workflowsOff;
}
static fromResponse(response: wire.CatalogResponse) {
const text = (key: string) => (typeof response?.[key] === "string" ? response[key] : null);
return new Catalog({
models: Array.isArray(response?.models) ? response.models : [],
commands: Array.isArray(response?.commands) ? response.commands : [],
fastModeState: text("fast_mode_state"),
fastModeDisabledReason: text("fast_mode_disabled_reason"),
});
}
slashCommands() {
return this.commands
.filter((command) => typeof command?.name === "string")
.map((command) => {
const out: SlashCommand = {
name: command.name ?? "",
description: typeof command.description === "string" ? command.description : "",
source: "agent",
};
if (typeof command.argumentHint === "string" && command.argumentHint) out.inputHint = command.argumentHint;
return out;
});
}
/// A model's effort levels, with ultracode after them when the model can
/// run it: the CLI accepts ultracode for any model and silently runs
/// without workflows where `xhigh` is missing.
modelEfforts(levels: string[]) {
const out = [...levels];
if (!this.workflowsOff && out.includes(XHIGH)) out.push(ULTRACODE);
return out;
}
/// Effort levels offered by any model, in the list's order.
effortLevels() {
const levels: string[] = [];
for (const model of this.manifest.models) {
for (const level of this.modelEfforts(model.effort)) if (!levels.includes(level)) levels.push(level);
}
return levels;
}
/// Whether any model the account can run declares a capability.
anyModel(capability: "supportsAdaptiveThinking" | "supportsFastMode") {
return this.models.some((model) => model?.[capability] === true);
}
/// One choice per model in `models.json`, the 1M context variant where
/// the model has one. The CLI's own values (`opus`, `opus[1m]`,
/// `default`) become aliases of the model they resolve to, so a chat
/// that stored one keeps its model.
modelChoices() {
return this.manifest.models.map((model) => {
const value = modelValue(model);
const aliases = this.models
.filter((cli) => typeof cli?.resolvedModel === "string" && modelMatches(model, cli.resolvedModel))
.map((cli) => cli.value)
.filter((alias) => typeof alias === "string");
if (model.slug !== value) aliases.push(model.slug);
const choice: ConfigChoice = { value, name: model.name };
const levels = this.modelEfforts(model.effort).map(effortChoice);
if (levels.length) choice.reasoningLevels = levels;
if (aliases.length) choice.aliases = aliases;
return choice;
});
}
/// The full option snapshot with `values` applied on top of the
/// catalog's own defaults.
options(values: Values = {}): ConfigOption[] {
const chosen = (id: string, fallback: string) => (typeof values?.[id] === "string" ? values[id] : fallback);
const defaultModel = this.manifest.models.find((model) => model.slug === this.manifest.default);
const options: ConfigOption[] = [
{
id: MODEL,
name: "Model",
category: "model",
kind: "select",
value: chosen(MODEL, defaultModel ? modelValue(defaultModel) : DEFAULT),
choices: this.modelChoices(),
},
{
id: EFFORT,
name: "Effort",
description: "Reasoning effort. Ultracode adds dynamic workflows.",
category: "reasoning",
kind: "select",
value: chosen(EFFORT, DEFAULT),
choices: [
{ value: DEFAULT, name: "Default", description: "Let Claude Code pick the effort for the model." },
...this.effortLevels().map(effortChoice),
],
},
// The CLI's own `current_permission_mode` is deliberately not
// adopted: the host reads an unset option as Supervised, so a
// session that silently ran looser would do more than the composer
// says.
permissionMode.option(permissionMode.selected(values), true),
];
// A toggle is only offered when the CLI proves it exists: a model that
// declares the capability, and for fast mode a CLI that reports its
// state at all.
if (this.anyModel("supportsAdaptiveThinking")) {
options.push({
id: THINKING,
name: "Thinking",
description: "Let the model think before answering. Applied when the session starts.",
category: "reasoning",
kind: "toggle",
value: toggle(values, THINKING, true),
choices: [],
});
}
if (this.fastModeState !== null && this.anyModel("supportsFastMode")) {
const option: ConfigOption = {
id: FAST_MODE,
name: "Fast mode",
category: "speed",
kind: "toggle",
value: toggle(values, FAST_MODE, false),
choices: [],
};
const note = this.fastModeDisabledReason !== null ? fastModeNote(this.fastModeDisabledReason) : null;
if (note) option.description = note;
options.push(option);
}
return options;
}
}
/// The choice a stored value selects: the one with that value, or the one
/// that lists it as an alias (the host's `ConfigOption::choice_for`).
export function choiceFor(option: ConfigOption, value: string) {
return (
(option.choices ?? []).find((choice) => choice.value === value) ??
(option.choices ?? []).find((choice) => (choice.aliases ?? []).includes(value)) ??
null
);
}Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 128.7 KB | 4 permissions | Listed |
No comments yet.