dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
华为云 CodeArts 浏览器登录,并把 CodeBuddy、WorkBuddy、LobsterAI、Qoder(国际版与中国版)、TRAE、Cline、Loomy、Raccoon Work、MiniMax Code、TeleAgent、ZCode、Step Code、Antigravity 注册为 LLM 提供商;各 provider 独立账号池,其中多家在同一面板内提供额度余额与每日签到。
deepseek-harness 插件:执行 CodeArts(华为云)登录流程,默认走新式 IAM OAuth
(portal /authorize 授权 → 本地 /oauth/callback 回调 → STS token 端点换取含
refresh_token 的凭据),到期前静默续期,无需再次打开浏览器;旧 ticket 流程保留
为显式回退(flow: 'ticket')。插件还注册一个 codearts LLM provider 路由,使该
凭证可直接用于 CodeArts 后端模型调用。
此外插件内置另外十三个 provider 路由(完整清单见 LLM provider); 其中带积分能力的几个:
openai-compat.ts);
无续期端点(失效只能重新登录);支持积分余额与每日签到。agy CLI) — 见
Antigravity provider;
唯一一个不由本插件持有凭据的面板(凭据归官方 agy 二进制、
一个账号 = 一个隔离 HOME 目录);支持多账号池、family 配额双窗
(5 小时滚动 + 本周)与限流自动切号。codearts 面板同样支持积分账户检测、积分余额与「一键领取积分」
(华为云「每日签到得积分」活动,走 SDK-HMAC-SHA256 签名)——
见 CodeArts 积分。
十四个 provider 面板(含 antigravity)都提供「显示列表」按钮,可逐个开关模型以控制其 是否出现在对话框的模型选择里(黑名单制,默认全部显示)—— 见 模型列表开关。
该包尚未发布到 npm registry。提供两种安装方式:git 仓库安装(推荐,自动拉取 并构建)和源码目录安装(本地开发联调)。
add 以 git+https 方式安装,pnpm 会运行本包的 prepare 脚本自动构建 lib/,
无需手动 pnpm build。
⚠️ pnpm 10 起会拦截依赖的构建脚本,必须先在 profile 的 pnpm-workspace.yaml
(路径形如 ~/.dsh/profiles/<name>/pnpm-workspace.yaml)里放行;而放行键的写法
在 pnpm 10 与 pnpm 11 之间互不兼容,写错就装不上(两种报错见本节末)。
1. 先跑一次安装(这一次必然失败,为的是让 pnpm 打印它期望的键):
dsh plugin --profile <name> add "https://gitee.com/iJetLi/deepseek-harness-codearts.git"
2. 按第 1 步的报错选一种写法,写进 profile 的 pnpm-workspace.yaml。
报 ERR_PNPM_INVALID_VERSION_UNION 的(pnpm 10.x)—— 只认纯包名键:
allowBuilds:
dsh-codearts-auth: true
报 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 的(pnpm 11.x,如本机实测的
一版 DSH Desktop 内置 pnpm 11.7.0)—— git 托管包不认纯包名键,必须用第 1 步
pnpm 打印的完整键(含 #<commit>):
allowBuilds:
dsh-codearts-auth@git+https://gitee.com/iJetLi/deepseek-harness-codearts.git#<第 1 步打印的 commit>: true
两代通用(省事,但放宽了权限):
dangerouslyAllowAllBuilds: true
它会放行该 profile 里所有依赖的构建脚本(不止本插件)。
3. 重跑第 1 步的命令:这次会拉取、构建并安装成功。之后每次升级重新 add 即可。
⚠️ 不要写 dsh-codearts-auth@git+https://gitee.com/…(不带 #<commit>)——
这个"看起来最自然"的键在两代 pnpm 上都不工作:
ERR_PNPM_INVALID_VERSION_UNION("Use exact versions
only."),安装当场失败 —— issue IKJCOC 报的就是它;ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED。⚠️ pnpm 11.x 的精确键里带 commit,因此升级(重新 add 拉到新 commit)后该键
失效,按新报错里的键替换即可;用 dangerouslyAllowAllBuilds 则不必改。
实测矩阵、判据与本仓库自身
pnpm-workspace.yaml为什么不能出现同类键, 见AGENTS.md的「安装(git 插件)的 allowBuilds 键在 pnpm 10 / 11 语义互不兼容」。
先在本仓库中构建 lib/,再用 dsh plugin install 将本地检出安装为 pnpm link:
依赖(指向本目录):
pnpm build:all
dsh plugin --profile <name> install <path-to-this-repo>
dsh plugin install以link:方式安装,pnpm 不会为link:依赖运行prepare脚本,因此必须先手动执行pnpm build:all生成lib/,否则 dsh 启动时 报ERR_MODULE_NOT_FOUND: ... dsh-codearts-auth/lib/index.js。 注意必须用build:all而非build:后者只编译宿主侧,不产出lib/client/jet-hub.js。
每次修改 src/ 或 plugin-src/ 后都需要重新执行 pnpm build:all——dsh 启动时
不会自动重建。
该包声明了 dsh.bundle 补丁(cordis.patch.yml),因此 profile 的 layer 栈会
自动拾取 codearts-auth 行。插件注入由 dsh base 提供的 credentials、
commands 和 llm 服务。
登录入口:Jet Hub 设置页的 CodeArts 面板(与其余五个 provider 一致)。
⚠️ 不注册任何斜杠命令。早期的 /codearts-login、/codearts-status、
/codearts-refresh 三个命令已移除 —— 登录、状态查看与续期统一在 Jet Hub 完成。
编程式调用仍可用:ctx.codeartsAuth.login() / startLogin() /
refreshAccountCredential(refName) / refreshAll(pool)。
插件在 ctx.llm 上注册了一个 codearts provider 路由(OpenAI 兼容端点
https://snap-access.cn-north-4.myhuaweicloud.com/api/v2)。每个模型请求都使用
存储的 AK/SK/SecurityToken 按华为 SDK-HMAC-SHA256 方案签名,并附带
Chat-Id/Session-Id 请求头。
模型列表以远端两个端点的合并结果为准(opengw gateway/config 的
result.models ∪ snap-access /v1/model/builtin 的 builtinModels)。
2026-10-09 实测远端共下发 7 条:deepseek-v4-flash / deepseek-v4-pro /
deepseek-v4.1-flash(每日 1000 万免费 Tokens 福利)、GLM-5.2、
glm-5.2-sft-harmony(展示名 GLM-5.2-ArkTS-SPARK)、
openpangu-2.0-flash (92B) / openpangu-2.0-pro (505B)。
登录后在 dsh Models 页面选择该 provider 即可。
注 1:CodeArts Agent IDE 模型列表显示的 flash ID 为
deepseek-v4-flash-0731(带日期后缀),但后端实际注册的可用 ID 是deepseek-v4-flash(无后缀)。 用deepseek-v4-flash-0731调用会返回InferHub.002002009.404 The model is not registered,因此本插件只注册无后缀的deepseek-v4-flash。注 2:
maas_type: benefit的判据已改为自适应(2026-10-09 第二次推翻)。 早期结论是「benefit 模型必须带该头,否则 404 not registered」,该结论 已失效:实测当前服务端对所有模型带上该头都回InferHub.4004.200 benefit not found,而默认不带时 GLM-5.2 / deepseek-v4-flash / kimi-k2.6-vl 等全部可用。故适配器现在默认不带, 只有当服务端以not registered明确拒绝时才带上重试一次,并把结果学习进缓存 —— 不再依赖任何静态或远端清单。详见src/models.ts的isCodeArtsBenefitModel(含三套判据的历史与失效方式)。注 3:
glm-5.3-flash已退役(服务端不再注册,带/不带该头都失败), 故src/models.ts的RETIRED_MODEL_IDS会把它从远端目录里过滤掉。GLM-5.1与GLM-5在src/llm-adapter.ts的DEFAULT_MODELS里保留, 但远端两个端点都不下发它们 —— 它们只在远端拉取失败时出现在列表里; 按「某次实测没看到 ≠ 不存在」的红线未删除(详见该常量注释)。注 4:视觉(VL)多模态模型不参与对话模型列表(它们上下文小、不支持工具 调用,只通过
analyzeImage等工具间接调用)。⚠️ 该过滤必须大小写不敏感 —— 真实缺陷(2026-10-09 实测):原判据是id.includes('-VL-') || id.endsWith('-VL'),而远端下发的 id 大小写并不统一:Qwen3-VL-235B是大写(会被过滤),而kimi-k2.6-vl是全小写, 原判据不命中,于是它混进了模型选择器。 远端自己对两者的标注完全同类(都是display_enabled: false+model_category: "图片理解"),可见「不该展示」是服务端的明确意图。 复核脚本scripts/probe-codearts-vl.mjs。
凭据来自默认的新式 IAM OAuth 流程(含 refresh_token)。请求发起时会解析最新
凭据,若已过期则先静默续期,再用新 AK/SK/SecurityToken 签名,无需重新打开浏览器。
除 codearts 外,插件另注册十四个独立的 provider 路由:buddy(见
buddy provider)、workbuddy(见
WorkBuddy provider)、lobsterai(见
LobsterAI provider)、qoder(见
Qoder provider)、qodercn(见
Qoder CN provider)、trae(见
TRAE provider)、cline(见
Cline provider)、loomy(见
Loomy provider)、raccoon(见
Raccoon Work provider)、minimax(见
MiniMax Code provider)、zcode(见
ZCode provider)、stepcode(见
Step Code provider)、antigravity(见
Antigravity provider)。十四者互不覆盖,可同时使用。
历史:2026-10-01 曾接入
vibex(RunningHub 的 vibe-coding 平台)作为一个 非 LLM 面板(OAuth 账号 + Remote MCP 工具挂载,无补全端点);2026-10-02 应要求整块移除。相关设计要点保留在 AGENTS.md 的「VibeX 维护要点」里, 仅作将来重新评估时的参考,代码与面板均已不存在。 2026-10-04 曾接入doubao(字节豆包桌面客户端的 CDP 调试通道桥接,非凭据型 LLM 路由);2026-10-05 应要求整块移除 —— 实现文件、Jet Hub 面板、RPC 分支 与本文档章节均已不存在。
CODEARTS_ACCOUNT_XXX(Ref 为 POSIX
标识符格式的凭证 ref),由 Jet Hub 设置页管理。CODEARTS_ACCESS_TOKEN、
适配器在账号池取不到时回退读它」的路径已删除。固定的
CODEARTS_ACCESS_TOKEN 不再被写入或读取 —— 若你此前只用它登录过,
模型列表会变空,请在 Jet Hub 的 CodeArts 面板重新登录一次。{ access_key_id, secret_access_key, security_token, expires_at, domain_id?, user_id?, user_name? } — AK/SK 对用于给每个 CodeArts
后端 API 请求签名。/authorize 授权 → 本地
/oauth/callback 回调收取 code → sts.cn-north-4.myhuaweicloud.com/v1/oauth2/tokens
换取含 refresh_token 的凭据。getFirstRefreshTime 语义:距过期 ≤1h 立即刷,
否则 now+1h 叠加随机秒偏移),全程无浏览器、无人工操作。refresh_token 失效后停止续期并提示
重新登录。ctx.codeartsAuth.login({ flow: 'ticket' })。ticket 凭据没有 refresh_token,
其续期仍意味着重新运行浏览器登录流程。refreshAccountCredential(refName)(账号卡片的「刷新」按钮)
与 refreshAll(pool)(定时调度器)。早期的 refresh() / logout() /
status() / scheduleRefresh() / scheduleModelRefresh() 只服务于单凭据路径,
已随该模式一并移除。jose(用于 DPoP JWS 签发,与 CodeArts Agent 插件实现一致)。refreshAll() 与续期调度器只按 refreshable 过滤,不看 enabled。
停用只应影响「账号池的自动选号」,与「凭据是否需要保持新鲜」无关 —— 停用账号仍然出现在 Jet Hub 里,也仍然参与积分领取。
真实缺陷:两个曾停用的 CodeBuddy 账号显示「凭证过期」,点「一键领取 积分」报
Unexpected token '<', "<html> <h"... is not valid JSON。 根因是两处都按enabled过滤:
refreshAll()里的if (!entry.enabled || !entry.refreshable) continue→ 停用期间refresh_token一路放到失效;src/index.ts的accounts.some(a => a.refreshable && a.enabled)→ 所有账号都停用时,续期定时器根本不启动。用户重新启用后拿到的是死凭据,只能重新登录。四个 provider 的
refreshAll与调度器都必须保持只看refreshable。
Unexpected token '<'积分请求原本直接 await response.json()。凭据失效时腾讯网关返回的是
HTML 错误页,于是抛出 Unexpected token '<', "<html> <h"... is not valid JSON —— 用户既不知道发生了什么,也看不出该重新登录。
现在先取文本再解析,非 JSON 时给出可读原因:
src/credits.ts(CodeBuddy 系)与 src/lobsterai-credits.ts 都已按此处理。
pnpm test — 单元测试(快速,无网络)。pnpm test:e2e — 针对华为线上端点的真实登录流程;需要在打开的浏览器中由人工
点击授权按钮(续期为静默刷新,无需再次点击)。pnpm typecheck、pnpm build:all。pnpm build — 用 tsc 将 src/ 编译到 lib/(生成 .js、.d.ts 和 source
map)。插件宿主侧入口是 lib/index.js。pnpm build:client — 用 esbuild 将 plugin-src/client/ 打包为
lib/client/jet-hub.js(Jet Hub 设置页的客户端 bundle,由 exports["./client"]
引用)。它不在 tsc 的编译范围内,必须单独构建。pnpm build:all — 依次执行上面两步(build + build:client),是完整的构建。pnpm typecheck — 只做类型检查(tsc --noEmit),不产出文件,可在构建前快速
验证。lib/ 已被 gitignore,因此构建是安装或运行前的必需步骤。只执行 pnpm build
会漏掉客户端 bundle,dsh 启动时会因 exports["./client"] 指向的文件不存在而
加载失败(Jet Hub 设置页不显示),请改用 pnpm build:all。
每次修改 src/ 或 plugin-src/ 后都需要重新执行 pnpm build:all——dsh 启动时
不会自动重建。
详见「安装」小节。dsh plugin install 以 link: 方式安装,pnpm 不会为 link:
依赖运行 prepare 脚本,因此必须先 pnpm build:all 生成 lib/(含客户端
bundle)。
默认登录流程(新式 IAM OAuth,PKCE + DPoP):
127.0.0.1 回调服务器。/authorize URL 并打开华为云授权页面。/oauth/callback,携带授权码 code。sts.cn-north-4.myhuaweicloud.com/v1/oauth2/tokens)用
code 换取含 refresh_token 的凭据 JSON,并存储到 CODEARTS_ACCESS_TOKEN 下。旧 ticket 流程保留为显式回退(编程式调用 ctx.codeartsAuth.login({ flow: 'ticket' })):
生成 ticket_id,打开 devcloud.cn-north-4.huaweicloud.com/doer/redirect 认证页,
回调后轮询 snap-manager ticket 端点(120 × 1 秒)获取临时凭证;此类凭据没有
refresh_token,其续期仍意味着重新运行浏览器登录流程。
独立路由 buddy(腾讯 CodeBuddy,OpenAI 兼容端点
https://copilot.tencent.com/v2/chat/completions),Bearer access_token 鉴权。
登录采用 external-link-v2 轮询式(与 CodeArts 的本地回调服务器不同,CodeBuddy 不起本地端口,而是轮询后端 API):
POST /v2/plugin/auth/state?platform=ide → 取得 state 与 authUrl。https://www.codebuddy.cn/login/?platform=ide&state=...。GET /v2/plugin/auth/token?state=...(1 秒间隔、5 分钟超时)→ 令牌;
错误码 11217 表示 token 未就绪,继续轮询。GET /v2/plugin/login/account?state=... → 账户信息;错误码 12151
表示账户信息未就绪,继续轮询。POST /v2/plugin/auth/token/refresh,通过 X-Refresh-Token 头提交
refresh_token。ctx.buddyAuth.login() / status() / refresh() / logout() /
fetchModels()。src/product.ts 的 fallbackModels),
远端 GET /v3/config 可用时优先采用其元数据。Authorization: Bearer 外,还需 X-Domain、X-Product、
X-Product-Code 以及伪装为 CodeBuddyIDE/1.106.1 的 User-Agent。BUDDY_ACCESS_TOKEN,值为含 access_token / refresh_token /
expires_at 的 JSON 字符串。流式工具调用 id 稳定性:CodeBuddy 仅首个工具调用分片携带真实 id (
chatcmpl-tool-xxx),后续参数分片只有index。适配器按 index 缓存并沿用 真实 id(缺失时回退call_{index}),保证同一工具的所有分片 id 一致——否则 跨轮次(每轮都从call_0重新编号)会把tool/result配对到错误的历史条目。
腾讯侧业务码 11140(request illegal)的文案写的是「内容未通过安全审核,请调整后重试」,
但实测是按账号生效:同一份请求体(哪怕只有「你好」两个字)发往池里 7 个账号,
结果是「2 个正常、4 个被拦、1 个限流」,换一个会话照样被拦。所以它的出路是换号,
不是让用户改内容。
适配器对它的处理(CodeBuddy 与 WorkBuddy 共用同一实现):
三条通道都会继续换号:首发 401/403 的认证换号、首发限流的换号、以及 HTTP 200 + SSE 流内的错误帧 —— 坏账号排在池里任何位置都不会中断这一轮。
被拦的账号会在「该模型」上冷却 30 分钟,避免每一轮都重撞它 (池的候选顺序由你在 Jet Hub 拖拽决定,排在前面的坏账号否则每次都会先吃一发)。 ⚠️ 账号卡片因此会亮「限额重置 · <模型> · 30 分钟后」徽章 —— 这个徽章现在同时承载两种原因(服务端限流 / 策略拦截冷却), 因为账号池里只有「模型 → 解禁时刻」这一个「暂时不可用」的载体,没有单独的字段。 若确认该账号是好的,点「重测」(它会真发一条最小消息,正常返回就清掉标记) 或「重置」即可立即解禁。
报错不会显示「API 密钥无效」:这类错误归 PERMISSION_DENIED 而不是 AUTH。
DSH 客户端对 AUTH 一律替换成「API 密钥无效」并丢掉原文,会把用户引向重新登录
—— 而实测这些账号的 token 全部有效。现在看到的是原文,例如:
workbuddy: 全部账号均被服务端安全策略拦截(HTTP 403,已逐个换号重试)。
该拦截按账号生效(同一请求在其他账号可正常返回),请在 Jet Hub 停用或更换被拦账号:
内容未通过安全审核,请调整后重试。 request illegal
max_tokens)远端下发 maxOutputTokens,适配器必须消费它并写进请求体。 这是「回答被
截断、UI 报『已达到输出 token 上限』」的唯一根因修复。
max_tokens 取值优先级:
options.maxTokens(DSH 注入)→ 远端 data.models[].maxOutputTokens → 产品兜底表。
三者皆无则不发该字段,交回网关默认值(不编造数值)。resolveModel() 同时把该值声明为 defaultMaxTokens。DSH 只在调用方未显式
给值时用声明的默认值兜底;适配器不声明就等于把上限永久交给网关默认。defaultMaxTokens 有硬校验(非安全整数
或 ≤0 直接抛 INVALID_MODEL_MAX_TOKENS,整轮对话起不来),故远端是外部
输入,0 / 负数 / NaN 一律视为未声明(见 positiveMaxTokens)。实测(2026-09-19,node scripts/dump-max-output.mjs):
| 模型 | 中国版 scoped 端点 | 中国版 /v3/config | 国际版 /v3/config |
|---|---|---|---|
deepseek-v4.1-flash | 128000 | 131072 | 128000 |
deepseek-v4-pro | 128000 | 131072 | — |
deepseek-v4-flash | 50000 | 50000 | — |
hy4-preview | 64000 | 64000 | 64000 |
⚠️ 网关默认值恰好是 32000(远端 auto / glm-4.6 / kimi-k2.6 等模型
声明的就是 32000),这正是未下发 max_tokens 时 deepseek-v4.1-flash 在
32000 处被截断的原因 —— 不是「网关固定上限」,而是上游声明的额度被适配器丢了。
网关确实接受 max_tokens 且精确生效(node scripts/verify-max-tokens.mjs,
国际版 deepseek-v4.1-flash,当时处于官方限免期 credit: 0):
| 请求 | 结果 |
|---|---|
不带 max_tokens | HTTP 200,finish_reason=stop |
max_tokens: 128000 | HTTP 200,接受 |
max_tokens: 64 | HTTP 200,finish_reason=length、completion_tokens=64 |
第三行是关键证据:输出被精确截断在 64,证明该字段被服务端真实消费,而不是 被静默忽略。
单次请求 ≠ 单轮:每个 step 都是独立请求、各有各的预算。因此 「拆多步写文件」仍是超出单次额度时最有效的手段;
max_tokens只是把单次 额度提升到上游声明的真实值。思考档位与输出预算共享同一额度:
reasoning_tokens计入completion_tokens(实测thinking内容与正文同池),故思考开到max时正文更早撞上上限。
模型切换列表里每个模型后面会显示它的计费倍率:
Deepseek-V4.1-Flash · x0.03
GLM-5.3 · x0.79→x0.50 ← 有促销活动时显示 原价→促销价
倍率拼在模型名(name)后面,而不是说明(description)里 ——
composer 的模型切换菜单只渲染 name,description 仅用于 /model 弹窗。
name 纯属展示,DSH 的选择与持久化只用 id,所以附加价格不会影响会话。
各 provider 的倍率字段形态互不相同,实现里是分开解析的:
| provider | 远端字段 | 真实形态 |
|---|---|---|
buddy / workbuddy | data.models[].credits | 字符串 "x0.29"(可为空串) |
buddy / workbuddy | modelPromotions[].discount.discountedCredits | 字符串 "0.50x"(x 在后);factor: 0 = 免费。⚠️ 只由 /v3/config 下发,企业模型端点没有;且须按 schedule 判时段 |
lobsterai | data[].costMultiplier | 裸数字 0.05 |
qoder | 目录 chat[].price_factor | 裸数字,0 = 免费 |
trae | display_contact_config.consumption_rate.data.rate | 裸数字 0.08;0 = 免费(⚠️ display_contact_config 本身是 JSON 字符串,须二次解析) |
codearts | 无 | 两个目录端点都不含计费字段 |
腾讯系的促销值带 schedule(每日时段 + 有效期 + 时区),必须按当前时间本地
推算此刻是否生效,不能只看 enabled;factor: 0 表示免费(如
hy4-preview 的夜间免费),只有没有时间窗口的 "0x" 才当作「已结束」占位。
TRAE 的活动折扣只用当前确实生效的那一档:activity_discount.enable 为
true 也可能是「无折扣」(discount_type: "none"、原价==折后价,实测 off_peak
型即如此),照显会得到 x0.13→x0.13;已过 end_at 的活动同样不展示。
Qoder 的免费模型(price_factor: 0)显示为「免费」而不是 x0;有错峰折扣的
模型在折扣时段内显示「原价→折后价」,时段外只显示原价:
Qwen3.8-Flash · 免费
Qwen3.8-Max · x0.5→x0.2 ← 22:00–08:00(UTC+8)内
Qwen3.8-Max · x0.5 ← 时段外
DeepSeek-V4-Pro · x0.5
⚠️ 折扣形态三个 provider 统一为「原价→折后价」(TRAE
x0.4→x0.2、 buddyx0.79→x0.50、Qoderx0.5→x0.2)。Qoder 早期是「只有折后价 + 中文角标」(x0.2 错峰 4 折),问题有二:① 看不出原价与折扣幅度; ② 角标与数字冗余(0.2/0.5 本就是 4 折)。
⚠️
credits与discountedCredits的x位置相反("x0.29"vs"0.50x")。早期版本只认前缀写法,导致促销价被静默丢弃。⚠️ 腾讯系两个端点下发的模型 id 集合不同,必须取并集 —— 促销可能只挂在 其中一个端点独有的 id 上。实测
hy4-preview-f(新用户限时免费)只由/v3/config下发且被 agent 引用,而 scoped 端点给的是hy4-preview(无促销)。只采信 scoped 就会显示x0.29而 IDE 显示免费(用户报障 「hy4 preview 现在 ide 是免费我们还是 0.29」)。不同账号下发的变体 id 也不同,排查时须多账号对照。⚠️ 腾讯系的促销只在
/v3/config下发,企业模型端点没有 —— 而后者被优先 返回,所以早期实现永远不显示促销(用户报障「codebuddy 的倍率显示也是 没折扣的,GLM-5.2 是 0.5,现在显示 0.79」)。现补取促销表并合并。 且必须按schedule判此刻是否生效(glm-5.2夜间/白天是两条互补活动, 不看时段会全天显示折扣价);factor: 0是「免费」而非「已结束」 (hy4-preview夜间免费,用户报障「夜间 0,现在显示 0.29」)。 ⚠️/v3/config有 UA 校验,UA 不对返回 HTTP 200 +code:12403, 极易误判为「该端点没有促销数据」。⚠️ 产品兜底表是白名单(不在表里的 id 会被丢弃),但被 agent 引用的 模型例外保留(服务端自己的「可选」信号)—— 否则
hy4-preview-f会被丢掉。 判据不是猜 id 后缀(-f/-x/-sg含义各异,猜错会放进不可用的模型)。⚠️ Qoder 的字段是
price_factor,不是cost_multiplier(后者是 LobsterAI 的)。而price_factor: 0是合法的免费值,不能用> 0过滤掉。⚠️ 倍率不能放进
description:那在切换模型列表里根本不可见 (用户报障「消耗倍率没有显示在切换模型列表的后面」)。⚠️ Qoder 的错峰时段按
windowStart/windowEnd本地推算,不采信目录的promotion.active—— 后者是目录下发那一刻的快照,客户端长时间不重启 就会与真实时段脱节(用户在时段外看到折后价、或时段内看不到折扣)。 生效价由beforePromotionPriceFactor × discountFactor推出(实测三条全部吻合)。 真实缺陷:早期表里存的是「采集时刻的生效价」却当成恒定值展示, 且 14 个模型的数值本身也是过期估值(smodel写 3.2 实际 8), 用户报障「qwen3.8-max 是 0.5 打折到 0.2,界面显示的是 0.5」。
服务端会给不同 id 配同一个展示名,实测三组:
| 模型 id | 远端展示名 | 实际差异 |
|---|---|---|
deepseek-v4.1-flash / deepseek-v4.1-flash-sg | 都是 Deepseek-V4.1-Flash | 新加坡区,倍率 x0.00 vs x0.03 |
hy3 / hy3-x | 都是 Hy3 | — |
hy4-preview-f / hy4-preview | 都是 Hy4 preview | — |
由于选择器按展示名渲染,这些会变成无法区分的重复条目(用户报障: 「workbuddy 国际版同时显示 2 个 ds v4.1 flash,IDE 只有一个」——IDE 按展示名 归并,本插件按 id 列出)。二者是不同区域的独立计费实体,不能简单丢弃其一, 故对撞车的 id 求公共前缀、把剩余段追加到名字后:
Deepseek-V4.1-Flash · x0.00 (deepseek-v4.1-flash)
Deepseek-V4.1-Flash · x0.03 SG (deepseek-v4.1-flash-sg)
Hy3 · x0.00 (hy3)
Hy3 · x0.05 X (hy3-x)
用公共前缀而不是硬编码 -sg,是因为撞车组会随服务端上新变化(本次实测三组
里只有一组带 -sg)。LobsterAI 实测无同名(28 个模型 0 组重名),故不做消歧。
独立路由 workbuddy(腾讯 WorkBuddy 国际版 / WorkBuddy AI),与
buddy provider 同源:共用同一 CLI 内核与同一认证协议
(cli-external-link 轮询式),Bearer access_token 鉴权。差异收敛在
src/product.ts 的产品配置里:
| 项 | CodeBuddy(中国) | WorkBuddy(国际版) |
|---|---|---|
endpoint | https://copilot.tencent.com | https://www.workbuddy.ai |
platform | ide | workbuddy-ai |
| 登录 URL 附加参数 | 无 | version / loginSessionId |
pluginVersion | — | 5.5.2 |
模型列表不能与中国版共用:两者的路径与响应解析完全相同
(GET /v3/config → data.data.models / data.data.agents),差异只来自
endpoint —— 不同区域的后端返回不同模型池(中国版含 glm / hy / deepseek 系,
国际版含 claude / gpt / gemini / kimi 系)。因此 endpoint 必须随产品切换,
不能被当成全局常量。
登录流程与 CodeBuddy 一致(auth/state → 浏览器授权 → 轮询 auth/token →
轮询 login/account),仅身份标识与端点按上表区分。X-Product-Code 为
workbuddy,X-Domain 随 apiDomain 切换为 www.workbuddy.ai。
没有每日签到积分:国际版后端不提供签到接口(内核中只有
/v2/billing/meter/get-dosage-notify 用量通知),因此 Jet Hub 的 WorkBuddy
面板不显示「一键领取积分」按钮;签到领取在 CodeBuddy 面板完成。
但积分余额(Credits Balance)可以查。 签到与余额是两项独立能力:国际版 确实没有签到,但有积分余额查询接口,见下节。不要因为"没有签到"就推断 也查不到余额。
ctx.workbuddyAuth.login() / status() / refresh() / logout() /
fetchModels()。WORKBUDDY_ACCESS_TOKEN,值为含 access_token / refresh_token /
expires_at 的 JSON 字符串(与 BUDDY_ACCESS_TOKEN 同构)。WORKBUDDY_ACCOUNT_<UUID_SHORT>,由 Jet Hub 设置页「+ 新建账号」
登录时自动生成并登记到账号池;每条账号记录带 provider: 'workbuddy',
与 CodeBuddy 的 BUDDY_ACCOUNT_* 相互隔离,不会串用凭据或限流标记。workbuddy 指向中国版
(copilot.tencent.com)。启动时会自动清理凭据 domain 与当前
apiDomain 不符的旧账号(这类凭据在新端点必然失败),清理结果记入日志,
请在 Jet Hub 重新登录。refresh_token 经 X-Refresh-Token 头提交),无需重新打开浏览器。Jet Hub(设置页)的账号面板按 provider 分组展示,WorkBuddy 是其中一栏:
provider: 'workbuddy' 的账号。credits.claimAll(实现见 src/jet-hub-rpc.ts,签到客户端见
src/credits.ts)。credits.status(查询某 provider 下全部启用账号的签到状态),
但前端尚无消费者:plugin-src/client/jet-hub.js 只调用 credits.claimAll,
credits.status 目前仅供外部脚本或直接 RPC 调用使用。llm-workbuddy。account.create 对全部四个 provider 都遵守同一契约:在用户完成授权之前
就返回 loginUrl,由前端立即 window.open,后台再异步等回调。
这不是风格偏好,而是浏览器安全模型的硬约束:window.open 只在用户点击后的
transient activation 窗口(约 5 秒)内被允许。若 account.create 阻塞到
用户授权完成才返回(数十秒),拿到 URL 时手势早已过期,弹窗必被拦截并返回
null,前端若兜底执行 window.location.href = loginUrl,就会把整个设置页
导航到外部登录页 —— 用户报障「codearts 新建账号应该弹出新的页面,现在主页面
直接跳转过去了」正是此因。
| provider | 后端实现 | 前端交互 |
|---|---|---|
buddy / workbuddy | runBuddyLoginFlow 不 await,立即返回 URL | 弹小窗 + 轮询 login.poll |
codearts | CodeArtsAuth.startLogin() | 同上 |
lobsterai | LobsteraiAuth.startLogin() | 同上 |
src/login.ts 的 startOAuthFlow 与 src/lobsterai-oauth.ts
的 startLobsteraiLoginFlow;原阻塞式 runOAuthFlow /
runLobsteraiLoginFlow 保留(CLI、e2e 仍用),现由前者实现。try/finally 兜底,故超时与「结果落定即关闭回调
服务器」都收在 start* 内部,避免泄漏监听端口。window.location.href 兜底:弹窗被拦截时改为展示一个可点击
的登录链接(loginUrlForManual),轮询照常进行,用户手动打开也能完成登录。tests/unit/jet-hub-rpc.spec.ts 的
「account.create 必须立即返回 loginUrl」:用「授权永不完成」的替身模拟用户
尚未操作 —— 旧实现会超时失败,新实现立即返回。Jet Hub 各 provider 面板的账号卡片可拖动调整顺序,卡片左上角显示序号。
这不是纯 UI 装饰:账号列表的数组顺序就是 getAvailableAccount 的候选
优先级 —— 自动选号、以及限流后换号重试,都按这个顺序取「第一个可用账号」。
把常用账号拖到前面,它就会优先被使用。
语义是「手动顺序优先,限流豁免」:
⚠️ 早期实现有一个
candidates.sort((a,b) => resetAtA - resetAtB)(按限流 重置时间最早到期优先)。它会让拖拽形同虚设:用户把某账号拖到首位,只要 另一个账号的重置时间更早,实际选中的仍是后者。该排序已移除,tests/unit/account-pool.spec.ts有回归用例锁死。
交互细节:
⠿ 手柄;实现:RPC account.reorder → AccountPool.reorderAccounts()
(只动本 provider 占用的下标,其他 provider 账号位置不变);
前端拖拽逻辑在 plugin-src/client/account-order.js(纯函数,可单测)。
账号卡片上的「积分」一行显示该账号的可用积分,与 IDE 顶部显示的
Credits Balance 是同一个数值。鼠标悬停可看到各资源包的明细与到期时间。
两个 buddy 的卡片还会额外显示「临时 X · 永久 Y」分桶(与下方 锁定永久积分同一判据),tooltip 里每个包 标出「距到期 N 天」。这条也直接解释了"锁上为什么没号可用"——临时桶是 0。
⚠️ 分类在渲染的那一刻现算,不缓存:它是「距扣费截止是否满 15 天」的判断,
而宿主长期开着、时间只向前流——一笔距到期 15 天 30 秒的余额,用户什么都不做,
半分钟后就越过了线。所以选号侧缓存的也只是 get-user-resource 的原始包列表
(TTL 60 秒只用于抑制网络请求),命中缓存同样重新分桶;面板侧每次渲染传当下的