{"record":{"id":"9f8f9b8d3a5fc22b","repo":"chatboxai/chatbox","slug":"parsed-error-message-upstream-google-generat","errorCode":null,"errorMessage":"${parsed.error.message || 'Upstream Google Generative AI stream error'}","messagePattern":"\\$\\{parsed\\.error\\.message \\|\\| 'Upstream Google Generative AI stream error'\\}","errorType":"http","errorClass":"MidStreamApiError","httpStatus":null,"severity":"error","filePath":"src/shared/models/utils/gemini-stream-error.ts","lineNumber":69,"sourceCode":"    if (!data || data === '[DONE]') {\n      return\n    }\n    let parsed: { error?: { message?: unknown; code?: unknown } }\n    try {\n      parsed = JSON.parse(data) as { error?: { message?: unknown; code?: unknown } }\n    } catch {\n      return\n    }\n    if (!parsed?.error || typeof parsed.error !== 'object') {\n      return\n    }\n    const message =\n      typeof parsed.error.message === 'string' && parsed.error.message\n        ? parsed.error.message\n        : 'Upstream Google Generative AI stream error'\n    const statusCode = typeof parsed.error.code === 'number' ? parsed.error.code : 503\n    if (forwardedContent) {\n      throw new MidStreamApiError(message, data, statusCode)\n    }\n    throw new ApiError(message, data, statusCode)\n  }\n\n  // Returns true when the event carried `data:` payload (actual stream content,\n  // as opposed to SSE comments/keepalives).\n  const processEvent = (event: string): boolean => {\n    const dataLines = event\n      .split(/\\r?\\n/)\n      .filter((line) => line.startsWith('data:'))\n      .map((line) => line.slice('data:'.length).trimStart())\n    // No SSE framing: some proxies report application/json and send a plain\n    // JSON body, so check the raw payload for the same error shape.\n    const payload = dataLines.length > 0 ? dataLines.join('\\n') : event.trim()\n    if (payload) {\n      throwIfGoogleErrorData(payload)\n    }\n    return dataLines.length > 0","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/shared/models/utils/gemini-stream-error.ts#L51-L87","documentation":"Thrown by the Gemini stream-error parser when an SSE event from Google Generative AI carries a structured `error` object AND no content has been forwarded to the user yet: it raises a regular ApiError (not MidStreamApiError) using the upstream error.message (or a default) and error.code as statusCode (default 503). The branch is selected by forwardedContent being falsy.","triggerScenarios":"Gemini streaming endpoint emits an error event before any data: content frame — e.g. prompt safety filter, invalid API key, quota exhausted, malformed request, model not found. The JSON parses, has a non-null object error, and forwardedContent is false, so ApiError (not MidStreamApiError) is thrown.","commonSituations":"Invalid Gemini API key (401/403); request tripped a safety filter; quota/billing limit hit; requested model name is unavailable; region restriction; prompt exceeds token limit; all of these arriving as the first SSE frame before any content.","solutions":["Read ApiError.statusCode: 400 → fix prompt/parameters; 401/403 → rotate the Gemini API key; 429 → quota, back off and retry; 404 → wrong model id.","Inspect ApiError.responseBody for Google's structured error (status, details) which often names the exact rejected field or policy.","Pre-validate the prompt against Gemini's content policy and token limits before streaming to avoid safety-filter errors.","Confirm the model id in provider config is a currently available Gemini model id."],"exampleFix":"// before\nif (forwardedContent) {\n  throw new MidStreamApiError(message, data, statusCode)\n}\nthrow new ApiError(message, data, statusCode)\n// after — keep the structured error body for diagnostics\nif (forwardedContent) {\n  throw new MidStreamApiError(message, JSON.stringify(parsed.error), statusCode)\n}\nthrow new ApiError(message, JSON.stringify(parsed.error), statusCode)","handlingStrategy":"try-catch","validationCode":"// Pre-validate Gemini request preconditions to avoid early-stream errors.\nfunction geminiRequestLikelyValid(modelId: string, apiKey: string, promptChars: number): string | null {\n  if (!apiKey) return 'Missing Gemini API key'\n  if (!modelId.startsWith('gemini-')) return `Unknown model id: ${modelId}`\n  if (promptChars > 1_000_000) return 'Prompt exceeds context limit'\n  return null\n}","typeGuard":"function isGeminiStreamApiError(e: unknown): e is ApiError {\n  return e instanceof ApiError && typeof (e as any).statusCode === 'number'\n}","tryCatchPattern":"try {\n  return await sdk.chatStream(messages, opts)\n} catch (e) {\n  if (e instanceof ApiError && typeof e.statusCode === 'number') {\n    if (e.statusCode === 401 || e.statusCode === 403) rotateGeminiKey()\n    else if (e.statusCode === 429) return retryWithBackoff(() => sdk.chatStream(messages, opts))\n    else showUser(`Gemini: ${e.message}`)\n    return\n  }\n  throw e\n}","preventionTips":["Use a current Gemini model id — deprecated ids return 404 as the first frame.","Keep prompts within content policy; run a safety pre-check for high-risk content.","Set sane quotas/billing alerts so 429s are rare."],"tags":["gemini","streaming","provider-error","api-error"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}