dsh-multi-router 0.6.12 → 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.
- package/README.md +2 -430
- package/package.json +5 -81
- package/CHANGELOG.md +0 -243
- package/LICENSE +0 -21
- package/THIRD_PARTY_NOTICES.md +0 -171
- package/cordis.patch.yml +0 -3
- package/lib/accounts.js +0 -19
- package/lib/accounts.js.map +0 -7
- package/lib/api-service.js +0 -11
- package/lib/api-service.js.map +0 -7
- package/lib/chunk-2UM45OXY.js +0 -181
- package/lib/chunk-2UM45OXY.js.map +0 -7
- package/lib/chunk-4L2CBGXN.js +0 -674
- package/lib/chunk-4L2CBGXN.js.map +0 -7
- package/lib/chunk-4V52V5GO.js +0 -105
- package/lib/chunk-4V52V5GO.js.map +0 -7
- package/lib/chunk-DNQPZMPF.js +0 -33
- package/lib/chunk-DNQPZMPF.js.map +0 -7
- package/lib/chunk-FLL3DUVP.js +0 -2017
- package/lib/chunk-FLL3DUVP.js.map +0 -7
- package/lib/chunk-GEBZMIMA.js +0 -287
- package/lib/chunk-GEBZMIMA.js.map +0 -7
- package/lib/chunk-KWXLQJS5.js +0 -1102
- package/lib/chunk-KWXLQJS5.js.map +0 -7
- package/lib/chunk-NZF36O5I.js +0 -5421
- package/lib/chunk-NZF36O5I.js.map +0 -7
- package/lib/chunk-S5KMFAQM.js +0 -50
- package/lib/chunk-S5KMFAQM.js.map +0 -7
- package/lib/chunk-TM5XDMGY.js +0 -332
- package/lib/chunk-TM5XDMGY.js.map +0 -7
- package/lib/chunk-V37VG7MN.js +0 -2101
- package/lib/chunk-V37VG7MN.js.map +0 -7
- package/lib/chunk-WDIT4E2W.js +0 -120
- package/lib/chunk-WDIT4E2W.js.map +0 -7
- package/lib/cli.js +0 -103
- package/lib/cli.js.map +0 -7
- package/lib/client.js +0 -2805
- package/lib/client.js.map +0 -7
- package/lib/clients.js +0 -30
- package/lib/clients.js.map +0 -7
- package/lib/host-v0.6.12.js +0 -414
- package/lib/host.js +0 -414
- package/lib/index.js +0 -414
- package/lib/index.js.map +0 -7
- package/lib/manager.js +0 -27
- package/lib/manager.js.map +0 -7
- package/lib/region-DO3KAV7V.js +0 -15
- package/lib/region-DO3KAV7V.js.map +0 -7
- package/lib/router.js +0 -14
- package/lib/router.js.map +0 -7
- package/lib/shim.js +0 -10
- package/lib/shim.js.map +0 -7
- package/lib/upstream-VP6J7LU2.js +0 -25
- package/lib/upstream-VP6J7LU2.js.map +0 -7
- package/router.example.json +0 -289
- package/upstream-lock.json +0 -17
package/lib/chunk-WDIT4E2W.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
hostIsLoopback,
|
|
3
|
-
originIsLoopback
|
|
4
|
-
} from "./chunk-DNQPZMPF.js";
|
|
5
|
-
import {
|
|
6
|
-
RouteError
|
|
7
|
-
} from "./chunk-4L2CBGXN.js";
|
|
8
|
-
import {
|
|
9
|
-
REASONING_EFFORTS
|
|
10
|
-
} from "./chunk-GEBZMIMA.js";
|
|
11
|
-
|
|
12
|
-
// src/shim.mjs
|
|
13
|
-
import { createServer } from "node:http";
|
|
14
|
-
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
15
|
-
import { Readable } from "node:stream";
|
|
16
|
-
import { pipeline } from "node:stream/promises";
|
|
17
|
-
function createShim(router, { modelPrefix = "" } = {}) {
|
|
18
|
-
let active = 0;
|
|
19
|
-
const secret = randomBytes(32).toString("base64url");
|
|
20
|
-
const server = createServer((req, res) => {
|
|
21
|
-
void handle(req, res);
|
|
22
|
-
});
|
|
23
|
-
const ready = new Promise((resolve, reject) => {
|
|
24
|
-
server.once("listening", resolve);
|
|
25
|
-
server.once("error", reject);
|
|
26
|
-
});
|
|
27
|
-
server.listen(0, "127.0.0.1");
|
|
28
|
-
const json = (res, status, value) => {
|
|
29
|
-
res.writeHead(status, { "Content-Type": "application/json" });
|
|
30
|
-
res.end(JSON.stringify(value));
|
|
31
|
-
};
|
|
32
|
-
async function handle(req, res) {
|
|
33
|
-
active++;
|
|
34
|
-
const controller = new AbortController();
|
|
35
|
-
req.on("aborted", () => controller.abort());
|
|
36
|
-
res.on("close", () => {
|
|
37
|
-
if (!res.writableEnded) controller.abort();
|
|
38
|
-
});
|
|
39
|
-
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(router.config.requestTimeoutMs)]);
|
|
40
|
-
try {
|
|
41
|
-
if (!hostIsLoopback(req.headers.host) || !originIsLoopback(req.headers.origin)) throw new RouteError(403, "loopback_required");
|
|
42
|
-
const bearer = Buffer.from((req.headers.authorization ?? "").replace(/^Bearer\s+/i, ""));
|
|
43
|
-
const expected = Buffer.from(secret);
|
|
44
|
-
if (bearer.length !== expected.length || !timingSafeEqual(bearer, expected)) throw new RouteError(401, "unauthorized");
|
|
45
|
-
if (req.method === "GET" && req.url === "/router/status") {
|
|
46
|
-
json(res, 200, router.status());
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (req.method === "GET" && req.url === "/v1/models") {
|
|
50
|
-
json(res, 200, { object: "list", data: Object.keys(router.config.routes).filter((id) => modelPrefix ? id.startsWith(modelPrefix) : !id.includes("::")).map((id) => ({ id: modelPrefix ? id.slice(modelPrefix.length) : id, object: "model", created: 0, owned_by: "multi-router" })) });
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (req.method !== "POST" || req.url !== "/v1/chat/completions") throw new RouteError(404, "not_found");
|
|
54
|
-
if (!/^application\/json(?:\s*;|$)/i.test(req.headers["content-type"] ?? "")) throw new RouteError(415, "json_required");
|
|
55
|
-
let size = 0;
|
|
56
|
-
const chunks = [];
|
|
57
|
-
for await (const chunk of req) {
|
|
58
|
-
size += chunk.length;
|
|
59
|
-
if (size > 32 * 1024 * 1024) throw new RouteError(413, "request_too_large");
|
|
60
|
-
chunks.push(chunk);
|
|
61
|
-
signal.throwIfAborted();
|
|
62
|
-
}
|
|
63
|
-
let input;
|
|
64
|
-
try {
|
|
65
|
-
input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
66
|
-
} catch {
|
|
67
|
-
throw new RouteError(400, "invalid_json");
|
|
68
|
-
}
|
|
69
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) throw new RouteError(400, "invalid_request");
|
|
70
|
-
const effort = req.headers["x-multi-router-reasoning"];
|
|
71
|
-
if (effort !== void 0) {
|
|
72
|
-
if (effort !== "default" && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, "invalid_reasoning_effort");
|
|
73
|
-
input.reasoning_effort = effort === "default" ? null : effort;
|
|
74
|
-
}
|
|
75
|
-
const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal, { callId: typeof req.headers["x-multi-router-call"] === "string" ? req.headers["x-multi-router-call"] : void 0, session: typeof req.headers["x-multi-router-session"] === "string" ? req.headers["x-multi-router-session"] : void 0 });
|
|
76
|
-
signal.throwIfAborted();
|
|
77
|
-
res.writeHead(200, {
|
|
78
|
-
"Content-Type": "text/event-stream; charset=utf-8",
|
|
79
|
-
"Cache-Control": "no-cache",
|
|
80
|
-
"X-Accel-Buffering": "no",
|
|
81
|
-
"X-Router-Account": selected.account,
|
|
82
|
-
"X-Router-Provider": selected.provider
|
|
83
|
-
});
|
|
84
|
-
await pipeline(Readable.fromWeb(selected.response.body), res, { signal });
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if (res.destroyed) return;
|
|
87
|
-
if (res.headersSent) {
|
|
88
|
-
res.destroy();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const status = error instanceof RouteError ? error.status : signal.aborted ? 504 : 500;
|
|
92
|
-
json(res, status, { error: {
|
|
93
|
-
type: error instanceof RouteError ? error.code : signal.aborted ? "request_timeout" : "internal_error",
|
|
94
|
-
message: error instanceof RouteError ? error.message : "Router request failed",
|
|
95
|
-
hint: error instanceof RouteError ? error.hint : void 0,
|
|
96
|
-
attempts: error instanceof RouteError ? error.attempts : []
|
|
97
|
-
} });
|
|
98
|
-
} finally {
|
|
99
|
-
active--;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
ready,
|
|
104
|
-
active: () => active,
|
|
105
|
-
token: () => secret,
|
|
106
|
-
baseUrl: () => `http://127.0.0.1:${server.address().port}`,
|
|
107
|
-
// Stop accepting new work, let an already streaming response finish. A
|
|
108
|
-
// reload can race a call that began immediately after the idle check.
|
|
109
|
-
close: async () => {
|
|
110
|
-
await ready.catch(() => {
|
|
111
|
-
});
|
|
112
|
-
await new Promise((resolve) => server.close(resolve));
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export {
|
|
118
|
-
createShim
|
|
119
|
-
};
|
|
120
|
-
//# sourceMappingURL=chunk-WDIT4E2W.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/shim.mjs"],
|
|
4
|
-
"sourcesContent": ["import { createServer } from 'node:http';\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { hostIsLoopback, originIsLoopback } from '../upstream/workbuddy/src/loopback.ts';\nimport { RouteError } from './router.mjs';\nimport { REASONING_EFFORTS } from './reasoning.mjs';\n\nexport function createShim(router, { modelPrefix = '' } = {}) {\n let active = 0;\n const secret = randomBytes(32).toString('base64url');\n const server = createServer((req, res) => { void handle(req, res); });\n const ready = new Promise((resolve, reject) => {\n server.once('listening', resolve); server.once('error', reject);\n });\n server.listen(0, '127.0.0.1');\n const json = (res, status, value) => {\n res.writeHead(status, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(value));\n };\n async function handle(req, res) {\n active++;\n const controller = new AbortController();\n req.on('aborted', () => controller.abort());\n res.on('close', () => { if (!res.writableEnded) controller.abort(); });\n const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(router.config.requestTimeoutMs)]);\n try {\n if (!hostIsLoopback(req.headers.host) || !originIsLoopback(req.headers.origin)) throw new RouteError(403, 'loopback_required');\n const bearer = Buffer.from((req.headers.authorization ?? '').replace(/^Bearer\\s+/i, ''));\n const expected = Buffer.from(secret);\n if (bearer.length !== expected.length || !timingSafeEqual(bearer, expected)) throw new RouteError(401, 'unauthorized');\n if (req.method === 'GET' && req.url === '/router/status') { json(res, 200, router.status()); return; }\n if (req.method === 'GET' && req.url === '/v1/models') {\n json(res, 200, { object: 'list', data: Object.keys(router.config.routes).filter(id => modelPrefix ? id.startsWith(modelPrefix) : !id.includes('::'))\n .map(id => ({ id: modelPrefix ? id.slice(modelPrefix.length) : id, object: 'model', created: 0, owned_by: 'multi-router' })) }); return;\n }\n if (req.method !== 'POST' || req.url !== '/v1/chat/completions') throw new RouteError(404, 'not_found');\n if (!/^application\\/json(?:\\s*;|$)/i.test(req.headers['content-type'] ?? '')) throw new RouteError(415, 'json_required');\n let size = 0; const chunks = [];\n for await (const chunk of req) {\n size += chunk.length;\n if (size > 32 * 1024 * 1024) throw new RouteError(413, 'request_too_large');\n chunks.push(chunk); signal.throwIfAborted();\n }\n let input;\n try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); }\n catch { throw new RouteError(400, 'invalid_json'); }\n if (!input || typeof input !== 'object' || Array.isArray(input)) throw new RouteError(400, 'invalid_request');\n const effort = req.headers['x-multi-router-reasoning'];\n if (effort !== undefined) {\n if (effort !== 'default' && !REASONING_EFFORTS.includes(effort)) throw new RouteError(400, 'invalid_reasoning_effort');\n input.reasoning_effort = effort === 'default' ? null : effort;\n }\n const selected = await router.chat({ ...input, model: modelPrefix + input.model }, signal, { callId: typeof req.headers['x-multi-router-call'] === 'string' ? req.headers['x-multi-router-call'] : undefined, session: typeof req.headers['x-multi-router-session'] === 'string' ? req.headers['x-multi-router-session'] : undefined });\n signal.throwIfAborted();\n res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache',\n 'X-Accel-Buffering': 'no', 'X-Router-Account': selected.account, 'X-Router-Provider': selected.provider });\n await pipeline(Readable.fromWeb(selected.response.body), res, { signal });\n } catch (error) {\n if (res.destroyed) return;\n if (res.headersSent) { res.destroy(); return; }\n const status = error instanceof RouteError ? error.status : signal.aborted ? 504 : 500;\n json(res, status, { error: { type: error instanceof RouteError ? error.code : signal.aborted ? 'request_timeout' : 'internal_error',\n message: error instanceof RouteError ? error.message : 'Router request failed',\n hint: error instanceof RouteError ? error.hint : undefined,\n attempts: error instanceof RouteError ? error.attempts : [] } });\n } finally { active--; }\n }\n return { ready, active: () => active, token: () => secret, baseUrl: () => `http://127.0.0.1:${server.address().port}`,\n // Stop accepting new work, let an already streaming response finish. A\n // reload can race a call that began immediately after the idle check.\n close: async () => { await ready.catch(() => {}); await new Promise(resolve => server.close(resolve)); } };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
package/lib/cli.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
createAccountClient
|
|
4
|
-
} from "./chunk-NZF36O5I.js";
|
|
5
|
-
import {
|
|
6
|
-
importAccount
|
|
7
|
-
} from "./chunk-2UM45OXY.js";
|
|
8
|
-
import "./chunk-FLL3DUVP.js";
|
|
9
|
-
import "./chunk-KWXLQJS5.js";
|
|
10
|
-
import "./chunk-S5KMFAQM.js";
|
|
11
|
-
import "./chunk-4V52V5GO.js";
|
|
12
|
-
import {
|
|
13
|
-
Router
|
|
14
|
-
} from "./chunk-4L2CBGXN.js";
|
|
15
|
-
import {
|
|
16
|
-
DEFAULT_ROUTES,
|
|
17
|
-
PROVIDERS,
|
|
18
|
-
validateConfig,
|
|
19
|
-
writePrivateJson
|
|
20
|
-
} from "./chunk-GEBZMIMA.js";
|
|
21
|
-
|
|
22
|
-
// src/cli.mjs
|
|
23
|
-
import { parseArgs } from "node:util";
|
|
24
|
-
import { readFile } from "node:fs/promises";
|
|
25
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
26
|
-
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
27
|
-
import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
|
|
28
|
-
var { values, positionals } = parseArgs({ allowPositionals: true, options: Object.fromEntries(
|
|
29
|
-
["config", "id", "provider", "source", "edition", "identity"].map((key) => [key, { type: "string" }])
|
|
30
|
-
) });
|
|
31
|
-
var file = resolve(values.config ?? process.env.DSH_MULTI_ROUTER_CONFIG ?? join(resolveDshHome(), "multi-router", "router.json"));
|
|
32
|
-
var [command] = positionals;
|
|
33
|
-
async function main() {
|
|
34
|
-
if (command === "init") {
|
|
35
|
-
await readFile(file).then(() => {
|
|
36
|
-
throw new Error("Config already exists");
|
|
37
|
-
}, (error) => {
|
|
38
|
-
if (error.code !== "ENOENT") throw error;
|
|
39
|
-
});
|
|
40
|
-
await writePrivateJson(file, { accounts: [], routes: DEFAULT_ROUTES });
|
|
41
|
-
console.log(`Created ${file}`);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (!["add", "remove", "enable", "disable", "status", "models"].includes(command)) {
|
|
45
|
-
console.log("dsh-multi-router init|add|remove|enable|disable|status|models [--config /path/router.json]\nadd --id wb1 --provider workbuddy --source /path/auth-file\nTrae add: --edition solo; own credential JSON: --identity /path/identity.json\nremove|enable|disable|models: --id account-id\nProviders: " + PROVIDERS.join(", "));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const raw = JSON.parse(await readFile(file, "utf8"));
|
|
49
|
-
let config = validateConfig(raw, file);
|
|
50
|
-
if (command === "status") {
|
|
51
|
-
console.log(JSON.stringify(new Router(config, () => {
|
|
52
|
-
}).status(), null, 2));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (command === "add") {
|
|
56
|
-
const { id, provider, source } = values;
|
|
57
|
-
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(id ?? "") || !PROVIDERS.includes(provider) || !source) throw new Error("add needs valid --id, --provider and --source");
|
|
58
|
-
if (raw.accounts.some((a) => a.id === id)) throw new Error("Account already exists; remove it before replacing");
|
|
59
|
-
const dir = join(dirname(file), "accounts", id);
|
|
60
|
-
const credentialFile = join(dir, "credential.json");
|
|
61
|
-
const identityFile = join(dir, "identity.json");
|
|
62
|
-
await importAccount({
|
|
63
|
-
provider,
|
|
64
|
-
source: resolve(source),
|
|
65
|
-
credentialFile,
|
|
66
|
-
identityFile,
|
|
67
|
-
edition: values.edition,
|
|
68
|
-
identitySource: values.identity ? resolve(values.identity) : void 0
|
|
69
|
-
});
|
|
70
|
-
raw.accounts.push({
|
|
71
|
-
id,
|
|
72
|
-
provider,
|
|
73
|
-
credentialFile: relative(dirname(file), credentialFile),
|
|
74
|
-
...provider.startsWith("trae") ? { identityFile: relative(dirname(file), identityFile) } : {}
|
|
75
|
-
});
|
|
76
|
-
validateConfig(raw, file);
|
|
77
|
-
await writePrivateJson(file, raw);
|
|
78
|
-
console.log(`Added ${id} (${provider}). The running plugin applies changes automatically.`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const account = config.accounts.find((a) => a.id === values.id);
|
|
82
|
-
if (!account) throw new Error("Unknown --id");
|
|
83
|
-
if (command === "models") {
|
|
84
|
-
const client = await createAccountClient(account, config);
|
|
85
|
-
console.log(JSON.stringify(await client.models(AbortSignal.timeout(6e4)), null, 2));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (command === "remove") {
|
|
89
|
-
raw.accounts = raw.accounts.filter((a) => a.id !== account.id);
|
|
90
|
-
validateConfig(raw, file);
|
|
91
|
-
await writePrivateJson(file, raw);
|
|
92
|
-
console.log(`Removed ${account.id} from routing; credential files kept. Changes apply automatically.`);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
raw.accounts.find((a) => a.id === account.id).enabled = command === "enable";
|
|
96
|
-
await writePrivateJson(file, raw);
|
|
97
|
-
console.log(`${command} ${account.id}. Changes apply automatically.`);
|
|
98
|
-
}
|
|
99
|
-
(["add", "remove", "enable", "disable"].includes(command) ? withFileLock(file, main, { waitMs: 3e4 }) : main()).catch((error) => {
|
|
100
|
-
console.error(error instanceof Error ? error.message.replace(/Bearer\s+\S+/gi, "Bearer [redacted]") : "Command failed");
|
|
101
|
-
process.exitCode = 1;
|
|
102
|
-
});
|
|
103
|
-
//# sourceMappingURL=cli.js.map
|
package/lib/cli.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/cli.mjs"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { resolveDshHome } from '@deepseek-ai/dsh-home-paths';\nimport { withFileLock } from '@deepseek-ai/dsh-atomic-write';\nimport { PROVIDERS, DEFAULT_ROUTES, readConfig, validateConfig, writePrivateJson } from './config.mjs';\nimport { importAccount } from './accounts.mjs';\nimport { createAccountClient } from './clients.mjs';\nimport { Router } from './router.mjs';\n\nconst { values, positionals } = parseArgs({ allowPositionals: true, options: Object.fromEntries(\n ['config', 'id', 'provider', 'source', 'edition', 'identity'].map(key => [key, { type: 'string' }])) });\nconst file = resolve(values.config ?? process.env.DSH_MULTI_ROUTER_CONFIG ?? join(resolveDshHome(), 'multi-router', 'router.json'));\nconst [command] = positionals;\nasync function main() {\n if (command === 'init') {\n await readFile(file).then(() => { throw new Error('Config already exists'); }, error => { if (error.code !== 'ENOENT') throw error; });\n await writePrivateJson(file, { accounts: [], routes: DEFAULT_ROUTES });\n console.log(`Created ${file}`); return;\n }\n if (!['add', 'remove', 'enable', 'disable', 'status', 'models'].includes(command)) {\n console.log('dsh-multi-router init|add|remove|enable|disable|status|models [--config /path/router.json]\\n'\n + 'add --id wb1 --provider workbuddy --source /path/auth-file\\n'\n + 'Trae add: --edition solo; own credential JSON: --identity /path/identity.json\\n'\n + 'remove|enable|disable|models: --id account-id\\nProviders: ' + PROVIDERS.join(', ')); return;\n }\n const raw = JSON.parse(await readFile(file, 'utf8'));\n let config = validateConfig(raw, file);\n if (command === 'status') {\n console.log(JSON.stringify(new Router(config, () => {}).status(), null, 2)); return;\n }\n if (command === 'add') {\n const { id, provider, source } = values;\n if (!/^[a-zA-Z0-9_-]{1,64}$/.test(id ?? '') || !PROVIDERS.includes(provider) || !source) throw new Error('add needs valid --id, --provider and --source');\n if (raw.accounts.some(a => a.id === id)) throw new Error('Account already exists; remove it before replacing');\n const dir = join(dirname(file), 'accounts', id);\n const credentialFile = join(dir, 'credential.json');\n const identityFile = join(dir, 'identity.json');\n await importAccount({ provider, source: resolve(source), credentialFile, identityFile, edition: values.edition,\n identitySource: values.identity ? resolve(values.identity) : undefined });\n raw.accounts.push({ id, provider, credentialFile: relative(dirname(file), credentialFile),\n ...(provider.startsWith('trae') ? { identityFile: relative(dirname(file), identityFile) } : {}) });\n validateConfig(raw, file); await writePrivateJson(file, raw);\n console.log(`Added ${id} (${provider}). The running plugin applies changes automatically.`); return;\n }\n const account = config.accounts.find(a => a.id === values.id);\n if (!account) throw new Error('Unknown --id');\n if (command === 'models') {\n const client = await createAccountClient(account, config);\n console.log(JSON.stringify(await client.models(AbortSignal.timeout(60000)), null, 2)); return;\n }\n if (command === 'remove') {\n raw.accounts = raw.accounts.filter(a => a.id !== account.id);\n // Reject dangling route bindings before changing files. Credential files remain recoverable.\n validateConfig(raw, file); await writePrivateJson(file, raw);\n console.log(`Removed ${account.id} from routing; credential files kept. Changes apply automatically.`); return;\n }\n raw.accounts.find(a => a.id === account.id).enabled = command === 'enable';\n await writePrivateJson(file, raw);\n console.log(`${command} ${account.id}. Changes apply automatically.`);\n}\n(['add', 'remove', 'enable', 'disable'].includes(command) ? withFileLock(file, main, { waitMs: 30000 }) : main())\n .catch(error => { console.error(error instanceof Error ? error.message.replace(/Bearer\\s+\\S+/gi, 'Bearer [redacted]') : 'Command failed'); process.exitCode = 1; });\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|