anye-99/dsh-codearts-auth ↗★ 0
dsh-codearts-auth
deepseek-harness plugin: CodeArts (Huawei Cloud) browser login, storing the temporary credential via ctx.credentials 适合需要调用CodeArts后端模型或使用其积分功能的用户。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:anye-99/dsh-codearts-authREADME
Read the full README ↗dsh-codearts-auth
deepseek-harness 插件:执行 CodeArts(华为云)登录流程,默认走新式 IAM OAuth
(portal /authorize 授权 → 本地 /oauth/callback 回调 → STS token 端点换取含
refresh_token 的凭据),到期前静默续期,无需再次打开浏览器;旧 ticket 流程保留
为显式回退(flow: 'ticket')。插件还注册一个 codearts LLM provider 路由,使该
凭证可直接用于 CodeArts 后端模型调用。
此外插件内置另外十三个 provider 路由(完整清单见 LLM provider); 其中带积分能力的几个:
- buddy(腾讯 CodeBuddy) — 见 buddy provider; 另支持「一键领取积分」(每日签到)与「锁定永久积分」。
- workbuddy(腾讯 WorkBuddy 国际版) — 见 WorkBuddy provider; 支持「锁定永久积分」。
- lobsterai(有道 LobsterAI / 龙虾) — 见 LobsterAI provider; 另支持「一键领取积分」(每日签到)。
- qoder(阿里系 Qoder) — 见 Qoder provider; 支持积分余额与每日领取(每日 100 Credits,10:00(UTC+8)刷新); 走加密推理端点,模型池与客户端一致(含 Qwen3.8 系列)。
- loomy(讯飞 Loomy 办公助手) — 见 Loomy provider; 唯一用短信验证码登录、唯一不能自动续期的 provider; 支持积分余额(两个池)、每日额度签到、新手任务一键领取 10000 积分, 以及「锁定永久积分」(CodeBuddy / WorkBuddy 也有同名按钮,但判据不同)。
- minimax(MiniMax Code 中国版) — 见 MiniMax Code provider; 首个 Anthropic Messages 协议族的 provider; 支持积分余额与每日签到,推理已启用(Anthropic Messages, 4 个模型实测通过;⚠️ 图片输入未实现)。
- teleagent(中国电信天翼 AI 星辰超级智能体) — 见
TeleAgent provider;
唯一一个登录产物需要用户手动粘贴回调地址的面板(它的回调 URL 是服务端常量、
且自定义协议插件注册不了,后端抓不到 token);
OpenAI 兼容且无加密(推理直接复用
openai-compat.ts); 无续期端点(失效只能重新登录);支持积分余额与每日签到。 - antigravity(Google Antigravity / 官方
agyCLI) — 见 Antigravity provider; 唯一一个不由本插件持有凭据的面板(凭据归官方agy二进制、 一个账号 = 一个隔离 HOME 目录);支持多账号池、family 配额双窗 (5 小时滚动 + 本周)与限流自动切号。
codearts 面板同样支持积分账户检测、积分余额与「一键领取积分」
(华为云「每日签到得积分」活动,走 SDK-HMAC-SHA256 签名)——
见 CodeArts 积分。
十四个 provider 面板(含 antigravity)都提供「显示列表」按钮,可逐个开关模型以控制其 是否出现在对话框的模型选择里(黑名单制,默认全部显示)—— 见 模型列表开关。
安装
该包尚未发布到 npm registry。提供两种安装方式:git 仓库安装(推荐,自动拉取 并构建)和源码目录安装(本地开发联调)。
方式一:从 git 仓库安装(推荐)
add 以 git+https 方式安装,pnpm 会运行本包的 prepare 脚本自动构建 lib/,
无需手动 pnpm build。
⚠️ pnpm 10 起会拦截依赖的构建脚本,必须先在 profile 的 pnpm-workspace.yaml
(路径形如 ~/.dsh/profiles//pnpm-workspace.yaml)里放行;而放行键的写法
在 pnpm 10 与 pnpm 11 之间互不兼容,写错就装不上(两种报错见本节末)。
1. 先跑一次安装(这一次必然失败,为的是让 pnpm 打印它期望的键):
dsh plugin --profile 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 打印的完整键(含 #):
allowBuilds:
dsh-codearts-auth@git+https://gitee.com/iJetLi/deepseek-harness-codearts.git#: true
两代通用(省事,但放宽了权限):
dangerouslyAllowAllBuilds: true
它会放行该 profile 里所有依赖的构建脚本(不止本插件)。
3. 重跑第 1 步的命令:这次会拉取、构建并安装成功。之后每次升级重新 add 即可。
⚠️ 不要写 dsh-codearts-auth@git+https://gitee.com/…(不带 #)——
这个"看起来最自然"的键在两代 pnpm 上都不工作:
- pnpm 10.x:解析该键即抛
ERR_PNPM_INVALID_VERSION_UNION("Use exact versions only."),安装当场失败 —— issue IKJCOC 报的就是它; - pnpm 11.x:不报错,但该键缺 commit,永远匹配不上真实依赖,表现为反复
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 install
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)。
LLM provider
插件在 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 设置页管理。 - 单凭据模式已移除:早期那条「登录写固定 ref
CODEARTS_ACCESS_TOKEN、 适配器在账号池取不到时回退读它」的路径已删除。固定的CODEARTS_ACCESS_TOKEN不再被写入或读取 —— 若你此前只用它登录过, 模型列表会变空,请在 Jet Hub 的 CodeArts 面板重新登录一次。 - 值:JSON 字符串
{ access_key_id, secret_access_key, security_token, expires_at, domain_id?, user_id?, user_name? }— AK/SK 对用于给每个 CodeArts 后端 API 请求签名。
续期(refresh)
- 默认登录流程为新式 IAM OAuth(PKCE + DPoP):portal
/authorize授权 → 本地/oauth/callback回调收取code→sts.cn-north-4.myhuaweicloud.com/v1/oauth2/tokens换取含refresh_token的凭据。 - 凭据在过期前 1 小时静默续期(
getFirstRefreshTime语义:距过期 ≤1h 立即刷, 否则now+1h叠加随机秒偏移),全程无浏览器、无人工操作。 - 刷新失败后 10 分钟重试(异常网络 1 分钟);
refresh_token失效后停止续期并提示 重新登录。 - 旧 ticket 流程保留为显式回退:编程式调用
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 ' 根因是两处都按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 ' 流式工具调用 id 稳定性:CodeBuddy 仅首个工具调用分片携带真实 id
(
chatcmpl-tool-xxx),后续参数分片只有index。适配器按 index 缓存并沿用 真实 id(缺失时回退call_{index}),保证同一工具的所有分片 id 一致——否则 跨轮次(每轮都从call_0重新编号)会把tool/result配对到错误的历史条目。
安全策略拦截(11140):会自动换号,被拦账号冷却 30 分钟
腾讯侧业务码 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 只在调用方未显式 给值时用声明的默认值兜底;适配器不声明就等于把上限永久交给网关默认。- ⚠️ 远端非法值必须过滤: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_con |