# dsh-gauge

中文 | [English](README.en.md)

为 DeepSeek Harness Web UI 提供精确缓存命中率、token 用量与费用估算。

官方统计行把缓存命中率四舍五入成整数——`Math.round` 会把 99.8% 显示成误导性的 **100%**。
dsh-gauge 用一位小数（可配置）的精确数值顶替它，并补充分桶 token 明细、会话用量面板，以及自动跟随 DeepSeek 官方调价的会话费用估算（使用官方 API）。

## 功能

- **精确缓存命中率** — `cacheRead / (cacheRead + uncached + cacheWrite)`，小数位可配置（默认 1），99.8% 就是 99.8%。
- **分桶明细** — 命中 / 未命中输入 token 与输出 token。*写入*桶为 0 时自动隐藏（opencode-go/pi-ai 适配器从不报告 cache-write，实际恒为 0）。
- **费用估算 + 调价对比** — 按实际用量 × 模型单价估算（deepseek-v4-flash / deepseek-v4-pro，自动从会话推导）。官方新价生效前显示 **当前费用 + 新价费用 + 预计涨幅**；生效时刻自动切换到新价。
- **按请求时刻的峰谷计价** — 估算**不是**"当前时刻"快照：每条 assistant 消息按自己的时间戳计价，高峰时段消耗的 token 按高峰价、闲时按折扣价，最后求和。
- **高峰时段徽标** — 北京时间 09:00–12:00 / 14:00–18:00 为 DeepSeek 峰谷定价的高峰窗口。统计行尾按当前时段显示**高峰**/**闲时**状态（切换为新价后同样按当前时段显示）。
- **用量面板** — 会话页头 ⓘ 按钮弹出面板：模型、命中率、计费输入、各桶总数（默认完整数字）、上下文占用与费用估算（含调价后对比）。
- **两行统计（官方指标 + 精确用量）** — 第一行保留官方会话指标（轮/步、LLM/工具调用时长、首 token 平均、tok/s），复刻官方样式（行距刻意收紧，两行视为一个整体）；第二行是插件的精确用量（命中率、分桶、输出、费用、高峰/闲时徽标）。`replaceNativeStatsLine: false` 时保留官方原生行（含原生用量段）。
- **双语 & 货币自适应** — UI 为英文时文案切英文、费用按国际价目表以 USD 估算。语言与货币实时跟随，无需重启。

## 截图

![统计行](https://raw.githubusercontent.com/noone89A/dsh-gauge/main/docs/images/stats-line.png)
![用量面板](https://raw.githubusercontent.com/noone89A/dsh-gauge/main/docs/images/usage-pane.png)

## 安装

> npm 官方包发布后一条命令：`dsh plugin --profile web add dsh-gauge`。
> 当前 npm 处于发布保护期，请用 **GitHub Release 附件（tgz）** 安装：

```sh
# ① 下载 dsh-gauge-0.1.0.tgz：
#    https://github.com/noone89A/dsh-gauge/releases/download/v0.1.0/dsh-gauge-0.1.0.tgz

# ② 本地安装（注意：不要用上面的 URL 直接 add——pnpm 对 GitHub Release URL
#    不记录完整性校验，之后装其他插件会报 ERR_PNPM_MISSING_TARBALL_INTEGRITY）：
dsh plugin --profile web add C:\path\to\dsh-gauge-0.1.0.tgz

# ③ 重启
dsh web
```

输入框下方应出现精确统计行，会话页头出现 ⓘ 用量入口。

> **npm 发布后**（推荐方式）：`dsh plugin --profile web add dsh-gauge`——dsh plugin 会 pnpm add 并自动把声明了 `dsh.bundle` 的包加进 `dsh.profile.bundles`。

> **本地开发/调试**：用源码安装替代第②步——`pnpm add file:C:/Object/dsh-plugin/dsh-gauge`（源码改动后 `npm run build` 即生效，适合改 `src/config.ts` 的价格/高峰窗口）。

### 开箱即用 & 配置卡片

装完重启后**开箱即用**，无需任何配置：

- 输入框下方两行统计：精确缓存命中率（99.8% 就是 99.8%）、分桶明细、输出、预估费用、高峰/闲时徽标；
- 会话页头 ⓘ 用量面板：完整 token 数字、上下文占用、模型、费用与调价对比；
- 中英文文案与费用货币自动跟随界面语言。

**设置 → 插件 → 可配置插件**里的 dsh-gauge 配置卡片：由于当前 DSH 版本把可暴露给网页端的插件设置写死在白名单（`dsh-host-apiproxy` 的 `WEB_SETTINGS_NAMESPACES`），第三方插件需一次性把 `gauge` 加入白名单后卡片才会显示（步骤见"故障排查"）。**不加入白名单不影响任何核心功能**——不想动白名单时，也可直接编辑 `cordis.patch.yml`（见"配置"）。

## 工作原理

- 统计行注册进 `conversation.composer.dock` 槽位。`replaceNativeStatsLine: true`（默认）时以 `priority: -1` 注册进官方 `stats` cell，影子顶替原生行；`false` 时以 `order: 1` 追加为第二行。
- token 总量来自 `tokenUsage` 投影（`@deepseek-ai/dsh-token-meter`）；上下文占用来自 `contextPressure`。
- 费用估算翻页拉取**全会话历史**（`sessions.history`）：每条已定稿的 assistant 消息自带完成时间与 `usage`，按消息自己的时间戳套高峰/闲时费率（新方案）或平价（当前旧价），再求和——窗口外（"加载更早"之前）的历史同样精确计价，不会出现"命中 2 亿 token 费用却只有几毛钱"。
- 当前模型从全量历史的最后一条 assistant 消息推导（`source.model`；无连接面时降级用 trajectory 视图的 `requestConfig.model`），或通过 `model` 配置固定。
- 上下文压缩（compaction）后旧事件被摘要替代：费用与官方 `tokenUsage` 投影基于相同的事件集合，两者保持一致（压缩丢弃的用量官方同样丢弃）。

## 配置

普通用户通常不需要做任何配置——插件开箱即用。以下键可通过官方**设置 → 插件 → 可配置插件**里的 dsh-gauge 卡片可视化开启/调整——**保存后立即生效，无需重启**（唯一例外：`replaceNativeStatsLine` 决定顶替注册，需重启），也可直接写进 `~/.dsh/profiles/web/cordis.patch.yml` 的 `gauge` 行：

| 键 | 默认 | 含义 |
|---|---|---|
| `showPrice` | `true` | 显示费用估算（统计行 + 面板） |
| `showPeakBadge` | `true` | 统计行显示北京高峰时段徽标 |
| `replaceNativeStatsLine` | `true` | 顶替官方统计行（`false` 保留官方原生行） |
| `hitRateDecimals` | `1` | 缓存命中率小数位（0–2） |
| `tokenDecimals` | `1` | K/M 缩写小数位（0–2） |
| `panelExactTokens` | `true` | 面板显示完整 token 总数（`false` 用 K/M 缩写） |
| `currency` | `auto` | `auto` 跟随 UI 语言（English → `$` + USD 价目，其余 → `¥` + CNY 价目）；可显式写 `¥` / `$` |
| `model` | `auto` | `auto` 从会话推导模型；或显式写模型 id |

**高级项（开发者）**：高峰窗口 `peakHours`、CNY 价目 `pricePlans`、USD 价目 `usdPricePlans`、新价生效时刻 `nextFrom`、闲时系数 `offPeakFactor` 默认值内置在 `src/config.ts`，普通用户无需也不应在配置文件里改动；需要调整时直接改源码 `src/config.ts` 里的默认常量。

```yaml
# ~/.dsh/profiles/web/cordis.patch.yml — 扁平 loader 补丁条目
- id: gauge
  config:
    showPrice: true
    showPeakBadge: true
    hitRateDecimals: 1
    tokenDecimals: 1
    panelExactTokens: true
    currency: auto
```

**内置价目 & 调价**：当前价（8.17 前，平峰同价）与官方新价（2026-08-17 起，峰谷计价，闲时减半）已内置在 `src/config.ts`（CNY 用 `pricePlans`，USD 用 `usdPricePlans`），费用估算会按每条请求的时刻自动套用高峰/闲时价，并在 `nextFrom` 时刻自动切换到新价。官方价目如有调整，修改 `src/config.ts` 的默认常量即可；官方价目没有单独的"缓存写入"桶。

> 费用为**估算值**，以官方实际账单为准。补丁条目是**扁平** `{id, ...}` loader 条目——没有 `update:`/`disable:` 包装层，写 `- update:` 会被报错拒绝。若**配置卡片**不显示，见"故障排查"的白名单说明。

## 与 dsh-usage 的对比

[`dsh-usage`](https://www.npmjs.com/package/dsh-usage)（v0.1.0）与本插件同一天出现，这里基于源码做客观对比。

| 维度 | dsh-usage | dsh-gauge |
|---|---|---|
| 缓存命中**率**（%） | — 完全没有命中率指标，只有原始缓存 token | 精确命中率，小数位可配置（99.8% 就是 99.8%） |
| 峰谷计价 | — 无峰谷处理；内置价目为 **2026-04-24 的 USD 表**，2026-08-16 调价后费用估算会失真 | 按消息时间戳的峰谷计价、新旧价对比、生效时刻自动切换 |
| 粒度 | 每条 assistant 消息下的 per-turn 读数 + 设置页 **Usage** 页（52 周热力图、provider/模型汇总、跨会话） | 会话级统计行（顶替原生行）+ 页头 ⓘ 面板（模型、分桶、上下文占用、费用） |
| 成本核算 | replay 派生的 `modelCost` 投影、按生效日期计价、unpriced/无 usage 覆盖说明 | `tokenUsage` 投影 × 内置价目表（CNY + USD） |
| 数据源 | 持久日志 replay（跨分页/压缩） | `tokenUsage`/`contextPressure` 投影 |
| 语言 | 仅英文 | 中英双语 |
| 原生行 | 追加自己的行 | 默认**影子顶替**官方行 |
| 写入桶 | 单独计价 | 为 0 时隐藏 |

**总结：** dsh-gauge 是精度/效率仪表——官方 UI 舍掉的精确命中率、高峰时段感知、以及免维护地跟随新峰谷价的实时费用检查。两者**互补**，可共存安装——槽位不同、id 不同、无冲突。

## 故障排查

- **"写入"一直是 0** — 设计如此：一些适配器从不报告 cache-write token，为 0 时隐藏该桶。未来有提供方上报时自动恢复显示。
- **费用看起来不对** — 内置价目表（改 `src/config.ts` 的 `pricePlans`/`usdPricePlans`）或显式设置 `currency`/`model`；费用为估算值，以官方账单为准。
- **改动没反映** — 除 `replaceNativeStatsLine`（决定顶替注册）外，配置保存后**立即生效**；若改动的是 `cordis.patch.yml`，需重启 `dsh web`。
- **设置 → 插件 → 可配置插件 里没有 dsh-gauge 卡片** — 当前 DSH 版本把可暴露给网页端的插件设置写死在 `dsh-host-apiproxy` 的 `WEB_SETTINGS_NAMESPACES` 白名单里（官方注释标注"插件自行声明"为 deferred work），**不在白名单的命名空间即使已注册，describe 也不会返回**，卡片因此不显示。在宿主安装里把 `gauge` 加入白名单后重启 `dsh web`：
  
  ```js
  // <dsh 安装目录>/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/index.js
  const WEB_SETTINGS_NAMESPACES = [
    "agent-loop", "shell", "locale", "permission",
    "ui-conversation", "ui-theme", "web-search-deepseek",
    "gauge", // ← 加这一行
  ];
  ```
  
  不加入白名单**不影响**统计行、面板等核心功能，只是配置卡片不显示（仍可用 `cordis.patch.yml` 配置）。等 DSH 开放插件自注册后此要求自动消失。
- **页面无法启动** — 确认 `lib/client.js` 是打包后的客户端产物（运行 `npm run build`，产出 `__ModuleLoader__.load` 格式；裸 tsc ESM 输出会导致页面白屏）。

## 开发

```sh
pnpm install
pnpm run typecheck
pnpm test
pnpm run build
```

`npm run build` 先用 tsc 编译，再用 `scripts/build-client.mjs` 把客户端入口打包成 DSH client-module loader 格式。

## License

MIT

