import { describe, expect, it } from "vitest"; import { parsePiJsonl, isPiUnknownSessionError } from "./parse.js"; describe("parsePiJsonl", () => { it("parses agent lifecycle and messages", () => { const stdout = [ JSON.stringify({ type: "agent_start" }), JSON.stringify({ type: "turn_end", message: { role: "assistant", content: [{ type: "text", text: "Hello from Pi" }], }, }), JSON.stringify({ type: "agent_end", messages: [] }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.messages).toContain("Hello from Pi"); expect(parsed.finalMessage).toBe("Hello from Pi"); }); it("parses streaming text deltas", () => { const stdout = [ JSON.stringify({ type: "message_update", assistantMessageEvent: { type: "text_delta", delta: "Hello " }, }), JSON.stringify({ type: "message_update", assistantMessageEvent: { type: "text_delta", delta: "World" }, }), JSON.stringify({ type: "turn_end", message: { role: "assistant", content: "Hello World", }, }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.messages).toContain("Hello World"); }); it("parses tool execution", () => { const stdout = [ JSON.stringify({ type: "tool_execution_start", toolCallId: "tool_1", toolName: "read", args: { path: "/tmp/test.txt" }, }), JSON.stringify({ type: "tool_execution_end", toolCallId: "tool_1", toolName: "read", result: "file contents", isError: false, }), JSON.stringify({ type: "turn_end", message: { role: "assistant", content: "Done" }, toolResults: [ { toolCallId: "tool_1", content: "file contents", isError: false, }, ], }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.toolCalls).toHaveLength(1); expect(parsed.toolCalls[0].toolName).toBe("read"); expect(parsed.toolCalls[0].result).toBe("file contents"); expect(parsed.toolCalls[0].isError).toBe(false); }); it("handles errors in tool execution", () => { const stdout = [ JSON.stringify({ type: "tool_execution_start", toolCallId: "tool_1", toolName: "read", args: { path: "/missing.txt" }, }), JSON.stringify({ type: "tool_execution_end", toolCallId: "tool_1", toolName: "read", result: "File not found", isError: true, }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.toolCalls).toHaveLength(1); expect(parsed.toolCalls[0].isError).toBe(true); expect(parsed.toolCalls[0].result).toBe("File not found"); }); it("extracts usage and cost from turn_end events", () => { const stdout = [ JSON.stringify({ type: "turn_end", message: { role: "assistant", content: "Response with usage", usage: { input: 100, output: 50, cacheRead: 20, totalTokens: 170, cost: { input: 0.001, output: 0.0015, cacheRead: 0.0001, cacheWrite: 0, total: 0.0026, }, }, }, toolResults: [], }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.usage.inputTokens).toBe(100); expect(parsed.usage.outputTokens).toBe(50); expect(parsed.usage.cachedInputTokens).toBe(20); expect(parsed.usage.costUsd).toBeCloseTo(0.0026, 4); }); it("accumulates usage from multiple turns", () => { const stdout = [ JSON.stringify({ type: "turn_end", message: { role: "assistant", content: "First response", usage: { input: 50, output: 25, cacheRead: 0, cost: { total: 0.001 }, }, }, }), JSON.stringify({ type: "turn_end", message: { role: "assistant", content: "Second response", usage: { input: 30, output: 20, cacheRead: 10, cost: { total: 0.0015 }, }, }, }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.usage.inputTokens).toBe(80); expect(parsed.usage.outputTokens).toBe(45); expect(parsed.usage.cachedInputTokens).toBe(10); expect(parsed.usage.costUsd).toBeCloseTo(0.0025, 4); }); it("handles standalone usage events with Pi format", () => { const stdout = [ JSON.stringify({ type: "usage", usage: { input: 200, output: 100, cacheRead: 50, cost: { total: 0.005 }, }, }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.usage.inputTokens).toBe(200); expect(parsed.usage.outputTokens).toBe(100); expect(parsed.usage.cachedInputTokens).toBe(50); expect(parsed.usage.costUsd).toBe(0.005); }); it("handles standalone usage events with generic format", () => { const stdout = [ JSON.stringify({ type: "usage", usage: { inputTokens: 150, outputTokens: 75, cachedInputTokens: 25, costUsd: 0.003, }, }), ].join("\n"); const parsed = parsePiJsonl(stdout); expect(parsed.usage.inputTokens).toBe(150); expect(parsed.usage.outputTokens).toBe(75); expect(parsed.usage.cachedInputTokens).toBe(25); expect(parsed.usage.costUsd).toBe(0.003); }); }); describe("isPiUnknownSessionError", () => { it("detects unknown session errors", () => { expect(isPiUnknownSessionError("session not found: s_123", "")).toBe(true); expect(isPiUnknownSessionError("", "unknown session id")).toBe(true); expect(isPiUnknownSessionError("", "no session available")).toBe(true); expect(isPiUnknownSessionError("all good", "")).toBe(false); expect(isPiUnknownSessionError("working fine", "no errors")).toBe(false); }); });