Skip to main content
L

dsh-radeon-capacity

lcy362/dsh-radeon-capacity

Shows AMD Radeon Cloud AI Capacity for every Radeon model — a per-model badge in the model picker, and the selected model's capacity on the same row as its name. The badges register only once a Radeon provider is configured, so they never displace the host's own model annotations. Quota is display-only, since AMD is currently free and unmetered. Its settings page can also write the provider into the DSH user layer, so the provider keeps working after the plugin is uninstalled. Its HTTP routes have no DSH access check, so a web page open in your browser could call them.

Install

dsh plugin --profile web add github:lcy362/dsh-radeon-capacity

README

dsh-radeon-capacity

给 DeepSeek Harness 的独立插件:在选模型之前,就能看到 AMD Radeon Cloud(Token Factory)每个模型现在忙不忙。

主功能是容量读数。配置能力是顺带的 —— 已经配好 Radeon provider 的人装完即用,什么都不用填。额度也会显示,但仅作展示:AMD 目前免费不限量,这个数字不影响你能不能调用。

它不接管任何模型调用 —— 传输、思考字段、用量计量仍由 DSH 自带的 dsh-llm-pi-ai 承担。

它解决什么

Radeon Cloud 的模型会在 idle / busy / full 之间浮动,这才是真正会咬人的东西 —— 撞上 full 就得等并发配额腾出来。原来的信息只有两个去处:

  1. Radeon 的网页控制台(要切窗口、要登录);
  2. 撞上限流之后看报错。

控制台里那根「AI Capacity」进度条的来源是 GET /radeon/api/tokenfactory/load,而 DSH 的模型选择器恰好有一个可以承载徽章的槽。把两边接起来,在你点下模型之前就知道它现在挤不挤。

它长什么样

位置内容
模型选择器每个 Radeon 模型名后一枚徽章:● 12%(绿)/ ● 63%(橙)/ ● 100%(红)
模型选择器同一行当前选中模型的容量,就在模型名左边(WorkBuddy / Trae 挂积分的位置);额度弱化跟在后面:● Busy 55% · $0.97 / $1.00
设置 → 插件 → dsh-radeon-capacity已写入的模型连同实时容量逐条列出;配置区(可选)

徽章是探测到 Radeon provider 之后才注册的。没有 Radeon provider 时它根本不占那个槽,不会把内核自带的模型标注顶掉。

读数挂在 conversation.input.right —— 它和模型选择器在同一个 standardControls 容器里、渲染顺序就在选择器左边,所以读数和模型名是并排的;选好模型、还没开始 会话时同样显示 —— 它和模型选择器共用同一个 sessionId 守卫,看得见模型选择器就看 得见读数。(早期版本用的是输入框下方的 conversation.composer.dock,那个槽 只在 composer 变体渲染,新会话是 hero 变体,于是"选定但未开始"时读数会消失。)

界面上的读数一律英文(Idle / Busy / Full 是 AMD 控制台自己的术语);设置页 走 DSH 的 locale 机制,中英双语,跟随界面语言。

安装

从 npm 装(推荐)

已发布到 npm,装到的是预构建产物,安装时不需要构建:

dsh plugin --profile <你的 profile> add dsh-radeon-capacity

桌面端(DSH Desktop)请用应用内的「设置 → 插件」搜索安装。用命令行装 desktop profile 之前 要先把 App 完全退出,否则命令写进去的是另一份 profile(profiles/web),界面里不会出现。

从社区目录装

dshmarket 读的那份目录由 awesome-dsh-plugin 策展。收录之后它会直接出现在「插件 → 发现」里,点一下就能装。

用本仓库自带的索引装

还没进目录时,也可以把本仓库自带的 format 1 索引加为市场来源 (设置 → 插件 → 发现 → 市场来源),把下面任一 URL 加进去。首选 jsDelivr,国内可达:

https://cdn.jsdelivr.net/gh/lcy362/dsh-radeon-capacity@main/market/index.json

同一份文件的 GitHub 原件(部分网络下拉不动):

https://raw.githubusercontent.com/lcy362/dsh-radeon-capacity/main/market/index.json

加完就能在「发现」里看到 Radeon Capacity,点安装即可(市场按索引里钉死的 commit 装, 不会装到分支上的半成品)。

从源码装

cd ~/.dsh/profiles/desktop
pnpm add link:/path/to/this/repo
# 把 "dsh-radeon-capacity" 登记进 package.json → dsh.profile.bundles

不用重启 DSH。 内核的 HMR(dsh-hmr)同时盯着 profile 的 package.json 和 cordis.patch.yml,一改就重算补丁、把新 bundle 装进来;客户端半区由 dsh-client-hmr 轮询 bundle 的 mtime,变了就推 /plugins/events,浏览器自动换成新 revision 的 client.js。

例外只有一种:日志里出现 changed modules keep running their previous code until a restart 时,那些已经被 旧代码加载过的模块要重启才换得掉。第一次安装不涉及这条(旧代码压根没加载过)。

已经配好 Radeon provider 的人:装完就有读数

什么都不用做。 插件按 baseURL 里是否含 developer.amd.com.cn 认人,不看你把 provider 叫 amd 还是 radeon-cloud,也不管密钥引用叫 AMD_API_KEY 还是别的。容量读数会直接出现在界面上,额度作为参考值跟在后面。

这是设计上的取舍:认 baseURL 而不是认 provider id,所以你已有的配置不需要为这个插件改任何东西。

顺带:还没有 provider 的话,插件也能替你配

设置 → 插件 → dsh-radeon-capacity → 填三格:

  • provider id:模型选择器里的路由名,默认 amd;
  • 凭据引用名:默认 AMD_API_KEY,密钥存在 ~/.dsh/.credentials.yaml 的这个键下;
  • API 密钥:rc-…,只经过宿主内存,不落浏览器、不回显。

点「写入配置」。写入的是 DSH 用户层(ctx.settings.update → profile 补丁),因此:

卸载本插件之后,provider 仍然在、仍然能用。

配置页不会让你对着一个空表单重新猜一遍 —— 它把已写入的真实状态摆出来:哪些模型已经写进去了、每个模型旁边标「已写入 / 新增 / 将移除」,按钮文案也跟着变(「新增 provider」/「保存修改」/「重新写入(无变化)」),下面还有一块「本次操作」把要加什么、要减什么逐条列清。

配置在提交后立刻返回,不等容量刷新 —— 容量端点单次约 25 秒,等它会让按钮白卡 25 秒,而配置其实早已落库。

插件自身的配置(可选)

写在 profile 的 cordis.patch.yml 里那行 radeon-capacity 下:

- id: radeon-capacity
  name: dsh-radeon-capacity
  config:
    providerId: amd              # 新建 provider 时的默认路由名
    apiKeyEnv: AMD_API_KEY       # 默认凭据引用
    displayName: Radeon Cloud    # 选择器里显示的供应商名
    baseURL: https://developer.amd.com.cn/radeon/api/v1
    quotaProbe: true             # 关掉就不再发 1-token 探针(额度读数消失)
    capacityTtlMs: 30000         # 容量缓存
    quotaTtlMs: 60000            # 额度缓存

插件自己只声明一行 insert,不写任何 provider 定义 —— 否则卸载插件会连带把 provider 带走。

数据源

容量:GET /radeon/api/tokenfactory/load

{ "models": { "GLM-5.3-Flash": { "state": "full", "label": "At capacity", "utilization": 100 } } }

这是控制台内部路由(不在公开文档里),有三个必须按它设计的事实:

  • 慢:TTFB 稳定在 ~25 秒,cache-control: no-store,?model= 之类的过滤参数无效 → 必须缓存 + 单飞,绝不能每次打开选择器就直连;
  • 集合会浮动:不同时刻返回 13~16 个模型(只含有流量的),所以「查不到」是正常状态,界面如实显示无读数,不猜成空闲;
  • 未文档化:随时可能消失,一切失败都静默降级。

因为慢,宿主侧是 stale-while-revalidate:过期就先给旧值、后台刷新,界面从不挂在一次 25 秒的请求上。数据年龄会出现在 tooltip 和插件配置页里 —— 读数最多就是 30 秒级新鲜,这是服务端决定的,做到更快只能造假。

额度(仅展示):x-ratelimit-* 响应头

网关在任何正常响应上都会回这几个头:

x-ratelimit-limit-user-daily-usd: 1
x-ratelimit-remaining-user-daily-usd: 0.982
x-ratelimit-limit-user-rpm: 20
x-ratelimit-remaining-user-rpm: 17

AMD 目前免费不限量,这些数字是名义值,不构成实际限制。所以插件只把它们展示出来:

  • 不做任何门控 —— 不会因为「额度用完」禁用模型或拦截请求;
  • 不参与告警、不改变颜色、不进主读数,界面上弱化处理;
  • tooltip 里明确写了「仅供参考,不影响能否调用」。

没有「查额度」的独立端点,所以插件发一次 max_tokens: 1 的探针把这些头读出来(实测 0.31 秒,成本可忽略,占 1 次 RPM)。探针有独立的 60 秒 TTL 与单飞,不与容量一起高频轮询。觉得没必要就 quotaProbe: false 完全关掉 —— 关掉只少一个参考数字,容量读数不受影响。

模型发现

档位表是实测快照(10 个对话模型)。上游会加新模型(2026-10 实测多了 Gemma-4-31B-IT;同一份目录里还有 Clef / d3 / OmniVoice 这类 context_length: 0、输出模态是 decision 的辅助路由服务,不是对话模型,会被过滤)。

插件配置页会额外问一次 GET /v1/models,把「线上有、表里没有」的模型单独列出来。 勾选后按 reasoningEfforts: false 写入 —— 猜档位会让请求直接 400,关掉档位至少能正常对话。

思考档位

Radeon 的模型目录接口不返回 reasoning_effort 支持情况,只能实测。本插件内置一张实测表(10 个对话模型,四种档位形状),配置 provider 时连同 reasoningEfforts 一起写进去:

形状模型档位
全档DeepSeek-V4.1-Flash / V4-Flash / Vision-Exp、MiMo-V2.6-Flash、MiniCPM-V-4.7-35B-A3Boff(none) + minimal/low/medium/high/xhigh/max
Qwen 系Qwen3.8-27B、Qwen3.8-Flash-Nextoff(none) + low/medium/xhigh(传 high 会 400)
三档GLM-5.3-Flash、MiniCPM-V-4.7-1Blow/medium/high(关不掉思考)
无MiniCPM5-2Bfalse(接受档位但不返回分离思考内容)

MinerU2.5-Pro 刻意不收:它走 /v1/ocr,context_length 为 0,当对话模型用必然失败。

配套的 compat 也有实测依据(6/10 个模型拒绝 DSH 默认发的 developer 角色、网关不认 max_completion_tokens、thinking_budget 三种写法都被静默丢弃),逐条注释见 src/shared/catalog.js。

实现要点(也给二次开发的人)

  • 只读同源路由:/api/radeon-capacity/{state,catalog,refresh,config},Origin 不同源一律 403;
  • 不把上游 body 透传:normalizeLoad 只保留 state/label/utilization 三个字段并夹到 0–100;
  • 凭据解析顺序:探测到的 provider 的 apiKeyEnv → 插件配置的 ref → RADEON_CLOUD_API_KEY,每次刷新重新解析(换钥免重启);
  • 客户端是手写单文件 bundle:window.__ModuleLoader__.load({ id, factory }),require() 只能拿平台 seed(react 等),所以没有构建步骤、没有相对 import;
  • single 槽的坑:conversation.input.model.label 声明为 kind: "single",渲染时取 entriesOfSlot(key)[0]。抢占它必须同时满足「priority 更靠前」和「自己输出模型名」—— fallback: model.name 只在槽里一条都没有时生效。所以本插件在没有 Radeon provider 时不注册,而不是注册一个空壳。
  • 接管是全局的:一旦接管,内核自带的 dsh-client-ui-deepseek-model-label-1017 就再也不会渲染。所以本插件按同一套判定(provider.id / model.id / model.name)把那个 1017 标记等价补上;内核改判定时要跟着改。不想承担这个耦合就把 modelLabelBadge 关掉(代价是选择器里没有容量徽章)。
  • 表单初值必须是派生的,不能靠 effect 回填:面板可能在 /state 返回前就渲染,那一刻「默认全选」会被固定成用户的选择,结果界面显示全部模型、实际只写入几个。这条有回归测试盯着。

已知限制

  • 容量读数最细只能到 ~30 秒(上游 25 秒 TTFB + 缓存),不是秒级;
  • full 表示并发预算已满,不等于不能用 —— 所以任何地方都不做硬禁用,只标红;
  • 上游只返回「有流量」的模型,冷门模型没有读数;
  • 额度是名义值(AMD 当前免费不限量),仅展示、不做门控;探针本身占 1 次 RPM;
  • 控制台内部端点可能变更;变更后容量会退回「暂无读数」,其余功能不受影响。

开发

npm test                        # 44 项:缓存/单飞/退避、额度头解析、provider 探测、路由,
                                # 以及把客户端 bundle 真加载一遍的界面集成测试
node scripts/smoke.mjs          # 真机冒烟:打真实端点,只打印容量与额度,不打印密钥

npm test 带 --test-force-exit:客户端半区会起一个轮询定时器(界面行为), 不带的话测试进程会一直挂着。

发布与收录(维护者)

发一个版本

npm version patch --no-git-tag-version   # 改 package.json 的版本号,但先不打 tag
git commit -am "chore(release): vX.Y.Z" && git push
bash scripts/release.sh                  # 跑测试 → 打包 → 建 tag 与 Release
npm publish                              # 需要先 npm login

scripts/release.sh 会把产物固定命名为 dsh-radeon-capacity.tgz 再上传,然后打印出新的 commit SHA,用它去更新 market/index.json 的 source.commit(索引不能指向自己那个 commit)。

资产名不能带版本号:索引里的 tarball 指向 releases/latest/download/,带版本号的文件名在 下一次发版后就会 404,而目录的 probe-tarballs.mjs 实测 404 之后会静默丢掉这个字段。

为什么一定要发 npm

dshmarket 的默认排序是「npm 下载量(近 30 天)」倒序,而比较函数只认 downloads 这一个字段:

const hasDownloads = (p) => typeof p.downloads === "number"
  • 没有 npm 数的条目一律排在所有有数的条目之后,在没数的那一堆内部才按 star 排;
  • 那份目录里没有 githubReleaseDownloads 字段(我核对过线上 plugins.json 的字段集), 所以「不发 npm,靠 GitHub Release 下载量兜底」这条路在这份目录上并不存在——卡片上直接没有数字。

两个前提:

  1. package.json 必须有指回本仓库的 repository。目录的 probe-npm.mjs 认「这个插件有 npm 包」 的条件是:读仓库 package.json 的 name → 去 registry 查 → 并且该 npm 包的 repository 指回同一个 GitHub 仓库(防抢名)。少了它会被永远判成 npm: null。
  2. 包名 dsh-radeon-capacity 在 npm 上未被占用。

收录到社区目录

dshmarket 读的目录来自 awesome-dsh-plugin/awesome-dsh-plugin, 一个插件一个 YAML、一次 PR 只加一个文件:

data/plugins/lcy362__dsh-radeon-capacity.yml

CI 会检查:仓库某处 package.json 声明了 dsh.bundle、仓库创建满 1 天、未归档、不是 DSH 自己; 另外还要求打 dsh-plugin topic、描述与代码相符。npm 包与 tarball 都在时,卡片优先给 npm 安装命令。

losebird/dsh-plugin-market 是另一份 registry(registry/curated/<id>.json), dshmarket 不会读它;想在那里看到条目,得把那个索引单独加进「发现 → 市场来源」。

许可

MIT。思考档位与 compat 的实测表来自社区插件 @zhz8888/dsh-radeon-cloud-patcher(MIT)的线上实测,本仓库独立实现读数与配置两半,未复用其代码路径。

Related plugins