← All Posts

给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>/go

OpenCode 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 掩码
  • 数据更新时间
  • set
  • refresh

因此信息并没有减少,只是按照使用频率重新分了层。

对应实现位于 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 解析。

现在解析器同时支持:

Rolling
Weekly
Monthly

以及:

滚动
每周
每月

中文 label 的解析逻辑位于 src/api.ts:143-164,同时增加了对应的解析测试。

这种兼容其实很重要。

一个用量工具最终面对的是用户当前看到的页面,而不是开发者习惯看到的英文页面。SSR 内容发生变化时,解析器需要能够识别对应的语言和结构。

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 typecheck
pnpm 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➡️