chatboxai/chatbox · error · MidStreamApiError

${parsed.error.message || 'Upstream Google Generative AI str

Error message

${parsed.error.message || 'Upstream Google Generative AI stream error'}

What it means

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.

Source

Thrown at src/shared/models/utils/gemini-stream-error.ts:69

    if (!data || data === '[DONE]') {
      return
    }
    let parsed: { error?: { message?: unknown; code?: unknown } }
    try {
      parsed = JSON.parse(data) as { error?: { message?: unknown; code?: unknown } }
    } catch {
      return
    }
    if (!parsed?.error || typeof parsed.error !== 'object') {
      return
    }
    const message =
      typeof parsed.error.message === 'string' && parsed.error.message
        ? parsed.error.message
        : 'Upstream Google Generative AI stream error'
    const statusCode = typeof parsed.error.code === 'number' ? parsed.error.code : 503
    if (forwardedContent) {
      throw new MidStreamApiError(message, data, statusCode)
    }
    throw new ApiError(message, data, statusCode)
  }

  // Returns true when the event carried `data:` payload (actual stream content,
  // as opposed to SSE comments/keepalives).
  const processEvent = (event: string): boolean => {
    const dataLines = event
      .split(/\r?\n/)
      .filter((line) => line.startsWith('data:'))
      .map((line) => line.slice('data:'.length).trimStart())
    // No SSE framing: some proxies report application/json and send a plain
    // JSON body, so check the raw payload for the same error shape.
    const payload = dataLines.length > 0 ? dataLines.join('\n') : event.trim()
    if (payload) {
      throwIfGoogleErrorData(payload)
    }
    return dataLines.length > 0

View on GitHub (pinned to 81571269ad)

Solutions

  1. Read ApiError.statusCode: 400 → fix prompt/parameters; 401/403 → rotate the Gemini API key; 429 → quota, back off and retry; 404 → wrong model id.
  2. Inspect ApiError.responseBody for Google's structured error (status, details) which often names the exact rejected field or policy.
  3. Pre-validate the prompt against Gemini's content policy and token limits before streaming to avoid safety-filter errors.
  4. Confirm the model id in provider config is a currently available Gemini model id.

Example fix

// before
if (forwardedContent) {
  throw new MidStreamApiError(message, data, statusCode)
}
throw new ApiError(message, data, statusCode)
// after — keep the structured error body for diagnostics
if (forwardedContent) {
  throw new MidStreamApiError(message, JSON.stringify(parsed.error), statusCode)
}
throw new ApiError(message, JSON.stringify(parsed.error), statusCode)
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-validate Gemini request preconditions to avoid early-stream errors.
function geminiRequestLikelyValid(modelId: string, apiKey: string, promptChars: number): string | null {
  if (!apiKey) return 'Missing Gemini API key'
  if (!modelId.startsWith('gemini-')) return `Unknown model id: ${modelId}`
  if (promptChars > 1_000_000) return 'Prompt exceeds context limit'
  return null
}

Type guard

function isGeminiStreamApiError(e: unknown): e is ApiError {
  return e instanceof ApiError && typeof (e as any).statusCode === 'number'
}

Try / catch

try {
  return await sdk.chatStream(messages, opts)
} catch (e) {
  if (e instanceof ApiError && typeof e.statusCode === 'number') {
    if (e.statusCode === 401 || e.statusCode === 403) rotateGeminiKey()
    else if (e.statusCode === 429) return retryWithBackoff(() => sdk.chatStream(messages, opts))
    else showUser(`Gemini: ${e.message}`)
    return
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/9f8f9b8d3a5fc22b. Report an issue: GitHub.