diff --git a/src/runtime/supervise/coordination-driver.test.ts b/src/runtime/supervise/coordination-driver.test.ts new file mode 100644 index 00000000..31565144 --- /dev/null +++ b/src/runtime/supervise/coordination-driver.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from 'vitest' +import { validateDriverPromptCache } from './coordination-driver' + +describe('validateDriverPromptCache', () => { + it('accepts fractional USD savings — the shape a real router reports', () => { + // The exact evidence that refused an otherwise healthy driver: tangle-router returns + // cache savings in dollars, which are fractional, and the count rule rejected them. + expect( + validateDriverPromptCache({ readTokens: 5888, writeTokens: 0, readSavingsUsd: 0.0034 }), + ).toBeUndefined() + }) + + it('still refuses a fractional TOKEN count', () => { + const error = validateDriverPromptCache({ readTokens: 12.5 }) + expect(error?.message).toContain('"readTokens"') + expect(error?.message).toContain('non-negative safe integer') + }) + + it('refuses a negative USD amount and a non-finite one', () => { + expect(validateDriverPromptCache({ readSavingsUsd: -0.01 })?.message).toContain( + 'non-negative finite number of dollars', + ) + expect(validateDriverPromptCache({ writeCostUsd: Number.NaN })?.message).toContain( + 'non-negative finite number of dollars', + ) + expect(validateDriverPromptCache({ writeCostUsd: Number.POSITIVE_INFINITY })).toBeDefined() + }) + + it('accepts a whole-dollar USD amount and -0, and keeps an absurd one out', () => { + expect(validateDriverPromptCache({ readSavingsUsd: 5 })).toBeUndefined() + expect(validateDriverPromptCache({ readSavingsUsd: -0 })).toBeUndefined() + // The old all-integer rule rejected 1e308 as a side effect; the ceiling keeps that. + expect(validateDriverPromptCache({ readSavingsUsd: 1e308 })?.message).toContain('dollars') + }) + + it('accepts the full router-reported shape including missTokens', () => { + expect( + validateDriverPromptCache({ + readTokens: 5888, + writeTokens: 0, + missTokens: 111, + readSavingsUsd: 0.0034, + status: 'hit', + }), + ).toBeUndefined() + }) + + it('refuses a negative token count and ignores string fields', () => { + expect(validateDriverPromptCache({ readTokens: -1 })?.message).toContain('safe integer') + expect(validateDriverPromptCache({ tier: 'ephemeral-1h' })).toBeUndefined() + }) + + it('treats absent evidence as acceptable rather than inventing zeroes', () => { + expect(validateDriverPromptCache(undefined)).toBeUndefined() + expect(validateDriverPromptCache({})).toBeUndefined() + }) + + it('classifies known schema members by what they are, not by their name', () => { + // readSavingsUsd is the schema's only dollar member; the token members stay counts even + // though nothing in their names says so. + expect(validateDriverPromptCache({ missTokens: 12.5 })?.message).toContain('safe integer') + expect(validateDriverPromptCache({ readSavingsUsd: 0.5 })).toBeUndefined() + }) + + it('falls back to the usd name-suffix convention for unknown pass-through fields', () => { + expect(validateDriverPromptCache({ savingsUSD: 1.25 })).toBeUndefined() + // `usdTokens` is a count despite carrying the substring — the suffix is what decides. + expect(validateDriverPromptCache({ usdTokens: 1.25 })?.message).toContain('safe integer') + }) +}) diff --git a/src/runtime/supervise/coordination-driver.ts b/src/runtime/supervise/coordination-driver.ts index ecfdc79d..db340fc1 100644 --- a/src/runtime/supervise/coordination-driver.ts +++ b/src/runtime/supervise/coordination-driver.ts @@ -296,6 +296,53 @@ function providerAttemptEvidence(model: string | undefined): ProviderModelExecut ) } +/** The USD-denominated members of {@link PromptCacheUsage} — the schema, not a guess. Every + * other known member (`readTokens`, `writeTokens`, `missTokens`) is a token COUNT. */ +const PROMPT_CACHE_USD_FIELDS: ReadonlySet = new Set(['readSavingsUsd']) + +/** Dollar amounts above this are provider nonsense, not evidence. The old all-integer rule + * rejected them as a side effect; keeping an explicit ceiling preserves that protection + * without pretending a dollar amount is an integer. */ +const MAX_PROMPT_CACHE_USD = 1_000_000 + +/** + * Validate provider-reported prompt-cache evidence. + * + * Prompt-cache carries two kinds of number and they obey different rules: token COUNTS are + * integers, and USD amounts are fractional by nature. Applying the count rule to a dollar + * field refuses every provider that reports cache savings in dollars — a healthy router + * response carrying `readSavingsUsd: 0.0034` failed the driver outright before this split. + * + * Classification is schema-first: a field named in {@link PromptCacheUsage} is validated by + * what that member IS. `promptCache` is an open record (the sandbox path forwards provider + * fields verbatim), so an unknown field falls back to the `usd` name-suffix convention — + * documented here as the contract a provider must follow to report dollars. + * + * Returns the refusal, or `undefined` when the evidence is acceptable. + */ +export function validateDriverPromptCache( + promptCache: Readonly> | undefined, +): ValidationError | undefined { + for (const [field, value] of Object.entries(promptCache ?? {})) { + if (typeof value !== 'number') continue + const isUsdField = PROMPT_CACHE_USD_FIELDS.has(field) || /usd$/i.test(field) + if (isUsdField) { + if (!Number.isFinite(value) || value < 0 || value > MAX_PROMPT_CACHE_USD) { + return new ValidationError( + `driverAgent: prompt-cache field ${JSON.stringify(field)} must be a non-negative finite number of dollars`, + ) + } + continue + } + if (!Number.isSafeInteger(value) || value < 0) { + return new ValidationError( + `driverAgent: prompt-cache field ${JSON.stringify(field)} must be a non-negative safe integer`, + ) + } + } + return undefined +} + /** * Build the intelligent recursive driver. Its `act` is the LLM tool-loop; spawn it as a * `driverChild` (`driver-executor.ts`) to run it inside a nested scope, recursively. @@ -496,14 +543,7 @@ export function driverAgent(opts: DriverAgentOptions): Agent { 'driverAgent: transportAttempts must be a positive safe integer when reported', ) } - for (const [field, value] of Object.entries(res.promptCache ?? {})) { - if (typeof value === 'number' && (!Number.isSafeInteger(value) || value < 0)) { - evidenceError = new ValidationError( - `driverAgent: prompt-cache field ${JSON.stringify(field)} must be a non-negative safe integer`, - ) - break - } - } + evidenceError = validateDriverPromptCache(res.promptCache) ?? evidenceError const trustedCost = res.costProvenance === 'provider-receipt' || res.costProvenance === 'billing-receipt' const cacheUsage = driverPromptCacheUsage(res.usage?.input, res.promptCache)