{"record":{"id":"c12fa818ca203b46","repo":"JuliusBrussee/caveman","slug":"cave-provider-usage-incomplete","errorCode":"cave_provider_usage_incomplete","errorMessage":"cave_provider_usage_incomplete","messagePattern":"cave_provider_usage_incomplete","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/execution-kernel.ts","lineNumber":150,"sourceCode":"  }\n  if (options.expected !== undefined &&\n      (evidence.provider !== options.expected.provider || evidence.model !== options.expected.model)) {\n    throw new Error(\"cave_provider_model_identity_mismatch\");\n  }\n  const values = [\n    evidence.inputTokens,\n    evidence.outputTokens,\n    evidence.cacheReadTokens,\n    evidence.cacheWriteTokens,\n    evidence.reasoningTokens,\n    evidence.totalTokens,\n  ];\n  const disjointTotal = evidence.inputTokens + evidence.outputTokens +\n    evidence.cacheReadTokens + evidence.cacheWriteTokens;\n  if (values.some((value) => !Number.isSafeInteger(value) || value < 0) ||\n      evidence.totalTokens <= 0 || evidence.totalTokens !== disjointTotal ||\n      evidence.reasoningTokens > evidence.outputTokens) {\n    throw new Error(\"cave_provider_usage_incomplete\");\n  }\n  const priced = catalogCost({\n    provider: evidence.provider,\n    model: evidence.model,\n    inputTokens: evidence.inputTokens,\n    outputTokens: evidence.outputTokens,\n    cacheReadTokens: evidence.cacheReadTokens,\n    cacheWriteTokens: evidence.cacheWriteTokens,\n    reasoningTokens: evidence.reasoningTokens,\n  });\n  if (!priced.priced && options.requirePriced === true) {\n    throw new Error(\"cave_provider_usage_unpriced\");\n  }\n  if (options.reportedCostUsd !== undefined &&\n      (!priced.priced || !Number.isFinite(options.reportedCostUsd) ||\n        options.reportedCostUsd < 0 ||\n        Math.abs(options.reportedCostUsd - priced.usd) > 1e-12)) {\n    throw new Error(\"cave_provider_cost_mismatch\");","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/execution-kernel.ts#L132-L168","documentation":"Thrown by validateProviderUsage (packages/agent/src/execution-kernel.ts:150): the token usage evidence is internally inconsistent or incomplete — a count is not a safe non-negative integer, totalTokens is <= 0, totalTokens does not equal input+output+cacheRead+cacheWrite, or reasoningTokens exceeds outputTokens. Complete usage is mandatory for reserved turns; arithmetic that does not add up is rejected.","triggerScenarios":"Passing usage where any of the six token fields is fractional, negative, undefined (NaN), or exceeds Number.MAX_SAFE_INTEGER; totalTokens set to 0 or omitted; totals that don't equal the sum of the disjoint components; reasoning tokens larger than output tokens.","commonSituations":"Adapters that omit cache/reasoning fields (undefined -> NaN in the sum) instead of defaulting them to 0; aggregating usage across streaming chunks and losing/adding tokens; providers reporting reasoning tokens counted outside output; hand-built usage objects in tests with totalTokens: 0.","solutions":["Default missing optional counters (cacheRead, cacheWrite, reasoning) to 0 when building evidence rather than passing undefined.","Recompute totalTokens as input+output+cacheRead+cacheWrite before validating, and ensure totalTokens > 0 for a real call.","For reasoning-capable models, make sure reasoningTokens is a subset of outputTokens as reported; if the provider reports them disjoint, map them into the fields the validator expects."],"exampleFix":"// before\nvalidateProviderUsage({ provider, model, inputTokens: 10, outputTokens: 5 }); // missing fields -> NaN\n\n// after\nvalidateProviderUsage({ provider, model, inputTokens: 10, outputTokens: 5, cacheReadTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0, totalTokens: 15 });","handlingStrategy":"validation","validationCode":"function normalizeUsage(u: Partial<ProviderUsageEvidence>): ProviderUsageEvidence {\n  const n = (v: number | undefined) => (Number.isSafeInteger(v) && v! >= 0 ? v! : 0);\n  const input = n(u.inputTokens), output = n(u.outputTokens);\n  const cacheRead = n(u.cacheReadTokens), cacheWrite = n(u.cacheWriteTokens);\n  const reasoning = Math.min(n(u.reasoningTokens), output);\n  return { provider: u.provider!, model: u.model!, inputTokens: input, outputTokens: output,\n    cacheReadTokens: cacheRead, cacheWriteTokens: cacheWrite, reasoningTokens: reasoning,\n    totalTokens: input + output + cacheRead + cacheWrite };\n}\nvalidateProviderUsage(normalizeUsage(rawUsage));","typeGuard":"function isConsistentUsage(e: ProviderUsageEvidence): boolean {\n  const vals = [e.inputTokens, e.outputTokens, e.cacheReadTokens, e.cacheWriteTokens, e.reasoningTokens, e.totalTokens];\n  return vals.every((v) => Number.isSafeInteger(v) && v >= 0) &&\n    e.totalTokens > 0 &&\n    e.totalTokens === e.inputTokens + e.outputTokens + e.cacheReadTokens + e.cacheWriteTokens &&\n    e.reasoningTokens <= e.outputTokens;\n}","tryCatchPattern":null,"preventionTips":["Default absent cache/reasoning counters to 0 instead of passing undefined.","Recompute totalTokens as the disjoint sum instead of trusting a separately reported total.","Unit-test usage mappers with partial provider responses."],"tags":["usage","tokens","validation","billing"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}