{"record":{"id":"a25958538730f159","repo":"can1357/oh-my-pi","slug":"anthropic-cache-refresh-response-omitted-usage","errorCode":null,"errorMessage":"Anthropic cache refresh response omitted usage","messagePattern":"Anthropic cache refresh response omitted usage","errorType":"exception","errorClass":"AnthropicStreamEnvelopeError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/anthropic.ts","lineNumber":2063,"sourceCode":"\t\t\t\t};\n\t\t\t\tconst request: unknown =\n\t\t\t\t\tisOAuthToken && client.beta\n\t\t\t\t\t\t? client.beta.messages.create(refreshParams, requestOptions)\n\t\t\t\t\t\t: client.messages.create(refreshParams, requestOptions);\n\t\t\t\tif (!hasAnthropicRawResponseRequest(request)) {\n\t\t\t\t\tthrow new AIError.AnthropicStreamEnvelopeError(\n\t\t\t\t\t\t\"Anthropic cache refresh request did not expose a raw response\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst response = await request.asResponse();\n\t\t\t\tawait notifyProviderResponse(options, response, model, response.headers.get(\"request-id\"));\n\t\t\t\tconst body: unknown = await response.json();\n\t\t\t\tif (!isRecord(body)) {\n\t\t\t\t\tthrow new AIError.AnthropicStreamEnvelopeError(\"Anthropic cache refresh returned a malformed response\");\n\t\t\t\t}\n\t\t\t\tconst wireUsage = parseAnthropicWireUsage(body.usage);\n\t\t\t\tif (!wireUsage) {\n\t\t\t\t\tthrow new AIError.AnthropicStreamEnvelopeError(\"Anthropic cache refresh response omitted usage\");\n\t\t\t\t}\n\t\t\t\tif (typeof body.id === \"string\") output.responseId = body.id;\n\t\t\t\toutput.usage.input = wireUsage.input_tokens ?? 0;\n\t\t\t\toutput.usage.output = wireUsage.output_tokens ?? 0;\n\t\t\t\toutput.usage.cacheRead = wireUsage.cache_read_input_tokens ?? 0;\n\t\t\t\toutput.usage.cacheWrite = wireUsage.cache_creation_input_tokens ?? 0;\n\t\t\t\tapplyAnthropicUsageExtras(output.usage, wireUsage);\n\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\toutput.duration = performance.now() - startTime;\n\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\tstream.push({ type: \"done\", reason: \"stop\", message: output });\n\t\t\t\tstream.end();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Opt-in flag: the response parser only honors `fallback` content","sourceCodeStart":2045,"sourceCodeEnd":2081,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/anthropic.ts#L2045-L2081","documentation":"This AnthropicStreamEnvelopeError is thrown when the cache-refresh response body is a valid JSON object but has no parsable usage field. The whole point of the cache-refresh call is to obtain up-to-date token/cache usage numbers, so a response without usage is useless and the library fails rather than reporting zeros.","triggerScenarios":"The refresh response's `usage` field is missing, null, or lacks the expected counters (input_tokens/output_tokens) so parseAnthropicWireUsage returns null — e.g. a non-Anthropic endpoint or an error-shaped object returned with 200.","commonSituations":"Gateways that reshape or strip Anthropic responses; provider emulators (local LLM gateways) that omit usage; API version changes altering the usage schema; mocks that forget the usage object.","solutions":["Ensure the endpoint is a genuine Anthropic Messages API that returns a usage object with input_tokens/output_tokens.","If using a gateway/emulator, upgrade it to one that forwards the Anthropic usage fields.","Check the configured API version header for schema changes to `usage`.","Update test mocks to include usage: { input_tokens, output_tokens } (plus cache counters when relevant)."],"exampleFix":"// before\n{ id: \"msg_1\", type: \"message\", role: \"assistant\" } // no usage\n\n// after\n{\n  id: \"msg_1\", type: \"message\", role: \"assistant\",\n  usage: { input_tokens: 10, output_tokens: 5, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 },\n}","handlingStrategy":"validation","validationCode":"const body = await response.json();\nconst usage = body?.usage;\nif (typeof usage !== \"object\" || usage === null ||\n    (typeof usage.input_tokens !== \"number\" && typeof usage.output_tokens !== \"number\")) {\n  throw new Error(\"Endpoint did not return Anthropic-style usage; check gateway/provider compatibility.\");\n}","typeGuard":"function hasWireUsage(body: unknown): body is { usage: { input_tokens?: number; output_tokens?: number; cache_read_input_tokens?: number; cache_creation_input_tokens?: number } } {\n  return isRecord(body) && isRecord(body.usage) &&\n    (\"input_tokens\" in body.usage || \"output_tokens\" in body.usage);\n}","tryCatchPattern":"try {\n  const usage = await refreshCacheUsage(params);\n  return usage;\n} catch (err) {\n  if (err instanceof AIError.AnthropicStreamEnvelopeError && /omitted usage/.test(err.message)) {\n    return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; // degrade gracefully\n  } else {\n    throw err;\n  }\n}","preventionTips":["Test gateways/emulators for usage passthrough before adopting them.","Pin the Anthropic API version header and review changelogs for usage schema changes.","Include usage fields (input_tokens, output_tokens, cache counters) in all refresh-response mocks.","Monitor for endpoints that silently drop optional fields after upgrades."],"tags":["anthropic","cache-refresh","usage","schema"],"backgroundTag":"missing-usage-in-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}