// @effect-diagnostics nodeBuiltinImport:offimport * as NodePath from "node:path";import * as NodeOS from "node:os";import * as NodeURL from "node:url";import * as NodeFS from "node:fs"; import * as NodeServices from "@effect/platform-node/NodeServices";import { it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as Option from "effect/Option";import * as TestClock from "effect/testing/TestClock";import * as Stream from "effect/Stream";import { describe, expect } from "vite-plus/test"; import * as AcpSessionRuntime from "./AcpSessionRuntime.ts";import type * as EffectAcpProtocol from "effect-acp/protocol"; const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");const mockAgentCommand = "node";const mockAgentArgs = [mockAgentPath]; describe("AcpSessionRuntime", () => { it.effect("merges custom initialize client capabilities into the ACP handshake", () => { const requestEvents: Array<AcpSessionRuntime.AcpSessionRequestLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const initializeStarted = requestEvents.find( (event) => event.method === "initialize" && event.status === "started", ); expect(initializeStarted?.payload).toMatchObject({ protocolVersion: 1, clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, _meta: { parameterizedModelPicker: true }, }, }); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientCapabilities: { _meta: { parameterizedModelPicker: true, }, }, clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", requestLogger: (event) => Effect.sync(() => { requestEvents.push(event); }), }), ), Effect.scoped, Effect.provide(NodeServices.layer), ); }); it.effect("starts a session, prompts, and emits normalized events against the mock agent", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; const started = yield* runtime.start(); expect(started.initializeResult).toMatchObject({ protocolVersion: 1 }); expect(started.sessionId).toBe("mock-session-1"); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 4))); expect(notes).toHaveLength(4); expect(notes.map((note) => note._tag)).toEqual([ "PlanUpdated", "AssistantItemStarted", "ContentDelta", "AssistantItemCompleted", ]); const planUpdate = notes.find((note) => note._tag === "PlanUpdated"); expect(planUpdate?._tag).toBe("PlanUpdated"); if (planUpdate?._tag === "PlanUpdated") { expect(planUpdate.payload.plan).toHaveLength(2); } const assistantStart = notes[1]; const assistantDelta = notes[2]; if ( assistantStart?._tag === "AssistantItemStarted" && assistantDelta?._tag === "ContentDelta" ) { expect(assistantDelta.itemId).toBe(assistantStart.itemId); } }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("keeps assistant item IDs unique when a provider session restarts", () => { const collectFirstAssistantItemId = Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; const started = yield* runtime.start(); expect(started.sessionId).toBe("mock-session-1"); yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); const events = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 4))); const assistantStart = events.find((event) => event._tag === "AssistantItemStarted"); expect(assistantStart?._tag).toBe("AssistantItemStarted"); return assistantStart?._tag === "AssistantItemStarted" ? assistantStart.itemId : ""; }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, ); return Effect.gen(function* () { const beforeRestart = yield* collectFirstAssistantItemId; const afterRestart = yield* collectFirstAssistantItemId; expect(afterRestart).not.toBe(beforeRestart); }).pipe(Effect.provide(NodeServices.layer)); }); it.effect("drops session updates emitted for a child ACP session", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 4))); expect(notes.map((note) => note._tag)).toEqual([ "AssistantItemStarted", "ContentDelta", "ContentDelta", "AssistantItemCompleted", ]); expect( notes .filter((note) => note._tag === "ContentDelta") .map((note) => note.text) .join(""), ).toBe("root before child root after child"); expect(notes.some((note) => note._tag === "ToolCallUpdated")).toBe(false); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_EMIT_FOREIGN_SESSION_UPDATES: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("supports successive standard ACP prompts", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const firstPromptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "first" }], }); const secondPromptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "second" }], }); expect(firstPromptResult).toMatchObject({ stopReason: "end_turn" }); expect(secondPromptResult).toMatchObject({ stopReason: "end_turn" }); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("releases a fully silent prompt when session/cancel is requested", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptFiber = yield* runtime .prompt({ prompt: [{ type: "text", text: "hang forever" }], }) .pipe(Effect.forkChild({ startImmediately: true })); yield* TestClock.adjust("500 millis"); yield* runtime.cancel; const firstPromptResult = yield* Fiber.join(promptFiber); expect(firstPromptResult).toMatchObject({ stopReason: "cancelled" }); const secondPromptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "second" }], }); expect(secondPromptResult).toMatchObject({ stopReason: "end_turn" }); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_HANG_FIRST_PROMPT_FOREVER: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("segments assistant text around ACP tool calls", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 7))); expect(notes.map((note) => note._tag)).toEqual([ "AssistantItemStarted", "ContentDelta", "AssistantItemCompleted", "ToolCallUpdated", "ToolCallUpdated", "AssistantItemStarted", "ContentDelta", ]); const firstStarted = notes[0]; const firstDelta = notes[1]; const firstCompleted = notes[2]; const secondStarted = notes[5]; const secondDelta = notes[6]; expect(firstStarted?._tag).toBe("AssistantItemStarted"); expect(firstCompleted?._tag).toBe("AssistantItemCompleted"); expect(secondStarted?._tag).toBe("AssistantItemStarted"); if ( firstStarted?._tag === "AssistantItemStarted" && firstDelta?._tag === "ContentDelta" && firstCompleted?._tag === "AssistantItemCompleted" && secondStarted?._tag === "AssistantItemStarted" && secondDelta?._tag === "ContentDelta" ) { expect(firstDelta.itemId).toBe(firstStarted.itemId); expect(firstCompleted.itemId).toBe(firstStarted.itemId); expect(secondStarted.itemId).not.toBe(firstStarted.itemId); expect(secondDelta.itemId).toBe(secondStarted.itemId); } }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_EMIT_INTERLEAVED_ASSISTANT_TOOL_CALLS: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("suppresses generic placeholder tool updates until completion", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 1))); expect(notes.map((note) => note._tag)).toEqual(["ToolCallUpdated"]); const toolCall = notes[0]; expect(toolCall?._tag).toBe("ToolCallUpdated"); if (toolCall?._tag === "ToolCallUpdated") { expect(toolCall.toolCall.status).toBe("completed"); expect(toolCall.toolCall.title).toBe("Read file"); } }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("logs ACP requests from the shared runtime", () => { const requestEvents: Array<AcpSessionRuntime.AcpSessionRequestLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); yield* runtime.setModel("composer-2"); yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect( requestEvents.some( (event) => event.method === "session/set_config_option" && event.status === "started", ), ).toBe(true); expect( requestEvents.some( (event) => event.method === "session/set_config_option" && event.status === "succeeded", ), ).toBe(true); expect( requestEvents.some( (event) => event.method === "session/prompt" && event.status === "started", ), ).toBe(true); expect( requestEvents.some( (event) => event.method === "session/prompt" && event.status === "succeeded", ), ).toBe(true); }).pipe( Effect.provide( AcpSessionRuntime.layer({ authMethodId: "test", spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, requestLogger: (event) => Effect.sync(() => { requestEvents.push(event); }), }), ), Effect.scoped, Effect.provide(NodeServices.layer), ); }); it.effect("skips no-op session config writes when the requested value is already active", () => { const requestEvents: Array<AcpSessionRuntime.AcpSessionRequestLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); yield* runtime.setConfigOption("model", "default"); yield* runtime.setMode("ask"); expect( requestEvents.some( (event) => event.method === "session/set_config_option" && event.status === "started", ), ).toBe(false); }).pipe( Effect.provide( AcpSessionRuntime.layer({ authMethodId: "test", spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, requestLogger: (event) => Effect.sync(() => { requestEvents.push(event); }), }), ), Effect.scoped, Effect.provide(NodeServices.layer), ); }); it.effect("emits low-level ACP protocol logs for raw and decoded messages", () => { const protocolEvents: Array<EffectAcpProtocol.AcpProtocolLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect( protocolEvents.some((event) => event.direction === "outgoing" && event.stage === "raw"), ).toBe(true); expect( protocolEvents.some((event) => event.direction === "outgoing" && event.stage === "decoded"), ).toBe(true); expect( protocolEvents.some((event) => event.direction === "incoming" && event.stage === "raw"), ).toBe(true); expect( protocolEvents.some((event) => event.direction === "incoming" && event.stage === "decoded"), ).toBe(true); }).pipe( Effect.provide( AcpSessionRuntime.layer({ authMethodId: "test", spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, protocolLogging: { logIncoming: true, logOutgoing: true, logger: (event) => Effect.sync(() => { protocolEvents.push(event); }), }, }), ), Effect.scoped,Lines 1–500 of 657. Load more
// @effect-diagnostics nodeBuiltinImport:off import * as NodePath from "node:path"; import * as NodeOS from "node:os"; import * as NodeURL from "node:url"; import * as NodeFS from "node:fs"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Option from "effect/Option"; import * as TestClock from "effect/testing/TestClock"; import * as Stream from "effect/Stream"; import { describe, expect } from "vite-plus/test"; import * as AcpSessionRuntime from "./AcpSessionRuntime.ts"; import type * as EffectAcpProtocol from "effect-acp/protocol"; const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)); const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts"); const mockAgentCommand = "node"; const mockAgentArgs = [mockAgentPath]; describe("AcpSessionRuntime", () => { it.effect("merges custom initialize client capabilities into the ACP handshake", () => { const requestEvents: Array<AcpSessionRuntime.AcpSessionRequestLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const initializeStarted = requestEvents.find( (event) => event.method === "initialize" && event.status === "started", ); expect(initializeStarted?.payload).toMatchObject({ protocolVersion: 1, clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, _meta: { parameterizedModelPicker: true }, }, }); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientCapabilities: { _meta: { parameterizedModelPicker: true, }, }, clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", requestLogger: (event) => Effect.sync(() => { requestEvents.push(event); }), }), ), Effect.scoped, Effect.provide(NodeServices.layer), ); }); it.effect("starts a session, prompts, and emits normalized events against the mock agent", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; const started = yield* runtime.start(); expect(started.initializeResult).toMatchObject({ protocolVersion: 1 }); expect(started.sessionId).toBe("mock-session-1"); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 4))); expect(notes).toHaveLength(4); expect(notes.map((note) => note._tag)).toEqual([ "PlanUpdated", "AssistantItemStarted", "ContentDelta", "AssistantItemCompleted", ]); const planUpdate = notes.find((note) => note._tag === "PlanUpdated"); expect(planUpdate?._tag).toBe("PlanUpdated"); if (planUpdate?._tag === "PlanUpdated") { expect(planUpdate.payload.plan).toHaveLength(2); } const assistantStart = notes[1]; const assistantDelta = notes[2]; if ( assistantStart?._tag === "AssistantItemStarted" && assistantDelta?._tag === "ContentDelta" ) { expect(assistantDelta.itemId).toBe(assistantStart.itemId); } }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("keeps assistant item IDs unique when a provider session restarts", () => { const collectFirstAssistantItemId = Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; const started = yield* runtime.start(); expect(started.sessionId).toBe("mock-session-1"); yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); const events = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 4))); const assistantStart = events.find((event) => event._tag === "AssistantItemStarted"); expect(assistantStart?._tag).toBe("AssistantItemStarted"); return assistantStart?._tag === "AssistantItemStarted" ? assistantStart.itemId : ""; }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, ); return Effect.gen(function* () { const beforeRestart = yield* collectFirstAssistantItemId; const afterRestart = yield* collectFirstAssistantItemId; expect(afterRestart).not.toBe(beforeRestart); }).pipe(Effect.provide(NodeServices.layer)); }); it.effect("drops session updates emitted for a child ACP session", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 4))); expect(notes.map((note) => note._tag)).toEqual([ "AssistantItemStarted", "ContentDelta", "ContentDelta", "AssistantItemCompleted", ]); expect( notes .filter((note) => note._tag === "ContentDelta") .map((note) => note.text) .join(""), ).toBe("root before child root after child"); expect(notes.some((note) => note._tag === "ToolCallUpdated")).toBe(false); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_EMIT_FOREIGN_SESSION_UPDATES: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("supports successive standard ACP prompts", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const firstPromptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "first" }], }); const secondPromptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "second" }], }); expect(firstPromptResult).toMatchObject({ stopReason: "end_turn" }); expect(secondPromptResult).toMatchObject({ stopReason: "end_turn" }); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("releases a fully silent prompt when session/cancel is requested", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptFiber = yield* runtime .prompt({ prompt: [{ type: "text", text: "hang forever" }], }) .pipe(Effect.forkChild({ startImmediately: true })); yield* TestClock.adjust("500 millis"); yield* runtime.cancel; const firstPromptResult = yield* Fiber.join(promptFiber); expect(firstPromptResult).toMatchObject({ stopReason: "cancelled" }); const secondPromptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "second" }], }); expect(secondPromptResult).toMatchObject({ stopReason: "end_turn" }); }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_HANG_FIRST_PROMPT_FOREVER: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("segments assistant text around ACP tool calls", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 7))); expect(notes.map((note) => note._tag)).toEqual([ "AssistantItemStarted", "ContentDelta", "AssistantItemCompleted", "ToolCallUpdated", "ToolCallUpdated", "AssistantItemStarted", "ContentDelta", ]); const firstStarted = notes[0]; const firstDelta = notes[1]; const firstCompleted = notes[2]; const secondStarted = notes[5]; const secondDelta = notes[6]; expect(firstStarted?._tag).toBe("AssistantItemStarted"); expect(firstCompleted?._tag).toBe("AssistantItemCompleted"); expect(secondStarted?._tag).toBe("AssistantItemStarted"); if ( firstStarted?._tag === "AssistantItemStarted" && firstDelta?._tag === "ContentDelta" && firstCompleted?._tag === "AssistantItemCompleted" && secondStarted?._tag === "AssistantItemStarted" && secondDelta?._tag === "ContentDelta" ) { expect(firstDelta.itemId).toBe(firstStarted.itemId); expect(firstCompleted.itemId).toBe(firstStarted.itemId); expect(secondStarted.itemId).not.toBe(firstStarted.itemId); expect(secondDelta.itemId).toBe(secondStarted.itemId); } }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_EMIT_INTERLEAVED_ASSISTANT_TOOL_CALLS: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("suppresses generic placeholder tool updates until completion", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); const promptResult = yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect(promptResult).toMatchObject({ stopReason: "end_turn" }); const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 1))); expect(notes.map((note) => note._tag)).toEqual(["ToolCallUpdated"]); const toolCall = notes[0]; expect(toolCall?._tag).toBe("ToolCallUpdated"); if (toolCall?._tag === "ToolCallUpdated") { expect(toolCall.toolCall.status).toBe("completed"); expect(toolCall.toolCall.title).toBe("Read file"); } }).pipe( Effect.provide( AcpSessionRuntime.layer({ spawn: { command: mockAgentCommand, args: mockAgentArgs, env: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1", }, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, authMethodId: "test", }), ), Effect.scoped, Effect.provide(NodeServices.layer), ), ); it.effect("logs ACP requests from the shared runtime", () => { const requestEvents: Array<AcpSessionRuntime.AcpSessionRequestLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); yield* runtime.setModel("composer-2"); yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect( requestEvents.some( (event) => event.method === "session/set_config_option" && event.status === "started", ), ).toBe(true); expect( requestEvents.some( (event) => event.method === "session/set_config_option" && event.status === "succeeded", ), ).toBe(true); expect( requestEvents.some( (event) => event.method === "session/prompt" && event.status === "started", ), ).toBe(true); expect( requestEvents.some( (event) => event.method === "session/prompt" && event.status === "succeeded", ), ).toBe(true); }).pipe( Effect.provide( AcpSessionRuntime.layer({ authMethodId: "test", spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, requestLogger: (event) => Effect.sync(() => { requestEvents.push(event); }), }), ), Effect.scoped, Effect.provide(NodeServices.layer), ); }); it.effect("skips no-op session config writes when the requested value is already active", () => { const requestEvents: Array<AcpSessionRuntime.AcpSessionRequestLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); yield* runtime.setConfigOption("model", "default"); yield* runtime.setMode("ask"); expect( requestEvents.some( (event) => event.method === "session/set_config_option" && event.status === "started", ), ).toBe(false); }).pipe( Effect.provide( AcpSessionRuntime.layer({ authMethodId: "test", spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, requestLogger: (event) => Effect.sync(() => { requestEvents.push(event); }), }), ), Effect.scoped, Effect.provide(NodeServices.layer), ); }); it.effect("emits low-level ACP protocol logs for raw and decoded messages", () => { const protocolEvents: Array<EffectAcpProtocol.AcpProtocolLogEvent> = []; return Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; yield* runtime.start(); yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }], }); expect( protocolEvents.some((event) => event.direction === "outgoing" && event.stage === "raw"), ).toBe(true); expect( protocolEvents.some((event) => event.direction === "outgoing" && event.stage === "decoded"), ).toBe(true); expect( protocolEvents.some((event) => event.direction === "incoming" && event.stage === "raw"), ).toBe(true); expect( protocolEvents.some((event) => event.direction === "incoming" && event.stage === "decoded"), ).toBe(true); }).pipe( Effect.provide( AcpSessionRuntime.layer({ authMethodId: "test", spawn: { command: mockAgentCommand, args: mockAgentArgs, }, cwd: process.cwd(), clientInfo: { name: "t3-test", version: "0.0.0" }, protocolLogging: { logIncoming: true, logOutgoing: true, logger: (event) => Effect.sync(() => { protocolEvents.push(event); }), }, }), ), Effect.scoped,