Files
wehub-resource-sync c48612c494
Docs Build / Build docs site (push) Waiting to run
Publish @openmaic packages / Build, validate & publish (push) Waiting to run
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

67 lines
2.2 KiB
TypeScript

import type { LanguageModelUsage } from 'ai';
/**
* Normalized token usage in the four billable classes plus reasoning tokens.
*
* This mirrors cc-switch's `TokenUsage` shape (input / output / cacheRead /
* cacheCreation) so the same per-class pricing model applies. `reasoningTokens`
* is carried for display/diagnostics; it is part of output tokens for billing.
*/
export interface NormalizedUsage {
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheCreationTokens: number;
reasoningTokens: number;
}
function num(value: number | undefined | null): number {
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
}
/**
* Extracts the four-class token shape from an AI SDK v6 `LanguageModelUsage`.
*
* Prefers the nested `inputTokenDetails` / `outputTokenDetails` fields and
* falls back to the deprecated flat `cachedInputTokens` / `reasoningTokens`
* for providers that only populate those. Any missing field becomes 0, so a
* partial or absent usage object yields an all-zero record rather than NaN.
*/
export function normalizeUsage(usage: LanguageModelUsage | undefined | null): NormalizedUsage {
if (!usage) {
return {
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheCreationTokens: 0,
reasoningTokens: 0,
};
}
const cacheRead = num(usage.inputTokenDetails?.cacheReadTokens) || num(usage.cachedInputTokens);
const cacheCreation = num(usage.inputTokenDetails?.cacheWriteTokens);
const reasoning = num(usage.outputTokenDetails?.reasoningTokens) || num(usage.reasoningTokens);
return {
inputTokens: num(usage.inputTokens),
outputTokens: num(usage.outputTokens),
cacheReadTokens: cacheRead,
cacheCreationTokens: cacheCreation,
reasoningTokens: reasoning,
};
}
/**
* Whether the usage has any billable tokens. Used to skip writing empty rows
* when an OpenAI-compatible upstream omits usage on a streamed response
* (mirrors cc-switch `parser.rs::has_billable_tokens`).
*/
export function hasBillableTokens(usage: NormalizedUsage): boolean {
return (
usage.inputTokens > 0 ||
usage.outputTokens > 0 ||
usage.cacheReadTokens > 0 ||
usage.cacheCreationTokens > 0
);
}