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.test.ts7.7 KB
// Ported from the Rust plugin's options.rs and models.rs tests.
import assert from "node:assert/strict";
import type { ConfigOption } from "./types.ts";
import { required, test } from "./testing.ts";
import {
Catalog,
DEFAULT,
EFFORT,
FAST_MODE,
MODEL,
PERMISSION_MODE,
THINKING,
ULTRACODE,
choiceFor,
effortSettings,
fastModeNote,
permissionFlag,
selected,
} from "./options.ts";
import { manifest } from "./models.ts";
import { permissionMode } from "../sdk/agent.ts";
function catalog() {
return Catalog.fromResponse({
models: [
{
value: "default",
resolvedModel: "claude-opus-5[1m]",
displayName: "Default (recommended)",
description: "Opus 5",
supportsEffort: true,
supportedEffortLevels: ["low", "medium", "high"],
supportsAdaptiveThinking: true,
supportsFastMode: true,
},
{ value: "haiku", resolvedModel: "claude-haiku-4-5", displayName: "Haiku", description: "Fast" },
],
commands: [{ name: "review", description: "Review code", argumentHint: "<path>" }],
current_permission_mode: "auto",
fast_mode_state: "off",
fast_mode_disabled_reason: "sdk_opt_in_required",
});
}
/// The CLI's catalog when the user's settings name `opus`.
function cliCatalog() {
return Catalog.fromResponse({
models: [
{ value: "default", resolvedModel: "claude-opus-5-5[1m]", displayName: "Default (recommended)" },
{ value: "opus[1m]", resolvedModel: "claude-opus-5-5[1m]", displayName: "Opus (1M context)" },
{ value: "claude-fable-5-1[1m]", resolvedModel: "claude-fable-5-1", displayName: "Fable" },
{ value: "sonnet", resolvedModel: "claude-sonnet-5", displayName: "Sonnet" },
{ value: "haiku", resolvedModel: "claude-haiku-4-5-20251001", displayName: "Haiku" },
{ value: "opus", resolvedModel: "claude-opus-5-5", displayName: "Opus" },
],
});
}
const option = (options: ConfigOption[], id: string) =>
options.find((option) => option.id === id) ?? assert.fail(`no ${id} option`);
/// The host's `model_reasoning`: a per-model catalog, or null.
function reasoningOf(option: ConfigOption, model: string) {
if (!required(option.choices).some((choice) => (choice.reasoningLevels ?? []).length)) return null;
return required(option.choices).find((choice) => choice.value === model)?.reasoningLevels ?? [];
}
test("the built-in list is valid and names its default", () => {
const list = manifest();
assert.ok(list.models.some((model) => model.slug === list.default));
});
test("models come from the list with versions and 1M context", () => {
const model = option(cliCatalog().options({}), MODEL);
assert.equal(model.category, "model");
assert.equal(model.value, "claude-opus-5-5[1m]");
const named = (value: string) => required(model.choices).find((choice) => choice.value === value)?.name;
assert.equal(named("claude-opus-5-5[1m]"), "Opus 5.5");
assert.equal(named("claude-fable-5[1m]"), "Fable 5");
assert.equal(named("claude-opus-4-8"), "Opus 4.8");
assert.equal(named("claude-haiku-4-5"), "Haiku 4.5");
assert.ok(!required(model.choices).some((choice) => choice.value === DEFAULT), "no Default entry");
assert.ok(required(model.choices).every((choice) => choice.group === undefined));
});
test("models carry their own effort levels", () => {
const model = option(cliCatalog().options({}), MODEL);
assert.equal(required(reasoningOf(model, "claude-opus-4-6[1m]")).length, 4);
assert.equal(required(reasoningOf(model, "claude-haiku-4-5")).length, 0);
});
test("ultracode is offered where the model has xhigh", () => {
const levels = (catalog: Catalog, model: string) =>
required(reasoningOf(option(catalog.options({}), MODEL), model)).map((choice) => choice.value);
const cli = cliCatalog();
assert.deepEqual(levels(cli, "claude-opus-5-5[1m]"), ["low", "medium", "high", "xhigh", "max", ULTRACODE]);
assert.ok(!levels(cli, "claude-opus-4-6[1m]").includes(ULTRACODE), "4.6 has no xhigh");
assert.deepEqual(levels(cli, "claude-haiku-4-5"), []);
const options = cli.options({});
const ultracode = required(reasoningOf(option(options, MODEL), "claude-opus-5-5[1m]")).at(-1);
assert.equal(required(ultracode).name, "Ultracode");
assert.ok(required(ultracode).icon, "the pill shows its mark");
assert.equal(required(ultracode).transient, true, "a new chat must not inherit it");
assert.ok(required(option(options, EFFORT).choices).some((choice) => choice.value === ULTRACODE));
const off = cliCatalog();
off.workflowsOff = true;
assert.ok(!levels(off, "claude-opus-5-5[1m]").includes(ULTRACODE), "workflows are off");
});
test("effort choices become flag settings", () => {
assert.deepEqual(effortSettings(ULTRACODE), { effortLevel: "xhigh", ultracode: true });
assert.deepEqual(effortSettings("high"), { effortLevel: "high", ultracode: false });
assert.deepEqual(effortSettings(DEFAULT), { effortLevel: null, ultracode: false });
});
test("the CLI values select the model they resolve to", () => {
const model = option(cliCatalog().options({}), MODEL);
const chosen = (value: string) => choiceFor(model, value)?.name;
for (const value of ["opus", "opus[1m]", "default", "claude-opus-5-5"])
assert.equal(chosen(value), "Opus 5.5", value);
assert.equal(chosen("claude-fable-5-1[1m]"), "Fable 5.1");
assert.equal(chosen("sonnet"), "Sonnet 5");
assert.equal(chosen("haiku"), "Haiku 4.5");
});
test("chosen values win over catalog defaults", () => {
const values = { [MODEL]: "claude-haiku-4-5", [EFFORT]: "high" };
const options = catalog().options(values);
assert.equal(option(options, MODEL).value, "claude-haiku-4-5");
assert.equal(option(options, EFFORT).value, "high");
assert.equal(selected(values, EFFORT), "high");
assert.equal(selected({ [EFFORT]: "default" }, EFFORT), null);
});
test("slash commands come from the CLI", () => {
assert.deepEqual(catalog().slashCommands(), [
{ name: "review", description: "Review code", source: "agent", inputHint: "<path>" },
]);
});
test("approvals use the shared four modes", () => {
const approvals = option(catalog().options({}), PERMISSION_MODE);
assert.equal(approvals.category, "approval");
assert.deepEqual(
required(approvals.choices).map((choice) => choice.value),
permissionMode.ALL,
"Claude has its own reviewer, so Auto is offered",
);
// The CLI is configured for `auto` here, and a chat still starts
// Supervised.
assert.equal(approvals.value, permissionMode.SUPERVISED);
assert.equal(
option(catalog().options({ [PERMISSION_MODE]: permissionMode.FULL }), PERMISSION_MODE).value,
permissionMode.FULL,
);
assert.equal(permissionFlag(permissionMode.SUPERVISED), "default");
assert.equal(permissionFlag(permissionMode.AUTO_EDITS), "acceptEdits");
assert.equal(permissionFlag(permissionMode.AUTO), "auto");
assert.equal(permissionFlag(permissionMode.FULL), "bypassPermissions");
});
test("toggles appear only when a model supports them", () => {
const options = catalog().options({ [FAST_MODE]: true });
assert.equal(option(options, THINKING).kind, "toggle");
assert.equal(option(options, THINKING).value, true);
assert.equal(option(options, FAST_MODE).value, true);
// The CLI says fast mode waits for a client to opt in, and this toggle
// is that opt in, so nothing is reported as unavailable.
assert.equal(option(options, FAST_MODE).description, undefined);
assert.equal(option(options, FAST_MODE).category, "speed");
assert.equal(fastModeNote("not_first_party"), "Only an Anthropic account can use fast mode.");
const plain = Catalog.fromResponse({ models: [{ value: "haiku", displayName: "Haiku", description: "" }] }).options(
{},
);
assert.ok(!plain.some((option) => option.id === THINKING));
assert.ok(!plain.some((option) => option.id === FAST_MODE));
});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.