import { describe, expect, test } from "bun:test" import type { McpServer, SessionConfigOption } from "@agentclientprotocol/sdk" import { makeACPFixture, makeSession, secondModel } from "./service-fixture" describe("acp service directory behavior", () => { test("creates sessions from a catalog shared by concurrent callers in the same cwd", async () => { let created = 0 await using fixture = makeACPFixture({ fetch(request) { if (request.method !== "POST" || request.path !== "/api/session") return undefined created++ return Response.json({ data: makeSession(`ses_${created}`, { cwd: created <= 2 ? "/workspace" : "/other" }), }) }, }) const first = await Promise.all([ fixture.service.newSession({ cwd: "/workspace", mcpServers: [] }), fixture.service.newSession({ cwd: "/workspace", mcpServers: [] }), ]) const other = await fixture.service.newSession({ cwd: "/other", mcpServers: [] }) expect(first.map((session) => session.sessionId).toSorted()).toEqual(["ses_1", "ses_2"]) expect(other.sessionId).toBe("ses_3") expect(currentValue(first[0], "model")).toBe("test/test-model") expect(currentValue(first[0], "mode")).toBe("build") expect( ["/api/model", "/api/model/default", "/api/agent", "/api/command", "/api/skill"].map((path) => fixture.requests .filter((request) => request.path === path) .map((request) => request.query["location[directory]"]), ), ).toEqual([ ["/workspace", "/other"], ["/workspace", "/other"], ["/workspace", "/other"], ["/workspace", "/other"], ["/workspace", "/other"], ]) expect( fixture.requests .filter((request) => request.method === "POST" && request.path === "/api/session") .map((request) => request.body), ).toEqual([ { agent: "build", model: { providerID: "test", id: "test-model", variant: "default" }, location: { directory: "/workspace" }, }, { agent: "build", model: { providerID: "test", id: "test-model", variant: "default" }, location: { directory: "/workspace" }, }, { agent: "build", model: { providerID: "test", id: "test-model", variant: "default" }, location: { directory: "/other" }, }, ]) expect( fixture.updates.map((item) => item.update.sessionUpdate === "available_commands_update" ? item.update.availableCommands.map((command) => command.name) : [], ), ).toEqual([ ["review", "verify"], ["review", "verify"], ["review", "verify"], ]) }) test("does not cache a failed catalog load", async () => { let modelCalls = 0 await using fixture = makeACPFixture({ fetch(request) { if (request.path === "/api/model") { modelCalls++ if (modelCalls === 1) { return Response.json( { name: "ModelsNotReadyError", data: { message: "catalog is warming" } }, { status: 503 }, ) } } if (request.method === "POST" && request.path === "/api/session") { return Response.json({ data: makeSession("ses_retry") }) } return undefined }, }) const failure = await fixture.service .newSession({ cwd: "/workspace", mcpServers: [] }) .catch((error: unknown) => error) expect(failure).toMatchObject({ name: "ModelsNotReadyError" }) const retried = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] }) expect(retried.sessionId).toBe("ses_retry") expect(modelCalls).toBe(2) }) test("switches model, effort, and mode against the warm catalog", async () => { await using fixture = makeACPFixture({ fetch(request) { if (request.method === "POST" && request.path === "/api/session") { return Response.json({ data: makeSession("ses_config") }) } if ( request.method === "POST" && (request.path === "/api/session/ses_config/model" || request.path === "/api/session/ses_config/agent") ) { return new Response(null, { status: 204 }) } return undefined }, }) const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] }) const selectedModel = await fixture.service.setSessionConfigOption({ sessionId: session.sessionId, configId: "model", value: "test/second-model", }) const selectedEffort = await fixture.service.setSessionConfigOption({ sessionId: session.sessionId, configId: "effort", value: "medium", }) const selectedMode = await fixture.service.setSessionConfigOption({ sessionId: session.sessionId, configId: "mode", value: "plan", }) await fixture.service.setSessionMode({ sessionId: session.sessionId, modeId: "build" }) expect(currentValue(selectedModel, "model")).toBe("test/second-model") expect(currentValue(selectedModel, "effort")).toBe("low") expect(currentValue(selectedEffort, "effort")).toBe("medium") expect(currentValue(selectedMode, "mode")).toBe("plan") expect( fixture.requests .filter((request) => request.path === "/api/session/ses_config/model") .map((request) => request.body), ).toEqual([ { model: { providerID: "test", id: secondModel.id } }, { model: { providerID: "test", id: secondModel.id, variant: "medium" } }, ]) expect( fixture.requests .filter((request) => request.path === "/api/session/ses_config/agent") .map((request) => request.body), ).toEqual([{ agent: "plan" }, { agent: "build" }]) expect(fixture.requests.filter((request) => request.path === "/api/model")).toHaveLength(1) const invalidEffort = await fixture.service .setSessionConfigOption({ sessionId: session.sessionId, configId: "effort", value: "maximum", }) .catch((error: unknown) => error) const invalidMode = await fixture.service .setSessionConfigOption({ sessionId: session.sessionId, configId: "mode", value: "missing", }) .catch((error: unknown) => error) const invalidConfig = await fixture.service .setSessionConfigOption({ sessionId: session.sessionId, configId: "missing", value: "value", }) .catch((error: unknown) => error) expect(invalidEffort).toMatchObject({ _tag: "ACPInvalidEffortError" }) expect(invalidMode).toMatchObject({ _tag: "ACPInvalidModeError" }) expect(invalidConfig).toMatchObject({ _tag: "ACPInvalidConfigOptionError" }) }) test("converts MCP configs and deduplicates registrations per session and config", async () => { const local: McpServer = { name: "tools", command: "bun", args: ["server.ts"], env: [{ name: "TOKEN", value: "x" }], } const changed: McpServer = { ...local, args: ["changed.ts"] } const remote: McpServer = { type: "http", name: "docs", url: "https://example.com/mcp", headers: [{ name: "Authorization", value: "Bearer x" }], } let created = 0 await using fixture = makeACPFixture({ fetch(request) { if (request.method === "POST" && request.path === "/api/session") { created++ return Response.json({ data: makeSession(`ses_${created}`) }) } if (request.method === "GET" && request.path === "/api/session/ses_1") { return Response.json({ data: makeSession("ses_1") }) } if (request.method === "PUT" && request.path.startsWith("/api/mcp/")) { return new Response(null, { status: 204 }) } return undefined }, }) await fixture.service.newSession({ cwd: "/workspace", mcpServers: [local, local, remote] }) await fixture.service.resumeSession({ cwd: "/workspace", sessionId: "ses_1", mcpServers: [local, remote] }) await fixture.service.resumeSession({ cwd: "/workspace", sessionId: "ses_1", mcpServers: [changed] }) await fixture.service.newSession({ cwd: "/workspace", mcpServers: [local] }) const adds = fixture.requests.filter((request) => request.method === "PUT" && request.path.startsWith("/api/mcp/")) expect(adds).toHaveLength(4) expect(adds.filter((request) => request.path === "/api/mcp/tools").map((request) => request.body)).toEqual([ { config: { type: "local", command: ["bun", "server.ts"], environment: { TOKEN: "x" }, }, }, { config: { type: "local", command: ["bun", "changed.ts"], environment: { TOKEN: "x" }, }, }, { config: { type: "local", command: ["bun", "server.ts"], environment: { TOKEN: "x" }, }, }, ]) expect(adds.find((request) => request.path === "/api/mcp/docs")?.body).toEqual({ config: { type: "remote", url: "https://example.com/mcp", headers: { Authorization: "Bearer x" }, oauth: false, }, }) expect(adds.map((request) => request.query)).toEqual([ { "location[directory]": "/workspace" }, { "location[directory]": "/workspace" }, { "location[directory]": "/workspace" }, { "location[directory]": "/workspace" }, ]) }) }) function currentValue( result: { readonly configOptions?: readonly SessionConfigOption[] | null } | undefined, id: string, ) { return result?.configOptions?.find((option) => option.id === id)?.currentValue }