补齐缓存写量与请求轮廓记账,便于核对提示缓存是否真的命中。

给用量行挂上轮次步骤和分段字数,统计页能看出缓存断在哪。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 23:47:51 +08:00
co-authored by Cursor
parent d0274afb80
commit bb4f934b3d
13 changed files with 738 additions and 50 deletions
+64 -1
View File
@@ -1,7 +1,8 @@
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { randomUUID } from "node:crypto";
import { ensureUserDataDirs, getUserDataDir } from "../config/user-data-dir.js";
import type { ContextSegment } from "../llm/context-outline.js";
export type TokenUsageRecord = {
id: string;
@@ -17,7 +18,25 @@ export type TokenUsageRecord = {
totalTokens: number;
cachedTokens?: number;
cacheMissTokens?: number;
cacheWriteTokens?: number;
messageId?: string;
turnId?: string;
step?: string;
retryIndex?: number;
segmentChars?: {
rules: number;
history: number;
products: number;
injected: number;
tail: number;
};
/** 有序标签与哈希,不含正文 */
segments?: ContextSegment[];
plannedTags?: string[];
succeededTags?: string[];
failedTags?: string[];
finish?: string;
latencyMs?: number;
};
/** Token usage attached to a chat message in SessionView */
@@ -97,7 +116,18 @@ export function recordTokenUsage(
totalTokens: partial.totalTokens,
cachedTokens: partial.cachedTokens,
cacheMissTokens: partial.cacheMissTokens,
cacheWriteTokens: partial.cacheWriteTokens,
messageId: partial.messageId,
turnId: partial.turnId,
step: partial.step,
retryIndex: partial.retryIndex,
segmentChars: partial.segmentChars,
segments: partial.segments,
plannedTags: partial.plannedTags,
succeededTags: partial.succeededTags,
failedTags: partial.failedTags,
finish: partial.finish,
latencyMs: partial.latencyMs,
};
appendFileSync(dayFile(new Date(record.at)), `${JSON.stringify(record)}\n`, "utf8");
return record;
@@ -238,6 +268,39 @@ export function getSessionTokenTotals(sessionId: string): {
};
}
export function patchTokenUsage(
id: string,
patch: Partial<
Pick<
TokenUsageRecord,
"plannedTags" | "succeededTags" | "failedTags" | "finish"
>
>,
): void {
if (!id) return;
const files = listStatFiles();
for (let i = files.length - 1; i >= 0; i--) {
const file = files[i]!;
const lines = readFileSync(file, "utf8").split(/\r?\n/);
let changed = false;
const next = lines.map((line) => {
if (!line.trim()) return line;
try {
const record = JSON.parse(line) as TokenUsageRecord;
if (record.id !== id) return line;
changed = true;
return JSON.stringify({ ...record, ...patch });
} catch {
return line;
}
});
if (changed) {
writeFileSync(file, next.join("\n"), "utf8");
return;
}
}
}
export function toMessageTokenUsage(record: TokenUsageRecord): MessageTokenUsage {
return {
totalTokens: record.totalTokens,