import type { TranscriptEntry } from "@paperclipai/adapter-utils"; function safeJsonParse(text: string): unknown { try { return JSON.parse(text); } catch { return null; } } function asRecord(value: unknown): Record | null { if (typeof value !== "object" || value === null || Array.isArray(value)) return null; return value as Record; } function asString(value: unknown, fallback = ""): string { return typeof value === "string" ? value : fallback; } function extractTextContent(content: string | Array<{ type: string; text?: string }>): string { if (typeof content === "string") return content; if (!Array.isArray(content)) return ""; return content .filter((c) => c.type === "text" && c.text) .map((c) => c.text!) .join(""); } export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { const parsed = asRecord(safeJsonParse(line)); if (!parsed) { return [{ kind: "stdout", ts, text: line }]; } const type = asString(parsed.type); // RPC protocol messages - filter these out (internal implementation detail) if (type === "response" || type === "extension_ui_request" || type === "extension_ui_response" || type === "extension_error") { return []; } // Agent lifecycle if (type === "agent_start") { return [{ kind: "system", ts, text: "Pi agent started" }]; } if (type === "agent_end") { return [{ kind: "system", ts, text: "Pi agent finished" }]; } // Turn lifecycle if (type === "turn_start") { return [{ kind: "system", ts, text: "Turn started" }]; } if (type === "turn_end") { const message = asRecord(parsed.message); const toolResults = parsed.toolResults as Array> | undefined; const entries: TranscriptEntry[] = []; if (message) { const content = message.content as string | Array<{ type: string; text?: string }>; const text = extractTextContent(content); if (text) { entries.push({ kind: "assistant", ts, text }); } } // Process tool results if (toolResults) { for (const tr of toolResults) { const content = tr.content; const isError = tr.isError === true; // Extract text from Pi's content array format let contentStr: string; if (typeof content === "string") { contentStr = content; } else if (Array.isArray(content)) { contentStr = extractTextContent(content as Array<{ type: string; text?: string }>); } else { contentStr = JSON.stringify(content); } entries.push({ kind: "tool_result", ts, toolUseId: asString(tr.toolCallId, "unknown"), toolName: asString(tr.toolName), content: contentStr, isError, }); } } return entries.length > 0 ? entries : [{ kind: "system", ts, text: "Turn ended" }]; } // Message streaming if (type === "message_start") { return []; } if (type === "message_update") { const assistantEvent = asRecord(parsed.assistantMessageEvent); if (assistantEvent) { const msgType = asString(assistantEvent.type); if (msgType === "text_delta") { const delta = asString(assistantEvent.delta); if (delta) { return [{ kind: "assistant", ts, text: delta, delta: true }]; } } } return []; } if (type === "message_end") { return []; } // Tool execution if (type === "tool_execution_start") { const toolName = asString(parsed.toolName); const args = parsed.args; if (toolName) { return [{ kind: "tool_call", ts, name: toolName, input: args, }]; } return [{ kind: "system", ts, text: `Tool started` }]; } if (type === "tool_execution_update") { return []; } if (type === "tool_execution_end") { const toolCallId = asString(parsed.toolCallId); const toolName = asString(parsed.toolName); const result = parsed.result; const isError = parsed.isError === true; // Extract text from Pi's content array format // Can be: {"content": [{"type": "text", "text": "..."}]} or [{"type": "text", "text": "..."}] let contentStr: string; if (typeof result === "string") { contentStr = result; } else if (Array.isArray(result)) { // Direct array format: result is [{"type": "text", "text": "..."}] contentStr = extractTextContent(result as Array<{ type: string; text?: string }>); } else if (result && typeof result === "object") { const resultObj = result as Record; if (Array.isArray(resultObj.content)) { // Wrapped format: result is {"content": [{"type": "text", "text": "..."}]} contentStr = extractTextContent(resultObj.content as Array<{ type: string; text?: string }>); } else { contentStr = JSON.stringify(result); } } else { contentStr = JSON.stringify(result); } return [{ kind: "tool_result", ts, toolUseId: toolCallId || "unknown", toolName, content: contentStr, isError, }]; } return [{ kind: "stdout", ts, text: line }]; }