dsh-connect-qoder
Verified@eghrhegpe/dsh-connect-qoder · v0.4.2 · MIT · Web UI
将本机已登录的 Qoder(国内版 Qoder CN / 国际版 Qoder)模型接入 DeepSeek Harness —— bring locally signed-in Qoder models into DeepSeek Harness with zero configuration.
Install
dsh plugin add @eghrhegpe/dsh-connect-qoder Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
DSH Connect Qoder
把本机已登录的 Qoder 模型接入 DeepSeek Harness, 零配置即可在 DSH 的模型选择器里使用你的 Qoder 账号额度。
国内版 Qoder CN 与国际版 Qoder 是两个并行的 provider(qoder-cn / qoder),
装哪个就出现哪一组模型,两个都装就两组并存,各自使用自己的账号与额度。
工作原理
DSH PiAiAdapter(每个区域一套)
-> 安全 loopback shim(随机端口 + 进程内随机 secret)
-> COSY 签名 + 自定义 base64 编码
-> 国内版 https://gateway.qoder.com.cn/
-> 国际版 https://api3.qoder.sh/
-> Qoder 双层包装 SSE
-> OpenAI SSE
-> DSH 本地执行工具并回传结果
Qoder 不是 OpenAI 兼容端点,所以需要三样东西:
- COSY 签名头 —— 每个网关请求都带
Cosy-*头与Authorization: Bearer COSY.<payload>.<sig>, 签名是对 base64 负载、RSA 包装的 AES 密钥、时间戳、请求体与签名路径做 MD5。 - 置换 base64 请求体 —— 查询串里的
Encode=1表示 JSON 体要先 base64、再按换过的字母表 逐字符替换、最后按三分之一旋转。 - 双层 SSE —— 每个
data:帧是一个信封对象,它的body字段本身又是 JSON 字符串, 里面才是 OpenAI 风格的 chunk。
凭据来源
插件复用 Qoder 桌面应用自己的登录状态,不启动额外的 OAuth 流程,也不写入应用的文件 (凭据文件以只读方式打开)。
Qoder 把登录信息放在 Chromium OSCrypt 格式的凭据文件里(v10 + nonce + 密文 + tag,AES-256-GCM)。
新版(0.3.x)的凭据直接保存在 <userData>/auth.v1.dat,旧版(0.2.x 及更早)则放在
VS Code 风格的 state.vscdb SQLite 数据库里。两种布局都支持,新版优先尝试。
密文用的密钥保存在应用的 Local State 中,由操作系统 keystore 包裹 —— Windows 上是当前用户
作用域的 DPAPI,因此同一用户下的进程都能解开它。
Node 没有内置 DPAPI 绑定,这一步交给 PowerShell,并通过临时文件交换结果(不使用管道),
这样在禁止管道 stdio 的沙箱里同样可用。
没有桌面应用登录时,可以用官方文档的 PAT 兜底:设置 QODERCN_PAT(国内版)或
QODER_PAT(国际版),插件会用它换取 job token。
平台边界:零配置路径只在 Windows 成立。 解密链路是 PowerShell + DPAPI(Crypt32.dll),
lib/ 里没有任何 macOS / Linux 解包分支——在其它平台上应用目录找得到(应用数据根目录
已按平台解析,见下),但凭据读不出来(loadCredential 返回 undefined,该区域不注册),
只剩上面的 PAT 兜底。
这不是"没有跨平台客户端",而是客户端已跨平台、插件尚未跟上:Qoder 桌面版在
macOS 12+ / Linux (.deb/.rpm) / HarmonyOS 上都有下载(qoder.com.cn/download),
但 lib/ 只实现了 Windows 这一条解密链。缺口在两处,全部登记在
docs/KNOWN_GAPS.md 第 6 条(跨平台凭据链未实现):
- OS keystore 封装(含 key 派生):Windows 走 DPAPI;macOS 需 Keychain(
security), 且 Chromium 在 macOS 上对encrypted_key还要做 PBKDF2-HMAC-SHA1 派生("peanuts" 常量 + 1003 次迭代)——与 DPAPI 直解是两种算法,不是换个命令;Linux 需 libsecret(secret-tool) 或 Chromium 在 Linux 上的peanuts硬编码 key 兜底。 - 跨平台 CI 与实机验证:GitHub Actions 的 macos / ubuntu runner 可以编译并跑单测,但
Keychain 弹窗、签名打包、
secret-tool的 D-Bus session 都得在实机或 runner 上验。
已修的两项(原缺口共四处):应用数据根目录现在按平台解析(Windows %APPDATA、
macOS ~/Library/Application Support、Linux $XDG_CONFIG_HOME/~/.config)——此前直接读
process.env.APPDATA,而该变量在 macOS 上不存在,于是探测得到空串、把"应用装着但读不出密钥"
谎报成"您没登录";MACHINE_OS 也补上了 darwin 档(本机实测过网关对
x86_64_darwin / aarch64_darwin 一律正常应答)。
剩下的是实机工作:service 名、PBKDF2 参数、Linux 的变体都必须实测确认,写出来就是盲代码。 macOS / Linux 用户只能走 PAT 兜底,或自行从本仓库移植。
CI 的 Ubuntu 绿灯说明测试在那边能跑,不等于零配置在 Linux 上存在。
安装
从 npm 安装(推荐)
dsh plugin --profile web add @eghrhegpe/dsh-connect-qoder
从 DSH 市场安装(DSH 桌面用户)
本仓库是 hdhgsysh/dsh-connect-qoder 的 fork。
可在 DSH 的「插件市场」里粘贴这个源:
github:eghrhegpe/dsh-connect-qoder
注意:npm 上不带 scope 的
dsh-connect-qoder由上游发布,装到的是上游那份; 本 fork 自己发的是 scoped 包@eghrhegpe/dsh-connect-qoder(上面那条),二者不是同一个包。
或本地开发模式:
dsh plugin --profile web add <本仓库路径>
安装后需要重启 DSH 进程:bundle 的 patch 在启动时读取。
凭据安全
插件不存储任何凭据:
- 复用本机已登录的 Qoder 桌面应用的凭据文件(新版
auth.v1.dat/ 旧版state.vscdb, 均以只读方式打开,不做任何写入)。 - 进程内随机 bearer token 绑定 loopback 端口;Qoder 真实的 RSA 包装 key 与 session key 不会离开本插件的沙箱。
已知行为
- 始终思考的模型:部分模型(如
GLM-5.3-Flash、Kimi-K3)声明了推理档位但不允许关闭思考, 对它们发送enable_thinking: false会被上游以provider_error 1210拒绝。插件从模型目录 识别这类模型并完全省略该字段,让模型使用自己的默认档位。 - 上游错误可见:上游失败时返回的是普通 200 帧里的错误对象,而不是 chunk。插件把它翻译成 一条可读的错误,而不是让用户看到一个空的助手回合。
- 今日请求次数用完(不是"排队"):Qoder 每天有请求次数上限,用满后上游会带一个以小时计的 重试提示(约两小时)把它伪装成排队。此前插件照单全收,显示成"重试延迟:7350 毫秒", 而这个等待怎么等都没用——次数要到日期切换才重置,不是排队排空的。现在它是一个独立状态: 不重试、不显示误导性的延迟,明确告知约几小时后重置,并指出仍然有效的两条路: 22:00-08:00 的错峰价(折后价,通常不计入或少计日次数)与每日签到。 状态码也从此前的 502("Qoder 坏了")改为 429("今天用完了")。判别点在 lib/errors.js 的 classifyUpstreamError:先认 110、再认队列标记—— 顺序是承重的,因为这个报文本身带着全部队列标记,由 test/daily-limit.test.js 钉住。
- 国际版额度:国际版的试用额度可能已用尽(
isQuotaExceeded),此时目录请求会返回 403Login expired,该区域就不会显示模型;国内版不受影响。 - 账号状态四档与卡片重读:插件入口读不出登录凭据的区域不注册 provider(启动日志会说
原因)。卡片顶部的「当前账号」面板收敛成版本条(对齐 WorkBuddy 的 tab 条):每个
区域一个胶囊——状态点(
ok绿 /expired红 /needs-app琥珀 /signed-out中性)+ 区域名 +「模型」开关;点哪个胶囊,账号详情、用量与模型列表都只看那一版,区域名因此 在卡片上只出现一次(模型行的区域 badge 已随之移除)。身份详情只带 name / email / 到期日,不带任何凭据(判定在lib/account-state.js,纯本地证据、可注入可直测)。 两个按钮各管一件事:「重读登录」 让宿主丢弃凭据缓存、重读应用存储,并把启动时未能上线的区域现在就上线(重建并重新 注册 adapter,失败时回滚到原注册,不影响已在服务的区域)——重新登录后不必再重启 DSH; 面板每次渲染只读已缓存的密钥,不会同步起 PowerShell(那会阻塞整个 DSH 进程,最坏 30 s), 所以解不开的机器也能立刻看到"读不到"和原因;即便真的需要解包(点「重读登录」时)也是 异步等待——本机实测调用 29 ms 就返回、等待期间 DSH 照常响应别的请求,而同步版本会冻结 490 ms;「重读登录」是您主动点的,那一次是真读,不受缓存与失败窗口限制。 密钥缓存与Local State文件绑定,Qoder 重装换掉主密钥后插件会立刻重新解包,不必重启 DSH; 「在线确认」是账号流程里唯一的联网调用(fetchUserInfo),回答「上游现在还认不认 这个登录」,失败按classifyUpstreamError分档(sign-in-expired/ 其他)。 - 每一版的模型可以单独关掉(
enabledRegions,版本条上的「模型」开关,对齐 WorkBuddy 的 per-tab 供应商开关):取消勾选的一版向 DSH 提供零个模型,它的 模型组按「空目录即隐藏」的同一条规则从选择器消失,但登录、用量与模型筛选全部 保留,重新勾选即恢复,无需重启 DSH;卡片模型列表与选择器用同一个regionEnabledFor谓词过滤,两个界面不会打架。 - 刷新失败会说出来,而不是留着旧数据装作没事:
- 上游当前没有模型(账号被收窄 / 模型全部下线)是结果不是故障——插件会照实把目录清空, 并把该版本的模型组从选择器撤下。此前这两种情况走同一条静默路径,于是旧名单永远留着, 点"刷新计费"也不可能有任何变化。
- 抓取失败(网络、凭据、上游 5xx)保留上一份好数据,并把卡片上的时间标成 「上次更新:X(刷新失败)」,而不是给一个全新的时间假装刚更新过。
- Qoder 改了接口格式(HTTP 200 但返回的信封不认识,例如分组改名)是一档独立状态,
卡片直接说"请更新插件(重新登录没有用)",且不显示时间戳——它不会被当成"排队"慢慢重试。
判别边界取自实测(
probe/model-shape.mjs,两端真实账号):空分组用[]表达, 所以"0 个模型"是可信结果、必须落盘,而"分组键缺失/类型变了"才是格式变化。
- 卡片接口只接受同源请求(安全):七条路由里三条是 POST 且能改状态——「签到领积分」
(真实账号变更)、「保存模型勾选与图像模式」、「重读登录」。这些接口此前只按"来源主机名是不是
本机"判断,因此本机任意端口上的任意 HTTP 服务都能让它的网页代为调用(浏览器会挡跨域
读响应,但操作已经发生)。现在要求来源的地址与端口与请求实际拨到的地址一致
(比对请求自己的
Host头,所以不写死端口、DSH 换端口也不用改),且必须是回环地址。 判定在lib/routes.js的loopbackRequest,由test/route-gates.test.js覆盖。 - 依赖 Qoder 客户端接口(非官方开放 API),Qoder 更新后插件可能需要随之调整。
- 设置命名空间由宿主决定,不能自选(0.1.7 起):
describe()用 Loader 条目 id 作为ns(本 bundle 是llm-qoder,不是dsh-connect-qoder),而「设置 → 模型」 页按精确匹配查namespaces.get(entry.settingsNs)。若插件宣告的命名空间与宿主实际 服务的不一致,该 provider 会被判为「未配置」,整行从页面上消失——不报错、不灰显, 而插件本身仍在正常注册和应答,非常难查。因此settingsNs一律经settingsNamespaceOf(ctx)从ctx.fiber.entry.options.id推导,常量只作为宿主不暴露 条目 id 时的回落值(对齐 WorkBuddy 2.1.0 的同款修复)。 - 设置保存走插件的
__save主机端点 + 读回校验(对齐 WorkBuddy 0.1.7 修复):DSH 0.1.7 的 客户端settingsScope.set()在原子写重试耗尽后会静默返回成功而不落盘,卡片改为「主机端点 权威写、本地写仅作镜像、写后读回确认」,保存按钮只会显示真实结果。在宿主未为本插件注册设置 命名空间行的环境(如link:开发安装)下端点会 503,卡片如实显示失败而不是假"已保存"; 正式(registry)安装下该端点可用。 - 每日签到:Qoder 每天 10:00(UTC+8)给每个账号发一轮
CLAIM_BENEFIT活动,一轮只能领一次, 重复领取是幂等的(replayed,不再发放)。卡片在「我的用量」里显示一行签到 + 三态按钮 (立即签到 / 签到中… / 今日已签到)——只有你点才会领,插件不会自动签到。每次点击都先重读一次 活动列表取当轮 id,绝不复用渲染时的旧 id(一天一轮,旧 id 就是上一轮的);领取后用量缓存作废 重读,因为 Credits 落在同一块面板显示的 Add-on 资源包里。两个过滤器是照真实返回加的:同一账号 下的另一个VIEW_DETAILS活动同样带着claimStatus: "CLAIMED"却没有 benefit,只按状态挑会对 它去领;而列表顶层的claimable在「已领过」和「没有活动」时都是false,不能当判据。 按钮文案落到addOnQuota旁边,是因为国内版免费层的计划额度为 0,签到攒的资源包是它唯一的额度。 - 国际版签到行需要 umid 机器身份:国际版
GET /sash/api/v1/me/campaigns把每日CLAIM_BENEFIT轮次按机器身份下发——请求须携带桌面应用 umid 服务的Cosy-MachineToken/Code/Type头,否则只回常驻的VIEW_DETAILS横幅,签到行按active: false设计隐藏。插件在 OpenAPI 请求上自动附加这组头(Windows 上定位安装 目录的resources\umid\runtime-info.exe读取,失败静默降级为原头组;国内版与 PAT 不受影响;发现与验证记录在 docs/KNOWN_GAPS.md 第 8 条(国际版 campaigns 端点按 umid 机器身份门控每日签到))。
目录
| 文件 | 作用 |
|---|---|
lib/credentials.js |
从 Qoder 应用读取并解密登录凭据(密钥缓存与 Local State 绑定、失败窗口 60 s、只读缓存的变体、不阻塞的异步解包) |
lib/upstream.js |
COSY 签名、请求体编码、目录与对话流 |
lib/shim.js |
面向 pi-ai 的 OpenAI 兼容回环端点 |
lib/adapter.js |
pi-ai provider 与 PiAiAdapter profile(被关的 provider 以零模型组呈现,由 DSH 自行隐藏) |
lib/catalog-entry.js |
目录条目的归一化、模型过滤(含按区域开关)与卡片行投影(无 peer 依赖) |
lib/catalog-store.js |
目录的磁盘缓存与原子落盘(无 peer 依赖) |
lib/catalog-refresh.js |
一次目录刷新的结果如何落地:空目录也是结果(照实清空并推进 fetchedAt),只有失败才保留上一份,且失败按 credential / no-credential / fetch / protocol-shape-changed 分档(无 peer 依赖) |
lib/credential-cache.js |
凭据缓存与「登录失效后重读」规则(无 peer 依赖) |
lib/account-payload.js |
账号面板三条路由共用的那份应答:逐区域状态 + 开关映射,以及「渲染读缓存 / 重读登录读真」这一个开关(从 index.js 抽出以便直测,无 peer 依赖) |
lib/account-state.js |
每区域账号状态四档判定(ok / expired / needs-app / signed-out;纯本地证据、不含凭据,无 peer 依赖);三种读取模式(默认 / cachedOnly 不解包 / force 忽略失败窗口) |
lib/settings-save.js |
设置命名空间的解析(0.1.7 由宿主推导,插件不能自选)、设置写入、按区域合并与落盘读回校验(无 peer 依赖) |
lib/pi-model.js |
pi-ai 模型描述符的构造(纯函数,无 peer 依赖) |
lib/adapter-models.js |
单个区域向 DSH 提供的模型列表:区域开关(只认显式 true)、勾选过滤、最大上下文开关、逐模型图像模式(从 adapter.js 抽出以便直测,无 peer 依赖) |
lib/region-gate.js |
一个区域能否作为 provider 上线:三档拒绝(无登录 / 已过期 / 读不到)各自的判定与日志级别(从 index.js 抽出以便直测,无 peer 依赖) |
lib/lifecycle.js |
插件 fiber 退出时要撤销的东西:路由注册的注销句柄收集与释放(宿主是否随 fiber 回收无法从插件侧确认,故两种语义都正确;无 peer 依赖) |
lib/preferences.js |
四个设置项的读取与 volatile 解包(enabledRegions 区域开关:缺失/非对象一律读作开启,只有显式 false 才关) |
lib/offpeak.js |
错峰窗口与费率算术(无 peer 依赖) |
lib/single-flight.js |
同类异步任务的并发合并:刷新在途时,后来的调用并入同一次请求(目录/用量刷新用,无 peer 依赖) |
lib/claim.js |
每日签到:当轮活动的挑选、可领状态判定与领取结果的归一化(纯函数,无 peer 依赖) |
lib/errors.js |
上游错误帧的判定:105(登录没了)与 10605(在排队)的分诊,队列提示的提取,以及协议形状变化这一档(ProtocolShapeChangedError,retryable: false 所以它不会被当成"排队"慢慢等)(无 peer 依赖) |
lib/time.js |
上游时间戳的单一换算(秒 / 毫秒 / RFC 3339 → epoch 毫秒),曾经的两份副本行为不一致(无 peer 依赖) |
lib/volatile.js |
0.1.7 volatile 活引用 { get() } 的解包,此前散在三处(无 peer 依赖) |
lib/http-utils.js |
回环路由共用的 JSON / OpenAI 形状错误响应(no-store,卡片轮询读不到陈旧数据;无 peer 依赖) |
lib/routes.js |
每条卡片路由共用的两道闸:方法检查(405 带 Allow、HEAD 交给 GET)与同源来源检查(403;比对 Host 头,POST 路由靠它挡住本机其它端口的网页),以及带 64 KiB 上限的 JSON body 读取器(无 peer 依赖) |
lib/index.js |
按区域注册 provider 的插件入口,与模型/用量/保存/账号状态路由(账号路由含「重读登录」的上线与回滚) |
lib/client.js 是注入到宿主设置页的那张卡片的构建产物(react 由宿主提供,产物不打包它)。
它的源码在 src/client/,npm run build 从源码重建它:
| src/client/paths.ts | 卡片用到的五条插件路由 |
| src/client/styles.ts | 卡片样式与 installStyles(dsm-* 一套与 dsh-connect-workbuddy 逐字一致,原因见文件头) |
| src/client/settings-write.ts | 「写入后读回校验」的浏览器半边 |
| src/client/copy.ts | 卡片文案聚合入口(中/英),index.ts 与 card.ts 只从这里取 |
| src/client/copy-row.ts copy-usage.ts copy-account.ts | 按面板拆分的三段文案:模型行与错峰、用量与签到、账号与区域标签条 |
| src/client/card.ts | 卡片的纯函数与五个组件(QoderPluginCard / QoderUsagePanel / QoderAccountPanel / RegionUsage / QuotaBlock) |
| src/client/index.ts | 注册入口(apply / inject / name) |
这些源码不是原始手稿,是还原出来的:2026-09 用 docs/history/restore-client-src.mjs 把当时的
产物按 //#region 标记机械切分而成,模块边界来自产物,card.ts 那一段在产物里没有标记、
是按引用关系推断的。还原后做过一次对拍——把卡片里每个纯函数用同一组输入各调一遍,新旧产物的
返回值与抛错逐条一致(方法记录在 docs/issues/17-client-source-restore.md;现在的探针清单与
调用数以 npm run verify:bundle 的输出为准,不再在文档里写死数字)。
产物是构建输出:改源码重建,不要手改产物。test/client-bundle.test.js 从产物里提取并
执行卡片的纯函数,所以产物一改那里的断言就得跟着看一眼。
测试
npm run verify # 下面三条串起来,全过才算过
npm test # node --test "test/*.test.js"
npm run test:coverage # 同上 + 覆盖率门槛(行 68 / 分支 85 / 函数 66,跌破即失败)
npm run verify:deploy # 比对已部署副本与本仓库,报告漂移
npm run build # 从 src/client 重建 lib/client.js
npm run verify 跑三件事,每件回答一个不同的问题:
| 步骤 | 回答什么 | 全过时的输出 |
|---|---|---|
npm test |
卡片逻辑与宿主半边没被改坏 | 最后一行 # fail 0 |
build --tsdown(不写) |
lib/client.js 确实由 src/client/ 生成 |
MATCH: … byte-for-byte identical |
verify:bundle |
重建产物与 HEAD 的行为一致 | behaviour: IDENTICAL |
中间那条是最要紧的:它不写产物,只比较。它过了,就说明产物不是手抄进来的副本——改
src/ 而产物不变的情况会在这里红。
绿灯不等于门禁有效。 这三道门禁每条都用故意的破坏验过:build 缺关键串时会拒绝写入
(第一次构建摇掉全部模块、只剩 84 行,bundler 仍然退出 0);verify:bundle 把探针下界从
Math.max(0, …) 改成 1,就会在 formatCountdown|undefined|0 上报出 00:00:00 →
00:00:01 并以非 0 退出。怀疑门禁时照这个法子再破一次,比看它绿不绿有用。
npm run build 需要构建器(tsdown),它是 devDependency,跑之前先 npm install。
测试仍然不需要安装任何东西(裸 node --test),两者是 CI 里分开的两个 job:一个证明
测试无依赖,一个证明产物可重建。
对拍覆盖不到渲染:JSX 被 stub 成 null,所以改了 UI 仍然要在浏览器里看一眼(展开卡片 →
切区域 → 改图像档位 → 保存 → 看错峰倒计时)。
.npmrc 里的 legacy-peer-deps=true 是必需的、不是随手加的:本包的 peer 依赖是
@deepseek-ai/*,由宿主在运行时提供,不在公共 registry 上,npm 自动安装 peer 会在装到
devDependencies 之前就失败。
verify:deploy 存在的理由和上面那些测试一样:一台机器上可以同时装着好几个版本的本插件。
link: 安装是指向本仓库的符号链接、永远最新;市场安装是复制,停在安装那一刻,
而且两边 package.json 的 version 一样——任何按版本判断新旧的升级路径都会认为「已是最新」。
该脚本比对 lib/** 的内容哈希、文件清单,以及三个由真实缺陷换来的标记
(错峰 active 门、账号状态模块、账号路由),并把「版本号相同但内容不同」单独标出来。
test/deploy-drift.test.js 用假目录树钉住这套判定。
CI 在 Node 22.19 / 24 × Ubuntu / Windows 上跑(.github/workflows/test.yml)——
Windows 不是冗余:shim 绑定回环监听、目录缓存依赖 rename 覆盖、凭据读取要调
PowerShell,这些在别的平台上行为不同。
测试只用 Node 内置的 node:test,不需要安装任何依赖——也不需要安装 peer 依赖,
这是刻意的:lib/ 中凡是纯逻辑的部分都放在无 peer 依赖的模块里(见上表),
这样它们才能被直接 import 并断言真实的实现,而不是在测试里手抄一份。
几个文件存在的理由,都是因为曾经出过问题:
test/catalog-fields.test.js与test/model-row.test.js—— 错峰机制曾因promotion.active/promotion.timezone在 Host 投影时被丢掉而全程哑火, 而当时的测试是绿的,因为它测的是自己手抄的副本。同一个毛病在客户端又犯过一次: 卡片自己算窗口时漏看promotion.active,把拿不到的折扣价显示给用户, 而模型选择器按before价计费,两个界面自相矛盾。model-row.test.js现在逐状态对拍两个门控;client-bundle.test.js更进一步, 直接从产物里提取卡片的offPeakState并执行——删掉那行门控会让它变红, 而只会让model-row.test.js保持绿色。副本不是防线。test/credential-cache.test.js—— 「重新登录无需重启」这条卖点的完整链路: 网关拒绝 → 谓词判定 → 置失效标志 → 下次请求重读。此前只有两端被测。test/credential-invalidation.test.js—— 上面那条链路上的两个纯谓词。test/account-state.test.js—— 账号状态四档判定(lib/account-state.js): 全注入的存储读器 + 真实临时目录跑目录存在性检查,钉住「判定只信本地证据」 与「状态记录不含任何凭据材料」两条不变量。test/errors-classify.test.js—— 105 与 10605 的优先级决定了「提示用户重新登录」 还是「排队等待」,两者弄反的代价完全不同。test/settings-save.test.js—— DSH 0.1.7 上set()会静默成功而不落盘; 这段代码用「写入→读回→深比较」把假成功变成显式失败,测试里直接模拟 「mutate成功但文档没变」的那个场景。也钉住了两件靠肉眼会漏的事: 命名空间只做全等匹配(llm-qoder-extra不算我们的),字段白名单用Object.hasOwn(constructor不是一个字段)。test/pi-model.test.js——toPiModel抽离成纯函数后的直接覆盖:compat.supportsDeveloperRole: false与「不声明maxTokens」这两处, 失效时每个请求都会 403,或长回复被截成finish: max-tokens。test/catalog-store.test.js—— 目录缓存的原子落盘,用真实临时目录跑, 在 CI 所在的平台上实测rename覆盖行为,而不是在注释里假设。test/shim.test.js—— 回环端点的鉴权与/v1/models过滤;对着真实 HTTP 服务器说话,Host 头用裸 socket 发送(fetch禁止设置该头)。test/oscrypt.test.js—— 凭据解密往返;夹具用真实 AES-256-GCM 构造, key 是固定哈希,失败可复现。test/upstream-protocol.test.js—— 编码与签名。两个盲区是原理上不可测的, 已在文件头写明。test/upstream-messages.test.js—— 消息与工具调用翻译,注释里自称 「最重要的一件事」,此前零覆盖。
尚未覆盖的部分集中登记在 docs/KNOWN_GAPS.md,不在各文件里重复叙述——
重复三处正是「手抄副本」那类问题的文档版。
免责声明
仅供个人学习研究使用,仅驱动使用者自己的 Qoder 账号在本机调用。使用者需遵守 Qoder 的服务条款, 因使用本项目产生的后果由使用者自行承担。本项目与 Qoder、DeepSeek 均无关联。
插件除「每日签到」外的所有请求都是只读的;签到是它唯一会对 Qoder 账号产生变更的操作(领取 Qoder 正在向你自己的账号发放的额度),它不会自动执行,每次都要手动点击。是否使用它、以及它是否符合 Qoder 当前的服务条款,请自行判断。
许可证
MIT