DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

xuluoluo /

xuluoluo/dsh-quota

Verified

dsh插件

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@86646fb8

dsh-quota —— 把 opencode-go / Grok / Command Code 的额度与用量摊到界面上

DSH 的模型选择器只告诉你「用哪个模型」,不告诉你「这个 provider 这个月还剩多少」、 「我刚用掉多少 token」。这个插件补的正是这两块:参考 yetone/magpie 的 internal/provider(quotas.go、 key_allowance.go、commandcode_plan.go、opencode_endpoints.go)与它的 Usage 页, 直接问厂商自己的接口,并把每次模型调用的 usage 记下来。

界面上是五块:

  1. composer 下方的常驻窄条(conversation.composer.dock)—— 不用打开设置就能看到 OpenCode Go 35% · Grok ⚠100% · 今日 ≈$0.42;过阈值的那家带 ⚠,点一下刷新。 鼠标停在那家身上会展开它的每一个窗口(滚动窗口 · 2% · 重置 1h 12m、一周 · 19% …、 一个月 · 33% …),再加上套餐与余额 —— 用的是原生 title,不会被 composer 的 overflow 裁掉。 composer 右侧那枚 chip 与它是同一份数字,默认关掉(showChip: true 才回来);
  2. composer 左侧的两格(conversation.input.left)—— 打开的是哪个文件夹、它在哪个 git 分支上(游离 HEAD 时是短哈希;不是仓库就写「不在 git 仓库里」)。文件夹那格的 title 是 完整路径,分支那格是仓库根 + 分支名;15 秒问一次 Host,会话一换就重新问;
  3. 设置 → 额度(settings.section 里的 quota 页)—— 页内分三栏:额度(每个 provider 的卡片:窗口进度条最紧的排最前 + 重置倒计时 + 凭据区,含保存 / 清除 / 测试)、用量(四宫格 + 七天条带 + 模型明细,带估算花费)、输入预测(开关 / 模型 / 提示词);
  4. 列表上方的 + 添加供应商 —— 选一家厂商、贴 key(Grok 是加完再登录),写进 profile 的 cordis.patch.yml,重启还在;模型路由会一起写好,所以加完就能在模型选择器里选到它;
  5. 卡片头上会写清这家有没有接进模型:已接入模型 / 未接入模型 / 仅额度(Command Code 没有可用的聊天接口,只在这里盯额度)。

没有默认供应商:没配置就是空页,只有一句「还没有供应商」和添加入口 —— 凭空出现的卡片 会让人以为模型选择器里也有它们。

一、额度从哪来

provider 接口 读什么
OpenCode Go GET https://opencode.ai/zen/go/v1/usage usage.rolling / weekly / monthly 的 percent 与 resetsAt
Grok(SuperGrok / X Premium) GET https://cli-chat-proxy.grok.com/v1/billing?format=credits config.creditUsagePercent、currentPeriod、productUsage(GrokBuild / Cursor / GrokBot 分项)、prepaidBalance、有 cap 时的 onDemand*
Command Code GET https://api.commandcode.ai/alpha/billing/credits
GET https://api.commandcode.ai/alpha/billing/subscriptions
windowLimits.fiveHour / weekly 的 used/cap/resetAt;credits.monthlyCredits + purchasedCredits + freeCredits 当月余额;订阅给出套餐名(Go / Pro / GOAT / Max / Ultra)、到期与是否续费
DeepSeek 官方 API GET https://api.deepseek.com/user/balance is_available,以及 balance_infos[0] 的 total_balance / granted_balance / topped_up_balance(按币种取符号:CNY ¥、USD $)
ChatGPT 订阅(Plus / Pro / Team,和 Codex 共用) GET https://chatgpt.com/backend-api/wham/usage rate_limit 的窗口。按 limit_window_seconds 认成 5 小时或一周(团队套餐现在常常只回一周,而且放在 primary_window 上)。套餐名来自 plan_type。登录读 ~/.codex/auth.json 的 ChatGPT access token,不是 OpenAI API key
Claude 订阅(Pro / Max,和 Claude Code 共用) GET https://api.anthropic.com/api/oauth/usage five_hour / seven_day 的 utilization(已经是 0–100 的百分比)和 resets_at。有单独的 Sonnet / Opus 窗口才画。套餐名来自 ~/.claude/.credentials.json 的 subscriptionType
阿里云百炼 —— 没有公开的余额 / 用量接口(账单在阿里云 Billing API,要 AccessKey,不是 DashScope key),所以这张卡只存 key,不编数字

Command Code 的 billing/credits 不告诉你套餐,而当月额度的分母是套餐给的(GOAT = $70/月), 所以第二支接口并行只读一次、缓存 10 分钟(magpie 也是这么做的;它有时要 15–20 秒, 排在后面会把整张卡拖慢)。读不到就退回「只显示余额」。

卡片和左侧额度面板优先显示 5 小时窗口,其余窗口按已用比例排序。窄条优先显示 5 小时;告警仍检查所有窗口。左侧面板可刷新并滚动查看供应商。

DeepSeek 的卡是余额,不是窗口:官方只给金额、不给分母,所以它不画进度条,只报 余额 · ¥110.00 和构成(充值 / 赠金),is_available: false 时在金额前写明「can not pay」。 它用的是 DEEPSEEK_API_KEY;DSH 账号本身的余额仍然看「账号与余额」页 —— 两套账单不是一回事。

这几个端点在这台机器上真实跑通过,不是照文档猜的形状 —— test/live-probe.mjs 可随时复看。

二、用量从哪来

llm/stream 是 DSH 每次模型调用都要过的 waterfall。插件包住它:每个 chunk 原样传下去 (看不见任何差别),同时记住结束前那一个 usage chunk —— { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens } —— 和这次调用的 provider / model。失败的调用(finish.kind = error | aborted)不记。

于是「谁发起的调用」都算得到:对话、压缩、自动起标题。数据落在一个小 JSON 里:

$DSH_HOME/.quota-usage.json          # 默认 ~/.dsh/.quota-usage.json
{ "version": 1, "days": { "2026-10-20": { "totals": {...}, "models": { "opencode-go/deepseek-v4.1-flash": {...} } } } }

保留 30 天,页面看近 7 天 + 今天;写入是 2 秒防抖的一次原子替换(先写 .tmp-<pid> 再 rename), 插件卸载时再刷一次。计数不是业务数据,写失败只吞掉,不打扰模型调用。

页面上:TOKEN 总量(输入 / 输出)、缓存读取(含命中率 = 缓存 /(缓存 + 新输入))、 推理 token、调用次数,七天条带,以及按 provider / model 的明细(重的那条在前)。

花费:按目录价折算

build/prices.js 是从 pi-ai 目录里抽出来的价目表(USD / 百万 token,input / output / cacheRead / cacheWrite),由 tools/gen-prices.mjs 从 _ref/pi-ai/dist/providers/data/*.json 生成,覆盖当前这套 route 用到的 36 个模型。有价就出数:TOKEN 卡上写近 7 天的 ≈$3.17, 每条模型行写它自己的 ≈$0.013,窄条写今天。

这是估算,不是账单:OpenCode Go / Grok 是订阅制,不是按 token 计价;按目录价折算只是让你 横向比较「今天这几轮大概值多少钱」。要改价,在配置里覆盖:

prices:
  "opencode-go/deepseek-v4.1-flash": { input: 0.15, output: 0.6, cacheRead: 0.003 }

阈值告警

alertAt(默认 90)与 alertCriticalAt(默认 97)按「最紧的那个窗口」判;余额卡按 balanceFloor(默认 5,同一币种单位)判。命中的卡片头挂 ⚠ 接近上限 / 已达上限, 窄条上那家的百分比变红并加 ⚠,页面顶部也会写一行「已达上限 · Grok」。

三、凭据:登录,或者存 key

插件自己不落任何秘密,两条路都走 DSH 的凭据缝(ctx.credentials):

Grok:设备码登录(会真的把 DSH 接通)

点「登录 Grok」→ Host 用 pi-ai 同一个 client_id(b1a00492-…)、同一套 scope 去 auth.x.ai/oauth2/device/code 领设备码 → 页面显示 user code 和带 code 的授权链接 → 你在浏览器里确认 → Host 轮询 /oauth2/token → 拿到 grant 后写进

~/.dsh/.credentials.yaml → records["llm-pi-ai/xai"]
{ kind: grant, payload: { type: oauth, access, refresh, expires } }

也就是 dsh-llm-pi-ai 自己读的那条记录 —— 所以在这里登录完,DSH 里选 Grok 模型就能直接对话。 token 快过期(< 5 分钟)时读额度会先用 refresh_token 续一次并回写,续不动就让卡片说「登录过期」。 「退出登录」删掉这条记录。

OpenCode Go / Command Code:API key

粘贴 key → POST /quota/key → ctx.credentials.set("OPENCODE_GO_API_KEY" | "COMMANDCODE_API_KEY", …)。 和 Models 页同一个存储,所以优先级也一样:凭据库 > 环境变量 > 项目 .env > ~/.dsh/.env; 卡片写明当前值来自哪一层,清除 只删凭据库那份。

Command Code 没存 key 时自动读它 CLI 的登录 ~/.commandcode/auth.json(apiKey + userName), 卡片显示「使用 Command Code CLI 的登录 · 用户名」。这条只读,所以没有「清除」。

阿里云百炼:只有 key

百炼的 DashScope key 是 OpenAI 兼容的(https://dashscope.aliyuncs.com/compatible-mode/v1), 但这版 pi-ai 的 provider 目录里没有百炼。所以除了把 key 存成 DASHSCOPE_API_KEY,添加这个 供应商时还会往 llm-pi-ai.providers 写一条路由:

yaml ali: displayName: 阿里云百炼 apiKeyEnv: DASHSCOPE_API_KEY api: openai-completions baseURL: https://dashscope.aliyuncs.com/compatible-mode/v1 models: [qwen3.8-max, qwen3.8-flash]

模型 id 是从 GET /compatible-mode/v1/models 的实际返回里挑的(那份列表有 178 个)。 之后调用产生的 token 照样进上面的用量统计。

四、添加 / 移除供应商

列表上方一个「+ 添加供应商」按钮。点开只有三样:厂商、可选的显示名、可选的 API key (Grok 是加完在卡片里点登录)。不再问 id / 网关地址 / 凭据引用 —— 那些是给第二套网关 准备的,收在「高级」里。添加成功后自动收起,id 由 Host 从预设推出(就是模型路由的名字)。

写的是 profile 自己的 patch 层(cordis.patch.yml 里 id 为 dsh-quota 的那一行), 不是插件内部的状态文件 —— 所以重启、升级都还在,也能被别的工具读到。没有内置供应商: 不配置就是空页,不会替用户初始化五张卡。

现在有七种预设:opencode-go、xai-billing(Grok,走设备码登录)、commandcode、 deepseek-api(只有余额)、bailian(只存 key)、chatgpt(ChatGPT 订阅,读 Codex 登录)、 claude(Claude 订阅,读 Claude Code 登录)。已经加过的厂商不再出现在下拉里。

添加时同时接模型,这就是它和以前不一样的地方:

预设 模型侧
opencode-go / deepseek-api pi-ai 目录认识它们:只往 llm-pi-ai.providers 补一条 apiKeyEnv,模型目录原样保留
xai-billing(Grok) 补 xai: {},认证走登录记录(写 apiKeyEnv 会盖掉登录,所以不写)
bailian 目录里没有:写完整路由(api / baseURL / models)
commandcode 没有可用的聊天接口:只做额度,卡片上标「仅额度」
chatgpt 接入 openai-codex 目录路由,不写 API key;额度读取 Codex 本机登录
claude 接入 anthropic 目录路由,不写 API key;额度读取 Claude Code 本机登录

额度读取和模型认证是两套登录:接入模型后,前往 DSH 的模型供应商设置完成对应 OAuth 登录。插件不会复制 CLI 令牌到模型凭据库。既有路由会保留原配置;如果已有 apiKeyEnv,它会优先于 OAuth,可能使用 API 计费。Codex 的目录路由由上游标为 legacy;Claude 订阅是否能在第三方客户端使用取决于供应商当前政策和服务端。CODEX_HOME 和 CLAUDE_CONFIG_DIR 可指定 CLI 登录目录。

写失败时额度行不会落盘 —— 不会出现「卡片有、模型里没有」的半截状态。与凭据有关的规则 没变:apiKeyEnv 填的是名字,不是 key(填成 key 会被 400 拒掉;配置里已经是 key 形状 的,读的时候会被丢掉、退回预设的引用)。移除时:插件自己写的路由会一起删,pi-ai 目录里 本来就有的路由保留 —— 那可能早就在给模型用了。

卡片头会写明 已接入模型 / 未接入模型 / 仅额度;写着「未接入模型」的那张卡旁边有 「接入模型」,点一下只补模型路由,不会把卡片加成第二张 —— 老配置(比如百炼那条路由还叫 ali)也能接上:插件按 baseURL 认得出同一个网关。

卡片里的模型清单

每张已接入模型的卡里有一条可展开的「▸ 模型」:

动作 做什么
展开 列出这条路由当前提供的模型(llm.listModels(provider))
获取模型 调 llm.discoverModels('llm-pi-ai', { provider, baseURL?, api? }):pi-ai 目录认识的按目录回答(不发请求);百炼这种手工路由才拿存着的 key 去问 GET {baseURL}/models。结果以候选行出现,点「加入」才写进去
添加模型 手填 id + 可选显示名,追加到 llm-pi-ai.providers[provider].models
删除 从同一份 models 里去掉;最后一条不让删(pi-ai 会拒绝解析不出模型的路由)

写的是模型路由自己的 models 列表,所以模型选择器下一次请求就能看到。代价要记住: 目录路由一旦被写上了 models,就固定成这份清单、不再跟随 pi-ai 目录(页面上有一行小字 写着);删掉的模型可以再加回来。

五、输入预测(输入框里的灰字)

参考 yepyeel/dsh-prompt-suggestion 的做法, 在这里重写了一份,和额度共用一套 Host。行为:

  1. 一轮回答结束(最后一条事件是 turn/end 且 reason.kind = completed)、输入框空着、 会话没在跑 → 隔 debounceMs(默认 700ms)去问一次模型;
  2. 模型答案洗掉引号/列表符号、只留第一行,作为灰字贴在光标后面(conversation.input.overlay); 输入框空着时它会顶掉 placeholder(两者都在左上角),光标量不出矩形(空段落常见)就退回 编辑区文字起点;
  3. Tab 或 → 填入(inputActions.captureInsertion() + insertText),Esc 忽略,永不自动发送; 一旦你开始打字,灰字和那次请求一起作废(AbortController);
  4. 用哪个模型:设置里指定就听指定的,否则跟随当前会话的选择,再否则用 profile 的默认模型。

预测本身是一次真实的模型调用,会算进上面的用量与花费 —— 不这样反而会骗自己。

设置页最下面就是它的开关、模型下拉和提示词(留空用内置的那段)。 Host 侧只有 build/prediction.js 一个文件:suggest(sessionId) 读 ctx.sessions 里那个会话的 事件流与 deriveMessages(),调 ctx.llm.stream(...) 收 text-delta。

六、安装

从 GitHub 安装:

dsh plugin --profile desktop add github:xuluoluo/dsh-quota

本地目录重装/更新:

plugin_manager install_bundle  target = D:\Documents\deepseek-harness\default-workspace\plugins\dsh-quota

生效规则:客户端 JS 每次页面加载都重新读盘,改 build/client.js 后刷新页面即可; Host 侧(index.js / providers.js / usage.js / xai-oauth.js / workspace.js)换了代码要重启 DSH。

七、配置(都是可选的)

- id: dsh-quota
  name: 'dsh-allowance'
  config:
    enabled: true          # false = 整页停用(chip 也消失)
    refreshMs: 60000       # 额度快照缓存与自动刷新间隔(10s–1h)
    timeoutMs: 8000        # 单个厂商接口超时
    providers:             # 不写就是空页;写了就是整份替换,页面上的「添加」也是写到这儿
      - id: opencode-go
        preset: opencode-go
        apiKeyEnv: OPENCODE_GO_API_KEY
      - id: grok
        preset: xai-billing
        record: llm-pi-ai/xai
      - id: commandcode
        preset: commandcode
        apiKeyEnv: COMMANDCODE_API_KEY
      - id: deepseek
        preset: deepseek-api
        apiKeyEnv: DEEPSEEK_API_KEY
      - id: bailian               # 目录里没有 → 需要 llm-pi-ai.providers 里那条路由
        preset: bailian
        apiKeyEnv: DASHSCOPE_API_KEY
      - id: my-oc                # 换网关:baseURL 替换预设的默认根路径
        preset: opencode-go
        baseURL: https://opencode.ai/inference/go
    predict:               # 输入预测(默认关)
      enabled: true
      provider: opencode-go   # 留空则跟随会话模型
      model: deepseek-v4.1-flash
      systemPrompt: ''         # 留空用内置提示词
      maxTokens: 120
      maxContextMessages: 12
      debounceMs: 700
    showChip: true         # 把 composer 右侧那枚 chip 打开(默认只有下方窄条)
    alertAt: 90            # 最紧窗口到这个百分比 → ⚠ 接近上限
    alertCriticalAt: 97    # 再到这个 → ⚠ 已达上限(红色)
    balanceFloor: 5        # 余额卡低于这个数(同币种单位)也算告警
    prices:                # 覆盖/补充 build/prices.js 的目录价(USD / 百万 token)
      "opencode-go/deepseek-v4.1-flash": { input: 0.15, output: 0.6, cacheRead: 0.003 }

八、HTTP 面(前端与 Host 之间)

都是同源 JSON,写在 build/index.js 的 ROUTES:

方法 路径 作用
GET /quota/state 快照:卡片 + 用量 + 预设清单(额度缓存 refreshMs)
GET /quota/workspace?sessionId= composer 左侧那两格:{root, name, branch, gitRoot, detached};branch: null = 不是仓库
POST /quota/refresh 强制重读额度
POST /quota/key {id, key} → 写凭据库
POST /quota/clear {id} → key 就 unset,oauth 就删记录
POST /quota/test {id} → 真打一次厂商接口,回 {ok, detail}(页面上的「测试」按钮)
POST /quota/suggest {sessionId} → {suggestion, reason};关掉时 reason: disabled,不碰模型
GET /quota/models 预测的模型下拉:{groups: [{provider, name, models}]}
POST /quota/predict {enabled?, provider?, model?, systemPrompt?, maxTokens?, debounceMs?} → 写进 patch
POST /quota/providers {preset, label?, key?, baseURL?, apiKeyEnv?, id?} → 写额度行 + 接模型路由 + 存 key
POST /quota/providers/attach {id} → 只补这家缺的模型路由(卡片上的「接入模型」)
GET /quota/provider/models?id= 这家的模型清单:{provider, models: [{id, name, contextWindow?, maxTokens?}]}
POST /quota/provider/models/discover {id} → 问厂商要模型(catalog 直接答):{ok, models}
POST /quota/provider/models/add {id, model: {id, name?}} → 追加到 llm-pi-ai.providers[].models
POST /quota/provider/models/remove {id, modelId} → 从那份 models 里去掉(至少留一条)
POST /quota/providers/remove {id} → 从 patch 移除;插件自己写的模型路由顺带删掉,pi-ai 目录里的路由保留
POST /quota/login/start 领设备码,返回 userCode / 授权链接
GET /quota/login/status?id= pending / success / failed / cancelled
POST /quota/login/cancel 取消一次登录

同源规则:只拦「别的站点的页面」。桌面渲染进程向自己的 Host 发请求时既不带 Origin 也不带 Sec-Fetch-Site,所以没有 Origin 就当作本机客户端放行;带了 Origin 的,只有 loopback (127.0.0.1 / localhost / [::1],任意端口)、Sec-Fetch-Site: same-origin|none、或与 Host 一致才放行,其余回 403 并把 origin / host / site 写进响应 —— 页面直接显示,这类问题下次能自己 说出原因(上一版就是被“写操作必须有 Origin”挡掉了自己的「保存」「移除」)。

安全边界(别忽略):这些路由由 Host 的 webServer 直接注册,不经过 DSH 页面那层 token 校验 (GET / 要 token,GET /quota/state 不要)。它们只回额度数字、套餐名、token 计数和 「凭据是否配置 / 来自哪一层」,从不回 token 或 key 明文;写操作受上面的同源规则约束。 本机任意进程都能读到这些数字 —— 在「本机进程本来就能读 ~/.dsh/.credentials.yaml」的前提下, 这不算新增暴露面。

九、验证

cd plugins\dsh-quota

node test\providers.test.mjs                  # 四个 reader 的纯逻辑(38 项)
$env:QUOTA_LIVE='1'; node test\providers.test.mjs   # 再打一次真接口(本机凭据)
node test\host.test.mjs                       # Host:路由、凭据、续期、登录、供应商、模型清单、工作区、用量、同源(108 项)
node test\client.test.mjs                     # Client:注册、取数、渲染、用量、成本、告警、窄条、工作区、模型清单、预测灰字、存 key、登录、加供应商(98 项)
node test\prediction.test.mjs                 # 预测:摘录、清洗、空闲判断、模型选择(22 项)
node test\workspace.test.mjs                  # 工作区:文件夹名、分支、游离 HEAD、探针缓存(10 项)

node tools\grok-login.test.mjs                # grok-login 的 YAML 写入回归(读本机凭据文件)

装好之后真进程上也能直接看:

curl.exe -s http://127.0.0.1:62756/quota/state          # 卡片 + usage + presets
curl.exe -s -o NUL -w '%{http_code}' -X POST -H 'content-type: application/json' `
  -H 'origin: http://127.0.0.1:62756' --data '{}' http://127.0.0.1:62756/quota/refresh

本机实测(2026-10-20 那次装完时):

opencode-go  rolling 2%  weekly 19%  monthly 33%
grok         weekly 100%(本周额度用尽)  GrokBuild 92 / Cursor 5 / GrokBot 3
commandcode  5h 0%  weekly 12%  当月 $69.93 / $70.00  plan=GOAT  10-22 到期不续

没验到的:真实浏览器里的像素级观感(没有浏览器控制)。client bundle 的行为用最小 hooks 运行时跑通了,slot 注册与文档里的名字一致。

十、回滚

  • plugin_manager remove_bundle target = dsh-allowance,或删掉 profile patch 里那一行;
  • Grok 登录写的是 llm-pi-ai/xai,删掉它等于退出登录,不影响其它记录;
  • ~/.dsh/.quota-usage.json 可以随便删,删掉只是用量清零。

十一、目录

package.json          dsh.bundle.patch + dsh.client + exports/files
cordis.patch.yml      insert: dsh-quota
icon.svg              自绘的仪表盘图标
locale/{en,zh}.json   meta.title / meta.description
build/providers.js    四个 reader + fetch(纯函数)
build/usage.js        用量折叠 / 汇总 / 花费折算 / 落盘(纯函数 + 一个小 store)
build/prices.js       目录价表(由 tools/gen-prices.mjs 生成,USD / 百万 token)
build/prediction.js   输入预测:读会话、问模型、洗答案(纯函数 + 一个小服务)
build/workspace.js    工作区:文件夹名 / git 分支 / 探针缓存(exec 注入,可离线测)
build/xai-oauth.js    设备码 / 续期 / grant 形状(fetch 注入)
build/index.js        Host:配置、快照、路由、登录、供应商、工作区、用量接线
build/client.js       Client:chip + 工作区两格 + 设置页(纯 JS,无构建步骤)
test/*.mjs            上面那三组测试 + live-probe
tools/*.mjs           与本机凭据打交道的命令行工具(不进 npm 包,见 package.json 的 files)
  grok-login.mjs        走设备码登录并把 grant 写进 ~/.dsh/.credentials.yaml
  grok-verify.mjs       验证那条凭据能不能直接当 xAI API 凭据用
  grok-login.test.mjs   grok-login 的 YAML 写入回归
  gen-prices.mjs        从 pi-ai 目录重生成 build/prices.js
  asar.mjs              从 DSH 的 app.asar 里 list / cat / extract 包源码

tools/ 里的脚本要读 YAML,用的是仓库外 _ref/yaml 的本地副本;那份第三方代码不进仓库, 缺席时脚本会明确报错退出。

—/ 5

No ratings yet

Verified DSH bundle

Commit 86646fb86b96

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout