From 6011f3e88694d5bc3cf86214c8bb324ab8dd382a Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sun, 12 Apr 2026 10:12:59 -0400 Subject: [PATCH] Fix ui-parser exports: make self-contained and export parseStdoutLine directly - ui-parser.ts: inline all logic, zero external imports (matches Paperclip adapter plugin UI parser contract) - Export parseStdoutLine as named export from index.ts (like claude_k8s exports printClaudeStreamEvent directly) Co-Authored-By: Claude Opus 4.6 --- src/index.ts | 1 + src/ui-parser.ts | 91 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6b74de0..cb8b275 100644 --- a/src/index.ts +++ b/src/index.ts @@ -63,3 +63,4 @@ Notes: `; export { createServerAdapter } from "./server/index.js"; +export { parseStdoutLine } from "./ui-parser.js"; diff --git a/src/ui-parser.ts b/src/ui-parser.ts index d98b587..92015f6 100644 --- a/src/ui-parser.ts +++ b/src/ui-parser.ts @@ -3,7 +3,92 @@ * Zero external imports — required by the Paperclip adapter plugin UI parser contract. */ -import type { TranscriptEntry } from "@paperclipai/adapter-utils"; -import { parseStdoutLine } from "./cli/format-event.js"; +type TranscriptEntry = + | { kind: "stdout"; ts: string; text: string } + | { kind: "stderr"; ts: string; text: string }; -export { parseStdoutLine }; +function asRecord(value: unknown): Record { + if (value && typeof value === "object") { + return value as Record; + } + return {}; +} + +function asString(value: unknown, fallback: string): string { + if (typeof value === "string") return value; + return fallback; +} + +function safeJsonParse(text: string): Record | null { + try { + const parsed = JSON.parse(text); + if (parsed && typeof parsed === "object") { + return parsed as Record; + } + return null; + } catch { + return null; + } +} + +function errorText(value: unknown): string { + if (typeof value === "string") return value; + const rec = asRecord(value); + const message = asString(rec.message, "").trim(); + if (message) return message; + const data = asRecord(rec.data); + const nestedMessage = asString(data.message, "").trim(); + if (nestedMessage) return nestedMessage; + const name = asString(rec.name, "").trim(); + if (name) return name; + const code = asString(rec.code, "").trim(); + if (code) return code; + try { + return JSON.stringify(rec); + } catch { + return ""; + } +} + +/** + * Parse a single stdout line into transcript entries for UI display. + * This is the Paperclip UI parser contract. + */ +export function parseStdoutLine(line: string, ts: string): TranscriptEntry[] { + const trimmed = line.trim(); + if (!trimmed) return []; + + const event = safeJsonParse(trimmed); + if (!event) { + // Non-JSON — treat as raw text + return [{ kind: "stdout", ts, text: trimmed }]; + } + + const type = asString(event.type, ""); + const part = asRecord(event.part ?? {}); + + if (type === "text") { + const text = asString(part.text, "").trim(); + if (text) return [{ kind: "stdout", ts, text }]; + return []; + } + + if (type === "step_finish") { + const text = asString(part.message, "").trim(); + if (text) return [{ kind: "stdout", ts, text }]; + return []; + } + + // Skip non-display events (step_start, tool_use in normal mode) + if (type === "step_start" || type === "tool_use") { + return []; + } + + if (type === "error") { + const text = errorText(event).trim(); + if (text) return [{ kind: "stderr", ts, text }]; + return []; + } + + return []; +}