{"record":{"id":"42cfe918c4bcb32e","repo":"JuliusBrussee/caveman","slug":"cave-vercel-usage-missing","errorCode":"cave_vercel_usage_missing","errorMessage":"cave_vercel_usage_missing","messagePattern":"cave_vercel_usage_missing","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/adapters.ts","lineNumber":384,"sourceCode":"    ...input.usage,\n    costUsd: priced.catalogCostUsd,\n    usageBasis: \"provider_reported\",\n    priceBasis: \"public_catalog\",\n    evaluatedTransformIDs: [...input.request.evaluatedTransformIDs],\n    appliedTransformIDs: [...input.request.appliedTransformIDs],\n    recoveryResolved: input.request.recoveryResolved,\n    latencyMs: input.latencyMs,\n  };\n}\n\nfunction usageFromAISDK(value: unknown, reasoningRequired: boolean): NormalizedUsage {\n  const usage = record(value, \"cave_vercel_usage_missing\");\n  const input = record(usage.inputTokenDetails, \"cave_vercel_usage_missing\");\n  const output = record(usage.outputTokenDetails, \"cave_vercel_usage_missing\");\n  const totalInput = strictInteger(usage.inputTokens, \"cave_vercel_usage_missing\");\n  const cacheRead = optionalInteger(input.cacheReadTokens, \"cave_vercel_usage_missing\");\n  const cacheWrite = optionalInteger(input.cacheWriteTokens, \"cave_vercel_usage_missing\");\n  if (cacheRead + cacheWrite > totalInput) throw new Error(\"cave_vercel_usage_missing\");\n  const noCache = input.noCacheTokens === undefined\n    ? totalInput - cacheRead - cacheWrite\n    : strictInteger(input.noCacheTokens, \"cave_vercel_usage_missing\");\n  if (noCache + cacheRead + cacheWrite !== totalInput) {\n    throw new Error(\"cave_vercel_usage_missing\");\n  }\n  const normalized = normalizeUsage({\n    inputTokens: noCache,\n    outputTokens: usage.outputTokens,\n    cacheReadTokens: cacheRead,\n    cacheWriteTokens: cacheWrite,\n    reasoningTokens: output.reasoningTokens,\n    totalTokens: usage.totalTokens,\n  }, reasoningRequired, \"cave_vercel_usage_missing\");\n  return normalized;\n}\n\nfunction usageFromMastra(value: unknown, reasoningRequired: boolean): NormalizedUsage {","sourceCodeStart":366,"sourceCodeEnd":402,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/adapters.ts#L366-L402","documentation":"`usageFromAISDK` normalizes the AI SDK's `LanguageModelUsage.v4` shape and throws `cave_vercel_usage_missing` when the raw usage is not a record, when `usage.inputTokenDetails` or `usage.outputTokenDetails` are missing/not records, when `usage.inputTokens` is not a non-negative safe integer, when the cache fields inside `inputTokenDetails` are not integers — or (line 384) when `cacheReadTokens + cacheWriteTokens` already exceeds the reported total `inputTokens`. The adapter demands complete token accounting because usage feeds priced evidence.","triggerScenarios":"A provider integration that omits `inputTokenDetails`/`outputTokenDetails` entirely; `usage.inputTokens` missing on an error-adjacent path; a provider reporting `cacheReadTokens` greater than its own `inputTokens`.","commonSituations":"Community AI SDK providers that never emit v4 usage details; a different `ai` package version with a changed usage shape (the adapter pins 7.0.43 exactly); middleware (caching, tracing) stripping or replacing `response.usage`.","solutions":["Run a raw `agent.generate` and assert the response carries `usage.inputTokens` plus both `*TokenDetails` records with integer values","Pin `ai@7.0.43` and use a provider implementation that emits v4 usage details","Remove or fix middleware that mutates/strips `response.usage`","If cache fields exceed input, capture the raw usage object and report it to the provider package — do not guess which number is right"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// pre-flight: assert the provider emits LanguageModelUsage.v4 with all fields\nconst { usage } = await agent.generate({ prompt: 'ping' }) as { usage: any };\nconst ok = usage && Number.isSafeInteger(usage.inputTokens) &&\n  usage.inputTokenDetails && usage.outputTokenDetails &&\n  (!usage.inputTokenDetails.cacheReadTokens ||\n   usage.inputTokenDetails.cacheReadTokens + (usage.inputTokenDetails.cacheWriteTokens ?? 0) <= usage.inputTokens);\nif (!ok) throw new Error('provider does not emit complete v4 usage details');","typeGuard":"function hasV4Usage(u: unknown): boolean {\n  if (typeof u !== 'object' || u === null) return false;\n  const s = u as Record<string, any>;\n  return Number.isSafeInteger(s.inputTokens) &&\n    typeof s.inputTokenDetails === 'object' && s.inputTokenDetails !== null &&\n    typeof s.outputTokenDetails === 'object' && s.outputTokenDetails !== null;\n}","tryCatchPattern":null,"preventionTips":["Smoke-test one raw generate and assert usage shape before adopting a provider","Pin ai@7.0.43 exactly — other versions change the usage contract","Don't let middleware rewrite or strip response.usage"],"tags":["typescript","vercel-ai-sdk","token-usage","llm","accounting"],"backgroundTag":"llm-token-usage-missing","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}