From 2970f0a06c234d1cb2fafb2e02754973bd1fa26a Mon Sep 17 00:00:00 2001 From: Taylor Mullen Date: Sun, 11 May 2025 13:32:56 -0700 Subject: [PATCH] feat: Integrate centralized error reporting for API interactions Implements robust error handling for Gemini API calls, integrating with the centralized error reporting system. - API errors are now caught and reported to dedicated log files, providing detailed diagnostics without cluttering the user interface. - A concise error message is surfaced to the user in the UI, indicating an API issue. - Ensures any pending UI updates are processed before an API error is displayed. This change improves our ability to diagnose API-related problems by capturing rich error context centrally, while maintaining a clean user experience. Signed-off-by: Gemini --- packages/cli/src/ui/hooks/useGeminiStream.ts | 13 +++- packages/server/src/core/turn.ts | 63 +++++++++++++------- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts index dbcb4e64..7ab30463 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.ts +++ b/packages/cli/src/ui/hooks/useGeminiStream.ts @@ -323,6 +323,18 @@ export const useGeminiStream = ( ); setStreamingState(StreamingState.Idle); return; // Stop processing the stream + } else if (event.type === ServerGeminiEventType.Error) { + // Flush out existing pending history item. + if (pendingHistoryItemRef.current) { + addItem(pendingHistoryItemRef.current, userMessageTimestamp); + setPendingHistoryItem(null); + } + addItem( + { type: 'error', text: `[API Error: ${event.value.message}]` }, + userMessageTimestamp, + ); + setStreamingState(StreamingState.Idle); + // Allow stream to end naturally } } // End stream loop @@ -335,7 +347,6 @@ export const useGeminiStream = ( setStreamingState(StreamingState.Idle); } catch (error: unknown) { if (!isNodeError(error) || error.name !== 'AbortError') { - console.error('Error processing stream or executing tool:', error); addItem( { type: 'error', diff --git a/packages/server/src/core/turn.ts b/packages/server/src/core/turn.ts index 62219938..8f473986 100644 --- a/packages/server/src/core/turn.ts +++ b/packages/server/src/core/turn.ts @@ -18,6 +18,8 @@ import { ToolResultDisplay, } from '../tools/tools.js'; import { getResponseText } from '../utils/generateContentResponseUtilities.js'; +import { reportError } from '../utils/errorReporting.js'; +import { getErrorMessage } from '../utils/errors.js'; // --- Types for Server Logic --- @@ -51,6 +53,11 @@ export enum GeminiEventType { ToolCallResponse = 'tool_call_response', ToolCallConfirmation = 'tool_call_confirmation', UserCancelled = 'user_cancelled', + Error = 'error', +} + +export interface GeminiErrorEventValue { + message: string; } export interface ToolCallRequestInfo { @@ -79,7 +86,8 @@ export type ServerGeminiStreamEvent = type: GeminiEventType.ToolCallConfirmation; value: ServerToolCallConfirmationDetails; } - | { type: GeminiEventType.UserCancelled }; + | { type: GeminiEventType.UserCancelled } + | { type: GeminiEventType.Error; value: GeminiErrorEventValue }; // A turn manages the agentic loop turn within the server context. export class Turn { @@ -108,31 +116,46 @@ export class Turn { req: PartListUnion, signal?: AbortSignal, ): AsyncGenerator { - const responseStream = await this.chat.sendMessageStream({ message: req }); + try { + const responseStream = await this.chat.sendMessageStream({ + message: req, + }); - for await (const resp of responseStream) { - this.debugResponses.push(resp); - if (signal?.aborted) { - yield { type: GeminiEventType.UserCancelled }; - return; - } + for await (const resp of responseStream) { + this.debugResponses.push(resp); + if (signal?.aborted) { + yield { type: GeminiEventType.UserCancelled }; + return; + } - const text = getResponseText(resp); - if (text) { - yield { type: GeminiEventType.Content, value: text }; - } + const text = getResponseText(resp); + if (text) { + yield { type: GeminiEventType.Content, value: text }; + } - if (!resp.functionCalls) { - continue; - } + if (!resp.functionCalls) { + continue; + } - // Handle function calls (requesting tool execution) - for (const fnCall of resp.functionCalls) { - const event = this.handlePendingFunctionCall(fnCall); - if (event) { - yield event; + // Handle function calls (requesting tool execution) + for (const fnCall of resp.functionCalls) { + const event = this.handlePendingFunctionCall(fnCall); + if (event) { + yield event; + } } } + } catch (error) { + const contextForReport = [...this.chat.getHistory(/*curated*/ true), req]; + await reportError( + error, + 'Error when talking to Gemini API', + contextForReport, + 'Turn.run-sendMessageStream', + ); + const errorMessage = getErrorMessage(error); + yield { type: GeminiEventType.Error, value: { message: errorMessage } }; + return; } // Execute pending tool calls