import { parseAgentOutput } from "./parse-agent-output"; function chunk(events: object[]): string { return events.map((e) => JSON.stringify(e)).join("\n"); } describe("parseAgentOutput", () => { // 1. toolInput is set on tool_call messages it("sets meta.toolInput on tool_call messages", () => { const input = chunk([ { type: "assistant", message: { content: [ { type: "tool_use", id: "tu1", name: "Read", input: { file_path: "/foo.ts" }, }, ], }, }, ]); const messages = parseAgentOutput(input); const toolCall = messages.find((m) => m.type === "tool_call"); expect(toolCall).toBeDefined(); expect(toolCall!.meta?.toolInput).toEqual({ file_path: "/foo.ts" }); }); // 2. tool_result with tool_use_id gets meta.toolName and meta.toolInput from registry it("correlates tool_result to its tool_use via registry", () => { const input = chunk([ { type: "assistant", message: { content: [ { type: "tool_use", id: "tu1", name: "Read", input: { file_path: "/foo.ts" }, }, ], }, }, { type: "user", message: { content: [ { type: "tool_result", tool_use_id: "tu1", content: "file contents", }, ], }, }, ]); const messages = parseAgentOutput(input); const toolResult = messages.find((m) => m.type === "tool_result"); expect(toolResult).toBeDefined(); expect(toolResult!.meta?.toolName).toBe("Read"); expect(toolResult!.meta?.toolInput).toEqual({ file_path: "/foo.ts" }); }); // 3. tool_result with no matching registry entry has no meta.toolName it("tool_result with unknown tool_use_id has no meta.toolName", () => { const input = chunk([ { type: "user", message: { content: [ { type: "tool_result", tool_use_id: "unknown-id", content: "output", }, ], }, }, ]); const messages = parseAgentOutput(input); const toolResult = messages.find((m) => m.type === "tool_result"); expect(toolResult).toBeDefined(); expect(toolResult!.meta?.toolName).toBeUndefined(); }); // 4. tool_result with is_error: true produces type: "error" and meta.isError: true it("tool_result with is_error: true produces error message", () => { const input = chunk([ { type: "user", message: { content: [ { type: "tool_result", tool_use_id: "tu1", is_error: true, content: "something went wrong", }, ], }, }, ]); const messages = parseAgentOutput(input); const errorMsg = messages.find((m) => m.content === "something went wrong"); expect(errorMsg).toBeDefined(); expect(errorMsg!.type).toBe("error"); expect(errorMsg!.meta?.isError).toBe(true); }); // 5. tool_result from a Task tool_use gets correct meta.toolName and meta.toolInput it("tool_result from Task tool_use has correct meta", () => { const taskInput = { subagent_type: "Explore", description: "find files", prompt: "search for *.ts", }; const input = chunk([ { type: "assistant", message: { content: [ { type: "tool_use", id: "tu2", name: "Task", input: taskInput, }, ], }, }, { type: "user", message: { content: [ { type: "tool_result", tool_use_id: "tu2", content: "found 10 files", }, ], }, }, ]); const messages = parseAgentOutput(input); const toolResult = messages.find((m) => m.type === "tool_result"); expect(toolResult).toBeDefined(); expect(toolResult!.meta?.toolName).toBe("Task"); expect(toolResult!.meta?.toolInput).toEqual(taskInput); }); // 6. Unknown top-level event type produces a system message it("unknown top-level event type produces system message", () => { const input = chunk([{ type: "future_event_type", data: {} }]); const messages = parseAgentOutput(input); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("system"); expect(messages[0].content).toBe("[unknown event: future_event_type]"); }); // 7. Unknown assistant content block type produces a system message it("unknown assistant content block type produces system message", () => { const input = chunk([ { type: "assistant", message: { content: [{ type: "image", data: "base64..." }], }, }, ]); const messages = parseAgentOutput(input); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("system"); expect(messages[0].content).toBe("[unsupported content block: image]"); }); // 8. Previously passing behavior unchanged describe("previously passing behavior", () => { it("system event with session_id produces system message", () => { const input = chunk([{ type: "system", session_id: "sess-123" }]); const messages = parseAgentOutput(input); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("system"); expect(messages[0].content).toBe("Session started: sess-123"); }); it("assistant text block produces text message", () => { const input = chunk([ { type: "assistant", message: { content: [{ type: "text", text: "Hello, world!" }], }, }, ]); const messages = parseAgentOutput(input); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("text"); expect(messages[0].content).toBe("Hello, world!"); }); it("assistant tool_use block produces tool_call message with meta.toolName", () => { const input = chunk([ { type: "assistant", message: { content: [ { type: "tool_use", id: "tu1", name: "Bash", input: { command: "ls -la", description: "list files" }, }, ], }, }, ]); const messages = parseAgentOutput(input); const toolCall = messages.find((m) => m.type === "tool_call"); expect(toolCall).toBeDefined(); expect(toolCall!.meta?.toolName).toBe("Bash"); }); it("result event with is_error: false produces session_end", () => { const input = chunk([ { type: "result", is_error: false, total_cost_usd: 0.01, duration_ms: 5000, }, ]); const messages = parseAgentOutput(input); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("session_end"); expect(messages[0].content).toBe("Session completed"); }); it("result event with is_error: true produces session_end with meta.isError", () => { const input = chunk([ { type: "result", is_error: true, total_cost_usd: 0.01, duration_ms: 5000, }, ]); const messages = parseAgentOutput(input); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("session_end"); expect(messages[0].meta?.isError).toBe(true); }); it("non-JSON line produces error message with raw line as content", () => { const rawLine = "This is not JSON at all"; const messages = parseAgentOutput(rawLine); expect(messages).toHaveLength(1); expect(messages[0].type).toBe("error"); expect(messages[0].content).toBe(rawLine); }); }); });