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
client.test.ts11.7 KB
import { test } from "node:test";
import assert from "node:assert/strict";
import * as Effect from "effect/Effect";
import {
LocalServer,
Trouble,
basicAuth,
checkVersion,
failure,
generatedPassword,
isLoopback,
listensOn,
openEvents,
parseListenUrl,
passwordFor,
queryString,
randomPort,
request,
semver,
speaksOurApi,
troubleOf,
versionIn,
} from "./client.ts";
import { fakeApi } from "../sdk/testing.ts";
import type { FakePeer } from "../sdk/testing.ts";
import type { SpawnOptions } from "../sdk/process.ts";
import { testRuntime } from "./testing.test.ts";
import { errorMessage } from "../sdk/errors.ts";
import { obj } from "./map.ts";
/// Every program the test starts is played by `script(program, args,
/// options, peer)`, which writes its output and ends it.
function scripted(
api: ReturnType<typeof fakeApi>,
script: (program: string, args: readonly string[], options: SpawnOptions, peer: FakePeer) => void,
) {
const spawn = api.process.spawn;
api.process.spawn = async (program, args = [], options = {}) => {
const child = await spawn(program, args, options);
const peer = api.peers.at(-1);
assert.ok(peer);
queueMicrotask(() => script(program, args, options, peer));
return child;
};
return api;
}
test("a local password never reaches a remote server", () => {
assert.equal(passwordFor(false, null, "local-secret"), "local-secret");
assert.equal(passwordFor(true, null, "local-secret"), null, "the ambient password must not be forwarded");
// A password the user chose applies wherever the server runs.
assert.equal(passwordFor(true, "configured", "local-secret"), "configured");
assert.equal(passwordFor(false, "configured", "local-secret"), "configured");
assert.equal(passwordFor(true, null, null), null);
});
test("a server we start always gets a password we know, new every launch", () => {
const one = generatedPassword();
const two = generatedPassword();
assert.match(one, /^[0-9a-f]{64}$/);
assert.notEqual(one, two, "a generated password must not repeat between launches");
assert.equal(
basicAuth({ username: "opencode", password: "secret" }),
`Basic ${Buffer.from("opencode:secret").toString("base64")}`,
);
assert.equal(
basicAuth({ username: "opencode", password: "pässword" }),
`Basic ${Buffer.from("opencode:pässword").toString("base64")}`,
);
assert.equal(basicAuth(null), null);
});
test("the reported address must match the requested port", () => {
assert.ok(listensOn("http://127.0.0.1:51574", 51574));
assert.ok(listensOn("http://127.0.0.1:51574/", 51574));
assert.ok(!listensOn("http://127.0.0.1:4096", 51574), "the default port is not the one we asked for");
for (let i = 0; i < 50; i += 1) {
const port = randomPort();
assert.ok(port >= 20000 && port < 50000, `${port}`);
}
});
test("a 2.x binary is named by its version, not guessed at", () => {
assert.equal(versionIn("opencode v2.0.1\n"), "2.0.1");
assert.equal(versionIn("1.18.29\n"), "1.18.29");
assert.equal(versionIn("opencode (unknown)"), null);
assert.ok(speaksOurApi("1.18.29"));
assert.ok(!speaksOurApi("2.0.1"));
assert.ok(speaksOurApi("weird-build"), "an unparsable version is trusted, as checkVersion does");
const message = new Trouble("unsupported", { binary: "/u/.local/bin/opencode", version: "2.0.1" }).message;
assert.match(message, /2\.0\.1/);
assert.match(message, /\/u\/\.local\/bin\/opencode/);
assert.match(message, /1\.x `opencode` first on your login PATH/);
});
test("the event stream is authorized like every other request", async () => {
const api = fakeApi({ onFetch: () => ({ status: 200, body: "" }) });
const { run } = testRuntime(api);
await run(openEvents("http://127.0.0.1:1", { username: "opencode", password: "secret" }, "/w"));
const [call] = api.fetches;
assert.ok(call);
assert.equal(call.url, "http://127.0.0.1:1/event?directory=%2Fw");
assert.equal(
obj(call.headers)["authorization"],
`Basic ${Buffer.from("opencode:secret").toString("base64")}`,
"the stream must send the password",
);
assert.equal(obj(call.headers)["accept"], "text/event-stream");
await run(openEvents("http://127.0.0.1:1", null, "/w"));
assert.equal(obj(api.fetches[1]?.headers)["authorization"], undefined);
});
test("a rejected password is told apart from any other failure", async () => {
assert.equal(troubleOf(failure(401, "GET", "/session", "no"))?.kind, "auth_rejected");
assert.equal(troubleOf(failure(403, "GET", "/session", "no"))?.kind, "auth_rejected");
assert.equal(troubleOf(failure(404, "GET", "/session", "gone")), null);
const api = fakeApi({ onFetch: () => ({ status: 401, body: "Unauthorized" }) });
const { run } = testRuntime(api);
const error = await run(request("http://127.0.0.1:1", null, "GET", "/session", { query: { directory: "/w" } })).catch(
(e) => e,
);
assert.equal(troubleOf(error)?.kind, "auth_rejected");
assert.match(errorMessage(error), /GET \/session failed with 401: Unauthorized/);
const old = (() => {
try {
checkVersion("1.9.0");
} catch (e) {
return e;
}
})();
assert.equal(troubleOf(old)?.kind, "too_old");
assert.match(errorMessage(old), /1\.14\.19/, "the message names the version needed");
});
test("requests carry JSON, credentials and the folder; an answer that is not JSON says what came", async () => {
const api = fakeApi({
onFetch: (url) =>
url.includes("/html")
? { status: 200, body: "<!doctype html>", headers: { "content-type": "text/html" } }
: { status: 200, body: { ok: 1 } },
});
const { run } = testRuntime(api);
const auth = { username: "opencode", password: "p" };
assert.deepEqual(
await run(
request("http://h:1", auth, "POST", "/session", { query: { directory: "/w a", roots: null }, body: { a: 1 } }),
),
{ ok: 1 },
);
const [call] = api.fetches;
assert.ok(call);
assert.equal(call.url, "http://h:1/session?directory=%2Fw%20a");
assert.equal(call.method, "POST");
assert.equal(call.body, '{"a":1}');
assert.equal(obj(call.headers)["content-type"], "application/json");
assert.ok(String(obj(call.headers)["authorization"]).startsWith("Basic "));
await assert.rejects(
run(request("http://h:1", auth, "GET", "/html")),
/decoding the response of GET \/html \(status 200, text\/html, 15 bytes: "<!doctype html>"\)/,
);
assert.equal(await run(request("http://h:1", auth, "POST", "/x", { body: {}, empty: true })), null);
assert.equal(queryString({}), "");
});
test("the listen address is anchored on the scheme", () => {
assert.equal(parseListenUrl("opencode server listening on http://127.0.0.1:52341"), "http://127.0.0.1:52341");
assert.equal(parseListenUrl("listening on http://127.0.0.1:1/ ready"), "http://127.0.0.1:1");
assert.equal(parseListenUrl("see https://opencode.ai/docs for help."), "https://opencode.ai/docs");
assert.equal(parseListenUrl("starting server"), null);
assert.equal(parseListenUrl("http://"), null);
});
test("versions compare by number, not by text", () => {
assert.deepEqual(semver("1.18.29"), [1, 18, 29]);
assert.deepEqual(semver("v1.2"), [1, 2, 0]);
assert.deepEqual(semver("1.19.0-beta.1"), [1, 19, 0]);
assert.equal(semver("nightly"), null);
// "1.9.0" sorts after "1.14.19" as text but is older as a version.
assert.throws(() => checkVersion("1.9.0"));
checkVersion("1.14.19");
checkVersion("1.18.29");
checkVersion("2.0.0");
checkVersion("nightly");
});
test("only loopback servers can reach the host's tools server", () => {
assert.ok(isLoopback("http://127.0.0.1:4096"));
assert.ok(isLoopback("http://localhost:1"));
assert.ok(isLoopback("http://[::1]:1"));
assert.ok(!isLoopback("https://opencode.example:4096"));
assert.ok(!isLoopback("http://127.0.0.1.evil.example"));
});
test("a local server starts on the port it picked, with the password in its environment, and answers its health check", async () => {
const api = scripted(
fakeApi({
onFetch: (url) =>
url.endsWith("/global/health") ? { body: { healthy: true, version: "1.18.29" } } : { status: 404 },
}),
(program, args, options, peer) => {
if (args[0] === "--version") {
peer.stdout.push("1.18.29\n");
peer.exit(0);
return;
}
const port = args[args.indexOf("--port") + 1];
peer.stdout.push(`opencode server listening on http://127.0.0.1:${port}\n`);
},
);
const { run } = testRuntime(api);
const auth = { username: "opencode", password: "secret" };
const server = await run(LocalServer.start(auth));
const serve = api.peers[1];
assert.ok(serve);
assert.equal(serve.program, "opencode");
assert.deepEqual(serve.args.slice(0, 3), ["serve", "--hostname", "127.0.0.1"]);
assert.deepEqual(serve.options.env, { OPENCODE_SERVER_PASSWORD: "secret" });
assert.equal(server.base, `http://127.0.0.1:${server.port}`);
assert.equal(server.version, "1.18.29");
assert.ok(server.alive);
assert.equal(obj(api.fetches[0]?.headers)["authorization"], basicAuth(auth));
await run(server.stop());
assert.deepEqual(serve.kills, ["SIGTERM"]);
assert.ok(!server.alive);
});
test("a 2.x binary is refused before it starts; a server that reports another port is stopped", async () => {
const two = scripted(
fakeApi({ onHost: () => ({ path: "/u/.local/bin/opencode", realPath: "/u/x" }) }),
(program, args, options, peer) => {
peer.stdout.push("opencode v2.0.16\n");
peer.exit(0);
},
);
const { run: runTwo } = testRuntime(two);
const error = await runTwo(
LocalServer.start(null, { describe: () => Effect.succeed("/u/.local/bin/opencode") }),
).catch((e) => e);
assert.equal(troubleOf(error)?.kind, "unsupported");
assert.match(errorMessage(error), /\/u\/\.local\/bin\/opencode is OpenCode 2\.0\.16/);
assert.equal(two.peers.length, 1, "no server was started");
const elsewhere = scripted(fakeApi({ onFetch: () => ({ status: 404 }) }), (program, args, options, peer) => {
if (args[0] === "--version") {
peer.stdout.push("1.18.29\n");
peer.exit(0);
return;
}
peer.stdout.push("opencode server listening on http://127.0.0.1:4096\n");
});
const { run: runElsewhere } = testRuntime(elsewhere);
await assert.rejects(runElsewhere(LocalServer.start(null)), /another server may own that address/);
// Each of the three attempts was stopped.
assert.equal(elsewhere.peers.filter((peer) => peer.args[0] === "serve").length, 3);
assert.ok(elsewhere.peers.filter((peer) => peer.args[0] === "serve").every((peer) => peer.kills.length));
});
test("a server that exits before its address names what it printed; a missing binary is `not installed`", async () => {
const dies = scripted(fakeApi(), (program, args, options, peer) => {
if (args[0] === "--version") {
peer.stdout.push("1.18.29\n");
peer.exit(0);
return;
}
peer.stderr.push("Error: database is locked\n");
peer.exit(1);
});
const { run: runDies } = testRuntime(dies);
await assert.rejects(
runDies(LocalServer.start(null)),
/exited before it reported a listen address; last output: Error: database is locked/,
);
const missing = fakeApi({ onSpawn: () => new Error("opencode is not on the PATH") });
const { run: runMissing } = testRuntime(missing);
const error = await runMissing(LocalServer.start(null)).catch((e) => e);
assert.equal(troubleOf(error)?.kind, "not_installed");
const path = fakeApi({
onSpawn: () =>
Object.assign(new Error("PermissionNotGranted: opencode has no grant for process /opt/oc/opencode"), {
name: "PermissionNotGranted",
}),
});
const { run: runPath } = testRuntime(path);
await assert.rejects(
runPath(LocalServer.start(null, { binary: "/opt/oc/opencode" })),
/only as `opencode` from the login PATH; starting \/opt\/oc\/opencode needs a grant/,
);
});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.