import { afterAll, beforeAll, expect, test } from "bun:test"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createOpencodeClient } from "@opencode-ai/sdk"; import type { Config, PluginOptions } from "@opencode-ai/plugin"; import plugin from "../src/index.js"; import { z } from "zod"; import { gptAgents } from "../src/config.js"; let directory: string; const originalModels = process.env.OPENCODE_MODELS_PATH; const originalAuth = process.env.OPENCODE_AUTH_CONTENT; beforeAll(async () => { directory = await mkdtemp(join(tmpdir(), "stanp-tests-")); const path = join(directory, "models.json"); await writeFile( path, JSON.stringify({ test: { id: "test", env: [], npm: "@ai-sdk/openai-compatible", models: Object.fromEntries( ["gpt-5.6-luna", "gpt-5.6-sol", "gpt-6-astra"].map((id) => [id, { id }]), ), }, }), ); process.env.OPENCODE_MODELS_PATH = path; process.env.OPENCODE_AUTH_CONTENT = "{}"; }); afterAll(async () => { if (originalModels === undefined) delete process.env.OPENCODE_MODELS_PATH; else process.env.OPENCODE_MODELS_PATH = originalModels; if (originalAuth === undefined) delete process.env.OPENCODE_AUTH_CONTENT; else process.env.OPENCODE_AUTH_CONTENT = originalAuth; await rm(directory, { recursive: true, force: true }); }); async function hooks(caller = "build", options: PluginOptions = { provider: "test" }) { return plugin.server( { client: createOpencodeClient({ baseUrl: "http://localhost:1", fetch: async (request) => Response.json( request.url.endsWith("/user") ? { info: { role: "user", agent: caller }, parts: [] } : [ { info: { role: "assistant", mode: "general", parentID: "user", }, parts: [], }, ], ), }), project: { id: "test", worktree: directory, time: { created: 0 } }, directory, worktree: directory, serverUrl: new URL("http://localhost:1"), experimental_workspace: { register() {} }, $: Bun.$, }, options, ); } test("registration is atomic; only missing Astra degrades to four workers", async () => { const full: Config = { provider: { test: {} } }; await (await hooks()).config(full); // The host's /agent endpoint rejects undefined options rather than JSON-stringifying them. z.json().parse(full.agent); expect(Object.keys(full.agent ?? {})).toEqual([ "fast-lo", "fast-hi", "good-lo", "good-hi", "deep-lo", "deep-hi", "coordinate", ]); const reduced: Config = { provider: { test: { blacklist: ["gpt-6-astra"] } } }; await (await hooks()).config(reduced); expect(Object.keys(reduced.agent ?? {})).toEqual([ "fast-lo", "fast-hi", "good-lo", "good-hi", "coordinate", ]); expect(reduced.agent?.coordinate?.model).toBe("test/gpt-5.6-sol"); for (const missing of ["gpt-5.6-luna", "gpt-5.6-sol"]) { const config: Config = { provider: { test: { blacklist: [missing] } } }; await (await hooks()).config(config); expect(config.agent).toBeUndefined(); } }); test("disconnected and explicitly disabled providers register nothing", async () => { const disconnected: Config = {}; await (await hooks()).config(disconnected); expect(disconnected.agent).toBeUndefined(); const disabled: Config = { provider: { test: {} }, disabled_providers: ["test"] }; await (await hooks()).config(disabled); expect(disabled.agent).toBeUndefined(); }); test("manual task targets cannot bypass coordinate's primary-only restriction", async () => { const active = await hooks(); await active.config({ provider: { test: {} } }); await expect( active["tool.execute.before"]( { tool: "task", sessionID: "test", callID: "test" }, { args: { subagent_type: "coordinate", prompt: "implement" } }, ), ).rejects.toThrow("cannot be delegated to"); }); test("subtask bypass uses the parent user agent, not the synthetic assistant target", async () => { const coordinate = await hooks("coordinate"); await coordinate.config({ provider: { test: { blacklist: ["gpt-6-astra"] } } }); for (const target of ["general", "deep-lo"]) { await expect( coordinate["tool.execute.before"]( { tool: "task", sessionID: "test", callID: "test" }, { args: { subagent_type: target } }, ), ).rejects.toThrow("only to its available workers"); } const worker = await hooks("fast-hi"); await worker.config({ provider: { test: {} } }); await expect( worker["tool.execute.before"]( { tool: "task", sessionID: "test", callID: "test" }, { args: { subagent_type: "good-lo" } }, ), ).rejects.toThrow("Workers cannot delegate"); }); test("custom assignments do not silently take the GPT fallback", async () => { const custom = await hooks("build", { mode: "custom", provider: "test", agents: { ...gptAgents, coordinate: { model: "local", effort: "low" } }, }); const config: Config = { provider: { test: { models: { local: { name: "Local" } } } } }; await custom.config(config); expect(config.agent?.coordinate?.model).toBe("test/local"); expect(config.agent?.coordinate?.options).toEqual({ reasoningEffort: "low" }); const missing: Config = { provider: { test: { blacklist: ["gpt-6-astra"], models: { local: {} } } }, }; await custom.config(missing); expect(missing.agent).toBeUndefined(); }); test("tier selection requires evidence and preserves the advertised fast tier", async () => { const namedFast = { name: "Luna", variants: { fast: { reasoningEffort: "low" } } }; const ordinary: Config = { provider: { test: { models: { "gpt-5.6-luna": namedFast } } } }; await (await hooks()).config(ordinary); expect(ordinary.agent?.["fast-lo"]?.options).toEqual({ reasoningEffort: "low" }); const advertised: Config = { provider: { test: { models: { "gpt-5.6-luna": { options: { serviceTier: "fast" } } }, }, }, }; await (await hooks()).config(advertised); expect(advertised.agent?.["fast-lo"]?.options).toEqual({ reasoningEffort: "low", serviceTier: "fast", }); });