mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 13:22:11 +03:00
- Fix getLoggedInputTokens to return full prompt_tokens (input + cache_read + cache_creation) - Fix usageExtractor for non-streaming Claude responses to calculate total correctly - Add formatUsageLog helper to show CR=<cache_read> in logs - Add migration 012 to fix historical token counts in usage_history - Move prompt cache metrics from Settings to /dashboard/cache page Per Claude API docs: Total input tokens = input_tokens + cache_creation_input_tokens + cache_read_input_tokens Fixes issue where totalInputTokens (71k) was less than totalCacheCreationTokens (1.35M). Tested: - All 1134 unit tests pass - Cache metrics API returns correct totals - Migration is idempotent and tracked in _omniroute_migrations - Logs show cache read tokens: 'in=6055 | out=211 | CR=22399' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
type JsonRecord = Record<string, unknown>;
|
|
|
|
function asRecord(value: unknown): JsonRecord {
|
|
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
|
|
}
|
|
|
|
function toFiniteNumber(value: unknown): number {
|
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
if (typeof value === "string" && value.trim().length > 0) {
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function getPromptTokenDetails(tokens: unknown): JsonRecord {
|
|
const tokenRecord = asRecord(tokens);
|
|
const promptDetails = asRecord(tokenRecord.prompt_tokens_details);
|
|
if (Object.keys(promptDetails).length > 0) return promptDetails;
|
|
return asRecord(tokenRecord.input_tokens_details);
|
|
}
|
|
|
|
export function getPromptCacheReadTokens(tokens: unknown): number {
|
|
const tokenRecord = asRecord(tokens);
|
|
const promptDetails = getPromptTokenDetails(tokenRecord);
|
|
return toFiniteNumber(
|
|
tokenRecord.cacheRead ??
|
|
tokenRecord.cache_read_input_tokens ??
|
|
tokenRecord.cached_tokens ??
|
|
promptDetails.cached_tokens
|
|
);
|
|
}
|
|
|
|
export function getPromptCacheCreationTokens(tokens: unknown): number {
|
|
const tokenRecord = asRecord(tokens);
|
|
const promptDetails = getPromptTokenDetails(tokenRecord);
|
|
return toFiniteNumber(
|
|
tokenRecord.cacheCreation ??
|
|
tokenRecord.cache_creation_input_tokens ??
|
|
promptDetails.cache_creation_tokens
|
|
);
|
|
}
|
|
|
|
export function getLoggedInputTokens(tokens: unknown): number {
|
|
const tokenRecord = asRecord(tokens);
|
|
|
|
if (tokenRecord.input !== undefined && tokenRecord.input !== null) {
|
|
return toFiniteNumber(tokenRecord.input);
|
|
}
|
|
|
|
if (tokenRecord.input_tokens !== undefined && tokenRecord.input_tokens !== null) {
|
|
return toFiniteNumber(tokenRecord.input_tokens);
|
|
}
|
|
|
|
// prompt_tokens from translator already includes input + cache_read + cache_creation
|
|
// Do NOT subtract cached tokens - we want the total billable prompt tokens
|
|
const promptTokens = toFiniteNumber(tokenRecord.prompt_tokens);
|
|
return promptTokens;
|
|
}
|
|
|
|
export function getLoggedOutputTokens(tokens: unknown): number {
|
|
const tokenRecord = asRecord(tokens);
|
|
if (tokenRecord.output !== undefined && tokenRecord.output !== null) {
|
|
return toFiniteNumber(tokenRecord.output);
|
|
}
|
|
return toFiniteNumber(tokenRecord.completion_tokens ?? tokenRecord.output_tokens);
|
|
}
|
|
|
|
export function formatUsageLog(tokens: unknown): string {
|
|
const input = getLoggedInputTokens(tokens);
|
|
const output = getLoggedOutputTokens(tokens);
|
|
const cacheRead = getPromptCacheReadTokens(tokens);
|
|
|
|
let msg = `in=${input} | out=${output}`;
|
|
if (cacheRead > 0) {
|
|
msg += ` | CR=${cacheRead}`;
|
|
}
|
|
return msg;
|
|
}
|