From 8ff2cc4d2298c450e58f2ca38235b1f275a4aeaa Mon Sep 17 00:00:00 2001 From: cpacker Date: Mon, 27 Oct 2025 18:45:04 -0700 Subject: [PATCH] fix: log mid-stream errors to transcript, notes on retrieving --- src/cli/App.tsx | 4 ++++ src/cli/helpers/accumulator.ts | 21 +++++++++++++++++++++ src/headless.ts | 4 ++++ 3 files changed, 29 insertions(+) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 3c1ce5d..55942b2 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -510,6 +510,10 @@ export default function App({ } // Unexpected stop reason + // TODO: For error stop reasons (error, llm_api_error, etc.), fetch step details + // using lastRunId to get full error message from step.errorData + // Example: client.runs.steps.list(lastRunId, { limit: 1, order: "desc" }) + // Then display step.errorData.message or full error details instead of generic message appendError(`Unexpected stop reason: ${stopReason}`); setStreaming(false); return; diff --git a/src/cli/helpers/accumulator.ts b/src/cli/helpers/accumulator.ts index 584a4ff..0eecc4d 100644 --- a/src/cli/helpers/accumulator.ts +++ b/src/cli/helpers/accumulator.ts @@ -198,6 +198,27 @@ export function onChunk( b: Buffers, chunk: Letta.agents.LettaStreamingResponse, ) { + // TODO remove once SDK v1 has proper typing for in-stream errors + // Check for streaming error objects (not typed in SDK but emitted by backend) + // These are emitted when LLM errors occur during streaming (rate limits, timeouts, etc.) + const chunkAny = chunk as any; + if (chunkAny.error && !chunk.messageType) { + const errorId = `err-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; + const errorMsg = chunkAny.error.message || "An error occurred"; + const errorDetail = chunkAny.error.detail || ""; + const fullErrorText = errorDetail + ? `${errorMsg}: ${errorDetail}` + : errorMsg; + + b.byId.set(errorId, { + kind: "error", + id: errorId, + text: `⚠ ${fullErrorText}`, + }); + b.order.push(errorId); + return; + } + switch (chunk.messageType) { case "reasoning_message": { const id = chunk.otid; diff --git a/src/headless.ts b/src/headless.ts index 8c1b43f..0e88729 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -183,6 +183,10 @@ export async function handleHeadlessCommand(argv: string[]) { } // Unexpected stop reason + // TODO: For error stop reasons (error, llm_api_error, etc.), fetch step details + // using lastRunId to get full error message from step.errorData + // Example: client.runs.steps.list(lastRunId, { limit: 1, order: "desc" }) + // Then display step.errorData.message or full error details instead of generic message console.error(`Unexpected stop reason: ${stopReason}`); process.exit(1); }