feat(ai): add totalFromProvider flag to usage cost (#11178)

Add Usage.cost.totalFromProvider to record whether cost.total is the
billed cost reported by the provider or an estimate from catalog rates.
Catalog-derived totals set it to false; a missing value also means
false. combineUsage marks a sum as provider-reported only when both
parts are.
This commit is contained in:
Vegard Stikbakke 2026-10-22 09:02:01 +02:00 • committed by GitHub
parent 58c0b02ce5
commit 7c22a93d0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 52 additions and 1 deletions

View file

@ -47,6 +47,8 @@ export function combineUsage(first: Usage, second: Usage): Usage {
cacheRead: first.cost.cacheRead + second.cost.cacheRead,
cacheWrite: first.cost.cacheWrite + second.cost.cacheWrite,
total: first.cost.total + second.cost.total,
// A sum is provider-reported only when every part is.
totalFromProvider: first.cost.totalFromProvider === true && second.cost.totalFromProvider === true,
},
};
}

View file

@ -314,7 +314,7 @@ describe("generateSummary reasoning options", () => {
input: 20,
output: 20,
totalTokens: 40,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, totalFromProvider: false },
});
});
});

View file

@ -0,0 +1,30 @@
import type { Usage } from "@earendil-works/pi-ai/compat";
import { describe, expect, it } from "vitest";
import { combineUsage } from "../src/core/usage-totals.ts";
function usage(total: number, totalFromProvider?: boolean): Usage {
return {
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total,
...(totalFromProvider === undefined ? {} : { totalFromProvider }),
},
};
}
describe("combineUsage", () => {
it("marks the combined total as provider-reported only when both totals are", () => {
expect(combineUsage(usage(1, true), usage(2, true)).cost).toMatchObject({ total: 3, totalFromProvider: true });
expect(combineUsage(usage(1, true), usage(2)).cost.totalFromProvider).toBe(false);
expect(combineUsage(usage(1), usage(2, true)).cost.totalFromProvider).toBe(false);
expect(combineUsage(usage(1), usage(2)).cost.totalFromProvider).toBe(false);
});
});