import * as NodeAssert from "node:assert/strict"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"; import { it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as Queue from "effect/Queue"; import { HostProcessEnvironment, HostProcessExecutablePath, HostProcessPlatform, } from "@t3tools/shared/hostProcess"; import { OpenCodeRuntime, OpenCodeRuntimeLive } from "./opencodeRuntime.ts"; const testLayer = OpenCodeRuntimeLive.pipe(Layer.provideMerge(NodeServices.layer)); it.layer(testLayer)("OpenCodeRuntime inventory", (it) => { it.effect("aborts pending SDK requests when inventory loading is interrupted", () => Effect.gen(function* () { const runtime = yield* OpenCodeRuntime; const started = yield* Queue.make(); const aborted = yield* Queue.make(); const client = createOpencodeClient({ baseUrl: "http://opencode.test", fetch: Object.assign( (input: string | Request | URL) => { const request = input instanceof Request ? input : new Request(input.toString()); return new Promise((_resolve, reject) => { request.signal.addEventListener( "abort", () => { Queue.offerUnsafe(aborted, new URL(request.url).pathname); reject(request.signal.reason); }, { once: true }, ); Queue.offerUnsafe(started, undefined); }); }, { preconnect: () => undefined }, ), }); const inventoryFiber = yield* runtime.loadOpenCodeInventory(client).pipe(Effect.forkChild); yield* Queue.takeN(started, 3); yield* Fiber.interrupt(inventoryFiber); NodeAssert.deepEqual((yield* Queue.takeAll(aborted)).toSorted(), [ "/agent", "/provider", "/skill", ]); }), ); it.effect("keeps provider inventory when agent discovery fails", () => Effect.gen(function* () { const runtime = yield* OpenCodeRuntime; const client = { provider: { list: () => Promise.resolve({ data: { connected: ["openai"], all: [], default: {}, }, }), }, app: { agents: () => Promise.reject(new Error("agents endpoint unavailable")), skills: () => Promise.resolve({ data: [] }), }, } as unknown as OpencodeClient; const inventory = yield* runtime.loadOpenCodeInventory(client); NodeAssert.deepEqual(inventory.providerList.connected, ["openai"]); NodeAssert.deepEqual(inventory.agents, []); NodeAssert.deepEqual(inventory.skills, []); }), ); it.effect("keeps provider inventory when skill discovery fails", () => Effect.gen(function* () { const runtime = yield* OpenCodeRuntime; const client = { provider: { list: () => Promise.resolve({ data: { connected: ["openai"], all: [], default: {}, }, }), }, app: { agents: () => Promise.resolve({ data: [] }), skills: () => Promise.reject(new Error("skills endpoint unavailable")), }, } as unknown as OpencodeClient; const inventory = yield* runtime.loadOpenCodeInventory(client); NodeAssert.deepEqual(inventory.providerList.connected, ["openai"]); NodeAssert.deepEqual(inventory.agents, []); NodeAssert.deepEqual(inventory.skills, []); }), ); it.effect("keeps only SDK skill metadata in inventory", () => Effect.gen(function* () { const runtime = yield* OpenCodeRuntime; const client = { provider: { list: () => Promise.resolve({ data: { connected: ["openai"], all: [], default: {}, }, }), }, app: { agents: () => Promise.resolve({ data: [] }), skills: () => Promise.resolve({ data: [ { name: "review", description: "Review code changes", location: "/skills/review/SKILL.md", content: "unused skill content", }, ], }), }, } as unknown as OpencodeClient; const inventory = yield* runtime.loadOpenCodeInventory(client); NodeAssert.deepEqual(inventory.skills, [ { name: "review", description: "Review code changes", location: "/skills/review/SKILL.md", }, ]); }), ); it.effect("drops oversized CLI skill output without losing the model inventory", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const hostEnvironment = yield* HostProcessEnvironment; const executablePath = yield* HostProcessExecutablePath; const hostPlatform = yield* HostProcessPlatform; const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-opencode-inventory-" }); const isWindows = hostPlatform === "win32"; const binaryPath = path.join(tempDir, isWindows ? "opencode.cmd" : "opencode"); const scriptPath = path.join(tempDir, "opencode.mjs"); const oversizedContentBytes = 8 * 1024 * 1024 + 1; yield* fs.writeFileString( scriptPath, [ 'if (process.argv[2] === "models") {', ' process.stdout.write(`openai/gpt-test\\n{"id":"gpt-test","providerID":"openai","name":"GPT Test"}\\n`);', '} else if (process.argv[2] === "debug") {', ` const content = "x".repeat(${oversizedContentBytes});`, ' process.stdout.write(`[{"name":"oversized","content":"${content}"}]`);', "}", "", ].join("\n"), ); yield* fs.writeFileString( binaryPath, [ ...(isWindows ? ["@echo off"] : ["#!/bin/sh"]), isWindows ? '"%T3_TEST_NODE_BINARY%" "%T3_TEST_OPENCODE_SCRIPT%" %*' : 'exec "$T3_TEST_NODE_BINARY" "$T3_TEST_OPENCODE_SCRIPT" "$@"', "", ].join("\n"), ); if (!isWindows) { yield* fs.chmod(binaryPath, 0o755); } const runtime = yield* OpenCodeRuntime; const inventory = yield* runtime.loadInventoryFromCli({ binaryPath, cwd: tempDir, environment: { ...hostEnvironment, T3_TEST_NODE_BINARY: executablePath, T3_TEST_OPENCODE_SCRIPT: scriptPath, }, }); NodeAssert.deepEqual(inventory.providerList.connected, ["openai"]); NodeAssert.equal(inventory.skills.length, 0); }), ); it.effect("caps and drains command stdout and stderr when requested", () => Effect.gen(function* () { const runtime = yield* OpenCodeRuntime; const executablePath = yield* HostProcessExecutablePath; const outputBytes = 2 * 1024 * 1024; const result = yield* runtime.runOpenCodeCommand({ binaryPath: executablePath, args: [ "-e", `process.stdout.write("o".repeat(${outputBytes})); process.stderr.write("e".repeat(${outputBytes}));`, ], maxOutputBytes: 64, }); NodeAssert.equal(result.stdout, "o".repeat(64)); NodeAssert.equal(result.stderr, "e".repeat(64)); NodeAssert.equal(result.code, 0); }), ); });