Files
claude-code/src/utils/cacheStats.ts
claude-code-best efaf4afd9c feat: 添加 Provider Registry、StatusLine、Cache Stats 和其他增强
- providerRegistry: OpenAI 兼容 provider 切换(Cerebras/Groq/DeepSeek/Qwen)
- StatusLine: 增强状态栏(缓存命中率、TTL 倒计时、自定义 shell 命令)
- cacheStats: 缓存命中率和 token 签名追踪
- ultrareviewPreflight: 代码审查预检服务
- SkillsMenu/filterSkills: 技能菜单过滤增强
- MagicDocs/langfuse prompts: 提示词更新
- claude.ts: API 客户端更新

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
2026-05-09 23:04:35 +08:00

110 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createHash } from 'node:crypto'
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { getClaudeConfigHomeDir } from './envUtils.js'
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface CacheUsage {
input_tokens: number
cache_creation_input_tokens: number
cache_read_input_tokens: number
}
export interface CacheStatsState {
version: 1
signature: string | null
lastResetAt: number | null // ms epoch; reset when signature changes
lastHitRate: number | null // persisted fallback
}
// ---------------------------------------------------------------------------
// Pure functions
// ---------------------------------------------------------------------------
/**
* Compute integer hit rate (0100) or null if denominator is zero / input null.
*/
export function computeHitRate(u: CacheUsage | null): number | null {
if (!u) return null
const denom =
u.input_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens
if (denom === 0) return null
return Math.round((u.cache_read_input_tokens / denom) * 100)
}
/**
* Stable string that uniquely identifies a usage snapshot.
* A change in signature means a new API response arrived — reset the TTL clock.
*/
export function tokenSignature(u: CacheUsage): string {
return `${u.input_tokens}|${u.cache_creation_input_tokens}|${u.cache_read_input_tokens}`
}
// ---------------------------------------------------------------------------
// State file I/O
// ---------------------------------------------------------------------------
/**
* Deterministic, short file name derived from sessionId so that:
* - Different sessions never collide.
* - The raw session id is never written to disk.
*/
export function getStateFilePath(sessionId: string): string {
const hash = createHash('sha256').update(sessionId).digest('hex').slice(0, 16)
return join(getClaudeConfigHomeDir(), 'cache-stats', `${hash}.json`)
}
const INIT_STATE: CacheStatsState = {
version: 1,
signature: null,
lastResetAt: null,
lastHitRate: null,
}
function isValidState(obj: unknown): obj is CacheStatsState {
if (typeof obj !== 'object' || obj === null) return false
const s = obj as Record<string, unknown>
return (
s['version'] === 1 &&
(s['signature'] === null || typeof s['signature'] === 'string') &&
(s['lastResetAt'] === null || typeof s['lastResetAt'] === 'number') &&
(s['lastHitRate'] === null || typeof s['lastHitRate'] === 'number')
)
}
/**
* Read state file. Returns init defaults on any error (corrupt, missing, etc.).
*/
export async function readState(filePath: string): Promise<CacheStatsState> {
try {
const raw = await readFile(filePath, 'utf8')
const parsed: unknown = JSON.parse(raw)
if (isValidState(parsed)) return parsed
return { ...INIT_STATE }
} catch {
return { ...INIT_STATE }
}
}
/**
* Write state atomically: write to a tmp file then rename — safe against
* partial-write corruption and concurrent reads.
*/
export async function writeStateAtomic(
filePath: string,
state: CacheStatsState,
): Promise<void> {
const dir = dirname(filePath)
await mkdir(dir, { recursive: true })
const tmp = `${filePath}.${process.pid}.tmp`
try {
await writeFile(tmp, JSON.stringify(state), 'utf8')
await rename(tmp, filePath)
} catch {
// Best-effort; silently ignore errors so the UI never crashes
}
}