Open History to see the last commit
1import * as NodeAssert from "node:assert/strict";
2
3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2";
5import { it } from "@effect/vitest";
6import * as Effect from "effect/Effect";
7import * as Fiber from "effect/Fiber";
8import * as Layer from "effect/Layer";
9import * as Queue from "effect/Queue";
10
11import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
12import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts";
13
14const layerTest = OpenCodeRuntime.layer.pipe(
15 Layer.provide(OpenCodeServerLedger.layerTest),
16 Layer.provideMerge(NodeServices.layer),
17);
18
19it.layer(layerTest)("OpenCodeRuntime inventory", (it) => {
20 it.effect("aborts pending SDK requests when inventory loading is interrupted", () =>
21 Effect.gen(function* () {
22 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;
23 const started = yield* Queue.make<void>();
24 const aborted = yield* Queue.make<string>();
25 const client = createOpencodeClient({
26 baseUrl: "http://opencode.test",
27 fetch: Object.assign(
28 (input: string | Request | URL) => {
29 const request = input instanceof Request ? input : new Request(input.toString());
30 return new Promise<Response>((_resolve, reject) => {
31 request.signal.addEventListener(
32 "abort",
33 () => {
34 Queue.offerUnsafe(aborted, new URL(request.url).pathname);
35 reject(request.signal.reason);
36 },
37 { once: true },
38 );
39 Queue.offerUnsafe(started, undefined);
40 });
41 },
42 { preconnect: () => undefined },
43 ),
44 });
45
46 const inventoryFiber = yield* runtime.loadOpenCodeInventory(client).pipe(Effect.forkChild);
47 yield* Queue.takeN(started, 4);
48 yield* Fiber.interrupt(inventoryFiber);
49
50 NodeAssert.deepEqual((yield* Queue.takeAll(aborted)).toSorted(), [
51 "/agent",
52 "/command",
53 "/provider",
54 "/skill",
55 ]);
56 }),
57 );
58
59 it.effect("discovers directory-scoped commands without retaining prompt templates", () =>
60 Effect.gen(function* () {
61 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;
62 const requests: Request[] = [];
63 const client = createOpencodeClient({
64 baseUrl: "http://opencode.test",
65 directory: "/workspace/project",
66 fetch: Object.assign(
67 async (input: string | Request | URL) => {
68 const request = input instanceof Request ? input : new Request(input.toString());
69 requests.push(request);
70 const route = new URL(request.url).pathname;
71 return Response.json(
72 route === "/provider"
73 ? { connected: ["openai"], all: [], default: {} }
74 : route === "/command"
75 ? [
76 {
77 name: "review",
78 description: "Review changes",
79 source: "command",
80 hints: ["$ARGUMENTS"],
81 template: "private native template",
82 },
83 ]
84 : [],
85 );
86 },
87 { preconnect: () => undefined },
88 ),
89 });
90 const inventory = yield* runtime.loadOpenCodeInventory(client);
91 NodeAssert.deepEqual(inventory.commands, [
92 {
93 name: "review",
94 description: "Review changes",
95 source: "command",
96 hints: ["$ARGUMENTS"],
97 },
98 ]);
99 const commandRequest = requests.find(
100 (request) => new URL(request.url).pathname === "/command",
101 );
102 NodeAssert.ok(commandRequest);
103 NodeAssert.equal(
104 new URL(commandRequest.url).searchParams.get("directory") ??
105 decodeURIComponent(commandRequest.headers.get("x-opencode-directory") ?? ""),
106 "/workspace/project",
107 );
108 }),
109 );
110
111 it.effect("keeps provider inventory when agent discovery fails", () =>
112 Effect.gen(function* () {
113 const runtime = yield* OpenCodeRuntime.OpenCodeRuntime;
114 const client = {
115 provider: {
116 list: () =>
117 Promise.resolve({
118 data: {
119 connected: ["openai"],
120 all: [],
121 default: {},
122 },
123 }),
124 },
125 app: {
126 agents: () => Promise.reject(new Error("agents endpoint unavailable")),
127 skills: () => Promise.resolve({ data: [] }),
128 },
129 } as unknown as OpencodeClient;
130
131 const inventory = yield* runtime.loadOpenCodeInventory(client);
132
133 NodeAssert.deepEqual(inventory.providerList.connected, ["openai"]);
134 NodeAssert.deepEqual(inventory.agents, []);
135 NodeAssert.deepEqual(inventory.skills, []);
136 }),
137 );
138});