dsh-multi-router 0.5.4 → 0.5.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。版本号从 `0.1` 起以功能迭代为主,接口在 `1.0` 前可能变化。
4
4
 
5
+ ## 0.5.6
6
+
7
+ - 修复 WorkBuddy 中国版 `workbuddy.cn` 签发的有效令牌被本地误判失效,同时兼容原 `codebuddy.cn` 签发地址;保留 RS256、账号身份与中国版精确签发地址校验。
8
+ - 添加账号 / 更新凭证面板新增 WorkBuddy 手机短信登录,自动填写 UID 与有效期、保存 AT / RT,可验证刷新令牌并保存轮换后的新凭证;无需 Python。同一登录身份更新已有账号,不自动开启签到。
9
+ - 增加「重新验证登录」只读恢复入口,官方状态验证通过后才解除旧认证失败记录,不重置已领取、结果不明、设备限制或平台暂停记录。
10
+ - 短信发送冷却跨重启保存,验证码会话短期有效、提交次数有限;请求不自动重试,平台限制时暂停。只使用官方 HTTPS 接口并启用证书校验,不向面板返回令牌或上游私有错误。
11
+
12
+ ## 0.5.5
13
+
14
+ - Token 明细与总览增加模型用时、工具调用用时、首 Token 平均(TTFT)与输出速度(TPS);支持单次展开与筛选内成功调用汇总,TPS 按输出 Token / 解码秒数加权,失败与取消不混入成功性能。DSH 步骤沿用会话统计口径,直接上游请求保留独立计时,工具用时不猜测;旧记录不补算。
15
+ - 使用每次调用的独立关联 ID 匹配 DSH 步骤,隔离并发会话与辅助调用;工具开始 / 结果按 ID 配对。只保存数值和散列后的会话标识,不保存原始会话 ID、标题、文本、工具名称、参数或结果。
16
+ - 账号池默认展示「全部供应商」,同时保留单供应商 / 区域筛选;各行展示供应商,跨平台全选 / 全不选原子保存,不改变自动签到设置。
17
+ - 每 24 小时自动检查用户安装的 GitHub / npm 插件;通过 DSH 全局浮层提示更新,支持稍后处理或查看并手动更新。已处理版本 / 提交持久化去重,重启不重复提醒;检查失败不频繁重试,DSH 内置插件不参与。
18
+ - README 更新为本版合成账号截图,替换历史真实账号界面,并使用 npm 也可访问的绝对图片链接。
19
+
5
20
  ## 0.5.4
6
21
 
7
22
  - 重整整体面板:简洁页签、统一表格与状态展示;账号池集中展示余额、3 天内临期积分、路由冷却、目录模型与签到,支持搜索、供应商内全选 / 全不选,管理详情收进弹窗,适配 DSH 明暗主题与窄窗口。
package/README.md CHANGED
@@ -6,15 +6,17 @@
6
6
 
7
7
  一个插件内置 WorkBuddy、Trae、Qoder 的认证、模型发现和聊天传输,提供三家的多账号池及集中路由。运行不依赖原来三个 DSH 插件;三家代码随安装包一起打包。基于 MIT 上游,固定版本见 `upstream-lock.json`,许可见 `THIRD_PARTY_NOTICES.md`。
8
8
 
9
- 版本:0.5.4。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
9
+ 版本:0.5.5。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
10
10
 
11
11
  ## 在 DSH 里管理
12
12
 
13
- 打开 **插件 → dsh-multi-router 的配置页**。默认进入总览,六个页签:
13
+ 打开 **插件 → dsh-multi-router 的配置页**。默认进入总览,八个页签:
14
14
 
15
15
  - **总览与用量**:今日/累计请求与 Token、近 7 日趋势、最近请求、三家平台余额与刷新时间。平台每分钟查询,面板每 15 秒刷新,可暂停面板自动刷新。
16
- - **Token 明细**:每次上游调用的时间、渠道、账号、路由、实际模型、输入/输出/总 Token 和结果;支持今日、近 3 日、近 7 日、近 30 日及自定义日期范围(北京时间自然日),并按账号、路由、结果筛选,每页 50 条。缓存输入和推理输出在平台返回时展示,不重复计入总量。默认滚动保留最近 30 × 24 小时,可设置 1–365 天,并随配置迁移。旧记录缺少的分项显示「—」。
17
- - **账号与登录**:启动时自动读取本机 WorkBuddy / Trae 已登录账号,每分钟重新检测;Trae 自动识别 CN / Global / SOLO,面板展示检测状态,并提供「重新检测本机登录」。添加账号弹窗也可一键获取;账号 ID 自动生成、显示名自动识别,重复账号会提示。Qoder 使用 PAT,可粘贴或获取本机旧连接器已保存的 PAT。新账号自动获取模型与平台用量。详情弹窗查看身份、认证类型、有效期、刷新能力、套餐与积分包;账号可命名、启停、排序和移出。
16
+ - **Token 明细**:每次上游调用的时间、渠道、账号、路由、实际模型、输入/输出/总 Token 和结果;支持今日、近 3 日、近 7 日、近 30 日及自定义日期范围(北京时间自然日),并按账号、路由、结果筛选,每页 50 条。缓存输入和推理输出在平台返回时展示,不重复计入总量。默认滚动保留最近 30 × 24 小时,可设置 1–365 天,并随配置迁移。同时展示模型用时、工具调用用时、首 Token 平均(TTFT)和输出速度(TPS),可展开单次性能。DSH 步骤采用会话统计相同口径;TPS 按有效输出 Token / 解码秒数加权计算。性能汇总只包含成功调用,随当前筛选变化;旧记录缺少的分项显示「—」。
17
+ - **账号与登录**:默认「全部供应商」集中展示所有账号,可按供应商 / 区域筛选、搜索、全选 / 全不选。启动时自动读取本机 WorkBuddy / Trae 已登录账号,每分钟重新检测;Trae 自动识别 CN / Global / SOLO,面板展示检测状态,并提供「重新检测本机登录」。添加账号弹窗也可一键获取;账号 ID 自动生成、显示名自动识别,重复账号会提示。Qoder 使用 PAT,可粘贴或获取本机旧连接器已保存的 PAT。新账号自动获取模型与平台用量。详情弹窗查看身份、认证类型、有效期、刷新能力、套餐与积分包;账号可命名、启停、排序和移出。
18
+ - **积分签到**:按账号设置每日启动签到或定时签到(北京时间),保留每日执行记录、领取意图与设备限制,避免重复请求。
19
+ - **插件更新**:仅检查用户安装的 GitHub / npm 插件,包含停用插件;每 24 小时自动检查,在 DSH 任意页面弹窗提醒新版本。同一更新点击关闭或「稍后处理」后不重复提醒,更高版本 / 新提交再次提醒。可在弹窗查看并手动更新;不自动安装。检查失败不冒充发现新版本,内置组件不检查、不展示。
18
20
  - **模型管理**:原始模型默认隐藏,只展示统一路由;提供所有渠道「全部展示」「全部隐藏(仅路由)」,作用于现有和以后发现的模型。可搜索、获取真实模型目录,修改显示名与单模型展示开关,每家独立保存。
19
21
  - **路由规则**:Free 免费路由排在首位,显示各账号当前免费模型与目录检查时间,支持立即检查。列表显示路由名称、切换策略和目标;新建/编辑打开二级弹窗。「添加模型预配置」可逐个添加或补齐全部三家预配置,不覆盖已有同 ID 路由。模型可从下拉列表选择,候选 ID 和上下文长度放在高级选项中。
20
22
 
@@ -28,17 +30,22 @@
28
30
 
29
31
  ### 界面预览
30
32
 
31
- | 总览与用量 | Token 明细 |
33
+ 以下为 **v0.5.5–v0.5.6 实际面板 + 合成演示数据**。账号名称已替换,不包含真实手机号、邮箱、身份 ID、设备标识、登录凭证或会话内容;余额、用量和性能均为演示数值。
34
+
35
+ | 总览与用量 | Token 明细与调用性能 |
32
36
  | --- | --- |
33
- | 今日 / 累计 Token、账号池状态与三家平台余额 | 逐次上游调用、输入 / 输出分项与保留期限设置 |
34
- | ![总览与用量](docs/screenshots/overview-usage.jpg) | ![Token 明细](docs/screenshots/token-details.jpg) |
37
+ | 今日 / 累计 Token、调用性能与平台余额 | 日期筛选、模型 / 工具用时、TTFT、TPS 与逐次明细 |
38
+ | ![总览与用量](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/overview-usage.jpg) | ![Token 明细与调用性能](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/token-details.jpg) |
35
39
 
36
- | 路由规则 · Free 动态免费模型 | DSH 会话模型选择器 |
40
+ | 全部供应商账号池 | 路由规则与免费模型 |
37
41
  | --- | --- |
38
- | 六个渠道目标 + 按模型自动创建的免费路由 | 「统一路由」分组置顶 Free 与各预配置 |
39
- | ![路由规则与免费模型](docs/screenshots/routes-free-models.jpg) | ![模型选择器](docs/screenshots/model-picker.jpg) |
42
+ | 所有平台集中展示,也可按供应商筛选 | 按免费模型动态生成路由,单独选择与设置推理等级 |
43
+ | ![全部供应商账号池](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/accounts-all.jpg) | ![路由规则与免费模型](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/routes-free-models.jpg) |
40
44
 
41
- > 截图取自作者本机环境,其中的账号 ID、积分余额与 Token 用量为**真实数值**(非凭证,不含任何 Token / PAT / 身份信息)。插件本身不记录、不上传凭证,凭证仅保存在本机 `~/.dsh/multi-router/`(权限 0600)。
45
+ | 每日积分签到 | 全局插件更新提醒 |
46
+ | --- | --- |
47
+ | 启动 / 定时计划、按账号开启与状态去重 | GitHub / npm 用户插件、手动更新、同版本提醒去重 |
48
+ | ![每日积分签到](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) |
42
49
 
43
50
  ## 内置渠道与默认路由
44
51
 
@@ -99,6 +106,16 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
99
106
 
100
107
  总览、签到记录和插件更新使用一致的表格布局;详细执行规则按需展开,路由继续通过二级弹窗编辑。沿用 DSH 明暗主题,窄窗口的宽表格可在表内横向滚动。
101
108
 
109
+ ### WorkBuddy 面板短信登录
110
+
111
+ 打开 **账号与登录 → 添加账号 → WorkBuddy → 短信登录并保存凭证**。输入已有账号的手机号,点击发送验证码,输入短信验证码后点击「登录并保存到账号池」。UID、有效期、AT / RT 自动识别保存,无需安装 Python;默认额外验证一次刷新令牌,并保存轮换后的新 AT / RT。同一登录身份更新已有账号,自动签到开关保持原设置。更新已有凭证也可使用此方式。
112
+
113
+ 手机号和验证码仅提交给 WorkBuddy 中国版官方 HTTPS 接口;凭证仅在本机后台保存,不在普通面板响应中返回。短信发送至少间隔 60 秒,冷却跨重启保存;验证码最长 5 分钟有效,最多提交 5 次,登录请求不自动重试。遇到平台限制 / 图形验证时暂停请求,请使用官方客户端登录后读取凭证。仅用于已有账号,注册请在官方客户端完成。流程参考 [WorkBuddy-Daily 登录工具](https://github.com/L0NE-6/WorkBuddy-Daily/blob/main/workbuddy_login.py),保留 HTTPS 证书验证。
114
+
115
+ ![WorkBuddy 短信登录面板(v0.5.6,合成数据,输入为空)](https://raw.githubusercontent.com/AndroidSix/dsh-multi-router/main/docs/screenshots/workbuddy-login.jpg)
116
+
117
+ 签到显示「令牌失效」时,可点击 **重新验证登录**:只查询官方签到状态,验证通过才解除旧认证失败记录,不提交领取、不清空已完成或结果不明的领取记录。该操作全局间隔至少 30 秒。`workbuddy.cn` 与 `codebuddy.cn` 两个中国版官方签发地址均支持;自动签到默认关闭,需在签到页单独开启。
118
+
102
119
  ## 每日积分签到
103
120
 
104
121
  在配置面板打开 **积分签到**。当前接入 WorkBuddy 国内版、Trae CN 与 Qoder CN / Global 的签到能力:先查询平台是否开放活动、账号与设备是否已领取,再提交领取;活动是否开放和奖励额度以平台响应为准。WorkBuddy AI 与 Trae Global 暂无已核验的签到接口,面板会显示需到官方客户端查看,不猜测接口或奖励。
@@ -106,7 +123,7 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
106
123
  - 每个账号可选择「立即签到」,或「签到全部可用账号」。**每日自动签到默认关闭**,按账号单独开启;已停用账号不参与。可选择「每日启动时签到」或「每天定时签到」,定时时间按北京时间设置,默认保留 10:05。启动模式在当天首次运行时执行,持续运行则新一天自动执行;定时模式错过时间后在下次启动补查。Qoder 最早 10:05 查询当日领取窗口,WorkBuddy / Trae 启动模式不受此窗口限制。20:05 后仅补查确认未提交领取的查询失败或尚未开放窗口,每个账号最多补查一次;DSH 关闭期间不执行。
107
124
  - Qoder 先用 PAT 交换临时 Job Token,活动请求使用 `Cosy-ClientType: 10`;仅领取有效期内 `CLAIM_BENEFIT` 且 `CLAIMABLE` 的活动。宣传、锁定或未开放给此账号的活动显示「无活动」,不会误报令牌失效;奖励仅显示领取响应实际返回的金额。TRAE 沿用客户端登录的 Refresh Token 和真实设备参数续期,不依赖短信登录,也不按账号数组位置保存状态。
108
125
  - WorkBuddy 国内版沿用已保存的客户端 AT / RT,按 JWT 中的账号身份校验请求;续期后保存新 AT 和轮换的 RT。先查今日签到状态及活动结束时间,再提交一次领取,已签或活动结束时跳过;面板展示返回的连签天数、活动累计积分和本次奖励。网络或 5xx 仅对领取前的只读状态查询最多重试两次(2 秒、4 秒),领取请求不做快速重试。接口契约参考 [WorkBuddy-Daily](https://github.com/L0NE-6/WorkBuddy-Daily/blob/main/workbuddy_daily.py),未采用成长任务、抽奖或客户端活跃模拟。
109
- - 启动、定时、手动按钮共用持久化记录;改签到计划、刷新面板或重启不重置记录。当天已处理的账号不会重复进入请求队列,令牌失效后等待显式更新凭证再恢复。
126
+ - 启动、定时、手动按钮共用持久化记录;改签到计划、刷新面板或重启不重置记录。当天已处理的账号不会重复进入请求队列,令牌失效后等待显式更新凭证或 WorkBuddy 只读登录验证通过再恢复。
110
127
  - 请求串行,账号间至少间隔 10 秒。先持久化当天意图再发请求,重复账号副本按平台身份去重,调整账号顺序不影响记录。每天最多提交一次领取;提交后网络超时或结果不明确时不重复提交,请到官方客户端核对。成功、今日已领、无活动和结果不明的账号不会加入晚间重试队列。
111
128
  - 保留 Trae 原有设备身份;同一设备当天的领取已使用或结果不明确时,不切到同设备的其他账号继续领取。Qoder 优先复用官方 CLI 的设备身份,没有时使用连接器持久化的单一设备身份,所有账号一致,不随签到轮换。
112
129
  - 验证、403 拒绝或限流会暂停该平台签到;在官方客户端处理后,可以点击「恢复后续签到」。恢复不会清除今日尝试记录与设备限制。401 或明确过期且无法续期的凭证单独显示「令牌失效」,请在「账号与登录」更新凭证;替换凭证只解除认证失败记录,已提交或结果不明的领取记录仍保留。普通网络查询失败不会要求重交令牌。不提供更换设备身份、代理或绕过验证码的能力;不能保证不触发平台风控。
@@ -144,7 +161,7 @@ dsh plugin --profile desktop add dsh-multi-router
144
161
  dsh plugin --profile web add dsh-multi-router
145
162
  ```
146
163
 
147
- 包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.4`。
164
+ 包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.5`。
148
165
 
149
166
  已使用 npm 来源的用户更新:
150
167
 
@@ -201,7 +218,7 @@ cd dsh-multi-router
201
218
  npm ci
202
219
  npm run check
203
220
  npm run pack:release
204
- dsh plugin --profile desktop add ./dsh-multi-router-0.5.4.tgz
221
+ dsh plugin --profile desktop add ./dsh-multi-router-0.5.5.tgz
205
222
  ```
206
223
 
207
224
  ### 配置账号
@@ -133,3 +133,34 @@ SOFTWARE.
133
133
  引入上述代码不构成对任何平台的授权。本项目**仅供个人学习研究**,解析客户端本地登录态或使用未公开接口可能违反对应平台服务条款,详见 [`DISCLAIMER.md`](./DISCLAIMER.md)。
134
134
 
135
135
  若你对本文件的完整性有疑问或发现遗漏,请提交 issue 或 PR。
136
+
137
+ ## WorkBuddy-Daily 登录流程参考
138
+
139
+ - 项目:https://github.com/L0NE-6/WorkBuddy-Daily
140
+ - 文件:`workbuddy_login.py`;MIT;Copyright (c) 2026 L0NE-6
141
+ - 本插件以 JavaScript 重写其短信登录接口流程,并增加本机冷却、短期会话、凭证轮换保存;不运行 Python 脚本,不采用关闭 TLS 证书验证、成长任务、活跃模拟或抽奖逻辑。
142
+
143
+ ```text
144
+ MIT License
145
+
146
+ Copyright (c) 2026 L0NE-6
147
+
148
+ Permission is hereby granted, free of charge, to any person obtaining a copy
149
+ of this software and associated documentation files (the "Software"), to deal
150
+ in the Software without restriction, including without limitation the rights
151
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
152
+ copies of the Software, and to permit persons to whom the Software is
153
+ furnished to do so, subject to the following conditions:
154
+
155
+ The above copyright notice and this permission notice shall be included in all
156
+ copies or substantial portions of the Software.
157
+
158
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
159
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
160
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
161
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
162
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
163
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
164
+ SOFTWARE.
165
+
166
+ ```
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-DNQPZMPF.js";
5
5
  import {
6
6
  RouteError
7
- } from "./chunk-6ZXEVC3R.js";
7
+ } from "./chunk-J3XO7RTF.js";
8
8
  import {
9
9
  REASONING_EFFORTS
10
10
  } from "./chunk-MCHV2R25.js";
@@ -70,7 +70,7 @@ function createShim(router, { modelPrefix = "" } = {}) {
70
70
  if (effort !== "default" && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, "invalid_reasoning_effort");
71
71
  input.reasoning_effort = effort === "default" ? null : effort;
72
72
  }
73
- const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal);
73
+ const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal, { callId: typeof req.headers["x-multi-router-call"] === "string" ? req.headers["x-multi-router-call"] : void 0, session: typeof req.headers["x-multi-router-session"] === "string" ? req.headers["x-multi-router-session"] : void 0 });
74
74
  signal.throwIfAborted();
75
75
  res.writeHead(200, {
76
76
  "Content-Type": "text/event-stream; charset=utf-8",
@@ -113,4 +113,4 @@ function createShim(router, { modelPrefix = "" } = {}) {
113
113
  export {
114
114
  createShim
115
115
  };
116
- //# sourceMappingURL=chunk-GDUBJ4Z4.js.map
116
+ //# sourceMappingURL=chunk-CT6ZYIAF.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/shim.mjs"],
4
- "sourcesContent": ["import { createServer } from 'node:http';\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { hostIsLoopback, originIsLoopback } from '../upstream/workbuddy/src/loopback.ts';\nimport { RouteError } from './router.mjs';\nimport { REASONING_EFFORTS } from './reasoning.mjs';\n\nexport function createShim(router, { modelPrefix = '' } = {}) {\n const secret = randomBytes(32).toString('base64url');\n const server = createServer((req, res) => { void handle(req, res); });\n const ready = new Promise((resolve, reject) => {\n server.once('listening', resolve); server.once('error', reject);\n });\n server.listen(0, '127.0.0.1');\n const json = (res, status, value) => {\n res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value));\n };\n async function handle(req, res) {\n const controller = new AbortController();\n req.on('aborted', () => controller.abort());\n res.on('close', () => { if (!res.writableEnded) controller.abort(); });\n const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(router.config.requestTimeoutMs)]);\n try {\n if (!hostIsLoopback(req.headers.host) || !originIsLoopback(req.headers.origin)) throw new RouteError(403, 'loopback_required');\n const bearer = Buffer.from((req.headers.authorization ?? '').replace(/^Bearer\\s+/i, ''));\n const expected = Buffer.from(secret);\n if (bearer.length !== expected.length || !timingSafeEqual(bearer, expected)) throw new RouteError(401, 'unauthorized');\n if (req.method === 'GET' && req.url === '/router/status') { json(res, 200, router.status()); return; }\n if (req.method === 'GET' && req.url === '/v1/models') {\n json(res, 200, { object: 'list', data: Object.keys(router.config.routes).filter(id => modelPrefix ? id.startsWith(modelPrefix) : !id.includes('::'))\n .map(id => ({ id: modelPrefix ? id.slice(modelPrefix.length) : id, object: 'model', created: 0, owned_by: 'multi-router' })) }); return;\n }\n if (req.method !== 'POST' || req.url !== '/v1/chat/completions') throw new RouteError(404, 'not_found');\n if (!/^application\\/json(?:\\s*;|$)/i.test(req.headers['content-type'] ?? '')) throw new RouteError(415, 'json_required');\n let size = 0; const chunks = [];\n for await (const chunk of req) {\n size += chunk.length;\n if (size > 32 * 1024 * 1024) throw new RouteError(413, 'request_too_large');\n chunks.push(chunk); signal.throwIfAborted();\n }\n let input;\n try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); }\n catch { throw new RouteError(400, 'invalid_json'); }\n if (!input || typeof input !== 'object' || Array.isArray(input)) throw new RouteError(400, 'invalid_request');\n const effort = req.headers['x-multi-router-reasoning'];\n if (effort !== undefined) {\n if (effort !== 'default' && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, 'invalid_reasoning_effort');\n input.reasoning_effort = effort === 'default' ? null : effort;\n }\n const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal);\n signal.throwIfAborted();\n res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache',\n 'X-Accel-Buffering': 'no', 'X-Router-Account': selected.account, 'X-Router-Provider': selected.provider });\n await pipeline(Readable.fromWeb(selected.response.body), res, { signal });\n } catch (error) {\n if (res.destroyed) return;\n if (res.headersSent) { res.destroy(); return; }\n const status = error instanceof RouteError ? error.status : signal.aborted ? 504 : 500;\n json(res, status, { error: { type: error instanceof RouteError ? error.code : signal.aborted ? 'request_timeout' : 'internal_error',\n message: error instanceof RouteError ? error.message : 'Router request failed',\n hint: error instanceof RouteError ? error.hint : undefined,\n attempts: error instanceof RouteError ? error.attempts : [] } });\n }\n }\n return { ready, token: () => secret, baseUrl: () => `http://127.0.0.1:${server.address().port}`,\n close: async () => { await ready.catch(() => {}); await new Promise(resolve => { server.close(resolve); server.closeAllConnections(); }); } };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createServer } from 'node:http';\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { hostIsLoopback, originIsLoopback } from '../upstream/workbuddy/src/loopback.ts';\nimport { RouteError } from './router.mjs';\nimport { REASONING_EFFORTS } from './reasoning.mjs';\n\nexport function createShim(router, { modelPrefix = '' } = {}) {\n const secret = randomBytes(32).toString('base64url');\n const server = createServer((req, res) => { void handle(req, res); });\n const ready = new Promise((resolve, reject) => {\n server.once('listening', resolve); server.once('error', reject);\n });\n server.listen(0, '127.0.0.1');\n const json = (res, status, value) => {\n res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value));\n };\n async function handle(req, res) {\n const controller = new AbortController();\n req.on('aborted', () => controller.abort());\n res.on('close', () => { if (!res.writableEnded) controller.abort(); });\n const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(router.config.requestTimeoutMs)]);\n try {\n if (!hostIsLoopback(req.headers.host) || !originIsLoopback(req.headers.origin)) throw new RouteError(403, 'loopback_required');\n const bearer = Buffer.from((req.headers.authorization ?? '').replace(/^Bearer\\s+/i, ''));\n const expected = Buffer.from(secret);\n if (bearer.length !== expected.length || !timingSafeEqual(bearer, expected)) throw new RouteError(401, 'unauthorized');\n if (req.method === 'GET' && req.url === '/router/status') { json(res, 200, router.status()); return; }\n if (req.method === 'GET' && req.url === '/v1/models') {\n json(res, 200, { object: 'list', data: Object.keys(router.config.routes).filter(id => modelPrefix ? id.startsWith(modelPrefix) : !id.includes('::'))\n .map(id => ({ id: modelPrefix ? id.slice(modelPrefix.length) : id, object: 'model', created: 0, owned_by: 'multi-router' })) }); return;\n }\n if (req.method !== 'POST' || req.url !== '/v1/chat/completions') throw new RouteError(404, 'not_found');\n if (!/^application\\/json(?:\\s*;|$)/i.test(req.headers['content-type'] ?? '')) throw new RouteError(415, 'json_required');\n let size = 0; const chunks = [];\n for await (const chunk of req) {\n size += chunk.length;\n if (size > 32 * 1024 * 1024) throw new RouteError(413, 'request_too_large');\n chunks.push(chunk); signal.throwIfAborted();\n }\n let input;\n try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); }\n catch { throw new RouteError(400, 'invalid_json'); }\n if (!input || typeof input !== 'object' || Array.isArray(input)) throw new RouteError(400, 'invalid_request');\n const effort = req.headers['x-multi-router-reasoning'];\n if (effort !== undefined) {\n if (effort !== 'default' && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, 'invalid_reasoning_effort');\n input.reasoning_effort = effort === 'default' ? null : effort;\n }\n const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal, { callId: typeof req.headers['x-multi-router-call'] === 'string' ? req.headers['x-multi-router-call'] : undefined, session: typeof req.headers['x-multi-router-session'] === 'string' ? req.headers['x-multi-router-session'] : undefined });\n signal.throwIfAborted();\n res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache',\n 'X-Accel-Buffering': 'no', 'X-Router-Account': selected.account, 'X-Router-Provider': selected.provider });\n await pipeline(Readable.fromWeb(selected.response.body), res, { signal });\n } catch (error) {\n if (res.destroyed) return;\n if (res.headersSent) { res.destroy(); return; }\n const status = error instanceof RouteError ? error.status : signal.aborted ? 504 : 500;\n json(res, status, { error: { type: error instanceof RouteError ? error.code : signal.aborted ? 'request_timeout' : 'internal_error',\n message: error instanceof RouteError ? error.message : 'Router request failed',\n hint: error instanceof RouteError ? error.hint : undefined,\n attempts: error instanceof RouteError ? error.attempts : [] } });\n }\n }\n return { ready, token: () => secret, baseUrl: () => `http://127.0.0.1:${server.address().port}`,\n close: async () => { await ready.catch(() => {}); await new Promise(resolve => { server.close(resolve); server.closeAllConnections(); }); } };\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,aAAa,uBAAuB;AAC7C,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AAKlB,SAAS,WAAW,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC,GAAG;AAC5D,QAAM,SAAS,YAAY,EAAE,EAAE,SAAS,WAAW;AACnD,QAAM,SAAS,aAAa,CAAC,KAAK,QAAQ;AAAE,SAAK,OAAO,KAAK,GAAG;AAAA,EAAG,CAAC;AACpE,QAAM,QAAQ,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC7C,WAAO,KAAK,aAAa,OAAO;AAAG,WAAO,KAAK,SAAS,MAAM;AAAA,EAChE,CAAC;AACD,SAAO,OAAO,GAAG,WAAW;AAC5B,QAAM,OAAO,CAAC,KAAK,QAAQ,UAAU;AACnC,QAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAAG,QAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAAA,EAC9F;AACA,iBAAe,OAAO,KAAK,KAAK;AAC9B,UAAM,aAAa,IAAI,gBAAgB;AACvC,QAAI,GAAG,WAAW,MAAM,WAAW,MAAM,CAAC;AAC1C,QAAI,GAAG,SAAS,MAAM;AAAE,UAAI,CAAC,IAAI,cAAe,YAAW,MAAM;AAAA,IAAG,CAAC;AACrE,UAAM,SAAS,YAAY,IAAI,CAAC,WAAW,QAAQ,YAAY,QAAQ,OAAO,OAAO,gBAAgB,CAAC,CAAC;AACvG,QAAI;AACF,UAAI,CAAC,eAAe,IAAI,QAAQ,IAAI,KAAK,CAAC,iBAAiB,IAAI,QAAQ,MAAM,EAAG,OAAM,IAAI,WAAW,KAAK,mBAAmB;AAC7H,YAAM,SAAS,OAAO,MAAM,IAAI,QAAQ,iBAAiB,IAAI,QAAQ,eAAe,EAAE,CAAC;AACvF,YAAM,WAAW,OAAO,KAAK,MAAM;AACnC,UAAI,OAAO,WAAW,SAAS,UAAU,CAAC,gBAAgB,QAAQ,QAAQ,EAAG,OAAM,IAAI,WAAW,KAAK,cAAc;AACrH,UAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,kBAAkB;AAAE,aAAK,KAAK,KAAK,OAAO,OAAO,CAAC;AAAG;AAAA,MAAQ;AACrG,UAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,cAAc;AACpD,aAAK,KAAK,KAAK,EAAE,QAAQ,QAAQ,MAAM,OAAO,KAAK,OAAO,OAAO,MAAM,EAAE,OAAO,QAAM,cAAc,GAAG,WAAW,WAAW,IAAI,CAAC,GAAG,SAAS,IAAI,CAAC,EAChJ,IAAI,SAAO,EAAE,IAAI,cAAc,GAAG,MAAM,YAAY,MAAM,IAAI,IAAI,QAAQ,SAAS,SAAS,GAAG,UAAU,eAAe,EAAE,EAAE,CAAC;AAAG;AAAA,MACrI;AACA,UAAI,IAAI,WAAW,UAAU,IAAI,QAAQ,uBAAwB,OAAM,IAAI,WAAW,KAAK,WAAW;AACtG,UAAI,CAAC,gCAAgC,KAAK,IAAI,QAAQ,cAAc,KAAK,EAAE,EAAG,OAAM,IAAI,WAAW,KAAK,eAAe;AACvH,UAAI,OAAO;AAAG,YAAM,SAAS,CAAC;AAC9B,uBAAiB,SAAS,KAAK;AAC7B,gBAAQ,MAAM;AACd,YAAI,OAAO,KAAK,OAAO,KAAM,OAAM,IAAI,WAAW,KAAK,mBAAmB;AAC1E,eAAO,KAAK,KAAK;AAAG,eAAO,eAAe;AAAA,MAC5C;AACA,UAAI;AACJ,UAAI;AAAE,gBAAQ,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC;AAAA,MAAG,QAC5D;AAAE,cAAM,IAAI,WAAW,KAAK,cAAc;AAAA,MAAG;AACnD,UAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,OAAM,IAAI,WAAW,KAAK,iBAAiB;AAC5G,YAAM,SAAS,IAAI,QAAQ,0BAA0B;AACrD,UAAI,WAAW,QAAW;AACxB,YAAI,WAAW,aAAa,CAAC,kBAAkB,SAAS,MAAM,EAAG,OAAM,IAAI,WAAW,KAAK,0BAA0B;AACrH,cAAM,mBAAmB,WAAW,YAAY,OAAO;AAAA,MACzD;AACA,YAAM,WAAW,MAAM,OAAO,KAAK,EAAE,GAAG,OAAO,OAAO,cAAc,MAAM,MAAM,GAAG,QAAQ,EAAE,QAAQ,OAAO,IAAI,QAAQ,qBAAqB,MAAM,WAAW,IAAI,QAAQ,qBAAqB,IAAI,QAAW,SAAS,OAAO,IAAI,QAAQ,wBAAwB,MAAM,WAAW,IAAI,QAAQ,wBAAwB,IAAI,OAAU,CAAC;AACtU,aAAO,eAAe;AACtB,UAAI,UAAU,KAAK;AAAA,QAAE,gBAAgB;AAAA,QAAoC,iBAAiB;AAAA,QACxF,qBAAqB;AAAA,QAAM,oBAAoB,SAAS;AAAA,QAAS,qBAAqB,SAAS;AAAA,MAAS,CAAC;AAC3G,YAAM,SAAS,SAAS,QAAQ,SAAS,SAAS,IAAI,GAAG,KAAK,EAAE,OAAO,CAAC;AAAA,IAC1E,SAAS,OAAO;AACd,UAAI,IAAI,UAAW;AACnB,UAAI,IAAI,aAAa;AAAE,YAAI,QAAQ;AAAG;AAAA,MAAQ;AAC9C,YAAM,SAAS,iBAAiB,aAAa,MAAM,SAAS,OAAO,UAAU,MAAM;AACnF,WAAK,KAAK,QAAQ,EAAE,OAAO;AAAA,QAAE,MAAM,iBAAiB,aAAa,MAAM,OAAO,OAAO,UAAU,oBAAoB;AAAA,QACjH,SAAS,iBAAiB,aAAa,MAAM,UAAU;AAAA,QACvD,MAAM,iBAAiB,aAAa,MAAM,OAAO;AAAA,QACjD,UAAU,iBAAiB,aAAa,MAAM,WAAW,CAAC;AAAA,MAAE,EAAE,CAAC;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AAAA,IAAE;AAAA,IAAO,OAAO,MAAM;AAAA,IAAQ,SAAS,MAAM,oBAAoB,OAAO,QAAQ,EAAE,IAAI;AAAA,IAC3F,OAAO,YAAY;AAAE,YAAM,MAAM,MAAM,MAAM;AAAA,MAAC,CAAC;AAAG,YAAM,IAAI,QAAQ,aAAW;AAAE,eAAO,MAAM,OAAO;AAAG,eAAO,oBAAoB;AAAA,MAAG,CAAC;AAAA,IAAG;AAAA,EAAE;AAChJ;",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,8 @@ import {
5
5
  } from "./chunk-MCHV2R25.js";
6
6
 
7
7
  // src/telemetry.mjs
8
+ import { createHash, randomUUID } from "node:crypto";
9
+ import { assistantStreamFirstTokenTime } from "@deepseek-ai/dsh-llm";
8
10
  import { readFile } from "node:fs/promises";
9
11
  var empty = () => ({ requests: 0, failures: 0, input: 0, output: 0, tokens: 0, unmetered: 0 });
10
12
  var day = (at) => new Date(at).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
@@ -101,7 +103,11 @@ var Telemetry = class {
101
103
  output,
102
104
  tokens: measured(event.usage?.total_tokens) ?? (input !== null && output !== null ? input + output : null),
103
105
  cachedInput: measured(event.usage?.prompt_tokens_details?.cached_tokens),
104
- reasoningOutput: measured(event.usage?.completion_tokens_details?.reasoning_tokens)
106
+ reasoningOutput: measured(event.usage?.completion_tokens_details?.reasoning_tokens),
107
+ callId: event.callId ?? null,
108
+ session: event.session ?? null,
109
+ timing: event.timing ? Object.fromEntries(["modelMs", "ttftMs", "decodeMs", "decodeTokens"].map((k) => [k, measured(event.timing[k])])) : null,
110
+ timingSource: event.timing ? "upstream" : null
105
111
  });
106
112
  const dates = Object.keys(this.data.days).sort();
107
113
  for (const key of dates.slice(0, -31)) delete this.data.days[key];
@@ -119,14 +125,15 @@ var Telemetry = class {
119
125
  return { ...structuredClone(this.data), retentionDays: this.retentionDays, today: day(this.now()), updatedAt: this.now() };
120
126
  }
121
127
  };
122
- function meterResponse(response, complete, signal) {
128
+ function meterResponse(response, complete, signal, { now = () => performance.now(), startedAt = now() } = {}) {
123
129
  if (!response.body) return response;
124
130
  const reader = response.body.getReader(), decoder = new TextDecoder();
125
- let pending = "", usage, ended = false, failed = false, finished = false;
131
+ let pending = "", usage, ended = false, failed = false, finished = false, firstToken = null, terminalAt = null;
126
132
  async function finish(cancelled = false) {
127
133
  if (finished) return;
128
134
  finished = true;
129
- await complete({ usage, ok: ended && !failed && !cancelled, cancelled });
135
+ const end = terminalAt ?? now();
136
+ await complete({ usage, ok: ended && !failed && !cancelled, cancelled, timing: { modelMs: Math.max(0, end - startedAt), ttftMs: firstToken === null ? null : Math.max(0, firstToken - startedAt), decodeMs: firstToken === null ? null : Math.max(0, end - firstToken), decodeTokens: firstToken === null ? null : measured(usage?.completion_tokens) } });
130
137
  }
131
138
  function inspect(bytes) {
132
139
  pending += decoder.decode(bytes, { stream: true });
@@ -137,13 +144,21 @@ function meterResponse(response, complete, signal) {
137
144
  const data = frame.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trimStart()).join("\n");
138
145
  if (data === "[DONE]") {
139
146
  ended = true;
147
+ terminalAt ??= now();
140
148
  continue;
141
149
  }
142
150
  try {
143
151
  const value = JSON.parse(data);
144
152
  if (value.usage && ["prompt_tokens", "completion_tokens", "total_tokens"].some((k) => Number.isFinite(value.usage[k]) && value.usage[k] >= 0)) usage = value.usage;
145
153
  if (value.error) failed = true;
146
- if (value.choices?.some((c) => c.finish_reason)) ended = true;
154
+ if (firstToken === null && value.choices?.some((c) => {
155
+ const d = c.delta;
156
+ return [d?.content, d?.reasoning_content, d?.reasoning].some((v) => typeof v === "string" && v.trim()) || d?.tool_calls?.some((t) => t.function?.name || t.function?.arguments);
157
+ })) firstToken = now();
158
+ if (value.choices?.some((c) => c.finish_reason)) {
159
+ ended = true;
160
+ terminalAt ??= now();
161
+ }
147
162
  } catch {
148
163
  }
149
164
  }
@@ -179,6 +194,66 @@ function meterResponse(response, complete, signal) {
179
194
  });
180
195
  return new Response(body, { status: response.status, headers: response.headers });
181
196
  }
197
+ var SessionMetrics = class {
198
+ constructor(telemetry) {
199
+ this.telemetry = telemetry;
200
+ this.steps = /* @__PURE__ */ new Map();
201
+ }
202
+ key(id) {
203
+ return createHash("sha256").update(String(id)).digest("hex").slice(0, 24);
204
+ }
205
+ beginCall(sessionId, purpose) {
206
+ const callId = randomUUID(), session = sessionId == null ? null : this.key(sessionId);
207
+ const step = session && this.steps.get(session);
208
+ if (step && !purpose) step.callId = callId;
209
+ return { callId, session };
210
+ }
211
+ event(session, event) {
212
+ const key = this.key(session.id), d = event.data;
213
+ if (event.type === "step/start") {
214
+ this.steps.set(key, { turn: d.turn, step: d.step, start: event.time, first: null, calls: /* @__PURE__ */ new Map(), toolMs: 0, toolCount: 0, toolsCompleted: 0 });
215
+ return;
216
+ }
217
+ const step = this.steps.get(key);
218
+ if (!step) return;
219
+ if (d.turn !== void 0 && (d.turn !== step.turn || d.step !== void 0 && d.step !== step.step)) return;
220
+ if (event.type === "assistant/attempt") {
221
+ step.first ??= assistantStreamFirstTokenTime(d.stream) ?? null;
222
+ return;
223
+ }
224
+ const row = this.telemetry.data.events.find((e) => e.callId === step.callId && e.session === key);
225
+ if (event.type === "assistant/message") {
226
+ if (!row || d.interrupted) return;
227
+ const first = step.first ?? assistantStreamFirstTokenTime(d.stream) ?? null;
228
+ const output = measured(d.usage?.outputTokens);
229
+ row.timing = {
230
+ modelMs: Math.max(0, event.time - step.start),
231
+ ttftMs: first === null ? null : Math.max(0, first - step.start),
232
+ decodeMs: first === null || output === null ? null : Math.max(0, event.time - first),
233
+ decodeTokens: first === null ? null : output
234
+ };
235
+ row.timingSource = "dsh-step";
236
+ } else if (event.type === "tool/call" && row?.timingSource === "dsh-step") {
237
+ if (!step.calls.has(d.callId)) {
238
+ step.calls.set(d.callId, event.time);
239
+ step.toolCount++;
240
+ }
241
+ } else if (event.type === "tool/result" && step.calls.has(d.message?.source?.callId)) {
242
+ const id = d.message.source.callId;
243
+ step.toolMs += Math.max(0, event.time - step.calls.get(id));
244
+ step.toolsCompleted++;
245
+ step.calls.delete(id);
246
+ } else if (event.type === "step/end" || event.type === "turn/end") {
247
+ this.steps.delete(key);
248
+ } else return;
249
+ if (row?.timingSource !== "dsh-step") return;
250
+ Object.assign(row.timing, { toolMs: step.toolMs, toolCount: step.toolCount, toolsCompleted: step.toolsCompleted });
251
+ return this.telemetry.persist();
252
+ }
253
+ dispose(session) {
254
+ this.steps.delete(this.key(session.id));
255
+ }
256
+ };
182
257
 
183
258
  // src/router.mjs
184
259
  var RouteError = class extends Error {
@@ -303,7 +378,7 @@ var Router = class {
303
378
  if (!attempts.length && ["tokens", "credits"].includes(strategy)) return `\u6240\u6709\u8D26\u53F7\u7684\u672C\u8F6E${strategy === "tokens" ? " Token" : "\u79EF\u5206"}\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6216\u56E0\u7F3A\u5C11\u7528\u91CF\u8BA1\u91CF\u800C\u6682\u505C\u53C2\u4E0E\u8F6E\u6362`;
304
379
  return void 0;
305
380
  }
306
- async chat(input, signal) {
381
+ async chat(input, signal, metadata = {}) {
307
382
  const alias = input.model;
308
383
  if (!Array.isArray(input.messages) || !input.messages.length || input.stream !== true) throw new RouteError(400, "messages_and_stream_true_required");
309
384
  const initialRoute = this.config.routes[alias];
@@ -384,7 +459,7 @@ var Router = class {
384
459
  signal?.throwIfAborted();
385
460
  const { account, target, key } = candidate;
386
461
  if (!available(candidate)) continue;
387
- let result, actualModel;
462
+ let result, actualModel, startedAt;
388
463
  try {
389
464
  const client = await abortable(this.client(account), signal);
390
465
  const catalog = await abortable(client.models(signal), signal);
@@ -406,17 +481,18 @@ var Router = class {
406
481
  const dedupe = `${account.id}:${model.id}`;
407
482
  if (tried.has(dedupe)) continue;
408
483
  tried.add(dedupe);
484
+ startedAt = performance.now();
409
485
  result = await abortable(client.chat({ ...input, model: model.id }, signal), signal);
410
486
  signal?.throwIfAborted();
411
487
  if (result.ok) {
412
488
  this.active.set(alias, key);
413
489
  this.onAttempt({ account: account.id, provider: account.provider, alias, model: model.id, status: 200 });
414
490
  const response = this.telemetry ? meterResponse(result.response, async (summary) => {
415
- await this.telemetry.record({ ...summary, account: account.id, provider: account.provider, alias, model: model.id, accepted: true, status: summary.ok ? 200 : summary.cancelled ? 499 : 502 });
491
+ await this.telemetry.record({ ...summary, ...metadata, account: account.id, provider: account.provider, alias, model: model.id, accepted: true, status: summary.ok ? 200 : summary.cancelled ? 499 : 502 });
416
492
  await this.syncExpiring(alias, route, expiryCandidates, below);
417
493
  if (strategy === "credits" || route.preferExpiringCredits) void this.monitor?.(account).then(() => this.syncExpiring(alias, route, expiryCandidates, below)).catch(() => {
418
494
  });
419
- }, signal) : result.response;
495
+ }, signal, { startedAt }) : result.response;
420
496
  return { response, account: account.id, provider: account.provider };
421
497
  }
422
498
  } catch (error) {
@@ -427,7 +503,7 @@ var Router = class {
427
503
  const status = result.status >= 400 && result.status <= 599 ? result.status : 502;
428
504
  const attempt = { account: account.id, provider: account.provider, status, kind: result.kind };
429
505
  attempts.push(attempt);
430
- await this.telemetry?.record({ ...attempt, alias, model: actualModel, ok: false });
506
+ await this.telemetry?.record({ ...attempt, ...metadata, alias, model: actualModel, ok: false, timing: startedAt === void 0 ? null : { modelMs: Math.max(0, performance.now() - startedAt) } });
431
507
  this.onAttempt({ ...attempt, alias });
432
508
  const retryable = status < 500 || this.config.retryServerErrors;
433
509
  if (!retryable) throw new RouteError(status, "upstream_failed", attempts, "\u4E0A\u6E38\u8FD4\u56DE 5xx \u4E14\u672A\u5F00\u542F\u300C5xx \u548C\u7F51\u7EDC\u9519\u8BEF\u4E5F\u5207\u6362\u8D26\u53F7\u300D\uFF0C\u672C\u6B21\u8BF7\u6C42\u5DF2\u505C\u6B62");
@@ -441,9 +517,10 @@ var Router = class {
441
517
 
442
518
  export {
443
519
  Telemetry,
520
+ SessionMetrics,
444
521
  RouteError,
445
522
  abortable,
446
523
  matchModel,
447
524
  Router
448
525
  };
449
- //# sourceMappingURL=chunk-6ZXEVC3R.js.map
526
+ //# sourceMappingURL=chunk-J3XO7RTF.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/telemetry.mjs", "../src/router.mjs"],
4
+ "sourcesContent": ["import { createHash, randomUUID } from 'node:crypto';\nimport { assistantStreamFirstTokenTime } from '@deepseek-ai/dsh-llm';\nimport { readFile } from 'node:fs/promises';\nimport { writePrivateJson } from './config.mjs';\nconst empty = () => ({ requests: 0, failures: 0, input: 0, output: 0, tokens: 0, unmetered: 0 });\nconst day = at => new Date(at).toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' });\nconst safe = n => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : 0;\nconst measured = n => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : null;\nexport class Telemetry {\n constructor(file, now = Date.now) { this.file = file; this.now = now; this.retentionDays = 30; this.data = { accounts: {}, routes: {}, days: {}, rounds: {}, expiringRounds: {}, events: [] }; this.queue = Promise.resolve(); }\n async load() {\n try { const saved = JSON.parse(await readFile(this.file, 'utf8')); if (saved.version !== 1) throw Error('Unsupported telemetry'); this.data = { ...saved, expiringRounds: saved.expiringRounds ?? {} }; }\n catch (e) { if (e.code !== 'ENOENT') throw e; }\n if (this.prune()) await this.persist();\n }\n configure(retentionDays) { this.retentionDays = retentionDays; }\n prune() {\n const cutoff = this.now() - this.retentionDays * 86400000, before = this.data.events.length;\n this.data.events = this.data.events.filter(e => Number.isFinite(e.at) && e.at >= cutoff);\n return before !== this.data.events.length;\n }\n row(group, key) { if (!Object.hasOwn(this.data[group], key)) Object.defineProperty(this.data[group], key, { value: empty(), enumerable: true, writable: true }); return this.data[group][key]; }\n count(alias, account) { return this.row('routes', JSON.stringify([alias, account])); }\n expiring(alias) {\n if (!Object.hasOwn(this.data.expiringRounds, alias)) Object.defineProperty(this.data.expiringRounds, alias, { value: { accounts: [], completed: [], cooldownUntil: 0, number: 1 }, enumerable: true, writable: true });\n return this.data.expiringRounds[alias];\n }\n restartExpiring(alias) { const row = this.expiring(alias); row.accounts = []; row.completed = []; row.cooldownUntil = 0; row.number++; return this.persist(); }\n round(alias, signature) {\n if (!Object.hasOwn(this.data.rounds, alias) || this.data.rounds[alias].signature !== signature) Object.defineProperty(this.data.rounds, alias, { value: { signature, number: 1, bases: {} }, enumerable: true, writable: true });\n return this.data.rounds[alias];\n }\n advance(alias, candidates, signature) {\n const round = this.round(alias, signature); round.number++;\n for (const c of candidates) Object.defineProperty(round.bases, c.account.id, { value: { tokens: this.count(alias, c.account.id).tokens, credits: this.row('accounts', c.account.id).creditsObserved ?? 0 }, enumerable: true, writable: true, configurable: true });\n return this.persist();\n }\n observe(account, snapshot) {\n const row = this.row('accounts', account);\n if (Number.isFinite(snapshot.remaining) && snapshot.unit === 'credits') {\n const sameCycle = row.balance?.cycle === snapshot.cycle;\n const change = sameCycle && Number.isFinite(row.balance?.remaining) ? Math.max(0, row.balance.remaining - snapshot.remaining) : 0;\n row.creditsObserved = (row.creditsObserved ?? 0) + change;\n }\n row.balance = snapshot;\n return this.persist();\n }\n record(event) {\n const at = this.now();\n const rows = [this.row('accounts', event.account), this.count(event.alias, event.account), this.row('days', day(at))];\n for (const row of rows) {\n row.requests++; if (!event.ok) row.failures++;\n if (event.usage) { const input = safe(event.usage.prompt_tokens), output = safe(event.usage.completion_tokens); row.input += input; row.output += output; row.tokens += safe(event.usage.total_tokens) || input + output; }\n else if (event.status === 200 || event.accepted) row.unmetered++;\n }\n const input = measured(event.usage?.prompt_tokens), output = measured(event.usage?.completion_tokens);\n this.data.events.unshift({ at, account: event.account, provider: event.provider, alias: event.alias, model: event.model ?? null, status: event.status,\n outcome: event.ok ? 'completed' : event.cancelled ? 'cancelled' : 'failed', input, output,\n tokens: measured(event.usage?.total_tokens) ?? (input !== null && output !== null ? input + output : null),\n cachedInput: measured(event.usage?.prompt_tokens_details?.cached_tokens), reasoningOutput: measured(event.usage?.completion_tokens_details?.reasoning_tokens),\n callId: event.callId ?? null, session: event.session ?? null,\n timing: event.timing ? Object.fromEntries(['modelMs', 'ttftMs', 'decodeMs', 'decodeTokens'].map(k => [k, measured(event.timing[k])])) : null, timingSource: event.timing ? 'upstream' : null });\n const dates = Object.keys(this.data.days).sort(); for (const key of dates.slice(0, -31)) delete this.data.days[key];\n return this.persist();\n }\n persist() { this.prune(); const saved = structuredClone({ ...this.data, version: 1 }); this.queue = this.queue.catch(() => {}).then(() => writePrivateJson(this.file, saved)); return this.queue; }\n state() { this.prune(); return { ...structuredClone(this.data), retentionDays: this.retentionDays, today: day(this.now()), updatedAt: this.now() }; }\n}\n\n// Pass bytes through unchanged; parse only usage and terminal/error markers, never persist content.\nexport function meterResponse(response, complete, signal, { now = () => performance.now(), startedAt = now() } = {}) {\n if (!response.body) return response;\n const reader = response.body.getReader(), decoder = new TextDecoder();\n let pending = '', usage, ended = false, failed = false, finished = false, firstToken = null, terminalAt = null;\n async function finish(cancelled = false) { if (finished) return; finished = true; const end = terminalAt ?? now(); await complete({ usage, ok: ended && !failed && !cancelled, cancelled, timing: { modelMs: Math.max(0, end - startedAt), ttftMs: firstToken === null ? null : Math.max(0, firstToken - startedAt), decodeMs: firstToken === null ? null : Math.max(0, end - firstToken), decodeTokens: firstToken === null ? null : measured(usage?.completion_tokens) } }); }\n function inspect(bytes) {\n pending += decoder.decode(bytes, { stream: true });\n let split;\n while ((split = /\\r?\\n\\r?\\n/.exec(pending))) {\n const frame = pending.slice(0, split.index); pending = pending.slice(split.index + split[0].length);\n const data = frame.split(/\\r?\\n/).filter(l => l.startsWith('data:')).map(l => l.slice(5).trimStart()).join('\\n');\n if (data === '[DONE]') { ended = true; terminalAt ??= now(); continue; }\n try { const value = JSON.parse(data); if (value.usage && ['prompt_tokens', 'completion_tokens', 'total_tokens'].some(k => Number.isFinite(value.usage[k]) && value.usage[k] >= 0)) usage = value.usage; if (value.error) failed = true; if (firstToken === null && value.choices?.some(c => { const d = c.delta; return [d?.content, d?.reasoning_content, d?.reasoning].some(v => typeof v === 'string' && v.trim()) || d?.tool_calls?.some(t => t.function?.name || t.function?.arguments); })) firstToken = now(); if (value.choices?.some(c => c.finish_reason)) { ended = true; terminalAt ??= now(); } } catch { /* Unknown frame is forwarded. */ }\n }\n if (pending.length > 1024 * 1024) { pending = ''; failed = true; }\n }\n const body = new ReadableStream({\n async pull(controller) {\n try { signal?.throwIfAborted(); const chunk = await reader.read(); if (chunk.done) { await finish(); controller.close(); } else { inspect(chunk.value); controller.enqueue(chunk.value); } }\n catch (e) { await finish(signal?.aborted); controller.error(e); await reader.cancel().catch(() => {}); }\n },\n async cancel(reason) { await reader.cancel(reason).catch(() => {}); await finish(!ended); },\n });\n return new Response(body, { status: response.status, headers: response.headers });\n}\n\n// Link local transport attempts to DSH steps without retaining titles, prompts,\n// tool names/arguments/results, or the original session identity.\nexport class SessionMetrics {\n constructor(telemetry) { this.telemetry = telemetry; this.steps = new Map(); }\n key(id) { return createHash('sha256').update(String(id)).digest('hex').slice(0, 24); }\n beginCall(sessionId, purpose) {\n const callId = randomUUID(), session = sessionId == null ? null : this.key(sessionId);\n const step = session && this.steps.get(session);\n if (step && !purpose) step.callId = callId;\n return { callId, session };\n }\n event(session, event) {\n const key = this.key(session.id), d = event.data;\n if (event.type === 'step/start') { this.steps.set(key, { turn: d.turn, step: d.step, start: event.time, first: null, calls: new Map(), toolMs: 0, toolCount: 0, toolsCompleted: 0 }); return; }\n const step = this.steps.get(key);\n if (!step) return;\n if (d.turn !== undefined && (d.turn !== step.turn || d.step !== undefined && d.step !== step.step)) return;\n if (event.type === 'assistant/attempt') { step.first ??= assistantStreamFirstTokenTime(d.stream) ?? null; return; }\n const row = this.telemetry.data.events.find(e => e.callId === step.callId && e.session === key);\n if (event.type === 'assistant/message') {\n if (!row || d.interrupted) return;\n const first = step.first ?? assistantStreamFirstTokenTime(d.stream) ?? null;\n const output = measured(d.usage?.outputTokens);\n row.timing = { modelMs: Math.max(0, event.time - step.start), ttftMs: first === null ? null : Math.max(0, first - step.start),\n decodeMs: first === null || output === null ? null : Math.max(0, event.time - first), decodeTokens: first === null ? null : output };\n row.timingSource = 'dsh-step';\n } else if (event.type === 'tool/call' && row?.timingSource === 'dsh-step') {\n if (!step.calls.has(d.callId)) { step.calls.set(d.callId, event.time); step.toolCount++; }\n } else if (event.type === 'tool/result' && step.calls.has(d.message?.source?.callId)) {\n const id = d.message.source.callId;\n step.toolMs += Math.max(0, event.time - step.calls.get(id)); step.toolsCompleted++; step.calls.delete(id);\n } else if (event.type === 'step/end' || event.type === 'turn/end') {\n this.steps.delete(key);\n } else return;\n if (row?.timingSource !== 'dsh-step') return;\n Object.assign(row.timing, { toolMs: step.toolMs, toolCount: step.toolCount, toolsCompleted: step.toolsCompleted });\n return this.telemetry.persist();\n }\n dispose(session) { this.steps.delete(this.key(session.id)); }\n}\n", "import { meterResponse } from './telemetry.mjs';\nimport { isFreeModel } from './config.mjs';\nimport { reasoningModes } from './reasoning.mjs';\nexport class RouteError extends Error {\n constructor(status, code, attempts = [], hint) {\n super(`${code}${attempts.length ? ': ' + attempts.map(a => `${a.account} HTTP ${a.status}`).join(', ') : ''}${hint ? `\uFF08${hint}\uFF09` : ''}`);\n this.status = status; this.code = code; this.attempts = attempts; this.hint = hint;\n }\n}\nconst normalize = value => value.toLowerCase().replace(/[^a-z0-9]/g, '').replace(/official$/, '');\nexport function abortable(promise, signal) {\n if (!signal) return promise;\n // Attach handlers even if already aborted, so a late rejection is consumed.\n return new Promise((resolve, reject) => {\n const abort = () => reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n signal.addEventListener('abort', abort, { once: true });\n if (signal.aborted) abort();\n Promise.resolve(promise).then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));\n });\n}\nexport function matchModel(alias, target, catalog, effort, provider = target.provider) {\n const allowed = m => m && (effort === undefined || reasoningModes(m, provider).includes(effort));\n if (target.model) return catalog.find(m => m.id === target.model && allowed(m));\n if (target.models) return target.models.map(id => catalog.find(m => m.id === id)).find(allowed);\n if (alias === 'ds4.1f') return catalog.find(m => allowed(m) && [m.id, m.name ?? ''].some(x => normalize(x) === 'deepseekv41flash'));\n}\n\nexport class Router {\n constructor(config, createClient, onAttempt = () => {}, now = Date.now) {\n this.config = config; this.createClient = createClient; this.onAttempt = onAttempt; this.now = now;\n this.telemetry = null; this.scores = new Map(); this.monitor = null; this.clients = new Map(); this.cooldowns = new Map(); this.active = new Map();\n }\n updateConfig(config) {\n for (const old of this.config.accounts) {\n const next = config.accounts.find(a => a.id === old.id);\n if (!next || next.provider !== old.provider || next.credentialFile !== old.credentialFile || next.identityFile !== old.identityFile) this.clients.delete(old.id);\n }\n this.config = config;\n for (const alias of this.active.keys()) if (!Object.hasOwn(config.routes, alias)) this.active.delete(alias);\n }\n async client(account) {\n if (!this.clients.has(account.id)) this.clients.set(account.id, Promise.resolve().then(() => this.createClient(account)));\n try { return await this.clients.get(account.id); }\n catch (error) { this.clients.delete(account.id); throw error; }\n }\n status() {\n return this.config.accounts.map(a => ({ id: a.id, provider: a.provider, enabled: a.enabled !== false,\n cooldowns: [...this.cooldowns].filter(([key, time]) => key.startsWith(`${a.id}:`) && time > this.now())\n .map(([key, time]) => ({ route: key.slice(a.id.length + 1), remainingMs: time - this.now() })),\n activeFor: [...this.active].filter(([, key]) => key.split(':')[0] === a.id).map(([alias]) => alias) }));\n }\n candidates(alias) {\n const route = this.config.routes[alias];\n if (!Object.hasOwn(this.config.routes, alias)) throw new RouteError(404, 'unknown_model', [], '\u63D2\u4EF6\u8DEF\u7531\u8868\u4E2D\u6CA1\u6709\u8BE5\u8C03\u7528\u540D\u79F0\uFF0C\u8BF7\u5728\u914D\u7F6E\u9875\u65B0\u5EFA\u6216\u6062\u590D\u5BF9\u5E94\u8DEF\u7531');\n const candidates = route.targets.flatMap((target, index) => this.config.accounts\n .filter(a => a.enabled !== false && a.provider === target.provider && (!target.account || a.id === target.account))\n .flatMap(account => route.freeOnly ? (this.freeModels?.(account) ?? []).filter(m => (!target.model || m.id === target.model) && (!target.models || target.models.includes(m.id)))\n .map(model => ({ account, target: { ...target, model: model.id, models: undefined }, key: `${account.id}:${index}:${model.id}` }))\n : [{ account, target, key: `${account.id}:${index}` }]));\n const active = candidates.findIndex(c => c.key === this.active.get(alias));\n return active < 0 ? candidates : [...candidates.slice(active), ...candidates.slice(0, active)];\n }\n expiringAt(account, route) {\n const balance = this.telemetry?.row('accounts', account.id).balance;\n if (!balance || balance.error || balance.unit !== 'credits' || this.now() - balance.fetchedAt > 120000) return Infinity;\n const horizon = this.now() + (route.expiringWithinDays ?? 7) * 86400000;\n return Math.min(Infinity, ...(balance.packages ?? []).filter(p => p.remaining > 0 && !p.unlimited && p.expiresAt > this.now() && p.expiresAt <= horizon).map(p => p.expiresAt));\n }\n async syncExpiring(alias, route, candidates, below = () => true) {\n if (!route.preferExpiringCredits || !this.telemetry) return false;\n const round = this.telemetry.expiring(alias);\n if (round.cooldownUntil > this.now()) return false;\n const before = JSON.stringify(round);\n if (round.cooldownUntil) { round.accounts = []; round.completed = []; round.cooldownUntil = 0; round.number++; }\n for (const id of round.accounts) {\n const matches = candidates.filter(c => c.account.id === id), balance = this.telemetry.row('accounts', id).balance;\n const fresh = balance && !balance.error && balance.unit === 'credits' && this.now() - balance.fetchedAt <= 120000;\n if (!matches.length || matches.every(c => !below(c)) || (fresh && !Number.isFinite(this.expiringAt({ id }, route)))) {\n if (!round.completed.includes(id)) round.completed.push(id);\n }\n }\n for (const c of candidates) if (below(c) && Number.isFinite(this.expiringAt(c.account, route)) && !round.accounts.includes(c.account.id)) round.accounts.push(c.account.id);\n // Cooling applies only to expiring-credit priority; ordinary routing keeps running.\n if (round.accounts.length && round.accounts.every(id => round.completed.includes(id))) round.cooldownUntil = this.now() + 3 * 86400000;\n if (JSON.stringify(round) !== before) await this.telemetry.persist();\n return round.cooldownUntil <= this.now();\n }\n remainingCooldown(candidate, alias, route) {\n const id = candidate.account.id;\n const keys = [`${id}:*`, `${id}:${alias}`];\n if (route.freeOnly) keys.push(`${id}:${alias}:${candidate.target.model}`);\n return Math.max(0, ...keys.map(key => (this.cooldowns.get(key) ?? 0) - this.now()));\n }\n exhaustHint({ alias, route, strategy, candidates, expiryCandidates, attempts }) {\n const kinds = new Set(attempts.map(a => a.kind));\n const modelOnly = attempts.length > 0 && [...kinds].every(k => ['model_unavailable', 'reasoning_effort_unavailable', 'model_no_longer_free'].includes(k));\n const cooling = candidates.map(c => this.remainingCooldown(c, alias, route)).filter(ms => ms > 0);\n const soonest = cooling.length ? Math.min(...cooling) : undefined;\n const countdown = soonest === undefined ? undefined : `\u7EA6 ${Math.max(1, Math.ceil(soonest / 1000))} \u79D2\u540E\u53EF\u91CD\u8BD5`;\n if (!expiryCandidates.length) return route.freeOnly\n ? '\u8BE5\u8DEF\u7531\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u514D\u8D39\u6A21\u578B\uFF1A\u53EF\u80FD\u5DF2\u6062\u590D\u6536\u8D39\u3001\u9650\u514D\u5230\u671F\u6216\u76EE\u5F55\u83B7\u53D6\u5931\u8D25'\n : '\u8BE5\u8DEF\u7531\u6CA1\u6709\u53EF\u7528\u7684\u8D26\u53F7\u76EE\u6807\uFF1A\u8D26\u53F7\u53EF\u80FD\u5DF2\u5168\u90E8\u7981\u7528\uFF0C\u6216\u4E0E\u76EE\u6807\u6E20\u9053\u4E0D\u5339\u914D';\n if (modelOnly) return `\u5DF2\u5C1D\u8BD5\u7684\u8D26\u53F7\u76EE\u5F55\u4E2D\u90FD\u6CA1\u6709\u8BE5\u6A21\u578B\u3001\u6240\u9009\u63A8\u7406\u6863\u4F4D\u6216\u514D\u8D39\u8D44\u683C\u4E0D\u6EE1\u8DB3${countdown ? `\uFF1B\u5176\u4F59\u8D26\u53F7\u51B7\u5374\u4E2D\uFF0C${countdown}` : ''}`;\n if (soonest !== undefined && cooling.length >= candidates.length) {\n return attempts.length ? `\u6240\u6709\u5019\u9009\u8D26\u53F7\u672C\u6B21\u8C03\u7528\u5747\u5931\u8D25\u5E76\u5DF2\u8FDB\u5165\u51B7\u5374\uFF0C${countdown}` : `\u5168\u90E8\u8D26\u53F7\u90FD\u5728\u5931\u8D25\u51B7\u5374\u4E2D\uFF0C${countdown}`;\n }\n if (soonest !== undefined) return `\u90E8\u5206\u8D26\u53F7\u4ECD\u5728\u5931\u8D25\u51B7\u5374\u4E2D\uFF0C${countdown}`;\n if (!attempts.length && ['tokens', 'credits'].includes(strategy)) return `\u6240\u6709\u8D26\u53F7\u7684\u672C\u8F6E${strategy === 'tokens' ? ' Token' : '\u79EF\u5206'}\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6216\u56E0\u7F3A\u5C11\u7528\u91CF\u8BA1\u91CF\u800C\u6682\u505C\u53C2\u4E0E\u8F6E\u6362`;\n return undefined;\n }\n async chat(input, signal, metadata = {}) {\n const alias = input.model;\n if (!Array.isArray(input.messages) || !input.messages.length || input.stream !== true) throw new RouteError(400, 'messages_and_stream_true_required');\n const initialRoute = this.config.routes[alias];\n if (initialRoute?.freeOnly) {\n await abortable(this.prepareFree?.(), signal);\n if (!this.config.routes[alias]) throw new RouteError(503, 'free_model_pool_unavailable', [], '\u672C\u8DEF\u7531\u4ECA\u5929\u6CA1\u6709\u53EF\u7528\u7684\u514D\u8D39\u6A21\u578B');\n }\n const effort = Object.hasOwn(input, 'reasoning_effort') ? input.reasoning_effort : this.config.routes[alias]?.reasoningEffort;\n input = { ...input }; delete input.reasoning_effort;\n if (effort !== undefined && effort !== null) input.reasoning_effort = effort;\n let candidates = this.candidates(alias);\n const expiryCandidates = [...candidates];\n const route = this.config.routes[alias], strategy = route.strategy ?? 'failover';\n const available = c => (this.cooldowns.get(`${c.account.id}:*`) ?? 0) <= this.now() && (this.cooldowns.get(`${c.account.id}:${alias}`) ?? 0) <= this.now()\n && (!route.freeOnly || (this.cooldowns.get(`${c.account.id}:${alias}:${c.target.model}`) ?? 0) <= this.now());\n if (route.preferExpiringCredits && this.monitor) {\n await Promise.all([...new Map(candidates.filter(available).map(c => [c.account.id, c.account])).values()].map(a => {\n const snapshot = this.telemetry?.row('accounts', a.id).balance;\n return !snapshot || snapshot.error || this.now() - snapshot.fetchedAt > 60000 ? this.monitor(a) : undefined;\n }));\n signal?.throwIfAborted();\n }\n if (strategy === 'credits') {\n await Promise.all([...new Map(candidates.filter(available).map(c => [c.account.id, c.account])).values()].map(a => this.monitor?.(a)));\n signal?.throwIfAborted();\n candidates = candidates.filter(c => { const balance = this.telemetry?.row('accounts', c.account.id).balance; return balance?.unit === 'credits' && Number.isFinite(balance.remaining) && this.now() - balance.fetchedAt < 120000 && !balance.error; });\n if (!candidates.length) throw new RouteError(503, 'credit_usage_unavailable', [], '\u65E0\u6CD5\u83B7\u53D6\u4EFB\u4F55\u5019\u9009\u8D26\u53F7\u7684\u79EF\u5206\u4F59\u989D\u6570\u636E');\n }\n const signature = JSON.stringify({ strategy, targets: route.targets });\n let below = () => true, preferActive;\n if (this.telemetry && ['tokens', 'credits'].includes(strategy)) {\n const round = this.telemetry.round(alias, signature);\n const counters = c => ({ tokens: this.telemetry.count(alias, c.account.id).tokens, credits: this.telemetry.row('accounts', c.account.id).creditsObserved ?? 0 });\n for (const c of candidates) if (!Object.hasOwn(round.bases, c.account.id)) Object.defineProperty(round.bases, c.account.id, { value: counters(c), enumerable: true, writable: true, configurable: true });\n below = c => !round.bases[c.account.id] || counters(c)[strategy] - round.bases[c.account.id][strategy] < (strategy === 'tokens' ? c.target.tokenBudget : c.target.creditBudget);\n preferActive = await this.syncExpiring(alias, route, expiryCandidates, below);\n // Start another round only when every configured, metered candidate has used its budget.\n if (candidates.length && candidates.every(c => !below(c))) {\n await this.telemetry.advance(alias, candidates, signature);\n this.active.delete(alias);\n const allowed = new Set(candidates.map(c => c.key));\n candidates = this.candidates(alias).filter(c => allowed.has(c.key));\n }\n candidates = candidates.filter(below);\n if (strategy === 'tokens' && candidates.some(c => this.telemetry.count(alias, c.account.id).unmetered > 0)) {\n // A missing upstream usage marker cannot silently count as zero forever.\n candidates = candidates.filter(c => this.telemetry.count(alias, c.account.id).unmetered === 0);\n }\n }\n preferActive ??= await this.syncExpiring(alias, route, expiryCandidates, below);\n const expiry = c => preferActive && !this.telemetry.expiring(alias).completed.includes(c.account.id) ? this.expiringAt(c.account, route) : Infinity;\n if (preferActive) candidates.sort((a, b) => expiry(a) - expiry(b));\n if (strategy === 'weighted') {\n let eligible = candidates.filter(available);\n const nearest = Math.min(Infinity, ...eligible.map(expiry));\n if (Number.isFinite(nearest)) eligible = eligible.filter(c => expiry(c) === nearest);\n if (eligible.length) {\n let scores = this.scores.get(alias);\n if (scores?.signature !== signature) { scores = { signature, values: new Map() }; this.scores.set(alias, scores); }\n let total = 0, winner;\n for (const c of eligible) { const weight = c.target.weight ?? 1; total += weight; scores.values.set(c.key, (scores.values.get(c.key) ?? 0) + weight); if (!winner || scores.values.get(c.key) > scores.values.get(winner.key)) winner = c; }\n scores.values.set(winner.key, scores.values.get(winner.key) - total);\n candidates = [winner, ...candidates.filter(c => c !== winner)];\n }\n }\n const attempts = [];\n // Each account/model target is attempted once, even when configuration repeats it.\n const tried = new Set();\n for (const candidate of candidates) {\n signal?.throwIfAborted();\n const { account, target, key } = candidate;\n if (!available(candidate)) continue;\n let result, actualModel, startedAt;\n try {\n const client = await abortable(this.client(account), signal);\n const catalog = await abortable(client.models(signal), signal);\n const model = matchModel(alias, target, catalog, effort ?? undefined);\n if (!model) { attempts.push({ account: account.id, provider: account.provider, status: 404,\n kind: matchModel(alias, target, catalog) ? 'reasoning_effort_unavailable' : 'model_unavailable' }); continue; }\n if (route.freeOnly && !isFreeModel(model, this.now())) { attempts.push({ account: account.id, provider: account.provider, status: 404, kind: 'model_no_longer_free' }); continue; }\n actualModel = model.id;\n const dedupe = `${account.id}:${model.id}`;\n if (tried.has(dedupe)) continue;\n tried.add(dedupe);\n startedAt = performance.now();\n result = await abortable(client.chat({ ...input, model: model.id }, signal), signal);\n signal?.throwIfAborted();\n if (result.ok) {\n this.active.set(alias, key);\n this.onAttempt({ account: account.id, provider: account.provider, alias, model: model.id, status: 200 });\n const response = this.telemetry ? meterResponse(result.response, async summary => {\n await this.telemetry.record({ ...summary, ...metadata, account: account.id, provider: account.provider, alias, model: model.id, accepted: true, status: summary.ok ? 200 : summary.cancelled ? 499 : 502 });\n await this.syncExpiring(alias, route, expiryCandidates, below);\n if (strategy === 'credits' || route.preferExpiringCredits) void this.monitor?.(account).then(() => this.syncExpiring(alias, route, expiryCandidates, below)).catch(() => {});\n }, signal, { startedAt }) : result.response;\n return { response, account: account.id, provider: account.provider };\n }\n } catch (error) {\n signal?.throwIfAborted();\n const reported = error.status ?? error.failure?.status ?? /\\bhttp\\s+(\\d{3})\\b/i.exec(error.message ?? '')?.[1];\n result = { ok: false, status: Number(reported) || 502, kind: error.code ?? error.failure?.code ?? 'transport' };\n }\n const status = result.status >= 400 && result.status <= 599 ? result.status : 502;\n const attempt = { account: account.id, provider: account.provider, status, kind: result.kind };\n attempts.push(attempt);\n await this.telemetry?.record({ ...attempt, ...metadata, alias, model: actualModel, ok: false, timing: startedAt === undefined ? null : { modelMs: Math.max(0, performance.now() - startedAt) } });\n // Upstream messages can contain credentials or request text: log metadata only.\n this.onAttempt({ ...attempt, alias });\n const retryable = status < 500 || this.config.retryServerErrors;\n if (!retryable) throw new RouteError(status, 'upstream_failed', attempts, '\u4E0A\u6E38\u8FD4\u56DE 5xx \u4E14\u672A\u5F00\u542F\u300C5xx \u548C\u7F51\u7EDC\u9519\u8BEF\u4E5F\u5207\u6362\u8D26\u53F7\u300D\uFF0C\u672C\u6B21\u8BF7\u6C42\u5DF2\u505C\u6B62');\n const scope = [401, 402, 429].includes(status) ? '*' : route.freeOnly && actualModel ? `${alias}:${actualModel}` : alias;\n this.cooldowns.set(`${account.id}:${scope}`, this.now() + this.config.cooldownMs);\n }\n const hint = this.exhaustHint({ alias, route, strategy, candidates, expiryCandidates, attempts });\n throw new RouteError(503, route.freeOnly ? 'free_model_pool_unavailable' : 'account_pool_exhausted', attempts, hint);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["input", "output"]
7
+ }
@@ -12,7 +12,7 @@ import {
12
12
  import {
13
13
  RouteError,
14
14
  abortable
15
- } from "./chunk-6ZXEVC3R.js";
15
+ } from "./chunk-J3XO7RTF.js";
16
16
  import {
17
17
  beijingDay,
18
18
  writePrivateJson
@@ -5393,7 +5393,7 @@ function workbuddyToken(token) {
5393
5393
  try {
5394
5394
  const [head, body, signature, extra] = token.split(".");
5395
5395
  const header = JSON.parse(Buffer.from(head, "base64url")), payload = JSON.parse(Buffer.from(body, "base64url"));
5396
- if (extra || !signature || header.alg !== "RS256" || payload.iss !== "https://www.codebuddy.cn/auth/realms/copilot" || typeof payload.sub !== "string" || !payload.sub) throw Error();
5396
+ if (extra || !signature || header.alg !== "RS256" || !["https://www.codebuddy.cn/auth/realms/copilot", "https://www.workbuddy.cn/auth/realms/copilot"].includes(payload.iss) || typeof payload.sub !== "string" || !payload.sub) throw Error();
5397
5397
  return payload;
5398
5398
  } catch {
5399
5399
  throw Object.assign(Error("HTTP 401 invalid_workbuddy_cn_token"), { status: 401 });
@@ -5402,7 +5402,7 @@ function workbuddyToken(token) {
5402
5402
  async function workbuddyPost(url, headers, signal, fetchImpl) {
5403
5403
  let response;
5404
5404
  try {
5405
- response = await fetchImpl(url, { method: "POST", headers: { "Content-Type": "application/json", "Accept-Encoding": "identity", ...headers }, body: "{}", signal });
5405
+ response = await fetchImpl(url, { method: "POST", headers: { "Content-Type": "application/json", "Accept-Encoding": "identity", ...headers }, body: "{}", signal, redirect: "error" });
5406
5406
  } catch {
5407
5407
  throw Object.assign(Error("workbuddy_checkin_network"), { transient: !signal?.aborted });
5408
5408
  }
@@ -5446,6 +5446,11 @@ function createWorkBuddyCheckin(credential, { fetchImpl = (...args) => fetch(...
5446
5446
  };
5447
5447
  return {
5448
5448
  identity: async () => ({ account: (await login()).uid }),
5449
+ validate: async (signal) => {
5450
+ const state = await post("checkin-activity-status", await login(), signal);
5451
+ if (![0, 200].includes(state?.code) || !state.data || typeof state.data.today_checked_in !== "boolean") throw Error("workbuddy_status_invalid");
5452
+ return { valid: true };
5453
+ },
5449
5454
  claim: async (signal) => {
5450
5455
  let submitted = false;
5451
5456
  try {
@@ -5491,6 +5496,7 @@ function createWorkBuddyCheckin(credential, { fetchImpl = (...args) => fetch(...
5491
5496
 
5492
5497
  export {
5493
5498
  qoderPluginDataDir,
5499
+ readLimitedText,
5494
5500
  supportsCheckin,
5495
5501
  Checkins,
5496
5502
  jsonCredential,
@@ -5498,7 +5504,8 @@ export {
5498
5504
  createAccountClient,
5499
5505
  claimTraeCheckin,
5500
5506
  createQoderCheckin,
5507
+ workbuddyToken,
5501
5508
  refreshWorkBuddyCheckinCredential,
5502
5509
  createWorkBuddyCheckin
5503
5510
  };
5504
- //# sourceMappingURL=chunk-LADXRFL7.js.map
5511
+ //# sourceMappingURL=chunk-U2MU2M5C.js.map