chatboxai/chatbox · error · ApiError

Error from ${this.name}${context}: ${extractStreamErrorMessa

Error message

Error from ${this.name}${context}: ${extractStreamErrorMessage(error)}

What it means

The terminal branch of AbstractAISDK.handleError: thrown for any error that is not a RetryError-wrapped ApiError/APICallError, not a bare ApiError, not a ChatboxAIAPIError. It exists because mid-stream provider errors frequently arrive as plain objects ({message,type,code}) which would interpolate to '[object Object]'; extractStreamErrorMessage unwraps them into a readable string before the ApiError is built.

Source

Thrown at src/shared/models/abstract-ai-sdk.ts:742

    if (
      RetryError.isInstance(error) &&
      (error.lastError instanceof ApiError || APICallError.isInstance(error.lastError))
    ) {
      return this.handleError(error.lastError, context)
    }
    if (APICallError.isInstance(error)) {
      const responseBody = this.sanitizeResponseBody(error.statusCode, error.responseBody)
      throw new ApiError(`Error from ${this.name}${context}`, responseBody, error.statusCode)
    }
    if (error instanceof ApiError) {
      throw error
    }
    if (error instanceof ChatboxAIAPIError) {
      throw error
    }
    // Mid-stream provider errors are often plain objects ({ message, type, code });
    // interpolating them yields "[object Object]".
    throw new ApiError(`Error from ${this.name}${context}: ${extractStreamErrorMessage(error)}`)
  }

  /**
   * Sanitize HTML error pages (e.g., 502/503/504 gateway errors) from response bodies.
   */
  private sanitizeResponseBody(statusCode: number | undefined, responseBody: string | undefined): string {
    if (!responseBody) return ''
    const trimmed = responseBody.trimStart().toLowerCase()
    if (trimmed.startsWith('<!doctype') || trimmed.startsWith('<html')) {
      const statusMessages: Record<number, string> = {
        502: 'Bad Gateway',
        503: 'Service Unavailable',
        504: 'Gateway Timeout',
      }
      const statusMsg = statusCode ? statusMessages[statusCode] || `HTTP ${statusCode}` : 'Server Error'
      return `${statusMsg} - The server returned an HTML error page instead of a valid response.`
    }
    return responseBody

View on GitHub (pinned to 81571269ad)

Solutions

  1. Inspect the caught ApiError.message after extractStreamErrorMessage — it should contain the upstream text; if it's still generic, log the raw error value at the handleError entry to capture the unhandled shape.
  2. For mid-stream network blips, the chat UI should preserve already-received tokens and offer a 'continue' rather than discarding the turn.
  3. If you see this frequently for a specific provider, extend handleError with another instanceof branch (and extractStreamErrorMessage with another shape) so the error is classified earlier.
  4. Check that the AbortSignal you pass isn't being triggered by an unrelated error (e.g. a CSP/fetch policy) — those arrive here as opaque errors.

Example fix

// before
throw new ApiError(`Error from ${this.name}${context}: ${extractStreamErrorMessage(error)}`)
// after — preserve statusCode when present and avoid 'unknown error' loss
const msg = extractStreamErrorMessage(error)
const status = (error && typeof error === 'object' && 'status' in error) ? Number(error.status) : undefined
throw new ApiError(`Error from ${this.name}${context}: ${msg}`, undefined, status)
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate stream setup before opening the connection to reduce opaque mid-stream errors.
function assertStreamPreconditions(signal: AbortSignal | undefined, messages: unknown[]): void {
  if (signal?.aborted) throw new Error('AbortSignal already aborted before stream start')
  if (!Array.isArray(messages) || messages.length === 0) throw new Error('No messages to stream')
}

Type guard

function isOpaqueStreamError(e: unknown): boolean {
  return e instanceof ApiError && /^Error from .+:/.test(e.message) && !e.statusCode
}

Try / catch

try {
  return await sdk.chatStream(messages, opts)
} catch (e) {
  if (e instanceof ApiError && /Error from .+:/.test(e.message)) {
    // preserve already-received tokens; offer 'continue'
    offerContinuePartialTurn()
    return
  }
  throw e
}

Prevention

When it happens

Trigger: A streaming chat call rejected with an opaque value: a plain object from an SSE error frame, a non-Error thrown by a transform, an AbortSignal-triggered rejection that wasn't an ApiError, or an unexpected exception type inside the SDK pipeline. extractStreamErrorMessage attempts to coerce it to a string.

Common situations: Provider sends a mid-stream error event as JSON the SDK doesn't classify; a downstream transformer throws a plain object; a network blip mid-stream produces a TypeError; an unexpected Content Security Policy violation aborts the stream.

Related errors


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