dsh-qoder-connect
把 Qoder CN 桌面 App 里已经买好的模型,接进 DeepSeek Harness(DSH)。 零配置:装完就能用,不需要填 API Key、不需要单独登录、不需要 PAT。
⚠️ 免责声明
请先读完这一段再用。
- 非官方项目。 本项目与 Qoder(含 Qoder CN)、DeepSeek、DeepSeek Harness 的官方团队没有任何关系,未获其授权、认可或赞助。所有商标归各自所有者。
- 协议是逆向得来的。 插件通过分析 Qoder CN 桌面客户端的本地文件与网络协议实现互通,不是官方公开 API。上游任何一次改版都可能让它失效,且不会有任何预告。
- 可能违反服务条款。 用第三方客户端访问 Qoder 服务,可能不符合 Qoder 的用户协议。账号被限制、被降级或积分被清零的风险由使用者自行承担。
- 仅限本人自用。 请只在你自己拥有合法账号的机器上使用。不要用它做账号共享、转售、批量刷额度或任何形式的牟利。
- 凭据只在你本机流转。 插件读取的登录态不会被上传到任何第三方服务器;所有请求都直接发往 Qoder 官方域名。但代码是公开的,请自行审计后再运行。
lib/wasm_prelude.mjs是 Qoder 的专有产物。 它是从 Qoder CN 客户端里提取的 WASM 签名模块(内含 Qoder 的 RSA 公钥与签名逻辑),版权属于 Qoder,此处仅为互操作性而附带。介意的话请自行从你本机的 Qoder 安装目录提取(见「关于 WASM」),或不要使用本项目。- 不提供任何担保。 软件按「原样」提供。因使用本项目导致的账号损失、数据丢失、服务中断或任何其它损害,作者概不负责。
- 别拿去卖。 本项目以 MIT 协议开源。你可以自由修改、分发,但请保留本免责声明,并且不要把它包装成付费产品或官方工具。
一句话:这是给自己用的工具,风险自担,别商用,别害人。
它解决什么问题
Qoder CN 桌面 App 自带一批模型(Qwen、DeepSeek、Kimi、GLM、MiniMax 等 14 个),订阅后即可调用。但这些模型只能在 Qoder 自己的客户端里用 —— 想在 DSH 里把它们当成一个 provider 来跑 agent,官方没有提供任何入口。
这个插件做的就是这件事:
Qoder CN 桌面 App 已经登录的账号
│
│ ① 读本地 auth.v1.dat(DPAPI + AES-256-GCM 解密)
▼
凭据存储 ──→ 自动续期(drt- → dt-),删掉 App 也能继续跑
│
│ ② WASM 签名 + WAF 编码
▼
Qoder 网关 ──→ 14 个模型,注册成 DSH 的 `qoder` provider
│
│ ③ 反向翻译成 OpenAI Chat Completions
▼
DSH 对话 / agent / 工具调用
核心卖点:零配置。 不用去开发者后台申请 Key,不用复制粘贴 Token,不用处理过期。只要你的 Qoder CN 桌面 App 登录着,插件就能自己找到凭据、自己续期、自己拉模型列表。
功能
| 功能 | 说明 |
|---|---|
| 🔑 凭据自动发现 | 直接解密 Qoder CN 桌面 App 的 auth.v1.dat,双源备份 + 自动续期,删掉 App 也能继续用 |
| 🧠 14 个模型 | 实时拉取官方模型目录,拉不到时用内置兜底 |
| 📏 1M 上下文档位 | 官方目录里 200K/400K/1M 三档,默认给最大档(竞品普遍锁在 180K) |
| 🔢 回合身份 | 重建 business.id / stage,让 Qoder 的积分面板按回合正确聚合 |
| 🔍 联网搜索 | 接成 DSH 的 web 搜索 provider,走 Qoder 自己的搜索端点 |
| 🖼️ 图片上传 | 内联 base64 自动传到 Qoder 图床换 OSS URL,失败自动回退 |
| 🎚️ 推理档位探测 | 实测每个模型真正接受哪些 effort(目录里写的经常是假的) |
| 💰 额度 / 签到 | 剩余积分、订阅套餐、每日奖励自动领取 |
| 🖥️ 图形界面 | DSH 插件详情页六个 tab:账号 / 额度 / 模型 / 档位 / 搜索 / 图片 |
| ⌨️ 命令行 | status doctor models ask refresh logout credits checkin search upload |
环境要求
- Windows(凭据解密依赖 Windows DPAPI)
- Qoder CN 桌面 App 已安装且至少登录过一次(之后删掉也行,见下)
- DeepSeek Harness 已安装
- Node.js
^22.19.0 || >=24.0.0
只支持 Qoder CN(
qoder.com.cn)。国际版 Qoder(qoder.sh)域名与协议不同,未做适配。
安装
方式一:作为 profile bundle(推荐)
git clone https://github.com/YSwo-fei/dsh-qoder-connect.git
然后把插件挂到你的 DSH profile 上。编辑 <DSH_HOME>/profiles/<profile>/package.json:
{
"dependencies": {
"dsh-qoder-connect": "file:/绝对路径/dsh-qoder-connect"
},
"dsh": {
"profile": {
"bundles": [
// …其它 bundle…
"dsh-qoder-connect"
]
}
}
}
然后在 profile 目录里 pnpm install,重启 DSH。
方式二:直接放进插件目录
把仓库整个复制到 <DSH_HOME>/.dsh/plugins/dsh-qoder-connect,再按上面的方式在 profile 里引用。
快速开始
装好重启 DSH 后,打开 设置 → 插件 → DSH Qoder CN Connect,应该能看到账号已登录、模型列表已加载。
命令行自检:
# 四项全绿才算健康
dsh plugin --profile desktop exec dsh-qoder-connect doctor
# 看看有哪些模型
dsh plugin --profile desktop exec dsh-qoder-connect models
# 直接问一句
dsh plugin --profile desktop exec dsh-qoder-connect ask qfmodel "你好"
在 DSH 里选模型时,provider 选 qoder,就能看到 14 个模型。
命令行
dsh-qoder-connect status # 凭据 / 模型 / 额度 总览
dsh-qoder-connect doctor [--json] # 逐项体检:凭据 / 目录 / 对话 / shim
dsh-qoder-connect models [--json] # 模型目录
dsh-qoder-connect ask <模型> <内容> # 直接打一次推理
dsh-qoder-connect refresh # 立即续期一次
dsh-qoder-connect logout # 删除自留凭据副本(不动桌面 App 的登录)
dsh-qoder-connect credits [--json] # 额度 + 套餐 + 活动列表
dsh-qoder-connect checkin # 立即签到
dsh-qoder-connect search "查询内容" # 联网搜索
dsh-qoder-connect upload ./图片.png # 上传图片到图床
凭据是怎么来的
这是本插件不依赖 Qoder CN 桌面 App 的关键。
| 来源 | 位置 | 说明 |
|---|---|---|
| 桌面副本 | %APPDATA%\com.qodercn.app.stable\auth.v1.dat |
Qoder CN 自己维护,DPAPI + AES-256-GCM 加密 |
| 自留副本 | <DSH_HOME>/.qoder-auth.json |
插件自己维护,明文 JSON,权限 0600 |
lib/authstore.js 的 QoderCredentialStore:
双源择一 —— 两份都在时,
uid不一致以桌面为准(说明用户重新登录过),否则取到期更晚的一份。启动即镜像 —— 只要读到桌面副本就顺手写一份自留副本(不必等到快过期)。
自动续期 —— 距到期不足 5 分钟时自己续:
POST https://openapi.qoder.com.cn/api/v1/deviceToken/refresh {"refresh_token":"drt-…"} → {"device_token":"dt-…","refresh_token":"drt-…","expires_at":"…"}续期成功后写回自留副本(临时文件 + 原子改名)。
resolve()是 single-flight 的,并发请求只发一次。续期失败不立刻死 —— 若还没真过期(留 30 秒余量),先用旧凭据继续跑,只记一条 warn。
不会把桌面 App 踢下线。 服务端每次续期都轮换
refresh_token,但旧值不会立刻失效(实测用已被轮换掉的旧drt-再打一次仍返回 200)。插件和 App 各自持有可用的drt-,互不影响。
所以删掉 Qoder CN 桌面 App 之后,插件照常工作。 唯一失效的情形是自留副本的 refresh_token_expires_at 也过了(约一年),那时需要重新登录一次桌面 App。
额度 / 订阅 / 自动签到
lib/account.js 走 Qoder 的 openapi:
| 方法 | 路径 | 内容 |
|---|---|---|
GET |
/sash/api/v2/me/usage |
额度用量(加购额度 + 套餐额度) |
GET |
/api/v2/user/plan |
订阅套餐与功能开关 |
GET |
/sash/api/v1/me/campaigns |
活动列表(含签到状态) |
POST |
/sash/api/v1/me/campaigns/<id>/claim |
领取奖励(幂等) |
GET |
/sash/api/v1/ai-conversations/credits-summary |
历史累计用量 |
自动签到
Qoder 的每日奖励是「活动」形式:每天 10:00(UTC+8)刷新,每次 100 Credits,领取后 30 天有效。
- 只对
actionType === "CLAIM_BENEFIT"且claimStatus === "CLAIMABLE"的活动发POST /claim; - 服务端幂等 —— 重复领取返回
replayed: true,不会重复到账,所以重试安全; - 结果落盘
<DSH_HOME>/.qoder-account.json; - 只对「上次成功」设 1 小时冷却 —— 上次失败要立刻重试,别让一次网络抖动赔上一小时。
默认 30 分钟轮询一次,启动时立即跑一次。关掉:autoCheckIn: false。
配置项
在 DSH 的插件配置里可以改(也可以写在 profile 的 cordis.patch.yml):
| 键 | 默认 | 说明 |
|---|---|---|
uid / token |
自动 | 手动指定凭据(一般不用填) |
appDataDir |
自动 | Qoder CN 的 AppData 目录 |
refreshIntervalMs |
— | 凭据续期轮询间隔 |
accountIntervalMs |
30 分钟 | 额度/签到轮询间隔 |
autoCheckIn |
true |
自动领取每日奖励 |
hideModels |
[] |
不想在 DSH 里出现的模型 |
contextTier |
{} |
逐模型指定上下文档位,如 {"qfmodel": 1000000} |
webSearch |
true |
把 Qoder 搜索注册成 DSH 的 web provider |
imageUpload |
true |
内联图片自动传图床 |
技术细节
推理端点与签名POST https://gateway.qoder.com.cn/algo/api/v2/service/pro/sse/agent_chat_generation
?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1
请求体是加密 + WAF 编码后的 SSE 请求。签名由 WASM 的 prepareInferRequest 生成:
- RSA-PKCS1 加密随机 AES Key
- AES-128-CBC 加密用户信息
Authorization: COSY.<468字符base64>.<32字符md5>- md5 签名串 =
payloadB64 + "\n" + cosyKey + "\n" + ts + "\n" + body + "\n" + sigPath
Encode=1 的端点必须用 WAF 编码(lib/cosy.js 的 qoderEncodeBody()):base64 → 按 floor(n/3) 旋转 → 自定义字母表替换。已实测与 WASM 输出逐字节一致。
POST https://gateway.qoder.com.cn/algo/api/v1/webSearch/oneSearch?Encode=1
body 两层套:内层 {query, timeRange:"NoLimit", contents:{…}} 的 JSON 字符串,外层再包成 {payload: <内层>, encodeVersion:"1"},整体过 WAF 编码。签名签的是编码后的串。
踩过的坑:
- 带
Encode=1却发明文 body → HTTP 500;不带Encode→ HTTP 400。 contents全开时响应多出mainText/markdownText/summary,但耗时从 ~0.45s 涨到 8.5s → 默认全关。- HTTP 200 里也可能是业务错误:query 超长返回
{errorCode:400,…}。必须查errorCode。上限 1000 字符。
为什么要改 web 行的 searchProvider: 基础层写死 deepseek-official,而缝先解析「已配置的 id」,配置了却不可用会直接抛 WEB_PROVIDER_CONFIGURED_UNAVAILABLE,不回退。DeepSeek 那个 provider 的 available() 判据里 resolveApiKey/resolveAccountToken 恒为函数,所以没配 DEEPSEEK_API_KEY 它也报 available,缝永远不会选到 Qoder。本插件的 bundle patch 覆盖了 web 行,profile 层优先级更高,想换回去只改 profile 即可。
PUT https://gateway.qoder.com.cn/algo/api/v2/image/upload?request_id=<32位无横线uuid>
Content-Type: multipart/form-data; boundary=----qodercli-<32位无横线uuid>
- CN 的
center与推理网关同域(gateway.qoder.com.cn)。 - 相对路径用官方的
/api/v2/image/upload(不带/algo,WASM 自己补)。 - 成功响应里 URL 在
result.url(不是顶层url)。
三个坑:
- 签名签的是 multipart body 字节长度的十进制字符串,不是 body 本身。
r.url/r.headers必须在r.free()之前读,否则 WASM 抛null pointer passed to rust。prepareRequest第一参必须是不含/algo的基址,第二参才是完整 path。
上传是可选优化:失败一律保留原始 base64,绝不打断推理。只处理 png/jpeg/gif/webp,单张上限 10 MiB,按 sha1(图片字节) 缓存(LRU 64 条)。
Qoder 目录里的 thinking_config 并不总是说实话:
- 有的模型声明了
[low,medium,high]却没有 disabled 分支,传low直接 400; - 有的模型快档只有 high(传
off/low/medium一律provider_error); - 有的模型声明了档位却根本不校验(传什么都接受)。
lib/probe.js 用「baseline → 随机垃圾档位哨兵 → 逐档验证」三步实测,最多 8 次请求。lib/catalog.js 据此算出一个保证可用的快档:
const CANNOT_DISABLE_FAST = {
auto: "low", q37fmodel: "low",
gmodel: "high", gfmodel: "high",
kmodel: "high", kmodel_latest: "high",
};
绝不把字段留空落到上游默认档 —— 那是最慢的一档(gmodel 不传要 16s)。
Qoder 的积分面板按 business.id 聚合。DSH 不把 session/turn id 交给 LLM 适配器(pi-ai 的 COMPLETIONS_COMPAT_GATE 里 sendSessionAffinityHeaders: "withhold"),所以回合身份只能从消息历史重建:
sessionKey = sha1(首条 user 消息).slice(0,32)runKey = sha1(sessionKey + "|" + user消息数).slice(0,32)
DSH 每次都重发完整历史 → sessionKey 稳定;工具轮次只追加 assistant/tool 消息 → runKey 不变;新 user 消息让计数 +1 → 新 runKey。stage 按 init → start → processing → complete 推进。
关于 WASM
lib/wasm_prelude.mjs(431 KB)是从 Qoder CN 客户端提取的 WASM 签名模块,内含 Qoder 的 RSA 公钥与签名逻辑,版权属于 Qoder。这里附带它纯粹是为了让插件开箱即用。
它内部已经把 WASM 二进制以 base64 内嵌,所以 lib/qoder_auth_wasm_bg.wasm 那个独立文件其实是冗余的(已实测确认)。
如果你不想使用提取版,可以自己从本机安装目录取一份:
<Qoder CN 安装目录>\resources\app.asar.unpacked\node_modules\@qoder-ai\qoder-cn-agent-sdk\dist\_worker\qoder-worker-runtime.obf.mjs
用其中文件头的解码器 _$d(s, k="Tyi1XHqJomzz") 解出内嵌的 WASM base64,替换 lib/wasm_prelude.mjs 即可。
已知上游限制
- 1×1 与 8×8 的极小 PNG 会被上游拒(
provider_error),16×16 起正常。与 base64 / OSS URL 形态无关。 - 图片只认
{type:"image_url", image_url:{url}}形状;扁平{type:"image_url", url}和 Anthropic 式能过网关但被静默丢弃。 - 请求体必须带顶层
business字段(哪怕是{}),否则上游 400Execution failed: null。 - 网关会随机丢请求(30~60s 挂住)。WASM 对照组一样中招,躲不掉。
项目结构
cordis.patch.yml insert: llm-qoder → dsh-qoder-connect
package.json bundle + client 声明、icon、exports
icon.svg 插件图标
locale/{zh,en}.json 卡片标题与描述
data/model-fallback.json 目录拉取失败时的内置兜底
lib/
index.js host 入口:凭据 → 目录 → shim → adapter;挂 status/probe 路由
bridge.js 请求体构造 + SSE 解析 + 加解密(WASM prepareInferRequest)
shim.js 回环 OpenAI 端点,把 DSH 请求转成 Qoder 格式
catalog.js 模型目录(实时 + 兜底),reasoningOf() 的档位映射
authstore.js 凭据存储:桌面副本 + 自留副本 + 自动续期
account.js 账号侧:额度用量 / 订阅套餐 / 活动签到
probe.js 推理档位探测
websearch.js 联网搜索 provider(走 DSH 的 ctx.web 搜索缝)
imageupload.js 图片上传:内联 base64 → Qoder 图床 → OSS URL
turn.js 回合身份(business.id / stage)
routes.js status / probe 两条 HTTP 路由
credentials.js 解密 auth.v1.dat(DPAPI + AES-256-GCM)
cosy.js 签名头派生 + WAF 编码
client.js 浏览器半边(两个 slot,六个 tab)
bin.js CLI
wasm_prelude.mjs WASM qodercontext(内嵌 base64)
常见问题
Q:会不会把我在桌面 App 上的登录挤掉?
A:不会。服务端轮换 refresh_token 但旧值不立刻失效,两边可以各持一份。
Q:删掉 Qoder CN 桌面 App 还能用吗? A:能。自留副本 + 自动续期。只有自留副本的 refresh token 也过期了(约一年)才需要重新登录。
Q:为什么搜不到 / 搜索报 WEB_PROVIDER_CONFIGURED_UNAVAILABLE?
A:检查 cordis.patch.yml 里的 web 行有没有生效,或者 profile 层是不是覆盖成了别的 provider。
Q:模型列表拉不到?
A:会自动退回内置兜底(data/model-fallback.json),功能不受影响,只是列表可能不是最新。
Q:支持国际版 Qoder 吗? A:不支持。域名、端点、协议都不同,未做适配。
Q:为什么只支持 Windows? A:凭据解密依赖 Windows DPAPI。理论上 macOS 的 Keychain 路径可以另写一份,但目前没有。
致谢
- 结构参考了 dsh-workbuddy-connect 的设计思路
- 协议分析基于对 Qoder CN 客户端本地文件的静态阅读,未使用任何破解或绕过手段
No comments yet. Be the first to write one.