CherryHQ/cherry-studio · error · APICallError

Invalid JSON response from SiliconFlow

Error message

Invalid JSON response from SiliconFlow

What it means

APICallError thrown when SiliconImageModel.doGenerate receives an ok HTTP status but the body is not valid JSON (JSON.parse throws). The original parse error is attached as `cause`. This guards against the SDK handing a parse error to image-decoding logic downstream; the response body is preserved on the error for inspection.

Source

Thrown at src/main/ai/provider/custom/silicon/SiliconImageModel.ts:130

    })
    const responseBody = await response.text()

    if (!response.ok) {
      throw new APICallError({
        message: responseBody || response.statusText,
        url,
        requestBodyValues: body,
        statusCode: response.status,
        responseHeaders,
        responseBody
      })
    }

    let parsed: ImageResponseBody
    try {
      parsed = JSON.parse(responseBody) as ImageResponseBody
    } catch (cause) {
      throw new APICallError({
        message: 'Invalid JSON response from SiliconFlow',
        cause,
        url,
        requestBodyValues: body,
        statusCode: response.status,
        responseHeaders,
        responseBody
      })
    }

    const items = parsed.images ?? parsed.data ?? []
    const images: string[] = items.flatMap((item) => {
      if (typeof item.b64_json === 'string') return [item.b64_json]
      if (typeof item.url === 'string') return [item.url]
      return []
    })

    return {

View on GitHub (pinned to 726446b54c)

Solutions

  1. Inspect error.responseBody — if it is HTML or empty, the request never reached the real SiliconFlow API; treat as transient and retry.
  2. Verify the configured baseURL resolves to the real siliconflow.cn API and not a captive portal/proxy page.
  3. Check network egress for transparent proxies rewriting the response.
  4. If intermittent, add a retry with backoff; if persistent, report a vendor/network issue.

Example fix

// before
parsed = JSON.parse(responseBody) as ImageResponseBody
// after — guard and surface what we got
let parsed: ImageResponseBody
try { parsed = JSON.parse(responseBody) as ImageResponseBody }
 catch (cause) { throw new APICallError({ message: `SiliconFlow returned non-JSON body (len=${responseBody.length}): ${responseBody.slice(0,200)}`, cause, ... }) }
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check reachability if a captive portal / proxy is suspected
const probe = await fetch(baseURL, { method: 'HEAD' })
const ct = probe.headers.get('content-type') ?? ''
if (ct.includes('text/html')) throw new Error('SiliconFlow endpoint returned HTML; check baseURL/proxy')

Type guard

export function isSiliconNonJsonError(e: unknown): boolean {
  return e instanceof APICallError && /Invalid JSON response from SiliconFlow/.test(e.message)
}

Try / catch

try {
  await imageModel.doGenerate(opts)
} catch (e) {
  if (isSiliconNonJsonError(e)) {
    // CDN/gateway returned HTML; retry once
    await backoffRetry(() => imageModel.doGenerate(opts), { maxAttempts: 1 })
  }
  throw e
}

Prevention

When it happens

Trigger: SiliconFlow returns 2xx but the body is HTML (a CDN/proxy error page), empty, truncated, or otherwise non-JSON. Concrete causes: gateway/CDN serving an HTML error page with 200, partial response due to connection drop, vendor maintenance returning an HTML banner, or a reverse-proxy injecting content.

Common situations: Vendor outage behind a CDN returning a friendly HTML error page, corporate proxy rewriting responses, mid-stream disconnect leaving truncated body, or a regional gateway returning an HTML interstitial.

Understand the failure class

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/def231d60264c9ef. Report an issue: GitHub.