@dsh-agent-toolkit/token-usage 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +30 -30
- package/cordis.patch.yml +7 -7
- package/lib/client.js +51252 -82
- package/lib/client.js.map +1 -1
- package/lib/index.js +51 -0
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -136,6 +136,10 @@ function dayParts(time, timeZone) {
|
|
|
136
136
|
hour: Number(get("hour")) % 24
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
+
/** 日期串加减天数(锚 UTC 正午,避开 DST)。 */
|
|
140
|
+
function shiftDate(date, days) {
|
|
141
|
+
return new Date(Date.parse(`${date}T12:00:00Z`) + days * 864e5).toISOString().slice(0, 10);
|
|
142
|
+
}
|
|
139
143
|
/** 计费 token 数自动换算 K/M/B(10 进制,1 位小数)。 */
|
|
140
144
|
function formatTokens(n) {
|
|
141
145
|
const units = [
|
|
@@ -153,6 +157,32 @@ function formatTokens(n) {
|
|
|
153
157
|
return unit === 0 ? String(n) : `${value.toFixed(1)}${units[unit]}`;
|
|
154
158
|
}
|
|
155
159
|
//#endregion
|
|
160
|
+
//#region src/heatmap.ts
|
|
161
|
+
/** token-usage 纯函数:range 端点参数与摘要、13 周热力图网格、缓存/新增拆分。无运行时依赖,两半共用。 */
|
|
162
|
+
/** 解析 range 端点 days 参数:null → 默认 91;1..366 合法;其余返回 null(非法)。 */
|
|
163
|
+
function parseDaysParam(raw) {
|
|
164
|
+
if (raw === null) return 91;
|
|
165
|
+
if (!/^\d+$/.test(raw)) return null;
|
|
166
|
+
const n = Number(raw);
|
|
167
|
+
return n >= 1 && n <= 366 ? n : null;
|
|
168
|
+
}
|
|
169
|
+
/** 以 today 为终点向前取 days 天的紧凑摘要(缺失日记 0),日期升序。 */
|
|
170
|
+
function rangeSummaries(get, today, days) {
|
|
171
|
+
return Array.from({ length: days }, (_, i) => {
|
|
172
|
+
const date = shiftDate(today, i - (days - 1));
|
|
173
|
+
const rec = get(date);
|
|
174
|
+
return rec === void 0 ? {
|
|
175
|
+
date,
|
|
176
|
+
billed: 0,
|
|
177
|
+
calls: 0
|
|
178
|
+
} : {
|
|
179
|
+
date,
|
|
180
|
+
billed: billedOf(rec.totals),
|
|
181
|
+
calls: rec.totals.calls
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
156
186
|
//#region src/render.ts
|
|
157
187
|
/** /token-usage 命令的文本视图(纯函数)。 */
|
|
158
188
|
function line(name, b) {
|
|
@@ -283,6 +313,27 @@ function apply(ctx, config) {
|
|
|
283
313
|
}));
|
|
284
314
|
}
|
|
285
315
|
}), "token-usage: /token-usage/api/daily route");
|
|
316
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
317
|
+
kind: "exact",
|
|
318
|
+
path: "/token-usage/api/range",
|
|
319
|
+
handler: async (req, res) => {
|
|
320
|
+
if (req.method !== "GET") {
|
|
321
|
+
res.writeHead(405).end();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const days = parseDaysParam(new URL(req.url ?? "", "http://127.0.0.1").searchParams.get("days"));
|
|
325
|
+
if (days === null) {
|
|
326
|
+
res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "bad days, want integer 1..366" }));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const table = await domainReady.then(() => daily);
|
|
330
|
+
const today = dayParts(Date.now(), config.timezone).date;
|
|
331
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
|
|
332
|
+
today,
|
|
333
|
+
days: rangeSummaries((d) => table.get(d), today, days)
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
}), "token-usage: /token-usage/api/range route");
|
|
286
337
|
});
|
|
287
338
|
ctx.effect(() => async () => {
|
|
288
339
|
await tail.catch(() => void 0);
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["z"],"sources":["../src/aggregate.ts","../src/render.ts","../src/store.ts","../src/index.ts"],"sourcesContent":["/** token-usage 纯函数:日期换算、K/M/B 格式化、聚合。无运行时依赖,浏览器半可内联。 */\r\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\r\nimport type { Message } from '@deepseek-ai/dsh-llm/types'\r\n// 激活 @deepseek-ai/dsh-compaction 对 SessionEventMap 的声明合并(compaction/summary 等事件类型)。\r\nimport type {} from '@deepseek-ai/dsh-compaction/types'\r\nimport type { Bucket, DailyRecord } from './store.ts'\r\n\r\n/** 一条待聚合样本;每个样本隐含 calls=1。 */\r\nexport interface UsageSample {\r\n date: string\r\n hour: number\r\n input: number\r\n output: number\r\n cacheRead: number\r\n cacheWrite: number\r\n /** 估算样本的计费 token 量;真实样本为 0。 */\r\n estimated: number\r\n estimatedCall: boolean\r\n /** 'provider/model';compaction 样本无。 */\r\n model?: string\r\n sessionId?: string\r\n cwd?: string\r\n compaction: boolean\r\n}\r\n\r\n/** 桶的计费总量(含估算)。 */\r\nexport function billedOf(b: Bucket): number {\r\n return b.input + b.output + b.cacheRead + b.cacheWrite + b.estimated\r\n}\r\n\r\nexport function emptyBucket(): Bucket {\r\n return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, calls: 0, estimated: 0 }\r\n}\r\n\r\nexport function emptyDaily(date: string): DailyRecord {\r\n return {\r\n date,\r\n totals: { ...emptyBucket(), estimatedCalls: 0 },\r\n hours: Array.from({ length: 24 }, emptyBucket),\r\n byModel: {},\r\n bySession: {},\r\n byProject: {},\r\n compaction: emptyBucket(),\r\n }\r\n}\r\n\r\nfunction addToBucket(b: Bucket, s: UsageSample): Bucket {\r\n return {\r\n input: b.input + s.input, output: b.output + s.output,\r\n cacheRead: b.cacheRead + s.cacheRead, cacheWrite: b.cacheWrite + s.cacheWrite,\r\n calls: b.calls + 1, estimated: b.estimated + s.estimated,\r\n }\r\n}\r\n\r\n/** 把一条样本并入日记录,返回新对象(存储记录禁止就地修改)。 */\r\nexport function addSample(rec: DailyRecord, s: UsageSample): DailyRecord {\r\n const totals = { ...addToBucket(rec.totals, s), estimatedCalls: rec.totals.estimatedCalls + (s.estimatedCall ? 1 : 0) }\r\n const hours = rec.hours.slice()\r\n hours[s.hour] = addToBucket(hours[s.hour], s)\r\n const byModel = { ...rec.byModel }\r\n if (s.model !== undefined) byModel[s.model] = addToBucket(byModel[s.model] ?? emptyBucket(), s)\r\n const bySession = { ...rec.bySession }\r\n if (s.sessionId !== undefined && s.cwd !== undefined) {\r\n bySession[s.sessionId] = { ...addToBucket(bySession[s.sessionId] ?? { ...emptyBucket(), cwd: s.cwd }, s), cwd: s.cwd }\r\n }\r\n const byProject = { ...rec.byProject }\r\n if (s.cwd !== undefined && !s.compaction) byProject[s.cwd] = addToBucket(byProject[s.cwd] ?? emptyBucket(), s)\r\n const compaction = s.compaction ? addToBucket(rec.compaction, s) : rec.compaction\r\n return { ...rec, totals, hours, byModel, bySession, byProject, compaction }\r\n}\r\n\r\n/** 从 session 事件提取样本;不相关事件与无 usage 的 compaction 返回 undefined。 */\r\nexport function sampleFromEvent(\r\n session: Session,\r\n event: SessionEvent,\r\n timeZone: string,\r\n estimate: (message: Message) => number,\r\n): UsageSample | undefined {\r\n const { date, hour } = dayParts(event.time, timeZone)\r\n if (event.type === 'assistant/message') {\r\n const { message, usage } = event.data\r\n const base = { date, hour, model: `${message.source.provider}/${message.source.model}`, sessionId: String(session.header.id), cwd: session.header.cwd, compaction: false }\r\n if (usage === undefined) {\r\n return { ...base, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, estimated: estimate(message), estimatedCall: true }\r\n }\r\n return {\r\n ...base, estimated: 0, estimatedCall: false,\r\n input: usage.inputTokens, output: usage.outputTokens,\r\n cacheRead: usage.cacheReadTokens ?? 0, cacheWrite: usage.cacheWriteTokens ?? 0,\r\n }\r\n }\r\n if (event.type === 'compaction/summary') {\r\n const { usage } = event.data\r\n if (usage === undefined) return undefined // 摘要块不是 Message,不做启发式估算\r\n return {\r\n date, hour, model: undefined, estimated: 0, estimatedCall: false, compaction: true,\r\n input: usage.inputTokens, output: usage.outputTokens,\r\n cacheRead: usage.cacheReadTokens ?? 0, cacheWrite: usage.cacheWriteTokens ?? 0,\r\n }\r\n }\r\n return undefined\r\n}\r\n\r\n/** 把 UTC 毫秒换算成指定时区的日期串与小时序号。 */\r\nexport function dayParts(time: number, timeZone: string): { date: string; hour: number } {\r\n const parts = new Intl.DateTimeFormat('en-CA', {\r\n timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', hour12: false,\r\n }).formatToParts(time)\r\n const get = (type: string): string => parts.find((p) => p.type === type)!.value\r\n return {\r\n date: `${get('year')}-${get('month')}-${get('day')}`,\r\n hour: Number(get('hour')) % 24, // 部分 ICU 版本午夜给 24\r\n }\r\n}\r\n\r\n/** 日期串加减天数(锚 UTC 正午,避开 DST)。 */\r\nexport function shiftDate(date: string, days: number): string {\r\n return new Date(Date.parse(`${date}T12:00:00Z`) + days * 86_400_000).toISOString().slice(0, 10)\r\n}\r\n\r\n/** 计费 token 数自动换算 K/M/B(10 进制,1 位小数)。 */\r\nexport function formatTokens(n: number): string {\r\n const units = ['', 'K', 'M', 'B'] as const\r\n let value = n\r\n let unit = 0\r\n while (value >= 1000 && unit < units.length - 1) {\r\n value /= 1000\r\n unit += 1\r\n }\r\n return unit === 0 ? String(n) : `${value.toFixed(1)}${units[unit]}`\r\n}\r\n","/** /token-usage 命令的文本视图(纯函数)。 */\r\nimport { billedOf, formatTokens } from './aggregate.ts'\r\nimport type { Bucket, DailyRecord } from './store.ts'\r\n\r\nfunction line(name: string, b: Bucket): string {\r\n return ` ${name} ${formatTokens(billedOf(b))} ${b.calls} 次调用`\r\n}\r\n\r\n/** 当日详情:总量(含估算标注)+ 模型/项目二维细分 + compaction 单列。 */\r\nexport function renderDay(rec: DailyRecord): string {\r\n const est = rec.totals.estimated > 0 ? `(含估算 ${formatTokens(rec.totals.estimated)})` : ''\r\n const rows: string[] = [\r\n `${rec.date} 用量:${formatTokens(billedOf(rec.totals))} ${rec.totals.calls} 次调用${est}`,\r\n ]\r\n const models = Object.entries(rec.byModel).sort((a, b) => billedOf(b[1]) - billedOf(a[1]))\r\n if (models.length > 0) rows.push('按模型:', ...models.map(([k, v]) => line(k, v)))\r\n const projects = Object.entries(rec.byProject).sort((a, b) => billedOf(b[1]) - billedOf(a[1]))\r\n if (projects.length > 0) rows.push('按项目:', ...projects.map(([k, v]) => line(k, v)))\r\n if (rec.compaction.calls > 0) rows.push(`上下文压缩:${formatTokens(billedOf(rec.compaction))} ${rec.compaction.calls} 次调用`)\r\n return rows.join('\\n')\r\n}\r\n\r\n/** 今日详情 + 近 7 日逐日摘要行(days[0] 为今日)。 */\r\nexport function renderWeek(today: string, days: readonly DailyRecord[]): string {\r\n const lines = days.map((d) => `${d.date} ${formatTokens(billedOf(d.totals))} ${d.totals.calls} 次调用`)\r\n return `${renderDay(days[0])}\\n\\n近 7 日:\\n${lines.join('\\n')}`\r\n}\r\n","/** token-usage 存储域声明:身份、版本、记录 zod schema 的单一来源。 */\r\nimport { z } from 'zod'\r\nimport { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'\r\n\r\nexport const BucketSchema = z.object({\r\n input: z.number().int().nonnegative(),\r\n output: z.number().int().nonnegative(),\r\n cacheRead: z.number().int().nonnegative(),\r\n cacheWrite: z.number().int().nonnegative(),\r\n calls: z.number().int().nonnegative(),\r\n /** 估算样本的计费 token 量(usage 缺失时经 tokenMeter 启发式得出)。 */\r\n estimated: z.number().int().nonnegative(),\r\n})\r\nexport type Bucket = z.infer<typeof BucketSchema>\r\n\r\nexport const DailyRecordSchema = z.object({\r\n date: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/),\r\n totals: BucketSchema.extend({ estimatedCalls: z.number().int().nonnegative() }),\r\n /** 24 小时桶,空小时为全零桶。 */\r\n hours: z.array(BucketSchema).length(24),\r\n /** key = 'provider/model'。 */\r\n byModel: z.record(z.string(), BucketSchema),\r\n /** key = sessionId。 */\r\n bySession: z.record(z.string(), BucketSchema.extend({ cwd: z.string() })),\r\n /** key = cwd(原样存储)。 */\r\n byProject: z.record(z.string(), BucketSchema),\r\n /** 压缩摘要调用单列;数值同时已并入 totals/hours。 */\r\n compaction: BucketSchema,\r\n})\r\nexport type DailyRecord = z.infer<typeof DailyRecordSchema>\r\n\r\n/** domain 名受 UNIT_NAME_RE 约束(^[a-z][a-z0-9_]*$),不允许连字符。 */\r\nexport const tokenUsageDomain = defineDomain({\r\n name: 'token_usage',\r\n version: 1,\r\n tables: { daily: domainTable<string, DailyRecord>(DailyRecordSchema) },\r\n})\r\n","/** token-usage 插件 Node 半:采集、按日聚合持久化、/token-usage 命令、JSON 查询端点。 */\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport type { KvTable } from '@deepseek-ai/dsh-storage-domain'\r\n// Type-only 激活对应包对 cordis Context 的声明合并(inject 的 service 属性)。\r\nimport type { WebServer } from '@deepseek-ai/dsh-host-webserver'\r\nimport type {} from '@deepseek-ai/dsh-token-meter'\r\nimport type {} from '@deepseek-ai/dsh-commands'\r\nimport { addSample, dayParts, emptyDaily, sampleFromEvent } from './aggregate.ts'\r\nimport { renderDay, renderWeek } from './render.ts'\r\nimport { tokenUsageDomain, type DailyRecord } from './store.ts'\r\n\r\nexport interface Config {\r\n /** 按日聚合的时区(IANA 名)。 */\r\n timezone: string\r\n}\r\n\r\nexport const Config: z<Config> = z.object({\r\n timezone: z.string().default(Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC'),\r\n})\r\n\r\nexport const name = 'token-usage'\r\n\r\nexport const inject = ['storageDomain', 'tokenMeter', 'commands']\r\n\r\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/\r\n\r\nexport function apply(ctx: Context, config: Config): void {\r\n let daily: KvTable<string, DailyRecord> | undefined\r\n // 写串行化:session/event 监听可并发触发,所有聚合写排进同一条 tail 链,\r\n // 之后 get+put 才不会互相覆盖(KvTable 不串行化并发读改写)。\r\n let tail: Promise<unknown> = Promise.resolve()\r\n const domainReady = ctx.storageDomain.open(tokenUsageDomain).then((domain) => {\r\n daily = domain.table('daily')\r\n return domain\r\n })\r\n // open 失败(version-mismatch/malformed-medium/invalid-record)时创建即挂\r\n // rejection handler:避免 unhandled rejection 崩掉宿主进程(Node 默认 throw)。\r\n // domainReady 仍保持 reject,写链(tail 吞掉)/命令(宿主转 kind:'error')/\r\n // 端点(宿主 500)/卸载(cordis 记录)均感知失败而不次生崩溃。\r\n domainReady.catch((error) => {\r\n ctx.logger.warn(`[token-usage] 存储域打开失败,token 统计不可用:${error instanceof Error ? error.message : String(error)}`)\r\n })\r\n\r\n ctx.on('session/event', (session, event) => {\r\n const sample = sampleFromEvent(session, event, config.timezone, (m) => ctx.tokenMeter.estimateMessage(m))\r\n if (sample === undefined) return\r\n // 链到前一条写上,使读改写按序执行;单次失败被吞掉,不中断后续写(采集尽力而为)。\r\n const write = tail.then(() => domainReady).then(() => {\r\n const table = daily!\r\n return table.put(sample.date, addSample(table.get(sample.date) ?? emptyDaily(sample.date), sample))\r\n })\r\n tail = write.then(() => undefined, () => undefined)\r\n })\r\n\r\n ctx.commands.register({\r\n name: 'token-usage',\r\n description: '查看 token 用量(今日+近7日,或指定日期)',\r\n input: { hint: 'YYYY-MM-DD,可空' },\r\n handler: async ({ rawInput }) => {\r\n const table = await domainReady.then(() => daily!)\r\n const arg = rawInput.trim()\r\n const today = dayParts(Date.now(), config.timezone).date\r\n if (arg !== '' && !DATE_RE.test(arg)) {\r\n return { kind: 'error' as const, text: '用法:/token-usage [YYYY-MM-DD]' }\r\n }\r\n if (arg !== '') {\r\n return { kind: 'success' as const, text: renderDay(table.get(arg) ?? emptyDaily(arg)) }\r\n }\r\n const days = Array.from({ length: 7 }, (_, i) => {\r\n const date = dayParts(Date.now() - i * 86_400_000, config.timezone).date\r\n return table.get(date) ?? emptyDaily(date)\r\n })\r\n void today\r\n return { kind: 'success' as const, text: renderWeek(days[0].date, days) }\r\n },\r\n })\r\n\r\n // webServer 是可选能力(headless/CLI 无此服务),不进入顶层 inject。经 ctx.inject\r\n // 子 fiber 等待其挂载后再注册:子 fiber 未激活时惰性、随父 fiber 卸载而清理(宿主\r\n // 对可选服务的既有模式,见 client/connection 对可选 apiProxy 的处理);headless 下\r\n // webServer 永不出现,任何分支都不注册。注册走 ctx.effect 接线 disposer\r\n // (\"Registrations are effects\"),HMR 重挂不会因重复 exact 路径抛错。\r\n ctx.inject(['webServer'], (webCtx) => {\r\n webCtx.effect(() => webCtx.webServer.register({\r\n kind: 'exact',\r\n path: '/token-usage/api/daily',\r\n handler: async (req, res) => {\r\n if (req.method !== 'GET') {\r\n res.writeHead(405).end()\r\n return\r\n }\r\n const date = new URL(req.url ?? '', 'http://127.0.0.1').searchParams.get('date')\r\n if (date !== null && !DATE_RE.test(date)) {\r\n res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'bad date, want YYYY-MM-DD' }))\r\n return\r\n }\r\n const table = await domainReady.then(() => daily!)\r\n const today = dayParts(Date.now(), config.timezone).date\r\n const key = date ?? today\r\n res.writeHead(200, { 'content-type': 'application/json' })\r\n .end(JSON.stringify({ today, record: table.get(key) ?? emptyDaily(key) }))\r\n },\r\n }), 'token-usage: /token-usage/api/daily route')\r\n })\r\n\r\n ctx.effect(() => async () => {\r\n await tail.catch(() => undefined) // 排空的写链落到后端后再关 domain\r\n await domainReady.then((domain) => domain.close())\r\n })\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["z"],"sources":["../src/aggregate.ts","../src/heatmap.ts","../src/render.ts","../src/store.ts","../src/index.ts"],"sourcesContent":["/** token-usage 纯函数:日期换算、K/M/B 格式化、聚合。无运行时依赖,浏览器半可内联。 */\r\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\r\nimport type { Message } from '@deepseek-ai/dsh-llm/types'\r\n// 激活 @deepseek-ai/dsh-compaction 对 SessionEventMap 的声明合并(compaction/summary 等事件类型)。\r\nimport type {} from '@deepseek-ai/dsh-compaction/types'\r\nimport type { Bucket, DailyRecord } from './store.ts'\r\n\r\n/** 一条待聚合样本;每个样本隐含 calls=1。 */\r\nexport interface UsageSample {\r\n date: string\r\n hour: number\r\n input: number\r\n output: number\r\n cacheRead: number\r\n cacheWrite: number\r\n /** 估算样本的计费 token 量;真实样本为 0。 */\r\n estimated: number\r\n estimatedCall: boolean\r\n /** 'provider/model';compaction 样本无。 */\r\n model?: string\r\n sessionId?: string\r\n cwd?: string\r\n compaction: boolean\r\n}\r\n\r\n/** 桶的计费总量(含估算)。 */\r\nexport function billedOf(b: Bucket): number {\r\n return b.input + b.output + b.cacheRead + b.cacheWrite + b.estimated\r\n}\r\n\r\nexport function emptyBucket(): Bucket {\r\n return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, calls: 0, estimated: 0 }\r\n}\r\n\r\nexport function emptyDaily(date: string): DailyRecord {\r\n return {\r\n date,\r\n totals: { ...emptyBucket(), estimatedCalls: 0 },\r\n hours: Array.from({ length: 24 }, emptyBucket),\r\n byModel: {},\r\n bySession: {},\r\n byProject: {},\r\n compaction: emptyBucket(),\r\n }\r\n}\r\n\r\nfunction addToBucket(b: Bucket, s: UsageSample): Bucket {\r\n return {\r\n input: b.input + s.input, output: b.output + s.output,\r\n cacheRead: b.cacheRead + s.cacheRead, cacheWrite: b.cacheWrite + s.cacheWrite,\r\n calls: b.calls + 1, estimated: b.estimated + s.estimated,\r\n }\r\n}\r\n\r\n/** 把一条样本并入日记录,返回新对象(存储记录禁止就地修改)。 */\r\nexport function addSample(rec: DailyRecord, s: UsageSample): DailyRecord {\r\n const totals = { ...addToBucket(rec.totals, s), estimatedCalls: rec.totals.estimatedCalls + (s.estimatedCall ? 1 : 0) }\r\n const hours = rec.hours.slice()\r\n hours[s.hour] = addToBucket(hours[s.hour], s)\r\n const byModel = { ...rec.byModel }\r\n if (s.model !== undefined) byModel[s.model] = addToBucket(byModel[s.model] ?? emptyBucket(), s)\r\n const bySession = { ...rec.bySession }\r\n if (s.sessionId !== undefined && s.cwd !== undefined) {\r\n bySession[s.sessionId] = { ...addToBucket(bySession[s.sessionId] ?? { ...emptyBucket(), cwd: s.cwd }, s), cwd: s.cwd }\r\n }\r\n const byProject = { ...rec.byProject }\r\n if (s.cwd !== undefined && !s.compaction) byProject[s.cwd] = addToBucket(byProject[s.cwd] ?? emptyBucket(), s)\r\n const compaction = s.compaction ? addToBucket(rec.compaction, s) : rec.compaction\r\n return { ...rec, totals, hours, byModel, bySession, byProject, compaction }\r\n}\r\n\r\n/** 从 session 事件提取样本;不相关事件与无 usage 的 compaction 返回 undefined。 */\r\nexport function sampleFromEvent(\r\n session: Session,\r\n event: SessionEvent,\r\n timeZone: string,\r\n estimate: (message: Message) => number,\r\n): UsageSample | undefined {\r\n const { date, hour } = dayParts(event.time, timeZone)\r\n if (event.type === 'assistant/message') {\r\n const { message, usage } = event.data\r\n const base = { date, hour, model: `${message.source.provider}/${message.source.model}`, sessionId: String(session.header.id), cwd: session.header.cwd, compaction: false }\r\n if (usage === undefined) {\r\n return { ...base, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, estimated: estimate(message), estimatedCall: true }\r\n }\r\n return {\r\n ...base, estimated: 0, estimatedCall: false,\r\n input: usage.inputTokens, output: usage.outputTokens,\r\n cacheRead: usage.cacheReadTokens ?? 0, cacheWrite: usage.cacheWriteTokens ?? 0,\r\n }\r\n }\r\n if (event.type === 'compaction/summary') {\r\n const { usage } = event.data\r\n if (usage === undefined) return undefined // 摘要块不是 Message,不做启发式估算\r\n return {\r\n date, hour, model: undefined, estimated: 0, estimatedCall: false, compaction: true,\r\n input: usage.inputTokens, output: usage.outputTokens,\r\n cacheRead: usage.cacheReadTokens ?? 0, cacheWrite: usage.cacheWriteTokens ?? 0,\r\n }\r\n }\r\n return undefined\r\n}\r\n\r\n/** 把 UTC 毫秒换算成指定时区的日期串与小时序号。 */\r\nexport function dayParts(time: number, timeZone: string): { date: string; hour: number } {\r\n const parts = new Intl.DateTimeFormat('en-CA', {\r\n timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', hour12: false,\r\n }).formatToParts(time)\r\n const get = (type: string): string => parts.find((p) => p.type === type)!.value\r\n return {\r\n date: `${get('year')}-${get('month')}-${get('day')}`,\r\n hour: Number(get('hour')) % 24, // 部分 ICU 版本午夜给 24\r\n }\r\n}\r\n\r\n/** 日期串加减天数(锚 UTC 正午,避开 DST)。 */\r\nexport function shiftDate(date: string, days: number): string {\r\n return new Date(Date.parse(`${date}T12:00:00Z`) + days * 86_400_000).toISOString().slice(0, 10)\r\n}\r\n\r\n/** 计费 token 数自动换算 K/M/B(10 进制,1 位小数)。 */\r\nexport function formatTokens(n: number): string {\r\n const units = ['', 'K', 'M', 'B'] as const\r\n let value = n\r\n let unit = 0\r\n while (value >= 1000 && unit < units.length - 1) {\r\n value /= 1000\r\n unit += 1\r\n }\r\n return unit === 0 ? String(n) : `${value.toFixed(1)}${units[unit]}`\r\n}\r\n","/** token-usage 纯函数:range 端点参数与摘要、13 周热力图网格、缓存/新增拆分。无运行时依赖,两半共用。 */\r\nimport { billedOf, shiftDate } from './aggregate.ts'\r\nimport type { Bucket, DailyRecord } from './store.ts'\r\n\r\n/** range 端点与热力图共用的每日紧凑摘要。 */\r\nexport interface HeatmapDay { date: string; billed: number; calls: number }\r\n\r\nconst DAY_MS = 86_400_000\r\n\r\n/** 解析 range 端点 days 参数:null → 默认 91;1..366 合法;其余返回 null(非法)。 */\r\nexport function parseDaysParam(raw: string | null): number | null {\r\n if (raw === null) return 91\r\n if (!/^\\d+$/.test(raw)) return null\r\n const n = Number(raw)\r\n return n >= 1 && n <= 366 ? n : null\r\n}\r\n\r\n/** 以 today 为终点向前取 days 天的紧凑摘要(缺失日记 0),日期升序。 */\r\nexport function rangeSummaries(get: (date: string) => DailyRecord | undefined, today: string, days: number): HeatmapDay[] {\r\n return Array.from({ length: days }, (_, i) => {\r\n const date = shiftDate(today, i - (days - 1))\r\n const rec = get(date)\r\n return rec === undefined\r\n ? { date, billed: 0, calls: 0 }\r\n : { date, billed: billedOf(rec.totals), calls: rec.totals.calls }\r\n })\r\n}\r\n\r\nexport type HeatmapLevel = 0 | 1 | 2 | 3 | 4\r\n\r\n/** 分档:0 用量 → 0;非零按 max 线性 1..4 档(max <= 0 时兜底 1)。 */\r\nexport function levelOf(billed: number, max: number): HeatmapLevel {\r\n if (billed <= 0) return 0\r\n if (max <= 0) return 1\r\n return Math.min(4, Math.max(1, Math.ceil((billed / max) * 4))) as HeatmapLevel\r\n}\r\n\r\nexport interface HeatmapCell {\r\n date: string\r\n day: HeatmapDay | undefined\r\n level: HeatmapLevel\r\n /** today 之后的格子:禁用、不计档位。 */\r\n future: boolean\r\n}\r\n\r\n/** 13 列 × 每列 7 格(周日在上);末列为 today 所在周(周六结束)。 */\r\nexport function heatmapGrid(today: string, days: HeatmapDay[]): HeatmapCell[][] {\r\n const byDate = new Map(days.map((d) => [d.date, d]))\r\n const todayMs = Date.parse(`${today}T12:00:00Z`)\r\n const endMs = todayMs + (6 - new Date(todayMs).getUTCDay()) * DAY_MS\r\n const startMs = endMs - 90 * DAY_MS\r\n const max = Math.max(0, ...days.map((d) => d.billed))\r\n return Array.from({ length: 13 }, (_, c) =>\r\n Array.from({ length: 7 }, (_, r) => {\r\n const ms = startMs + (c * 7 + r) * DAY_MS\r\n const date = new Date(ms).toISOString().slice(0, 10)\r\n const day = byDate.get(date)\r\n const future = ms > todayMs\r\n return { date, day, level: future || day === undefined ? 0 : levelOf(day.billed, max), future }\r\n }))\r\n}\r\n\r\n/** 缓存/新增拆分:缓存 = cacheRead;新增 = input+output+cacheWrite+estimated。 */\r\nexport function cacheSplit(b: Bucket): { fresh: number; cached: number } {\r\n return { fresh: b.input + b.output + b.cacheWrite + b.estimated, cached: b.cacheRead }\r\n}\r\n\r\n/** 缓存命中率 = cacheRead/(input+cacheRead);分母为 0 返回 null。 */\r\nexport function cacheHitRate(totals: Bucket): number | null {\r\n const denom = totals.input + totals.cacheRead\r\n return denom === 0 ? null : totals.cacheRead / denom\r\n}\r\n","/** /token-usage 命令的文本视图(纯函数)。 */\r\nimport { billedOf, formatTokens } from './aggregate.ts'\r\nimport type { Bucket, DailyRecord } from './store.ts'\r\n\r\nfunction line(name: string, b: Bucket): string {\r\n return ` ${name} ${formatTokens(billedOf(b))} ${b.calls} 次调用`\r\n}\r\n\r\n/** 当日详情:总量(含估算标注)+ 模型/项目二维细分 + compaction 单列。 */\r\nexport function renderDay(rec: DailyRecord): string {\r\n const est = rec.totals.estimated > 0 ? `(含估算 ${formatTokens(rec.totals.estimated)})` : ''\r\n const rows: string[] = [\r\n `${rec.date} 用量:${formatTokens(billedOf(rec.totals))} ${rec.totals.calls} 次调用${est}`,\r\n ]\r\n const models = Object.entries(rec.byModel).sort((a, b) => billedOf(b[1]) - billedOf(a[1]))\r\n if (models.length > 0) rows.push('按模型:', ...models.map(([k, v]) => line(k, v)))\r\n const projects = Object.entries(rec.byProject).sort((a, b) => billedOf(b[1]) - billedOf(a[1]))\r\n if (projects.length > 0) rows.push('按项目:', ...projects.map(([k, v]) => line(k, v)))\r\n if (rec.compaction.calls > 0) rows.push(`上下文压缩:${formatTokens(billedOf(rec.compaction))} ${rec.compaction.calls} 次调用`)\r\n return rows.join('\\n')\r\n}\r\n\r\n/** 今日详情 + 近 7 日逐日摘要行(days[0] 为今日)。 */\r\nexport function renderWeek(today: string, days: readonly DailyRecord[]): string {\r\n const lines = days.map((d) => `${d.date} ${formatTokens(billedOf(d.totals))} ${d.totals.calls} 次调用`)\r\n return `${renderDay(days[0])}\\n\\n近 7 日:\\n${lines.join('\\n')}`\r\n}\r\n","/** token-usage 存储域声明:身份、版本、记录 zod schema 的单一来源。 */\r\nimport { z } from 'zod'\r\nimport { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'\r\n\r\nexport const BucketSchema = z.object({\r\n input: z.number().int().nonnegative(),\r\n output: z.number().int().nonnegative(),\r\n cacheRead: z.number().int().nonnegative(),\r\n cacheWrite: z.number().int().nonnegative(),\r\n calls: z.number().int().nonnegative(),\r\n /** 估算样本的计费 token 量(usage 缺失时经 tokenMeter 启发式得出)。 */\r\n estimated: z.number().int().nonnegative(),\r\n})\r\nexport type Bucket = z.infer<typeof BucketSchema>\r\n\r\nexport const DailyRecordSchema = z.object({\r\n date: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/),\r\n totals: BucketSchema.extend({ estimatedCalls: z.number().int().nonnegative() }),\r\n /** 24 小时桶,空小时为全零桶。 */\r\n hours: z.array(BucketSchema).length(24),\r\n /** key = 'provider/model'。 */\r\n byModel: z.record(z.string(), BucketSchema),\r\n /** key = sessionId。 */\r\n bySession: z.record(z.string(), BucketSchema.extend({ cwd: z.string() })),\r\n /** key = cwd(原样存储)。 */\r\n byProject: z.record(z.string(), BucketSchema),\r\n /** 压缩摘要调用单列;数值同时已并入 totals/hours。 */\r\n compaction: BucketSchema,\r\n})\r\nexport type DailyRecord = z.infer<typeof DailyRecordSchema>\r\n\r\n/** domain 名受 UNIT_NAME_RE 约束(^[a-z][a-z0-9_]*$),不允许连字符。 */\r\nexport const tokenUsageDomain = defineDomain({\r\n name: 'token_usage',\r\n version: 1,\r\n tables: { daily: domainTable<string, DailyRecord>(DailyRecordSchema) },\r\n})\r\n","/** token-usage 插件 Node 半:采集、按日聚合持久化、/token-usage 命令、JSON 查询端点。 */\r\nimport type { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport type { KvTable } from '@deepseek-ai/dsh-storage-domain'\r\n// Type-only 激活对应包对 cordis Context 的声明合并(inject 的 service 属性)。\r\nimport type { WebServer } from '@deepseek-ai/dsh-host-webserver'\r\nimport type {} from '@deepseek-ai/dsh-token-meter'\r\nimport type {} from '@deepseek-ai/dsh-commands'\r\nimport { addSample, dayParts, emptyDaily, sampleFromEvent } from './aggregate.ts'\r\nimport { parseDaysParam, rangeSummaries } from './heatmap.ts'\r\nimport { renderDay, renderWeek } from './render.ts'\r\nimport { tokenUsageDomain, type DailyRecord } from './store.ts'\r\n\r\nexport interface Config {\r\n /** 按日聚合的时区(IANA 名)。 */\r\n timezone: string\r\n}\r\n\r\nexport const Config: z<Config> = z.object({\r\n timezone: z.string().default(Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'UTC'),\r\n})\r\n\r\nexport const name = 'token-usage'\r\n\r\nexport const inject = ['storageDomain', 'tokenMeter', 'commands']\r\n\r\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/\r\n\r\nexport function apply(ctx: Context, config: Config): void {\r\n let daily: KvTable<string, DailyRecord> | undefined\r\n // 写串行化:session/event 监听可并发触发,所有聚合写排进同一条 tail 链,\r\n // 之后 get+put 才不会互相覆盖(KvTable 不串行化并发读改写)。\r\n let tail: Promise<unknown> = Promise.resolve()\r\n const domainReady = ctx.storageDomain.open(tokenUsageDomain).then((domain) => {\r\n daily = domain.table('daily')\r\n return domain\r\n })\r\n // open 失败(version-mismatch/malformed-medium/invalid-record)时创建即挂\r\n // rejection handler:避免 unhandled rejection 崩掉宿主进程(Node 默认 throw)。\r\n // domainReady 仍保持 reject,写链(tail 吞掉)/命令(宿主转 kind:'error')/\r\n // 端点(宿主 500)/卸载(cordis 记录)均感知失败而不次生崩溃。\r\n domainReady.catch((error) => {\r\n ctx.logger.warn(`[token-usage] 存储域打开失败,token 统计不可用:${error instanceof Error ? error.message : String(error)}`)\r\n })\r\n\r\n ctx.on('session/event', (session, event) => {\r\n const sample = sampleFromEvent(session, event, config.timezone, (m) => ctx.tokenMeter.estimateMessage(m))\r\n if (sample === undefined) return\r\n // 链到前一条写上,使读改写按序执行;单次失败被吞掉,不中断后续写(采集尽力而为)。\r\n const write = tail.then(() => domainReady).then(() => {\r\n const table = daily!\r\n return table.put(sample.date, addSample(table.get(sample.date) ?? emptyDaily(sample.date), sample))\r\n })\r\n tail = write.then(() => undefined, () => undefined)\r\n })\r\n\r\n ctx.commands.register({\r\n name: 'token-usage',\r\n description: '查看 token 用量(今日+近7日,或指定日期)',\r\n input: { hint: 'YYYY-MM-DD,可空' },\r\n handler: async ({ rawInput }) => {\r\n const table = await domainReady.then(() => daily!)\r\n const arg = rawInput.trim()\r\n const today = dayParts(Date.now(), config.timezone).date\r\n if (arg !== '' && !DATE_RE.test(arg)) {\r\n return { kind: 'error' as const, text: '用法:/token-usage [YYYY-MM-DD]' }\r\n }\r\n if (arg !== '') {\r\n return { kind: 'success' as const, text: renderDay(table.get(arg) ?? emptyDaily(arg)) }\r\n }\r\n const days = Array.from({ length: 7 }, (_, i) => {\r\n const date = dayParts(Date.now() - i * 86_400_000, config.timezone).date\r\n return table.get(date) ?? emptyDaily(date)\r\n })\r\n void today\r\n return { kind: 'success' as const, text: renderWeek(days[0].date, days) }\r\n },\r\n })\r\n\r\n // webServer 是可选能力(headless/CLI 无此服务),不进入顶层 inject。经 ctx.inject\r\n // 子 fiber 等待其挂载后再注册:子 fiber 未激活时惰性、随父 fiber 卸载而清理(宿主\r\n // 对可选服务的既有模式,见 client/connection 对可选 apiProxy 的处理);headless 下\r\n // webServer 永不出现,任何分支都不注册。注册走 ctx.effect 接线 disposer\r\n // (\"Registrations are effects\"),HMR 重挂不会因重复 exact 路径抛错。\r\n ctx.inject(['webServer'], (webCtx) => {\r\n webCtx.effect(() => webCtx.webServer.register({\r\n kind: 'exact',\r\n path: '/token-usage/api/daily',\r\n handler: async (req, res) => {\r\n if (req.method !== 'GET') {\r\n res.writeHead(405).end()\r\n return\r\n }\r\n const date = new URL(req.url ?? '', 'http://127.0.0.1').searchParams.get('date')\r\n if (date !== null && !DATE_RE.test(date)) {\r\n res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'bad date, want YYYY-MM-DD' }))\r\n return\r\n }\r\n const table = await domainReady.then(() => daily!)\r\n const today = dayParts(Date.now(), config.timezone).date\r\n const key = date ?? today\r\n res.writeHead(200, { 'content-type': 'application/json' })\r\n .end(JSON.stringify({ today, record: table.get(key) ?? emptyDaily(key) }))\r\n },\r\n }), 'token-usage: /token-usage/api/daily route')\r\n\r\n webCtx.effect(() => webCtx.webServer.register({\r\n kind: 'exact',\r\n path: '/token-usage/api/range',\r\n handler: async (req, res) => {\r\n if (req.method !== 'GET') {\r\n res.writeHead(405).end()\r\n return\r\n }\r\n const days = parseDaysParam(new URL(req.url ?? '', 'http://127.0.0.1').searchParams.get('days'))\r\n if (days === null) {\r\n res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'bad days, want integer 1..366' }))\r\n return\r\n }\r\n const table = await domainReady.then(() => daily!)\r\n const today = dayParts(Date.now(), config.timezone).date\r\n res.writeHead(200, { 'content-type': 'application/json' })\r\n .end(JSON.stringify({ today, days: rangeSummaries((d) => table.get(d), today, days) }))\r\n },\r\n }), 'token-usage: /token-usage/api/range route')\r\n })\r\n\r\n ctx.effect(() => async () => {\r\n await tail.catch(() => undefined) // 排空的写链落到后端后再关 domain\r\n await domainReady.then((domain) => domain.close())\r\n })\r\n}\r\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-agent-toolkit/token-usage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DeepSeek Harness plugin: per-day token usage statistics — /token-usage command, JSON endpoint, sidebar UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
46
46
|
"clsx": "^2.0.0",
|
|
47
|
+
"recharts": "^3.10.1",
|
|
47
48
|
"zod": "^4.4.3"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
@@ -52,8 +53,8 @@
|
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@deepseek-ai/cordis": "link:../../deepseek-harness/vendor/cordis",
|
|
54
55
|
"@deepseek-ai/dsh-client-runtime": "link:../../deepseek-harness/packages/client/runtime",
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "link:../../deepseek-harness/packages/client/ui-sidebar",
|
|
56
56
|
"@deepseek-ai/dsh-client-ui-primitives": "link:../../deepseek-harness/packages/client/ui-primitives",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "link:../../deepseek-harness/packages/client/ui-sidebar",
|
|
57
58
|
"@deepseek-ai/dsh-client-ui-slots": "link:../../deepseek-harness/packages/client/ui-slots",
|
|
58
59
|
"@deepseek-ai/dsh-commands": "link:../../deepseek-harness/packages/interaction/commands",
|
|
59
60
|
"@deepseek-ai/dsh-compaction": "link:../../deepseek-harness/packages/compaction/compaction",
|
|
@@ -62,7 +63,6 @@
|
|
|
62
63
|
"@deepseek-ai/dsh-session": "link:../../deepseek-harness/packages/core/session",
|
|
63
64
|
"@deepseek-ai/dsh-storage-domain": "link:../../deepseek-harness/packages/storage/storage-domain",
|
|
64
65
|
"@deepseek-ai/dsh-token-meter": "link:../../deepseek-harness/packages/llm/token-meter",
|
|
65
|
-
"@deepseek-ai/schemastery": "link:../../deepseek-harness/vendor/schemastery",
|
|
66
66
|
"@testing-library/react": "^16.1.0",
|
|
67
67
|
"@types/node": "^22.20.1",
|
|
68
68
|
"@types/react": "~18.3.1",
|