dsh-multi-router 0.6.11 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +2 -427
  2. package/package.json +5 -81
  3. package/CHANGELOG.md +0 -237
  4. package/LICENSE +0 -21
  5. package/THIRD_PARTY_NOTICES.md +0 -171
  6. package/cordis.patch.yml +0 -3
  7. package/lib/accounts.js +0 -19
  8. package/lib/accounts.js.map +0 -7
  9. package/lib/api-service.js +0 -11
  10. package/lib/api-service.js.map +0 -7
  11. package/lib/chunk-2UM45OXY.js +0 -181
  12. package/lib/chunk-2UM45OXY.js.map +0 -7
  13. package/lib/chunk-4L2CBGXN.js +0 -674
  14. package/lib/chunk-4L2CBGXN.js.map +0 -7
  15. package/lib/chunk-4V52V5GO.js +0 -105
  16. package/lib/chunk-4V52V5GO.js.map +0 -7
  17. package/lib/chunk-DNQPZMPF.js +0 -33
  18. package/lib/chunk-DNQPZMPF.js.map +0 -7
  19. package/lib/chunk-FLL3DUVP.js +0 -2017
  20. package/lib/chunk-FLL3DUVP.js.map +0 -7
  21. package/lib/chunk-GEBZMIMA.js +0 -287
  22. package/lib/chunk-GEBZMIMA.js.map +0 -7
  23. package/lib/chunk-KWXLQJS5.js +0 -1102
  24. package/lib/chunk-KWXLQJS5.js.map +0 -7
  25. package/lib/chunk-NZF36O5I.js +0 -5421
  26. package/lib/chunk-NZF36O5I.js.map +0 -7
  27. package/lib/chunk-S5KMFAQM.js +0 -50
  28. package/lib/chunk-S5KMFAQM.js.map +0 -7
  29. package/lib/chunk-TM5XDMGY.js +0 -332
  30. package/lib/chunk-TM5XDMGY.js.map +0 -7
  31. package/lib/chunk-WDIT4E2W.js +0 -120
  32. package/lib/chunk-WDIT4E2W.js.map +0 -7
  33. package/lib/chunk-YCAO7GCT.js +0 -2059
  34. package/lib/chunk-YCAO7GCT.js.map +0 -7
  35. package/lib/cli.js +0 -103
  36. package/lib/cli.js.map +0 -7
  37. package/lib/client.js +0 -2793
  38. package/lib/client.js.map +0 -7
  39. package/lib/clients.js +0 -30
  40. package/lib/clients.js.map +0 -7
  41. package/lib/host-v0.6.11.js +0 -414
  42. package/lib/host.js +0 -414
  43. package/lib/index.js +0 -414
  44. package/lib/index.js.map +0 -7
  45. package/lib/manager.js +0 -27
  46. package/lib/manager.js.map +0 -7
  47. package/lib/region-DO3KAV7V.js +0 -15
  48. package/lib/region-DO3KAV7V.js.map +0 -7
  49. package/lib/router.js +0 -14
  50. package/lib/router.js.map +0 -7
  51. package/lib/shim.js +0 -10
  52. package/lib/shim.js.map +0 -7
  53. package/lib/upstream-VP6J7LU2.js +0 -25
  54. package/lib/upstream-VP6J7LU2.js.map +0 -7
  55. package/router.example.json +0 -289
  56. package/upstream-lock.json +0 -17
@@ -1,2059 +0,0 @@
1
- import {
2
- Checkins,
3
- jsonCredential,
4
- qoderPluginDataDir,
5
- readLimitedText,
6
- refreshWorkBuddyCheckinCredential,
7
- supportsCheckin,
8
- workbuddyToken
9
- } from "./chunk-NZF36O5I.js";
10
- import {
11
- discoverLocalAccounts,
12
- importAccount,
13
- localAccountKey,
14
- normalizeLocalAccountKey,
15
- readPortableCredential
16
- } from "./chunk-2UM45OXY.js";
17
- import {
18
- modelWithCurrentPromotion
19
- } from "./chunk-KWXLQJS5.js";
20
- import {
21
- ApiService
22
- } from "./chunk-TM5XDMGY.js";
23
- import {
24
- hostIsLoopback
25
- } from "./chunk-DNQPZMPF.js";
26
- import {
27
- Telemetry,
28
- contextInfo,
29
- matchModel,
30
- resolveContext
31
- } from "./chunk-4L2CBGXN.js";
32
- import {
33
- PROVIDERS,
34
- PROVIDER_NAMES,
35
- ROUTE_PRESETS,
36
- beijingDay,
37
- creditRate,
38
- isFreeModel,
39
- routeReasoning,
40
- validateConfig,
41
- writePrivateJson
42
- } from "./chunk-GEBZMIMA.js";
43
-
44
- // src/manager.mjs
45
- import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
46
-
47
- // package.json
48
- var version = "0.6.11";
49
-
50
- // src/manager.mjs
51
- import { readFile as readFile4, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
52
- import { watch as watch2 } from "node:fs";
53
- import { dirname as dirname3, join as join5, relative as relative2, resolve } from "node:path";
54
- import { withFileLock as withFileLock3 } from "@deepseek-ai/dsh-atomic-write";
55
-
56
- // upstream/qoder/src/status-paths.ts
57
- var QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
58
- var QODER_PROBE_PATH = "/plugins/dsh-qoder-connect/probe";
59
- var QODER_AUTH_PATH = "/plugins/dsh-qoder-connect/auth";
60
- var QODER_GLOBAL_STATUS_PATH = "/plugins/dsh-qoder-connect/global/status";
61
- var QODER_GLOBAL_PROBE_PATH = "/plugins/dsh-qoder-connect/global/probe";
62
- var QODER_GLOBAL_AUTH_PATH = "/plugins/dsh-qoder-connect/global/auth";
63
-
64
- // upstream/qoder/src/variants.ts
65
- var QODER_VARIANTS = [
66
- {
67
- id: "qoder",
68
- displayName: "Qoder",
69
- appName: "Qoder",
70
- region: "china",
71
- ownFilename: ".qoder-auth.json",
72
- probeFilename: ".qoder-probe.json",
73
- catalogFilename: ".qoder-catalog.json",
74
- statusPath: QODER_STATUS_PATH,
75
- probePath: QODER_PROBE_PATH,
76
- authPath: QODER_AUTH_PATH
77
- },
78
- {
79
- id: "qoder-global",
80
- displayName: "Qoder Global",
81
- appName: "Qoder Global",
82
- region: "global",
83
- ownFilename: ".qoder-global-auth.json",
84
- probeFilename: ".qoder-global-probe.json",
85
- catalogFilename: ".qoder-global-catalog.json",
86
- statusPath: QODER_GLOBAL_STATUS_PATH,
87
- probePath: QODER_GLOBAL_PROBE_PATH,
88
- authPath: QODER_GLOBAL_AUTH_PATH
89
- }
90
- ];
91
- var CHINA_VARIANT = QODER_VARIANTS[0];
92
- var GLOBAL_VARIANT = QODER_VARIANTS[1];
93
-
94
- // upstream/qoder/src/auth.ts
95
- import { dirname, join } from "node:path";
96
- import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
97
- function qoderOwnAuthPath(variant) {
98
- return join(qoderPluginDataDir(), variant.ownFilename);
99
- }
100
-
101
- // src/migration.mjs
102
- import { randomBytes, scrypt, createCipheriv, createDecipheriv } from "node:crypto";
103
- import { promisify } from "node:util";
104
- import { mkdir, mkdtemp, rm } from "node:fs/promises";
105
- import { dirname as dirname2, join as join2, relative } from "node:path";
106
- var derive = promisify(scrypt);
107
- var FORMAT = "dsh-multi-router";
108
- var MAX_BYTES = 512 * 1024;
109
- var pick = (value, keys) => Object.fromEntries(keys.filter((k) => Object.hasOwn(value ?? {}, k)).map((k) => [k, value[k]]));
110
- var options = ["cooldownMs", "requestTimeoutMs", "catalogTtlMs", "retryServerErrors", "modelVisibility", "usageRetentionDays", "checkinSchedule", "sidebarUsage", "seenRoutePresets"];
111
- function settings(config) {
112
- return {
113
- ...pick(config, options),
114
- models: Object.fromEntries(Object.entries(config.models ?? {}).map(([p, rows]) => [p, Object.fromEntries(Object.entries(rows).map(([id, pref]) => [id, pick(pref, ["name", "visible", "contextWindow", "supportsImages", "reasoningEffort"])]))])),
115
- accounts: config.accounts.map((a) => pick(a, ["id", "provider", "name", "enabled", "autoCheckin"])),
116
- routes: Object.fromEntries(Object.entries(config.routes).map(([id, r]) => [id, {
117
- ...pick(r, ["name", "visible", "strategy", "contextMode", "contextWindow", "maxTokens", "reasoningEffort", "preferExpiringCredits", "expiringWithinDays", "freeOnly", "freeModels"]),
118
- targets: r.targets.map((t) => pick(t, ["provider", "account", "model", "models", "weight", "tokenBudget", "creditBudget"]))
119
- }]))
120
- };
121
- }
122
- var problem = (message) => {
123
- throw Error(message);
124
- };
125
- function password(value, exporting = false) {
126
- if (typeof value !== "string" || value.length > 1024 || value.length < (exporting ? 10 : 1)) problem(exporting ? "\u8FC1\u79FB\u5BC6\u7801\u81F3\u5C11 10 \u4E2A\u5B57\u7B26" : "\u8BF7\u8F93\u5165\u8FC1\u79FB\u5BC6\u7801");
127
- }
128
- async function exportConfiguration(manager, input) {
129
- const payload = { format: FORMAT, version: 1, encrypted: false, createdAt: (/* @__PURE__ */ new Date()).toISOString(), config: settings(manager.config) };
130
- if (!input.includeCredentials) return payload;
131
- password(input.password, true);
132
- payload.credentials = {};
133
- for (const account of manager.config.accounts) {
134
- try {
135
- const credential = await readPortableCredential(account);
136
- if (credential) Object.defineProperty(payload.credentials, account.id, { value: credential, enumerable: true });
137
- } catch {
138
- problem(`\u8D26\u53F7 ${account.id} \u7684\u51ED\u8BC1\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u4FEE\u590D\u540E\u5BFC\u51FA\u6216\u9009\u62E9\u4EC5\u914D\u7F6E`);
139
- }
140
- }
141
- const salt = randomBytes(16), iv = randomBytes(12), key = await derive(input.password, salt, 32);
142
- const cipher = createCipheriv("aes-256-gcm", key, iv);
143
- const bytes = Buffer.from(JSON.stringify(payload));
144
- if (bytes.length > MAX_BYTES) problem("\u914D\u7F6E\u8FC7\u5927\uFF0C\u65E0\u6CD5\u5BFC\u51FA");
145
- const data = Buffer.concat([cipher.update(bytes), cipher.final()]);
146
- return {
147
- format: FORMAT,
148
- version: 1,
149
- encrypted: true,
150
- cipher: "aes-256-gcm",
151
- kdf: "scrypt",
152
- salt: salt.toString("base64"),
153
- iv: iv.toString("base64"),
154
- tag: cipher.getAuthTag().toString("base64"),
155
- data: data.toString("base64")
156
- };
157
- }
158
- async function decodeConfiguration(input) {
159
- let payload;
160
- try {
161
- if (typeof input.document !== "string" || Buffer.byteLength(input.document) > 900 * 1024) problem("\u8FC1\u79FB\u6587\u4EF6\u8FC7\u5927");
162
- payload = JSON.parse(input.document);
163
- } catch {
164
- problem("\u8BF7\u9009\u62E9\u6709\u6548\u7684\u63D2\u4EF6\u8FC1\u79FB JSON \u6587\u4EF6\uFF08\u5C0F\u4E8E 900 KB\uFF09");
165
- }
166
- if (payload?.format !== FORMAT || payload.version !== 1) problem("\u4E0D\u662F\u53D7\u652F\u6301\u7684\u63D2\u4EF6\u8FC1\u79FB\u6587\u4EF6");
167
- const encrypted = payload.encrypted === true;
168
- if (encrypted) {
169
- password(input.password);
170
- try {
171
- if (payload.cipher !== "aes-256-gcm" || payload.kdf !== "scrypt") throw Error();
172
- const salt = Buffer.from(payload.salt, "base64"), iv = Buffer.from(payload.iv, "base64"), tag = Buffer.from(payload.tag, "base64"), data = Buffer.from(payload.data, "base64");
173
- if (salt.length !== 16 || iv.length !== 12 || tag.length !== 16 || data.length > MAX_BYTES) throw Error();
174
- const decipher = createDecipheriv("aes-256-gcm", await derive(input.password, salt, 32), iv);
175
- decipher.setAuthTag(tag);
176
- payload = JSON.parse(Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8"));
177
- } catch {
178
- problem("\u5BC6\u7801\u9519\u8BEF\u6216\u8FC1\u79FB\u6587\u4EF6\u5DF2\u635F\u574F");
179
- }
180
- }
181
- if (payload?.format !== FORMAT || payload.version !== 1 || !Array.isArray(payload.config?.accounts) || payload.config.accounts.length > 200 || !payload.config.routes || typeof payload.config.routes !== "object" || Object.keys(payload.config.routes).length > 500) problem("\u8FC1\u79FB\u914D\u7F6E\u683C\u5F0F\u65E0\u6548");
182
- if (payload.credentials && !encrypted) problem("\u542B\u51ED\u8BC1\u7684\u8FC1\u79FB\u6587\u4EF6\u5FC5\u987B\u52A0\u5BC6");
183
- if (payload.credentials && (typeof payload.credentials !== "object" || Array.isArray(payload.credentials))) problem("\u8FC1\u79FB\u51ED\u8BC1\u683C\u5F0F\u65E0\u6548");
184
- const config = settings(payload.config);
185
- validateConfig({ ...config, accounts: config.accounts.map((a) => ({ ...a, credentialFile: "placeholder.json" })) });
186
- if (payload.credentials && Object.keys(payload.credentials).some((id) => !config.accounts.some((a) => a.id === id))) problem("\u8FC1\u79FB\u51ED\u8BC1\u5B58\u5728\u672A\u77E5\u8D26\u53F7");
187
- return { config, credentials: payload.credentials ?? {}, encrypted };
188
- }
189
- function migrationSummary(manager, portable, mode) {
190
- if (!["merge", "replace"].includes(mode)) problem("\u8BF7\u9009\u62E9\u5408\u5E76\u6216\u66FF\u6362");
191
- for (const a of portable.config.accounts) {
192
- const old = manager.config.accounts.find((b) => b.id === a.id);
193
- if (mode === "merge" && old && old.provider !== a.provider) problem(`\u8D26\u53F7 ${a.id} \u7684\u4F9B\u5E94\u5546\u51B2\u7A81\uFF0C\u8BF7\u6539\u7528\u66FF\u6362\u6216\u4FEE\u6539\u5BFC\u5165\u6587\u4EF6`);
194
- }
195
- const existing = (id) => mode === "merge" && manager.config.accounts.some((a) => a.id === id);
196
- return {
197
- mode,
198
- accounts: portable.config.accounts.length,
199
- newAccounts: portable.config.accounts.filter((a) => !existing(a.id)).length,
200
- credentials: Object.keys(portable.credentials).length,
201
- needsLogin: portable.config.accounts.filter((a) => !Object.hasOwn(portable.credentials, a.id) && !existing(a.id)).length,
202
- routes: Object.keys(portable.config.routes).length,
203
- overwrittenRoutes: Object.keys(portable.config.routes).filter((id) => Object.hasOwn(manager.config.routes, id)).length
204
- };
205
- }
206
- async function prepareMigration(manager, raw, portable, mode) {
207
- migrationSummary(manager, portable, mode);
208
- const base = join2(dirname2(manager.file), "accounts");
209
- await mkdir(base, { recursive: true, mode: 448 });
210
- const staging = await mkdtemp(join2(base, "migration-"));
211
- try {
212
- const imported = [];
213
- for (const a of portable.config.accounts) {
214
- const old = mode === "merge" && raw.accounts.find((b) => b.id === a.id), secret = Object.hasOwn(portable.credentials, a.id) ? portable.credentials[a.id] : void 0;
215
- if (!secret && old) {
216
- imported.push({ ...old, ...a });
217
- continue;
218
- }
219
- const credentialFile = join2(staging, a.id, "credential.json"), identityFile = join2(staging, a.id, "identity.json");
220
- if (secret) await importAccount({ provider: a.provider, credentialFile, identityFile, document: secret.document, identityDocument: secret.identity });
221
- else {
222
- await writePrivateJson(credentialFile, {});
223
- if (a.provider.startsWith("trae")) await writePrivateJson(identityFile, {});
224
- }
225
- imported.push({
226
- ...a,
227
- ...!secret ? { enabled: false } : {},
228
- credentialFile: relative(dirname2(manager.file), credentialFile),
229
- ...a.provider.startsWith("trae") ? { identityFile: relative(dirname2(manager.file), identityFile) } : {}
230
- });
231
- }
232
- const next = mode === "replace" ? { ...portable.config, accounts: imported } : {
233
- ...raw,
234
- ...pick(portable.config, options),
235
- ...raw.seenRoutePresets || portable.config.seenRoutePresets ? { seenRoutePresets: [.../* @__PURE__ */ new Set([...raw.seenRoutePresets ?? [], ...portable.config.seenRoutePresets ?? []])] } : {},
236
- accounts: [...raw.accounts.map((a) => imported.find((b) => b.id === a.id) ?? a), ...imported.filter((a) => !raw.accounts.some((b) => b.id === a.id))],
237
- routes: { ...raw.routes, ...portable.config.routes },
238
- models: Object.fromEntries([.../* @__PURE__ */ new Set([...Object.keys(raw.models ?? {}), ...Object.keys(portable.config.models)])].map((p) => [p, { ...raw.models?.[p], ...portable.config.models[p] }]))
239
- };
240
- validateConfig(next, manager.file);
241
- return { raw: next, staging };
242
- } catch (error) {
243
- await rm(staging, { recursive: true, force: true });
244
- throw error;
245
- }
246
- }
247
-
248
- // src/plugin-updates.mjs
249
- import { readFile as readFile2, stat, rm as rm2 } from "node:fs/promises";
250
- import { join as join4 } from "node:path";
251
- import { randomUUID } from "node:crypto";
252
- import { createRequire } from "node:module";
253
- import { pathToFileURL } from "node:url";
254
- import { parse as parseYaml } from "yaml";
255
- import { withFileLock, writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
256
-
257
- // src/plugin-reload.mjs
258
- import { readFile } from "node:fs/promises";
259
- import { watch } from "node:fs";
260
- import { join as join3 } from "node:path";
261
- import { parse } from "yaml";
262
- function outsideActivation(hmr, operation) {
263
- const scope = hmr?.executing;
264
- return typeof scope?.exit === "function" ? scope.exit(operation) : operation();
265
- }
266
- var PluginReload = class {
267
- constructor({ service, profile, canReload, hmr = () => void 0, busy = () => false, now = Date.now, runningVersion }) {
268
- Object.assign(this, { service, profile, canReload, hmr, busy, now, runningVersion });
269
- this.phase = "current";
270
- this.installedVersion = runningVersion;
271
- this.retryAt = 0;
272
- }
273
- state() {
274
- return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
275
- }
276
- start() {
277
- const dir = this.profile()?.dir;
278
- this.watchers = [];
279
- if (dir) for (const target of [dir, join3(dir, "node_modules")]) try {
280
- const watcher = watch(target, () => this.schedule());
281
- watcher.unref();
282
- this.watchers.push(watcher);
283
- } catch {
284
- }
285
- this.schedule();
286
- }
287
- schedule(delay = 500) {
288
- if (this.closed) return;
289
- clearTimeout(this.timer);
290
- this.timer = setTimeout(() => {
291
- void this.check().catch(() => {
292
- });
293
- }, delay);
294
- this.timer.unref();
295
- }
296
- stop() {
297
- this.closed = true;
298
- clearTimeout(this.timer);
299
- for (const watcher of this.watchers ?? []) watcher.close();
300
- }
301
- check() {
302
- if (this.closed) return Promise.resolve();
303
- if (this.flight) return this.flight;
304
- this.flight = this.inspect().finally(() => {
305
- this.flight = void 0;
306
- });
307
- return this.flight;
308
- }
309
- async inspect() {
310
- const dir = this.profile()?.dir;
311
- if (!dir) return;
312
- let manifest, installed;
313
- try {
314
- manifest = JSON.parse(await readFile(join3(dir, "package.json"), "utf8"));
315
- installed = JSON.parse(await readFile(join3(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
316
- } catch {
317
- return;
318
- }
319
- if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
320
- if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
321
- this.installedVersion = installed.version;
322
- if (installed.version === this.runningVersion) {
323
- this.phase = "current";
324
- this.message = void 0;
325
- return;
326
- }
327
- const declared = manifest.dependencies?.["dsh-multi-router"];
328
- const exact = typeof declared === "string" && /^(?:npm:dsh-multi-router@)?(\d+\.\d+\.\d+(?:-[\w.-]+)?)$/.exec(declared)?.[1];
329
- let installing = false;
330
- const pid = Number(await readFile(join3(dir, "package.json.lock"), "utf8").catch(() => ""));
331
- if (Number.isInteger(pid) && pid > 0) try {
332
- process.kill(pid, 0);
333
- installing = true;
334
- } catch (e) {
335
- installing = e.code !== "ESRCH";
336
- }
337
- if (installing || exact && exact !== installed.version) {
338
- this.phase = "waiting";
339
- this.message = "\u5B89\u88C5\u4E8B\u52A1\u5C1A\u672A\u5B8C\u6210\uFF0C\u6216\u5B89\u88C5\u5931\u8D25\u5DF2\u56DE\u6EDA\uFF1B\u7B49\u5F85\u914D\u7F6E\u4E0E\u6587\u4EF6\u7248\u672C\u4E00\u81F4\u540E\u518D\u52A0\u8F7D\u3002";
340
- if (installing) this.schedule(1e3);
341
- return;
342
- }
343
- if (!this.canReload() || !this.service()?.setBundleEnabled) {
344
- this.phase = "restart-required";
345
- this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
346
- return;
347
- }
348
- if (this.busy()) {
349
- this.phase = "waiting";
350
- this.message = "\u65B0\u7248\u672C\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u8BF7\u6C42\u548C\u8D26\u53F7\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u751F\u6548\u3002";
351
- this.schedule(1e3);
352
- return;
353
- }
354
- const root = join3(dir, "node_modules/dsh-multi-router");
355
- try {
356
- const patch = parse(await readFile(join3(root, "cordis.patch.yml"), "utf8"));
357
- const row = Array.isArray(patch) && patch.flatMap((p) => p?.insert ?? []).find((e) => e?.id === `llm-multi-router-v2-${installed.version}`);
358
- if (row?.name !== `./lib/host-v${installed.version}.js` || row.disabled) throw Error("legacy entry");
359
- await readFile(join3(root, `lib/host-v${installed.version}.js`));
360
- } catch {
361
- this.phase = "restart-required";
362
- this.message = "\u5B89\u88C5\u7248\u672C\u7F3A\u5C11\u7248\u672C\u5316\u5165\u53E3\uFF0C\u91CD\u65B0\u5B89\u88C5\u6700\u65B0\u7248\u540E\u518D\u8BD5\u3002";
363
- return;
364
- }
365
- if (this.now() < this.retryAt) return;
366
- this.phase = "reloading";
367
- this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
368
- this.retryAt = this.now() + 6e4;
369
- try {
370
- const result = await outsideActivation(this.hmr(), () => this.service().setBundleEnabled("dsh-multi-router", true));
371
- if (this.closed) return;
372
- this.phase = "restart-required";
373
- this.message = result.application === "applied" ? "DSH \u5C1A\u672A\u5207\u6362\u5230\u65B0\u7248\u672C\uFF0C\u8BF7\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u6216\u67E5\u770B\u7EC4\u4EF6\u9519\u8BEF\u3002" : "DSH \u672A\u80FD\u91CD\u8F7D\u63D2\u4EF6\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
374
- } catch {
375
- if (!this.closed) {
376
- this.phase = "failed";
377
- this.message = "\u63D2\u4EF6\u91CD\u8F7D\u672A\u5B8C\u6210\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5DF2\u4FDD\u5B58\u7684\u914D\u7F6E\u4FDD\u6301\u4E0D\u53D8\u3002";
378
- }
379
- }
380
- }
381
- };
382
-
383
- // upstream/workbuddy/src/update.ts
384
- var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
385
- var WORKBUDDY_UPDATE_PACKAGE_NAME = "dsh-workbuddy-connect";
386
- var WORKBUDDY_UPDATE_NPM_METADATA_URL = `https://registry.npmjs.org/-/package/${WORKBUDDY_UPDATE_PACKAGE_NAME}/dist-tags`;
387
- var WORKBUDDY_UPDATE_RELEASES_API_URL = `${WORKBUDDY_REPOSITORY_URL.replace("https://github.com", "https://api.github.com/repos")}/releases?per_page=100`;
388
- var WORKBUDDY_RELEASE_PAGE_BASE = `${WORKBUDDY_REPOSITORY_URL}/releases/tag/`;
389
- var WORKBUDDY_UPDATE_MAX_METADATA_BYTES = 64 * 1024;
390
- var WORKBUDDY_UPDATE_MAX_RELEASES_BYTES = 1024 * 1024;
391
- function parseWorkBuddyVersion(raw) {
392
- if (typeof raw !== "string") return void 0;
393
- const normalized = raw.startsWith("v") ? raw.slice(1) : raw;
394
- const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(normalized);
395
- if (match === null) return void 0;
396
- const rawPrerelease = match[4] === void 0 ? [] : match[4].split(".");
397
- if (rawPrerelease.some((identifier) => /^\d+$/u.test(identifier) && !/^(0|[1-9]\d*)$/u.test(identifier))) return void 0;
398
- const prerelease = rawPrerelease.map((identifier) => /^(0|[1-9]\d*)$/u.test(identifier) ? Number(identifier) : identifier);
399
- if (prerelease.some((identifier) => typeof identifier === "number" && !Number.isSafeInteger(identifier))) return void 0;
400
- const parsed = {
401
- major: Number(match[1]),
402
- minor: Number(match[2]),
403
- patch: Number(match[3]),
404
- prerelease
405
- };
406
- return [parsed.major, parsed.minor, parsed.patch].every(Number.isSafeInteger) ? parsed : void 0;
407
- }
408
- function compareIdentifiers(left, right) {
409
- if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
410
- if (typeof left === "number") return -1;
411
- if (typeof right === "number") return 1;
412
- return left < right ? -1 : left > right ? 1 : 0;
413
- }
414
- function compareWorkBuddyVersions(left, right) {
415
- const a = parseWorkBuddyVersion(left);
416
- const b = parseWorkBuddyVersion(right);
417
- if (a === void 0 || b === void 0) throw new TypeError("invalid WorkBuddy Connect version");
418
- for (const [aPart, bPart] of [[a.major, b.major], [a.minor, b.minor], [a.patch, b.patch]]) {
419
- if (aPart !== bPart) return aPart < bPart ? -1 : 1;
420
- }
421
- if (a.prerelease.length === 0 && b.prerelease.length !== 0) return 1;
422
- if (a.prerelease.length !== 0 && b.prerelease.length === 0) return -1;
423
- for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
424
- const aPart = a.prerelease[index];
425
- const bPart = b.prerelease[index];
426
- if (aPart === void 0) return -1;
427
- if (bPart === void 0) return 1;
428
- const comparison = compareIdentifiers(aPart, bPart);
429
- if (comparison !== 0) return comparison;
430
- }
431
- return 0;
432
- }
433
-
434
- // src/plugin-updates.mjs
435
- var packageName = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
436
- var safeRef = /^[a-zA-Z0-9._/-]{1,200}$/;
437
- function parsePluginSource(name, spec) {
438
- if (!packageName.test(name) || typeof spec !== "string" || spec.length > 600 || spec.startsWith("-") || spec.trim() !== spec || /[\x00-\x1f]/.test(spec)) return { kind: "unsupported" };
439
- const git = /^(?:github:|git\+https:\/\/github\.com\/|https:\/\/github\.com\/|git\+ssh:\/\/git@github\.com\/|git@github\.com:)?([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#(.+))?$/.exec(spec);
440
- if (git && !spec.startsWith("@") && !spec.startsWith("file:") && !spec.startsWith("link:")) {
441
- const [, owner, repo, ref] = git;
442
- if (owner === "." || owner === ".." || repo === "." || repo === ".." || ref && (!safeRef.test(ref) || ref.includes(".."))) return { kind: "unsupported" };
443
- return { kind: "github", owner, repo, ref, source: `github:${owner}/${repo}${ref ? "#" + ref : ""}` };
444
- }
445
- if (/^(?:file:|link:|workspace:|https?:|git|\/|\.)/.test(spec)) return { kind: "unsupported" };
446
- const alias = /^npm:((?:@[^/]+\/)?[^@]+)(?:@(.+))?$/.exec(spec);
447
- const actual = alias?.[1] ?? name, range = alias?.[2] ?? spec;
448
- if (!packageName.test(actual) || !/^[a-zA-Z0-9.*^~<>=|+ -]{1,160}$/.test(range)) return { kind: "unsupported" };
449
- const channel = /^[a-zA-Z][\w.-]*$/.test(range);
450
- return { kind: "npm", package: actual, alias: !!alias, channel, tag: channel ? range : "latest", source: spec };
451
- }
452
- function githubCommit(value) {
453
- if (typeof value !== "string") return void 0;
454
- return /(?:\/tar\.gz\/|\/tarball\/|#)([a-f0-9]{40})(?:\b|\()/i.exec(value)?.[1]?.toLowerCase();
455
- }
456
- async function jsonFile(file) {
457
- try {
458
- return JSON.parse(await readFile2(file, "utf8"));
459
- } catch {
460
- return void 0;
461
- }
462
- }
463
- async function locks(dir) {
464
- let pnpm;
465
- try {
466
- pnpm = parseYaml(await readFile2(join4(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
467
- } catch {
468
- }
469
- const npm = await jsonFile(join4(dir, "package-lock.json"));
470
- const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join4(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
471
- return { pnpm, npm, preferNpm: times[1] > times[0] };
472
- }
473
- async function publicJson(url, fetchImpl, signal) {
474
- const res = await fetchImpl(url, {
475
- signal: AbortSignal.any([signal, AbortSignal.timeout(12e3)]),
476
- // Request only the SHA: the default JSON includes file patches and can be megabytes.
477
- headers: { Accept: "application/vnd.github.sha", "User-Agent": "dsh-multi-router-plugin-updates" },
478
- redirect: "error"
479
- });
480
- if (!res.ok) throw Error(res.status === 403 || res.status === 429 ? "\u8FDC\u7AEF\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : res.status === 404 ? "\u4ED3\u5E93 / \u7248\u672C\u4E0D\u5B58\u5728\uFF0C\u6216\u9700\u8981\u79C1\u6709\u4ED3\u5E93\u6388\u6743" : `\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${res.status}\uFF09`);
481
- let size = 0, chunks = [];
482
- for await (const chunk of res.body) {
483
- size += chunk.byteLength;
484
- if (size > 256 * 1024) throw Error("\u66F4\u65B0\u5143\u6570\u636E\u8FC7\u5927");
485
- chunks.push(Buffer.from(chunk));
486
- }
487
- const text = Buffer.concat(chunks).toString("utf8").trim();
488
- return /^[a-f0-9]{40}$/i.test(text) ? { sha: text } : JSON.parse(text);
489
- }
490
- var short = (sha) => sha?.slice(0, 8);
491
- var installFailed = (r) => !!(r.error || ["failed", "cancelled"].includes(r.application) || r.packageResult && r.packageResult.exitCode !== 0);
492
- function installFailure(result) {
493
- const text = [result.error?.diagnostic, result.packageResult?.output, result.packageResult?.kind].filter((v) => typeof v === "string").join("\n");
494
- let reason = "unknown", message = "\u5B89\u88C5\u672A\u5B8C\u6210\uFF1B\u8BF7\u5728 DSH \u539F\u751F\u63D2\u4EF6\u9875\u67E5\u770B\u5B89\u88C5\u65E5\u5FD7\uFF0C\u6216\u590D\u5236\u4E0B\u65B9\u8BCA\u65AD\u4FE1\u606F\u3002";
495
- if (result.application === "cancelled") {
496
- reason = "cancelled";
497
- message = "\u5B89\u88C5\u5DF2\u53D6\u6D88\uFF0C\u53EF\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\u3002";
498
- } else if (/ERR_PNPM_(?:MINIMUM_RELEASE_AGE_VIOLATION|NO_MATURE_MATCHING_VERSION)|minimumReleaseAge/i.test(text)) {
499
- reason = "release-age";
500
- message = "\u65B0\u7248\u672C\u5C1A\u5728\u5B89\u88C5\u5668\u7684\u65B0\u5305\u7B49\u5F85\u671F\u5185\u3002\u8F93\u5165\u5305\u540D\u53EF\u80FD\u9009\u4E2D\u65E7\u7248\uFF0C\u6307\u5B9A\u65B0\u7248\u672C\u53EF\u80FD\u88AB\u62D2\u7EDD\uFF1B\u8BF7\u7B49\u5F85\u7B56\u7565\u89C4\u5B9A\u7684\u65F6\u95F4\u540E\u91CD\u8BD5\uFF0C\u6216\u7531\u7528\u6237\u660E\u786E\u4E3A\u8BE5\u7248\u672C\u8BBE\u7F6E\u4F8B\u5916\u3002\u6362\u955C\u50CF\u6216\u91CD\u542F\u4E0D\u4F1A\u89E3\u9664\u6B64\u9650\u5236\u3002";
501
- } else if (result.pendingBuilds?.length) {
502
- reason = "build";
503
- message = "\u4F9D\u8D56\u6784\u5EFA\u811A\u672C\u9700\u8981\u6388\u6743\uFF0C\u8BF7\u5230 DSH \u63D2\u4EF6\u5B89\u88C5\u9875\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u540E\u91CD\u8BD5\u3002";
504
- } else if (result.error?.code === "incompatible-version") {
505
- reason = "compatibility";
506
- message = "\u63D2\u4EF6\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5148\u5347\u7EA7 DSH \u6216\u9009\u62E9\u517C\u5BB9\u7248\u672C\u3002";
507
- } else if (/\b(?:EPERM|EBUSY|EACCES)\b|resource busy|file.*(?:locked|in use)/i.test(text)) {
508
- reason = "files";
509
- message = "\u63D2\u4EF6\u6587\u4EF6\u88AB\u5360\u7528\u6216\u76EE\u5F55\u6743\u9650\u4E0D\u8DB3\uFF0C\u5B89\u88C5\u672A\u5B8C\u6210\u3002\u8BF7\u5148\u5728 DSH \u505C\u7528\u8BE5\u63D2\u4EF6\u540E\u91CD\u8BD5\uFF0C\u5E76\u68C0\u67E5\u5B89\u88C5\u76EE\u5F55\u6743\u9650\uFF1B\u4ECD\u88AB\u5360\u7528\u65F6\u9700\u9000\u51FA\u6258\u76D8\u5E94\u7528\u3002";
510
- } else if (/HMR transactions cannot be nested|activation|did not activate|entry.*activate/i.test(text)) {
511
- reason = "activation";
512
- message = "\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\u3002\u8BF7\u68C0\u67E5 DSH \u63D2\u4EF6\u9875\u7684\u7EC4\u4EF6\u9519\u8BEF\uFF0C\u518D\u5173\u95ED\u5E76\u91CD\u65B0\u542F\u7528\uFF1B\u5B89\u88C5\u4E0E\u52A0\u8F7D\u662F\u4E24\u4E2A\u72EC\u7ACB\u6B65\u9AA4\u3002";
513
- } else if (result.packageResult?.timedOut || /\b(?:ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED)\b|network|fetch|download/i.test(text)) {
514
- reason = "network";
515
- message = "\u63D2\u4EF6\u4E0B\u8F7D\u6216\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u5B89\u88C5\u672A\u5B8C\u6210\u3002npm \u6765\u6E90\u53EF\u5207\u6362\u5230\u300Cnpm \u5B98\u65B9\u300D\u540E\u91CD\u8BD5\uFF1BGitHub \u6765\u6E90\u8BF7\u68C0\u67E5 GitHub \u7F51\u7EDC\u8FDE\u63A5\u3002";
516
- }
517
- const code = /\b(?:ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION|ERR_PNPM_NO_MATURE_MATCHING_VERSION|EPERM|EBUSY|EACCES|ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED)\b/.exec(text)?.[0] ?? (["operation-error", "incompatible-version"].includes(result.error?.code) ? result.error.code : "unknown");
518
- return { status: "failed", application: result.application, message, diagnostic: {
519
- reason,
520
- code,
521
- platform: process.platform,
522
- stage: reason === "activation" ? "activation" : "install",
523
- ...Number.isInteger(result.packageResult?.exitCode) ? { exitCode: result.packageResult.exitCode } : {}
524
- } };
525
- }
526
- async function registryVersion(service, profile, spec, signal, requestedRegistry) {
527
- const require2 = createRequire(profile.installAnchor);
528
- const { viewProfilePackage } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
529
- const { registryPlan } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
530
- for (const registry of registryPlan(requestedRegistry, await service.registries())) {
531
- const answer = await viewProfilePackage(profile.dir, spec, { ...profile.packageManager, registry, timeoutMs: 12e3, signal });
532
- if (signal.aborted) throw Error("\u68C0\u67E5\u5DF2\u53D6\u6D88");
533
- if (answer.exitCode !== 0) continue;
534
- const parsed = JSON.parse(answer.stdout), metadata = Array.isArray(parsed) ? parsed.at(-1) : parsed;
535
- if (metadata?.version) return metadata;
536
- }
537
- throw Error("npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7F51\u7EDC\u3001registry \u8BBE\u7F6E\u6216\u5305\u7684\u8BBF\u95EE\u6743\u9650");
538
- }
539
- async function refreshGitPlugin(service, profile, row, options2, signal) {
540
- const require2 = createRequire(profile.installAnchor);
541
- const { runProfilePnpm } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
542
- const { registryPlan, attributeFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
543
- const { classifyInstallFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager")).href);
544
- const plan = registryPlan(options2.registry, await service.registries());
545
- return withFileLock(join4(profile.dir, "package.json"), async () => {
546
- const files = await Promise.all(["package.json", "pnpm-lock.yaml"].map(async (name) => {
547
- const path = join4(profile.dir, name);
548
- try {
549
- return { path, content: await readFile2(path, "utf8") };
550
- } catch (e) {
551
- if (e.code !== "ENOENT") throw e;
552
- return { path };
553
- }
554
- }));
555
- const restore = () => Promise.all(files.map((f) => f.content === void 0 ? rm2(f.path, { force: true }) : writeFileAtomic2(f.path, f.content, { mode: 384 })));
556
- if (JSON.parse(files[0].content).dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
557
- let result;
558
- try {
559
- for (const registry of plan) {
560
- signal.throwIfAborted();
561
- const run = await runProfilePnpm({ ...profile, profile: profile.name }, ["update", row.name, ...registry ? [`--registry=${registry}`] : []], {
562
- execution: "service",
563
- ...profile.packageManager,
564
- signal,
565
- outputBytes: 16384,
566
- idleTimeoutMs: 6e5,
567
- lookupTimeoutMs: 2e4,
568
- activateNewBundles: false
569
- });
570
- if (run.exitCode === 0 && !run.timedOut && !signal.aborted) return { packageResult: run, application: "restart-required" };
571
- const kind = classifyInstallFailure({ log: run.output, timedOut: run.timedOut });
572
- result = {
573
- packageResult: { ...run, kind },
574
- application: signal.aborted ? "cancelled" : "failed",
575
- ...run.incompatible?.length ? { error: { code: "incompatible-version" } } : {}
576
- };
577
- await restore();
578
- if (signal.aborted || attributeFailure(kind, run.output, { kind: "git", host: "github.com" }) !== "registry") break;
579
- }
580
- return result;
581
- } catch (e) {
582
- await restore();
583
- throw e;
584
- }
585
- }, { waitMs: 12e4 });
586
- }
587
- var PluginUpdates = class {
588
- constructor({ service = () => void 0, profile = () => void 0, hmr = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, onInstalled, stateFile, now = Date.now } = {}) {
589
- this.stateFile = stateFile;
590
- this.now = now;
591
- this.dismissed = {};
592
- this.lastAttempt = 0;
593
- this.saveQueue = Promise.resolve();
594
- this.service = service;
595
- this.profile = profile;
596
- this.hmr = hmr;
597
- this.fetchImpl = fetchImpl;
598
- this.rows = [];
599
- this.abort = new AbortController();
600
- this.lookup = lookup;
601
- this.refreshGit = refreshGit;
602
- this.onInstalled = onInstalled;
603
- }
604
- async load() {
605
- const saved = this.stateFile && await jsonFile(this.stateFile);
606
- if (saved?.version === 1) {
607
- this.lastAttempt = Number.isFinite(saved.lastAttempt) ? saved.lastAttempt : 0;
608
- this.checkedAt = saved.checkedAt;
609
- this.dismissed = saved.dismissed && typeof saved.dismissed === "object" && !Array.isArray(saved.dismissed) ? saved.dismissed : {};
610
- this.rows = Array.isArray(saved.rows) ? saved.rows.filter((r) => ["github", "npm"].includes(r.source?.kind)) : [];
611
- }
612
- }
613
- persist() {
614
- if (!this.stateFile) return Promise.resolve();
615
- const saved = structuredClone({ version: 1, lastAttempt: this.lastAttempt, checkedAt: this.checkedAt, dismissed: this.dismissed, rows: this.rows.filter((r) => ["github", "npm"].includes(r.source.kind)) });
616
- this.saveQueue = this.saveQueue.catch(() => {
617
- }).then(() => writePrivateJson(this.stateFile, saved));
618
- return this.saveQueue;
619
- }
620
- reminderKey(row) {
621
- return JSON.stringify([row.name, row.source, row.version, row.latestVersion ?? row.latestCommit]);
622
- }
623
- reminders() {
624
- return this.rows.filter((r) => r.status === "available" && (!Object.hasOwn(this.dismissed, r.name) || this.dismissed[r.name] !== this.reminderKey(r))).map((r) => ({ key: this.reminderKey(r), name: r.name, version: r.version, latestVersion: r.latestVersion, latestCommit: r.latestCommit }));
625
- }
626
- async dismiss(keys) {
627
- if (!Array.isArray(keys) || keys.length > 200 || keys.some((k) => typeof k !== "string")) throw Error("\u65E0\u6548\u7684\u63D0\u9192\u5217\u8868");
628
- for (const row of this.reminders()) if (keys.includes(row.key)) Object.defineProperty(this.dismissed, row.name, { value: row.key, enumerable: true, configurable: true, writable: true });
629
- await this.persist();
630
- }
631
- async tick() {
632
- if (this.flight || !this.state().available) return;
633
- await this.list();
634
- if (this.abort.signal.aborted) return;
635
- if (this.flight || this.now() >= this.lastAttempt && this.now() - this.lastAttempt < 864e5) return;
636
- this.lastAttempt = this.now();
637
- await this.persist();
638
- if (!this.flight && !this.abort.signal.aborted) this.check();
639
- }
640
- state() {
641
- return {
642
- available: !!this.service()?.listBundles && !!this.profile()?.dir,
643
- checkedAt: this.checkedAt,
644
- reminders: this.reminders(),
645
- autoCheckHours: 24,
646
- rows: this.rows.map(({ spec, ...row }) => ({ ...row, source: row.source.kind === "unsupported" ? { kind: "unsupported" } : row.source })),
647
- job: this.job,
648
- error: this.error
649
- };
650
- }
651
- async list() {
652
- const service = this.service(), profile = this.profile();
653
- if (!service?.listBundles || !profile?.dir) {
654
- this.rows = [];
655
- return;
656
- }
657
- const [manifest, bundles, lock] = await Promise.all([jsonFile(join4(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
658
- if (!manifest) throw Error("\u5F53\u524D DSH \u914D\u7F6E\u7684\u63D2\u4EF6\u6E05\u5355\u65E0\u6CD5\u8BFB\u53D6");
659
- this.rows = bundles.filter((b) => (b.installed || b.version) && manifest.dependencies?.[b.name] && b.removable !== false).map((b) => {
660
- const spec = manifest.dependencies?.[b.name];
661
- const source = parsePluginSource(b.name, spec);
662
- const npm = lock.npm?.packages?.[`node_modules/${b.name}`]?.resolved;
663
- const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[b.name]?.version;
664
- const installed = lock.preferNpm ? npm ?? pnpm : pnpm ?? npm;
665
- const previous = this.rows.find((r) => r.name === b.name && r.spec === spec && r.version === b.version && r.commit === githubCommit(installed));
666
- return {
667
- ...previous,
668
- name: b.name,
669
- version: b.version,
670
- enabled: b.enabled,
671
- spec,
672
- source,
673
- commit: githubCommit(installed),
674
- status: previous?.status ?? (source.kind === "unsupported" ? "unsupported" : "unchecked"),
675
- ...source.kind === "unsupported" ? { message: "\u672C\u5730\u6587\u4EF6 / \u94FE\u63A5\u6765\u6E90\uFF0C\u8BF7\u901A\u8FC7\u539F\u5B89\u88C5\u65B9\u5F0F\u66F4\u65B0" } : {}
676
- };
677
- });
678
- }
679
- async checkRow(row) {
680
- try {
681
- if (row.source.kind === "npm") {
682
- const spec = `${row.source.package}@${row.source.tag}`;
683
- const found = await this.lookup(this.service(), this.profile(), spec, this.abort.signal);
684
- if (!found.version || found.problem && found.problem !== "already-installed") throw Error(found.problem === "not-found" ? "npm \u4E2D\u672A\u627E\u5230\u8BE5\u5305\u6216\u9891\u9053" : "npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7684\u7F51\u7EDC\u4E0E registry \u8BBE\u7F6E");
685
- if (!parseWorkBuddyVersion(found.version) || row.version && !parseWorkBuddyVersion(row.version)) throw Error("\u65E0\u6CD5\u6BD4\u8F83\u7248\u672C\u53F7");
686
- row.latestVersion = found.version;
687
- row.target = `${row.name}@${row.source.alias ? `npm:${row.source.package}@` : ""}${row.source.channel ? row.source.tag : found.version}`;
688
- row.status = !row.version || compareWorkBuddyVersions(found.version, row.version) > 0 ? "available" : "current";
689
- row.message = !row.version ? "\u5B89\u88C5\u6587\u4EF6\u4E0D\u53EF\u8BFB\uFF0C\u53EF\u91CD\u65B0\u5B89\u88C5\u8BE5\u6765\u6E90\u4FEE\u590D" : row.status === "current" ? "\u5F53\u524D\u7248\u672C\u5DF2\u662F\u8BE5\u9891\u9053\u6700\u65B0\uFF0C\u6216\u9AD8\u4E8E\u8BE5\u9891\u9053" : void 0;
690
- } else if (row.source.kind === "github") {
691
- const { owner, repo, ref } = row.source;
692
- const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref ?? "HEAD")}`;
693
- const found = await publicJson(url, this.fetchImpl, this.abort.signal);
694
- if (!/^[a-f0-9]{40}$/i.test(found.sha)) throw Error("GitHub \u8FD4\u56DE\u65E0\u6548\u63D0\u4EA4\u4FE1\u606F");
695
- row.latestCommit = found.sha.toLowerCase();
696
- row.target = `${row.name}@${row.spec}`;
697
- row.status = row.commit === row.latestCommit ? "current" : "available";
698
- row.message = row.commit ? row.status === "available" ? `\u63D0\u4EA4 ${short(row.commit)} \u2192 ${short(row.latestCommit)}` : "\u8BE5\u5206\u652F / \u6807\u7B7E\u7684\u63D0\u4EA4\u5DF2\u540C\u6B65" : "\u65E0\u6CD5\u8BFB\u53D6\u672C\u5730 Git \u63D0\u4EA4\uFF0C\u53EF\u91CD\u65B0\u5B89\u88C5\u5DF2\u68C0\u67E5\u7684\u6765\u6E90";
699
- if (ref && /^[a-f0-9]{7,40}$/i.test(ref)) {
700
- row.status = "pinned";
701
- row.message = "\u5B89\u88C5\u6765\u6E90\u56FA\u5B9A\u5728 Git \u63D0\u4EA4\uFF1B\u4FDD\u7559\u6B64\u63D0\u4EA4\uFF0C\u4E0D\u81EA\u52A8\u5207\u6362\u5206\u652F";
702
- }
703
- }
704
- row.checkedAt = Date.now();
705
- row.checkedEnabled = row.enabled;
706
- } catch (error) {
707
- row.status = "failed";
708
- row.message = error.message;
709
- delete row.target;
710
- }
711
- }
712
- start(kind, task) {
713
- if (this.flight) throw Error("\u5DF2\u6709\u63D2\u4EF6\u64CD\u4F5C\u6B63\u5728\u6267\u884C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210");
714
- this.job = { id: randomUUID(), kind, state: "running", startedAt: Date.now(), results: [], total: 0 };
715
- this.error = void 0;
716
- this.flight = Promise.resolve().then(() => task(this.job)).then(() => {
717
- this.job.state = this.job.cancelRequested ? "cancelled" : "done";
718
- }, (e) => {
719
- this.job.state = this.job.cancelRequested ? "cancelled" : "failed";
720
- this.job.message = e.message;
721
- }).finally(async () => {
722
- this.job.finishedAt = Date.now();
723
- try {
724
- await this.persist();
725
- } catch {
726
- this.error = "\u66F4\u65B0\u68C0\u67E5\u8BB0\u5F55\u672A\u80FD\u4FDD\u5B58\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u76EE\u5F55\u6743\u9650";
727
- }
728
- this.flight = void 0;
729
- });
730
- }
731
- check() {
732
- this.start("check", async (job) => {
733
- this.lastAttempt = this.now();
734
- await this.persist();
735
- await this.list();
736
- const rows = this.rows.filter((r) => ["npm", "github"].includes(r.source.kind));
737
- job.total = rows.length;
738
- let next = 0;
739
- await Promise.all(Array.from({ length: Math.min(3, rows.length) }, async () => {
740
- while (next < rows.length && !job.cancelRequested) {
741
- const row = rows[next++];
742
- await this.checkRow(row);
743
- job.results.push({ name: row.name, status: row.status, message: row.message });
744
- }
745
- }));
746
- this.checkedAt = Date.now();
747
- });
748
- }
749
- async installRow(row, registry) {
750
- const original = await jsonFile(join4(this.profile().dir, "package.json"));
751
- if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
752
- const current = (await this.service().listBundles()).find((b) => b.name === row.name);
753
- if (!current || current.enabled !== row.checkedEnabled || current.version !== row.version) throw Error("\u63D2\u4EF6\u7248\u672C\u6216\u542F\u7528\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
754
- const options2 = { enabled: row.enabled, requestId: this.job.id, ...registry ? { registry } : {} };
755
- let result;
756
- if (row.source.kind === "github") {
757
- this.gitAbort = new AbortController();
758
- try {
759
- result = await this.refreshGit(this.service(), this.profile(), row, options2, AbortSignal.any([this.abort.signal, this.gitAbort.signal]));
760
- } finally {
761
- this.gitAbort = void 0;
762
- }
763
- } else result = await outsideActivation(this.hmr(), () => this.service().installBundle(row.target, options2));
764
- if (installFailed(result)) return { name: row.name, ...installFailure(result), expectedVersion: row.latestVersion };
765
- const installed = (await this.service().listBundles()).find((b) => b.name === row.name);
766
- if (row.source.kind === "npm" && installed?.version !== row.latestVersion) return {
767
- name: row.name,
768
- status: "failed",
769
- message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${row.latestVersion} \u4E0D\u540C\uFF1B\u8BF7\u7A0D\u540E\u91CD\u65B0\u68C0\u67E5\uFF0C\u53EF\u80FD\u662F\u955C\u50CF\u5C1A\u672A\u540C\u6B65\u6216\u53D1\u884C\u65F6\u95F4\u9650\u5236`
770
- };
771
- if (row.source.kind === "github") {
772
- const lock = await locks(this.profile().dir);
773
- const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[row.name]?.version, npm = lock.npm?.packages?.[`node_modules/${row.name}`]?.resolved;
774
- if (githubCommit(lock.preferNpm ? npm ?? pnpm : pnpm ?? npm) !== row.latestCommit) return {
775
- name: row.name,
776
- status: "failed",
777
- restartRequired: true,
778
- message: "\u5B9E\u9645 Git \u63D0\u4EA4\u4E0E\u68C0\u67E5\u7ED3\u679C\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\uFF1B\u4E0D\u628A\u4ECD\u505C\u7559\u5728\u65E7\u9501\u6587\u4EF6\u7684\u5B89\u88C5\u62A5\u544A\u4E3A\u6210\u529F"
779
- };
780
- }
781
- const reloadPending = result.application === "restart-required" && this.onInstalled?.(row.name) === true;
782
- return { name: row.name, status: "installed", application: reloadPending ? "reload-pending" : result.application, message: reloadPending ? "\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u52A0\u8F7D\uFF1B\u8FD0\u884C\u7248\u672C\u89C1\u9762\u677F\u9876\u90E8" : result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 DSH \u540E\u751F\u6548" : "\u5B89\u88C5\u5B8C\u6210", restartRequired: !reloadPending && result.application === "restart-required" };
783
- }
784
- update(names, registry) {
785
- if (!Array.isArray(names) || !names.length || names.length > 200 || names.some((n) => typeof n !== "string") || new Set(names).size !== names.length) throw Error("\u8BF7\u9009\u62E9\u8981\u66F4\u65B0\u7684\u63D2\u4EF6");
786
- const selected = names.map((name) => this.rows.find((r) => r.name === name));
787
- if (selected.some((r) => !r || !["available", "failed"].includes(r.status) || !r.target || Date.now() - r.checkedAt > 6e5)) throw Error("\u8BF7\u5148\u68C0\u67E5\u66F4\u65B0\uFF0C\u518D\u9009\u62E9\u53EF\u66F4\u65B0\u7684\u63D2\u4EF6\uFF08\u68C0\u67E5\u7ED3\u679C\u6709\u6548\u671F 10 \u5206\u949F\uFF09");
788
- selected.sort((a, b) => Number(a.name === "dsh-multi-router") - Number(b.name === "dsh-multi-router"));
789
- this.start("update", async (job) => {
790
- job.total = selected.length;
791
- for (const row of selected) {
792
- if (job.cancelRequested) break;
793
- job.current = row.name;
794
- let result;
795
- try {
796
- result = await this.installRow(row, registry);
797
- } catch (e) {
798
- result = { name: row.name, status: "failed", message: e.message };
799
- }
800
- job.results.push(result);
801
- row.status = result.status;
802
- row.message = result.message;
803
- }
804
- delete job.current;
805
- await this.list();
806
- });
807
- }
808
- install(spec, registry) {
809
- if (typeof spec !== "string" || spec.length > 600 || spec.startsWith("-") || /[\s\x00-\x1f]/.test(spec)) throw Error("\u8BF7\u8F93\u5165\u4E00\u4E2A npm \u5305\u540D\u6216 GitHub \u5B89\u88C5\u5730\u5740");
810
- const named = /^((?:@[^/]+\/)?[^@]+)(?:@(.+))?$/.exec(spec);
811
- const github = parsePluginSource("placeholder", spec);
812
- const npm = named && parsePluginSource(named[1], named[2] || "latest");
813
- if (github.kind !== "github" && npm?.kind !== "npm") throw Error("\u4EC5\u652F\u6301 npm \u5305\u540D / \u7248\u672C\u6216 github:\u7528\u6237/\u4ED3\u5E93");
814
- this.start("install", async (job) => {
815
- job.total = 1;
816
- job.current = spec;
817
- let expected;
818
- if (npm?.kind === "npm" && github.kind !== "github") {
819
- const found = await this.lookup(this.service(), this.profile(), `${npm.package}@${named[2] || "latest"}`, this.abort.signal, registry);
820
- if (!parseWorkBuddyVersion(found.version)) throw Error("npm \u672A\u8FD4\u56DE\u6709\u6548\u7248\u672C\uFF0C\u8BF7\u68C0\u67E5\u5B89\u88C5\u6E90\u540E\u91CD\u8BD5");
821
- expected = found.version;
822
- }
823
- const result = await outsideActivation(this.hmr(), () => this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} }));
824
- if (installFailed(result)) {
825
- job.results.push({ name: result.bundle ?? spec, ...installFailure(result), expectedVersion: expected });
826
- delete job.current;
827
- await this.list();
828
- return;
829
- }
830
- const installed = (await this.service().listBundles()).find((b) => b.name === (result.bundle ?? named?.[1]));
831
- if (expected && installed?.version !== expected) {
832
- job.results.push({
833
- name: result.bundle ?? named[1],
834
- status: "failed",
835
- expectedVersion: expected,
836
- installedVersion: installed?.version,
837
- message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${expected} \u4E0D\u540C\uFF1B\u8BF7\u5207\u6362\u300Cnpm \u5B98\u65B9\u300D\u540E\u91CD\u65B0\u68C0\u67E5\u5E76\u5B89\u88C5\u6307\u5B9A\u7248\u672C\u3002`
838
- });
839
- delete job.current;
840
- await this.list();
841
- return;
842
- }
843
- const reloadPending = !result.error && result.packageResult?.exitCode === 0 && result.application === "restart-required" && this.onInstalled?.(result.bundle) === true;
844
- job.results.push({
845
- name: result.bundle ?? spec,
846
- status: result.error || ["failed", "cancelled"].includes(result.application) || result.packageResult && result.packageResult.exitCode !== 0 ? "failed" : "installed",
847
- message: result.error ? `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error.code}\uFF09\uFF0C\u8BF7\u67E5\u770B DSH \u63D2\u4EF6\u5B89\u88C5\u65E5\u5FD7` : reloadPending ? "\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u52A0\u8F7D" : result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F DSH" : "\u5B89\u88C5\u5B8C\u6210",
848
- application: reloadPending ? "reload-pending" : result.application,
849
- pendingBuilds: result.pendingBuilds ?? [],
850
- restartRequired: !reloadPending && result.application === "restart-required"
851
- });
852
- delete job.current;
853
- await this.list();
854
- });
855
- }
856
- async action(input) {
857
- if (!this.state().available) throw Error("\u5F53\u524D DSH \u672A\u63D0\u4F9B\u63D2\u4EF6\u7BA1\u7406\u63A5\u53E3\uFF0C\u8BF7\u5347\u7EA7 DSH \u540E\u91CD\u8BD5");
858
- if (input.registry && !["https://registry.npmjs.org/", "https://registry.npmmirror.com/"].includes(input.registry)) throw Error("\u65E0\u6548\u7684 npm \u6765\u6E90");
859
- if (input.action === "plugins.list") {
860
- if (!this.flight) await this.list();
861
- } else if (input.action === "plugins.dismiss") await this.dismiss(input.keys);
862
- else if (input.action === "plugins.check") this.check();
863
- else if (input.action === "plugins.update") this.update(input.names, input.registry);
864
- else if (input.action === "plugins.install") this.install(input.spec, input.registry);
865
- else if (input.action === "plugins.cancel") {
866
- if (this.job?.state === "running") {
867
- this.job.cancelRequested = true;
868
- if (this.gitAbort) this.gitAbort.abort();
869
- else if (this.job.kind !== "check") await this.service().cancelInstall?.(this.job.id);
870
- }
871
- } else throw Error("\u672A\u77E5\u63D2\u4EF6\u64CD\u4F5C");
872
- }
873
- };
874
-
875
- // src/workbuddy-login.mjs
876
- import { randomBytes as randomBytes2 } from "node:crypto";
877
- import { readFile as readFile3 } from "node:fs/promises";
878
- import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
879
- var WorkBuddyLogin = class {
880
- constructor(file, { fetchImpl = (...args) => fetch(...args), now = Date.now } = {}) {
881
- this.file = file;
882
- this.fetchImpl = fetchImpl;
883
- this.now = now;
884
- this.abort = new AbortController();
885
- this.abort.signal.addEventListener("abort", () => this.finish(), { once: true });
886
- }
887
- async reserve(field, ms) {
888
- await withFileLock2(this.file, async () => {
889
- let state = {};
890
- try {
891
- state = JSON.parse(await readFile3(this.file, "utf8"));
892
- } catch (e) {
893
- if (e.code !== "ENOENT") throw Error("\u767B\u5F55\u51B7\u5374\u8BB0\u5F55\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u68C0\u67E5\u672C\u673A\u6587\u4EF6");
894
- }
895
- if (state.pausedUntil > this.now() || state[field] > this.now()) throw Error("\u8BF7\u6C42\u51B7\u5374\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5E73\u53F0\u9650\u5236\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u5904\u7406");
896
- state[field] = this.now() + ms;
897
- await writePrivateJson(this.file, state);
898
- });
899
- }
900
- async pause() {
901
- this.pending = void 0;
902
- await withFileLock2(this.file, async () => {
903
- const state = JSON.parse(await readFile3(this.file, "utf8"));
904
- state.pausedUntil = this.now() + 15 * 6e4;
905
- await writePrivateJson(this.file, state);
906
- });
907
- }
908
- async post(path, body) {
909
- let response;
910
- try {
911
- response = await this.fetchImpl(`https://www.workbuddy.cn/v2/plugin/login/${path}`, {
912
- method: "POST",
913
- headers: { "Content-Type": "application/json", Accept: "application/json", Origin: "https://www.workbuddy.cn", Referer: "https://www.workbuddy.cn/" },
914
- body: JSON.stringify(body),
915
- signal: AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]),
916
- redirect: "error"
917
- });
918
- } catch {
919
- throw Error("\u767B\u5F55\u8BF7\u6C42\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u91CD\u8BD5\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u53D1\u77ED\u4FE1");
920
- }
921
- if ([403, 429].includes(response.status)) {
922
- await this.pause();
923
- throw Error("\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u767B\u5F55\u8BF7\u6C42\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
924
- }
925
- if (!response.ok) throw Error("\u5B98\u65B9\u767B\u5F55\u670D\u52A1\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
926
- let result;
927
- try {
928
- result = JSON.parse(await readLimitedText(response, 256 * 1024, "Login"));
929
- } catch {
930
- throw Error("\u767B\u5F55\u54CD\u5E94\u65E0\u6CD5\u8BC6\u522B\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
931
- }
932
- if (result?.code !== 0) {
933
- if ([403, 429].includes(result?.code) || /频繁|图形验证码|滑块|安全验证|captcha|risk|too many|frequent|验证失败次数/i.test(result?.msg || "")) {
934
- await this.pause();
935
- throw Error("\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u767B\u5F55\u8BF7\u6C42\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
936
- }
937
- throw Error(path === "token" ? "\u767B\u5F55\u672A\u6210\u529F\uFF0C\u8BF7\u68C0\u67E5\u9A8C\u8BC1\u7801\u662F\u5426\u6B63\u786E\u3001\u8FC7\u671F\uFF1B\u4EC5\u652F\u6301\u5DF2\u6709\u8D26\u53F7" : "\u77ED\u4FE1\u672A\u53D1\u9001\u6210\u529F\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u6709\u8D26\u53F7\u53CA\u624B\u673A\u53F7\uFF0C\u6216\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
938
- }
939
- return result.data ?? {};
940
- }
941
- async send(phone) {
942
- if (typeof phone !== "string" || !/^1[3-9]\d{9}$/.test(phone)) throw Error("\u8BF7\u8F93\u5165 11 \u4F4D\u4E2D\u56FD\u5927\u9646\u624B\u673A\u53F7");
943
- if (this.flight) throw Error("\u767B\u5F55\u8BF7\u6C42\u6B63\u5728\u5904\u7406\u4E2D");
944
- this.flight = (async () => {
945
- await this.reserve("smsAt", 6e4);
946
- this.pending = void 0;
947
- const data = await this.post("send-sms", { phone });
948
- const expires = Math.min(300, Math.max(1, Number(data.expires_in) || 300)) * 1e3;
949
- this.pending = { phone, challenge: randomBytes2(24).toString("hex"), expiresAt: this.now() + expires, attempts: 0 };
950
- clearTimeout(this.expiryTimer);
951
- this.expiryTimer = setTimeout(() => this.finish(), expires);
952
- this.expiryTimer.unref();
953
- return { challenge: this.pending.challenge, expiresAt: this.pending.expiresAt, resendAt: this.now() + 6e4 };
954
- })();
955
- try {
956
- return await this.flight;
957
- } finally {
958
- this.flight = void 0;
959
- }
960
- }
961
- async complete(challenge, code, verify = true) {
962
- if (typeof challenge !== "string" || !this.pending || challenge !== this.pending.challenge || this.pending.expiresAt <= this.now()) throw Error("\u672C\u6B21\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u63D2\u4EF6\u5DF2\u91CD\u542F\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6\u9A8C\u8BC1\u7801");
963
- if (this.flight) throw Error("\u767B\u5F55\u8BF7\u6C42\u6B63\u5728\u5904\u7406\u4E2D");
964
- if (this.pending.document) return this.pending;
965
- if (typeof code !== "string" || !/^\d{4,8}$/.test(code) || typeof verify !== "boolean") throw Error("\u8BF7\u8F93\u5165\u6536\u5230\u7684\u77ED\u4FE1\u9A8C\u8BC1\u7801");
966
- if (this.pending.attempts >= 5) throw Error("\u672C\u6B21\u9A8C\u8BC1\u7801\u5C1D\u8BD5\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u65B0\u83B7\u53D6");
967
- this.flight = (async () => {
968
- await this.reserve("loginAt", 5e3);
969
- const pending = this.pending;
970
- pending.attempts++;
971
- const data = await this.post("token", { login_method: "phone", phone: pending.phone, sms_code: code });
972
- const accessToken = data.accessToken || data.access_token, refreshToken = data.refreshToken || data.refresh_token;
973
- let token;
974
- try {
975
- token = workbuddyToken(accessToken);
976
- } catch {
977
- throw Error("\u767B\u5F55\u8FD4\u56DE\u7684\u4EE4\u724C\u4E0D\u5C5E\u4E8E WorkBuddy \u4E2D\u56FD\u7248\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
978
- }
979
- if (typeof refreshToken !== "string" || !refreshToken || !Number.isFinite(token.exp) || token.exp * 1e3 <= this.now()) throw Error("\u767B\u5F55\u54CD\u5E94\u7F3A\u5C11\u6709\u6548\u51ED\u8BC1\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
980
- let credential = { accessToken, refreshToken, uid: token.sub, expiresAtMs: token.exp * 1e3, domain: "www.workbuddy.cn", source: "dsh" };
981
- if (verify) {
982
- try {
983
- const rotated = await refreshWorkBuddyCheckinCredential(credential, { fetchImpl: this.fetchImpl, signal: AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]) });
984
- credential = {
985
- ...credential,
986
- accessToken: rotated.accessToken,
987
- refreshToken: rotated.refreshToken || credential.refreshToken,
988
- expiresAtMs: Date.now() + rotated.expiresInSec * 1e3
989
- };
990
- } catch {
991
- pending.warning = "\u767B\u5F55\u51ED\u8BC1\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u5237\u65B0\u4EE4\u724C\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1B\u8BF7\u7A0D\u540E\u5728\u8D26\u53F7\u8BE6\u60C5\u68C0\u67E5\u767B\u5F55\u6709\u6548\u671F\u3002";
992
- }
993
- }
994
- delete pending.phone;
995
- pending.document = { version: 1, credential };
996
- return pending;
997
- })();
998
- try {
999
- return await this.flight;
1000
- } finally {
1001
- this.flight = void 0;
1002
- }
1003
- }
1004
- finish() {
1005
- clearTimeout(this.expiryTimer);
1006
- this.pending = void 0;
1007
- }
1008
- };
1009
-
1010
- // src/manager.mjs
1011
- var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
1012
- var SUMMARY_PATH = `${MANAGEMENT_PATH}/summary`;
1013
- var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
1014
- var ManagementError = class extends Error {
1015
- constructor(status, message) {
1016
- super(message);
1017
- this.status = status;
1018
- }
1019
- };
1020
- var bad = (message) => {
1021
- throw new ManagementError(400, message);
1022
- };
1023
- var finite = (value, fallback) => Number.isSafeInteger(value) && value > 0 ? value : fallback;
1024
- function modelInfo(row) {
1025
- const billing = row.billing ? {
1026
- free: row.billing.free === true,
1027
- rateUnknown: row.billing.rateUnknown === true,
1028
- ...typeof row.billing.credits === "string" ? { credits: row.billing.credits } : {}
1029
- } : void 0;
1030
- const reasoning = row.reasoning?.supported ? {
1031
- supports: true,
1032
- supportedEfforts: row.reasoning.supported,
1033
- defaultEffort: row.reasoning.defaultEffort,
1034
- canDisableThinking: row.reasoning.supported.includes("off")
1035
- } : row.reasoning;
1036
- return {
1037
- id: row.id,
1038
- name: row.name || row.id,
1039
- ...contextInfo(row),
1040
- maxTokens: Math.min(finite(row.maxTokens, 32e3), contextInfo(row).maxContextWindow),
1041
- supportsImages: row.supportsImages === true || row.multimodal === true || row.input?.includes("image") === true,
1042
- ...reasoning ? { reasoning } : {},
1043
- ...billing ? { billing } : {},
1044
- ...typeof row.creditMultiplier === "number" && Number.isFinite(row.creditMultiplier) && row.creditMultiplier >= 0 ? { creditMultiplier: row.creditMultiplier } : {},
1045
- ...Array.isArray(row.promotions) ? { promotions: row.promotions.map((p) => ({ start: p.start, end: p.end, factor: p.factor, priority: p.priority, label: typeof p.label === "string" ? p.label : "" })) } : {}
1046
- };
1047
- }
1048
- var Manager = class {
1049
- constructor(file, router, onChange = () => {
1050
- }, pluginAccess = {}) {
1051
- this.file = file;
1052
- this.router = router;
1053
- this.onChange = onChange;
1054
- this.catalogFile = join5(dirname3(file), "catalogs.json");
1055
- this.catalogs = /* @__PURE__ */ new Map();
1056
- this.catalogErrors = /* @__PURE__ */ new Map();
1057
- this.refreshes = /* @__PURE__ */ new Map();
1058
- this.closed = false;
1059
- this.monitorFlights = /* @__PURE__ */ new Map();
1060
- this.loginInfo = /* @__PURE__ */ new Map();
1061
- this.telemetry = new Telemetry(join5(dirname3(file), "usage.json"));
1062
- this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join5(dirname3(file), "plugin-updates.json"), onInstalled: (name) => {
1063
- if (name !== "dsh-multi-router" || !this.runtime?.canReload()) return false;
1064
- this.runtime.schedule();
1065
- return true;
1066
- } });
1067
- this.checkins = new Checkins(join5(dirname3(file), "checkins.json"), this);
1068
- this.workbuddyLogin = new WorkBuddyLogin(join5(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
1069
- this.setupFile = join5(dirname3(file), "initial-setup.json");
1070
- this.api = new ApiService(join5(dirname3(file), "api-service.json"), this);
1071
- router.telemetry = this.telemetry;
1072
- router.monitor = (a) => this.monitorAccount(a, { maxAgeMs: 6e4 });
1073
- router.prepareFree = () => this.checkFreeModels(true);
1074
- router.freeModels = (account) => this.freeModels(account);
1075
- }
1076
- async load() {
1077
- this.raw = JSON.parse(await readFile4(this.file, "utf8"));
1078
- try {
1079
- this.setup = JSON.parse(await readFile4(this.setupFile, "utf8"));
1080
- } catch {
1081
- this.setup = void 0;
1082
- }
1083
- this.config = validateConfig(this.raw, this.file);
1084
- await this.checkins.load();
1085
- await this.plugins.load();
1086
- try {
1087
- const saved = JSON.parse(await readFile4(this.catalogFile, "utf8"));
1088
- for (const account of this.config.accounts) {
1089
- const item = saved[account.id];
1090
- if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
1091
- this.catalogs.set(account.id, { ...item, ...item.contextVersion !== 1 ? { fetchedAt: 0 } : {}, models: item.models.filter((m) => typeof m.id === "string" && m.id.length > 0).map(modelInfo) });
1092
- }
1093
- }
1094
- } catch {
1095
- }
1096
- this.telemetry.configure(this.config.usageRetentionDays);
1097
- await this.telemetry.load();
1098
- await this.api.load();
1099
- await Promise.all(this.config.accounts.map((a) => this.readLogin(a)));
1100
- this.publish();
1101
- }
1102
- async readLogin(account) {
1103
- try {
1104
- const raw = await jsonCredential(account.credentialFile), c = raw.credential;
1105
- this.loginInfo.set(account.id, account.provider.startsWith("qoder") ? { state: raw.pat ? "configured" : "missing", kind: "PAT", refreshable: false } : {
1106
- state: !c?.accessToken ? "missing" : c?.expiresAtMs > Date.now() ? "valid" : c?.refreshToken ? "refresh-needed" : "expired",
1107
- name: c?.nickname || c?.accountName || c?.uid || c?.userId,
1108
- identity: c?.uid || c?.userId,
1109
- expiresAt: c?.expiresAtMs,
1110
- refreshExpiresAt: c?.refreshExpiresAtMs,
1111
- refreshable: !!c?.refreshToken,
1112
- kind: "\u5BA2\u6237\u7AEF\u767B\u5F55\u6001",
1113
- edition: c?.edition
1114
- });
1115
- } catch {
1116
- this.loginInfo.set(account.id, { state: "missing" });
1117
- }
1118
- }
1119
- async syncLocalAccounts(provider, force = false) {
1120
- if (this.localScan) return this.localScan;
1121
- this.localScan = (async () => {
1122
- const discovered = await discoverLocalAccounts(provider);
1123
- this.localDiscovery = [
1124
- ...(this.localDiscovery ?? []).filter((s) => !discovered.status.some((n) => n.provider === s.provider)),
1125
- ...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
1126
- ];
1127
- if (this.closed || !discovered.accounts.length) return;
1128
- await withFileLock3(this.file, async () => {
1129
- if (this.closed) return;
1130
- let raw = JSON.parse(await readFile4(this.file, "utf8"));
1131
- let changed = false;
1132
- const saved = /* @__PURE__ */ new Map();
1133
- for (const item of discovered.accounts) {
1134
- const key = localAccountKey(item.provider, item.document);
1135
- if (!force && raw.ignoredLocalAccounts?.some((k) => normalizeLocalAccountKey(k) === key)) continue;
1136
- const config = validateConfig(raw, this.file);
1137
- let existing, current;
1138
- for (const a of config.accounts.filter((a2) => a2.provider === item.provider)) {
1139
- try {
1140
- const value = await jsonCredential(a.credentialFile);
1141
- if (localAccountKey(a.provider, value) === key) {
1142
- existing = a;
1143
- current = value;
1144
- break;
1145
- }
1146
- } catch {
1147
- }
1148
- }
1149
- const id = existing?.id ?? `local_${createHash("sha256").update(key).digest("hex").slice(0, 20)}`;
1150
- const recordSaved = () => {
1151
- if (!saved.has(item.provider)) saved.set(item.provider, /* @__PURE__ */ new Set());
1152
- saved.get(item.provider).add(id);
1153
- };
1154
- let sameDevice = true;
1155
- if (existing && item.identityDocument) {
1156
- try {
1157
- const identity = await jsonCredential(existing.identityFile);
1158
- sameDevice = identity.machineId === item.identityDocument.machineId && identity.deviceId === item.identityDocument.deviceId;
1159
- } catch {
1160
- sameDevice = false;
1161
- }
1162
- }
1163
- if (existing && sameDevice && current.credential.expiresAtMs >= item.document.credential.expiresAtMs) {
1164
- recordSaved();
1165
- continue;
1166
- }
1167
- const base = join5(dirname3(this.file), "accounts");
1168
- await mkdir2(base, { recursive: true, mode: 448 });
1169
- const staging = await mkdtemp2(join5(base, `${id}-`));
1170
- try {
1171
- const credentialFile = join5(staging, "credential.json"), identityFile = join5(staging, "identity.json");
1172
- await importAccount({ ...item, credentialFile, identityFile });
1173
- const paths = {
1174
- credentialFile: relative2(dirname3(this.file), credentialFile),
1175
- ...item.provider.startsWith("trae") ? { identityFile: relative2(dirname3(this.file), identityFile) } : {}
1176
- };
1177
- const next = structuredClone(raw);
1178
- if (existing) Object.assign(next.accounts.find((a) => a.id === id), paths);
1179
- else next.accounts.push({
1180
- id,
1181
- provider: item.provider,
1182
- name: item.document.credential.nickname || item.document.credential.accountName || item.document.credential.uid || item.document.credential.userId,
1183
- ...paths
1184
- });
1185
- next.ignoredLocalAccounts = (raw.ignoredLocalAccounts ?? []).filter((k) => normalizeLocalAccountKey(k) !== key);
1186
- validateConfig(next, this.file);
1187
- await writePrivateJson(this.file, next);
1188
- raw = next;
1189
- changed = true;
1190
- recordSaved();
1191
- this.router.clients.delete(id);
1192
- this.catalogs.delete(id);
1193
- this.catalogErrors.delete(id);
1194
- } catch (e) {
1195
- await rm3(staging, { recursive: true, force: true });
1196
- this.localDiscovery = this.localDiscovery.map((s) => s.provider === item.provider ? { ...s, state: "unreadable", code: "import_failed" } : s);
1197
- }
1198
- }
1199
- if (changed) {
1200
- this.raw = raw;
1201
- this.config = validateConfig(raw, this.file);
1202
- await Promise.all(this.config.accounts.map((a) => this.readLogin(a)));
1203
- this.publish();
1204
- }
1205
- this.localDiscovery = this.localDiscovery.map((s) => discovered.status.some((d) => d.provider === s.provider) ? { ...s, savedCount: saved.get(s.provider)?.size ?? 0 } : s);
1206
- }, { waitMs: 3e4 });
1207
- })().finally(() => {
1208
- this.localScan = void 0;
1209
- });
1210
- return this.localScan;
1211
- }
1212
- async finishInitialSetup() {
1213
- if (this.setup?.completedAt) return;
1214
- await withFileLock3(this.setupFile, async () => {
1215
- try {
1216
- this.setup = JSON.parse(await readFile4(this.setupFile, "utf8"));
1217
- } catch {
1218
- }
1219
- if (this.setup?.completedAt) return;
1220
- this.setup = { completedAt: Date.now(), dismissed: false };
1221
- await writePrivateJson(this.setupFile, this.setup);
1222
- });
1223
- }
1224
- async monitorAccount(account, { force = false, maxAgeMs = 3e5 } = {}) {
1225
- if (this.monitorFlights.has(account.id)) return this.monitorFlights.get(account.id);
1226
- const cached = this.telemetry.row("accounts", account.id).balance;
1227
- const last = cached?.error ? cached.attemptedAt : cached?.fetchedAt;
1228
- const ttl = cached?.error ? 3e5 : maxAgeMs;
1229
- if (!force && Number.isFinite(last) && Date.now() >= last && Date.now() - last < ttl) return;
1230
- const promise = (async () => {
1231
- try {
1232
- const client = await this.router.client(account);
1233
- if (!client.accountInfo) return;
1234
- const snapshot = await client.accountInfo(AbortSignal.timeout(3e4));
1235
- if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
1236
- this.loginInfo.set(account.id, snapshot.login);
1237
- await this.telemetry.observe(account.id, snapshot);
1238
- } catch (e) {
1239
- if (this.closed) return;
1240
- const old = this.telemetry.row("accounts", account.id).balance;
1241
- await this.telemetry.observe(account.id, { ...old, error: { code: "usage_fetch_failed", status: Number(e.status ?? e.failure?.status) || 502 }, attemptedAt: Date.now() });
1242
- await this.readLogin(account);
1243
- }
1244
- })().finally(() => this.monitorFlights.delete(account.id));
1245
- this.monitorFlights.set(account.id, promise);
1246
- return promise;
1247
- }
1248
- async monitorAll(provider, options2) {
1249
- await Promise.all(this.config.accounts.filter((a) => (!provider || a.provider === provider) && (options2?.force || a.enabled !== false)).map((a) => this.monitorAccount(a, options2)));
1250
- }
1251
- freeAccounts() {
1252
- const targets = Object.values(this.config.routes).filter((r) => r.freeOnly).flatMap((r) => r.targets);
1253
- return this.config.accounts.filter((a) => a.enabled !== false && targets.some((t) => t.provider === a.provider && (!t.account || t.account === a.id)));
1254
- }
1255
- freeModels(account) {
1256
- const saved = this.catalogs.get(account.id);
1257
- if (this.catalogErrors.has(account.id) || !saved || beijingDay(saved.fetchedAt) !== beijingDay(Date.now())) return [];
1258
- return saved.models.map((m) => modelWithCurrentPromotion(m)).filter((m) => isFreeModel(m));
1259
- }
1260
- async checkFreeModels(force = false) {
1261
- await Promise.all(this.freeAccounts().filter((a) => {
1262
- const saved = this.catalogs.get(a.id);
1263
- if (force) return true;
1264
- if (Date.now() - (this.catalogErrors.get(a.id)?.at ?? 0) < 3e5) return false;
1265
- return !saved || beijingDay(saved.fetchedAt) !== beijingDay(Date.now()) || saved.pricingVersion !== 1;
1266
- }).map((a) => this.refreshAccount(a, "pricing")));
1267
- }
1268
- freeStatus(row) {
1269
- const accounts = this.config.accounts.filter((a) => a.enabled !== false && row.targets.some((t) => t.provider === a.provider && (!t.account || t.account === a.id)));
1270
- return {
1271
- checking: accounts.some((a) => this.refreshes.has(a.id)),
1272
- accounts: accounts.map((a) => ({
1273
- id: a.id,
1274
- provider: a.provider,
1275
- name: a.name ?? a.id,
1276
- checkedAt: this.catalogs.get(a.id)?.fetchedAt,
1277
- error: this.catalogErrors.get(a.id),
1278
- models: this.freeModels(a).map((m) => ({ id: m.id, name: m.name }))
1279
- }))
1280
- };
1281
- }
1282
- freeRoutes(parentId, row) {
1283
- const groups = /* @__PURE__ */ new Map();
1284
- for (const account of this.config.accounts.filter((a) => a.enabled !== false)) {
1285
- for (const model of this.freeModels(account)) {
1286
- if (!row.targets.some((t) => t.provider === account.provider && (!t.account || t.account === account.id) && (!t.model || t.model === model.id) && (!t.models || t.models.includes(model.id)))) continue;
1287
- const id = `free_${createHash("sha256").update(JSON.stringify([parentId, account.provider, model.id])).digest("hex").slice(0, 24)}`;
1288
- if (!groups.has(id)) groups.set(id, { id, models: [], targets: [], accountNames: [], modelName: model.name, provider: account.provider, modelId: model.id });
1289
- const group = groups.get(id);
1290
- group.models.push({ model, provider: account.provider, accountId: account.id });
1291
- group.targets.push({ provider: account.provider, account: account.id, model: model.id });
1292
- group.accountNames.push(account.name ?? account.id);
1293
- }
1294
- }
1295
- return [...groups.values()].map(({ models, ...group }) => {
1296
- const preference = row.freeModels?.[group.id] ?? {};
1297
- const context = resolveContext(models.map((s) => s.model), preference.contextWindow ?? (row.contextMode === "custom" ? row.contextWindow : null), row.contextWindow);
1298
- const eligible = models.filter((s) => contextInfo(s.model).maxContextWindow >= context.contextWindow);
1299
- const eligibleAccountIds = eligible.map((s) => s.accountId);
1300
- const reasoning = routeReasoning(eligible);
1301
- const selected = preference.reasoningEffort;
1302
- return {
1303
- ...group,
1304
- ...context,
1305
- eligibleAccountIds,
1306
- targets: group.targets.filter((t) => eligibleAccountIds.includes(t.account)),
1307
- accountNames: group.accountNames.filter((_, i) => eligibleAccountIds.includes(models[i].accountId)),
1308
- enforceContext: true,
1309
- parentId,
1310
- dynamicFree: true,
1311
- freeOnly: true,
1312
- strategy: "failover",
1313
- preferExpiringCredits: false,
1314
- name: `Free \xB7 ${group.modelName} \xB7 ${PROVIDER_NAMES[group.provider]}`,
1315
- reasoning,
1316
- ...selected && reasoning?.supportedEfforts.includes(selected) ? { reasoningEffort: selected } : {},
1317
- configuredEffort: selected ?? null,
1318
- effortUnavailable: !!selected && !reasoning?.supportedEfforts.includes(selected),
1319
- visible: row.visible !== false && preference.visible !== false,
1320
- selected: preference.visible !== false,
1321
- supportsImages: eligible.every(({ model }) => model.supportsImages),
1322
- maxTokens: Math.min(row.maxTokens, context.contextWindow, ...eligible.map(({ model }) => model.maxTokens))
1323
- };
1324
- });
1325
- }
1326
- adapterModels() {
1327
- const rows = this.routeModels();
1328
- return [
1329
- ...rows.filter((r) => r.freeOnly).flatMap((r) => this.freeRoutes(r.id, r)),
1330
- ...rows.filter((r) => !r.freeOnly),
1331
- ...rows.filter((r) => r.freeOnly).map((r) => ({ ...r, visible: false }))
1332
- ];
1333
- }
1334
- publish() {
1335
- this.telemetry.configure(this.config.usageRetentionDays);
1336
- this.revision = revisionOf(this.raw);
1337
- const routes = Object.fromEntries(this.routeModels().map((row) => [row.id, row]));
1338
- for (const model of this.adapterModels().filter((m) => m.dynamicFree)) routes[model.id] = model;
1339
- for (const provider of PROVIDERS) for (const model of this.providerModels(provider)) {
1340
- routes[`${provider}::${model.id}`] = { ...model, targets: [{ provider, model: model.id }] };
1341
- }
1342
- this.router.updateConfig({ ...this.config, routes });
1343
- this.onChange();
1344
- }
1345
- providerModels(provider) {
1346
- const rows = /* @__PURE__ */ new Map();
1347
- for (const account of this.config.accounts.filter((a) => a.provider === provider && a.enabled !== false)) {
1348
- for (const model of this.catalogs.get(account.id)?.models ?? []) {
1349
- if (!rows.has(model.id)) rows.set(model.id, []);
1350
- rows.get(model.id).push(model);
1351
- }
1352
- }
1353
- return [...rows.values()].map((models) => {
1354
- const model = models[0], preference = this.config.models[provider]?.[model.id] ?? {};
1355
- const context = resolveContext(models, preference.contextWindow);
1356
- const eligible = models.filter((m) => contextInfo(m).maxContextWindow >= context.contextWindow);
1357
- const reasoning = routeReasoning(eligible.map((model2) => ({ model: model2, provider })));
1358
- const selected = preference.reasoningEffort;
1359
- return {
1360
- ...model,
1361
- ...context,
1362
- enforceContext: true,
1363
- name: preference.name ?? model.name,
1364
- reasoning,
1365
- supportsImages: preference.supportsImages !== false && eligible.every((m) => m.supportsImages),
1366
- imageCapable: eligible.every((m) => m.supportsImages),
1367
- ...selected && reasoning?.supportedEfforts.includes(selected) ? { reasoningEffort: selected } : {},
1368
- configuredEffort: selected ?? null,
1369
- effortUnavailable: !!selected && !reasoning?.supportedEfforts.includes(selected),
1370
- maxTokens: Math.min(context.contextWindow, ...eligible.map((m) => m.maxTokens))
1371
- };
1372
- });
1373
- }
1374
- hiddenModels(provider) {
1375
- return this.providerModels(provider).filter((m) => !this.modelVisible(provider, m.id)).map((m) => m.id);
1376
- }
1377
- modelVisible(provider, id) {
1378
- return this.config.models[provider]?.[id]?.visible ?? this.config.modelVisibility[provider] ?? false;
1379
- }
1380
- routeModels(routes = this.config.routes) {
1381
- return Object.entries(routes).sort(([, a], [, b]) => Number(!!b.freeOnly) - Number(!!a.freeOnly)).map(([id, row]) => {
1382
- const effectiveTargets = row.freeOnly ? this.freeRoutes(id, row).flatMap((r) => r.targets) : row.targets;
1383
- const sources = effectiveTargets.flatMap((target) => this.config.accounts.filter((a) => a.enabled !== false && a.provider === target.provider && (!target.account || target.account === a.id)).flatMap((a) => {
1384
- const catalog = this.catalogs.get(a.id)?.models ?? [];
1385
- const models2 = target.models ? target.models.map((model) => catalog.find((m) => m.id === model)).filter(Boolean) : [matchModel(id, target, catalog)].filter(Boolean);
1386
- return models2.map((model) => ({ model, provider: a.provider, accountId: a.id }));
1387
- }));
1388
- const context = resolveContext(sources.map((s) => s.model), row.contextMode === "custom" ? row.contextWindow : null, row.contextWindow);
1389
- const eligible = sources.filter((s) => contextInfo(s.model).maxContextWindow >= context.contextWindow);
1390
- const models = eligible.map((s) => s.model), reasoning = routeReasoning(eligible);
1391
- const rates = {};
1392
- for (const source of eligible) {
1393
- const rate = creditRate(source.model, this.telemetry.now());
1394
- if (rate === void 0) continue;
1395
- rates[source.accountId] = rates[source.accountId] === void 0 ? rate : Math.min(rates[source.accountId], rate);
1396
- }
1397
- return {
1398
- ...row,
1399
- ...context,
1400
- enforceContext: true,
1401
- supportsImages: models.length > 0 && models.every((m) => m.supportsImages),
1402
- reasoning,
1403
- eligibleAccountIds: [...new Set(eligible.map((s) => s.accountId))],
1404
- creditRates: rates,
1405
- maxTokens: Math.min(row.maxTokens, context.contextWindow, ...models.map((m) => m.maxTokens)),
1406
- id,
1407
- name: row.name ?? id,
1408
- expiringStatus: { ...this.telemetry.data.expiringRounds[id] },
1409
- ...row.freeOnly ? { freeStatus: { ...this.freeStatus(row), modelRoutes: this.freeRoutes(id, row) } } : {}
1410
- };
1411
- });
1412
- }
1413
- async ensureCatalog(provider, force = false) {
1414
- await Promise.all(this.config.accounts.filter((a) => a.provider === provider && a.enabled !== false).map((a) => this.refreshAccount(a, force)));
1415
- }
1416
- async refreshAccount(account, force) {
1417
- const saved = this.catalogs.get(account.id);
1418
- if (!force && saved && Date.now() - saved.fetchedAt < this.config.catalogTtlMs) return;
1419
- if (!force && Date.now() - (this.catalogErrors.get(account.id)?.at ?? 0) < 1e4) return;
1420
- if (this.refreshes.has(account.id)) return this.refreshes.get(account.id);
1421
- const promise = (async () => {
1422
- try {
1423
- if (force === true) this.router.clients.delete(account.id);
1424
- const client = await this.router.client(account);
1425
- const models = (await client.models(AbortSignal.timeout(6e4), !!force)).map(modelInfo);
1426
- if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
1427
- this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, contextVersion: 1, models });
1428
- this.catalogErrors.delete(account.id);
1429
- await withFileLock3(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
1430
- } catch (error) {
1431
- if (this.closed || !this.config.accounts.some((a) => a.id === account.id)) return;
1432
- this.catalogErrors.set(account.id, { status: Number(error.status ?? error.failure?.status) || 502, code: "catalog_fetch_failed", at: Date.now() });
1433
- }
1434
- if (!this.closed) this.publish();
1435
- })().finally(() => this.refreshes.delete(account.id));
1436
- this.refreshes.set(account.id, promise);
1437
- return promise;
1438
- }
1439
- summaryState() {
1440
- const today = new Date(this.telemetry.now()).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
1441
- const daily = this.telemetry.data.days[today] ?? {};
1442
- return {
1443
- pluginVersion: version,
1444
- enabled: this.config.sidebarUsage,
1445
- today,
1446
- tokens: daily.tokens ?? 0,
1447
- requests: daily.requests ?? 0,
1448
- unmetered: daily.unmetered ?? 0,
1449
- totalTokens: Object.values(this.telemetry.data.accounts).reduce((sum, row) => sum + (row.tokens ?? 0), 0)
1450
- };
1451
- }
1452
- state() {
1453
- const status = this.router.status();
1454
- return {
1455
- pluginVersion: version,
1456
- startup: this.startup,
1457
- runtime: this.runtime?.state(),
1458
- revision: this.revision,
1459
- localDiscovery: this.localDiscovery ?? [],
1460
- setup: this.setup,
1461
- pluginUpdates: this.plugins.state(),
1462
- checkins: this.checkins.state(),
1463
- telemetry: this.telemetry.state(),
1464
- apiService: this.api.state(),
1465
- accounts: this.config.accounts.map((a) => ({
1466
- id: a.id,
1467
- provider: a.provider,
1468
- name: a.name ?? a.id,
1469
- enabled: a.enabled !== false,
1470
- status: status.find((s) => s.id === a.id),
1471
- login: this.loginInfo.get(a.id),
1472
- usage: this.telemetry.row("accounts", a.id),
1473
- modelCount: this.catalogs.get(a.id)?.models.length ?? 0,
1474
- fetchedAt: this.catalogs.get(a.id)?.fetchedAt,
1475
- error: this.catalogErrors.get(a.id)
1476
- })),
1477
- providers: PROVIDERS.map((id) => ({ id, name: PROVIDER_NAMES[id], models: this.providerModels(id).map((m) => ({
1478
- ...m,
1479
- visible: this.modelVisible(id, m.id)
1480
- })) })),
1481
- presets: Object.entries(ROUTE_PRESETS).map(([id, route]) => ({
1482
- id,
1483
- ...structuredClone(route),
1484
- providerCount: route.targets.length,
1485
- added: Object.hasOwn(this.config.routes, id),
1486
- availableProviders: [...new Set(route.targets.filter((t) => route.freeOnly ? this.config.accounts.some((a) => a.enabled !== false && a.provider === t.provider && this.freeModels(a).length) : matchModel(id, t, this.providerModels(t.provider))).map((t) => t.provider))]
1487
- })),
1488
- routes: this.routeModels(),
1489
- options: {
1490
- cooldownMs: this.config.cooldownMs,
1491
- requestTimeoutMs: this.config.requestTimeoutMs,
1492
- retryServerErrors: this.config.retryServerErrors,
1493
- usageRetentionDays: this.config.usageRetentionDays,
1494
- sidebarUsage: this.config.sidebarUsage
1495
- }
1496
- };
1497
- }
1498
- async action(input) {
1499
- if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
1500
- if (input.action === "route.preview") {
1501
- if (input.route?.freeOnly) bad("\u514D\u8D39\u6A21\u578B\u8BF7\u9010\u9879\u8BBE\u7F6E\u63A8\u7406\u7B49\u7EA7");
1502
- let config;
1503
- try {
1504
- config = validateConfig({ ...this.raw, routes: { [input.id || "preview"]: input.route } }, this.file);
1505
- } catch {
1506
- bad("\u8BF7\u5148\u9009\u62E9\u6709\u6548\u7684\u76EE\u6807\u6A21\u578B\u4E0E\u4E0A\u4E0B\u6587\u5BB9\u91CF");
1507
- }
1508
- const { reasoning, contextKnown } = this.routeModels(config.routes)[0];
1509
- return { routePreview: { reasoning, contextKnown } };
1510
- }
1511
- if (input.action === "model.test") {
1512
- const model = this.providerModels(input.provider).find((m) => m.id === input.id);
1513
- if (!model) bad("\u8BF7\u5148\u5237\u65B0\u5E76\u9009\u62E9\u5DF2\u53D1\u73B0\u7684\u6A21\u578B");
1514
- if (this.modelTestFlight || Date.now() - (this.modelTestAt ?? 0) < 1e4) bad("\u8BF7\u7B49\u5F85\u5F53\u524D\u6D4B\u8BD5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u95F4\u9694\u81F3\u5C11 10 \u79D2");
1515
- this.modelTestFlight = true;
1516
- this.modelTestAt = Date.now();
1517
- this.modelTestAbort = new AbortController();
1518
- const started = performance.now();
1519
- let modelTest;
1520
- try {
1521
- const { response } = await this.router.chat(
1522
- {
1523
- model: `${input.provider}::${input.id}`,
1524
- stream: true,
1525
- messages: [{ role: "system", content: "Answer briefly." }, { role: "user", content: "Reply with OK." }],
1526
- max_tokens: 16
1527
- },
1528
- AbortSignal.any([this.modelTestAbort.signal, AbortSignal.timeout(45e3)])
1529
- );
1530
- const reader = response.body.getReader();
1531
- let size = 0, text = "";
1532
- const decoder = new TextDecoder();
1533
- try {
1534
- for (; ; ) {
1535
- const chunk = await reader.read();
1536
- if (chunk.done) break;
1537
- size += chunk.value.byteLength;
1538
- if (size > 1024 * 1024) throw Error("probe_response_too_large");
1539
- text += decoder.decode(chunk.value, { stream: true });
1540
- }
1541
- text += decoder.decode();
1542
- let answered = false;
1543
- for (const line of text.split(/\r?\n/).filter((l) => l.startsWith("data:"))) {
1544
- if (line.slice(5).trim() === "[DONE]") continue;
1545
- const payload = JSON.parse(line.slice(5));
1546
- if (payload.error || payload.choices?.some((c) => c.finish_reason === "error")) throw Error("probe_stream_failed");
1547
- answered ||= payload.choices?.some((c) => c.delta?.content || c.delta?.reasoning_content || c.delta?.tool_calls?.length) ?? false;
1548
- }
1549
- if (!answered || !/^data:\s*\[DONE\]\s*$/m.test(text)) throw Error("probe_incomplete");
1550
- } finally {
1551
- await reader.cancel().catch(() => {
1552
- });
1553
- reader.releaseLock();
1554
- }
1555
- modelTest = { ok: true, elapsedMs: Math.round(performance.now() - started) };
1556
- } catch (error) {
1557
- modelTest = {
1558
- ok: false,
1559
- status: Number(error.status) || 502,
1560
- hint: error.hint || "\u6D4B\u8BD5\u672A\u901A\u8FC7\uFF0C\u8BF7\u67E5\u770B\u8D26\u53F7\u72B6\u6001\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002"
1561
- };
1562
- } finally {
1563
- this.modelTestFlight = false;
1564
- this.modelTestAbort = void 0;
1565
- }
1566
- return { ...this.state(), modelTest };
1567
- }
1568
- if (input.action?.startsWith("api.")) {
1569
- let result;
1570
- try {
1571
- result = await this.api.action(input);
1572
- } catch (e) {
1573
- bad(e.message);
1574
- }
1575
- return { ...this.state(), ...result };
1576
- }
1577
- if (input.action === "plugin.reload") {
1578
- if (!this.runtime) bad("\u5F53\u524D\u540E\u53F0\u4E0D\u652F\u6301\u91CD\u65B0\u52A0\u8F7D");
1579
- this.runtime.retryAt = 0;
1580
- this.runtime.schedule(0);
1581
- return this.state();
1582
- }
1583
- if (input.action === "workbuddy.sms") {
1584
- try {
1585
- const workbuddyLogin = await this.workbuddyLogin.send(input.phone);
1586
- return { ...this.state(), workbuddyLogin };
1587
- } catch (e) {
1588
- bad(e.message);
1589
- }
1590
- }
1591
- if (input.action === "workbuddy.login") {
1592
- if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u63D0\u4EA4\u767B\u5F55");
1593
- let login;
1594
- try {
1595
- login = await this.workbuddyLogin.complete(input.challenge, input.code, input.verify ?? true);
1596
- } catch (e) {
1597
- bad(e.message);
1598
- }
1599
- let existing;
1600
- for (const a of this.config.accounts.filter((a2) => a2.provider === "workbuddy")) {
1601
- try {
1602
- if ((await jsonCredential(a.credentialFile)).credential.uid === login.document.credential.uid) {
1603
- existing = a;
1604
- break;
1605
- }
1606
- } catch {
1607
- }
1608
- }
1609
- if (input.id && input.id !== existing?.id) bad("\u767B\u5F55\u8EAB\u4EFD\u4E0E\u6B63\u5728\u7F16\u8F91\u7684\u8D26\u53F7\u4E0D\u540C\uFF0C\u8BF7\u4ECE\u6DFB\u52A0\u8D26\u53F7\u5165\u53E3\u4FDD\u5B58");
1610
- const state = await this.action({
1611
- action: existing ? "account.credentials" : "account.add",
1612
- revision: input.revision,
1613
- id: existing?.id ?? `workbuddy_${randomBytes3(8).toString("hex")}`,
1614
- provider: "workbuddy",
1615
- name: input.name,
1616
- document: login.document
1617
- });
1618
- this.workbuddyLogin.finish();
1619
- return { ...state, loginResult: { warning: login.warning, saved: true } };
1620
- }
1621
- if (input.action === "checkins.verify") {
1622
- const account = this.config.accounts.find((a) => a.id === input.id && a.provider === "workbuddy");
1623
- if (!account) bad("\u8BF7\u9009\u62E9 WorkBuddy \u4E2D\u56FD\u7248\u8D26\u53F7");
1624
- if (this.checkins.flight || this.checkins.failed) bad("\u8BF7\u7B49\u5F85\u7B7E\u5230\u5B8C\u6210\uFF0C\u6216\u5148\u4FEE\u590D\u7B7E\u5230\u8BB0\u5F55");
1625
- try {
1626
- await this.workbuddyLogin.reserve("verifyAt", 3e4);
1627
- } catch (e) {
1628
- bad(e.message);
1629
- }
1630
- try {
1631
- const client = await this.router.client(account);
1632
- await client.checkin.validate(AbortSignal.timeout(3e4));
1633
- await this.checkins.credentialsUpdated(account.id);
1634
- await this.readLogin(account);
1635
- } catch {
1636
- bad("\u767B\u5F55\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1A\u8BF7\u5728\u8D26\u53F7\u4E0E\u767B\u5F55\u4E2D\u66F4\u65B0\u51ED\u8BC1\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u8BD5\u6216\u9886\u53D6");
1637
- }
1638
- return this.state();
1639
- }
1640
- if (input.action === "checkins.run") {
1641
- try {
1642
- this.checkins.start(input.ids);
1643
- } catch (e) {
1644
- bad(e.message);
1645
- }
1646
- return this.state();
1647
- }
1648
- if (input.action === "checkins.resume") {
1649
- try {
1650
- await this.checkins.resume(input.provider);
1651
- } catch (e) {
1652
- bad(e.message);
1653
- }
1654
- return this.state();
1655
- }
1656
- if (typeof input.action === "string" && input.action.startsWith("plugins.")) {
1657
- try {
1658
- await this.plugins.action(input);
1659
- } catch (e) {
1660
- bad(e.message);
1661
- }
1662
- return this.state();
1663
- }
1664
- if (input.action === "setup.dismiss") {
1665
- await withFileLock3(this.setupFile, async () => {
1666
- this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
1667
- await writePrivateJson(this.setupFile, this.setup);
1668
- });
1669
- return this.state();
1670
- }
1671
- if (input.action === "account.export") {
1672
- if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
1673
- const account = this.config.accounts.find((a) => a.id === input.id);
1674
- if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1675
- let credential;
1676
- try {
1677
- credential = await readPortableCredential(account);
1678
- } catch {
1679
- bad("\u8D26\u53F7\u51ED\u8BC1\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u8865\u5145\u6216\u66F4\u65B0\u540E\u518D\u63D0\u53D6");
1680
- }
1681
- if (!credential) bad("\u8D26\u53F7\u5C1A\u672A\u4FDD\u5B58\u51ED\u8BC1\uFF0C\u8BF7\u5148\u8865\u5145\u767B\u5F55\u4FE1\u606F");
1682
- return { ...this.state(), exported: {
1683
- filename: `dsh-account-${account.provider}-${account.id}.json`,
1684
- document: {
1685
- ...credential.document,
1686
- ...credential.identity ? { identity: credential.identity } : {},
1687
- format: "dsh-multi-router-account",
1688
- provider: account.provider,
1689
- name: account.name || account.id
1690
- }
1691
- } };
1692
- }
1693
- if (input.action === "config.export") {
1694
- if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
1695
- try {
1696
- return { ...this.state(), exported: { filename: `dsh-multi-router-${input.includeCredentials ? "encrypted" : "settings"}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`, document: await exportConfiguration(this, input) } };
1697
- } catch (e) {
1698
- bad(e.message);
1699
- }
1700
- }
1701
- if (input.action === "config.preview") {
1702
- try {
1703
- const portable = await decodeConfiguration(input);
1704
- return { ...this.state(), importPreview: migrationSummary(this, portable, input.mode) };
1705
- } catch (e) {
1706
- bad(e.message);
1707
- }
1708
- }
1709
- if (input.action === "monitor.refresh" || input.action === "account.refresh") {
1710
- if (input.provider && !PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
1711
- if (input.action === "account.refresh") {
1712
- const account = this.config.accounts.find((a) => a.id === input.id);
1713
- if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1714
- this.router.clients.delete(account.id);
1715
- await this.monitorAccount(account, { force: true });
1716
- } else {
1717
- await this.syncLocalAccounts(input.provider);
1718
- await this.monitorAll(input.provider, { force: true });
1719
- }
1720
- return this.state();
1721
- }
1722
- if (input.action === "accounts.discover") {
1723
- if (input.provider && !PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
1724
- await this.syncLocalAccounts(input.provider, true);
1725
- await Promise.all([this.monitorAll(input.provider), ...this.config.accounts.filter((a) => !input.provider || a.provider === input.provider).map((a) => this.refreshAccount(a, true))]);
1726
- return this.state();
1727
- }
1728
- if (input.action === "account.capture") {
1729
- if (!PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
1730
- input = { ...input, action: "account.add", id: `${input.provider.replace("-", "_")}_${randomBytes3(4).toString("hex")}`, capture: true };
1731
- }
1732
- if (input.action === "refresh") {
1733
- if (input.provider && !PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
1734
- await Promise.all((input.provider ? [input.provider] : PROVIDERS).map((p) => this.ensureCatalog(p, true)));
1735
- return this.state();
1736
- }
1737
- if (input.action === "free.refresh") {
1738
- await this.checkFreeModels(true);
1739
- return this.state();
1740
- }
1741
- return withFileLock3(this.file, async () => {
1742
- let raw = JSON.parse(await readFile4(this.file, "utf8"));
1743
- if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
1744
- raw.routes ??= structuredClone(this.config.routes);
1745
- let staging;
1746
- try {
1747
- const account = raw.accounts.find((a) => a.id === input.id);
1748
- if (input.action === "config.import") {
1749
- try {
1750
- const prepared = await prepareMigration(this, raw, await decodeConfiguration(input), input.mode);
1751
- staging = prepared.staging;
1752
- await writePrivateJson(`${this.file}.before-import-${Date.now()}-${randomBytes3(3).toString("hex")}.bak`, raw);
1753
- raw = prepared.raw;
1754
- } catch (e) {
1755
- bad(e.message);
1756
- }
1757
- } else if (input.action === "account.add" || input.action === "account.credentials") {
1758
- const replacing = input.action === "account.credentials";
1759
- if (replacing) {
1760
- if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1761
- input = { ...input, provider: account.provider };
1762
- }
1763
- if (!/^[a-zA-Z0-9_-]{1,64}$/.test(input.id ?? "") || !PROVIDERS.includes(input.provider)) bad("\u8D26\u53F7 ID \u6216\u4F9B\u5E94\u5546\u65E0\u6548");
1764
- if (account && !replacing) bad("\u8D26\u53F7 ID \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C ID");
1765
- const base = join5(dirname3(this.file), "accounts");
1766
- await mkdir2(base, { recursive: true, mode: 448 });
1767
- staging = await mkdtemp2(join5(base, `${input.id}-`));
1768
- const credentialFile = join5(staging, "credential.json"), identityFile = join5(staging, "identity.json");
1769
- const added = {
1770
- id: input.id,
1771
- name: input.name || account?.name || input.id,
1772
- provider: input.provider,
1773
- credentialFile: relative2(dirname3(this.file), credentialFile),
1774
- ...input.provider.startsWith("trae") ? { identityFile: relative2(dirname3(this.file), identityFile) } : {}
1775
- };
1776
- if (replacing) raw.accounts[raw.accounts.indexOf(account)] = { ...account, ...added, enabled: true };
1777
- else raw.accounts.push(added);
1778
- validateConfig(raw, this.file);
1779
- if (input.document !== void 0) {
1780
- try {
1781
- await importAccount({ provider: input.provider, credentialFile, identityFile, document: input.document, identityDocument: input.identityDocument });
1782
- } catch {
1783
- bad("\u51ED\u8BC1\u683C\u5F0F\u65E0\u6548\uFF1A\u8BF7\u68C0\u67E5\u5FC5\u586B\u5B57\u6BB5\u3001\u533A\u57DF\u548C Trae \u8BBE\u5907\u8EAB\u4EFD");
1784
- }
1785
- } else if (input.provider.startsWith("qoder") && input.pat) {
1786
- if (typeof input.pat !== "string" || input.pat.length > 8192 || /\s/.test(input.pat.trim())) bad("PAT \u683C\u5F0F\u65E0\u6548");
1787
- await writePrivateJson(credentialFile, {
1788
- version: 2,
1789
- pat: input.pat.trim(),
1790
- region: input.provider === "qoder" ? "china" : "global",
1791
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
1792
- });
1793
- } else {
1794
- const source = input.source;
1795
- if (!source && (input.provider.startsWith("workbuddy") || input.provider.startsWith("trae"))) {
1796
- const discovered = await discoverLocalAccounts(input.provider);
1797
- const item = discovered.accounts.find((a) => !input.edition || input.edition === "auto" || a.document.credential.edition === input.edition);
1798
- if (!item) bad(discovered.status.some((s) => s.state === "unreadable") ? "\u627E\u5230\u672C\u673A\u767B\u5F55\u6587\u4EF6\uFF0C\u4F46\u51ED\u8BC1\u6216\u8BBE\u5907\u8EAB\u4EFD\u65E0\u6CD5\u8BFB\u53D6\u3002\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u91CD\u65B0\u767B\u5F55\u540E\u91CD\u8BD5\uFF0C\u6216\u624B\u52A8\u5BFC\u5165\u51ED\u8BC1\u3002" : "\u672A\u627E\u5230\u6240\u9009\u533A\u57DF\u7684\u672C\u673A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u767B\u5F55\uFF0C\u518D\u70B9\u51FB\u91CD\u65B0\u68C0\u6D4B\u3002");
1799
- try {
1800
- await importAccount({ ...item, credentialFile, identityFile });
1801
- } catch {
1802
- bad("\u672C\u673A\u51ED\u8BC1\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u5BA2\u6237\u7AEF\u767B\u5F55\u72B6\u6001\u4E0E\u8BBE\u5907\u8EAB\u4EFD");
1803
- }
1804
- } else {
1805
- const file = source || (input.provider.startsWith("qoder") ? qoderOwnAuthPath(QODER_VARIANTS.find((v) => v.id === input.provider)) : void 0);
1806
- if (typeof file !== "string" || !file.trim()) bad("\u672A\u627E\u5230\u767B\u5F55\u6587\u4EF6\uFF0C\u8BF7\u586B\u5199\u6765\u6E90\u8DEF\u5F84\u6216 Qoder PAT");
1807
- try {
1808
- await importAccount({
1809
- provider: input.provider,
1810
- source: resolve(file),
1811
- credentialFile,
1812
- identityFile,
1813
- edition: input.edition,
1814
- identitySource: input.identity ? resolve(input.identity) : void 0
1815
- });
1816
- } catch {
1817
- bad("\u8D26\u53F7\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u767B\u5F55\u6587\u4EF6\u3001\u533A\u57DF\u3001Trae edition \u548C\u8BBE\u5907\u8EAB\u4EFD");
1818
- }
1819
- }
1820
- }
1821
- const imported = await jsonCredential(credentialFile);
1822
- const fingerprint = (value) => input.provider.startsWith("qoder") ? value.pat : value.credential?.uid || value.credential?.userId;
1823
- for (const existing of input.capture || input.document !== void 0 ? this.config.accounts.filter((a) => a.provider === input.provider && a.id !== input.id) : []) {
1824
- let value;
1825
- try {
1826
- value = await jsonCredential(existing.credentialFile);
1827
- } catch {
1828
- continue;
1829
- }
1830
- if (fingerprint(value) && fingerprint(value) === fingerprint(imported)) bad(`\u6B64\u767B\u5F55\u8D26\u53F7\u5DF2\u5728\u8D26\u53F7\u6C60\u4E2D\uFF1A${existing.name || existing.id}\uFF0C\u8BF7\u5207\u6362\u5BA2\u6237\u7AEF\u8D26\u53F7\u540E\u518D\u83B7\u53D6`);
1831
- }
1832
- if (!input.name && !replacing) added.name = imported.credential?.nickname || imported.credential?.accountName || input.id;
1833
- } else if (input.action === "account.update") {
1834
- if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1835
- if (input.autoCheckin !== void 0) {
1836
- if (typeof input.autoCheckin !== "boolean" || !supportsCheckin(account.provider)) bad("\u6B64\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u7B7E\u5230");
1837
- account.autoCheckin = input.autoCheckin;
1838
- }
1839
- if (input.name !== void 0) account.name = input.name;
1840
- if (input.enabled !== void 0) account.enabled = input.enabled;
1841
- } else if (input.action === "accounts.enabled") {
1842
- if (input.provider !== void 0 && !PROVIDERS.includes(input.provider) || typeof input.enabled !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u8DEF\u7531\u53C2\u4E0E\u5F00\u5173\u65E0\u6548");
1843
- for (const a of raw.accounts.filter((a2) => !input.provider || a2.provider === input.provider)) a.enabled = input.enabled;
1844
- } else if (input.action === "account.move") {
1845
- if (!account || ![-1, 1].includes(input.direction)) bad("\u8D26\u53F7\u6216\u987A\u5E8F\u65E0\u6548");
1846
- const group = raw.accounts.filter((a) => a.provider === account.provider), position = group.indexOf(account) + input.direction;
1847
- if (position >= 0 && position < group.length) {
1848
- const index = raw.accounts.indexOf(account), next = raw.accounts.indexOf(group[position]);
1849
- [raw.accounts[index], raw.accounts[next]] = [raw.accounts[next], raw.accounts[index]];
1850
- }
1851
- } else if (input.action === "account.remove") {
1852
- if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1853
- if (Object.values(raw.routes).some((r) => r.targets.some((t) => t.account === input.id))) bad("\u8BF7\u5148\u79FB\u9664\u8DEF\u7531\u4E2D\u7ED1\u5B9A\u6B64\u8D26\u53F7\u7684\u76EE\u6807");
1854
- if (account.provider.startsWith("workbuddy") || account.provider.startsWith("trae")) {
1855
- try {
1856
- raw.ignoredLocalAccounts = [.../* @__PURE__ */ new Set([...raw.ignoredLocalAccounts ?? [], localAccountKey(account.provider, await jsonCredential(this.config.accounts.find((a) => a.id === account.id).credentialFile))])];
1857
- } catch {
1858
- }
1859
- }
1860
- raw.accounts = raw.accounts.filter((a) => a.id !== input.id);
1861
- } else if (input.action === "models.visibility") {
1862
- if (input.provider !== void 0 && !PROVIDERS.includes(input.provider) || typeof input.visible !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u5C55\u793A\u5F00\u5173\u65E0\u6548");
1863
- raw.modelVisibility = { ...raw.modelVisibility };
1864
- raw.models = { ...raw.models };
1865
- for (const provider of input.provider ? [input.provider] : PROVIDERS) {
1866
- raw.modelVisibility[provider] = input.visible;
1867
- raw.models[provider] = Object.fromEntries(Object.entries(raw.models[provider] ?? {}).map(([id, pref]) => [id, { ...pref, visible: input.visible }]));
1868
- }
1869
- } else if (input.action === "model.update") {
1870
- if (!PROVIDERS.includes(input.provider) || typeof input.id !== "string" || !input.id || input.id.length > 256) bad("\u6A21\u578B\u65E0\u6548");
1871
- const model = this.providerModels(input.provider).find((m) => m.id === input.id);
1872
- if (input.contextWindow != null && (!Number.isSafeInteger(input.contextWindow) || input.contextWindow <= 0 || !model || input.contextWindow > model.maxContextWindow)) bad("\u4E0A\u4E0B\u6587\u9884\u7B97\u4E0D\u80FD\u8D85\u8FC7\u8BE5\u6E20\u9053\u6A21\u578B\u7684\u6700\u5927\u5BB9\u91CF");
1873
- if (input.supportsImages === true && !model?.imageCapable) bad("\u8BE5\u6A21\u578B\u672A\u58F0\u660E\u56FE\u7247\u8F93\u5165\u80FD\u529B");
1874
- if (input.reasoningEffort != null && !model?.reasoning?.supportedEfforts.includes(input.reasoningEffort)) bad("\u6B64\u6A21\u578B\u4E0D\u652F\u6301\u6240\u9009\u63A8\u7406\u7B49\u7EA7");
1875
- raw.models = { ...raw.models, [input.provider]: {
1876
- ...raw.models?.[input.provider],
1877
- [input.id]: {
1878
- ...raw.models?.[input.provider]?.[input.id],
1879
- ...Object.fromEntries(["name", "visible", "contextWindow", "supportsImages", "reasoningEffort"].filter((k) => input[k] !== void 0).map((k) => [k, input[k]]))
1880
- }
1881
- } };
1882
- } else if (input.action === "free.model.update") {
1883
- const parent = raw.routes[input.parentId];
1884
- if (!parent?.freeOnly || !this.freeRoutes(input.parentId, this.config.routes[input.parentId]).some((m) => m.id === input.id)) bad("\u514D\u8D39\u6A21\u578B\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u9875\u9762");
1885
- const model = this.freeRoutes(input.parentId, this.config.routes[input.parentId]).find((m) => m.id === input.id);
1886
- if (input.contextWindow != null && (!Number.isSafeInteger(input.contextWindow) || input.contextWindow <= 0 || input.contextWindow > model.maxContextWindow)) bad("\u4E0A\u4E0B\u6587\u9884\u7B97\u4E0D\u80FD\u8D85\u8FC7\u8BE5\u514D\u8D39\u6A21\u578B\u7684\u6700\u5927\u5BB9\u91CF");
1887
- if (input.reasoningEffort !== void 0 && input.reasoningEffort !== null && !model.reasoning?.supportedEfforts.includes(input.reasoningEffort)) bad("\u6B64\u514D\u8D39\u6A21\u578B\u4E0D\u652F\u6301\u6240\u9009\u63A8\u7406\u7B49\u7EA7");
1888
- parent.freeModels = { ...parent.freeModels, [input.id]: {
1889
- ...parent.freeModels?.[input.id],
1890
- ...input.visible !== void 0 ? { visible: input.visible } : {},
1891
- ...input.contextWindow !== void 0 ? { contextWindow: input.contextWindow } : {},
1892
- ...input.reasoningEffort !== void 0 ? { reasoningEffort: input.reasoningEffort } : {}
1893
- } };
1894
- } else if (input.action === "route.save") {
1895
- if (input.id === void 0 || input.id === "") {
1896
- input.id = input.previousId || `route_${randomBytes3(8).toString("hex")}`;
1897
- }
1898
- if (!/^[a-zA-Z0-9_.-]{1,128}$/.test(input.id ?? "")) bad("\u8DEF\u7531 ID \u4EC5\u652F\u6301\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF");
1899
- if (input.previousId && !Object.hasOwn(raw.routes, input.previousId)) bad("\u539F\u8DEF\u7531\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u9875\u9762");
1900
- if (input.previousId !== input.id && Object.hasOwn(raw.routes, input.id)) bad("\u8DEF\u7531 ID \u5DF2\u5B58\u5728");
1901
- raw.routes = { ...raw.routes, [input.id]: input.route };
1902
- if (input.previousId && input.previousId !== input.id) delete raw.routes[input.previousId];
1903
- } else if (input.action === "route.force-expiring") {
1904
- if (!Object.hasOwn(raw.routes, input.id)) bad("\u8DEF\u7531\u4E0D\u5B58\u5728");
1905
- raw.routes[input.id].preferExpiringCredits = true;
1906
- await this.telemetry.restartExpiring(input.id);
1907
- const route = this.router.config.routes[input.id], strategy = route.strategy ?? "failover";
1908
- if (["tokens", "credits"].includes(strategy)) await this.telemetry.advance(input.id, this.router.candidates(input.id), JSON.stringify({ strategy, targets: route.targets }));
1909
- } else if (input.action === "route.remove") {
1910
- if (!Object.hasOwn(raw.routes, input.id)) bad("\u8DEF\u7531\u4E0D\u5B58\u5728");
1911
- if (Object.hasOwn(ROUTE_PRESETS, input.id)) raw.seenRoutePresets = [.../* @__PURE__ */ new Set([...raw.seenRoutePresets ?? [], input.id])];
1912
- delete raw.routes[input.id];
1913
- } else if (input.action === "preset.add") {
1914
- const id = input.id ?? "deepseek-v4.1-flash";
1915
- if (!Object.hasOwn(ROUTE_PRESETS, id)) bad("\u672A\u77E5\u9884\u914D\u7F6E\u8DEF\u7531");
1916
- if (Object.hasOwn(raw.routes, id)) bad("\u9884\u914D\u7F6E\u8DEF\u7531\u5DF2\u5B58\u5728\uFF0C\u4E0D\u8986\u76D6\u5DF2\u6709\u8BBE\u7F6E");
1917
- raw.routes[id] = structuredClone(ROUTE_PRESETS[id]);
1918
- } else if (input.action === "preset.add-all") {
1919
- for (const [id, route] of Object.entries(ROUTE_PRESETS)) if (!Object.hasOwn(raw.routes, id)) raw.routes[id] = structuredClone(route);
1920
- } else if (input.action === "checkins.settings") {
1921
- if (this.checkins.flight) bad("\u8BF7\u7B49\u5F85\u5F53\u524D\u7B7E\u5230\u5B8C\u6210\u540E\u4FEE\u6539\u8BA1\u5212");
1922
- if (!input.schedule) bad("\u8BF7\u586B\u5199\u7B7E\u5230\u8BA1\u5212");
1923
- raw.checkinSchedule = input.schedule;
1924
- } else if (input.action === "options.save") {
1925
- for (const key of ["cooldownMs", "requestTimeoutMs", "retryServerErrors", "usageRetentionDays", "sidebarUsage"]) if (input[key] !== void 0) raw[key] = input[key];
1926
- } else bad("\u672A\u77E5\u64CD\u4F5C");
1927
- let config;
1928
- try {
1929
- config = validateConfig(raw, this.file);
1930
- } catch (error) {
1931
- bad(error.message);
1932
- }
1933
- if (input.action === "route.save" && input.route.reasoningEffort != null) {
1934
- const previous = this.config.routes[input.previousId], route = config.routes[input.id];
1935
- const bindings = (r) => JSON.stringify(r.targets.map((t) => ({
1936
- provider: t.provider,
1937
- account: t.account,
1938
- models: t.models ?? (t.model ? [t.model] : [])
1939
- })));
1940
- const unchanged = previous?.reasoningEffort === route.reasoningEffort && bindings(previous) === bindings(route) && previous.contextMode === route.contextMode && (route.contextMode === "maximum" || previous.contextWindow === route.contextWindow);
1941
- if (!unchanged && !this.routeModels({ [input.id]: route })[0].reasoning?.supportedEfforts.includes(route.reasoningEffort)) bad("\u76EE\u6807\u6A21\u578B\u5728\u5F53\u524D\u4E0A\u4E0B\u6587\u5BB9\u91CF\u4E0B\u4E0D\u652F\u6301\u6240\u9009\u601D\u8003\u6DF1\u5EA6\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9");
1942
- }
1943
- await writePrivateJson(this.file, raw);
1944
- staging = void 0;
1945
- this.raw = raw;
1946
- this.config = config;
1947
- for (const id of this.catalogs.keys()) if (!config.accounts.some((a) => a.id === id && a.credentialFile === this.catalogs.get(id).credentialFile)) {
1948
- this.catalogs.delete(id);
1949
- this.catalogErrors.delete(id);
1950
- }
1951
- await Promise.all(config.accounts.map((a) => this.readLogin(a)));
1952
- this.publish();
1953
- await this.telemetry.persist();
1954
- } finally {
1955
- if (staging) await rm3(staging, { recursive: true, force: true });
1956
- }
1957
- return this.state();
1958
- }, { waitMs: 3e4 }).then(async (state) => {
1959
- if (input.action === "config.import") {
1960
- void Promise.all([this.monitorAll(), ...PROVIDERS.map((p) => this.ensureCatalog(p, true))]).catch(() => {
1961
- });
1962
- }
1963
- if (input.action === "preset.add" || input.action === "preset.add-all") {
1964
- await this.checkFreeModels();
1965
- return this.state();
1966
- }
1967
- if (input.action === "account.add" || input.action === "account.credentials") {
1968
- const account = this.config.accounts.find((a) => a.id === input.id);
1969
- if (input.action === "account.credentials" && !this.checkins.failed) await this.checkins.credentialsUpdated(input.id);
1970
- if (account) {
1971
- await Promise.all([this.refreshAccount(account, true), this.monitorAccount(account)]);
1972
- return this.state();
1973
- }
1974
- }
1975
- return state;
1976
- });
1977
- }
1978
- watch(onError) {
1979
- let timer;
1980
- const watcher = watch2(dirname3(this.file), (_, filename) => {
1981
- if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
1982
- clearTimeout(timer);
1983
- timer = setTimeout(() => {
1984
- void withFileLock3(this.file, async () => {
1985
- const raw = JSON.parse(await readFile4(this.file, "utf8"));
1986
- if (this.closed || revisionOf(raw) === this.revision) return;
1987
- const config = validateConfig(raw, this.file);
1988
- this.raw = raw;
1989
- this.config = config;
1990
- this.publish();
1991
- await this.telemetry.persist();
1992
- }).catch(() => onError?.());
1993
- }, 150);
1994
- });
1995
- return () => {
1996
- this.closed = true;
1997
- clearTimeout(timer);
1998
- watcher.close();
1999
- };
2000
- }
2001
- };
2002
- function managementHandler(manager, { summaryOnly = false } = {}) {
2003
- const key = randomBytes3(32).toString("base64url");
2004
- const json = (res, status, value) => {
2005
- res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
2006
- res.end(JSON.stringify(value));
2007
- };
2008
- return async (req, res) => {
2009
- manager.requests = (manager.requests ?? 0) + 1;
2010
- try {
2011
- const remote = req.socket?.remoteAddress;
2012
- if (!hostIsLoopback(req.headers.host) || remote && !["127.0.0.1", "::1", "::ffff:127.0.0.1"].includes(remote)) throw new ManagementError(403, "\u4EC5\u5141\u8BB8\u672C\u673A\u8BF7\u6C42");
2013
- if (req.headers.origin && req.headers.origin !== `http://${req.headers.host}` && req.headers.origin !== `https://${req.headers.host}`) throw new ManagementError(403, "\u6765\u6E90\u65E0\u6548");
2014
- if (req.headers["sec-fetch-site"] === "cross-site") throw new ManagementError(403, "\u6765\u6E90\u65E0\u6548");
2015
- if (req.method === "GET") {
2016
- json(res, 200, summaryOnly ? manager.summaryState() : { ...manager.state(), key });
2017
- return;
2018
- }
2019
- if (summaryOnly) throw new ManagementError(405, "\u65B9\u6CD5\u65E0\u6548");
2020
- if (req.method !== "POST") throw new ManagementError(405, "\u65B9\u6CD5\u65E0\u6548");
2021
- const actual = Buffer.from(req.headers["x-multi-router-key"] ?? ""), expected = Buffer.from(key);
2022
- if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) throw new ManagementError(401, "\u8BF7\u5237\u65B0\u914D\u7F6E\u9875\u9762");
2023
- if (!/^application\/json(?:\s*;|$)/i.test(req.headers["content-type"] ?? "")) throw new ManagementError(415, "\u9700\u8981 JSON");
2024
- let size = 0, chunks = [];
2025
- for await (const chunk of req) {
2026
- size += chunk.length;
2027
- if (size > 1024 * 1024) throw new ManagementError(413, "\u8BF7\u6C42\u8FC7\u5927");
2028
- chunks.push(chunk);
2029
- }
2030
- let input;
2031
- try {
2032
- input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
2033
- } catch {
2034
- throw new ManagementError(400, "JSON \u65E0\u6548");
2035
- }
2036
- json(res, 200, { ...await manager.action(input), key });
2037
- } catch (error) {
2038
- json(
2039
- res,
2040
- error instanceof ManagementError ? error.status : 500,
2041
- { error: error instanceof ManagementError ? error.message : "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E\u548C\u6765\u6E90\u6587\u4EF6" }
2042
- );
2043
- } finally {
2044
- manager.requests--;
2045
- }
2046
- };
2047
- }
2048
-
2049
- export {
2050
- version,
2051
- PluginReload,
2052
- MANAGEMENT_PATH,
2053
- SUMMARY_PATH,
2054
- ManagementError,
2055
- modelInfo,
2056
- Manager,
2057
- managementHandler
2058
- };
2059
- //# sourceMappingURL=chunk-YCAO7GCT.js.map