import { describe, it, expect } from 'vitest'; import { stepEndedToUsage } from '../opencode-usage.js'; describe('stepEndedToUsage (U.6)', () => { it('folds cache read+write into input and reasoning into output', () => { const u = stepEndedToUsage({ cost: 0.0123, tokens: { input: 100, output: 50, reasoning: 20, cache: { read: 10, write: 5 } }, }); expect(u).toEqual({ input: 115, output: 70, cost: 0.0123 }); }); it('handles a step with no cache and no reasoning', () => { const u = stepEndedToUsage({ cost: 0, tokens: { input: 8, output: 4, reasoning: 0, cache: { read: 0, write: 0 } }, }); expect(u).toEqual({ input: 8, output: 4, cost: 0 }); }); it('is defensive against a missing tokens block', () => { const u = stepEndedToUsage({ cost: 0.5 } as never); expect(u).toEqual({ input: 0, output: 0, cost: 0.5 }); }); it('is defensive against undefined props', () => { expect(stepEndedToUsage(undefined)).toEqual({ input: 0, output: 0, cost: 0 }); }); it('drops NaN / negative noise to zero rather than poisoning the accumulated total', () => { const u = stepEndedToUsage({ cost: Number.NaN, tokens: { input: -5, output: Number.NaN, reasoning: 3, cache: { read: Number.POSITIVE_INFINITY, write: 2 }, }, }); // input: (-5→0) + (Inf→0) + 2 = 2; output: (NaN→0) + 3 = 3; cost: NaN→0 expect(u).toEqual({ input: 2, output: 3, cost: 0 }); }); it('rounds fractional token counts', () => { const u = stepEndedToUsage({ cost: 1.5, tokens: { input: 10.6, output: 4.4, reasoning: 0, cache: { read: 0, write: 0 } }, }); expect(u).toEqual({ input: 11, output: 4, cost: 1.5 }); }); });