{"record":{"id":"f64edccd37ce85ca","repo":"can1357/oh-my-pi","slug":"google-api-returned-an-empty-response-body","errorCode":null,"errorMessage":"Google API returned an empty response body","messagePattern":"Google API returned an empty response body","errorType":"exception","errorClass":"AIError.ProviderResponseError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/google-shared.ts","lineNumber":990,"sourceCode":"\t\t\tconst bodyJson = JSON.stringify(paramsToWireBody(params));\n\t\t\tconst fetchImpl = plan.fetch ?? options?.fetch ?? (globalThis.fetch.bind(globalThis) as FetchImpl);\n\t\t\tconst openStreamAt = async (requestUrl: string): Promise<ReadableStream<Uint8Array>> => {\n\t\t\t\tconst response = await fetchImpl(requestUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: { ...plan.headers, \"Content-Type\": \"application/json\", Accept: \"text/event-stream\" },\n\t\t\t\t\tbody: bodyJson,\n\t\t\t\t\tsignal: options?.signal,\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst errorText = await response.text().catch(() => \"\");\n\t\t\t\t\tthrow new AIError.GoogleApiError(\n\t\t\t\t\t\t`Google API error (${response.status}): ${extractGoogleErrorMessage(errorText)}`,\n\t\t\t\t\t\tresponse.status,\n\t\t\t\t\t\t{ headers: response.headers },\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!response.body) {\n\t\t\t\t\tthrow new AIError.ProviderResponseError(\"Google API returned an empty response body\", {\n\t\t\t\t\t\tprovider: model.provider,\n\t\t\t\t\t\tkind: \"empty-body\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn response.body as ReadableStream<Uint8Array>;\n\t\t\t};\n\t\t\t// A regional Vertex endpoint 404s for models published only on the\n\t\t\t// global endpoint; retry global once so a stale/ambient region never\n\t\t\t// breaks a request that worked before regional routing existed.\n\t\t\tconst openStream = async (): Promise<ReadableStream<Uint8Array>> => {\n\t\t\t\tif (!plan.fallbackUrl) return openStreamAt(plan.url);\n\t\t\t\ttry {\n\t\t\t\t\treturn await openStreamAt(plan.url);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof AIError.GoogleApiError && error.status === 404) {\n\t\t\t\t\t\treturn openStreamAt(plan.fallbackUrl);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;","sourceCodeStart":972,"sourceCodeEnd":1008,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/google-shared.ts#L972-L1008","documentation":"streamGoogleGenAI received a response with ok status but a null/undefined body, so there is no SSE stream to consume. The library throws AIError.ProviderResponseError with kind \"empty-body\" because a successful streaming response always has a body.","triggerScenarios":"HTTP 200 with empty body — typically caused by an intermediary (proxy, service worker, mocked fetch in tests) stripping the body, or an edge node returning an empty success.","commonSituations":"Tests with incomplete fetch mocks (status set but body omitted); corporate proxies returning empty 200s; intermittent CDN/load-balancer bugs.","solutions":["If in tests, fix the fetch mock to return a ReadableStream body","Retry the request — rare in production and usually transient","Inspect proxies/middlewares between the client and Google that could drop the body","Catch AIError.ProviderResponseError with kind \"empty-body\" and fall back to a non-streaming request"],"exampleFix":"// before\n// test mock\nfetch.mockResolvedValue({ ok: true, status: 200 });\n// after\nfetch.mockResolvedValue({\n  ok: true,\n  status: 200,\n  body: new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode('data: {...}\\n\\n')); c.close(); } }),\n  headers: new Headers({ \"content-type\": \"text/event-stream\" }),\n});","handlingStrategy":"retry","validationCode":"null","typeGuard":"function hasBody(res: Response): res is Response & { body: ReadableStream<Uint8Array> } {\n  return res.body !== null && res.body !== undefined;\n}","tryCatchPattern":"try {\n  return await streamGoogle(model, params, { signal });\n} catch (err) {\n  if (err instanceof AIError.ProviderResponseError && err.context?.kind === \"empty-body\") {\n    return withBackoff(() => streamGoogle(model, params, { signal }), 2);\n  }\n  throw err;\n}","preventionTips":["In tests, always provide a real ReadableStream body in fetch mocks for streaming endpoints","Audit proxies/interceptors between client and Google for body-stripping behavior","Add a single retry for empty-body responses — rare and usually transient","Prefer non-streaming generateContent as a fallback when streaming repeatedly returns empty bodies"],"tags":["google","streaming","empty-body","network","testing"],"backgroundTag":"empty-response-body","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}