dsh-multi-router 0.5.9 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +46 -6
- package/THIRD_PARTY_NOTICES.md +4 -0
- package/cordis.patch.yml +2 -2
- package/lib/accounts.js +4 -4
- package/lib/api-service.js +11 -0
- package/lib/api-service.js.map +7 -0
- package/lib/{chunk-U2MU2M5C.js → chunk-2LH6TD2U.js} +25 -115
- package/lib/chunk-2LH6TD2U.js.map +7 -0
- package/lib/chunk-4V52V5GO.js +105 -0
- package/lib/chunk-4V52V5GO.js.map +7 -0
- package/lib/chunk-625KMEYS.js +332 -0
- package/lib/chunk-625KMEYS.js.map +7 -0
- package/lib/{chunk-P43AWJ4Q.js → chunk-BSAI3SW5.js} +3 -3
- package/lib/{chunk-J3XO7RTF.js → chunk-DZ4ZUAYH.js} +50 -6
- package/lib/chunk-DZ4ZUAYH.js.map +7 -0
- package/lib/{chunk-MCHV2R25.js → chunk-NGVWVIBM.js} +22 -6
- package/lib/chunk-NGVWVIBM.js.map +7 -0
- package/lib/{chunk-FTM7RHQV.js → chunk-OK2T2UAZ.js} +4 -4
- package/lib/{chunk-S6ZQ45SS.js → chunk-VZPXRK7Q.js} +3 -3
- package/lib/{chunk-I4EW5TI7.js → chunk-ZQZSXCVR.js} +164 -48
- package/lib/chunk-ZQZSXCVR.js.map +7 -0
- package/lib/cli.js +7 -6
- package/lib/cli.js.map +1 -1
- package/lib/client.js +462 -44
- package/lib/client.js.map +2 -2
- package/lib/clients.js +9 -5
- package/lib/{host-v0.5.9.js → host-v0.6.1.js} +19 -13
- package/lib/host.js +19 -13
- package/lib/index.js +19 -13
- package/lib/index.js.map +2 -2
- package/lib/manager.js +11 -7
- package/lib/router.js +2 -2
- package/lib/shim.js +3 -3
- package/package.json +1 -1
- package/lib/chunk-I4EW5TI7.js.map +0 -7
- package/lib/chunk-J3XO7RTF.js.map +0 -7
- package/lib/chunk-MCHV2R25.js.map +0 -7
- package/lib/chunk-U2MU2M5C.js.map +0 -7
- /package/lib/{chunk-P43AWJ4Q.js.map → chunk-BSAI3SW5.js.map} +0 -0
- /package/lib/{chunk-FTM7RHQV.js.map → chunk-OK2T2UAZ.js.map} +0 -0
- /package/lib/{chunk-S6ZQ45SS.js.map → chunk-VZPXRK7Q.js.map} +0 -0
|
@@ -6,41 +6,45 @@ import {
|
|
|
6
6
|
refreshWorkBuddyCheckinCredential,
|
|
7
7
|
supportsCheckin,
|
|
8
8
|
workbuddyToken
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-2LH6TD2U.js";
|
|
10
10
|
import {
|
|
11
11
|
discoverLocalAccounts,
|
|
12
12
|
importAccount,
|
|
13
13
|
localAccountKey,
|
|
14
14
|
normalizeLocalAccountKey,
|
|
15
15
|
readPortableCredential
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VZPXRK7Q.js";
|
|
17
|
+
import {
|
|
18
|
+
modelWithCurrentPromotion
|
|
19
|
+
} from "./chunk-TEZ2FMEO.js";
|
|
20
|
+
import {
|
|
21
|
+
ApiService
|
|
22
|
+
} from "./chunk-625KMEYS.js";
|
|
17
23
|
import {
|
|
18
24
|
hostIsLoopback
|
|
19
25
|
} from "./chunk-DNQPZMPF.js";
|
|
20
26
|
import {
|
|
21
27
|
Telemetry,
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
contextInfo,
|
|
29
|
+
matchModel,
|
|
30
|
+
resolveContext
|
|
31
|
+
} from "./chunk-DZ4ZUAYH.js";
|
|
24
32
|
import {
|
|
25
33
|
PROVIDERS,
|
|
26
34
|
PROVIDER_NAMES,
|
|
27
35
|
ROUTE_PRESETS,
|
|
28
36
|
beijingDay,
|
|
29
37
|
isFreeModel,
|
|
30
|
-
reasoningModes,
|
|
31
38
|
routeReasoning,
|
|
32
39
|
validateConfig,
|
|
33
40
|
writePrivateJson
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import {
|
|
36
|
-
modelWithCurrentPromotion
|
|
37
|
-
} from "./chunk-TEZ2FMEO.js";
|
|
41
|
+
} from "./chunk-NGVWVIBM.js";
|
|
38
42
|
|
|
39
43
|
// src/manager.mjs
|
|
40
44
|
import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
41
45
|
|
|
42
46
|
// package.json
|
|
43
|
-
var version = "0.
|
|
47
|
+
var version = "0.6.1";
|
|
44
48
|
|
|
45
49
|
// src/manager.mjs
|
|
46
50
|
import { readFile as readFile4, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
|
|
@@ -102,14 +106,14 @@ var derive = promisify(scrypt);
|
|
|
102
106
|
var FORMAT = "dsh-multi-router";
|
|
103
107
|
var MAX_BYTES = 512 * 1024;
|
|
104
108
|
var pick = (value, keys) => Object.fromEntries(keys.filter((k) => Object.hasOwn(value ?? {}, k)).map((k) => [k, value[k]]));
|
|
105
|
-
var options = ["cooldownMs", "requestTimeoutMs", "catalogTtlMs", "retryServerErrors", "modelVisibility", "usageRetentionDays", "checkinSchedule"];
|
|
109
|
+
var options = ["cooldownMs", "requestTimeoutMs", "catalogTtlMs", "retryServerErrors", "modelVisibility", "usageRetentionDays", "checkinSchedule", "sidebarUsage"];
|
|
106
110
|
function settings(config) {
|
|
107
111
|
return {
|
|
108
112
|
...pick(config, options),
|
|
109
|
-
models: Object.fromEntries(Object.entries(config.models ?? {}).map(([p, rows]) => [p, Object.fromEntries(Object.entries(rows).map(([id, pref]) => [id, pick(pref, ["name", "visible"])]))])),
|
|
113
|
+
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"])]))])),
|
|
110
114
|
accounts: config.accounts.map((a) => pick(a, ["id", "provider", "name", "enabled", "autoCheckin"])),
|
|
111
115
|
routes: Object.fromEntries(Object.entries(config.routes).map(([id, r]) => [id, {
|
|
112
|
-
...pick(r, ["name", "visible", "strategy", "contextWindow", "maxTokens", "preferExpiringCredits", "expiringWithinDays", "freeOnly", "freeModels"]),
|
|
116
|
+
...pick(r, ["name", "visible", "strategy", "contextMode", "contextWindow", "maxTokens", "preferExpiringCredits", "expiringWithinDays", "freeOnly", "freeModels"]),
|
|
113
117
|
targets: r.targets.map((t) => pick(t, ["provider", "account", "model", "models", "weight", "tokenBudget", "creditBudget"]))
|
|
114
118
|
}]))
|
|
115
119
|
};
|
|
@@ -1003,6 +1007,7 @@ var WorkBuddyLogin = class {
|
|
|
1003
1007
|
|
|
1004
1008
|
// src/manager.mjs
|
|
1005
1009
|
var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
|
|
1010
|
+
var SUMMARY_PATH = `${MANAGEMENT_PATH}/summary`;
|
|
1006
1011
|
var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
|
|
1007
1012
|
var ManagementError = class extends Error {
|
|
1008
1013
|
constructor(status, message) {
|
|
@@ -1029,8 +1034,8 @@ function modelInfo(row) {
|
|
|
1029
1034
|
return {
|
|
1030
1035
|
id: row.id,
|
|
1031
1036
|
name: row.name || row.id,
|
|
1032
|
-
|
|
1033
|
-
maxTokens: Math.min(finite(row.maxTokens, 32e3),
|
|
1037
|
+
...contextInfo(row),
|
|
1038
|
+
maxTokens: Math.min(finite(row.maxTokens, 32e3), contextInfo(row).maxContextWindow),
|
|
1034
1039
|
supportsImages: row.supportsImages === true || row.multimodal === true || row.input?.includes("image") === true,
|
|
1035
1040
|
...reasoning ? { reasoning } : {},
|
|
1036
1041
|
...billing ? { billing } : {},
|
|
@@ -1060,6 +1065,7 @@ var Manager = class {
|
|
|
1060
1065
|
this.checkins = new Checkins(join5(dirname3(file), "checkins.json"), this);
|
|
1061
1066
|
this.workbuddyLogin = new WorkBuddyLogin(join5(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
|
|
1062
1067
|
this.setupFile = join5(dirname3(file), "initial-setup.json");
|
|
1068
|
+
this.api = new ApiService(join5(dirname3(file), "api-service.json"), this);
|
|
1063
1069
|
router.telemetry = this.telemetry;
|
|
1064
1070
|
router.monitor = (a) => this.monitorAccount(a, { maxAgeMs: 6e4 });
|
|
1065
1071
|
router.prepareFree = () => this.checkFreeModels(true);
|
|
@@ -1080,13 +1086,14 @@ var Manager = class {
|
|
|
1080
1086
|
for (const account of this.config.accounts) {
|
|
1081
1087
|
const item = saved[account.id];
|
|
1082
1088
|
if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
|
|
1083
|
-
this.catalogs.set(account.id, { ...item, models: item.models.filter((m) => typeof m.id === "string" && m.id.length > 0).map(modelInfo) });
|
|
1089
|
+
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) });
|
|
1084
1090
|
}
|
|
1085
1091
|
}
|
|
1086
1092
|
} catch {
|
|
1087
1093
|
}
|
|
1088
1094
|
this.telemetry.configure(this.config.usageRetentionDays);
|
|
1089
1095
|
await this.telemetry.load();
|
|
1096
|
+
await this.api.load();
|
|
1090
1097
|
await Promise.all(this.config.accounts.map((a) => this.readLogin(a)));
|
|
1091
1098
|
this.publish();
|
|
1092
1099
|
}
|
|
@@ -1278,16 +1285,25 @@ var Manager = class {
|
|
|
1278
1285
|
const id = `free_${createHash("sha256").update(JSON.stringify([parentId, account.provider, model.id])).digest("hex").slice(0, 24)}`;
|
|
1279
1286
|
if (!groups.has(id)) groups.set(id, { id, models: [], targets: [], accountNames: [], modelName: model.name, provider: account.provider, modelId: model.id });
|
|
1280
1287
|
const group = groups.get(id);
|
|
1281
|
-
group.models.push({ model, provider: account.provider });
|
|
1288
|
+
group.models.push({ model, provider: account.provider, accountId: account.id });
|
|
1282
1289
|
group.targets.push({ provider: account.provider, account: account.id, model: model.id });
|
|
1283
1290
|
group.accountNames.push(account.name ?? account.id);
|
|
1284
1291
|
}
|
|
1285
1292
|
}
|
|
1286
1293
|
return [...groups.values()].map(({ models, ...group }) => {
|
|
1287
|
-
const preference = row.freeModels?.[group.id] ?? {}
|
|
1294
|
+
const preference = row.freeModels?.[group.id] ?? {};
|
|
1295
|
+
const context = resolveContext(models.map((s) => s.model), preference.contextWindow ?? (row.contextMode === "custom" ? row.contextWindow : null), row.contextWindow);
|
|
1296
|
+
const eligible = models.filter((s) => contextInfo(s.model).maxContextWindow >= context.contextWindow);
|
|
1297
|
+
const eligibleAccountIds = eligible.map((s) => s.accountId);
|
|
1298
|
+
const reasoning = routeReasoning(eligible);
|
|
1288
1299
|
const selected = preference.reasoningEffort;
|
|
1289
1300
|
return {
|
|
1290
1301
|
...group,
|
|
1302
|
+
...context,
|
|
1303
|
+
eligibleAccountIds,
|
|
1304
|
+
targets: group.targets.filter((t) => eligibleAccountIds.includes(t.account)),
|
|
1305
|
+
accountNames: group.accountNames.filter((_, i) => eligibleAccountIds.includes(models[i].accountId)),
|
|
1306
|
+
enforceContext: true,
|
|
1291
1307
|
parentId,
|
|
1292
1308
|
dynamicFree: true,
|
|
1293
1309
|
freeOnly: true,
|
|
@@ -1300,9 +1316,8 @@ var Manager = class {
|
|
|
1300
1316
|
effortUnavailable: !!selected && !reasoning?.supportedEfforts.includes(selected),
|
|
1301
1317
|
visible: row.visible !== false && preference.visible !== false,
|
|
1302
1318
|
selected: preference.visible !== false,
|
|
1303
|
-
supportsImages:
|
|
1304
|
-
|
|
1305
|
-
maxTokens: Math.min(row.maxTokens, ...models.map(({ model }) => model.maxTokens), ...models.map(({ model }) => model.contextWindow))
|
|
1319
|
+
supportsImages: eligible.every(({ model }) => model.supportsImages),
|
|
1320
|
+
maxTokens: Math.min(row.maxTokens, context.contextWindow, ...eligible.map(({ model }) => model.maxTokens))
|
|
1306
1321
|
};
|
|
1307
1322
|
});
|
|
1308
1323
|
}
|
|
@@ -1317,7 +1332,7 @@ var Manager = class {
|
|
|
1317
1332
|
publish() {
|
|
1318
1333
|
this.telemetry.configure(this.config.usageRetentionDays);
|
|
1319
1334
|
this.revision = revisionOf(this.raw);
|
|
1320
|
-
const routes =
|
|
1335
|
+
const routes = Object.fromEntries(this.routeModels().map((row) => [row.id, row]));
|
|
1321
1336
|
for (const model of this.adapterModels().filter((m) => m.dynamicFree)) routes[model.id] = model;
|
|
1322
1337
|
for (const provider of PROVIDERS) for (const model of this.providerModels(provider)) {
|
|
1323
1338
|
routes[`${provider}::${model.id}`] = { ...model, targets: [{ provider, model: model.id }] };
|
|
@@ -1329,23 +1344,30 @@ var Manager = class {
|
|
|
1329
1344
|
const rows = /* @__PURE__ */ new Map();
|
|
1330
1345
|
for (const account of this.config.accounts.filter((a) => a.provider === provider && a.enabled !== false)) {
|
|
1331
1346
|
for (const model of this.catalogs.get(account.id)?.models ?? []) {
|
|
1332
|
-
|
|
1333
|
-
rows.
|
|
1334
|
-
...old,
|
|
1335
|
-
contextWindow: Math.min(old.contextWindow, model.contextWindow),
|
|
1336
|
-
maxTokens: Math.min(old.maxTokens, model.maxTokens),
|
|
1337
|
-
supportsImages: old.supportsImages && model.supportsImages,
|
|
1338
|
-
reasoning: routeReasoning([{ model: old, provider }, { model, provider }])
|
|
1339
|
-
} : {
|
|
1340
|
-
...model,
|
|
1341
|
-
...reasoningModes(model, provider).length ? { reasoning: routeReasoning([{ model, provider }]) } : {}
|
|
1342
|
-
});
|
|
1347
|
+
if (!rows.has(model.id)) rows.set(model.id, []);
|
|
1348
|
+
rows.get(model.id).push(model);
|
|
1343
1349
|
}
|
|
1344
1350
|
}
|
|
1345
|
-
return [...rows.values()].map((
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1351
|
+
return [...rows.values()].map((models) => {
|
|
1352
|
+
const model = models[0], preference = this.config.models[provider]?.[model.id] ?? {};
|
|
1353
|
+
const context = resolveContext(models, preference.contextWindow);
|
|
1354
|
+
const eligible = models.filter((m) => contextInfo(m).maxContextWindow >= context.contextWindow);
|
|
1355
|
+
const reasoning = routeReasoning(eligible.map((model2) => ({ model: model2, provider })));
|
|
1356
|
+
const selected = preference.reasoningEffort;
|
|
1357
|
+
return {
|
|
1358
|
+
...model,
|
|
1359
|
+
...context,
|
|
1360
|
+
enforceContext: true,
|
|
1361
|
+
name: preference.name ?? model.name,
|
|
1362
|
+
reasoning,
|
|
1363
|
+
supportsImages: preference.supportsImages !== false && eligible.every((m) => m.supportsImages),
|
|
1364
|
+
imageCapable: eligible.every((m) => m.supportsImages),
|
|
1365
|
+
...selected && reasoning?.supportedEfforts.includes(selected) ? { reasoningEffort: selected } : {},
|
|
1366
|
+
configuredEffort: selected ?? null,
|
|
1367
|
+
effortUnavailable: !!selected && !reasoning?.supportedEfforts.includes(selected),
|
|
1368
|
+
maxTokens: Math.min(context.contextWindow, ...eligible.map((m) => m.maxTokens))
|
|
1369
|
+
};
|
|
1370
|
+
});
|
|
1349
1371
|
}
|
|
1350
1372
|
hiddenModels(provider) {
|
|
1351
1373
|
return this.providerModels(provider).filter((m) => !this.modelVisible(provider, m.id)).map((m) => m.id);
|
|
@@ -1359,14 +1381,19 @@ var Manager = class {
|
|
|
1359
1381
|
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) => {
|
|
1360
1382
|
const catalog = this.catalogs.get(a.id)?.models ?? [];
|
|
1361
1383
|
const models2 = target.models ? target.models.map((model) => catalog.find((m) => m.id === model)).filter(Boolean) : [matchModel(id, target, catalog)].filter(Boolean);
|
|
1362
|
-
return models2.map((model) => ({ model, provider: a.provider }));
|
|
1384
|
+
return models2.map((model) => ({ model, provider: a.provider, accountId: a.id }));
|
|
1363
1385
|
}));
|
|
1364
|
-
const
|
|
1386
|
+
const context = resolveContext(sources.map((s) => s.model), row.contextMode === "custom" ? row.contextWindow : null, row.contextWindow);
|
|
1387
|
+
const eligible = sources.filter((s) => contextInfo(s.model).maxContextWindow >= context.contextWindow);
|
|
1388
|
+
const models = eligible.map((s) => s.model), reasoning = routeReasoning(eligible);
|
|
1365
1389
|
return {
|
|
1366
1390
|
...row,
|
|
1391
|
+
...context,
|
|
1392
|
+
enforceContext: true,
|
|
1367
1393
|
supportsImages: models.length > 0 && models.every((m) => m.supportsImages),
|
|
1368
1394
|
reasoning,
|
|
1369
|
-
|
|
1395
|
+
eligibleAccountIds: [...new Set(eligible.map((s) => s.accountId))],
|
|
1396
|
+
maxTokens: Math.min(row.maxTokens, context.contextWindow, ...models.map((m) => m.maxTokens)),
|
|
1370
1397
|
id,
|
|
1371
1398
|
name: row.name ?? id,
|
|
1372
1399
|
expiringStatus: { ...this.telemetry.data.expiringRounds[id] },
|
|
@@ -1388,7 +1415,7 @@ var Manager = class {
|
|
|
1388
1415
|
const client = await this.router.client(account);
|
|
1389
1416
|
const models = (await client.models(AbortSignal.timeout(6e4), !!force)).map(modelInfo);
|
|
1390
1417
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
|
|
1391
|
-
this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, models });
|
|
1418
|
+
this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, contextVersion: 1, models });
|
|
1392
1419
|
this.catalogErrors.delete(account.id);
|
|
1393
1420
|
await withFileLock3(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
|
|
1394
1421
|
} catch (error) {
|
|
@@ -1400,6 +1427,19 @@ var Manager = class {
|
|
|
1400
1427
|
this.refreshes.set(account.id, promise);
|
|
1401
1428
|
return promise;
|
|
1402
1429
|
}
|
|
1430
|
+
summaryState() {
|
|
1431
|
+
const today = new Date(this.telemetry.now()).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
|
|
1432
|
+
const daily = this.telemetry.data.days[today] ?? {};
|
|
1433
|
+
return {
|
|
1434
|
+
pluginVersion: version,
|
|
1435
|
+
enabled: this.config.sidebarUsage,
|
|
1436
|
+
today,
|
|
1437
|
+
tokens: daily.tokens ?? 0,
|
|
1438
|
+
requests: daily.requests ?? 0,
|
|
1439
|
+
unmetered: daily.unmetered ?? 0,
|
|
1440
|
+
totalTokens: Object.values(this.telemetry.data.accounts).reduce((sum, row) => sum + (row.tokens ?? 0), 0)
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1403
1443
|
state() {
|
|
1404
1444
|
const status = this.router.status();
|
|
1405
1445
|
return {
|
|
@@ -1412,6 +1452,7 @@ var Manager = class {
|
|
|
1412
1452
|
pluginUpdates: this.plugins.state(),
|
|
1413
1453
|
checkins: this.checkins.state(),
|
|
1414
1454
|
telemetry: this.telemetry.state(),
|
|
1455
|
+
apiService: this.api.state(),
|
|
1415
1456
|
accounts: this.config.accounts.map((a) => ({
|
|
1416
1457
|
id: a.id,
|
|
1417
1458
|
provider: a.provider,
|
|
@@ -1431,6 +1472,7 @@ var Manager = class {
|
|
|
1431
1472
|
presets: Object.entries(ROUTE_PRESETS).map(([id, route]) => ({
|
|
1432
1473
|
id,
|
|
1433
1474
|
...structuredClone(route),
|
|
1475
|
+
providerCount: route.targets.length,
|
|
1434
1476
|
added: Object.hasOwn(this.config.routes, id),
|
|
1435
1477
|
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))]
|
|
1436
1478
|
})),
|
|
@@ -1439,12 +1481,79 @@ var Manager = class {
|
|
|
1439
1481
|
cooldownMs: this.config.cooldownMs,
|
|
1440
1482
|
requestTimeoutMs: this.config.requestTimeoutMs,
|
|
1441
1483
|
retryServerErrors: this.config.retryServerErrors,
|
|
1442
|
-
usageRetentionDays: this.config.usageRetentionDays
|
|
1484
|
+
usageRetentionDays: this.config.usageRetentionDays,
|
|
1485
|
+
sidebarUsage: this.config.sidebarUsage
|
|
1443
1486
|
}
|
|
1444
1487
|
};
|
|
1445
1488
|
}
|
|
1446
1489
|
async action(input) {
|
|
1447
1490
|
if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
|
|
1491
|
+
if (input.action === "model.test") {
|
|
1492
|
+
const model = this.providerModels(input.provider).find((m) => m.id === input.id);
|
|
1493
|
+
if (!model) bad("\u8BF7\u5148\u5237\u65B0\u5E76\u9009\u62E9\u5DF2\u53D1\u73B0\u7684\u6A21\u578B");
|
|
1494
|
+
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");
|
|
1495
|
+
this.modelTestFlight = true;
|
|
1496
|
+
this.modelTestAt = Date.now();
|
|
1497
|
+
this.modelTestAbort = new AbortController();
|
|
1498
|
+
const started = performance.now();
|
|
1499
|
+
let modelTest;
|
|
1500
|
+
try {
|
|
1501
|
+
const { response } = await this.router.chat(
|
|
1502
|
+
{
|
|
1503
|
+
model: `${input.provider}::${input.id}`,
|
|
1504
|
+
stream: true,
|
|
1505
|
+
messages: [{ role: "system", content: "Answer briefly." }, { role: "user", content: "Reply with OK." }],
|
|
1506
|
+
max_tokens: 16
|
|
1507
|
+
},
|
|
1508
|
+
AbortSignal.any([this.modelTestAbort.signal, AbortSignal.timeout(45e3)])
|
|
1509
|
+
);
|
|
1510
|
+
const reader = response.body.getReader();
|
|
1511
|
+
let size = 0, text = "";
|
|
1512
|
+
const decoder = new TextDecoder();
|
|
1513
|
+
try {
|
|
1514
|
+
for (; ; ) {
|
|
1515
|
+
const chunk = await reader.read();
|
|
1516
|
+
if (chunk.done) break;
|
|
1517
|
+
size += chunk.value.byteLength;
|
|
1518
|
+
if (size > 1024 * 1024) throw Error("probe_response_too_large");
|
|
1519
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
1520
|
+
}
|
|
1521
|
+
text += decoder.decode();
|
|
1522
|
+
let answered = false;
|
|
1523
|
+
for (const line of text.split(/\r?\n/).filter((l) => l.startsWith("data:"))) {
|
|
1524
|
+
if (line.slice(5).trim() === "[DONE]") continue;
|
|
1525
|
+
const payload = JSON.parse(line.slice(5));
|
|
1526
|
+
if (payload.error || payload.choices?.some((c) => c.finish_reason === "error")) throw Error("probe_stream_failed");
|
|
1527
|
+
answered ||= payload.choices?.some((c) => c.delta?.content || c.delta?.reasoning_content || c.delta?.tool_calls?.length) ?? false;
|
|
1528
|
+
}
|
|
1529
|
+
if (!answered || !/^data:\s*\[DONE\]\s*$/m.test(text)) throw Error("probe_incomplete");
|
|
1530
|
+
} finally {
|
|
1531
|
+
await reader.cancel().catch(() => {
|
|
1532
|
+
});
|
|
1533
|
+
reader.releaseLock();
|
|
1534
|
+
}
|
|
1535
|
+
modelTest = { ok: true, elapsedMs: Math.round(performance.now() - started) };
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
modelTest = {
|
|
1538
|
+
ok: false,
|
|
1539
|
+
status: Number(error.status) || 502,
|
|
1540
|
+
hint: error.hint || "\u6D4B\u8BD5\u672A\u901A\u8FC7\uFF0C\u8BF7\u67E5\u770B\u8D26\u53F7\u72B6\u6001\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002"
|
|
1541
|
+
};
|
|
1542
|
+
} finally {
|
|
1543
|
+
this.modelTestFlight = false;
|
|
1544
|
+
this.modelTestAbort = void 0;
|
|
1545
|
+
}
|
|
1546
|
+
return { ...this.state(), modelTest };
|
|
1547
|
+
}
|
|
1548
|
+
if (input.action?.startsWith("api.")) {
|
|
1549
|
+
let result;
|
|
1550
|
+
try {
|
|
1551
|
+
result = await this.api.action(input);
|
|
1552
|
+
} catch (e) {
|
|
1553
|
+
bad(e.message);
|
|
1554
|
+
}
|
|
1555
|
+
return { ...this.state(), ...result };
|
|
1556
|
+
}
|
|
1448
1557
|
if (input.action === "plugin.reload") {
|
|
1449
1558
|
if (!this.runtime) bad("\u5F53\u524D\u540E\u53F0\u4E0D\u652F\u6301\u91CD\u65B0\u52A0\u8F7D");
|
|
1450
1559
|
this.runtime.retryAt = 0;
|
|
@@ -1739,22 +1848,27 @@ var Manager = class {
|
|
|
1739
1848
|
}
|
|
1740
1849
|
} else if (input.action === "model.update") {
|
|
1741
1850
|
if (!PROVIDERS.includes(input.provider) || typeof input.id !== "string" || !input.id || input.id.length > 256) bad("\u6A21\u578B\u65E0\u6548");
|
|
1851
|
+
const model = this.providerModels(input.provider).find((m) => m.id === input.id);
|
|
1852
|
+
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");
|
|
1853
|
+
if (input.supportsImages === true && !model?.imageCapable) bad("\u8BE5\u6A21\u578B\u672A\u58F0\u660E\u56FE\u7247\u8F93\u5165\u80FD\u529B");
|
|
1854
|
+
if (input.reasoningEffort != null && !model?.reasoning?.supportedEfforts.includes(input.reasoningEffort)) bad("\u6B64\u6A21\u578B\u4E0D\u652F\u6301\u6240\u9009\u63A8\u7406\u7B49\u7EA7");
|
|
1742
1855
|
raw.models = { ...raw.models, [input.provider]: {
|
|
1743
1856
|
...raw.models?.[input.provider],
|
|
1744
1857
|
[input.id]: {
|
|
1745
1858
|
...raw.models?.[input.provider]?.[input.id],
|
|
1746
|
-
...
|
|
1747
|
-
...input.visible !== void 0 ? { visible: input.visible } : {}
|
|
1859
|
+
...Object.fromEntries(["name", "visible", "contextWindow", "supportsImages", "reasoningEffort"].filter((k) => input[k] !== void 0).map((k) => [k, input[k]]))
|
|
1748
1860
|
}
|
|
1749
1861
|
} };
|
|
1750
1862
|
} else if (input.action === "free.model.update") {
|
|
1751
1863
|
const parent = raw.routes[input.parentId];
|
|
1752
1864
|
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");
|
|
1753
1865
|
const model = this.freeRoutes(input.parentId, this.config.routes[input.parentId]).find((m) => m.id === input.id);
|
|
1866
|
+
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");
|
|
1754
1867
|
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");
|
|
1755
1868
|
parent.freeModels = { ...parent.freeModels, [input.id]: {
|
|
1756
1869
|
...parent.freeModels?.[input.id],
|
|
1757
1870
|
...input.visible !== void 0 ? { visible: input.visible } : {},
|
|
1871
|
+
...input.contextWindow !== void 0 ? { contextWindow: input.contextWindow } : {},
|
|
1758
1872
|
...input.reasoningEffort !== void 0 ? { reasoningEffort: input.reasoningEffort } : {}
|
|
1759
1873
|
} };
|
|
1760
1874
|
} else if (input.action === "route.save") {
|
|
@@ -1787,7 +1901,7 @@ var Manager = class {
|
|
|
1787
1901
|
if (!input.schedule) bad("\u8BF7\u586B\u5199\u7B7E\u5230\u8BA1\u5212");
|
|
1788
1902
|
raw.checkinSchedule = input.schedule;
|
|
1789
1903
|
} else if (input.action === "options.save") {
|
|
1790
|
-
for (const key of ["cooldownMs", "requestTimeoutMs", "retryServerErrors", "usageRetentionDays"]) if (input[key] !== void 0) raw[key] = input[key];
|
|
1904
|
+
for (const key of ["cooldownMs", "requestTimeoutMs", "retryServerErrors", "usageRetentionDays", "sidebarUsage"]) if (input[key] !== void 0) raw[key] = input[key];
|
|
1791
1905
|
} else bad("\u672A\u77E5\u64CD\u4F5C");
|
|
1792
1906
|
let config;
|
|
1793
1907
|
try {
|
|
@@ -1854,7 +1968,7 @@ var Manager = class {
|
|
|
1854
1968
|
};
|
|
1855
1969
|
}
|
|
1856
1970
|
};
|
|
1857
|
-
function managementHandler(manager) {
|
|
1971
|
+
function managementHandler(manager, { summaryOnly = false } = {}) {
|
|
1858
1972
|
const key = randomBytes3(32).toString("base64url");
|
|
1859
1973
|
const json = (res, status, value) => {
|
|
1860
1974
|
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
@@ -1868,9 +1982,10 @@ function managementHandler(manager) {
|
|
|
1868
1982
|
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");
|
|
1869
1983
|
if (req.headers["sec-fetch-site"] === "cross-site") throw new ManagementError(403, "\u6765\u6E90\u65E0\u6548");
|
|
1870
1984
|
if (req.method === "GET") {
|
|
1871
|
-
json(res, 200, { ...manager.state(), key });
|
|
1985
|
+
json(res, 200, summaryOnly ? manager.summaryState() : { ...manager.state(), key });
|
|
1872
1986
|
return;
|
|
1873
1987
|
}
|
|
1988
|
+
if (summaryOnly) throw new ManagementError(405, "\u65B9\u6CD5\u65E0\u6548");
|
|
1874
1989
|
if (req.method !== "POST") throw new ManagementError(405, "\u65B9\u6CD5\u65E0\u6548");
|
|
1875
1990
|
const actual = Buffer.from(req.headers["x-multi-router-key"] ?? ""), expected = Buffer.from(key);
|
|
1876
1991
|
if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) throw new ManagementError(401, "\u8BF7\u5237\u65B0\u914D\u7F6E\u9875\u9762");
|
|
@@ -1904,9 +2019,10 @@ export {
|
|
|
1904
2019
|
version,
|
|
1905
2020
|
PluginReload,
|
|
1906
2021
|
MANAGEMENT_PATH,
|
|
2022
|
+
SUMMARY_PATH,
|
|
1907
2023
|
ManagementError,
|
|
1908
2024
|
modelInfo,
|
|
1909
2025
|
Manager,
|
|
1910
2026
|
managementHandler
|
|
1911
2027
|
};
|
|
1912
|
-
//# sourceMappingURL=chunk-
|
|
2028
|
+
//# sourceMappingURL=chunk-ZQZSXCVR.js.map
|