mslxdff 0.1.200 → 0.1.203

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 (326) hide show
  1. package/README.md +3 -3
  2. package/bin/mslxdff.js +1 -1
  3. package/package.json +38 -38
  4. package/plugins/README.md +14 -14
  5. package/plugins/prefer-model.mjs.example +14 -14
  6. package/src/anthropic/count-tokens.js +61 -61
  7. package/src/anthropic/request-body.js +126 -126
  8. package/src/anthropic/shared.js +18 -18
  9. package/src/anthropic/sse-aggregate.js +56 -56
  10. package/src/anthropic/translate.js +287 -287
  11. package/src/anthropic/web-search.js +361 -361
  12. package/src/api-kind.js +31 -31
  13. package/src/autostart.js +288 -288
  14. package/src/bench/probe.js +1 -1
  15. package/src/bench/runner.js +1 -1
  16. package/src/bench/via-probe.js +1 -1
  17. package/src/bench/via.js +144 -144
  18. package/src/bench/workbuddy-bench.js +1 -1
  19. package/src/chat/config.js +11 -11
  20. package/src/chat/cooling.js +81 -81
  21. package/src/chat/direct.js +58 -58
  22. package/src/chat/index.js +6 -6
  23. package/src/chat/spinner.js +38 -38
  24. package/src/chat/sse.js +69 -69
  25. package/src/chat/stats.js +247 -247
  26. package/src/chat/store.js +48 -48
  27. package/src/chat/tool-handlers.js +67 -67
  28. package/src/chat/tools.js +1 -1
  29. package/src/chat/upstream.js +59 -59
  30. package/src/chat-pipeline/auto-race.js +158 -158
  31. package/src/chat-pipeline/empty-turn.js +147 -147
  32. package/src/chat-pipeline/engine.js +17 -17
  33. package/src/chat-pipeline/serial-trial.js +283 -283
  34. package/src/chat-pipeline/terminal-verdict.js +147 -147
  35. package/src/chooser.js +54 -54
  36. package/src/claude-desktop/apply.js +101 -101
  37. package/src/claude-desktop/context1m.js +38 -38
  38. package/src/claude-desktop/doctor.js +146 -146
  39. package/src/claude-desktop/slots.js +152 -152
  40. package/src/cli/bootstrap.js +1 -1
  41. package/src/cli/commands/claude-desktop.js +284 -284
  42. package/src/cli/commands/daemon.js +231 -231
  43. package/src/cli/commands/model/list-live.js +22 -22
  44. package/src/cli/commands/model/list-providers.js +76 -76
  45. package/src/cli/commands/model/list-render.js +77 -77
  46. package/src/cli/commands/model/list-sort.js +52 -52
  47. package/src/cli/commands/model/list-tty.js +76 -76
  48. package/src/cli/commands/model/list.js +166 -166
  49. package/src/cli/commands/model/live-models.js +24 -24
  50. package/src/cli/commands/model/stats.js +43 -43
  51. package/src/cli/commands/model/status.js +47 -47
  52. package/src/cli/commands/model.js +51 -51
  53. package/src/cli/commands/provider/add.js +108 -108
  54. package/src/cli/commands/provider/allowlist.js +99 -99
  55. package/src/cli/commands/provider/cline-free.js +99 -99
  56. package/src/cli/commands/provider/cline-login.js +218 -218
  57. package/src/cli/commands/provider/cline-quota.js +65 -65
  58. package/src/cli/commands/provider/codearts-login.js +45 -45
  59. package/src/cli/commands/provider/config.js +119 -119
  60. package/src/cli/commands/provider/globalqwenwork-login.js +126 -126
  61. package/src/cli/commands/provider/keys-guard.js +50 -50
  62. package/src/cli/commands/provider/keys.js +161 -161
  63. package/src/cli/commands/provider/qoder-checkin.js +81 -81
  64. package/src/cli/commands/provider/qoder-login.js +102 -102
  65. package/src/cli/commands/provider/raccoon-login.js +180 -180
  66. package/src/cli/commands/provider/raccoon-password.js +162 -162
  67. package/src/cli/commands/provider/raccoon-quota.js +109 -109
  68. package/src/cli/commands/provider/traework-checkin.js +144 -144
  69. package/src/cli/commands/provider/traework-login.js +164 -164
  70. package/src/cli/commands/provider/workbuddy-login.js +153 -153
  71. package/src/cli/commands/provider/zcode-login.js +77 -77
  72. package/src/cli/commands/provider/zcode-quota.js +55 -55
  73. package/src/cli/commands/provider.js +1 -1
  74. package/src/cli/commands/stats.js +149 -149
  75. package/src/cli/commands/system.js +261 -261
  76. package/src/cli/commands/timezone.js +47 -47
  77. package/src/cli/commands/use-group.js +60 -60
  78. package/src/cli/commands/workbuddy.js +189 -189
  79. package/src/cli/format.js +141 -141
  80. package/src/cli/group-helpers.js +70 -70
  81. package/src/cli/help.js +80 -80
  82. package/src/cli/index.js +70 -70
  83. package/src/cli/interactive.js +121 -121
  84. package/src/cli/join-core.js +99 -99
  85. package/src/cli/join-wizard.js +175 -175
  86. package/src/cli/policy.js +136 -136
  87. package/src/cli/provider-row.js +126 -126
  88. package/src/cli/util.js +24 -24
  89. package/src/compat.js +53 -53
  90. package/src/events.js +38 -38
  91. package/src/free-lane.js +131 -131
  92. package/src/free-watcher.js +63 -63
  93. package/src/groups.js +262 -262
  94. package/src/logs.js +195 -195
  95. package/src/metrics.js +62 -62
  96. package/src/model-capabilities/context-lookup.js +65 -65
  97. package/src/model-capabilities/merge.js +142 -142
  98. package/src/model-trace.js +187 -187
  99. package/src/models.js +1 -1
  100. package/src/opencode-identity.js +72 -72
  101. package/src/peers.js +254 -251
  102. package/src/plugins.js +107 -107
  103. package/src/providers/base.js +164 -164
  104. package/src/providers/classify.js +22 -22
  105. package/src/providers/cline/allowlist-sync.js +93 -93
  106. package/src/providers/cline/free-catalog.js +71 -71
  107. package/src/providers/cline/headers.js +37 -37
  108. package/src/providers/cline/index.js +130 -130
  109. package/src/providers/cline/models.js +150 -150
  110. package/src/providers/cline/usage.js +274 -274
  111. package/src/providers/codearts/auth-pool.js +148 -148
  112. package/src/providers/codearts/concurrency.js +34 -34
  113. package/src/providers/codearts/const.js +65 -65
  114. package/src/providers/codearts/dpop.js +95 -95
  115. package/src/providers/codearts/index.js +175 -175
  116. package/src/providers/codearts/login.js +214 -214
  117. package/src/providers/codearts/models.js +182 -182
  118. package/src/providers/codearts/sign.js +96 -96
  119. package/src/providers/codearts/sse.js +204 -204
  120. package/src/providers/codearts/stream.js +209 -209
  121. package/src/providers/codearts/sts.js +124 -124
  122. package/src/providers/codearts.js +1 -1
  123. package/src/providers/dispatcher.js +146 -146
  124. package/src/providers/generic.js +156 -156
  125. package/src/providers/globalqwenwork/account-store.js +141 -141
  126. package/src/providers/globalqwenwork/constants.js +73 -73
  127. package/src/providers/globalqwenwork/crypto.js +2 -2
  128. package/src/providers/globalqwenwork/http.js +2 -2
  129. package/src/providers/globalqwenwork/index.js +331 -331
  130. package/src/providers/globalqwenwork/payload.js +147 -147
  131. package/src/providers/globalqwenwork/rsa.js +2 -2
  132. package/src/providers/globalqwenwork/sse.js +2 -2
  133. package/src/providers/globalqwenwork/stream.js +2 -2
  134. package/src/providers/globalqwenwork.js +1 -1
  135. package/src/providers/globalworkbuddy/account-store.js +198 -198
  136. package/src/providers/globalworkbuddy/auth.js +97 -97
  137. package/src/providers/globalworkbuddy/catalog.js +138 -138
  138. package/src/providers/globalworkbuddy/constants.js +73 -73
  139. package/src/providers/globalworkbuddy/device-auth.js +120 -120
  140. package/src/providers/globalworkbuddy/errors.js +98 -98
  141. package/src/providers/globalworkbuddy/headers.js +89 -89
  142. package/src/providers/globalworkbuddy/models.js +96 -96
  143. package/src/providers/globalworkbuddy/payload.js +80 -80
  144. package/src/providers/index.js +5 -5
  145. package/src/providers/model-id.js +172 -172
  146. package/src/providers/opencode.js +19 -19
  147. package/src/providers/openrouter.js +31 -31
  148. package/src/providers/qoder/account-store.js +106 -106
  149. package/src/providers/qoder/aggregate.js +60 -60
  150. package/src/providers/qoder/baseprompt.json +591 -591
  151. package/src/providers/qoder/checkin.js +160 -160
  152. package/src/providers/qoder/constants.js +44 -44
  153. package/src/providers/qoder/encode.js +41 -41
  154. package/src/providers/qoder/fingerprint.js +38 -38
  155. package/src/providers/qoder/models.js +77 -77
  156. package/src/providers/qoder/oauth.js +84 -84
  157. package/src/providers/qoder/payload.js +162 -162
  158. package/src/providers/qoder/request.js +27 -27
  159. package/src/providers/qoder/session.js +134 -134
  160. package/src/providers/qoder/sticky.js +38 -38
  161. package/src/providers/qoder/stream.js +116 -116
  162. package/src/providers/qoder.js +1 -1
  163. package/src/providers/raccoon/account-store.js +216 -216
  164. package/src/providers/raccoon/auth.js +67 -67
  165. package/src/providers/raccoon/chat.js +228 -228
  166. package/src/providers/raccoon/const.js +180 -165
  167. package/src/providers/raccoon/credits.js +185 -175
  168. package/src/providers/raccoon/crypto.js +43 -0
  169. package/src/providers/raccoon/envelope.js +31 -31
  170. package/src/providers/raccoon/headers.js +45 -45
  171. package/src/providers/raccoon/index.js +216 -216
  172. package/src/providers/raccoon/login.js +279 -279
  173. package/src/providers/raccoon/models.js +160 -160
  174. package/src/providers/raccoon/password-login.js +102 -87
  175. package/src/providers/raccoon/qr-codewords.js +115 -115
  176. package/src/providers/raccoon/qr.js +236 -236
  177. package/src/providers/raccoon/sse.js +135 -135
  178. package/src/providers/responses-channel.js +120 -120
  179. package/src/providers/share-keys.js +53 -53
  180. package/src/providers/traework/account-store.js +234 -234
  181. package/src/providers/traework/chat.js +217 -217
  182. package/src/providers/traework/checkin.js +77 -77
  183. package/src/providers/traework/constants.js +41 -41
  184. package/src/providers/traework/errors.js +52 -52
  185. package/src/providers/traework/headers.js +79 -79
  186. package/src/providers/traework/index.js +132 -132
  187. package/src/providers/traework/market-identity.js +116 -116
  188. package/src/providers/traework/models.js +98 -98
  189. package/src/providers/traework/payload.js +86 -86
  190. package/src/providers/traework/sse.js +184 -184
  191. package/src/providers/traework/token.js +52 -52
  192. package/src/providers/traework.js +2 -2
  193. package/src/providers/workbuddy/account-store.js +186 -186
  194. package/src/providers/workbuddy/auth.js +158 -158
  195. package/src/providers/workbuddy/balance.js +86 -86
  196. package/src/providers/workbuddy/cat-travel.js +125 -125
  197. package/src/providers/workbuddy/checkin.js +75 -75
  198. package/src/providers/workbuddy/desktop-info.js +78 -78
  199. package/src/providers/workbuddy/growth-api.js +67 -67
  200. package/src/providers/workbuddy/growth-plans.js +63 -63
  201. package/src/providers/workbuddy/growth.js +181 -181
  202. package/src/providers/workbuddy/index.js +217 -217
  203. package/src/providers/workbuddy/models.js +126 -126
  204. package/src/providers/workbuddy/payload.js +141 -141
  205. package/src/providers/workbuddy/rotation-log.js +83 -83
  206. package/src/providers/workbuddy/sanitize-tools.js +61 -61
  207. package/src/providers/workbuddy/sdk-chat.js +26 -26
  208. package/src/providers/workbuddy-balance.js +78 -78
  209. package/src/providers/zcode/account-store.js +129 -129
  210. package/src/providers/zcode/auth.js +28 -28
  211. package/src/providers/zcode/chat.js +179 -179
  212. package/src/providers/zcode/const.js +54 -54
  213. package/src/providers/zcode/context-shape.js +162 -162
  214. package/src/providers/zcode/headers.js +93 -93
  215. package/src/providers/zcode/index.js +124 -124
  216. package/src/providers/zcode/models.js +65 -65
  217. package/src/providers/zcode/oauth.js +120 -120
  218. package/src/providers/zcode/quota.js +176 -176
  219. package/src/providers/zcode/sse.js +196 -196
  220. package/src/readline-compat.js +25 -25
  221. package/src/responses/translate.js +311 -311
  222. package/src/routes/chat/broadband-handler.js +72 -72
  223. package/src/routes/chat/empty-turn-report.js +31 -31
  224. package/src/routes/chat/exhausted-handler.js +87 -87
  225. package/src/routes/chat/hedge-handler.js +130 -130
  226. package/src/routes/chat/index.js +1 -1
  227. package/src/routes/chat/local-handler.js +63 -63
  228. package/src/routes/chat/peer-handler.js +81 -81
  229. package/src/routes/chat.js +3 -3
  230. package/src/routes/fallback.js +88 -88
  231. package/src/routes/groups-relay.js +214 -214
  232. package/src/routes/groups.js +122 -122
  233. package/src/routes/helpers.js +83 -83
  234. package/src/routes/index.js +147 -147
  235. package/src/routes/messages-route.js +365 -365
  236. package/src/routes/peers.js +23 -1
  237. package/src/routes/relay-queue.js +181 -181
  238. package/src/routes/relay.js +60 -60
  239. package/src/routes/responses-route.js +213 -213
  240. package/src/routes/stream-body.js +84 -84
  241. package/src/routes/stream-hold.js +337 -337
  242. package/src/routes/stream-scan.js +366 -366
  243. package/src/routes.js +3 -3
  244. package/src/runtime/auto-update.js +113 -113
  245. package/src/runtime/bootstrap.js +8 -0
  246. package/src/runtime/broadband-stream.js +79 -79
  247. package/src/runtime/broadband.js +103 -103
  248. package/src/runtime/group-sync.js +81 -81
  249. package/src/runtime/keep-warm.js +120 -0
  250. package/src/runtime/lifecycle-forensics.js +116 -116
  251. package/src/runtime/lifecycle-log.js +87 -87
  252. package/src/runtime/log-stamp.js +73 -73
  253. package/src/runtime/proc-info.js +137 -137
  254. package/src/runtime/qoder-checkin.js +93 -93
  255. package/src/runtime/raccoon-checkin.js +126 -126
  256. package/src/runtime/server-lifecycle.js +189 -189
  257. package/src/runtime/traework-checkin.js +118 -118
  258. package/src/runtime/workbuddy-checkin.js +120 -120
  259. package/src/runtime/workbuddy-growth.js +133 -133
  260. package/src/server.js +7 -0
  261. package/src/state/facade.js +58 -58
  262. package/src/state/memory.js +162 -162
  263. package/src/state/merge.js +25 -25
  264. package/src/state/migrations/cline-unify.js +94 -94
  265. package/src/state/migrations.js +33 -33
  266. package/src/state/persist.js +42 -42
  267. package/src/state/provider-config.js +142 -142
  268. package/src/state/schemas/group.js +31 -31
  269. package/src/state/schemas/model.js +94 -94
  270. package/src/state/schemas/peer.js +31 -31
  271. package/src/state/schemas/port.js +10 -10
  272. package/src/state/schemas/provider.js +202 -202
  273. package/src/state/schemas/timezone.js +70 -70
  274. package/src/state/schemas/token.js +48 -48
  275. package/src/state/schemas/use-group.js +102 -102
  276. package/src/state/store.js +176 -176
  277. package/src/state.js +1 -1
  278. package/src/sync-claude-desktop.js +234 -234
  279. package/src/sync-claude.js +152 -152
  280. package/src/sync-codex.js +116 -116
  281. package/src/sync-workbuddy.js +158 -158
  282. package/src/talk-full-redact.js +129 -129
  283. package/src/talk-full.js +131 -131
  284. package/src/talk-log.js +257 -257
  285. package/src/text-tokens.js +52 -52
  286. package/src/thinking-effort.js +67 -67
  287. package/src/time.js +106 -106
  288. package/src/timeline.js +26 -26
  289. package/src/transport/index.js +1 -1
  290. package/src/upstream-engine/index.js +74 -74
  291. package/src/upstream-engine/mode.js +15 -15
  292. package/src/upstream-engine/sdk/attempt.js +221 -221
  293. package/src/upstream-engine/sdk/chat.js +28 -28
  294. package/src/upstream-engine/sdk/convert.js +192 -192
  295. package/src/upstream-engine/sdk/diagnose.js +62 -62
  296. package/src/upstream-engine/sdk/dispatch.js +55 -55
  297. package/src/upstream-engine/sdk/responses.js +176 -176
  298. package/src/upstream-engine/sdk/sse.js +166 -166
  299. package/src/upstream-responses.js +305 -305
  300. package/src/usage/record.js +106 -106
  301. package/src/usage/report.js +195 -195
  302. package/src/v6pool/claim.js +157 -157
  303. package/src/v6pool/config.js +65 -65
  304. package/src/v6pool/detect.js +130 -130
  305. package/src/v6pool/index.js +152 -152
  306. package/src/v6pool/prefix.js +140 -140
  307. package/src/v6pool/probe.js +40 -40
  308. package/src/v6pool/report.js +104 -104
  309. package/src/v6pool/rotate.js +263 -263
  310. package/src/warp-pool/config.js +74 -74
  311. package/src/warp-pool/health.js +106 -106
  312. package/src/warp-pool/identity.js +141 -141
  313. package/src/warp-pool/index.js +358 -358
  314. package/src/warp-pool/keys.js +29 -29
  315. package/src/warp-pool/nodes.js +95 -95
  316. package/src/warp-pool/ports.js +160 -160
  317. package/src/warp-pool/register.js +113 -113
  318. package/src/warp-pool/report.js +232 -232
  319. package/src/warp-pool/revive.js +168 -168
  320. package/src/warp-pool/rotate.js +214 -214
  321. package/src/warp-pool/strategy.js +43 -43
  322. package/src/warp-pool/verdict.js +69 -69
  323. package/src/warp-pool/wireproxy.js +162 -162
  324. package/docs/cli_help_mini.md +0 -146
  325. package/src/providers/AGENTS.md +0 -49
  326. package/src/routes/AGENTS.md +0 -37
@@ -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,87 +1,102 @@
1
- // raccoon 密码登录(手机号 + 密码)—— 多账号自动化添加的捷径(免扫码)。
2
- // 契约与「为什么不用官方前端那条加密端点」见 const.js 的 raccoonPasswordLoginUrl 注释。
3
- //
4
- // ⚠ 安全红线:密码**只作为请求体出现一次**。本模块不落盘、不打印、不记日志;
5
- // 抛出的错误里也绝不带上请求体原文(上游 message 是它自己的文案,安全)。
6
- import {
7
- RACCOON_PASSWORD_ERRORS,
8
- RACCOON_REQUEST_TIMEOUT_MS,
9
- raccoonPasswordLoginUrl,
10
- } from "./const.js";
11
- import { raccoonAnonymousHeaders } from "./headers.js";
12
- import { isRaccoonOk, parseRaccoonEnvelope, raccoonEnvelopeText } from "./envelope.js";
13
- import { decodeJwtExpMs } from "./auth.js";
14
-
15
- /** nation_code 归一:接受 `"86"` / `"+86"` / `86`,统一成不带加号的数字串。 */
16
- export function normalizeNationCode(raw) {
17
- const s = String(raw ?? "").trim().replace(/^\+/, "");
18
- return /^\d{1,4}$/.test(s) ? s : "";
19
- }
20
-
21
- /** 手机号归一:只留数字(去空格、连字符、括号)。空结果由调用方判错。 */
22
- export function normalizePhone(raw) {
23
- return String(raw ?? "").replace(/[^\d]/g, "");
24
- }
25
-
26
- /** 上游登录失败 → 人话 Error。命中码表用它,否则透出上游 message(绝不带请求体)。 */
27
- function passwordLoginError(envelope, status) {
28
- const key = String(envelope?.code ?? "");
29
- const friendly = RACCOON_PASSWORD_ERRORS[key];
30
- const err = new Error(`raccoon: 密码登录失败:${friendly || raccoonEnvelopeText(envelope, `HTTP ${status}`)}`);
31
- err.code = envelope?.code;
32
- err.status = status;
33
- return err;
34
- }
35
-
36
- /**
37
- * 手机号 + 密码 → credential(与扫码登录**同一形状**,可直接喂 saveRaccoonAccount)。
38
- *
39
- * 返回 `{ access_token, refresh_token, expires_at?, office_identity? }`;
40
- * 失败抛「可操作」的错误(码表命中给人话,未命中透出上游文案)。
41
- * 本地校验先于出网:手机号/密码为空当场报错,不白跑一次请求。
42
- */
43
- export async function loginRaccoonWithPassword({ phone, password, nationCode = "86" } = {}, {
44
- fetchImpl = fetch,
45
- env = process.env,
46
- timeoutMs = RACCOON_REQUEST_TIMEOUT_MS,
47
- } = {}) {
48
- const nc = normalizeNationCode(nationCode);
49
- const ph = normalizePhone(phone);
50
- if (!nc) throw new Error("raccoon: 国家码不合法(用 86 这类数字,或 +86)");
51
- if (!ph) throw new Error("raccoon: 手机号不能为空");
52
- if (String(password ?? "").length === 0) throw new Error("raccoon: 密码不能为空");
53
-
54
- let res;
55
- try {
56
- res = await fetchImpl(raccoonPasswordLoginUrl(), {
57
- method: "POST",
58
- headers: raccoonAnonymousHeaders({ env }),
59
- body: JSON.stringify({ nation_code: nc, phone: ph, password: String(password) }),
60
- signal: AbortSignal.timeout(timeoutMs),
61
- });
62
- } catch (error) {
63
- throw new Error(`raccoon: 密码登录请求失败:${error?.message ?? error}`);
64
- }
65
-
66
- let parsed;
67
- try {
68
- parsed = await res.json();
69
- } catch {
70
- throw new Error(`raccoon: 密码登录响应不是 JSON(HTTP ${res.status})`);
71
- }
72
- const envelope = parseRaccoonEnvelope(parsed, res.status);
73
- if (!isRaccoonOk(envelope)) throw passwordLoginError(envelope, res.status);
74
-
75
- const data = envelope.data ?? {};
76
- const accessToken = typeof data.access_token === "string" ? data.access_token : "";
77
- if (!accessToken) throw new Error("raccoon: 密码登录响应缺少 access_token");
78
- const refreshToken = typeof data.refresh_token === "string" ? data.refresh_token : "";
79
- const officeIdentity = typeof data.office_identity === "string" ? data.office_identity : "";
80
- const expMs = decodeJwtExpMs(accessToken);
81
- return {
82
- access_token: accessToken,
83
- refresh_token: refreshToken,
84
- ...(expMs === undefined ? {} : { expires_at: String(expMs) }),
85
- ...(officeIdentity ? { office_identity: officeIdentity } : {}),
86
- };
87
- }
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
+ }