import type { AssistantUsageMetric, CacheUsageTotals } from "./types.js"; /** * Prompt-cache TTL. Anthropic's explicit cache has a documented 5-minute * default; OpenAI-style implicit prefix caches (openai-completions / * openai-responses and their many compatible providers) evict on the order * of 5-10+ minutes with no committed bound, so applying the 5-minute * constant to them both mislabels healthy idle gaps as cache expiry and * hides real causes behind a wrong tag. */ export const CACHE_TTL_MS = 5 * 60 * 1000; export const IMPLICIT_CACHE_TTL_MS = 10 * 60 * 1000; /** TTL that applies to a model's API protocol. */ export function cacheTtlMsForApi(api: string | undefined): number { return api === "anthropic-messages" ? CACHE_TTL_MS : IMPLICIT_CACHE_TTL_MS; } /** True when the gap to the previous turn exceeds the applicable cache TTL. */ export function isIdleExpired(idleMs: number | undefined, ttlMs: number = CACHE_TTL_MS): boolean { return idleMs !== undefined && idleMs > ttlMs; } /** * Canonical cache-hit % formula. * * Denominator = full prompt size sent on the turn. * Anthropic-style: input excludes newly-cached tokens, which arrive in * cacheWrite — so both must be included. * OpenAI-style: cacheWrite is 0, so this is backwards-compatible. */ export function computeCacheHitPercent(input: number, cacheRead: number, cacheWrite: number): number { const denominator = input + cacheRead + cacheWrite; if (denominator <= 0) return 0; return (cacheRead / denominator) * 100; } export function emptyTotals(): CacheUsageTotals { return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, assistantMessages: 0, }; } export function addToTotals(totals: CacheUsageTotals, message: AssistantUsageMetric): void { totals.input += message.input; totals.output += message.output; totals.cacheRead += message.cacheRead; totals.cacheWrite += message.cacheWrite; totals.totalTokens += message.totalTokens; totals.assistantMessages += 1; }