dsh-multi-router 0.5.8 → 0.6.0

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +49 -8
  3. package/cordis.patch.yml +2 -2
  4. package/lib/accounts.js +6 -4
  5. package/lib/api-service.js +11 -0
  6. package/lib/api-service.js.map +7 -0
  7. package/lib/chunk-4V52V5GO.js +105 -0
  8. package/lib/chunk-4V52V5GO.js.map +7 -0
  9. package/lib/{chunk-OFFVP6YN.js → chunk-JUOESKAK.js} +22 -5
  10. package/lib/chunk-JUOESKAK.js.map +7 -0
  11. package/lib/{chunk-P43AWJ4Q.js → chunk-LLDJVBTQ.js} +3 -3
  12. package/lib/{chunk-U2MU2M5C.js → chunk-NSKCFVHP.js} +13 -111
  13. package/lib/chunk-NSKCFVHP.js.map +7 -0
  14. package/lib/{chunk-ILRW3ELD.js → chunk-OCMOMSB4.js} +293 -77
  15. package/lib/chunk-OCMOMSB4.js.map +7 -0
  16. package/lib/{chunk-FTM7RHQV.js → chunk-OK2T2UAZ.js} +4 -4
  17. package/lib/{chunk-J3XO7RTF.js → chunk-ROZINFTV.js} +3 -2
  18. package/lib/chunk-ROZINFTV.js.map +7 -0
  19. package/lib/chunk-SNEZMYNU.js +332 -0
  20. package/lib/chunk-SNEZMYNU.js.map +7 -0
  21. package/lib/{chunk-MCHV2R25.js → chunk-WVNY4LSL.js} +3 -1
  22. package/lib/{chunk-MCHV2R25.js.map → chunk-WVNY4LSL.js.map} +2 -2
  23. package/lib/cli.js +7 -6
  24. package/lib/cli.js.map +1 -1
  25. package/lib/client.js +743 -315
  26. package/lib/client.js.map +2 -2
  27. package/lib/clients.js +6 -5
  28. package/lib/{host-v0.5.8.js → host-v0.6.0.js} +22 -123
  29. package/lib/host.js +22 -123
  30. package/lib/index.js +22 -123
  31. package/lib/index.js.map +4 -4
  32. package/lib/manager.js +11 -7
  33. package/lib/router.js +2 -2
  34. package/lib/shim.js +3 -3
  35. package/package.json +1 -1
  36. package/lib/chunk-ILRW3ELD.js.map +0 -7
  37. package/lib/chunk-J3XO7RTF.js.map +0 -7
  38. package/lib/chunk-OFFVP6YN.js.map +0 -7
  39. package/lib/chunk-U2MU2M5C.js.map +0 -7
  40. /package/lib/{chunk-P43AWJ4Q.js.map → chunk-LLDJVBTQ.js.map} +0 -0
  41. /package/lib/{chunk-FTM7RHQV.js.map → chunk-OK2T2UAZ.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。版本号从 `0.1` 起以功能迭代为主,接口在 `1.0` 前可能变化。
4
4
 
5
+ ## 0.6.0
6
+
7
+ - 新增左侧底部 Token 用量概览,展示今日与累计 Token,点击直达插件详情;默认显示,可在版本号旁关闭并保存到配置。仅读取本机缓存统计,每分钟更新,隐藏窗口暂停,不额外查询供应商。
8
+
9
+ - 新增默认关闭的内置 API 服务,提供 OpenAI 兼容的 `/v1/models` 与 `/v1/chat/completions`,支持 SSE / 普通 JSON、工具调用、推理参数及 usage;复用现有账号池、免费模型复核、额度和故障切换。
10
+ - 新增 API 面板:即时启停、端口、本机 / 网络访问、Base URL、独立服务 Key 生成 / 显示 / 复制 / 重置 / 撤销,以及路由开放范围选择。默认跟随展示路由,动态免费模型随目录更新,直接调用也必须满足开放范围。
11
+ - API 调用纳入 Token 明细,新增来源筛选和今日 API 汇总;不保存对话、工具参数、服务 Key 或调用客户端身份。所有 API 接口需要 Bearer Key,错误响应不泄露供应商凭证、账号 ID 或原始错误日志。
12
+ - 服务启用状态按设备保存;默认不监听端口。端口冲突显示可操作提示,关闭 / 撤销 / 卸载会取消 API 请求并释放端口。限制并发、请求与响应大小,调用中断或异常流不冒充完整 JSON 成功结果。
13
+ - 增加 API 集成回归测试,并纳入 Windows / Linux CI;迁移文件不导出本机 API Key 或自动开放网络监听。
14
+
15
+ ## 0.5.9
16
+
17
+ - 本机账号导入按供应商区域与账号身份去重,同一 Trae 登录同时出现在 CN / SOLO 缓存时只保留较新的凭证;首次安装先比较已有账号与缓存,保留名称、权重、停用及移除选择,兼容旧版移除记录。
18
+ - 统一模型、路由、免费模型、Token 明细、插件更新与配置迁移的紧凑表格布局;重命名、免费模型档位和导入导出收进二级弹窗,完整保留推理等级与操作入口。界面适配 DSH 明暗主题和窄窗口。
19
+ - 面板自动刷新从 15 秒改为 1 分钟,隐藏页面暂停轮询;平台余额后台查询改为 5 分钟缓存,失败同样退避,手动刷新立即查询,积分轮换与临期优先仍按需复核。
20
+ - 重载入口按 YAML 结构校验,兼容 Windows CRLF 换行;额外监听插件目录替换。安装操作脱离继承的 HMR 激活标记后进入官方队列,避免嵌套事务。
21
+ - 新包等待策略单独诊断,不再混同网络、缓存或构建脚本错误;不自动更改安装安全策略。外部安装持有官方配置锁、或配置已回滚而文件仍是新版本时,暂停自动重载,避免加载安装中的文件。
22
+ - 新安装也核验 npm 实际版本;镜像或缓存返回旧版时不再报告更新成功。错误区分文件占用 / 权限、网络下载、加载和兼容问题,并提供不含原始日志、用户名路径与凭证的诊断复制入口。
23
+ - 增加 Windows / Linux CI,使用官方 DSH 加载器验证界面注册与连续更新;不把通用 operation-error 直接认定为已修复,用户机器仍需结合具体安装日志诊断。
24
+
5
25
  ## 0.5.8
6
26
 
7
27
  - 修复全局更新提醒的浮层注册参数错误,避免该组件失败时整个配置面板无法显示。增加使用 DSH 实际界面注册器的回归检查,覆盖配置面板与提醒组件的共同注册、卸载。
package/README.md CHANGED
@@ -6,16 +6,17 @@
6
6
 
7
7
  一个插件内置 WorkBuddy、Trae、Qoder 的认证、模型发现和聊天传输,提供三家的多账号池及集中路由。运行不依赖原来三个 DSH 插件;三家代码随安装包一起打包。基于 MIT 上游,固定版本见 `upstream-lock.json`,许可见 `THIRD_PARTY_NOTICES.md`。
8
8
 
9
- 版本:0.5.8。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
9
+ 版本:0.6.0。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
10
10
 
11
11
  ## 在 DSH 里管理
12
12
 
13
13
  打开 **插件 → dsh-multi-router 的配置页**。默认进入总览,八个页签:
14
14
 
15
- - **总览与用量**:今日/累计请求与 Token、近 7 日趋势、最近请求、三家平台余额与刷新时间。平台每分钟查询,面板每 15 秒刷新,可暂停面板自动刷新。
15
+ - **总览与用量**:今日/累计请求与 Token、近 7 日趋势、最近请求、三家平台余额与刷新时间。平台余额后台每 5 分钟查询,面板每分钟读取本机缓存,隐藏页面暂停轮询,可关闭自动刷新;手动刷新平台用量会立即查询。
16
16
  - **Token 明细**:每次上游调用的时间、渠道、账号、路由、实际模型、输入/输出/总 Token 和结果;支持今日、近 3 日、近 7 日、近 30 日及自定义日期范围(北京时间自然日),并按账号、路由、结果筛选,每页 50 条。缓存输入和推理输出在平台返回时展示,不重复计入总量。默认滚动保留最近 30 × 24 小时,可设置 1–365 天,并随配置迁移。同时展示模型用时、工具调用用时、首 Token 平均(TTFT)和输出速度(TPS),可展开单次性能。DSH 步骤采用会话统计相同口径;TPS 按有效输出 Token / 解码秒数加权计算。性能汇总只包含成功调用,随当前筛选变化;旧记录缺少的分项显示「—」。
17
17
  - **账号与登录**:默认「全部供应商」集中展示所有账号,可按供应商 / 区域筛选、搜索、全选 / 全不选。启动时自动读取本机 WorkBuddy / Trae 已登录账号,每分钟重新检测;Trae 自动识别 CN / Global / SOLO,面板展示检测状态,并提供「重新检测本机登录」。添加账号弹窗也可一键获取;账号 ID 自动生成、显示名自动识别,重复账号会提示。Qoder 使用 PAT,可粘贴或获取本机旧连接器已保存的 PAT。新账号自动获取模型与平台用量。详情弹窗查看身份、认证类型、有效期、刷新能力、套餐与积分包;账号可命名、启停、排序和移出。
18
18
  - **积分签到**:按账号设置每日启动签到或定时签到(北京时间),保留每日执行记录、领取意图与设备限制,避免重复请求。
19
+ - **API 服务**:默认关闭,按需提供 OpenAI 兼容的模型列表和聊天接口,支持流式 / 普通 JSON、工具调用与推理参数;复用现有账号池及路由,Key 独立管理,API 用量可在 Token 明细按来源筛选。
19
20
  - **插件更新**:仅检查用户安装的 GitHub / npm 插件,包含停用插件;每 24 小时自动检查,在 DSH 任意页面弹窗提醒新版本。同一更新点击关闭或「稍后处理」后不重复提醒,更高版本 / 新提交再次提醒。可在弹窗查看并手动更新;不自动安装。检查失败不冒充发现新版本,内置组件不检查、不展示。
20
21
  - **模型管理**:原始模型默认隐藏,只展示统一路由;提供所有渠道「全部展示」「全部隐藏(仅路由)」,作用于现有和以后发现的模型。可搜索、获取真实模型目录,修改显示名与单模型展示开关,每家独立保存。
21
22
  - **路由规则**:Free 免费路由排在首位,显示各账号当前免费模型与目录检查时间,支持立即检查。列表显示路由名称、切换策略和目标;新建/编辑打开二级弹窗。「添加模型预配置」可逐个添加或补齐全部三家预配置,不覆盖已有同 ID 路由。模型可从下拉列表选择,候选 ID 和上下文长度放在高级选项中。
@@ -32,7 +33,7 @@
32
33
 
33
34
  ### 界面预览
34
35
 
35
- 以下为 **v0.5.5–v0.5.8 实际面板 + 合成演示数据**。账号名称已替换,不包含真实手机号、邮箱、身份 ID、设备标识、登录凭证或会话内容;余额、用量和性能均为演示数值。
36
+ 以下主要页面已更新为 **v0.5.9 实际面板 + 合成演示数据**。账号名称已替换,不包含真实手机号、邮箱、身份 ID、设备标识、登录凭证或会话内容;余额、用量和性能均为演示数值。
36
37
 
37
38
  | 总览与用量 | Token 明细与调用性能 |
38
39
  | --- | --- |
@@ -49,6 +50,12 @@
49
50
  | 启动 / 定时计划、按账号开启与状态去重 | GitHub / npm 用户插件、手动更新、同版本提醒去重 |
50
51
  | ![每日积分签到](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/checkins.jpg) | ![全局插件更新提醒](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/update-reminder.jpg) |
51
52
 
53
+ | 模型管理 | 安装失败诊断 |
54
+ | --- | --- |
55
+ | ![模型管理](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/models-management.jpg) | ![脱敏安装诊断](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/update-diagnostic.jpg) |
56
+
57
+
58
+
52
59
  ## 内置渠道与默认路由
53
60
 
54
61
  内置 provider:`workbuddy`、`workbuddy-ai`、`trae`、`trae-global`、`qoder`、`qoder-global`。每个渠道直接使用本插件的账号池及传输,4xx 自动切下一账号。原有三个连接器需要关闭,避免同名 provider 冲突;仅关闭,不要求卸载,也不会修改其凭证。
@@ -100,7 +107,7 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
100
107
 
101
108
  ## 首次使用与账号自动获取
102
109
 
103
- 第一次启用插件时会执行 WorkBuddy / WorkBuddy AI 与 Trae CN / Global / SOLO 的本机登录检测,将找到的有效登录保存为独立账号副本。首页会展示「本机账号自动获取」结果,区分已保存、未登录和无法读取,并提供「一键获取并保存」及手动添加入口;无需先逐个点击添加账号。之后每分钟检测新增登录,重复检测不重复添加,已停用和已删除的账号保持用户选择。Qoder 仍需添加 PAT。
110
+ 第一次启用插件时会执行 WorkBuddy / WorkBuddy AI 与 Trae CN / Global / SOLO 的本机登录检测,将找到的有效登录保存为独立账号副本。首页会展示「本机账号自动获取」结果,区分已保存、未登录和无法读取,并提供「一键获取并保存」及手动添加入口;无需先逐个点击添加账号。首次导入先比较已有账号、凭证缓存和移除记录;同一 Trae 账号的 CN / SOLO 客户端缓存合并去重,不按客户端版本重复建号。之后每分钟检测新增登录,重复检测不重复添加,已停用和已删除的账号保持用户选择。Qoder 仍需添加 PAT。
104
111
 
105
112
  ## 面板布局
106
113
 
@@ -143,6 +150,30 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
143
150
  - 结果显示实际安装是否成功及是否需要重启。npm 镜像尚未同步、网络超时、版本不兼容会明确提示。GitHub 下载不会由 npm 镜像代替。
144
151
  - 本地文件 / 开发链接标记为原方式更新。不同 DSH 配置之间相互独立,只操作当前配置。
145
152
 
153
+ ## 侧栏用量概览
154
+
155
+ 左侧底部默认显示今日与累计 Token,点击打开本插件详情。可在面板版本号旁取消「侧栏用量」,设置会持久保存。今日按北京时间统计;每分钟仅读取本机统计缓存,隐藏窗口暂停,不增加平台用量请求。未返回 usage 的调用不估算 Token,悬停可查看说明。
156
+
157
+ ## API 服务(2API)
158
+
159
+ 在插件面板打开 **API 服务** → **生成 API Key** → **启用 API 服务**。默认 Base URL 为 `http://127.0.0.1:8787/v1`,在其他工具中选择 OpenAI 兼容接入,填入地址与服务 Key。服务随 DSH 运行,启用状态会保存;关闭服务、停用插件或退出 DSH 后停止监听。更改端口 / 访问范围、关闭或撤销 Key 会中断正在处理的 API 请求。
160
+
161
+ - `GET /v1/models`:返回已开放路由。默认跟随展示设置(包含动态免费模型),也可单独勾选;不开放原始供应商模型或混合 Free 别名。聊天入口也检查开放范围,不能通过直接填写隐藏 ID 绕过。
162
+ - `POST /v1/chat/completions`:支持 `stream: true` 的 SSE 与省略 / 设置 `stream: false` 的普通 JSON。模型填写 API 页展示的调用 ID;支持 `messages`、工具调用、图片及推理参数的范围取决于所选模型和上游渠道,当前仅支持 `n: 1`。
163
+ - 推理等级沿用路由默认值,可用 `reasoning_effort` 覆盖。上下文由调用客户端提交;外部工具负责执行工具调用并回传结果,不自动读取 DSH 会话。此版本不提供 Responses、Embeddings 或浏览器跨站 CORS 接口。协议参考 [OpenAI Chat Completions](https://developers.openai.com/api/reference/resources/chat)。
164
+ - 所有接口均要求 `Authorization: Bearer <服务 Key>`。Key 可显示、复制、重置及撤销;重置后旧 Key 不能再发起请求。常规面板响应只包含掩码,不包含完整 Key;凭证文件使用私有权限保存。API 配置与 Key 按设备单独保存,不随配置迁移文件导出;迁移账号和路由后,在新设备重新启用即可。
165
+ - 默认仅监听本机。按需开启「允许局域网访问」后监听所有 IPv4 网卡,页面列出网卡地址;使用 HTTP,只用于可信网络。需要外网访问时自行配置 HTTPS 和访问限制,插件不会自动修改防火墙或端口映射。
166
+ - 每次 API 上游尝试均纳入现有统计与轮换额度,来源标注「API 服务」,可按今日 / 近 3 日 / 近 7 日或自定义日期筛选。不保存请求文本、工具参数、API Key 或客户端身份;API 请求的工具执行耗时由外部客户端掌握,此处只记录模型用时、TTFT 和 TPS。服务最多处理 8 个并发 API 请求,超出返回 429。
167
+
168
+ ```bash
169
+ curl http://127.0.0.1:8787/v1/chat/completions \
170
+ -H 'Authorization: Bearer YOUR_API_KEY' \
171
+ -H 'Content-Type: application/json' \
172
+ -d '{"model":"deepseek-v4.1-flash","messages":[{"role":"user","content":"你好"}],"stream":true}'
173
+ ```
174
+
175
+ ![API 服务(合成数据,未启用)](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/api-service.jpg)
176
+
146
177
  ## 安装
147
178
 
148
179
  ### 方式一:npm 安装(首选)
@@ -163,7 +194,7 @@ dsh plugin --profile desktop add dsh-multi-router
163
194
  dsh plugin --profile web add dsh-multi-router
164
195
  ```
165
196
 
166
- 包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.8`。
197
+ 包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.9`。
167
198
 
168
199
  已使用 npm 来源的用户更新:
169
200
 
@@ -183,6 +214,16 @@ dsh plugin --profile desktop add dsh-multi-router
183
214
 
184
215
  **首次从 v0.5.7 或更早版本升级:** 旧后台缺少完整自动重载逻辑,在 DSH 插件页关闭再重新启用一次 `dsh-multi-router` 即可加载新入口,无需退出应用。之后升级自动生效。若遇到连接或版本同步异常,面板提供「重新加载插件」与「重新连接」;只有不提供热加载接口的 DSH 才需要升级 DSH 或重启服务。其他插件是否能即时更新仍以 DSH 返回的结果为准。
185
216
 
217
+ ### 安装后仍显示旧版本 / 新包等待期 / Windows 安装失败
218
+
219
+ 在 DSH 原生插件安装框明确输入 `dsh-multi-router@0.5.9`;使用命令行时可指定官方源:
220
+
221
+ ```bash
222
+ dsh plugin --profile desktop add dsh-multi-router@0.5.9 --registry=https://registry.npmjs.org/
223
+ ```
224
+
225
+ 先确认安装成功,再核对插件页的安装版本及面板顶部的运行版本。DSH 内置 pnpm 的新包等待策略可能选择至少 24 小时前的版本;指定刚发布的版本也可能触发 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`。此时换镜像、卸载重装或重启不会解除策略,应等待到期,或由用户明确为所需版本配置例外;插件不会自动关闭等待策略。输入包名却得到旧版,可能涉及安装器缓存、镜像、旧锁文件或安装失败回滚,不能只凭 `operation-error` 判断。插件内更新器会验证实际版本、展示安装声明与检查频道;失败时可查看脱敏诊断。文件占用、网络失败与插件加载错误分别处理,不把失败安装归因于面板缓存。v0.5.6 旧后台首次迁移后可关闭再启用本插件一次;无需先卸载或删除账号配置。
226
+
186
227
  ### 方式二:GitHub 链接安装
187
228
 
188
229
 
@@ -202,7 +243,7 @@ dsh plugin --profile desktop add github:AndroidSix/dsh-multi-router
202
243
  dsh plugin --profile web add github:AndroidSix/dsh-multi-router
203
244
  ```
204
245
 
205
- 默认获取 main 分支;仓库已包含编译好的 `lib/`,安装端无需 clone、构建或运行初始化命令,也没有安装时构建脚本。首次启用自动在当前设备的 DSH 数据目录创建配置及预制路由,并读取本机已登录的 WorkBuddy / Trae 账号,无需先手动添加。Qoder 使用 PAT,或读取旧连接器保存的 PAT。凭证只保存独立副本;再次检测不会重复添加,也不会启用已停用账号。移出的本机账号不会被定时扫描重新加入,可点击「重新检测本机登录」主动恢复。已有配置不会被覆盖;损坏的配置也不会自动重置。安装 / 更新 / 卸载后需**完全退出并重启**对应的 DSH 进程生效。
246
+ 默认获取 main 分支;仓库已包含编译好的 `lib/`,安装端无需 clone、构建或运行初始化命令,也没有安装时构建脚本。首次启用自动在当前设备的 DSH 数据目录创建配置及预制路由,并读取本机已登录的 WorkBuddy / Trae 账号,无需先手动添加。Qoder 使用 PAT,或读取旧连接器保存的 PAT。凭证只保存独立副本;再次检测不会重复添加,也不会启用已停用账号。移出的本机账号不会被定时扫描重新加入,可点击「重新检测本机登录」主动恢复。已有配置不会被覆盖;损坏的配置也不会自动重置。首次安装由 DSH 启用组件;本插件更新支持运行中重载,首次从旧版本迁移按上方说明关闭再启用一次。
206
247
 
207
248
  已安装用户更新(也适用于旧版从本地 `.tgz` 安装的用户):
208
249
 
@@ -210,7 +251,7 @@ dsh plugin --profile web add github:AndroidSix/dsh-multi-router
210
251
  dsh plugin --profile desktop add github:AndroidSix/dsh-multi-router
211
252
  ```
212
253
 
213
- 这会将同名插件的安装源切换为 GitHub 并获取当前版本,账号、路由和统计保存在包目录之外,不会随包替换。旧的 `file:...tgz` 依赖绑定一个固定本地文件,普通更新不会自动改为 GitHub 来源。已经使用 GitHub 来源时,也可以执行 `dsh plugin --profile desktop update dsh-multi-router`。安装后的运行进程仍需完全退出并重启;「刷新面板」只刷新账号和用量,不会更新插件代码。GitHub 提交、GitHub Releases 和 npm 版本是不同发布来源,第三方商店是否提示更新取决于其检测方式。
254
+ 这会将同名插件的安装源切换为 GitHub 并获取当前版本,账号、路由和统计保存在包目录之外,不会随包替换。旧的 `file:...tgz` 依赖绑定一个固定本地文件,普通更新不会自动改为 GitHub 来源。已经使用 GitHub 来源时,也可以执行 `dsh plugin --profile desktop update dsh-multi-router`。本插件更新后等待空闲自动重载;「刷新面板」只读取账号和用量,连接异常时使用「重新加载插件」。GitHub 提交、GitHub Releases 和 npm 版本是不同发布来源,第三方商店是否提示更新取决于其检测方式。
214
255
 
215
256
  Desktop 若提示 CLI 仅安装依赖、未自动启用 bundle:完全退出 Desktop 后,在 desktop profile 的 `package.json` 的 `dsh.profile.bundles` 数组里追加 `"dsh-multi-router"`(保留其他 bundle),再启动并打开配置页。
216
257
 
@@ -224,7 +265,7 @@ cd dsh-multi-router
224
265
  npm ci
225
266
  npm run check
226
267
  npm run pack:release
227
- dsh plugin --profile desktop add ./dsh-multi-router-0.5.5.tgz
268
+ dsh plugin --profile desktop add ./dsh-multi-router-0.5.9.tgz
228
269
  ```
229
270
 
230
271
  ### 配置账号
package/cordis.patch.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  - insert:
2
- - id: llm-multi-router-v2-0.5.8
3
- name: ./lib/host-v0.5.8.js
2
+ - id: llm-multi-router-v2-0.6.0
3
+ name: ./lib/host-v0.6.0.js
package/lib/accounts.js CHANGED
@@ -2,16 +2,18 @@ import {
2
2
  discoverLocalAccounts,
3
3
  importAccount,
4
4
  localAccountKey,
5
+ normalizeLocalAccountKey,
5
6
  readPortableCredential
6
- } from "./chunk-OFFVP6YN.js";
7
- import "./chunk-FTM7RHQV.js";
8
- import "./chunk-S5KMFAQM.js";
9
- import "./chunk-MCHV2R25.js";
7
+ } from "./chunk-JUOESKAK.js";
8
+ import "./chunk-OK2T2UAZ.js";
10
9
  import "./chunk-TEZ2FMEO.js";
10
+ import "./chunk-S5KMFAQM.js";
11
+ import "./chunk-WVNY4LSL.js";
11
12
  export {
12
13
  discoverLocalAccounts,
13
14
  importAccount,
14
15
  localAccountKey,
16
+ normalizeLocalAccountKey,
15
17
  readPortableCredential
16
18
  };
17
19
  //# sourceMappingURL=accounts.js.map
@@ -0,0 +1,11 @@
1
+ import {
2
+ ApiService
3
+ } from "./chunk-SNEZMYNU.js";
4
+ import "./chunk-4V52V5GO.js";
5
+ import "./chunk-DNQPZMPF.js";
6
+ import "./chunk-ROZINFTV.js";
7
+ import "./chunk-WVNY4LSL.js";
8
+ export {
9
+ ApiService
10
+ };
11
+ //# sourceMappingURL=api-service.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,105 @@
1
+ // upstream/trae/src/sse.ts
2
+ var SseDecoder = class {
3
+ buffer = "";
4
+ event;
5
+ id;
6
+ retry;
7
+ data = [];
8
+ push(chunk) {
9
+ this.buffer += chunk;
10
+ const events = [];
11
+ while (true) {
12
+ const match = /\r?\n/.exec(this.buffer);
13
+ if (match === null || match.index === void 0) break;
14
+ const line = this.buffer.slice(0, match.index);
15
+ this.buffer = this.buffer.slice(match.index + match[0].length);
16
+ const emitted = this.consumeLine(line);
17
+ if (emitted !== void 0) events.push(emitted);
18
+ }
19
+ return events;
20
+ }
21
+ finish() {
22
+ const events = [];
23
+ if (this.buffer !== "") {
24
+ const emitted = this.consumeLine(this.buffer);
25
+ this.buffer = "";
26
+ if (emitted !== void 0) events.push(emitted);
27
+ }
28
+ const final = this.dispatch();
29
+ if (final !== void 0) events.push(final);
30
+ return events;
31
+ }
32
+ consumeLine(line) {
33
+ if (line === "") return this.dispatch();
34
+ if (line.startsWith(":")) return void 0;
35
+ const colon = line.indexOf(":");
36
+ const field = colon === -1 ? line : line.slice(0, colon);
37
+ let value = colon === -1 ? "" : line.slice(colon + 1);
38
+ if (value.startsWith(" ")) value = value.slice(1);
39
+ if (field === "event") this.event = value;
40
+ else if (field === "data") this.data.push(value);
41
+ else if (field === "id" && !value.includes("\0")) this.id = value;
42
+ else if (field === "retry" && /^\d+$/.test(value)) this.retry = Number(value);
43
+ return void 0;
44
+ }
45
+ dispatch() {
46
+ if (this.data.length === 0) {
47
+ this.event = void 0;
48
+ this.retry = void 0;
49
+ return void 0;
50
+ }
51
+ const result = {
52
+ ...this.event === void 0 || this.event === "" ? {} : { event: this.event },
53
+ data: this.data.join("\n"),
54
+ ...this.id === void 0 ? {} : { id: this.id },
55
+ ...this.retry === void 0 ? {} : { retry: this.retry }
56
+ };
57
+ this.event = void 0;
58
+ this.retry = void 0;
59
+ this.data = [];
60
+ return result;
61
+ }
62
+ };
63
+ function decodeTraeEvent(event) {
64
+ if (event.data === "[DONE]") return { type: "done", finishReason: "stop" };
65
+ let payload;
66
+ try {
67
+ payload = JSON.parse(event.data);
68
+ } catch {
69
+ return { type: "unknown", ...event.event === void 0 ? {} : { event: event.event }, data: event.data };
70
+ }
71
+ const record = typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : {};
72
+ if (event.event === "request_wait_in_queue") {
73
+ return { type: "queue", ...typeof record["position"] === "number" ? { position: record["position"] } : {} };
74
+ }
75
+ if (event.event === "progress_notice") return { type: "progress", notice: payload };
76
+ if (event.event === "token_usage") {
77
+ return {
78
+ type: "usage",
79
+ ...typeof record["prompt_tokens"] === "number" ? { inputTokens: record["prompt_tokens"] } : {},
80
+ ...typeof record["completion_tokens"] === "number" ? { outputTokens: record["completion_tokens"] } : {},
81
+ ...typeof record["total_tokens"] === "number" ? { totalTokens: record["total_tokens"] } : {},
82
+ ...typeof record["reasoning_tokens"] === "number" ? { reasoningTokens: record["reasoning_tokens"] } : {},
83
+ ...typeof record["cache_read_input_tokens"] === "number" ? { cacheReadTokens: record["cache_read_input_tokens"] } : {},
84
+ ...typeof record["cache_creation_input_tokens"] === "number" ? { cacheWriteTokens: record["cache_creation_input_tokens"] } : {}
85
+ };
86
+ }
87
+ if (event.event === "done" || typeof record["finish_reason"] === "string" && record["response"] === void 0) {
88
+ return { type: "done", finishReason: typeof record["finish_reason"] === "string" ? record["finish_reason"] : "stop" };
89
+ }
90
+ if (event.event === "output" || record["response"] !== void 0 || record["reasoning_content"] !== void 0) {
91
+ return {
92
+ type: "delta",
93
+ text: typeof record["response"] === "string" ? record["response"] : "",
94
+ ...typeof record["reasoning_content"] === "string" ? { reasoning: record["reasoning_content"] } : {},
95
+ ...record["tool_calls"] === void 0 || record["tool_calls"] === null ? {} : { toolCalls: record["tool_calls"] }
96
+ };
97
+ }
98
+ return { type: "unknown", ...event.event === void 0 ? {} : { event: event.event }, data: payload };
99
+ }
100
+
101
+ export {
102
+ SseDecoder,
103
+ decodeTraeEvent
104
+ };
105
+ //# sourceMappingURL=chunk-4V52V5GO.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../upstream/trae/src/sse.ts"],
4
+ "sourcesContent": ["export interface SseEvent {\n event?: string\n data: string\n id?: string\n retry?: number\n}\n\n/** Incremental SSE decoder supporting CRLF, chunk splits and multi-line data. */\nexport class SseDecoder {\n private buffer = ''\n private event: string | undefined\n private id: string | undefined\n private retry: number | undefined\n private data: string[] = []\n\n push(chunk: string): SseEvent[] {\n this.buffer += chunk\n const events: SseEvent[] = []\n while (true) {\n const match = /\\r?\\n/.exec(this.buffer)\n if (match === null || match.index === undefined) break\n const line = this.buffer.slice(0, match.index)\n this.buffer = this.buffer.slice(match.index + match[0].length)\n const emitted = this.consumeLine(line)\n if (emitted !== undefined) events.push(emitted)\n }\n return events\n }\n\n finish(): SseEvent[] {\n const events: SseEvent[] = []\n if (this.buffer !== '') {\n const emitted = this.consumeLine(this.buffer)\n this.buffer = ''\n if (emitted !== undefined) events.push(emitted)\n }\n const final = this.dispatch()\n if (final !== undefined) events.push(final)\n return events\n }\n\n private consumeLine(line: string): SseEvent | undefined {\n if (line === '') return this.dispatch()\n if (line.startsWith(':')) return undefined\n const colon = line.indexOf(':')\n const field = colon === -1 ? line : line.slice(0, colon)\n let value = colon === -1 ? '' : line.slice(colon + 1)\n if (value.startsWith(' ')) value = value.slice(1)\n if (field === 'event') this.event = value\n else if (field === 'data') this.data.push(value)\n else if (field === 'id' && !value.includes('\\0')) this.id = value\n else if (field === 'retry' && /^\\d+$/.test(value)) this.retry = Number(value)\n return undefined\n }\n\n private dispatch(): SseEvent | undefined {\n if (this.data.length === 0) {\n this.event = undefined\n this.retry = undefined\n return undefined\n }\n const result: SseEvent = {\n ...this.event === undefined || this.event === '' ? {} : { event: this.event },\n data: this.data.join('\\n'),\n ...this.id === undefined ? {} : { id: this.id },\n ...this.retry === undefined ? {} : { retry: this.retry },\n }\n this.event = undefined\n this.retry = undefined\n this.data = []\n return result\n }\n}\n\nexport type TraeStreamEvent =\n | { type: 'queue'; position?: number }\n | { type: 'progress'; notice: unknown }\n | { type: 'delta'; text: string; reasoning?: string; toolCalls?: unknown }\n | {\n type: 'usage'\n inputTokens?: number\n outputTokens?: number\n totalTokens?: number\n reasoningTokens?: number\n /**\n * Trae's prompt-cache accounting (`cache_read_input_tokens` /\n * `cache_creation_input_tokens` on the `token_usage` event). Verified\n * 2026-09-22 (docs/ISSUE10_DIAGNOSIS.md): a byte-identical 9224-token\n * prompt reports `prompt_tokens: 9224` while `cache_read_input_tokens`\n * rises to 9216, so these are a *subset* of `prompt_tokens` \u2014 the OpenAI\n * convention, not Anthropic's exclusive one. Forwarding them as\n * `cached_tokens` is therefore arithmetically correct.\n */\n cacheReadTokens?: number\n cacheWriteTokens?: number\n }\n | { type: 'done'; finishReason: string }\n | { type: 'unknown'; event?: string; data: unknown }\n\nexport function decodeTraeEvent(event: SseEvent): TraeStreamEvent {\n if (event.data === '[DONE]') return { type: 'done', finishReason: 'stop' }\n let payload: unknown\n try { payload = JSON.parse(event.data) as unknown } catch { return { type: 'unknown', ...event.event === undefined ? {} : { event: event.event }, data: event.data } }\n const record = typeof payload === 'object' && payload !== null && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n if (event.event === 'request_wait_in_queue') {\n return { type: 'queue', ...typeof record['position'] === 'number' ? { position: record['position'] } : {} }\n }\n if (event.event === 'progress_notice') return { type: 'progress', notice: payload }\n if (event.event === 'token_usage') {\n return {\n type: 'usage',\n ...typeof record['prompt_tokens'] === 'number' ? { inputTokens: record['prompt_tokens'] } : {},\n ...typeof record['completion_tokens'] === 'number' ? { outputTokens: record['completion_tokens'] } : {},\n ...typeof record['total_tokens'] === 'number' ? { totalTokens: record['total_tokens'] } : {},\n ...typeof record['reasoning_tokens'] === 'number' ? { reasoningTokens: record['reasoning_tokens'] } : {},\n ...typeof record['cache_read_input_tokens'] === 'number' ? { cacheReadTokens: record['cache_read_input_tokens'] } : {},\n ...typeof record['cache_creation_input_tokens'] === 'number' ? { cacheWriteTokens: record['cache_creation_input_tokens'] } : {},\n }\n }\n if (event.event === 'done' || typeof record['finish_reason'] === 'string' && record['response'] === undefined) {\n return { type: 'done', finishReason: typeof record['finish_reason'] === 'string' ? record['finish_reason'] : 'stop' }\n }\n if (event.event === 'output' || record['response'] !== undefined || record['reasoning_content'] !== undefined) {\n return {\n type: 'delta',\n text: typeof record['response'] === 'string' ? record['response'] : '',\n ...typeof record['reasoning_content'] === 'string' ? { reasoning: record['reasoning_content'] } : {},\n ...record['tool_calls'] === undefined || record['tool_calls'] === null ? {} : { toolCalls: record['tool_calls'] },\n }\n }\n return { type: 'unknown', ...event.event === undefined ? {} : { event: event.event }, data: payload }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -5,14 +5,14 @@ import {
5
5
  atRestKeyProviderFor,
6
6
  readTraeIdentity,
7
7
  traeStorageCandidates
8
- } from "./chunk-FTM7RHQV.js";
8
+ } from "./chunk-OK2T2UAZ.js";
9
9
  import {
10
10
  regionOfCredential
11
11
  } from "./chunk-S5KMFAQM.js";
12
12
  import {
13
13
  PROVIDERS,
14
14
  writePrivateJson
15
- } from "./chunk-MCHV2R25.js";
15
+ } from "./chunk-WVNY4LSL.js";
16
16
 
17
17
  // src/accounts.mjs
18
18
  import { readFile } from "node:fs/promises";
@@ -24,8 +24,19 @@ var localAccountKey = (provider, document) => JSON.stringify([
24
24
  provider,
25
25
  document.credential?.uid || document.credential?.userId,
26
26
  document.credential?.enterpriseId || "",
27
- document.credential?.edition || ""
27
+ provider.startsWith("trae") ? "" : document.credential?.edition || ""
28
28
  ]);
29
+ function normalizeLocalAccountKey(key) {
30
+ try {
31
+ const parts = JSON.parse(key);
32
+ if (Array.isArray(parts) && parts.length === 4 && ["trae", "trae-global"].includes(parts[0])) {
33
+ parts[3] = "";
34
+ return JSON.stringify(parts);
35
+ }
36
+ } catch {
37
+ }
38
+ return key;
39
+ }
29
40
  async function discoverLocalAccounts(provider) {
30
41
  const accounts = [], status = [];
31
42
  for (const variant of WORKBUDDY_VARIANTS.filter((v) => !provider || v.id === provider)) {
@@ -77,7 +88,12 @@ async function discoverLocalAccounts(provider) {
77
88
  }
78
89
  status.push({ provider: id, state: found ? "found" : unreadable ? "unreadable" : "not-found" });
79
90
  }
80
- return { accounts, status };
91
+ const identities = /* @__PURE__ */ new Map();
92
+ for (const item of accounts) {
93
+ const key = localAccountKey(item.provider, item.document), previous = identities.get(key);
94
+ if (!previous || item.document.credential.expiresAtMs > previous.document.credential.expiresAtMs) identities.set(key, item);
95
+ }
96
+ return { accounts: [...identities.values()], status };
81
97
  }
82
98
  var pick = (value, keys) => Object.fromEntries(keys.filter((k) => Object.hasOwn(value ?? {}, k)).map((k) => [k, value[k]]));
83
99
  async function readPortableCredential(account) {
@@ -157,8 +173,9 @@ async function importAccount({ provider, source, credentialFile, identityFile, e
157
173
 
158
174
  export {
159
175
  localAccountKey,
176
+ normalizeLocalAccountKey,
160
177
  discoverLocalAccounts,
161
178
  readPortableCredential,
162
179
  importAccount
163
180
  };
164
- //# sourceMappingURL=chunk-OFFVP6YN.js.map
181
+ //# sourceMappingURL=chunk-JUOESKAK.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/accounts.mjs"],
4
+ "sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport { WorkBuddyCredentialStore } from '../upstream/workbuddy/src/auth.ts';\nimport { WORKBUDDY_VARIANTS } from '../upstream/workbuddy/src/variants.ts';\nimport { atRestKeyProviderFor } from '../upstream/workbuddy/src/desktop-credential-protection.ts';\nimport { TraeCredentialStore } from '../upstream/trae/src/auth.ts';\nimport { readTraeIdentity } from '../upstream/trae/src/identity.ts';\nimport { PROVIDERS, writePrivateJson } from './config.mjs';\nimport { traeStorageCandidates } from '../upstream/trae/src/paths.ts';\nimport { regionOfCredential } from '../upstream/trae/src/region.ts';\n\nconst localKeyProviders = new Map();\nexport const localAccountKey = (provider, document) => JSON.stringify([provider,\n document.credential?.uid || document.credential?.userId,\n document.credential?.enterpriseId || '', provider.startsWith('trae') ? '' : document.credential?.edition || '']);\nexport function normalizeLocalAccountKey(key) {\n try { const parts = JSON.parse(key); if (Array.isArray(parts) && parts.length === 4 && ['trae', 'trae-global'].includes(parts[0])) { parts[3] = ''; return JSON.stringify(parts); } } catch { /* Preserve unknown legacy records. */ }\n return key;\n}\n\n// Reuse the upstream readers, including WorkBuddy's environment overrides,\n// candidate fallback and encrypted desktop format. Never refresh/write upstream files.\nexport async function discoverLocalAccounts(provider) {\n const accounts = [], status = [];\n for (const variant of WORKBUDDY_VARIANTS.filter(v => !provider || v.id === provider)) {\n try {\n if (!localKeyProviders.has(variant.id)) localKeyProviders.set(variant.id, atRestKeyProviderFor(variant));\n const store = new WorkBuddyCredentialStore({ variant, ownPath: join(tmpdir(), `dsh-router-scan-${randomBytes(16).toString('hex')}.json`),\n keyProvider: localKeyProviders.get(variant.id), refresh: async () => { throw new Error('Discovery cannot refresh'); } });\n const credential = await store.current();\n if (credential) accounts.push({ provider: variant.id, document: { version: 1, credential } });\n status.push({ provider: variant.id, state: credential ? 'found' : 'not-found' });\n } catch (e) { status.push({ provider: variant.id, state: 'unreadable', code: e.code || 'credential_unreadable' }); }\n }\n for (const id of ['trae', 'trae-global'].filter(id => !provider || id === provider)) {\n let found = false, unreadable = false;\n for (const candidate of traeStorageCandidates().filter(c => c.source === 'desktop'\n && (id === 'trae' ? ['cn', 'solo'] : ['sg', 'solo-sg']).includes(c.edition))) {\n try {\n await readFile(candidate.path, 'utf8');\n const store = new TraeCredentialStore({ storagePath: candidate.path, edition: candidate.edition,\n region: id === 'trae' ? 'cn' : 'ai', ownPath: `${candidate.path}.dsh-router-no-owned-credential`,\n legacyOwnPath: `${candidate.path}.dsh-router-no-legacy`, refresh: async () => { throw new Error('Discovery cannot refresh'); } });\n const credential = await store.current();\n if (!credential) { const diagnostic = await store.diagnose(); unreadable ||= diagnostic.failures.some(f => f.reason !== 'missing'); continue; }\n if ((regionOfCredential(credential) === 'ai') !== (id === 'trae-global')) continue;\n const identityDocument = await readTraeIdentity(candidate);\n accounts.push({ provider: id, document: { version: 1, credential }, identityDocument }); found = true;\n } catch (e) { if (e.code !== 'ENOENT') unreadable = true; }\n }\n status.push({ provider: id, state: found ? 'found' : unreadable ? 'unreadable' : 'not-found' });\n }\n const identities = new Map();\n for (const item of accounts) {\n const key = localAccountKey(item.provider, item.document), previous = identities.get(key);\n if (!previous || item.document.credential.expiresAtMs > previous.document.credential.expiresAtMs) identities.set(key, item);\n }\n return { accounts: [...identities.values()], status };\n}\n\nconst pick = (value, keys) => Object.fromEntries(keys.filter(k => Object.hasOwn(value ?? {}, k)).map(k => [k, value[k]]));\n// Share the same portable fields with full encrypted configuration migration.\nexport async function readPortableCredential(account) {\n const raw = JSON.parse(await readFile(account.credentialFile, 'utf8'));\n if (account.provider.startsWith('qoder')) return raw.pat ? { document: pick(raw, ['version', 'pat', 'region']) } : null;\n if (!raw.credential?.accessToken) return null;\n const document = { version: 1, credential: pick(raw.credential, ['accessToken', 'refreshToken', 'expiresAtMs', 'refreshExpiresAtMs', 'uid', 'nickname', 'enterpriseId', 'domain', 'userId', 'accountName', 'edition', 'userRegion', 'host']) };\n const identity = account.provider.startsWith('trae') ? pick(JSON.parse(await readFile(account.identityFile, 'utf8')), ['machineId', 'deviceId', 'edition']) : undefined;\n if (identity && (!identity.machineId || !identity.deviceId || identity.edition !== raw.credential.edition)) throw new Error('Trae identity is incomplete');\n return { document, identity };\n}\n\n// Imports never refresh tokens or write to the provider's own files.\nexport async function importAccount({ provider, source, credentialFile, identityFile, edition, identitySource, document, identityDocument }) {\n if (!PROVIDERS.includes(provider)) throw new Error('Unknown provider');\n const inline = document !== undefined;\n const raw = inline ? JSON.stringify(document) : await readFile(source, 'utf8');\n if (!inline) try { document = JSON.parse(raw); } catch { /* A Qoder PAT can be a plain text file. */ }\n if (document?.format === 'dsh-multi-router-account' && document.provider !== provider) throw new Error('Portable credential provider does not match selected provider');\n const text = value => typeof value === 'string' && value.trim().length > 0 && value.length <= 32768;\n if (provider.startsWith('workbuddy')) {\n let credential;\n if (document?.version === 1 && document.credential) credential = document.credential;\n else if (inline) credential = document;\n else {\n const variant = WORKBUDDY_VARIANTS.find(v => v.id === provider);\n const store = new WorkBuddyCredentialStore({ desktopPath: source, ownPath: `${credentialFile}.import-empty`,\n variant, keyProvider: atRestKeyProviderFor(variant), refresh: async () => { throw new Error('Import cannot refresh'); } });\n credential = await store.current();\n }\n if (!text(credential?.accessToken) || !text(credential.uid) || !Number.isFinite(credential.expiresAtMs) || credential.expiresAtMs <= 0 || typeof credential.domain !== 'string' || /[\\r\\n]/.test(credential.domain)) throw new Error('No valid WorkBuddy credential in source');\n // Use the upstream region resolver rather than guessing regional domains.\n const { regionOf } = await import('../upstream/workbuddy/src/upstream.ts');\n if ((regionOf(credential.domain) === 'global') !== (provider === 'workbuddy-ai')) throw new Error('WorkBuddy credential region does not match provider');\n await writePrivateJson(credentialFile, { version: 1, credential: { ...credential, source: 'dsh' } });\n } else if (provider.startsWith('trae')) {\n let credential;\n if (document?.version === 1 && document.credential) credential = document.credential;\n else if (inline) credential = document;\n else {\n if (!['cn', 'sg', 'solo', 'solo-sg'].includes(edition)) throw new Error('Trae desktop import needs --edition cn|sg|solo|solo-sg');\n const store = new TraeCredentialStore({ storagePath: source, edition, ownPath: `${credentialFile}.import-empty`,\n legacyOwnPath: `${credentialFile}.no-legacy`, region: provider === 'trae' ? 'cn' : 'ai',\n refresh: async () => { throw new Error('Import cannot refresh'); } });\n credential = await store.current();\n }\n if (!text(credential?.accessToken) || !text(credential.userId) || !Number.isFinite(credential.expiresAtMs) || credential.expiresAtMs <= 0 || !['cn', 'sg', 'solo', 'solo-sg'].includes(credential.edition)) throw new Error('No valid Trae credential in source');\n const { regionOfCredential, regionOfHost } = await import('../upstream/trae/src/region.ts');\n if (credential.host && !regionOfHost(credential.host)) throw new Error('Invalid Trae credential host');\n if ((regionOfCredential(credential) === 'ai') !== (provider === 'trae-global')) throw new Error('Trae credential region does not match provider');\n const identity = identityDocument ?? document?.identity ?? (identitySource ? JSON.parse(await readFile(identitySource, 'utf8'))\n : source ? await readTraeIdentity({ edition: credential.edition, path: source, source: 'desktop' }) : undefined);\n if (!text(identity?.machineId) || !text(identity.deviceId) || identity.edition !== credential.edition) throw new Error('Identity must belong to this Trae edition');\n await writePrivateJson(identityFile, identity);\n await writePrivateJson(credentialFile, { version: 1, credential: { ...credential, source: 'dsh' } });\n } else {\n const region = provider === 'qoder' ? 'china' : 'global';\n const pat = typeof document?.pat === 'string' ? document.pat.trim() : raw.trim();\n if (!pat || (document && typeof document.pat !== 'string') || /\\s/.test(pat)) throw new Error('Source must be a Qoder PAT text file or credential JSON');\n if (document?.region && document.region !== region) throw new Error('Qoder PAT region does not match provider');\n await writePrivateJson(credentialFile, { version: 2, pat, region, savedAt: new Date().toISOString() });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["id", "regionOfCredential"]
7
+ }
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-DNQPZMPF.js";
5
5
  import {
6
6
  RouteError
7
- } from "./chunk-J3XO7RTF.js";
7
+ } from "./chunk-ROZINFTV.js";
8
8
  import {
9
9
  REASONING_EFFORTS
10
- } from "./chunk-MCHV2R25.js";
10
+ } from "./chunk-WVNY4LSL.js";
11
11
 
12
12
  // src/shim.mjs
13
13
  import { createServer } from "node:http";
@@ -117,4 +117,4 @@ function createShim(router, { modelPrefix = "" } = {}) {
117
117
  export {
118
118
  createShim
119
119
  };
120
- //# sourceMappingURL=chunk-P43AWJ4Q.js.map
120
+ //# sourceMappingURL=chunk-LLDJVBTQ.js.map
@@ -4,24 +4,28 @@ import {
4
4
  WorkBuddyCredentialStore,
5
5
  identityHeaders,
6
6
  traeAccountId
7
- } from "./chunk-FTM7RHQV.js";
7
+ } from "./chunk-OK2T2UAZ.js";
8
+ import {
9
+ WorkBuddyUpstreamClient,
10
+ modelWithCurrentPromotion,
11
+ prepareChatBody
12
+ } from "./chunk-TEZ2FMEO.js";
8
13
  import {
9
14
  REGION_GATEWAYS,
10
15
  regionOfCredential
11
16
  } from "./chunk-S5KMFAQM.js";
17
+ import {
18
+ SseDecoder,
19
+ decodeTraeEvent
20
+ } from "./chunk-4V52V5GO.js";
12
21
  import {
13
22
  RouteError,
14
23
  abortable
15
- } from "./chunk-J3XO7RTF.js";
24
+ } from "./chunk-ROZINFTV.js";
16
25
  import {
17
26
  beijingDay,
18
27
  writePrivateJson
19
- } from "./chunk-MCHV2R25.js";
20
- import {
21
- WorkBuddyUpstreamClient,
22
- modelWithCurrentPromotion,
23
- prepareChatBody
24
- } from "./chunk-TEZ2FMEO.js";
28
+ } from "./chunk-WVNY4LSL.js";
25
29
 
26
30
  // src/clients.mjs
27
31
  import { readFile as readFile2 } from "node:fs/promises";
@@ -790,108 +794,6 @@ var TraeSoloRemoteCatalogClient = class {
790
794
 
791
795
  // upstream/trae/src/solo-bridge.ts
792
796
  import { randomUUID as randomUUID2 } from "node:crypto";
793
-
794
- // upstream/trae/src/sse.ts
795
- var SseDecoder = class {
796
- buffer = "";
797
- event;
798
- id;
799
- retry;
800
- data = [];
801
- push(chunk) {
802
- this.buffer += chunk;
803
- const events = [];
804
- while (true) {
805
- const match = /\r?\n/.exec(this.buffer);
806
- if (match === null || match.index === void 0) break;
807
- const line = this.buffer.slice(0, match.index);
808
- this.buffer = this.buffer.slice(match.index + match[0].length);
809
- const emitted = this.consumeLine(line);
810
- if (emitted !== void 0) events.push(emitted);
811
- }
812
- return events;
813
- }
814
- finish() {
815
- const events = [];
816
- if (this.buffer !== "") {
817
- const emitted = this.consumeLine(this.buffer);
818
- this.buffer = "";
819
- if (emitted !== void 0) events.push(emitted);
820
- }
821
- const final = this.dispatch();
822
- if (final !== void 0) events.push(final);
823
- return events;
824
- }
825
- consumeLine(line) {
826
- if (line === "") return this.dispatch();
827
- if (line.startsWith(":")) return void 0;
828
- const colon = line.indexOf(":");
829
- const field = colon === -1 ? line : line.slice(0, colon);
830
- let value = colon === -1 ? "" : line.slice(colon + 1);
831
- if (value.startsWith(" ")) value = value.slice(1);
832
- if (field === "event") this.event = value;
833
- else if (field === "data") this.data.push(value);
834
- else if (field === "id" && !value.includes("\0")) this.id = value;
835
- else if (field === "retry" && /^\d+$/.test(value)) this.retry = Number(value);
836
- return void 0;
837
- }
838
- dispatch() {
839
- if (this.data.length === 0) {
840
- this.event = void 0;
841
- this.retry = void 0;
842
- return void 0;
843
- }
844
- const result = {
845
- ...this.event === void 0 || this.event === "" ? {} : { event: this.event },
846
- data: this.data.join("\n"),
847
- ...this.id === void 0 ? {} : { id: this.id },
848
- ...this.retry === void 0 ? {} : { retry: this.retry }
849
- };
850
- this.event = void 0;
851
- this.retry = void 0;
852
- this.data = [];
853
- return result;
854
- }
855
- };
856
- function decodeTraeEvent(event) {
857
- if (event.data === "[DONE]") return { type: "done", finishReason: "stop" };
858
- let payload;
859
- try {
860
- payload = JSON.parse(event.data);
861
- } catch {
862
- return { type: "unknown", ...event.event === void 0 ? {} : { event: event.event }, data: event.data };
863
- }
864
- const record2 = typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : {};
865
- if (event.event === "request_wait_in_queue") {
866
- return { type: "queue", ...typeof record2["position"] === "number" ? { position: record2["position"] } : {} };
867
- }
868
- if (event.event === "progress_notice") return { type: "progress", notice: payload };
869
- if (event.event === "token_usage") {
870
- return {
871
- type: "usage",
872
- ...typeof record2["prompt_tokens"] === "number" ? { inputTokens: record2["prompt_tokens"] } : {},
873
- ...typeof record2["completion_tokens"] === "number" ? { outputTokens: record2["completion_tokens"] } : {},
874
- ...typeof record2["total_tokens"] === "number" ? { totalTokens: record2["total_tokens"] } : {},
875
- ...typeof record2["reasoning_tokens"] === "number" ? { reasoningTokens: record2["reasoning_tokens"] } : {},
876
- ...typeof record2["cache_read_input_tokens"] === "number" ? { cacheReadTokens: record2["cache_read_input_tokens"] } : {},
877
- ...typeof record2["cache_creation_input_tokens"] === "number" ? { cacheWriteTokens: record2["cache_creation_input_tokens"] } : {}
878
- };
879
- }
880
- if (event.event === "done" || typeof record2["finish_reason"] === "string" && record2["response"] === void 0) {
881
- return { type: "done", finishReason: typeof record2["finish_reason"] === "string" ? record2["finish_reason"] : "stop" };
882
- }
883
- if (event.event === "output" || record2["response"] !== void 0 || record2["reasoning_content"] !== void 0) {
884
- return {
885
- type: "delta",
886
- text: typeof record2["response"] === "string" ? record2["response"] : "",
887
- ...typeof record2["reasoning_content"] === "string" ? { reasoning: record2["reasoning_content"] } : {},
888
- ...record2["tool_calls"] === void 0 || record2["tool_calls"] === null ? {} : { toolCalls: record2["tool_calls"] }
889
- };
890
- }
891
- return { type: "unknown", ...event.event === void 0 ? {} : { event: event.event }, data: payload };
892
- }
893
-
894
- // upstream/trae/src/solo-bridge.ts
895
797
  var TRAE_ERROR_HINTS = {
896
798
  1005: "Trae requires a paid plan for this model; the current account's plan does not cover it",
897
799
  4001: "Trae does not serve this model under the SOLO function the request used (config_name rejected)",
@@ -5508,4 +5410,4 @@ export {
5508
5410
  refreshWorkBuddyCheckinCredential,
5509
5411
  createWorkBuddyCheckin
5510
5412
  };
5511
- //# sourceMappingURL=chunk-U2MU2M5C.js.map
5413
+ //# sourceMappingURL=chunk-NSKCFVHP.js.map