{"record":{"id":"75a2b3512f097d8a","repo":"nexu-io/open-design","slug":"aihubmix-image-gemini-resp-status-text-sli","errorCode":null,"errorMessage":"aihubmix image (gemini) ${resp.status}: ${text.slice(0, 240)}","messagePattern":"aihubmix image \\(gemini\\) (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/integrations/aihubmix.ts","lineNumber":145,"sourceCode":"  const resp = await doFetch(url, {\n    method: 'POST',\n    redirect: 'error',\n    headers: {\n      'content-type': 'application/json',\n      'x-goog-api-key': req.apiKey,\n      ...aihubmixAppCodeHeader(),\n    },\n    body: JSON.stringify({\n      contents: [{ role: 'user', parts: [{ text: req.prompt }] }],\n      generationConfig: {\n        responseModalities: ['TEXT', 'IMAGE'],\n        imageConfig: { aspectRatio: req.aspect },\n      },\n    }),\n  });\n  if (!resp.ok) {\n    const text = await resp.text().catch(() => '');\n    throw new Error(`aihubmix image (gemini) ${resp.status}: ${text.slice(0, 240)}`);\n  }\n  const data = (await resp.json()) as any;\n  const parts: any[] = data?.candidates?.[0]?.content?.parts ?? [];\n  const b64 = parts\n    .map((p) => p?.inlineData?.data || p?.inline_data?.data)\n    .find((d) => typeof d === 'string' && d);\n  if (!b64) {\n    throw new Error(\n      `aihubmix gemini image response had no inline image data: ${JSON.stringify(data).slice(0, 200)}`,\n    );\n  }\n  return Buffer.from(b64, 'base64');\n}\n\n// Catalogue ids vs wire names. The media registry requires globally-unique\n// model ids, but `gpt-image-1` / `dall-e-3` / `tts-1` are already owned by the\n// `openai` provider. So AIHubMix's models are registered with an `aihubmix-`\n// prefix and mapped back to the real upstream name here. A plain prefix strip","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/integrations/aihubmix.ts#L127-L163","documentation":"Plain Error thrown by aihubmixGeminiImageBytes when the AIHubMix Gemini-native generateContent endpoint returns a non-OK HTTP status. The error includes the status code and the first 240 chars of the response body so the upstream error (auth failure, quota, model unavailable, malformed request) is visible. This is the AIHubMix BYOK image-generation path for gemini/imagen-family models that reject the OpenAI /images/generations shape.","triggerScenarios":"Any non-2xx from POST {origin}/gemini/v1beta/models/{model}:generateContent: 401/403 (bad/revoked API key), 429 (rate limit / quota), 400 (unsupported aspect ratio, model does not support image output, malformed prompt), 404 (wrong wireModel name), 5xx (AIHubMix or upstream Gemini outage). The fetch uses redirect:'error' so a 3xx redirect also surfaces here.","commonSituations":"Expired or quota-exhausted AIHubMix API key; wrong wireModel mapping (e.g. asking a text-only gemini model for image output); aspect ratio string the model rejects; AIHubMix gateway outage; APP-Code header stripped by a proxy; the gemini model not enabled on the user's AIHubMix plan.","solutions":["Read the interpolated status + body text: 401/403 → fix API key, 429 → wait/upgrade quota, 400 → check aspectRatio and model image capability, 404 → fix wireModel.","Verify the AIHubMix key is valid at aihubmix.com and has Gemini image access on the plan.","Confirm the wireModel resolves to a real gemini/imagen image model (use the aihubmix- prefix mapping).","Check the aspect ratio string ('1:1','16:9', etc.) is supported by the target model.","Retry transient 5xx after a brief backoff; persistent failure points at config."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"async function aihubmixKeyValid(baseUrl: string, apiKey: string): Promise<boolean> {\n  try {\n    const r = await fetch(`${baseUrl.replace(/\\/$/, '')}/models`, { headers: { authorization: `Bearer ${apiKey}` } });\n    return r.ok;\n  } catch { return false; }\n}","typeGuard":"function isAihubmixImageHttpError(err: unknown): boolean {\n  return err instanceof Error && /^aihubmix image \\(gemini\\) \\d{3}:/.test(err.message);\n}\nfunction aihubmixStatus(err: unknown): number | null {\n  const m = err instanceof Error ? err.message.match(/^aihubmix image \\(gemini\\) (\\d{3}):/) : null;\n  return m ? Number(m[1]) : null;\n}","tryCatchPattern":"try {\n  return await aihubmixGeminiImageBytes(req, doFetch);\n} catch (err) {\n  const status = aihubmixStatus(err);\n  if (status && (status === 429 || status >= 500)) {\n    // transient — retry with backoff\n    return await retryWithBackoff(() => aihubmixGeminiImageBytes(req, doFetch));\n  }\n  if (status === 401 || status === 403) throw new InvalidApiKeyError('AIHubMix key rejected');\n  throw err;\n}","preventionTips":["Validate the AIHubMix key + plan covers gemini image models before the first image call.","Confirm the wireModel is an image-capable gemini/imagen variant.","Use a supported aspectRatio string for the target model.","Retry only 429/5xx; auth and 400-class errors are not transient."],"tags":["aihubmix","byok","gemini","image-generation","network","upstream"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}