bowenliang123/dsh-context
bowenliang123/dsh-contextDSH 上下文洞察面板:Context 仪表盘 + /context命令 + Context 浏览器,查看 Context的分类组成、内容详情、演进趋势、压缩/注入事件、统计等一站式 Context 全生命周期管理。
在 DSH 中新增整页用量统计,提供每日与累计 token、缓存命中率、分模型明细以及一年期活动热力图,全部数字取自 provider 上报的 usage。
跨全部会话的 token 用量统计插件(DSH)。在 Web GUI 左侧栏新增「用量统计」入口,主区域打开整页仪表盘。
Whole-corpus token usage statistics for DSH: a sidebar entry plus the dashboard it opens.
插件只回答一个问题:这些会话一共用了多少 token。除此之外它什么都不做。
usage,不推断、不按字数折算、不补零。GET /api/usage-statistics.data)。所有区块共用同一份载荷与同一个时间窗口,不存在「图看的是 7 天、表算的是 30 天」这种漂移。页面是一份整页报告,控件只有两个:整页的「刷新」,以及一组 7 天 / 30 天 / 全部的时间范围切换。后者只改视图窗口,载荷与统计口径不变,因此不存在「被悄悄过滤掉」的数据。时间范围控件独立成一行,同时驱动下面三块视图,三块共用一个窗口。
698.9M 这样的量级,不打印完整数字——一天的用量动辄上亿位,完整数字在浮层里根本读不出来。调用数是计数而非量级,仍是整数;缓存率仍是百分比。需要精确数字时看概览卡片的附注与明细表,它们始终是完整值。「全部」一档等于语料自身的跨度:如果只有 7 天 / 30 天两档,任何在更早时间用过的模型都会从环图与明细表中消失,加上「全部」后所有模型都可见。
整页与「插件」页同宽(960px 居中列)。
会话事件 assistant/message 自带 usage:
{ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, totalTokens }
计数互不重叠 —— inputTokens 仅为未缓存输入,计费输入 = 三者之和。模型归属取自该事件之前最近一次 request/header 的 config.provider / config.model。
路由:GET /api/usage-statistics.data?days=<7..1100>,默认 371 天(一年热力图)。days 是下限而非上限:语料比它更老时,窗口会回退到语料起点,否则早期模型会从环图与明细表里永久消失。走共享 /api 通道鉴权,因此只能在浏览器内(已登录)访问。
热力图与趋势图使用两个独立的窗口:payload.days(热力图)始终是整段请求窗口,payload.trend.days(趋势 / 环图 / 明细表)则从语料第一天起。这样热力图保留一年上下文,而「全部」不会用几个月的空列把新语料垫满。
其他实现要点:
time;时区偏移的符号约定为东为正,日界据此划分本地日期。inherited: true 标签的 session/end-seed 标记位置去重,避免把父会话的 token 重复计入;不带该标签的 session/end-seed 是普通生命周期边界,不作为切点。recoveredSessions);语料扫描每个进程只跑一次,不可读会话计入永久集合,计数器不会随轮询增长。sessionQuery.listSessions())失败会在载荷里上报 warnings.corpusFailures,面板据此显示一条说明。这一条不能靠其它计数器推断:枚举失败时没有任何会话被折叠,所有计数器都合法地读作 0,实时折叠也因归属无法判定而拒收全部会话 —— 页面会把「没读到」显示成「用量为零」。失败按请求重试,某次成功后该标记与原因一起撤下。# 从 GitHub 安装
dsh plugin add https://github.com/fakeNihilist/dsh-plugin-usage-stats
# 本地开发时从目录安装
plugin_manager install_bundle <本目录绝对路径>
改完 index.js 需要重启 Host 才会加载新代码(插件是进程内已加载的模块);客户端半(client.js)是浏览器按需加载的 bundle,刷新页面即可。顺序是先重启 Host、再刷新页面,反过来会短暂出现新版前端配旧版宿主。
npm test # 单测:折叠层、fork 切点规则、窗口起点、实时折叠的会话归属、v0 归档恢复、路由参数校验、客户端渲染、热力几何、悬停读数、命中率柱
npm run verify:logs # 用真实会话日志跑一遍折叠,打印真实观测到的字段形状
npm run verify:cut # 对比不同 fork 切点策略
npm run verify:payload # 校验路由 payload 的各项不变式
npm run verify:read # 逐个会话试读,定位 sessionsFailed 的真实原因
npm run verify:v0 # 检查被迁移拒绝的 v0 归档里有多少可回收的用量
npm run verify:corpus # 完整语料扫描两遍,证明计数器幂等不增长
npm run verify:trend # 按趋势图的口径打印输入/输出/缓存读与命中率
npm run verify:replay # 用真实日志重演一整天:冷启动扫描 + 事后实时折叠,与按日原始值对账
npm test 默认完全合成:每个用例自带 fixture(固定时区偏移与固定日期),不读本机语料,因此换台机器或隔一周再跑,结论都不变。
要看真实语料跑一遍,加环境变量:
USAGE_STATS_REAL_LOGS=1 npm test # 额外跑一个真实语料冒烟用例;其余用例仍用合成 fixture
该用例只断言「任何语料都成立」的性质(各视图都渲染、没有预开的浮层、环图与明细表模型数一致)。它刻意不依赖语料内容——库为空或只有一条 route 时它自动跳过。真实语料的价值在于形状是意外的:异常的 cwd、稀疏的月份、v0 归档、fixture 里不会出现的模型名。
verify:replay 需要先给出扫描时刻(默认当天 09:30),它复现的正是「服务一直开着,当日数字不动」这一类问题:扫描只看到该时刻之前已存在的会话,之后新建的会话全靠实时折叠补上,因此可以直接看出修复前后「当日 Token」的差别。
test/ 与 verify/ 只在开发时使用,不参与运行时;verify/ 直接读取 ~/.dsh/sessions 下的真实日志(多帧 zstd JSONL),把结论建立在实测数据而不是文档假设上。
cacheWriteTokens 在当前 DeepSeek adapter 上恒为 0,明细表不再列出该列;在其它 provider 上写缓存会稀释命中率。cwd 分组,没有 cwd 的会话以 session id 单独成组。界面不提供工作区与时间区间控件;宿主侧仍保留 ?workspace= 与 ?start=&end= 参数,但当前无人调用。days 是下限不是上限:语料比请求窗口更老时,两种窗口都会放宽到语料起点,热力图与趋势都可能超过一年。这是刻意的:截断会让早期模型从环图与明细表里永久消失。trend.days),不是「建库以来全部」:比路由请求窗口(默认一年,或语料起点)更早的用量不在载荷里。buildPayload 时不能合并,否则要么热力图丢掉上下文、要么趋势被空列垫满。routesInRange),依赖载荷里的 trend.routes 按天序列;该字段缺失时回落到整份 routes,宿主侧不能删掉这个字段。