import assert from "node:assert/strict"; import * as NodeServices from "@effect/platform-node/NodeServices";import { it } from "@effect/vitest";import { Effect, Layer } from "effect";import { beforeEach } from "vitest"; import { ServerConfig } from "../../config.ts";import { ServerSettingsService } from "../../serverSettings.ts";import { OpenCodeProvider } from "../Services/OpenCodeProvider.ts";import { OpenCodeRuntime, OpenCodeRuntimeError, type OpenCodeRuntimeShape,} from "../opencodeRuntime.ts";import { OpenCodeProviderLive } from "./OpenCodeProvider.ts";import type { OpenCodeInventory } from "../opencodeRuntime.ts"; const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n"; const runtimeMock = { state: { runVersionError: null as Error | null, versionStdout: DEFAULT_VERSION_STDOUT, inventoryError: null as Error | null, inventory: { providerList: { connected: [] as string[], all: [] as unknown[], default: {} }, agents: [] as unknown[], } as unknown, }, reset() { this.state.runVersionError = null; this.state.versionStdout = DEFAULT_VERSION_STDOUT; this.state.inventoryError = null; this.state.inventory = { providerList: { connected: [], all: [] as unknown[], default: {} }, agents: [] as unknown[], }; },}; const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = { startOpenCodeServerProcess: () => Effect.succeed({ url: "http://127.0.0.1:4301", exitCode: Effect.never, }), connectToOpenCodeServer: ({ serverUrl }) => Effect.succeed({ url: serverUrl ?? "http://127.0.0.1:4301", exitCode: null, external: Boolean(serverUrl), }), runOpenCodeCommand: () => runtimeMock.state.runVersionError ? Effect.fail( new OpenCodeRuntimeError({ operation: "runOpenCodeCommand", detail: runtimeMock.state.runVersionError.message, cause: runtimeMock.state.runVersionError, }), ) : Effect.succeed({ stdout: runtimeMock.state.versionStdout, stderr: "", code: 0 }), createOpenCodeSdkClient: () => ({}) as unknown as ReturnType<OpenCodeRuntimeShape["createOpenCodeSdkClient"]>, loadOpenCodeInventory: () => runtimeMock.state.inventoryError ? Effect.fail( new OpenCodeRuntimeError({ operation: "loadOpenCodeInventory", detail: runtimeMock.state.inventoryError.message, cause: runtimeMock.state.inventoryError, }), ) : Effect.succeed(runtimeMock.state.inventory as OpenCodeInventory),}; beforeEach(() => { runtimeMock.reset();}); const makeTestLayer = (settingsOverrides?: Parameters<typeof ServerSettingsService.layerTest>[0]) => OpenCodeProviderLive.pipe( Layer.provideMerge(Layer.succeed(OpenCodeRuntime, OpenCodeRuntimeTestDouble)), Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())), Layer.provideMerge(ServerSettingsService.layerTest(settingsOverrides)), Layer.provideMerge(NodeServices.layer), ); it.layer(makeTestLayer())("OpenCodeProviderLive", (it) => { it.effect("shows a codex-style missing binary message", () => Effect.gen(function* () { runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT"); const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; assert.equal(snapshot.status, "error"); assert.equal(snapshot.installed, false); assert.equal(snapshot.message, "OpenCode CLI (`opencode`) is not installed or not on PATH."); }), ); it.effect("hides generic Effect.tryPromise text for local CLI probe failures", () => Effect.gen(function* () { runtimeMock.state.runVersionError = new Error("An error occurred in Effect.tryPromise"); const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; assert.equal(snapshot.status, "error"); assert.equal(snapshot.installed, true); assert.equal(snapshot.message, "Failed to execute OpenCode CLI health check."); }), ); it.effect("refuses to probe when opencode is older than the required minimum", () => Effect.gen(function* () { runtimeMock.state.versionStdout = "opencode 1.4.7\n"; const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; assert.equal(snapshot.status, "error"); assert.equal(snapshot.installed, true); assert.equal(snapshot.version, "1.4.7"); assert.ok(snapshot.message?.includes("1.14.19")); assert.ok(snapshot.message?.toLowerCase().includes("upgrade")); }), ); it.effect("refuses to probe when opencode --version output is unparseable", () => Effect.gen(function* () { runtimeMock.state.versionStdout = "garbled binary output\n"; const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; assert.equal(snapshot.status, "error"); assert.equal(snapshot.installed, true); assert.equal(snapshot.version, null); assert.ok(snapshot.message?.includes("1.14.19")); }), ); it.effect("emits OpenCode variant defaults so trait picker can resolve a visible selection", () => Effect.gen(function* () { runtimeMock.state.inventory = { providerList: { connected: ["openai"], all: [ { id: "openai", name: "OpenAI", models: { "gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4", variants: { none: {}, low: {}, medium: {}, high: {}, xhigh: {}, }, }, }, }, ], default: {}, }, agents: [ { name: "build", hidden: false, mode: "primary" }, { name: "plan", hidden: false, mode: "primary" }, ], }; const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; const model = snapshot.models.find((entry) => entry.slug === "openai/gpt-5.4"); assert.ok(model); const variantDescriptor = model.capabilities?.optionDescriptors?.find( (descriptor) => descriptor.id === "variant" && descriptor.type === "select", ); assert.ok(variantDescriptor && variantDescriptor.type === "select"); assert.equal(variantDescriptor.options.find((option) => option.isDefault)?.id, "medium"); const agentDescriptor = model.capabilities?.optionDescriptors?.find( (descriptor) => descriptor.id === "agent" && descriptor.type === "select", ); assert.ok(agentDescriptor && agentDescriptor.type === "select"); assert.equal(agentDescriptor.options.find((option) => option.isDefault)?.id, "build"); }), );}); it.layer( makeTestLayer({ providers: { opencode: { serverUrl: "http://127.0.0.1:9999", serverPassword: "secret-password", }, }, }),)("OpenCodeProviderLive with configured server URL", (it) => { it.effect("surfaces a friendly auth error for configured servers", () => Effect.gen(function* () { runtimeMock.state.inventoryError = new Error("401 Unauthorized"); const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; assert.equal(snapshot.status, "error"); assert.equal(snapshot.installed, true); assert.equal( snapshot.message, "OpenCode server rejected authentication. Check the server URL and password.", ); }), ); it.effect("surfaces a friendly connection error for configured servers", () => Effect.gen(function* () { runtimeMock.state.inventoryError = new Error( "fetch failed: connect ECONNREFUSED 127.0.0.1:9999", ); const provider = yield* OpenCodeProvider; const snapshot = yield* provider.refresh; assert.equal(snapshot.status, "error"); assert.equal(snapshot.installed, true); assert.equal( snapshot.message, "Couldn't reach the configured OpenCode server at http://127.0.0.1:9999. Check that the server is running and the URL is correct.", ); }), );});
import assert from "node:assert/strict";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { it } from "@effect/vitest";
import { Effect, Layer } from "effect";
import { beforeEach } from "vitest";
import { ServerConfig } from "../../config.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { OpenCodeProvider } from "../Services/OpenCodeProvider.ts";
import {
OpenCodeRuntime,
OpenCodeRuntimeError,
type OpenCodeRuntimeShape,
} from "../opencodeRuntime.ts";
import { OpenCodeProviderLive } from "./OpenCodeProvider.ts";
import type { OpenCodeInventory } from "../opencodeRuntime.ts";
const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n";
const runtimeMock = {
state: {
runVersionError: null as Error | null,
versionStdout: DEFAULT_VERSION_STDOUT,
inventoryError: null as Error | null,
inventory: {
providerList: { connected: [] as string[], all: [] as unknown[], default: {} },
agents: [] as unknown[],
} as unknown,
},
reset() {
this.state.runVersionError = null;
this.state.versionStdout = DEFAULT_VERSION_STDOUT;
this.state.inventoryError = null;
this.state.inventory = {
providerList: { connected: [], all: [] as unknown[], default: {} },
agents: [] as unknown[],
};
},
};
const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = {
startOpenCodeServerProcess: () =>
Effect.succeed({
url: "http://127.0.0.1:4301",
exitCode: Effect.never,
}),
connectToOpenCodeServer: ({ serverUrl }) =>
Effect.succeed({
url: serverUrl ?? "http://127.0.0.1:4301",
exitCode: null,
external: Boolean(serverUrl),
}),
runOpenCodeCommand: () =>
runtimeMock.state.runVersionError
? Effect.fail(
new OpenCodeRuntimeError({
operation: "runOpenCodeCommand",
detail: runtimeMock.state.runVersionError.message,
cause: runtimeMock.state.runVersionError,
}),
)
: Effect.succeed({ stdout: runtimeMock.state.versionStdout, stderr: "", code: 0 }),
createOpenCodeSdkClient: () =>
({}) as unknown as ReturnType<OpenCodeRuntimeShape["createOpenCodeSdkClient"]>,
loadOpenCodeInventory: () =>
runtimeMock.state.inventoryError
? Effect.fail(
new OpenCodeRuntimeError({
operation: "loadOpenCodeInventory",
detail: runtimeMock.state.inventoryError.message,
cause: runtimeMock.state.inventoryError,
}),
)
: Effect.succeed(runtimeMock.state.inventory as OpenCodeInventory),
};
beforeEach(() => {
runtimeMock.reset();
});
const makeTestLayer = (settingsOverrides?: Parameters<typeof ServerSettingsService.layerTest>[0]) =>
OpenCodeProviderLive.pipe(
Layer.provideMerge(Layer.succeed(OpenCodeRuntime, OpenCodeRuntimeTestDouble)),
Layer.provideMerge(ServerConfig.layerTest(process.cwd(), process.cwd())),
Layer.provideMerge(ServerSettingsService.layerTest(settingsOverrides)),
Layer.provideMerge(NodeServices.layer),
);
it.layer(makeTestLayer())("OpenCodeProviderLive", (it) => {
it.effect("shows a codex-style missing binary message", () =>
Effect.gen(function* () {
runtimeMock.state.runVersionError = new Error("spawn opencode ENOENT");
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
assert.equal(snapshot.status, "error");
assert.equal(snapshot.installed, false);
assert.equal(snapshot.message, "OpenCode CLI (`opencode`) is not installed or not on PATH.");
}),
);
it.effect("hides generic Effect.tryPromise text for local CLI probe failures", () =>
Effect.gen(function* () {
runtimeMock.state.runVersionError = new Error("An error occurred in Effect.tryPromise");
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
assert.equal(snapshot.status, "error");
assert.equal(snapshot.installed, true);
assert.equal(snapshot.message, "Failed to execute OpenCode CLI health check.");
}),
);
it.effect("refuses to probe when opencode is older than the required minimum", () =>
Effect.gen(function* () {
runtimeMock.state.versionStdout = "opencode 1.4.7\n";
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
assert.equal(snapshot.status, "error");
assert.equal(snapshot.installed, true);
assert.equal(snapshot.version, "1.4.7");
assert.ok(snapshot.message?.includes("1.14.19"));
assert.ok(snapshot.message?.toLowerCase().includes("upgrade"));
}),
);
it.effect("refuses to probe when opencode --version output is unparseable", () =>
Effect.gen(function* () {
runtimeMock.state.versionStdout = "garbled binary output\n";
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
assert.equal(snapshot.status, "error");
assert.equal(snapshot.installed, true);
assert.equal(snapshot.version, null);
assert.ok(snapshot.message?.includes("1.14.19"));
}),
);
it.effect("emits OpenCode variant defaults so trait picker can resolve a visible selection", () =>
Effect.gen(function* () {
runtimeMock.state.inventory = {
providerList: {
connected: ["openai"],
all: [
{
id: "openai",
name: "OpenAI",
models: {
"gpt-5.4": {
id: "gpt-5.4",
name: "GPT-5.4",
variants: {
none: {},
low: {},
medium: {},
high: {},
xhigh: {},
},
},
},
},
],
default: {},
},
agents: [
{ name: "build", hidden: false, mode: "primary" },
{ name: "plan", hidden: false, mode: "primary" },
],
};
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
const model = snapshot.models.find((entry) => entry.slug === "openai/gpt-5.4");
assert.ok(model);
const variantDescriptor = model.capabilities?.optionDescriptors?.find(
(descriptor) => descriptor.id === "variant" && descriptor.type === "select",
);
assert.ok(variantDescriptor && variantDescriptor.type === "select");
assert.equal(variantDescriptor.options.find((option) => option.isDefault)?.id, "medium");
const agentDescriptor = model.capabilities?.optionDescriptors?.find(
(descriptor) => descriptor.id === "agent" && descriptor.type === "select",
);
assert.ok(agentDescriptor && agentDescriptor.type === "select");
assert.equal(agentDescriptor.options.find((option) => option.isDefault)?.id, "build");
}),
);
});
it.layer(
makeTestLayer({
providers: {
opencode: {
serverUrl: "http://127.0.0.1:9999",
serverPassword: "secret-password",
},
},
}),
)("OpenCodeProviderLive with configured server URL", (it) => {
it.effect("surfaces a friendly auth error for configured servers", () =>
Effect.gen(function* () {
runtimeMock.state.inventoryError = new Error("401 Unauthorized");
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
assert.equal(snapshot.status, "error");
assert.equal(snapshot.installed, true);
assert.equal(
snapshot.message,
"OpenCode server rejected authentication. Check the server URL and password.",
);
}),
);
it.effect("surfaces a friendly connection error for configured servers", () =>
Effect.gen(function* () {
runtimeMock.state.inventoryError = new Error(
"fetch failed: connect ECONNREFUSED 127.0.0.1:9999",
);
const provider = yield* OpenCodeProvider;
const snapshot = yield* provider.refresh;
assert.equal(snapshot.status, "error");
assert.equal(snapshot.installed, true);
assert.equal(
snapshot.message,
"Couldn't reach the configured OpenCode server at http://127.0.0.1:9999. Check that the server is running and the URL is correct.",
);
}),
);
});