Skip to content

Commit 9df32fa

Browse files
committed
fix(server): Prevent error message on stream abort (complete fix)
Root Cause Analysis: When user presses ESC to abort streaming, the AI SDK throws NoOutputGeneratedError which propagates through TWO error handlers: 1. ai-orchestrator.ts: Stream error events (case "error") 2. stream-orchestrator.ts: Outer catch block for execution errors Previously, both handlers unconditionally emitted error events, causing "No output generated" to appear in UI on intentional abort. Complete Fix: 1. ai-orchestrator.ts (lines 400-422): - Stream error events now check if error contains "No output generated" - Abort errors → set state.aborted, call onAbort() (no error emitted) - Real errors → add error part, emit error event 2. stream-orchestrator.ts (lines 564-571): - Outer catch block now checks errorMessage and state.aborted - Abort errors → skip emitError(), only complete observer - Real errors → emit error event normally Architecture: - Two-layer error detection prevents abort errors at both levels - state.aborted provides additional signal for abort detection - Error message pattern matching catches AI SDK abort errors - Real errors still emit properly for debugging Result: ESC abort now cleanly stops without error message or hanging.
1 parent 4676f92 commit 9df32fa

File tree

2 files changed

+40
-9
lines changed

2 files changed

+40
-9
lines changed

packages/code-server/src/services/streaming/ai-orchestrator.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,26 @@ export async function processAIStream(
398398
}
399399

400400
case "error": {
401-
state.currentStepParts.push({
402-
type: "error",
403-
error: String(chunk.error),
404-
status: "completed",
405-
});
406-
hasError = true;
407-
emitError(observer, String(chunk.error));
408-
callbacks.onError?.(String(chunk.error));
401+
// Check if this is an abort-related error (NoOutputGeneratedError)
402+
// These errors are expected when user cancels stream via ESC key
403+
const errorStr = String(chunk.error);
404+
const isAbortError = errorStr.includes("No output generated") || state.aborted;
405+
406+
if (isAbortError) {
407+
// Treat as abort, not error
408+
state.aborted = true;
409+
callbacks.onAbort?.();
410+
} else {
411+
// Real error - add to parts and emit
412+
state.currentStepParts.push({
413+
type: "error",
414+
error: errorStr,
415+
status: "completed",
416+
});
417+
hasError = true;
418+
emitError(observer, errorStr);
419+
callbacks.onError?.(errorStr);
420+
}
409421
break;
410422
}
411423

packages/code-server/src/services/streaming/stream-orchestrator.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,26 @@ export function streamAIResponse(opts: StreamAIResponseOptions): Observable<Stre
560560
console.error("[StreamOrchestrator] Error keys:", Object.keys(error));
561561
console.error("[StreamOrchestrator] Error JSON:", JSON.stringify(error, null, 2));
562562
}
563-
emitError(observer, error instanceof Error ? error.message : String(error));
563+
564+
// Check if this is an abort error (NoOutputGeneratedError or intentional abort)
565+
const errorMessage = error instanceof Error ? error.message : String(error);
566+
const isAbortError = errorMessage.includes("No output generated") || state.aborted;
567+
568+
if (isAbortError) {
569+
// For abort errors: update message status to trigger cleanup, but don't emit error event
570+
await updateMessageStatus(
571+
assistantMessageId,
572+
"abort",
573+
undefined, // no finishReason
574+
undefined, // no usage
575+
messageRepository,
576+
observer,
577+
);
578+
} else {
579+
// Real error: emit error event for debugging
580+
emitError(observer, errorMessage);
581+
}
582+
564583
observer.complete();
565584
}
566585
})();

0 commit comments

Comments
 (0)