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
main.test.ts5.6 KB
// Scripted scenarios for the opencode conversion.
import { test } from "node:test";
import assert from "node:assert/strict";
import { runPlugin } from "convergence/effect";
import type { OpenCodeAgent } from "./agent.ts";
import { registerServers } from "./main.ts";
import { fakeApi, settle } from "../sdk/testing.ts";
import { obj } from "./map.ts";
function present<T>(value: T | null | undefined): T {
assert.ok(value != null);
return value;
}
test("the local server's agent registers at once, stored servers after", async (t) => {
const api = fakeApi({
env: { HOME: "/Users/me", OPENCODE_SERVER_PASSWORD: "ambient" },
onHost: (method, params) => {
if (method === "host/storage.get") {
assert.equal(params.key, "instances");
return {
value: [
{ id: "", name: "My OpenCode" },
{ id: "team", name: "OpenCode (team)", serverUrl: "https://oc.example:4096/", password: "team-secret" },
],
};
}
return {};
},
});
const agents: OpenCodeAgent[] = [];
const dispose = runPlugin(registerServers(agents))(api);
t.after(dispose);
assert.deepEqual(
api.registered.map((def) => def.id),
["opencode"],
"registered before anything is awaited",
);
await settle();
assert.deepEqual(
api.registered.map((def) => [def.id, def.name]),
[
["opencode", "OpenCode"],
["opencode:team", "OpenCode (team)"],
],
);
// The runtime tells the host of agents registered after the load.
assert.equal(
api.hostCalls.some((call) => call.method === "host/agents.changed"),
false,
);
// The environment and the stored settings reach an agent before its
// first call.
const team = present(agents[1]);
const seen: { url: string; init: unknown }[] = [];
api.net.fetch = async (url, init) => {
seen.push({ url, init });
throw new Error("stop here");
};
const definition = present(api.registered[1]);
const info = await present(definition.initialize)(
{ agentId: definition.id },
{ agentId: definition.id, emit: () => {} },
);
assert.equal(info.status?.state, "unavailable");
assert.ok(seen[0]?.url.startsWith("https://oc.example:4096/global/health"));
assert.equal(
obj(obj(seen[0]?.init)["headers"])["authorization"],
`Basic ${Buffer.from("opencode:team-secret").toString("base64")}`,
"the instance's own password, never the local one",
);
assert.equal(team.env.HOME, "/Users/me");
api.process.spawn = async () => {
throw new Error("opencode is not on the PATH");
};
const local = present(api.registered[0]);
await assert.rejects(
Promise.resolve(
present(local.list_sessions)({ agentId: local.id, workspace: "/w" }, { agentId: local.id, emit: () => {} }),
),
/not found on PATH; install opencode/,
);
assert.equal(present(agents[0]).name, "My OpenCode", "the stored entry without an id names the local agent");
// Events an agent emits go out under its own id.
team.emit({ sessionId: "s", event: "run_finished", outcome: { status: "completed" } });
assert.equal(present(api.emitted.at(-1))["agentId"], "opencode:team");
});
test("without stored servers only the local one is served", async (t) => {
const api = fakeApi({ onHost: () => ({ value: null }) });
const dispose = runPlugin(registerServers())(api);
t.after(dispose);
await settle();
assert.deepEqual(
api.registered.map((def) => def.id),
["opencode"],
);
assert.equal(
api.hostCalls.some((call) => call.method === "host/agents.changed"),
false,
);
});
// The `binaryPath` setting (MARKETPLACE.md §15.3, STATUS gap 14): the
// binary the user chose runs instead of the first one the login PATH finds,
// and a later change applies to the next launch.
test("the binary from Settings runs the local server", async (t) => {
const api = fakeApi({
onHost: () => ({ value: [{ id: "", command: "/usr/local/bin/opencode-stored" }] }),
settings: { binaryPath: "/opt/homebrew/bin/opencode" },
});
const spawned: string[] = [];
api.process.spawn = async (program) => {
spawned.push(program);
throw new Error("stop here");
};
const collected: OpenCodeAgent[] = [];
const dispose = runPlugin(registerServers(collected))(api);
t.after(dispose);
await settle();
const local = present(api.registered[0]);
await Promise.resolve(
present(local.list_sessions)({ agentId: local.id, workspace: "/w" }, { agentId: local.id, emit: () => {} }),
).catch(() => {});
assert.equal(spawned[0], "/opt/homebrew/bin/opencode", "the setting comes before a stored binary and the PATH");
api.changeSettings({ binaryPath: null });
await settle();
assert.equal(
present(collected[0]).instance.command,
"/usr/local/bin/opencode-stored",
"cleared, the stored one again",
);
api.changeSettings({ binaryPath: "/Users/me/.opencode/bin/opencode" });
await settle();
assert.equal(present(collected[0]).instance.command, "/Users/me/.opencode/bin/opencode");
});
// The `serverUrl` setting: its card grants the connection (the manifest's
// `${settings.serverUrl}` net scope), so the external server it names is
// the one the default agent talks to.
test("the external server from Settings is the default agent's server", async (t) => {
const api = fakeApi({ onHost: () => ({ value: [] }), settings: { serverUrl: "https://oc.example.com:4096" } });
const servers: OpenCodeAgent[] = [];
const dispose = runPlugin(registerServers(servers))(api);
t.after(dispose);
await settle();
assert.equal(present(servers[0]).external, "https://oc.example.com:4096");
api.changeSettings({ serverUrl: null });
await settle();
assert.equal(present(servers[0]).external, null, "cleared, a local server again");
});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.