nexu-io/open-design · error · Error

${providerTag} non-JSON response: ${truncate(text, 200)}

Error message

${providerTag} non-JSON response: ${truncate(text, 200)}

What it means

Thrown by parseOpenAICompatibleJson when resp.ok is true but the body is not parseable as JSON. Same shape as the first-party 'openai non-JSON response' but for any provider routed through the shared helper. The providerTag prefix identifies which integration returned the bad body.

Source

Thrown at apps/daemon/src/media/index.ts:1133

function customImageOverridesOpenAIModel(
  ctx: MediaContext,
  credentials: ProviderConfig | null,
): credentials is ProviderConfig {
  const baseUrl = credentials?.baseUrl?.trim();
  const model = credentials?.model?.trim();
  if (!baseUrl || !model) return false;
  return model === ctx.model || model === ctx.wireModel;
}

async function parseOpenAICompatibleJson(resp: Response, providerTag: string): Promise<any> {
  const text = await resp.text();
  if (!resp.ok) {
    throw new Error(`${providerTag} ${resp.status}: ${truncate(text, 240)}`);
  }
  try {
    return JSON.parse(text);
  } catch {
    throw new Error(`${providerTag} non-JSON response: ${truncate(text, 200)}`);
  }
}

async function bytesFromOpenAICompatibleData(data: any, providerTag: string, requestInit: MediaRequestInit = {}): Promise<Buffer> {
  const entry = data && Array.isArray(data.data) ? data.data[0] : null;
  if (!entry) throw new Error(`${providerTag} response had no data[0]`);
  if (typeof entry.b64_json === 'string' && entry.b64_json) {
    const raw = entry.b64_json.includes(',')
      ? entry.b64_json.slice(entry.b64_json.indexOf(',') + 1)
      : entry.b64_json;
    return Buffer.from(raw, 'base64');
  }
  if (typeof entry.url === 'string' && entry.url) {
    const mediaResp = await fetch(entry.url, requestInit);
    if (!mediaResp.ok) {
      throw new Error(`${providerTag} media fetch ${mediaResp.status}`);
    }
    const arr = await mediaResp.arrayBuffer();

View on GitHub (pinned to 5be4028344)

Solutions

  1. Inspect the inlined body snippet to identify what is returning non-JSON (HTML page, plain text, SSE chunk).
  2. Point baseUrl at the gateway's JSON API root, not its UI or docs host.
  3. Bypass or reconfigure intercepting proxies for the gateway host.
  4. If the gateway streams, switch to a model/endpoint that returns a single JSON body, or extend the renderer to parse the stream.

Example fix

// before
OD_IMAGEROUTER_BASE_URL=https://imagerouter.io/
// after (API root, not marketing site)
OD_IMAGEROUTER_BASE_URL=https://api.imagerouter.io/v1/openai
Defensive patterns

Strategy: try-catch

Validate before calling

function isCompatibleJsonEndpoint(baseUrl: string): boolean {
  return /\/v\d+$/.test(baseUrl) || /api\./.test(baseUrl);
}

Try / catch

try {
  return await parseOpenAICompatibleJson(resp, providerTag);
} catch (err) {
  const m = err instanceof Error ? err.message : '';
  if (m.includes('non-JSON response')) {
    throw new ConfigError(`${providerTag} returned non-JSON. Check baseUrl and intercepting proxies. Body: ${m}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Gateway returns an HTML block/landing page with HTTP 200; corporate proxy intercepts and rewrites the response; baseUrl points at a browser UI rather than the API; the gateway returns SSE or plain text on success for some models.

Common situations: Wrong baseUrl host; TLS-intercepting proxy; gateway that streams responses instead of returning a single JSON document.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/40847f39b47b7c94. Report an issue: GitHub.