给DeepSeek Harness加一个实时用量试图
opencode go插件
给 DeepSeek Harness 加一个实时用量视图
OpenCode Go。5 小时滚动窗口、每周窗口、每月窗口同时向前计算,用量到了某个位置,下一次请求才会告诉你:暂时不能继续了。
以前我通常是在快要撞上限流的时候,才想起来打开用量页面看一眼。
所以我给 DeepSeek Harness 的 Web 界面做了一个小插件:dsh-ocgo-usage。
它把 OpenCode Go 的订阅用量放到了输入框底部工具行、模型选择下拉旁边。默认只显示一行非常短的比例:
5H 0% · W 65% · M 83%5 小时、每周、每月。
不用打开额外页面,也不用等请求失败。看一眼,就知道当前大概还能用多少。
点击这行之后,再展开完整的百分比、距离重置还有多久,以及凭据设置和手动刷新的入口。
我希望它解决的事情很简单:把“额度不足”从请求之后的错误,变成请求之前就能看到的状态。
它是怎么工作的#
这个插件实际上分成两个部分:host 端和浏览器端。
host 端负责和 OpenCode Go 通信。
用户配置 workspace id 和 cookie 后,插件请求:
/workspace/<workspace>/goOpenCode Go 的这个页面会通过 SSR HTML 返回用量信息。插件从其中的:
data-slot="usage-item"结构里解析出三个窗口:
- Rolling:5 小时滚动窗口
- Weekly:每周窗口
- Monthly:每月窗口
然后 host 端通过同源 JSON 接口把结果提供给浏览器:
/api/ocgo-usage/api/ocgo-usage/refresh/api/ocgo-usage/config入口和服务挂载在 src/index.ts:23-43,具体路由在 src/routes.ts:15-126。
浏览器端不直接接触 OpenCode Go,也不负责保存完整的会话 cookie。它拿到的只是已经解析好的用量数据。
这样划分以后,cookie 就停留在 host 端,页面里只需要处理几个百分比和时间戳。
从一个 fork 开始#
这个插件最初来自 v587d/dsh-opencode-go-usage。
上游已经完成了用量抓取、解析、缓存、错误处理以及 DSH 客户端注入等基础能力,所以我没有重新实现整套逻辑,而是在现有结构上调整交互和实现细节。
最开始的问题主要出现在 UI。
原来的用量 chip 是一个悬浮组件。它需要自己寻找 composer 输入框的位置,计算坐标,跟随输入框移动,还支持拖拽。
它当然可以工作,但放在 DSH 里总有一点“插件”的感觉。
我更希望它成为输入框工具行的一部分。
所以后来把组件迁移到了 DSH 的 conversation.input.right slot,也就是模型选择下拉所在的工具区域。组件不再需要寻找输入框,也不再决定自己应该出现在哪里。
位置由 DSH 决定,插件只负责展示用量。
这个变化之后,原来为悬浮组件服务的大量逻辑也就没有存在的必要了。
position: fixed、位置计算、requestAnimationFrame、拖拽相关代码全部被移除。组件从一个需要管理自己位置的浮动对象,变成了一个普通的内联元素。
这一轮调整最终删除了 773 行代码,只留下约 85 行核心组件逻辑。
相关实现见 src/client/OcgoDockEntry.tsx:10-13。
默认状态只显示最重要的信息#
组件放回正确的位置之后,还有一个问题:默认显示的信息太多。
用量工具的特点决定了它应该是一个“扫一眼就知道状态”的组件。
所以最后把默认状态收缩成:
5H x% · W x% · M x%窗口名称被缩短,重置时间、凭据配置、更新时间等信息全部收进点击后的面板。
默认状态只回答一个问题:
现在用了多少?
如果需要进一步了解,再展开:
- 完整窗口名称
- 当前使用百分比
- 距离重置还有多久
- 当前配置的 workspace
- cookie 掩码
- 数据更新时间
setrefresh
因此信息并没有减少,只是按照使用频率重新分了层。
对应实现位于 src/client/OcgoDockEntry.tsx:59-71 和 src/client/OcgoDockEntry.tsx:348-435。
让它看起来像 DSH 的一部分#
把组件放进 slot 之后,另一个问题就变得明显了。
如果用量组件和模型选择下拉使用完全不同的视觉语言,即使位置正确,它仍然会像一个外部插件。
所以后续调整主要集中在视觉系统上。
组件颜色改为使用 DSH 的 --dsw-alias-* 语义 token。
背景、文字、hover、成功、警告和错误状态都不再写死颜色,而是交给 DSH 当前主题决定。
这样浅色和深色模式可以自然跟随,同时保留用量阈值:
- 80%:warning
- 90%:error
- 已经触发限流:error
判断逻辑位于 src/client/OcgoDockEntry.tsx:97-101,样式位于 src/client/ocgo.module.css:18-82。
之后又进一步参考 DSH 模型选择下拉的交互方式,统一了 trigger、圆角 cell、hover 状态、边框层级以及下拉阴影。
这部分调整的目的不是单纯让它“更好看”。
而是让它放进 DSH 之后,尽可能遵循原有界面的视觉规则。
相关样式见 src/client/ocgo.module.css:67-99。
中文界面也需要被正确解析#
开发过程中还遇到过一个比较隐蔽的问题。
OpenCode Go 的用量页面在中文环境下,会返回:
滚动用量每周用量每月用量而原来的解析逻辑只识别英文 label。
结果就是页面请求本身成功了,HTML 也正常返回,但解析器一个窗口都没有识别出来,最后只能得到类似:
<err:http302>这类问题很容易被误认为是网络请求失败。
实际上,请求没有问题,失败的是 HTML 解析。
现在解析器同时支持:
RollingWeeklyMonthly以及:
滚动每周每月中文 label 的解析逻辑位于 src/api.ts:143-164,同时增加了对应的解析测试。
这种兼容其实很重要。
一个用量工具最终面对的是用户当前看到的页面,而不是开发者习惯看到的英文页面。SSR 内容发生变化时,解析器需要能够识别对应的语言和结构。
cookie 不应该进入浏览器#
OpenCode Go 的 cookie 需要单独处理。
它不是一个只允许访问某个接口的普通 API key,而是一份完整的用户会话凭据。拿到它的人,可以访问用户账户中的 workspace、订阅等信息。
因此,这个插件没有把 cookie 放进前端运行环境。
cookie 始终保存在 host 端。
浏览器请求的是同源接口:
/api/ocgo-usage配置界面也不会直接展示完整 cookie,而是只显示:
••••1234这样的掩码结果。
配置优先级、cookie 规范化以及掩码逻辑位于 src/config.ts:55-103。
如果需要修改配置,也不需要打开终端。
可以直接在用量面板中设置:
- workspace id
- cookie
保存之后写入:
$DSH_HOME/ocgo-usage.json文件权限设置为 0600。
同时,新的配置保存完成后会立即清除旧缓存和失败冷却状态,让新的凭据可以马上生效。
相关逻辑位于 src/config.ts:105-138 和 src/routes.ts:75-95。
这并不能让 cookie 本身变得安全,但至少不会因为一个用量显示组件,把完整会话凭据暴露给浏览器页面。
浏览器每 10 秒轮询,但 host 不会每 10 秒请求 OpenCode Go#
用量是实时变化的,所以浏览器端需要定期更新。
当前策略是每 10 秒请求一次 host 端快照。
但 host 端并不会因此每 10 秒都请求 OpenCode Go。
默认缓存时间是 300 秒,并且做了几层保护:
- 缓存 300 秒,避免频繁访问远端页面
- 相同时间内的并发请求复用同一个 in-flight Promise
- 请求失败后进入 60 秒冷却
- 普通请求遵循缓存
- 手动
refresh可以绕过普通缓存 - 修改 workspace 或 cookie 后立即清除缓存和失败冷却
因此浏览器可以保持比较高的刷新频率,而真正发往 OpenCode Go 的请求频率仍然受到控制。
相关实现位于 src/service.ts:41-121。
只在使用 OpenCode Go 时显示#
还有一个细节是 provider 判断。
如果当前会话使用的不是 OpenCode Go,就没有必要在输入框旁边一直显示 OpenCode Go 的用量。
所以浏览器端会读取当前会话的 session.models,判断当前模型是否属于 opencode-go provider。
如果不是,chip 隐藏。
切换回 OpenCode Go 后,它会重新出现并更新数据。
这意味着用量组件跟随的是当前会话状态,而不是等下一次模型请求真正发生之后才知道用户切换了 provider。
相关逻辑位于 src/client/OcgoDockEntry.tsx:134-174。
最后再确认它真的能工作#
这种插件最容易出现的问题,就是 UI 看起来正常,但某个异常场景一来就失效。
所以最后保留了一组比较完整的测试,覆盖几个主要边界:
- SSR HTML 用量解析
- 中英文 label
- 百分比范围
- 重置时间转换
- cookie 规范化
- 配置优先级
- 敏感信息掩码
- provider 切换
- 缓存
- 并发请求去重
- 失败冷却
- 手动刷新
当前验证结果是:
4 个测试文件41 个测试全部通过同时:
pnpm run typecheckpnpm run build均通过。
构建产物继续输出到 lib/,项目脚本和 DSH bundle 配置见 package.json:10-16、package.json:54-75。
一个很小的功能#
dsh-ocgo-usage 本身并不复杂。
它没有重新实现 OpenCode Go 的用量系统,也没有增加一套复杂的状态管理。
核心事情只有几件:
把 OpenCode Go 的用量解析出来。
把 cookie 留在 host 端。
把结果放进 DSH 原本的输入框工具行。
默认只显示最重要的几个数字。
在需要的时候,再展开完整信息。
从一个能工作的 fork,到现在这个版本,中间做的更多是整理和收敛。
悬浮组件被放回 DSH 的 slot,位置管理逻辑因此被删除;大量默认信息被收进详情面板;颜色交给 DSH 的主题系统;解析器开始同时理解中英文;cookie 留在 host 端;缓存、失败冷却和并发请求也有了明确的边界。
最终它只剩下一行很短的提示:
5H 0% · W 65% · M 83%但这行数字解决了一个实际问题。
我不需要等请求失败以后,才知道自己已经用完额度。
在下一次点击发送之前,我就能看到它。
具体的项目地址:GitHub➡️