dsh-router-qoder 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +275 -2
- package/cordis.patch.yml +10 -0
- package/lib/index.js +1766 -0
- package/package.json +63 -4
- package/src/cn.ts +41 -0
- package/src/contract.ts +169 -0
- package/src/core.test.ts +552 -0
- package/src/core.ts +425 -0
- package/src/cosy.ts +270 -0
- package/src/global.ts +38 -0
- package/src/icon.ts +8 -0
- package/src/index.ts +64 -0
- package/src/messages.ts +337 -0
- package/src/sse.ts +176 -0
- package/src/types.ts +42 -0
- package/src/upstream.ts +992 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CARVIN
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,276 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">dsh-router-qoder</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">dsh-router 的 Qoder 族供应商插件(国内版 + 国际版)</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-10b981?style=flat-square" alt="MIT license"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="支持的 DSH 版本:0.2.0+" src="https://img.shields.io/badge/DSH-0.2.0%2B-4d6bfe" /></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="#快速安装">快速安装</a> ·
|
|
12
|
+
<a href="#两个供应商">两个供应商</a> ·
|
|
13
|
+
<a href="#取-pat">取 PAT</a> ·
|
|
14
|
+
<a href="#能力">能力</a> ·
|
|
15
|
+
<a href="https://github.com/CARVIN94/dsh-router#readme">dsh-router 核心</a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
为 [dsh-router](https://github.com/CARVIN94/dsh-router) 提供 **Qoder**(阿里 AI 编码平台)的
|
|
19
|
+
**两个**供应商(PAT 认证,cosy 签名网关)。单独装它没用——它只是向核心注册供应商,
|
|
20
|
+
面板、账号池、组合回退、模型管理都在核心里。
|
|
21
|
+
|
|
22
|
+
## 两个供应商
|
|
23
|
+
|
|
24
|
+
| 供应商 id | 面板名 | 网关 | openapi | uid 前缀 |
|
|
25
|
+
|---|---|---|---|---|
|
|
26
|
+
| `qoder` | Qoder | `gateway.qoder.com.cn` | `openapi.qoder.com.cn` | `qd` |
|
|
27
|
+
| `qoder-global` | Qoder Global | `api1.qoder.sh` | `openapi.qoder.sh` | `qdg` |
|
|
28
|
+
|
|
29
|
+
两个区域**同协议同契约**(cosy 签名、jobToken 交换、同一套 chat/models 端点),
|
|
30
|
+
所以共用同一份实现(`src/core.ts`),差异只在 `src/cn.ts` / `src/global.ts` 的
|
|
31
|
+
profile 与 `QODER_REGIONS` 的主机表。
|
|
32
|
+
|
|
33
|
+
两个 id **同时是凭证与配置的存储键**(`credentials.sqlite` 的 `supplier` 列、
|
|
34
|
+
`supplier-config.json` 的键)——改 id = 已添加的账号全部消失。
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
src/
|
|
38
|
+
core.ts 共享实现 —— 供应商契约、账号/积分缓存、chatOnce、签到、addApiKey
|
|
39
|
+
cosy.ts cosy 协议原语 —— 自定义 base64、请求签名、RSA/AES 会话、设备指纹
|
|
40
|
+
messages.ts OpenAI 请求 → Qoder 请求体(模板 + 消息/工具转换)
|
|
41
|
+
upstream.ts Qoder HTTP 客户端 —— jobToken 交换、模型目录、SSE 聊天、用量、活动/领取
|
|
42
|
+
sse.ts Qoder SSE → OpenAI SSE(核心只接受 OpenAI 格式的字节)
|
|
43
|
+
cn.ts qoder profile(区域、图标、uid 前缀、弹窗提示文案)
|
|
44
|
+
global.ts qoder-global profile
|
|
45
|
+
icon.ts 内联 SVG data URI(面板图标不依赖另一个服务活着)
|
|
46
|
+
index.ts 插件入口(host 半,一次注册两个供应商)
|
|
47
|
+
contract.ts 供应商契约(自含,与 dsh-router 契约同步,含 addApiKey/apiKeyHint)
|
|
48
|
+
types.ts 通用类型
|
|
49
|
+
cordis.patch.yml bundle patch,把插件插入 DSH cordis bundle stack
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 与 CodeBuddy 族的两处不同
|
|
53
|
+
|
|
54
|
+
1. **认证是 PAT,不是 OAuth。** 粘贴一个 `pt-…` 就完事,没有浏览器登录、没有回调、
|
|
55
|
+
没有 refresh token。所以本插件实现的是契约里的 `addApiKey` 那条路,不是
|
|
56
|
+
`generateLoginUrl`/`completeLogin`。添加时会**真的交换一次 jobToken** 校验——
|
|
57
|
+
打错一个字符的 PAT 不会存进去。
|
|
58
|
+
2. **上游不是 OpenAI 兼容端点。** Qoder 官方没有 OpenAI 兼容接口,chat 直连它的
|
|
59
|
+
`agent_chat_generation`:请求体是私有模板 + cosy 编码加密,响应是「信封套一层
|
|
60
|
+
OpenAI 形状」的 SSE。`sse.ts` 把这层拆开重新包成 OpenAI SSE 交给核心。
|
|
61
|
+
|
|
62
|
+
## 快速安装
|
|
63
|
+
|
|
64
|
+
需要 **DSH `0.2.0` 及以上**。先装核心,再装本插件,然后**重启 `dsh web`**:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
dsh plugin --profile web add dsh-router-core
|
|
68
|
+
dsh plugin --profile web add dsh-router-qoder
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> **本包尚未发布到 npm**(截至 2026-10-28,`dsh-router-qoder` 在 registry 上是 404),
|
|
72
|
+
> 所以现在第二条命令会失败。发布前请用下面的 `link:` 方式装本地仓库;发布后这段提示
|
|
73
|
+
> 就可以删掉。
|
|
74
|
+
|
|
75
|
+
`dsh plugin add` 会在 profile 里 `pnpm add`,并自动把声明了 `dsh.bundle.patch`
|
|
76
|
+
的包加入 `dsh.profile.bundles`(本插件即声明了,即 `cordis.patch.yml`)。
|
|
77
|
+
|
|
78
|
+
> 本地开发版:不用 npm,直接 `dependencies` 加
|
|
79
|
+
> `"dsh-router-qoder": "link:/path/to/dsh-router-qoder"` 指向本地仓库
|
|
80
|
+
> (当前本机 profile 就是这么装的,改完 `pnpm build` 再重启 `dsh web` 即生效)。
|
|
81
|
+
|
|
82
|
+
重启后本插件以 cordis service `router.suppliers` 向 dsh-router 注册两个供应商,
|
|
83
|
+
面板「供应商」出现 **Qoder** 与 **Qoder Global** 两张卡片。
|
|
84
|
+
|
|
85
|
+
## 能力
|
|
86
|
+
|
|
87
|
+
| 能力 | 说明 |
|
|
88
|
+
|---|---|
|
|
89
|
+
| 添加账号(PAT) | 弹窗粘贴 `pt-…`。先交换 jobToken 校验,成功才落盘,昵称取上游下发的账号名;同一 PAT 不会重复建号。粘贴时带 `Bearer ` 前缀也能识别(会被剥掉)。 |
|
|
90
|
+
| 模型列表 | 「获取模型」从 `GET /algo/api/v2/model/list` 实时拉取。**面板显示上游的 `display_name`、请求仍用上游 key**(对照表见[使用](#使用))。上游的 `auto` 不列出来:它是该区的默认路由(请求时才决定解析成哪个模型),不是可描述的模型。没有 PAT 时列表为空——这是正确状态,不是故障。 |
|
|
91
|
+
| 对话 | 直连 cosy 网关:请求体走 Qoder 私有模板(`chat_context`/`model_config`/`business`/`session_id`),body 经自定义 base64 加密后参与签名;响应 SSE 转成 OpenAI SSE(含 `reasoning_content`、`tool_calls`、`usage`、`[DONE]`)。`usage` 里的 `prompt_tokens_details.cached_tokens` 原样透传,宿主的缓存命中率因此是真实读数。 |
|
|
92
|
+
| 图文输入 | 请求里出现 `image_url` / `input_image` 时把 `model_config.is_vl` 置真,否则置假——图片能力按**这次请求**决定,不靠一张静态能力表。 |
|
|
93
|
+
| 思考强度 | 核心给的推理等级按上游约定的**两处同时写**(顶层 `reasoningEffort` + `parameters.reasoning_effort`)——官方客户端如此,只写一个可能被某个区域忽略。`auto`/空/`none`/`off` 一律不下发,用模型自己的默认档。 |
|
|
94
|
+
| 积分显示 | 面板账号积分 = `sash/api/v2/me/usage` 的剩余量(基础包 `userQuota` + 加量包 `addOnQuota` + 企业包 `orgResourcePackage` 的 `remaining` 求和),内存缓存 10 分钟。读不到报 `-1`(不是 0),核心据此保留上次持久化的值。 |
|
|
95
|
+
| 每日签到 | 走**活动**接口(`/sash/api/v1/me/campaigns` → `…/{id}/claim`),路径里没有「签到」二字。`CLAIMED` 与 409 都算「今日已领」(幂等,不是失败);上游今日没下发可领活动时如实说明。 |
|
|
96
|
+
| 多账号池 | 核心按池顺序/策略选号回退,本插件只报单个账号的成败与语义状态。 |
|
|
97
|
+
|
|
98
|
+
## 取 PAT
|
|
99
|
+
|
|
100
|
+
本插件只认 **PAT**(Personal Access Token,`pt-` 开头)。两个区域**各签发一份、互不通用**:
|
|
101
|
+
|
|
102
|
+
| 用哪个卡片 | 控制台 | 入口 |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| Qoder(国内版) | [qoder.cn](https://qoder.cn)(同 `qoder.com.cn`) | 设置 → **个人访问令牌**(旧页面/旧文档写作「API 令牌」)→ 创建令牌 |
|
|
105
|
+
| Qoder Global(国际版) | [qoder.com](https://qoder.com) | Settings → **Personal Access Tokens** → Create token |
|
|
106
|
+
|
|
107
|
+
创建时填名称(只是给你自己看的备注),有效期按需选;**完整令牌只在创建那一刻显示一次**,
|
|
108
|
+
当场复制。漏了就只能删掉重建。
|
|
109
|
+
|
|
110
|
+
官方原文(令牌格式与作用域说明):
|
|
111
|
+
|
|
112
|
+
- 国内版:[docs.qoder.cn · 认证](https://docs.qoder.cn/cloud-agents/api-authentication.md)
|
|
113
|
+
- 国际版:[docs.qoder.com · Authentication](https://docs.qoder.com/cloud-agents/api/conventions/authentication)
|
|
114
|
+
|
|
115
|
+
三件容易踩的事:
|
|
116
|
+
|
|
117
|
+
- **国际版的控制台不是 `qoder.sh`**。那只是网关与开放 API 的主机名(`api1.qoder.sh` /
|
|
118
|
+
`openapi.qoder.sh`),没有网页控制台,域名本身也不解析。发 PAT 的是 `qoder.com`。
|
|
119
|
+
- **区域对不上一定被拒**:国内版的 `pt-…` 贴进国际版卡片(或反之)交换 jobToken 就失败,
|
|
120
|
+
这不是插件坏了、也不是令牌过期。
|
|
121
|
+
- **别把令牌提交进仓库或贴进 issue**:它等价于你的账号权限,谁拿到都能以你的身份调接口。
|
|
122
|
+
|
|
123
|
+
## 使用
|
|
124
|
+
|
|
125
|
+
1. 重启 `dsh web`
|
|
126
|
+
2. 面板 → 供应商 → Qoder / Qoder Global 卡片 → 添加 API key → 粘贴 PAT
|
|
127
|
+
(从哪个入口拿见上一节)
|
|
128
|
+
3. 点「获取模型」从上游拉目录,勾选要用的模型
|
|
129
|
+
4. 请求模型可写 `qmodel_38max`,或带别名前缀 `qoder/qmodel_38max`(插件自动剥前缀)
|
|
130
|
+
|
|
131
|
+
> **模型名:显示用名字,请求用 key。** 上游的 key 是内部标识(`qmodel_38max`、
|
|
132
|
+
> `kmodel_latest`),人读的是它同时下发的 `display_name`(`Qwen3.8-Max`、
|
|
133
|
+
> `Kimi-K3.1-Pro`)。插件把两者都报给核心:**面板、搜索、组合挑选显示可读名,
|
|
134
|
+
> 组合里存的与请求里发的仍是 key**——拿显示名去请求上游只会 404。
|
|
135
|
+
> 面板每行的 key 以小号淡字跟在名字后面,hover 也有完整说明。
|
|
136
|
+
|
|
137
|
+
2026-10-28 国内版实测快照(会随上游变,以面板为准):
|
|
138
|
+
|
|
139
|
+
| 面板显示 | 上游 key(请求/组合用) | 上下文 | 倍率 |
|
|
140
|
+
|---|---|---|---|
|
|
141
|
+
| Qwen3.8-Max | `qmodel_38max` | 180K | 0.5× |
|
|
142
|
+
| Qwen3.8-Flash | `qfmodel` | 180K | 免费 |
|
|
143
|
+
| DeepSeek-V4-Pro | `dmodel` | 96K | 0.5× |
|
|
144
|
+
| DeepSeek-Flash | `dfmodel` | 180K | 免费 |
|
|
145
|
+
| GLM-5.3 | `gmodel` | 180K | 0.8× |
|
|
146
|
+
| GLM-5.3-Flash | `gfmodel` | 1000K | 0.1× |
|
|
147
|
+
| Kimi-K3.1-Pro | `kmodel_latest` | 180K | 1× |
|
|
148
|
+
| Kimi-K2.8-Preview | `kmodel` | 180K | 0.8× |
|
|
149
|
+
| MiniMax-M3.1-Flash | `mmodel` | 180K | 0.1× |
|
|
150
|
+
|
|
151
|
+
命名规律只是巧合(`q`=Qwen、`d`=DeepSeek、`g`=GLM、`k`=Kimi、`m`=MiniMax,`f` 后缀
|
|
152
|
+
=Flash、`_latest`=同族较新),**不要照规律猜 key**——猜错就是 404。
|
|
153
|
+
|
|
154
|
+
## 上游
|
|
155
|
+
|
|
156
|
+
**国内版**(`https://gateway.qoder.com.cn` + `https://openapi.qoder.com.cn`):
|
|
157
|
+
|
|
158
|
+
- **chat**:`POST /algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1`
|
|
159
|
+
- **模型**:`GET /algo/api/v2/model/list?Encode=1` → `chat[]`(`key` / `display_name` / `max_input_tokens` / `price_factor` / `thinking_config.efforts`)
|
|
160
|
+
- **jobToken**:`POST /algo/api/v3/user/jobToken?Encode=1`(旧式签名:`date` + `signature`;body 整体 cosy 编码,`personalToken` 与 `refreshToken` 二选一)
|
|
161
|
+
- **sash 令牌**:`POST {openapi}/api/v1/jobToken/exchange`(`{personal_token}` → `token`)
|
|
162
|
+
- **用量**:`GET /sash/api/v2/me/usage`(sash 面,`Bearer <jt-…>`)
|
|
163
|
+
- **签到**:`GET /sash/api/v1/me/campaigns?forceRefresh=true` → `POST …/{campaignId}/claim`
|
|
164
|
+
- 凭证:核心的 `credentials.sqlite`,`supplier='qoder'`,blob 为 `{nickname, pat, userId?}`
|
|
165
|
+
|
|
166
|
+
**国际版**(`https://api1.qoder.sh` + `https://openapi.qoder.sh`)—— 路径逐字相同,
|
|
167
|
+
差异只有一个:sash 面(积分/签到)需要设备风控头(见「已知限制」)。
|
|
168
|
+
凭证存在同一张表,`supplier='qoder-global'`。
|
|
169
|
+
|
|
170
|
+
## 与核心的分工
|
|
171
|
+
|
|
172
|
+
本插件只管**对单个账号调通上游**:PAT 校验、cosy 会话、chat、模型目录、积分、签到。
|
|
173
|
+
|
|
174
|
+
**策略全在核心**(`AccountPool`):选号、冷却、禁用、连续错误累计、遍历回退、
|
|
175
|
+
响应写入。所以:
|
|
176
|
+
|
|
177
|
+
- `chatOnce(uid, lv, req)` 一次只服务一个账号,**不遍历账号、不维护冷却表、不写响应**
|
|
178
|
+
- 失败时返回语义状态(`rate_limit` / `quota` / `session_dead` / `unavailable` /
|
|
179
|
+
`transport` / `unknown` / `no_such_model` / `bad_request`),由核心决定冷却多久、
|
|
180
|
+
是否禁用、要不要换号
|
|
181
|
+
- `status()` 只报「现在状态」(凭证 + 积分),冷却/禁用由核心叠加后给面板
|
|
182
|
+
- 积分只报**值**,不落盘(持久化归核心 `supplier-config.json`):拿不到时报 `-1`
|
|
183
|
+
(不是 0),核心保留上次持久化的值
|
|
184
|
+
- 非流式请求也走同一条路:本插件**只产出 OpenAI SSE**,客户端要 JSON 时由核心
|
|
185
|
+
聚合成整包(协议错配由核心吸收,插件不做第二套出口)
|
|
186
|
+
|
|
187
|
+
完整契约见 [dsh-router 的 `docs/suppliers.md`](https://github.com/CARVIN94/dsh-router/blob/main/docs/suppliers.md)。
|
|
188
|
+
|
|
189
|
+
## 错误分类(一个重要细节)
|
|
190
|
+
|
|
191
|
+
**业务码优先于 HTTP 状态**。Qoder 对「当日用量用尽」回的是
|
|
192
|
+
`HTTP 403 {"code":"110","Billing daily count exceeded"}` —— 只看状态会判成
|
|
193
|
+
「凭据被拒」,于是「今天用满、明天自愈」的号会被要求换 PAT。所以:
|
|
194
|
+
|
|
195
|
+
| 上游信号 | 归类 | 后果 |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `code 110/113/115/116/117/118/119/122/402/6004`(额度族) | `quota` | 不判账号死,等周期重置或补额度 |
|
|
198
|
+
| `code 105` / `HTTP 401` / `HTTP 403`(无业务码) | `session_dead` | 凭据失效,需要换 PAT |
|
|
199
|
+
| `HTTP 429` | `rate_limit` | 短冷换号 |
|
|
200
|
+
| `code 406` / 内容审核标记 | `bad_request` | 请求级(同一请求对每个号都一样),不惩罚账号 |
|
|
201
|
+
| `code 416/430/80411/80412` / `HTTP 400` | `bad_request` | 请求形态问题(范围/能力/过长/媒体过多) |
|
|
202
|
+
| `code 500/10408/10500/10605` | `rate_limit` | 瞬时(失败/超时/模型内部错/排队),换号确实管用 |
|
|
203
|
+
|
|
204
|
+
失败帧会层层套(外层 `403` + 内层 `10605`),所以取码是**沿 `message` 下钻到最深
|
|
205
|
+
一层认识的码**,只剥一层会把「排队一分钟」判成「凭据被拒」。认不出的码继续往里找,
|
|
206
|
+
最终回落 HTTP 状态分类。
|
|
207
|
+
|
|
208
|
+
流已经写出字节之后上游才报错(HTTP 200 信封里带业务码)时,插件发一条 OpenAI 形状
|
|
209
|
+
的 `error` 帧 + `[DONE]`,并通过 `onLateFailure` 把「这个号额度用尽」报给核心——
|
|
210
|
+
后续请求才会避开它。
|
|
211
|
+
|
|
212
|
+
## 已知限制
|
|
213
|
+
|
|
214
|
+
- **国际版的积分与签到需要本机 Qoder 客户端的设备风控组件**(`Cosy-Machine*` 三件套,
|
|
215
|
+
由 UMID 组件生成)。本插件**不伪造**它:拿不到时如实报「该区域需要设备组件」,
|
|
216
|
+
而不是显示「今天没有活动」。国际版的**聊天与模型列表不受影响**(走 cosy 面)。
|
|
217
|
+
- **国内版已用真实 PAT 端到端跑通(2026-10-28 实测)**:模型目录 9 条全部带可读名、
|
|
218
|
+
对话出字(含 `reasoning_content` 与 `usage`)、积分读到 400(300 基础 + 100 加量)、
|
|
219
|
+
活动列表读到当日已领。**国际版尚未用真实 PAT 联调**——chat 与模型列表与国内版同协议,
|
|
220
|
+
但设备风控那条只能在你本机试。
|
|
221
|
+
- 协议来自社区实测记录(见「致谢」)。单测覆盖编码/签名/请求构造/SSE 转换/错误分类/
|
|
222
|
+
签到状态机;第一次失败建议看 `dsh web` 日志里 `qoder` 前缀那几行——它会带上游原文。
|
|
223
|
+
|
|
224
|
+
## 架构
|
|
225
|
+
|
|
226
|
+
通过 cordis service `router.suppliers` 的**共享聚合表**向 dsh-router 注册两个供应商工厂:
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
dsh web (cordis)
|
|
230
|
+
├─ dsh-router-core 先 provide 空的 router.suppliers 表
|
|
231
|
+
└─ dsh-router-qoder ctx.inject(['router.suppliers']) 等表出现
|
|
232
|
+
├─ 往表里追加 qoder / qoder-global 两个工厂(已存在则跳过,幂等)
|
|
233
|
+
└─ ctx.emit('internal/service') 让核心重扫 → 面板出现两张卡片
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
cordis 每个 service name 只允许一个插件 `provide`,所以这里**不能**各自 provide,
|
|
237
|
+
只能等核心先提供表再往里追加;`inject` + 广播的写法与加载顺序无关,先装谁都能工作。
|
|
238
|
+
|
|
239
|
+
## 开发
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pnpm install
|
|
243
|
+
pnpm build # lib/index.js
|
|
244
|
+
pnpm typecheck
|
|
245
|
+
pnpm test # 行为回归闸门(node --test,Node 原生 TS 类型剥离)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`src/core.test.ts` 锁的是**协议事实**,不是实现细节——那些字节每一个都直接决定网关
|
|
249
|
+
认不认请求,改错不报编译错、只会得到 401 `Signature invalid`:
|
|
250
|
+
|
|
251
|
+
1. cosy 编码/解码可逆、字母表与签名格式逐字固定;
|
|
252
|
+
2. 设备指纹对同一凭据稳定、换凭据就变;
|
|
253
|
+
3. 添加账号先验一次 jobToken(打错的 PAT 不落盘),重复 PAT 不重复建号;
|
|
254
|
+
4. chat 请求是**cosy 编码后的密文**、带 `Bearer COSY.*` 与 cosy 头;
|
|
255
|
+
5. 上游 SSE 信封被转成 OpenAI SSE(含 role/content/usage/`[DONE]`);
|
|
256
|
+
6. 错误分类:403+code110 是额度耗尽(不是凭据被拒),401 才是凭证失效;
|
|
257
|
+
7. 积分读 sash(jt- 令牌)、模型目录过滤上游的 `auto` 且带上 `display_name`;
|
|
258
|
+
8. 签到走活动接口,`CLAIMED` 与 409 幂等算成功。
|
|
259
|
+
|
|
260
|
+
## 致谢
|
|
261
|
+
|
|
262
|
+
cosy 协议的实现来自以下 MIT 项目的实测记录(本仓库的 `src/cosy.ts` /
|
|
263
|
+
`src/messages.ts` 移植自第一项,并按本项目风格重写、加注):
|
|
264
|
+
|
|
265
|
+
- [dingminhua/dsh-connect-qoder](https://github.com/dingminhua/dsh-connect-qoder)(MIT)
|
|
266
|
+
- fengyinxia/qoder2api(MIT)—— PAT→jobToken、cosy 签名、自定义 base64、RSA/AES 会话
|
|
267
|
+
- x_Zhengyuuuui/qoder2api(Go,MIT)—— 同一协议 + 指纹派生 + 双区域端点
|
|
268
|
+
- @qoder-ai/qoder-agent-sdk 的 `.d.ts` —— 模型/用量字段的官方命名
|
|
269
|
+
|
|
270
|
+
## 许可证
|
|
271
|
+
|
|
272
|
+
[MIT](LICENSE)
|
|
273
|
+
|
|
274
|
+
## 免责声明
|
|
275
|
+
|
|
276
|
+
本项目仅用于学习与技术研究,请勿用于商业用途。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# dsh-router-qoder bundle patch
|
|
2
|
+
#
|
|
3
|
+
# Mounts dsh-router-qoder into the DSH cordis bundle stack. This plugin
|
|
4
|
+
# registers the qoder supplier FAMILY (qoder + qoder-global) into the
|
|
5
|
+
# `router.suppliers` cordis service (host half) so dsh-router can load them.
|
|
6
|
+
# dsh-router defers via ctx.inject until the service is available, so order vs
|
|
7
|
+
# dsh-router doesn't matter.
|
|
8
|
+
- insert:
|
|
9
|
+
- id: dsh-router-qoder
|
|
10
|
+
name: 'dsh-router-qoder'
|