import { createServer, type IncomingMessage, type ServerResponse, } from "node:http"; import { createTestSession, type TestSession, } from "@marcfargas/pi-test-harness"; import { Type } from "typebox"; import { afterEach, describe, expect, it } from "vitest"; import klaus from "../index"; import { type KlausQueryHandle, startSdkQuery } from "../src/agent-sdk"; import { MemorySessionStore } from "../src/cache"; import type { KlausContentEvent, KlausRequest } from "../src/protocol"; interface CapturedRequest { url: string; headers: IncomingMessage["headers"]; body: unknown; } const requests: CapturedRequest[] = []; let closeServer: (() => Promise) | undefined; let queryHandle: KlausQueryHandle | undefined; let piSession: TestSession | undefined; function messageRequests(): CapturedRequest[] { return requests .filter((request) => request.url.startsWith("/v1/messages")) .filter((request) => !request.url.includes("count_tokens")); } async function createKlausTestSession(): Promise { return createTestSession({ extensionFactories: [klaus] }); } function sse(response: ServerResponse, frames: unknown[]): void { response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive", }); for (const frame of frames) { const type = (frame as { type: string }).type; response.write(`event: ${type}\ndata: ${JSON.stringify(frame)}\n\n`); } response.end(); } async function startStalledAnthropic(): Promise { const server = createServer(async (request, response) => { const chunks: Buffer[] = []; for await (const chunk of request) chunks.push(Buffer.from(chunk)); const text = Buffer.concat(chunks).toString("utf8"); requests.push({ url: request.url ?? "", headers: request.headers, body: text ? JSON.parse(text) : undefined, }); response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive", }); response.write( `event: message_start\ndata: ${JSON.stringify({ type: "message_start", message: { id: "msg_stalled", type: "message", role: "assistant", model: "claude-sonnet-5", content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 1, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, }, }, })}\n\n`, ); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); if (!address || typeof address === "string") throw new Error("Fake Anthropic server failed to bind."); closeServer = async () => { server.closeAllConnections(); await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())), ); }; return `http://127.0.0.1:${address.port}`; } async function startFakeAnthropic(toolScenario = false): Promise { let messageCount = 0; const server = createServer(async (request, response) => { const chunks: Buffer[] = []; for await (const chunk of request) chunks.push(Buffer.from(chunk)); const text = Buffer.concat(chunks).toString("utf8"); requests.push({ url: request.url ?? "", headers: request.headers, body: text ? JSON.parse(text) : undefined, }); if (request.url?.startsWith("/v1/messages/count_tokens")) { response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify({ input_tokens: 8 })); return; } if (!request.url?.startsWith("/v1/messages")) { response.writeHead(404).end(); return; } messageCount += 1; const start = { type: "message_start", message: { id: `msg_klaus_${messageCount}`, type: "message", role: "assistant", model: "claude-sonnet-5", content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 8, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, }, }, }; if (toolScenario && messageCount === 1) { sse(response, [ start, { type: "content_block_start", index: 0, content_block: { type: "tool_use", id: "toolu_klaus_1", name: "echo", input: {}, }, }, { type: "content_block_delta", index: 0, delta: { type: "input_json_delta", partial_json: '{"value":"hello"}', }, }, { type: "content_block_stop", index: 0 }, { type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: 4 }, }, { type: "message_stop" }, ]); return; } sse(response, [ start, { type: "content_block_start", index: 0, content_block: { type: "text", text: "" }, }, { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Klaus works." }, }, { type: "content_block_stop", index: 0 }, { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: 3 }, }, { type: "message_stop" }, ]); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); if (!address || typeof address === "string") throw new Error("Fake Anthropic server failed to bind."); closeServer = async () => await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())), ); return `http://127.0.0.1:${address.port}`; } afterEach(async () => { await queryHandle?.close("E2E finished."); queryHandle = undefined; if (piSession) { await piSession.session.extensionRunner.emit({ type: "session_shutdown", reason: "quit", }); piSession.dispose(); piSession = undefined; } await closeServer?.(); closeServer = undefined; requests.length = 0; delete process.env.KLAUS_E2E_BASE_URL; }); describe("Klaus real Agent SDK E2E", () => { it( "streams through the registered Pi provider route", async () => { process.env.KLAUS_E2E_BASE_URL = await startFakeAnthropic(); piSession = await createKlausTestSession(); const runtime = piSession.session.modelRuntime; Object.defineProperty(runtime, "isUsingOAuth", { value: () => true, }); Object.defineProperty(runtime, "getAuth", { value: async () => ({ auth: { apiKey: "fake" }, source: "OAuth", }), }); const model = runtime.getModel("klaus", "claude-sonnet-5"); if (!model) throw new Error("Klaus model was not registered."); const result = await runtime.completeSimple( model, { systemPrompt: "Answer exactly as scripted.", messages: [{ role: "user", content: "hello", timestamp: Date.now() }], }, { sessionId: piSession.session.sessionManager.getSessionId(), onPayload: () => undefined, }, ); expect(result.stopReason).toBe("stop"); expect(result.content).toContainEqual({ type: "text", text: "Klaus works.", }); expect(result.usage.input).toBe(8); expect(result.usage.output).toBe(3); expect(result.usage.cost.total).toBeGreaterThan(0); expect(messageRequests()).not.toHaveLength(0); }, 10 * 60_000, ); it( "aborts a stalled real Claude request on Pi activity timeout", async () => { process.env.KLAUS_E2E_BASE_URL = await startStalledAnthropic(); piSession = await createKlausTestSession(); const runtime = piSession.session.modelRuntime; Object.defineProperty(runtime, "isUsingOAuth", { value: () => true }); Object.defineProperty(runtime, "getAuth", { value: async () => ({ auth: { apiKey: "fake" }, source: "OAuth", }), }); const model = runtime.getModel("klaus", "claude-sonnet-5"); if (!model) throw new Error("Klaus model was not registered."); const result = await runtime.completeSimple( model, { messages: [{ role: "user", content: "stall", timestamp: Date.now() }], }, { sessionId: piSession.session.sessionManager.getSessionId(), timeoutMs: 100, }, ); expect(result.stopReason).toBe("error"); expect(result.errorMessage).toContain("timed out"); }, 10 * 60_000, ); it( "streams text through the real Claude child and fake Anthropic endpoint", async () => { process.env.KLAUS_E2E_BASE_URL = await startFakeAnthropic(); const content: KlausContentEvent[] = []; const request: KlausRequest = { modelId: "claude-haiku-4-5", selector: "haiku", systemPrompt: "Answer exactly as scripted.", prompt: "hello", tools: [], thinking: "high", thinkingBudget: 1234, cwd: process.cwd(), headers: { "x-pi-e2e": "present", "x-klaus-private": "blocked" }, }; const finished = Promise.withResolvers(); queryHandle = await startSdkQuery(request, "fake", { onReady: async () => undefined, onActivity: () => undefined, onNotice: () => undefined, onContent: (event) => content.push(event), onToolBoundary: () => finished.reject(new Error("Unexpected tool boundary.")), onResult: () => finished.resolve(), onError: (error) => finished.reject(error), }); await finished.promise; expect(content).toContainEqual({ type: "text-delta", index: 0, delta: "Klaus works.", }); const messageRequest = messageRequests()[0]; expect(messageRequest).toBeDefined(); expect(messageRequest?.headers.authorization).toBe("Bearer fake"); expect(messageRequest?.headers["x-pi-e2e"]).toBe("present"); expect( Object.keys(messageRequest?.headers ?? {}).some((name) => name.startsWith("x-klaus-"), ), ).toBe(false); expect(JSON.stringify(messageRequest?.body)).not.toContain("x-klaus-"); expect(messageRequest?.body).toMatchObject({ model: "claude-haiku-4-5-20251001", thinking: { type: "enabled", budget_tokens: 1234 }, }); }, 10 * 60_000, ); it( "sends Pi images as native Agent SDK image blocks", async () => { process.env.KLAUS_E2E_BASE_URL = await startFakeAnthropic(); const finished = Promise.withResolvers(); queryHandle = await startSdkQuery( { modelId: "claude-sonnet-5", selector: "sonnet", systemPrompt: "Describe the image.", prompt: "image", images: [ { data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9WlXz9sAAAAASUVORK5CYII=", mimeType: "image/png", }, ], tools: [], thinking: undefined, cwd: process.cwd(), headers: {}, }, "fake", { onReady: async () => undefined, onActivity: () => undefined, onNotice: () => undefined, onContent: () => undefined, onToolBoundary: () => finished.reject(new Error("Unexpected tool boundary.")), onResult: () => finished.resolve(), onError: (error) => finished.reject(error), }, ); await finished.promise; const body = JSON.stringify(messageRequests()[0]?.body); expect(body).toContain('"type":"image"'); expect(body).toContain('"media_type":"image/png"'); }, 10 * 60_000, ); it( "parks an MCP tool in Claude and resumes it with a Pi result", async () => { process.env.KLAUS_E2E_BASE_URL = await startFakeAnthropic(true); const boundary = Promise.withResolvers(); const finished = Promise.withResolvers(); const toolEnds: KlausContentEvent[] = []; const request: KlausRequest = { modelId: "claude-sonnet-5", selector: "sonnet", systemPrompt: "Call echo once.", prompt: "echo hello", tools: [ { name: "echo", description: "Echo a value.", inputSchema: { type: "object", properties: { value: { type: "string" } }, required: ["value"], additionalProperties: false, }, }, ], thinking: undefined, cwd: process.cwd(), sessionId: "klaus-e2e-tools", headers: {}, }; queryHandle = await startSdkQuery(request, "fake", { onReady: async () => undefined, onActivity: () => undefined, onNotice: () => undefined, onContent: (event) => { if (event.type === "tool-end") toolEnds.push(event); }, onToolBoundary: () => boundary.resolve(), onResult: () => finished.resolve(), onError: (error) => finished.reject(error), }); await boundary.promise; expect(toolEnds).toContainEqual({ type: "tool-end", index: 0, id: "toolu_klaus_1", name: "echo", arguments: { value: "hello" }, }); await queryHandle.bridge.waitForPending(["toolu_klaus_1"]); expect( queryHandle.bridge.settle({ id: "toolu_klaus_1", content: [{ type: "text", text: "hello" }], isError: false, }), ).toBe(true); await finished.promise; const messages = messageRequests(); expect(messages).toHaveLength(2); expect(JSON.stringify(messages[0]?.body)).toContain('"name":"echo"'); expect(JSON.stringify(messages[1]?.body)).toContain("hello"); }, 10 * 60_000, ); it( "restarts canonically when Pi changes tools across a parked call", async () => { process.env.KLAUS_E2E_BASE_URL = await startFakeAnthropic(true); piSession = await createKlausTestSession(); const runtime = piSession.session.modelRuntime; Object.defineProperty(runtime, "isUsingOAuth", { value: () => true }); Object.defineProperty(runtime, "getAuth", { value: async () => ({ auth: { apiKey: "fake" }, source: "OAuth" }), }); const model = runtime.getModel("klaus", "claude-sonnet-5"); if (!model) throw new Error("Klaus model was not registered."); const sessionId = piSession.session.sessionManager.getSessionId(); const user = { role: "user" as const, content: "echo hello", timestamp: 1, }; const first = await runtime.completeSimple( model, { messages: [user], tools: [ { name: "echo", description: "Echo a value.", parameters: Type.Object({ value: Type.String() }), }, ], }, { sessionId }, ); const call = first.content.find((item) => item.type === "toolCall"); if (call?.type !== "toolCall") throw new Error("Claude did not call echo."); const second = await runtime.completeSimple( model, { messages: [ user, first, { role: "toolResult", toolCallId: call.id, toolName: call.name, content: [{ type: "text", text: "hello" }], isError: false, timestamp: 2, }, ], tools: [ { name: "new_tool", description: "A newly active tool.", parameters: Type.Object({}), }, ], }, { sessionId }, ); expect(second.content).toContainEqual({ type: "text", text: "Klaus works.", }); const messages = messageRequests(); expect(messages.length).toBeGreaterThanOrEqual(2); const replayBody = JSON.stringify(messages.at(-1)?.body); expect(replayBody).toContain('"name":"new_tool"'); expect(replayBody).not.toContain('"name":"echo"'); }, 10 * 60_000, ); it( "resumes an opaque mirrored session through the public SessionStore", async () => { process.env.KLAUS_E2E_BASE_URL = await startFakeAnthropic(); const store = new MemorySessionStore(); let sdkSessionId = ""; let firstPosition = ""; const firstFinished = Promise.withResolvers(); queryHandle = await startSdkQuery( { modelId: "claude-sonnet-5", selector: "sonnet", systemPrompt: "Continue linearly.", prompt: "first", tools: [], thinking: undefined, cwd: process.cwd(), sessionId: "pi-session", headers: {}, sessionStore: store, }, "fake", { onReady: async () => undefined, onActivity: () => undefined, onNotice: () => undefined, onContent: () => undefined, onToolBoundary: () => firstFinished.reject(new Error("Unexpected tool boundary.")), onResult: (result) => { sdkSessionId = result.sdkSessionId ?? ""; firstPosition = result.position ?? ""; firstFinished.resolve(); }, onError: (error) => firstFinished.reject(error), }, ); await firstFinished.promise; await queryHandle.close("First turn complete."); expect(sdkSessionId).not.toBe(""); const secondFinished = Promise.withResolvers(); queryHandle = await startSdkQuery( { modelId: "claude-sonnet-5", selector: "sonnet", systemPrompt: "Continue linearly.", prompt: "second", tools: [], thinking: undefined, cwd: process.cwd(), sessionId: "pi-session", headers: {}, sessionStore: store, resume: sdkSessionId, }, "fake", { onReady: async () => undefined, onActivity: () => undefined, onNotice: () => undefined, onContent: () => undefined, onToolBoundary: () => secondFinished.reject(new Error("Unexpected tool boundary.")), onResult: () => secondFinished.resolve(), onError: (error) => secondFinished.reject(error), }, ); await secondFinished.promise; await queryHandle.close("Second turn complete."); const branchFinished = Promise.withResolvers(); queryHandle = await startSdkQuery( { modelId: "claude-sonnet-5", selector: "sonnet", systemPrompt: "Continue linearly.", prompt: "branch", tools: [], thinking: undefined, cwd: process.cwd(), sessionId: "pi-session", headers: {}, sessionStore: store, resume: sdkSessionId, resumeAt: firstPosition, forkSession: true, }, "fake", { onReady: async () => undefined, onActivity: () => undefined, onNotice: () => undefined, onContent: () => undefined, onToolBoundary: () => branchFinished.reject(new Error("Unexpected tool boundary.")), onResult: () => branchFinished.resolve(), onError: (error) => branchFinished.reject(error), }, ); await branchFinished.promise; const messages = messageRequests(); expect(messages).toHaveLength(3); expect(JSON.stringify(messages[1]?.body)).toContain("second"); expect(JSON.stringify(messages[2]?.body)).toContain("branch"); }, 10 * 60_000, ); });