JuliusBrussee/caveman · error · Error
cave_provider_usage_incomplete
cave_provider_usage_incomplete
Error message
cave_provider_usage_incomplete
What it means
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.
Source
Thrown at packages/agent/src/execution-kernel.ts:150
}
if (options.expected !== undefined &&
(evidence.provider !== options.expected.provider || evidence.model !== options.expected.model)) {
throw new Error("cave_provider_model_identity_mismatch");
}
const values = [
evidence.inputTokens,
evidence.outputTokens,
evidence.cacheReadTokens,
evidence.cacheWriteTokens,
evidence.reasoningTokens,
evidence.totalTokens,
];
const disjointTotal = evidence.inputTokens + evidence.outputTokens +
evidence.cacheReadTokens + evidence.cacheWriteTokens;
if (values.some((value) => !Number.isSafeInteger(value) || value < 0) ||
evidence.totalTokens <= 0 || evidence.totalTokens !== disjointTotal ||
evidence.reasoningTokens > evidence.outputTokens) {
throw new Error("cave_provider_usage_incomplete");
}
const priced = catalogCost({
provider: evidence.provider,
model: evidence.model,
inputTokens: evidence.inputTokens,
outputTokens: evidence.outputTokens,
cacheReadTokens: evidence.cacheReadTokens,
cacheWriteTokens: evidence.cacheWriteTokens,
reasoningTokens: evidence.reasoningTokens,
});
if (!priced.priced && options.requirePriced === true) {
throw new Error("cave_provider_usage_unpriced");
}
if (options.reportedCostUsd !== undefined &&
(!priced.priced || !Number.isFinite(options.reportedCostUsd) ||
options.reportedCostUsd < 0 ||
Math.abs(options.reportedCostUsd - priced.usd) > 1e-12)) {
throw new Error("cave_provider_cost_mismatch");View on GitHub (pinned to 27d5a3981a)
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.
Example fix
// before
validateProviderUsage({ provider, model, inputTokens: 10, outputTokens: 5 }); // missing fields -> NaN
// after
validateProviderUsage({ provider, model, inputTokens: 10, outputTokens: 5, cacheReadTokens: 0, cacheWriteTokens: 0, reasoningTokens: 0, totalTokens: 15 }); Defensive patterns
Strategy: validation
Validate before calling
function normalizeUsage(u: Partial<ProviderUsageEvidence>): ProviderUsageEvidence {
const n = (v: number | undefined) => (Number.isSafeInteger(v) && v! >= 0 ? v! : 0);
const input = n(u.inputTokens), output = n(u.outputTokens);
const cacheRead = n(u.cacheReadTokens), cacheWrite = n(u.cacheWriteTokens);
const reasoning = Math.min(n(u.reasoningTokens), output);
return { provider: u.provider!, model: u.model!, inputTokens: input, outputTokens: output,
cacheReadTokens: cacheRead, cacheWriteTokens: cacheWrite, reasoningTokens: reasoning,
totalTokens: input + output + cacheRead + cacheWrite };
}
validateProviderUsage(normalizeUsage(rawUsage)); Type guard
function isConsistentUsage(e: ProviderUsageEvidence): boolean {
const vals = [e.inputTokens, e.outputTokens, e.cacheReadTokens, e.cacheWriteTokens, e.reasoningTokens, e.totalTokens];
return vals.every((v) => Number.isSafeInteger(v) && v >= 0) &&
e.totalTokens > 0 &&
e.totalTokens === e.inputTokens + e.outputTokens + e.cacheReadTokens + e.cacheWriteTokens &&
e.reasoningTokens <= e.outputTokens;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cave_provider_identity_missing
- cave_provider_model_identity_mismatch
- cave_provider_cost_mismatch
- caveman agent: artifact maxInlineTokens must be a non-negati
- caveman agent: output maxTokens must be a positive integer
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/c12fa818ca203b46.
Report an issue: GitHub.