Official
opencode
OpenCode agent provider: runs opencode serve and talks to it over HTTP and server-sent events.
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/opencode@0.2.0
Permissions in 0.2.0
Files
options.test.ts4.7 KB
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildOptions, contextWindow, reasoningOption, splitModel, variantChoices } from "./options.ts";
test("model values split on the provider; a model id may hold slashes", () => {
assert.deepEqual(splitModel("opencode/big-pickle"), ["opencode", "big-pickle"]);
assert.deepEqual(splitModel("lmstudio/qwen/qwen3-coder-30b"), ["lmstudio", "qwen/qwen3-coder-30b"]);
assert.equal(splitModel("nope"), null);
assert.equal(splitModel("/x"), null);
assert.equal(splitModel(null), null);
});
test("variants are ordered by effort, unknown names after", () => {
const model = { variants: { high: {}, none: {}, medium: {}, low: {}, xhigh: {}, turbo: {} } };
assert.deepEqual(
variantChoices(model).map((choice) => choice.value),
["none", "low", "medium", "high", "xhigh", "turbo"],
);
assert.deepEqual(variantChoices({}), []);
});
test("the reasoning option survives a model without variants", () => {
const models = [{ value: "plain" }, { value: "thinking", reasoningLevels: [{ value: "custom", name: "custom" }] }];
const initial = reasoningOption(models, "plain", undefined);
assert.deepEqual(initial?.choices, []);
assert.equal(initial?.value, null);
const selected = reasoningOption(models, "thinking", "custom");
assert.deepEqual(selected?.choices, models[1]?.reasoningLevels);
assert.equal(selected?.value, "custom");
assert.equal(reasoningOption(models.slice(0, 1), "plain", undefined), null);
});
const PROVIDERS = {
providers: [
{
id: "opencode",
name: "OpenCode Zen",
models: {
"big-pickle": {
id: "big-pickle",
name: "Big Pickle",
release_date: "2025-10-17",
variants: {},
limit: { context: 200000 },
},
"free-new": { id: "free-new", name: "Free New", release_date: "2026-02-01", variants: { high: {}, low: {} } },
},
},
{ id: "openai", name: "OpenAI", models: { "gpt-5": { id: "gpt-5", name: "GPT-5", variants: { medium: {} } } } },
],
default: { openai: "gpt-5", opencode: "big-pickle" },
};
const AGENTS = [
{ name: "build", mode: "primary", description: "The default agent" },
{ name: "explore", mode: "subagent" },
{ name: "plan", mode: "all" },
{ name: "title", mode: "primary", hidden: true },
];
test("the options come from the live catalog: models grouped and newest first, primary modes, reasoning, permissions", () => {
const options = buildOptions(PROVIDERS, AGENTS, {});
assert.deepEqual(
options.map((option) => option.id),
["model", "agent", "variant", "permission_mode"],
);
const [model, mode, reasoning, permission] = options;
assert.deepEqual(
model?.choices?.map((choice) => [choice.value, choice.group]),
[
["opencode/free-new", "OpenCode Zen"],
["opencode/big-pickle", "OpenCode Zen"],
["openai/gpt-5", "OpenAI"],
],
);
// The first provider with a default names the initial model.
assert.equal(model?.value, "opencode/big-pickle");
assert.deepEqual(
model?.choices?.[0]?.reasoningLevels?.map((level) => level.value),
["low", "high"],
);
assert.equal(model?.choices?.[1]?.reasoningLevels, undefined, "a model without variants has no reasoning control");
assert.deepEqual(
mode?.choices?.map((choice) => choice.value),
["build", "plan"],
);
assert.equal(mode?.value, "build");
assert.equal(reasoning?.value, null, "the provider default until the user picks one");
assert.deepEqual(reasoning?.choices, []);
// Without an approval reviewer the composer must not offer Auto.
assert.deepEqual(
permission?.choices?.map((choice) => choice.value),
["supervised", "auto_edits", "full"],
);
const chosen = buildOptions(PROVIDERS, AGENTS, {
model: "opencode/free-new",
variant: "high",
agent: "plan",
permission_mode: "full",
});
assert.equal(chosen[0]?.value, "opencode/free-new");
assert.equal(chosen[1]?.value, "plan");
assert.equal(chosen[2]?.value, "high");
assert.deepEqual(
chosen[2]?.choices?.map((choice) => choice.value),
["low", "high"],
);
assert.equal(chosen[3]?.value, "full");
});
test("an empty catalog is an error, never an invented list", () => {
assert.throws(
() => buildOptions({ providers: [], default: {} }, AGENTS, {}),
/no connected providers; run `opencode auth login` first/,
);
assert.throws(() => buildOptions(null, [], {}), /no connected providers/);
});
test("the context window comes from the catalog entry", () => {
assert.equal(contextWindow(PROVIDERS, "opencode", "big-pickle"), 200000);
assert.equal(contextWindow(PROVIDERS, "openai", "gpt-5"), null);
assert.equal(contextWindow(PROVIDERS, "nobody", "x"), null);
});Versions
| Version | Published | Plugin API | Size | Permissions | Status |
|---|---|---|---|---|---|
| 0.2.0latest | Oct 5, 2026 | >=2 <3 | 94.8 KB | 5 permissions | Listed |
No comments yet.