mslxdff 0.1.203 → 0.1.208

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 (332) hide show
  1. package/README.md +3 -3
  2. package/bin/mslxdff.js +1 -1
  3. package/docs/cli_help_mini.md +147 -0
  4. package/package.json +38 -38
  5. package/plugins/README.md +14 -14
  6. package/plugins/prefer-model.mjs.example +14 -14
  7. package/src/anthropic/canonical-json.js +55 -0
  8. package/src/anthropic/count-tokens.js +61 -61
  9. package/src/anthropic/request-body.js +183 -126
  10. package/src/anthropic/request-shape.js +60 -0
  11. package/src/anthropic/session-id.js +58 -0
  12. package/src/anthropic/shared.js +18 -18
  13. package/src/anthropic/sse-aggregate.js +56 -56
  14. package/src/anthropic/tool-media.js +75 -0
  15. package/src/anthropic/translate.js +296 -287
  16. package/src/anthropic/web-search.js +361 -361
  17. package/src/api-kind.js +31 -31
  18. package/src/autostart.js +288 -288
  19. package/src/bench/probe.js +1 -1
  20. package/src/bench/runner.js +1 -1
  21. package/src/bench/via-probe.js +1 -1
  22. package/src/bench/via.js +144 -144
  23. package/src/bench/workbuddy-bench.js +1 -1
  24. package/src/chat/config.js +11 -11
  25. package/src/chat/cooling.js +81 -81
  26. package/src/chat/direct.js +58 -58
  27. package/src/chat/index.js +6 -6
  28. package/src/chat/spinner.js +38 -38
  29. package/src/chat/sse.js +69 -69
  30. package/src/chat/stats.js +247 -247
  31. package/src/chat/store.js +48 -48
  32. package/src/chat/tool-handlers.js +67 -67
  33. package/src/chat/tools.js +1 -1
  34. package/src/chat/upstream.js +59 -59
  35. package/src/chat-pipeline/auto-race.js +158 -158
  36. package/src/chat-pipeline/empty-turn.js +147 -147
  37. package/src/chat-pipeline/engine.js +17 -17
  38. package/src/chat-pipeline/index.js +8 -40
  39. package/src/chat-pipeline/serial-trial.js +279 -283
  40. package/src/chat-pipeline/terminal-verdict.js +132 -147
  41. package/src/chooser.js +54 -54
  42. package/src/claude-desktop/apply.js +101 -101
  43. package/src/claude-desktop/context1m.js +38 -38
  44. package/src/claude-desktop/doctor.js +146 -146
  45. package/src/claude-desktop/slots.js +152 -152
  46. package/src/cli/bootstrap.js +1 -1
  47. package/src/cli/commands/claude-desktop.js +284 -284
  48. package/src/cli/commands/daemon.js +231 -231
  49. package/src/cli/commands/model/list-live.js +22 -22
  50. package/src/cli/commands/model/list-providers.js +76 -76
  51. package/src/cli/commands/model/list-render.js +77 -77
  52. package/src/cli/commands/model/list-sort.js +52 -52
  53. package/src/cli/commands/model/list-tty.js +76 -76
  54. package/src/cli/commands/model/list.js +166 -166
  55. package/src/cli/commands/model/live-models.js +24 -24
  56. package/src/cli/commands/model/stats.js +43 -43
  57. package/src/cli/commands/model/status.js +47 -47
  58. package/src/cli/commands/model.js +51 -51
  59. package/src/cli/commands/provider/add.js +108 -108
  60. package/src/cli/commands/provider/allowlist.js +99 -99
  61. package/src/cli/commands/provider/cline-free.js +99 -99
  62. package/src/cli/commands/provider/cline-login.js +218 -218
  63. package/src/cli/commands/provider/cline-quota.js +65 -65
  64. package/src/cli/commands/provider/codearts-login.js +45 -45
  65. package/src/cli/commands/provider/config.js +119 -119
  66. package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
  67. package/src/cli/commands/provider/keys-guard.js +50 -50
  68. package/src/cli/commands/provider/keys.js +161 -161
  69. package/src/cli/commands/provider/qoder-checkin.js +81 -81
  70. package/src/cli/commands/provider/qoder-login.js +102 -102
  71. package/src/cli/commands/provider/raccoon-login.js +180 -180
  72. package/src/cli/commands/provider/raccoon-password.js +162 -162
  73. package/src/cli/commands/provider/raccoon-quota.js +109 -109
  74. package/src/cli/commands/provider/traework-checkin.js +144 -144
  75. package/src/cli/commands/provider/traework-login.js +164 -164
  76. package/src/cli/commands/provider/workbuddy-login.js +153 -153
  77. package/src/cli/commands/provider/zcode-login.js +77 -77
  78. package/src/cli/commands/provider/zcode-quota.js +55 -55
  79. package/src/cli/commands/provider.js +1 -1
  80. package/src/cli/commands/stats.js +158 -149
  81. package/src/cli/commands/system.js +261 -261
  82. package/src/cli/commands/timezone.js +47 -47
  83. package/src/cli/commands/use-group.js +60 -60
  84. package/src/cli/commands/v6-pool.js +123 -0
  85. package/src/cli/commands/workbuddy.js +189 -189
  86. package/src/cli/format.js +141 -141
  87. package/src/cli/group-helpers.js +70 -70
  88. package/src/cli/help.js +80 -80
  89. package/src/cli/index.js +73 -70
  90. package/src/cli/interactive.js +121 -121
  91. package/src/cli/join-core.js +99 -99
  92. package/src/cli/join-wizard.js +175 -175
  93. package/src/cli/policy.js +136 -136
  94. package/src/cli/provider-row.js +126 -126
  95. package/src/cli/util.js +24 -24
  96. package/src/compat.js +53 -53
  97. package/src/events.js +38 -38
  98. package/src/free-lane.js +131 -131
  99. package/src/free-watcher.js +63 -63
  100. package/src/groups.js +262 -262
  101. package/src/logs.js +195 -195
  102. package/src/metrics.js +71 -62
  103. package/src/model-capabilities/context-lookup.js +65 -65
  104. package/src/model-capabilities/merge.js +142 -142
  105. package/src/model-trace.js +187 -187
  106. package/src/models.js +1 -1
  107. package/src/opencode-identity.js +72 -72
  108. package/src/peers.js +254 -254
  109. package/src/plugins.js +107 -107
  110. package/src/providers/AGENTS.md +49 -0
  111. package/src/providers/base.js +164 -164
  112. package/src/providers/classify.js +22 -22
  113. package/src/providers/cline/allowlist-sync.js +93 -93
  114. package/src/providers/cline/free-catalog.js +71 -71
  115. package/src/providers/cline/headers.js +37 -37
  116. package/src/providers/cline/index.js +130 -130
  117. package/src/providers/cline/models.js +150 -150
  118. package/src/providers/cline/usage.js +274 -274
  119. package/src/providers/codearts/auth-pool.js +148 -148
  120. package/src/providers/codearts/concurrency.js +34 -34
  121. package/src/providers/codearts/const.js +65 -65
  122. package/src/providers/codearts/dpop.js +95 -95
  123. package/src/providers/codearts/index.js +175 -175
  124. package/src/providers/codearts/login.js +214 -214
  125. package/src/providers/codearts/models.js +182 -182
  126. package/src/providers/codearts/sign.js +96 -96
  127. package/src/providers/codearts/sse.js +204 -204
  128. package/src/providers/codearts/stream.js +209 -209
  129. package/src/providers/codearts/sts.js +124 -124
  130. package/src/providers/codearts.js +1 -1
  131. package/src/providers/dispatcher.js +146 -146
  132. package/src/providers/generic.js +156 -156
  133. package/src/providers/globalqwenwork/account-store.js +141 -141
  134. package/src/providers/globalqwenwork/constants.js +73 -73
  135. package/src/providers/globalqwenwork/crypto.js +2 -2
  136. package/src/providers/globalqwenwork/http.js +2 -2
  137. package/src/providers/globalqwenwork/index.js +331 -331
  138. package/src/providers/globalqwenwork/payload.js +147 -147
  139. package/src/providers/globalqwenwork/rsa.js +2 -2
  140. package/src/providers/globalqwenwork/sse.js +2 -2
  141. package/src/providers/globalqwenwork/stream.js +2 -2
  142. package/src/providers/globalqwenwork.js +1 -1
  143. package/src/providers/globalworkbuddy/account-store.js +198 -198
  144. package/src/providers/globalworkbuddy/auth.js +97 -97
  145. package/src/providers/globalworkbuddy/catalog.js +138 -138
  146. package/src/providers/globalworkbuddy/constants.js +73 -73
  147. package/src/providers/globalworkbuddy/device-auth.js +120 -120
  148. package/src/providers/globalworkbuddy/errors.js +98 -98
  149. package/src/providers/globalworkbuddy/headers.js +89 -89
  150. package/src/providers/globalworkbuddy/models.js +96 -96
  151. package/src/providers/globalworkbuddy/payload.js +80 -80
  152. package/src/providers/index.js +5 -5
  153. package/src/providers/model-id.js +172 -172
  154. package/src/providers/opencode.js +19 -19
  155. package/src/providers/openrouter.js +31 -31
  156. package/src/providers/qoder/account-store.js +106 -106
  157. package/src/providers/qoder/aggregate.js +60 -60
  158. package/src/providers/qoder/baseprompt.json +591 -591
  159. package/src/providers/qoder/checkin.js +160 -160
  160. package/src/providers/qoder/constants.js +44 -44
  161. package/src/providers/qoder/encode.js +41 -41
  162. package/src/providers/qoder/fingerprint.js +38 -38
  163. package/src/providers/qoder/models.js +77 -77
  164. package/src/providers/qoder/oauth.js +84 -84
  165. package/src/providers/qoder/payload.js +162 -162
  166. package/src/providers/qoder/request.js +27 -27
  167. package/src/providers/qoder/session.js +134 -134
  168. package/src/providers/qoder/sticky.js +38 -38
  169. package/src/providers/qoder/stream.js +116 -116
  170. package/src/providers/qoder.js +1 -1
  171. package/src/providers/raccoon/account-store.js +216 -216
  172. package/src/providers/raccoon/auth.js +67 -67
  173. package/src/providers/raccoon/chat.js +228 -228
  174. package/src/providers/raccoon/const.js +180 -180
  175. package/src/providers/raccoon/credits.js +185 -185
  176. package/src/providers/raccoon/crypto.js +43 -43
  177. package/src/providers/raccoon/envelope.js +31 -31
  178. package/src/providers/raccoon/headers.js +45 -45
  179. package/src/providers/raccoon/index.js +216 -216
  180. package/src/providers/raccoon/login.js +279 -279
  181. package/src/providers/raccoon/models.js +160 -160
  182. package/src/providers/raccoon/password-login.js +102 -102
  183. package/src/providers/raccoon/qr-codewords.js +115 -115
  184. package/src/providers/raccoon/qr.js +236 -236
  185. package/src/providers/raccoon/sse.js +135 -135
  186. package/src/providers/responses-channel.js +120 -120
  187. package/src/providers/share-keys.js +53 -53
  188. package/src/providers/traework/account-store.js +234 -234
  189. package/src/providers/traework/chat.js +217 -217
  190. package/src/providers/traework/checkin.js +77 -77
  191. package/src/providers/traework/constants.js +41 -41
  192. package/src/providers/traework/errors.js +52 -52
  193. package/src/providers/traework/headers.js +79 -79
  194. package/src/providers/traework/index.js +132 -132
  195. package/src/providers/traework/market-identity.js +116 -116
  196. package/src/providers/traework/models.js +98 -98
  197. package/src/providers/traework/payload.js +86 -86
  198. package/src/providers/traework/sse.js +184 -184
  199. package/src/providers/traework/token.js +52 -52
  200. package/src/providers/traework.js +2 -2
  201. package/src/providers/workbuddy/account-store.js +186 -186
  202. package/src/providers/workbuddy/auth.js +158 -158
  203. package/src/providers/workbuddy/balance.js +86 -86
  204. package/src/providers/workbuddy/cat-travel.js +125 -125
  205. package/src/providers/workbuddy/checkin.js +75 -75
  206. package/src/providers/workbuddy/desktop-info.js +78 -78
  207. package/src/providers/workbuddy/growth-api.js +67 -67
  208. package/src/providers/workbuddy/growth-plans.js +63 -63
  209. package/src/providers/workbuddy/growth.js +181 -181
  210. package/src/providers/workbuddy/index.js +217 -217
  211. package/src/providers/workbuddy/models.js +126 -126
  212. package/src/providers/workbuddy/payload.js +141 -141
  213. package/src/providers/workbuddy/rotation-log.js +83 -83
  214. package/src/providers/workbuddy/sanitize-tools.js +61 -61
  215. package/src/providers/workbuddy/sdk-chat.js +26 -26
  216. package/src/providers/workbuddy-balance.js +78 -78
  217. package/src/providers/zcode/account-store.js +129 -129
  218. package/src/providers/zcode/auth.js +28 -28
  219. package/src/providers/zcode/chat.js +179 -179
  220. package/src/providers/zcode/const.js +54 -54
  221. package/src/providers/zcode/context-shape.js +162 -162
  222. package/src/providers/zcode/headers.js +93 -93
  223. package/src/providers/zcode/index.js +124 -124
  224. package/src/providers/zcode/models.js +65 -65
  225. package/src/providers/zcode/oauth.js +120 -120
  226. package/src/providers/zcode/quota.js +176 -176
  227. package/src/providers/zcode/sse.js +196 -196
  228. package/src/readline-compat.js +25 -25
  229. package/src/responses/translate.js +311 -311
  230. package/src/routes/AGENTS.md +37 -0
  231. package/src/routes/chat/broadband-handler.js +72 -72
  232. package/src/routes/chat/empty-turn-report.js +31 -31
  233. package/src/routes/chat/exhausted-handler.js +87 -87
  234. package/src/routes/chat/hedge-handler.js +130 -130
  235. package/src/routes/chat/index.js +1 -1
  236. package/src/routes/chat/local-handler.js +63 -63
  237. package/src/routes/chat/peer-handler.js +81 -81
  238. package/src/routes/chat.js +3 -3
  239. package/src/routes/fallback.js +88 -88
  240. package/src/routes/groups-relay.js +214 -214
  241. package/src/routes/groups.js +122 -122
  242. package/src/routes/helpers.js +93 -83
  243. package/src/routes/index.js +147 -147
  244. package/src/routes/messages-route.js +375 -365
  245. package/src/routes/peers.js +10 -2
  246. package/src/routes/relay-queue.js +181 -181
  247. package/src/routes/relay.js +60 -60
  248. package/src/routes/responses-route.js +213 -213
  249. package/src/routes/stream-body.js +84 -84
  250. package/src/routes/stream-hold.js +337 -337
  251. package/src/routes/stream-scan.js +366 -366
  252. package/src/routes.js +3 -3
  253. package/src/runtime/auto-update.js +113 -113
  254. package/src/runtime/broadband-stream.js +79 -79
  255. package/src/runtime/broadband.js +103 -103
  256. package/src/runtime/group-sync.js +81 -81
  257. package/src/runtime/keep-warm.js +120 -120
  258. package/src/runtime/lifecycle-forensics.js +116 -116
  259. package/src/runtime/lifecycle-log.js +87 -87
  260. package/src/runtime/log-stamp.js +73 -73
  261. package/src/runtime/proc-info.js +137 -137
  262. package/src/runtime/qoder-checkin.js +93 -93
  263. package/src/runtime/raccoon-checkin.js +126 -126
  264. package/src/runtime/server-lifecycle.js +189 -189
  265. package/src/runtime/traework-checkin.js +118 -118
  266. package/src/runtime/workbuddy-checkin.js +120 -120
  267. package/src/runtime/workbuddy-growth.js +133 -133
  268. package/src/state/facade.js +58 -58
  269. package/src/state/memory.js +162 -162
  270. package/src/state/merge.js +25 -25
  271. package/src/state/migrations/cline-unify.js +94 -94
  272. package/src/state/migrations.js +33 -33
  273. package/src/state/persist.js +42 -42
  274. package/src/state/provider-config.js +142 -142
  275. package/src/state/schemas/group.js +31 -31
  276. package/src/state/schemas/model.js +94 -94
  277. package/src/state/schemas/peer.js +31 -31
  278. package/src/state/schemas/port.js +10 -10
  279. package/src/state/schemas/provider.js +202 -202
  280. package/src/state/schemas/timezone.js +70 -70
  281. package/src/state/schemas/token.js +48 -48
  282. package/src/state/schemas/use-group.js +102 -102
  283. package/src/state/store.js +176 -176
  284. package/src/state.js +1 -1
  285. package/src/sync-claude-desktop.js +234 -234
  286. package/src/sync-claude.js +152 -152
  287. package/src/sync-codex.js +116 -116
  288. package/src/sync-workbuddy.js +158 -158
  289. package/src/talk-full-redact.js +129 -129
  290. package/src/talk-full.js +131 -131
  291. package/src/talk-log.js +257 -257
  292. package/src/text-tokens.js +52 -52
  293. package/src/thinking-effort.js +67 -67
  294. package/src/time.js +106 -106
  295. package/src/timeline.js +26 -26
  296. package/src/transport/index.js +1 -1
  297. package/src/upstream-engine/index.js +74 -74
  298. package/src/upstream-engine/mode.js +15 -15
  299. package/src/upstream-engine/sdk/attempt.js +221 -221
  300. package/src/upstream-engine/sdk/chat.js +28 -28
  301. package/src/upstream-engine/sdk/convert.js +192 -192
  302. package/src/upstream-engine/sdk/diagnose.js +62 -62
  303. package/src/upstream-engine/sdk/dispatch.js +55 -55
  304. package/src/upstream-engine/sdk/responses.js +176 -176
  305. package/src/upstream-engine/sdk/sse.js +166 -166
  306. package/src/upstream-responses.js +305 -305
  307. package/src/usage/cache-fields.js +58 -0
  308. package/src/usage/record.js +109 -106
  309. package/src/usage/report.js +209 -195
  310. package/src/v6pool/claim.js +157 -157
  311. package/src/v6pool/config.js +65 -65
  312. package/src/v6pool/detect.js +130 -130
  313. package/src/v6pool/index.js +152 -152
  314. package/src/v6pool/prefix.js +140 -140
  315. package/src/v6pool/probe.js +40 -40
  316. package/src/v6pool/report.js +104 -104
  317. package/src/v6pool/rotate.js +263 -263
  318. package/src/v6pool/selftest.js +154 -0
  319. package/src/warp-pool/config.js +74 -74
  320. package/src/warp-pool/health.js +106 -106
  321. package/src/warp-pool/identity.js +141 -141
  322. package/src/warp-pool/index.js +358 -358
  323. package/src/warp-pool/keys.js +29 -29
  324. package/src/warp-pool/nodes.js +95 -95
  325. package/src/warp-pool/ports.js +160 -160
  326. package/src/warp-pool/register.js +113 -113
  327. package/src/warp-pool/report.js +232 -232
  328. package/src/warp-pool/revive.js +168 -168
  329. package/src/warp-pool/rotate.js +214 -214
  330. package/src/warp-pool/strategy.js +43 -43
  331. package/src/warp-pool/verdict.js +69 -69
  332. package/src/warp-pool/wireproxy.js +162 -162
@@ -1,160 +1,160 @@
1
- // raccoon 模型目录:上游 `model_catalog` 解析 + 兜底目录 + 积分倍率展示名 + allowlist 补齐。
2
- // 倍率是这家供应商的**成本唯一载体**(积分制,不是免费额度),所以它在模型名与列表里都必须出现:
3
- // 0 → 「免费」;1 → 也显示 `x1`(省略会让用户以为「没倍率=不扣分」);促销 → `x0.2→x0.1`。
4
- import {
5
- RACCOON_FALLBACK_MODELS,
6
- RACCOON_REQUEST_TIMEOUT_MS,
7
- raccoonModelCatalogUrl,
8
- } from "./const.js";
9
- import { raccoonAuthHeaders } from "./headers.js";
10
- import { parseRaccoonEnvelope } from "./envelope.js";
11
- import { loadProviderAllowedModels, saveProviderAllowedModels } from "../../state/schemas/allowlist.js";
12
- import { joinModelId } from "../model-id.js";
13
-
14
- const pickString = (src, keys) => {
15
- for (const k of keys) {
16
- const v = src?.[k];
17
- if (typeof v === "string" && v.trim().length > 0) return v.trim();
18
- }
19
- return "";
20
- };
21
-
22
- const pickNumber = (src, keys) => {
23
- for (const k of keys) {
24
- const v = src?.[k];
25
- if (typeof v === "number" && Number.isFinite(v)) return v;
26
- if (typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim())) return Number(v.trim());
27
- }
28
- return undefined;
29
- };
30
-
31
- const pickBool = (src, keys) => {
32
- for (const k of keys) {
33
- const v = src?.[k];
34
- if (typeof v === "boolean") return v;
35
- }
36
- return undefined;
37
- };
38
-
39
- /** 兜底目录(const.js 的静态表,字段名已归一)。 */
40
- export function raccoonFallbackModels() {
41
- return RACCOON_FALLBACK_MODELS.map((m) => ({ ...m }));
42
- }
43
-
44
- /** 单条目录条目:字段名容错(缺字段即省略,不猜、不填假值)。 */
45
- function parseRaccoonModelEntry(raw) {
46
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
47
- const id = pickString(raw, ["id", "modelId", "model_id", "modelName", "name"]);
48
- if (!id) return null;
49
- const model = { id };
50
- const name = pickString(raw, ["modelName", "name", "displayName", "description"]);
51
- if (name) model.name = name;
52
- const ctx = pickNumber(raw, ["contextWindow", "context_window", "contextLength", "context_length"]);
53
- if (ctx !== undefined && ctx > 0) model.contextWindow = ctx;
54
- const maxTokens = pickNumber(raw, ["maxTokens", "max_tokens", "maxCompletionTokens"]);
55
- if (maxTokens !== undefined && maxTokens > 0) model.maxTokens = maxTokens;
56
- const img = pickBool(raw, ["supportsImage", "supports_image", "supportsVision"]);
57
- if (img !== undefined) model.supportsImage = img;
58
- const eff = pickNumber(raw, ["effectiveMultiplier", "effective_multiplier", "multiplier", "costMultiplier"]);
59
- if (eff !== undefined && eff >= 0) model.multiplier = eff;
60
- const base = pickNumber(raw, ["baseMultiplier", "base_multiplier"]);
61
- if (base !== undefined && base > 0) model.baseMultiplier = base;
62
- return model;
63
- }
64
-
65
- /** 目录信封 → 条目数组。兼容 `data` 为数组、`data.models`、`data.list`、`data.items` 四种形状。 */
66
- export function parseRaccoonCatalog(data) {
67
- const list = Array.isArray(data)
68
- ? data
69
- : Array.isArray(data?.models)
70
- ? data.models
71
- : Array.isArray(data?.list)
72
- ? data.list
73
- : Array.isArray(data?.items)
74
- ? data.items
75
- : [];
76
- const out = [];
77
- const seen = new Set();
78
- for (const raw of list) {
79
- const m = parseRaccoonModelEntry(raw);
80
- if (!m || seen.has(m.id)) continue;
81
- seen.add(m.id);
82
- out.push(m);
83
- }
84
- return out;
85
- }
86
-
87
- /** 倍率文案:0 → 免费;促销 → `x基→x实`;其余 → `x实`(含 x1)。 */
88
- export function raccoonMultiplierLabel(model) {
89
- const eff = typeof model?.multiplier === "number" ? model.multiplier : undefined;
90
- if (eff === undefined) return "";
91
- if (eff === 0) return "免费";
92
- const fmt = (v) => String(Number(v.toFixed(4)));
93
- const base = typeof model?.baseMultiplier === "number" ? model.baseMultiplier : undefined;
94
- if (base !== undefined && base > eff) return `x${fmt(base)}→x${fmt(eff)}`;
95
- return `x${fmt(eff)}`;
96
- }
97
-
98
- /** 列表/模型卡展示名:`<名字或 id> · <倍率>`(无倍率时省略后缀)。 */
99
- export function raccoonDisplayName(model) {
100
- const base = model?.name || model?.id || "";
101
- const label = raccoonMultiplierLabel(model);
102
- return label ? `${base} · ${label}` : base;
103
- }
104
-
105
- /** 拉上游目录;失败/空目录返回 null(调用方回落兜底表)。 */
106
- export async function fetchRaccoonModelCatalog({
107
- credential,
108
- fetchImpl = fetch,
109
- env = process.env,
110
- timeoutMs = RACCOON_REQUEST_TIMEOUT_MS,
111
- } = {}) {
112
- if (!credential?.access_token) return null;
113
- try {
114
- const res = await fetchImpl(raccoonModelCatalogUrl(), {
115
- method: "GET",
116
- headers: raccoonAuthHeaders(credential, { env }),
117
- signal: AbortSignal.timeout(timeoutMs),
118
- });
119
- if (!res.ok) return null;
120
- const envelope = parseRaccoonEnvelope(await res.json(), res.status);
121
- if (envelope.code !== 0) return null;
122
- const models = parseRaccoonCatalog(envelope.data);
123
- return models.length > 0 ? models : null;
124
- } catch {
125
- return null;
126
- }
127
- }
128
-
129
- /** 上游优先、兜底次之。返回 `{ models, source }`,source ∈ upstream|fallback。 */
130
- export async function listRaccoonModels({ credential, fetchImpl, env } = {}) {
131
- const upstream = await fetchRaccoonModelCatalog({ credential, fetchImpl, env });
132
- if (upstream) return { models: upstream, source: "upstream" };
133
- return { models: raccoonFallbackModels(), source: "fallback" };
134
- }
135
-
136
- /** 登录后把兜底目录 id 只增不减并入 allowlist(避免「登录成功却处处 403」)。 */
137
- export function seedRaccoonAllowlist({ ids, file } = {}) {
138
- const opts = file ? { file } : {};
139
- const target = ids || RACCOON_FALLBACK_MODELS.map((m) => m.id);
140
- const cur = loadProviderAllowedModels("raccoon", opts);
141
- const added = target.filter((m) => !cur.includes(m));
142
- if (added.length) saveProviderAllowedModels("raccoon", [...cur, ...added], opts);
143
- return { allowed: [...cur, ...added], added };
144
- }
145
-
146
- /** 对外目录条目(`/v1/models` 与 CLI models 共用形状)。 */
147
- export function buildRaccoonModelList({ id = "raccoon", models } = {}) {
148
- const list = models || raccoonFallbackModels();
149
- return list.map((m) => ({
150
- id: joinModelId(id, m.id),
151
- object: "model",
152
- owned_by: "raccoon",
153
- name: raccoonDisplayName(m),
154
- context_length: m.contextWindow,
155
- max_tokens: m.maxTokens,
156
- supports_image: m.supportsImage,
157
- multiplier: m.multiplier,
158
- mark: raccoonMultiplierLabel(m) || undefined,
159
- }));
160
- }
1
+ // raccoon 模型目录:上游 `model_catalog` 解析 + 兜底目录 + 积分倍率展示名 + allowlist 补齐。
2
+ // 倍率是这家供应商的**成本唯一载体**(积分制,不是免费额度),所以它在模型名与列表里都必须出现:
3
+ // 0 → 「免费」;1 → 也显示 `x1`(省略会让用户以为「没倍率=不扣分」);促销 → `x0.2→x0.1`。
4
+ import {
5
+ RACCOON_FALLBACK_MODELS,
6
+ RACCOON_REQUEST_TIMEOUT_MS,
7
+ raccoonModelCatalogUrl,
8
+ } from "./const.js";
9
+ import { raccoonAuthHeaders } from "./headers.js";
10
+ import { parseRaccoonEnvelope } from "./envelope.js";
11
+ import { loadProviderAllowedModels, saveProviderAllowedModels } from "../../state/schemas/allowlist.js";
12
+ import { joinModelId } from "../model-id.js";
13
+
14
+ const pickString = (src, keys) => {
15
+ for (const k of keys) {
16
+ const v = src?.[k];
17
+ if (typeof v === "string" && v.trim().length > 0) return v.trim();
18
+ }
19
+ return "";
20
+ };
21
+
22
+ const pickNumber = (src, keys) => {
23
+ for (const k of keys) {
24
+ const v = src?.[k];
25
+ if (typeof v === "number" && Number.isFinite(v)) return v;
26
+ if (typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim())) return Number(v.trim());
27
+ }
28
+ return undefined;
29
+ };
30
+
31
+ const pickBool = (src, keys) => {
32
+ for (const k of keys) {
33
+ const v = src?.[k];
34
+ if (typeof v === "boolean") return v;
35
+ }
36
+ return undefined;
37
+ };
38
+
39
+ /** 兜底目录(const.js 的静态表,字段名已归一)。 */
40
+ export function raccoonFallbackModels() {
41
+ return RACCOON_FALLBACK_MODELS.map((m) => ({ ...m }));
42
+ }
43
+
44
+ /** 单条目录条目:字段名容错(缺字段即省略,不猜、不填假值)。 */
45
+ function parseRaccoonModelEntry(raw) {
46
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
47
+ const id = pickString(raw, ["id", "modelId", "model_id", "modelName", "name"]);
48
+ if (!id) return null;
49
+ const model = { id };
50
+ const name = pickString(raw, ["modelName", "name", "displayName", "description"]);
51
+ if (name) model.name = name;
52
+ const ctx = pickNumber(raw, ["contextWindow", "context_window", "contextLength", "context_length"]);
53
+ if (ctx !== undefined && ctx > 0) model.contextWindow = ctx;
54
+ const maxTokens = pickNumber(raw, ["maxTokens", "max_tokens", "maxCompletionTokens"]);
55
+ if (maxTokens !== undefined && maxTokens > 0) model.maxTokens = maxTokens;
56
+ const img = pickBool(raw, ["supportsImage", "supports_image", "supportsVision"]);
57
+ if (img !== undefined) model.supportsImage = img;
58
+ const eff = pickNumber(raw, ["effectiveMultiplier", "effective_multiplier", "multiplier", "costMultiplier"]);
59
+ if (eff !== undefined && eff >= 0) model.multiplier = eff;
60
+ const base = pickNumber(raw, ["baseMultiplier", "base_multiplier"]);
61
+ if (base !== undefined && base > 0) model.baseMultiplier = base;
62
+ return model;
63
+ }
64
+
65
+ /** 目录信封 → 条目数组。兼容 `data` 为数组、`data.models`、`data.list`、`data.items` 四种形状。 */
66
+ export function parseRaccoonCatalog(data) {
67
+ const list = Array.isArray(data)
68
+ ? data
69
+ : Array.isArray(data?.models)
70
+ ? data.models
71
+ : Array.isArray(data?.list)
72
+ ? data.list
73
+ : Array.isArray(data?.items)
74
+ ? data.items
75
+ : [];
76
+ const out = [];
77
+ const seen = new Set();
78
+ for (const raw of list) {
79
+ const m = parseRaccoonModelEntry(raw);
80
+ if (!m || seen.has(m.id)) continue;
81
+ seen.add(m.id);
82
+ out.push(m);
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /** 倍率文案:0 → 免费;促销 → `x基→x实`;其余 → `x实`(含 x1)。 */
88
+ export function raccoonMultiplierLabel(model) {
89
+ const eff = typeof model?.multiplier === "number" ? model.multiplier : undefined;
90
+ if (eff === undefined) return "";
91
+ if (eff === 0) return "免费";
92
+ const fmt = (v) => String(Number(v.toFixed(4)));
93
+ const base = typeof model?.baseMultiplier === "number" ? model.baseMultiplier : undefined;
94
+ if (base !== undefined && base > eff) return `x${fmt(base)}→x${fmt(eff)}`;
95
+ return `x${fmt(eff)}`;
96
+ }
97
+
98
+ /** 列表/模型卡展示名:`<名字或 id> · <倍率>`(无倍率时省略后缀)。 */
99
+ export function raccoonDisplayName(model) {
100
+ const base = model?.name || model?.id || "";
101
+ const label = raccoonMultiplierLabel(model);
102
+ return label ? `${base} · ${label}` : base;
103
+ }
104
+
105
+ /** 拉上游目录;失败/空目录返回 null(调用方回落兜底表)。 */
106
+ export async function fetchRaccoonModelCatalog({
107
+ credential,
108
+ fetchImpl = fetch,
109
+ env = process.env,
110
+ timeoutMs = RACCOON_REQUEST_TIMEOUT_MS,
111
+ } = {}) {
112
+ if (!credential?.access_token) return null;
113
+ try {
114
+ const res = await fetchImpl(raccoonModelCatalogUrl(), {
115
+ method: "GET",
116
+ headers: raccoonAuthHeaders(credential, { env }),
117
+ signal: AbortSignal.timeout(timeoutMs),
118
+ });
119
+ if (!res.ok) return null;
120
+ const envelope = parseRaccoonEnvelope(await res.json(), res.status);
121
+ if (envelope.code !== 0) return null;
122
+ const models = parseRaccoonCatalog(envelope.data);
123
+ return models.length > 0 ? models : null;
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ /** 上游优先、兜底次之。返回 `{ models, source }`,source ∈ upstream|fallback。 */
130
+ export async function listRaccoonModels({ credential, fetchImpl, env } = {}) {
131
+ const upstream = await fetchRaccoonModelCatalog({ credential, fetchImpl, env });
132
+ if (upstream) return { models: upstream, source: "upstream" };
133
+ return { models: raccoonFallbackModels(), source: "fallback" };
134
+ }
135
+
136
+ /** 登录后把兜底目录 id 只增不减并入 allowlist(避免「登录成功却处处 403」)。 */
137
+ export function seedRaccoonAllowlist({ ids, file } = {}) {
138
+ const opts = file ? { file } : {};
139
+ const target = ids || RACCOON_FALLBACK_MODELS.map((m) => m.id);
140
+ const cur = loadProviderAllowedModels("raccoon", opts);
141
+ const added = target.filter((m) => !cur.includes(m));
142
+ if (added.length) saveProviderAllowedModels("raccoon", [...cur, ...added], opts);
143
+ return { allowed: [...cur, ...added], added };
144
+ }
145
+
146
+ /** 对外目录条目(`/v1/models` 与 CLI models 共用形状)。 */
147
+ export function buildRaccoonModelList({ id = "raccoon", models } = {}) {
148
+ const list = models || raccoonFallbackModels();
149
+ return list.map((m) => ({
150
+ id: joinModelId(id, m.id),
151
+ object: "model",
152
+ owned_by: "raccoon",
153
+ name: raccoonDisplayName(m),
154
+ context_length: m.contextWindow,
155
+ max_tokens: m.maxTokens,
156
+ supports_image: m.supportsImage,
157
+ multiplier: m.multiplier,
158
+ mark: raccoonMultiplierLabel(m) || undefined,
159
+ }));
160
+ }
@@ -1,102 +1,102 @@
1
- // raccoon 密码登录(手机号 + 密码)—— 多账号自动化添加的捷径(免扫码)。
2
- // 契约、端点选择理由与「为什么必须加密」见 const.js 的 raccoonPasswordLoginUrl 注释;
3
- // 加密算法与 key 见 crypto.js(单一源)。
4
- //
5
- // ⚠ 安全红线:密码**只作为请求体出现一次**(且出站前已被加密)。本模块不落盘、不打印、不记日志;
6
- // 抛出的错误里也绝不带上请求体原文(上游 message 是它自己的文案,安全)。
7
- import {
8
- RACCOON_PASSWORD_ERRORS,
9
- RACCOON_REQUEST_TIMEOUT_MS,
10
- raccoonPasswordLoginUrl,
11
- } from "./const.js";
12
- import { raccoonAnonymousHeaders } from "./headers.js";
13
- import { isRaccoonOk, parseRaccoonEnvelope, raccoonEnvelopeText } from "./envelope.js";
14
- import { decodeJwtExpMs } from "./auth.js";
15
- import { raccoonEncryptField } from "./crypto.js";
16
-
17
- /** nation_code 归一:接受 `"86"` / `"+86"` / `86`,统一成不带加号的数字串。 */
18
- export function normalizeNationCode(raw) {
19
- const s = String(raw ?? "").trim().replace(/^\+/, "");
20
- return /^\d{1,4}$/.test(s) ? s : "";
21
- }
22
-
23
- /** 手机号归一:只留数字(去空格、连字符、括号)。空结果由调用方判错。 */
24
- export function normalizePhone(raw) {
25
- return String(raw ?? "").replace(/[^\d]/g, "");
26
- }
27
-
28
- /** 上游登录失败 → 人话 Error。命中码表用它,否则透出上游 message(绝不带请求体)。 */
29
- function passwordLoginError(envelope, status) {
30
- const key = String(envelope?.code ?? "");
31
- const friendly = RACCOON_PASSWORD_ERRORS[key];
32
- const err = new Error(`raccoon: 密码登录失败:${friendly || raccoonEnvelopeText(envelope, `HTTP ${status}`)}`);
33
- err.code = envelope?.code;
34
- err.status = status;
35
- return err;
36
- }
37
-
38
- /**
39
- * 手机号 + 密码 → credential(与扫码登录**同一形状**,可直接喂 saveRaccoonAccount)。
40
- *
41
- * 返回 `{ access_token, refresh_token, expires_at?, office_identity? }`;
42
- * 失败抛「可操作」的错误(码表命中给人话,未命中透出上游文案)。
43
- * 本地校验先于出网:手机号/密码为空当场报错,不白跑一次请求。
44
- *
45
- * ⚠ 出站前 phone/password 都经 `raccoonEncryptField` 加密(上游契约,见 const.js)——
46
- * 发明文只会换来一句「手机号或密码不正确」,把格式错误伪装成密码错误。
47
- */
48
- export async function loginRaccoonWithPassword({ phone, password, nationCode = "86" } = {}, {
49
- fetchImpl = fetch,
50
- env = process.env,
51
- timeoutMs = RACCOON_REQUEST_TIMEOUT_MS,
52
- } = {}) {
53
- const nc = normalizeNationCode(nationCode);
54
- const ph = normalizePhone(phone);
55
- if (!nc) throw new Error("raccoon: 国家码不合法(用 86 这类数字,或 +86)");
56
- if (!ph) throw new Error("raccoon: 手机号不能为空");
57
- if (String(password ?? "").length === 0) throw new Error("raccoon: 密码不能为空");
58
-
59
- // 未知错误码时透出上游 message 而非裸码 —— 上游新旧通道对同一错误的形态不同:
60
- // 新 IAM 把 message 放顶层(AUTH_ACCOUNT_OR_PASSWORD_INVALID),旧 web 只有 message/details。
61
- const body = {
62
- nation_code: nc,
63
- phone: raccoonEncryptField(ph),
64
- password: raccoonEncryptField(String(password)),
65
- };
66
-
67
- let res;
68
- try {
69
- res = await fetchImpl(raccoonPasswordLoginUrl(), {
70
- method: "POST",
71
- headers: raccoonAnonymousHeaders({ env }),
72
- body: JSON.stringify(body),
73
- signal: AbortSignal.timeout(timeoutMs),
74
- });
75
- } catch (error) {
76
- throw new Error(`raccoon: 密码登录请求失败:${error?.message ?? error}`);
77
- }
78
-
79
- let parsed;
80
- try {
81
- parsed = await res.json();
82
- } catch {
83
- throw new Error(`raccoon: 密码登录响应不是 JSON(HTTP ${res.status})`);
84
- }
85
- const envelope = parseRaccoonEnvelope(parsed, res.status);
86
- // 上游把错误码同时塞在 `code` 与 `message`:web 通道 `{code:"AUTH_...",message:"AUTH_..."}`
87
- // 时 message 是裸码,不能当人话用 —— 故这里把 code 也纳入,让码表能命中。
88
- if (!isRaccoonOk(envelope)) throw passwordLoginError(envelope, res.status);
89
-
90
- const data = envelope.data ?? {};
91
- const accessToken = typeof data.access_token === "string" ? data.access_token : "";
92
- if (!accessToken) throw new Error("raccoon: 密码登录响应缺少 access_token");
93
- const refreshToken = typeof data.refresh_token === "string" ? data.refresh_token : "";
94
- const officeIdentity = typeof data.office_identity === "string" ? data.office_identity : "";
95
- const expMs = decodeJwtExpMs(accessToken);
96
- return {
97
- access_token: accessToken,
98
- refresh_token: refreshToken,
99
- ...(expMs === undefined ? {} : { expires_at: String(expMs) }),
100
- ...(officeIdentity ? { office_identity: officeIdentity } : {}),
101
- };
102
- }
1
+ // raccoon 密码登录(手机号 + 密码)—— 多账号自动化添加的捷径(免扫码)。
2
+ // 契约、端点选择理由与「为什么必须加密」见 const.js 的 raccoonPasswordLoginUrl 注释;
3
+ // 加密算法与 key 见 crypto.js(单一源)。
4
+ //
5
+ // ⚠ 安全红线:密码**只作为请求体出现一次**(且出站前已被加密)。本模块不落盘、不打印、不记日志;
6
+ // 抛出的错误里也绝不带上请求体原文(上游 message 是它自己的文案,安全)。
7
+ import {
8
+ RACCOON_PASSWORD_ERRORS,
9
+ RACCOON_REQUEST_TIMEOUT_MS,
10
+ raccoonPasswordLoginUrl,
11
+ } from "./const.js";
12
+ import { raccoonAnonymousHeaders } from "./headers.js";
13
+ import { isRaccoonOk, parseRaccoonEnvelope, raccoonEnvelopeText } from "./envelope.js";
14
+ import { decodeJwtExpMs } from "./auth.js";
15
+ import { raccoonEncryptField } from "./crypto.js";
16
+
17
+ /** nation_code 归一:接受 `"86"` / `"+86"` / `86`,统一成不带加号的数字串。 */
18
+ export function normalizeNationCode(raw) {
19
+ const s = String(raw ?? "").trim().replace(/^\+/, "");
20
+ return /^\d{1,4}$/.test(s) ? s : "";
21
+ }
22
+
23
+ /** 手机号归一:只留数字(去空格、连字符、括号)。空结果由调用方判错。 */
24
+ export function normalizePhone(raw) {
25
+ return String(raw ?? "").replace(/[^\d]/g, "");
26
+ }
27
+
28
+ /** 上游登录失败 → 人话 Error。命中码表用它,否则透出上游 message(绝不带请求体)。 */
29
+ function passwordLoginError(envelope, status) {
30
+ const key = String(envelope?.code ?? "");
31
+ const friendly = RACCOON_PASSWORD_ERRORS[key];
32
+ const err = new Error(`raccoon: 密码登录失败:${friendly || raccoonEnvelopeText(envelope, `HTTP ${status}`)}`);
33
+ err.code = envelope?.code;
34
+ err.status = status;
35
+ return err;
36
+ }
37
+
38
+ /**
39
+ * 手机号 + 密码 → credential(与扫码登录**同一形状**,可直接喂 saveRaccoonAccount)。
40
+ *
41
+ * 返回 `{ access_token, refresh_token, expires_at?, office_identity? }`;
42
+ * 失败抛「可操作」的错误(码表命中给人话,未命中透出上游文案)。
43
+ * 本地校验先于出网:手机号/密码为空当场报错,不白跑一次请求。
44
+ *
45
+ * ⚠ 出站前 phone/password 都经 `raccoonEncryptField` 加密(上游契约,见 const.js)——
46
+ * 发明文只会换来一句「手机号或密码不正确」,把格式错误伪装成密码错误。
47
+ */
48
+ export async function loginRaccoonWithPassword({ phone, password, nationCode = "86" } = {}, {
49
+ fetchImpl = fetch,
50
+ env = process.env,
51
+ timeoutMs = RACCOON_REQUEST_TIMEOUT_MS,
52
+ } = {}) {
53
+ const nc = normalizeNationCode(nationCode);
54
+ const ph = normalizePhone(phone);
55
+ if (!nc) throw new Error("raccoon: 国家码不合法(用 86 这类数字,或 +86)");
56
+ if (!ph) throw new Error("raccoon: 手机号不能为空");
57
+ if (String(password ?? "").length === 0) throw new Error("raccoon: 密码不能为空");
58
+
59
+ // 未知错误码时透出上游 message 而非裸码 —— 上游新旧通道对同一错误的形态不同:
60
+ // 新 IAM 把 message 放顶层(AUTH_ACCOUNT_OR_PASSWORD_INVALID),旧 web 只有 message/details。
61
+ const body = {
62
+ nation_code: nc,
63
+ phone: raccoonEncryptField(ph),
64
+ password: raccoonEncryptField(String(password)),
65
+ };
66
+
67
+ let res;
68
+ try {
69
+ res = await fetchImpl(raccoonPasswordLoginUrl(), {
70
+ method: "POST",
71
+ headers: raccoonAnonymousHeaders({ env }),
72
+ body: JSON.stringify(body),
73
+ signal: AbortSignal.timeout(timeoutMs),
74
+ });
75
+ } catch (error) {
76
+ throw new Error(`raccoon: 密码登录请求失败:${error?.message ?? error}`);
77
+ }
78
+
79
+ let parsed;
80
+ try {
81
+ parsed = await res.json();
82
+ } catch {
83
+ throw new Error(`raccoon: 密码登录响应不是 JSON(HTTP ${res.status})`);
84
+ }
85
+ const envelope = parseRaccoonEnvelope(parsed, res.status);
86
+ // 上游把错误码同时塞在 `code` 与 `message`:web 通道 `{code:"AUTH_...",message:"AUTH_..."}`
87
+ // 时 message 是裸码,不能当人话用 —— 故这里把 code 也纳入,让码表能命中。
88
+ if (!isRaccoonOk(envelope)) throw passwordLoginError(envelope, res.status);
89
+
90
+ const data = envelope.data ?? {};
91
+ const accessToken = typeof data.access_token === "string" ? data.access_token : "";
92
+ if (!accessToken) throw new Error("raccoon: 密码登录响应缺少 access_token");
93
+ const refreshToken = typeof data.refresh_token === "string" ? data.refresh_token : "";
94
+ const officeIdentity = typeof data.office_identity === "string" ? data.office_identity : "";
95
+ const expMs = decodeJwtExpMs(accessToken);
96
+ return {
97
+ access_token: accessToken,
98
+ refresh_token: refreshToken,
99
+ ...(expMs === undefined ? {} : { expires_at: String(expMs) }),
100
+ ...(officeIdentity ? { office_identity: officeIdentity } : {}),
101
+ };
102
+ }