dsh-multi-router 0.5.6 → 0.5.8

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.
@@ -0,0 +1,514 @@
1
+ import {
2
+ createShim
3
+ } from "./chunk-P43AWJ4Q.js";
4
+ import {
5
+ MANAGEMENT_PATH,
6
+ Manager,
7
+ managementHandler,
8
+ version
9
+ } from "./chunk-ILRW3ELD.js";
10
+ import {
11
+ createAccountClient
12
+ } from "./chunk-U2MU2M5C.js";
13
+ import "./chunk-OFFVP6YN.js";
14
+ import "./chunk-FTM7RHQV.js";
15
+ import "./chunk-S5KMFAQM.js";
16
+ import "./chunk-DNQPZMPF.js";
17
+ import {
18
+ Router,
19
+ SessionMetrics,
20
+ abortable
21
+ } from "./chunk-J3XO7RTF.js";
22
+ import {
23
+ PROVIDERS,
24
+ PROVIDER_NAMES,
25
+ ensureConfig
26
+ } from "./chunk-MCHV2R25.js";
27
+ import {
28
+ normalizeCredits
29
+ } from "./chunk-TEZ2FMEO.js";
30
+
31
+ // src/index.mjs
32
+ import z from "@deepseek-ai/schemastery";
33
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
34
+ import { resolveRetryPolicy as resolveRetryPolicy2, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
35
+ import { join as join2, resolve } from "node:path";
36
+
37
+ // upstream/workbuddy/src/adapter.ts
38
+ import { createProvider } from "@earendil-works/pi-ai";
39
+ import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
40
+ import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
41
+ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
42
+ var WORKBUDDY_PROVIDER = "workbuddy";
43
+ var WORKBUDDY_STREAM_IDLE_TIMEOUT_MS = 3e5;
44
+ var REQUEST_IMAGE_BUDGETS = {
45
+ maxRequestImageBytes: 20971520,
46
+ requestImagePixelBudget: 4194304,
47
+ requestImageMaxBytes: 1048576
48
+ };
49
+ var INERT_AUTH = {
50
+ credentials: {
51
+ async read() {
52
+ return void 0;
53
+ },
54
+ async list() {
55
+ return [];
56
+ },
57
+ async modify() {
58
+ throw new Error("dsh-workbuddy-connect: the workbuddy route has no pi-ai credential lifecycle");
59
+ },
60
+ async delete() {
61
+ }
62
+ },
63
+ authContext: {
64
+ async env() {
65
+ return void 0;
66
+ },
67
+ async fileExists() {
68
+ return false;
69
+ }
70
+ }
71
+ };
72
+ var NO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
73
+ function withLegacyImageBudget(store) {
74
+ return new Proxy(store, {
75
+ get(target, property, receiver) {
76
+ if (property === "imageHostPath") {
77
+ return target.imageHostPath.bind(target);
78
+ }
79
+ if (property !== "readImageRequest") return Reflect.get(target, property, receiver);
80
+ return (...args) => {
81
+ const [ref, policy, signal] = args;
82
+ const present = policy?.maxPixels;
83
+ const withPixels = Number.isSafeInteger(present) && present > 0 ? policy : { ...policy, maxPixels: REQUEST_IMAGE_BUDGETS.requestImagePixelBudget };
84
+ return target.readImageRequest(ref, withPixels, signal);
85
+ };
86
+ }
87
+ });
88
+ }
89
+ var RATE_SEPARATOR = " \xB7 ";
90
+ function displaySuffix(info) {
91
+ const parts = [
92
+ normalizeCredits(info.billing?.credits),
93
+ ...info.billing?.badges ?? []
94
+ ].filter((part) => part !== void 0 && part !== "");
95
+ return parts.length === 0 ? void 0 : parts.join(" \xB7 ");
96
+ }
97
+ function withCatalogDisplay(name2, info) {
98
+ const suffix = displaySuffix(info);
99
+ return suffix === void 0 ? name2 : `${name2}${RATE_SEPARATOR}${suffix}`;
100
+ }
101
+ function reasoningFields(info, observed, region = "cn") {
102
+ const reasoning = info.reasoning;
103
+ if (reasoning === void 0 || reasoning.supports !== true) {
104
+ return { reasoning: false };
105
+ }
106
+ const declared = reasoning.supportedEfforts;
107
+ const efforts = declared !== void 0 && declared.length > 0 ? declared : observed?.validation === "validating" && observed.efforts.length > 0 ? observed.efforts : void 0;
108
+ if (efforts === void 0) {
109
+ return { reasoning: false };
110
+ }
111
+ const map = {
112
+ // Probing never grants `off`; only an explicit declaration does.
113
+ //
114
+ // On CN, `onlyReasoning` vetoes it even when declared (#87). That catalog
115
+ // ships both flags true on `deepseek-v4.1-flash` and `kimi-k2.8-preview`,
116
+ // which cannot both hold — a model that only ever reasons has no
117
+ // non-thinking mode to select — and the endpoint sides with
118
+ // `onlyReasoning`, answering HTTP 400 (「模型不支持该思考强度」) for every
119
+ // `off` request. Offering a level that is certain to fail is worse than not
120
+ // offering it, so the contradiction is resolved toward the flag the wire
121
+ // agrees with.
122
+ //
123
+ // Scoped to CN on purpose: the international catalog does not carry this
124
+ // contradiction, and hiding Off there would remove a level those models
125
+ // can still use. The request layer strips the spelling in both regions
126
+ // (`dropUnsupportedEffort`), so a model whose flags flip between catalog
127
+ // reads cannot put `off` on the wire either way.
128
+ off: reasoning.canDisableThinking === true && (region !== "cn" || reasoning.onlyReasoning !== true) && declared !== void 0 && declared.length > 0 ? "off" : null,
129
+ // `minimal` is not in the upstream effort vocabulary (EFFORT_VALUES), so
130
+ // no declared set — and no probe candidate — can ever contain it.
131
+ minimal: null,
132
+ low: efforts.includes("low") ? "low" : null,
133
+ medium: efforts.includes("medium") ? "medium" : null,
134
+ high: efforts.includes("high") ? "high" : null,
135
+ xhigh: efforts.includes("xhigh") ? "xhigh" : null,
136
+ max: efforts.includes("max") ? "max" : null
137
+ };
138
+ return { reasoning: true, thinkingLevelMap: map };
139
+ }
140
+ function toPiModel(info, baseUrl, observed, providerId, region) {
141
+ return {
142
+ id: info.id,
143
+ name: info.name,
144
+ api: "openai-completions",
145
+ provider: providerId,
146
+ baseUrl,
147
+ input: info.supportsImages === true ? ["text", "image"] : ["text"],
148
+ ...reasoningFields(info, observed, region),
149
+ cost: NO_COST,
150
+ contextWindow: info.contextWindow,
151
+ maxTokens: info.maxTokens,
152
+ // pi-ai cannot infer WorkBuddy's field spelling from the shim's random
153
+ // loopback URL, so name the upstream-required field explicitly.
154
+ compat: { maxTokensField: "max_tokens" }
155
+ };
156
+ }
157
+ function createWorkBuddyAdapter(options) {
158
+ const { shim, store, catalog, resolveAttachments, resolveImageAccess, observe, hidden } = options;
159
+ const providerId = options.providerId ?? WORKBUDDY_PROVIDER;
160
+ const displayName = options.displayName ?? "WorkBuddy";
161
+ const region = options.region ?? "cn";
162
+ const buildModels = () => {
163
+ const baseUrl = `${shim.baseUrl()}/v1`;
164
+ return catalog.current().map((info) => toPiModel(info, baseUrl, observe?.(info.id), providerId, region));
165
+ };
166
+ const base = createProvider({
167
+ id: providerId,
168
+ name: displayName,
169
+ auth: {
170
+ apiKey: {
171
+ name: "WorkBuddy OAuth bearer token",
172
+ async resolve({ credential }) {
173
+ const apiKey = credential?.key;
174
+ return apiKey === void 0 || apiKey.length === 0 ? void 0 : { auth: { apiKey }, source: "WorkBuddy" };
175
+ }
176
+ }
177
+ },
178
+ models: buildModels(),
179
+ api: openAICompletionsApi()
180
+ });
181
+ const provider = { ...base, getModels: () => buildModels() };
182
+ const profile = {
183
+ provider: providerId,
184
+ displayName,
185
+ streamIdleTimeoutMs: WORKBUDDY_STREAM_IDLE_TIMEOUT_MS,
186
+ retryPolicy: resolveRetryPolicy(void 0, "dsh-workbuddy-connect retryPolicy"),
187
+ configuredMaxTokens: /* @__PURE__ */ new Map(),
188
+ // Required since 0.1.5-alpha.2; the live catalog only exposes models that
189
+ // probed successfully, so there is never a per-model failure to report.
190
+ modelErrors: /* @__PURE__ */ new Map(),
191
+ ...REQUEST_IMAGE_BUDGETS,
192
+ piProvider: provider
193
+ };
194
+ let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
195
+ const adapter = new WorkBuddyPiAiAdapter(catalog, hidden ?? (() => []), {
196
+ profiles: () => profiles,
197
+ auth: INERT_AUTH,
198
+ // Resolve the shim's per-process shared secret as the OpenAI apiKey so
199
+ // pi-ai sends it as `Authorization: Bearer <shared-secret>`. The shim
200
+ // validates this before forwarding and resolves the real WorkBuddy token
201
+ // itself via the store, so the secret never reaches upstream.
202
+ resolveApiKey: async () => shim.token(),
203
+ // Every store the adapter hands to pi-ai passes the legacy-budget wrapper:
204
+ // see withLegacyImageBudget — the mismatch it heals depends on which host
205
+ // generation owns the attachment service, not on anything observable here.
206
+ ...resolveAttachments === void 0 ? {} : {
207
+ resolveAttachments: () => {
208
+ const store2 = resolveAttachments();
209
+ return store2 === void 0 ? void 0 : withLegacyImageBudget(store2);
210
+ }
211
+ },
212
+ ...resolveImageAccess === void 0 ? {} : { resolveImageAccess }
213
+ });
214
+ return {
215
+ adapter,
216
+ invalidate: () => {
217
+ profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
218
+ }
219
+ };
220
+ }
221
+ var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
222
+ constructor(catalog, hidden, options) {
223
+ super(options);
224
+ this.catalog = catalog;
225
+ this.hidden = hidden;
226
+ }
227
+ /** Catalog entry for one model id, or undefined when the catalog omits it. */
228
+ infoFor(model) {
229
+ return this.catalog.current().find((entry) => entry.id === model);
230
+ }
231
+ async listModels(provider) {
232
+ const models = await super.listModels(provider);
233
+ const hidden = new Set(this.hidden());
234
+ return models.flatMap((model) => {
235
+ if (hidden.has(model.id)) return [];
236
+ const info = this.infoFor(model.id);
237
+ if (info === void 0) return [model];
238
+ return [{ ...model, name: withCatalogDisplay(model.name, info) }];
239
+ });
240
+ }
241
+ async resolveModel(provider, model, signal) {
242
+ const resolved = await super.resolveModel(provider, model, signal);
243
+ const info = this.infoFor(model);
244
+ if (info === void 0) return resolved;
245
+ return { ...resolved, name: withCatalogDisplay(resolved.name, info) };
246
+ }
247
+ };
248
+
249
+ // src/plugin-reload.mjs
250
+ import { readFile } from "node:fs/promises";
251
+ import { watch } from "node:fs";
252
+ import { join } from "node:path";
253
+ function outsideActivation(hmr, operation) {
254
+ const scope = hmr?.executing;
255
+ return typeof scope?.exit === "function" ? scope.exit(operation) : operation();
256
+ }
257
+ var PluginReload = class {
258
+ constructor({ service, profile, canReload, hmr = () => void 0, busy = () => false, now = Date.now, runningVersion }) {
259
+ Object.assign(this, { service, profile, canReload, hmr, busy, now, runningVersion });
260
+ this.phase = "current";
261
+ this.installedVersion = runningVersion;
262
+ this.retryAt = 0;
263
+ }
264
+ state() {
265
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
266
+ }
267
+ start() {
268
+ const dir = this.profile()?.dir;
269
+ if (dir) try {
270
+ this.watcher = watch(dir, () => this.schedule());
271
+ this.watcher.unref();
272
+ } catch {
273
+ }
274
+ this.schedule();
275
+ }
276
+ schedule(delay = 500) {
277
+ if (this.closed) return;
278
+ clearTimeout(this.timer);
279
+ this.timer = setTimeout(() => {
280
+ void this.check().catch(() => {
281
+ });
282
+ }, delay);
283
+ this.timer.unref();
284
+ }
285
+ stop() {
286
+ this.closed = true;
287
+ clearTimeout(this.timer);
288
+ this.watcher?.close();
289
+ }
290
+ check() {
291
+ if (this.closed) return Promise.resolve();
292
+ if (this.flight) return this.flight;
293
+ this.flight = this.inspect().finally(() => {
294
+ this.flight = void 0;
295
+ });
296
+ return this.flight;
297
+ }
298
+ async inspect() {
299
+ const dir = this.profile()?.dir;
300
+ if (!dir) return;
301
+ let manifest, installed;
302
+ try {
303
+ manifest = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
304
+ installed = JSON.parse(await readFile(join(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
305
+ } catch {
306
+ return;
307
+ }
308
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
309
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
310
+ this.installedVersion = installed.version;
311
+ if (installed.version === this.runningVersion) {
312
+ this.phase = "current";
313
+ this.message = void 0;
314
+ return;
315
+ }
316
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
317
+ this.phase = "restart-required";
318
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
319
+ return;
320
+ }
321
+ if (this.busy()) {
322
+ this.phase = "waiting";
323
+ 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";
324
+ this.schedule(1e3);
325
+ return;
326
+ }
327
+ const root = join(dir, "node_modules/dsh-multi-router");
328
+ try {
329
+ const patch = await readFile(join(root, "cordis.patch.yml"), "utf8");
330
+ if (!patch.includes(`id: llm-multi-router-v2-${installed.version}
331
+ `) || !patch.includes(`name: ./lib/host-v${installed.version}.js`)) throw Error("legacy entry");
332
+ await readFile(join(root, `lib/host-v${installed.version}.js`));
333
+ } catch {
334
+ this.phase = "restart-required";
335
+ 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";
336
+ return;
337
+ }
338
+ if (this.now() < this.retryAt) return;
339
+ this.phase = "reloading";
340
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
341
+ this.retryAt = this.now() + 6e4;
342
+ try {
343
+ const result = await outsideActivation(this.hmr(), () => this.service().setBundleEnabled("dsh-multi-router", true));
344
+ if (this.closed) return;
345
+ this.phase = "restart-required";
346
+ 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";
347
+ } catch {
348
+ if (!this.closed) {
349
+ this.phase = "failed";
350
+ 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";
351
+ }
352
+ }
353
+ }
354
+ };
355
+
356
+ // src/index.mjs
357
+ var name = "dsh-multi-router";
358
+ var inject = ["llm"];
359
+ var defaultConfigFile = () => join2(resolveDshHome(), "multi-router", "router.json");
360
+ var Config = z.object({ configFile: z.string().description("Absolute path to multi-account router.json") });
361
+ async function apply(ctx, options = {}) {
362
+ let disposed = false, manager, stopWatch, monitorTimer, startup;
363
+ const releases = [], shims = [], wrappers = [];
364
+ ctx.effect(() => () => {
365
+ disposed = true;
366
+ stopWatch?.();
367
+ clearInterval(monitorTimer);
368
+ manager?.runtime?.stop();
369
+ if (manager) manager.closed = true;
370
+ manager?.plugins.abort.abort();
371
+ manager?.checkins.abort.abort();
372
+ manager?.workbuddyLogin.abort.abort();
373
+ for (const release of releases) release();
374
+ return Promise.all([...shims.map((shim) => shim.close()), ...manager?.refreshes.values() ?? [], ...manager?.monitorFlights.values() ?? [], startup, manager?.localScan, manager?.checkins.flight, manager?.plugins.flight, manager?.plugins.saveQueue.catch(() => {
375
+ }), manager?.telemetry.queue.catch(() => {
376
+ })]);
377
+ });
378
+ const file = resolve(options.configFile ?? process.env.DSH_MULTI_ROUTER_CONFIG ?? defaultConfigFile());
379
+ try {
380
+ const config = await ensureConfig(file);
381
+ if (disposed) return;
382
+ const router = new Router(
383
+ config,
384
+ (a) => createAccountClient(a, manager?.config ?? config, ctx.get("attachments")),
385
+ (event) => ctx.logger.info("multi-router", JSON.stringify(event))
386
+ );
387
+ manager = new Manager(file, router, () => {
388
+ for (const wrapper of wrappers) wrapper.invalidate();
389
+ ctx.emit("llm/adapters-updated");
390
+ }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
391
+ await manager.load();
392
+ if (disposed) return;
393
+ manager.startup = { phase: "discovering" };
394
+ manager.runtime = new PluginReload({
395
+ runningVersion: version,
396
+ service: () => ctx.get("pluginManager"),
397
+ profile: () => ctx.get("profileContext"),
398
+ canReload: () => !!ctx.get("hmr"),
399
+ hmr: () => ctx.get("hmr"),
400
+ busy: () => !!(manager.localScan || manager.requests || manager.plugins.flight || manager.checkins.flight || manager.workbuddyLogin.flight || manager.workbuddyLogin.pending || manager.refreshes.size || manager.monitorFlights.size || shims.some((s) => s.active()))
401
+ });
402
+ ctx.inject?.(["webServer"], (webCtx) => {
403
+ webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
404
+ });
405
+ const metrics = new SessionMetrics(manager.telemetry);
406
+ ctx.on("session/event", (session, event) => {
407
+ try {
408
+ return metrics.event(session, event)?.catch(() => ctx.logger.warn("multi-router: performance persistence failed"));
409
+ } catch {
410
+ ctx.logger.warn("multi-router: performance event unavailable");
411
+ }
412
+ });
413
+ ctx.on("session/disposed", (session) => metrics.dispose(session));
414
+ for (const provider of ["multi-router", ...PROVIDERS]) {
415
+ if (disposed) return;
416
+ const isRouter = provider === "multi-router";
417
+ const shim = createShim(router, { modelPrefix: isRouter ? "" : `${provider}::` });
418
+ shims.push(shim);
419
+ await shim.ready;
420
+ if (disposed) {
421
+ await shim.close();
422
+ return;
423
+ }
424
+ const wrapper = createWorkBuddyAdapter({
425
+ providerId: provider,
426
+ displayName: isRouter ? "\u7EDF\u4E00\u8DEF\u7531" : `${PROVIDER_NAMES[provider]} \xB7 \u5185\u7F6E\u8D26\u53F7\u6C60`,
427
+ shim,
428
+ catalog: { current: () => isRouter ? manager.adapterModels() : manager.providerModels(provider) },
429
+ hidden: () => isRouter ? manager.adapterModels().filter((r) => r.visible === false).map((r) => r.id) : manager.hiddenModels(provider),
430
+ region: provider.endsWith("global") || provider === "workbuddy-ai" ? "global" : "cn",
431
+ resolveAttachments: () => ctx.get("attachments"),
432
+ resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(
433
+ attachments,
434
+ (path) => ctx.get("fs")?.processPathFromHostPath(path),
435
+ ref
436
+ )
437
+ });
438
+ if (!isRouter) {
439
+ for (const method of ["listModels", "resolveModel", "prepareCall"]) {
440
+ const original = wrapper.adapter[method].bind(wrapper.adapter);
441
+ wrapper.adapter[method] = async (...args) => {
442
+ await abortable(manager.ensureCatalog(provider), args[2]);
443
+ return original(...args);
444
+ };
445
+ }
446
+ }
447
+ const streamWithSnapshot = wrapper.adapter.streamWithSnapshot.bind(wrapper.adapter);
448
+ wrapper.adapter.streamWithSnapshot = (options2, snapshot) => {
449
+ const call = metrics.beginCall(options2.sessionId, options2.purpose);
450
+ const route = router.config.routes[(isRouter ? "" : `${provider}::`) + options2.model];
451
+ const effort = options2.reasoningEffort ?? route?.reasoningEffort;
452
+ const profiles = new Map(snapshot.profiles), profile = profiles.get(options2.provider);
453
+ if (!profile) return streamWithSnapshot(options2, snapshot);
454
+ profiles.set(options2.provider, { ...profile, headers: { ...profile.headers, "X-Multi-Router-Reasoning": effort ?? "default", "X-Multi-Router-Call": call.callId, ...call.session ? { "X-Multi-Router-Session": call.session } : {} } });
455
+ return streamWithSnapshot({ ...options2, ...effort ? { reasoningEffort: effort } : {} }, { ...snapshot, profiles });
456
+ };
457
+ const withDefault = (model) => {
458
+ const effort = router.config.routes[(isRouter ? "" : `${provider}::`) + model.id]?.reasoningEffort;
459
+ return effort && model.reasoning ? { ...model, reasoning: { ...model.reasoning, defaultEffort: effort } } : model;
460
+ };
461
+ const resolveModel = wrapper.adapter.resolveModel.bind(wrapper.adapter), prepareCall = wrapper.adapter.prepareCall.bind(wrapper.adapter);
462
+ wrapper.adapter.resolveModel = async (...args) => withDefault(await resolveModel(...args));
463
+ wrapper.adapter.prepareCall = async (...args) => {
464
+ const prepared = await prepareCall(...args);
465
+ return { ...prepared, model: withDefault(prepared.model) };
466
+ };
467
+ wrapper.adapter.providerRetryPolicy = () => resolveRetryPolicy2({ mode: "normal", maxRetries: 0 }, provider);
468
+ wrappers.push(wrapper);
469
+ releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
470
+ }
471
+ stopWatch = manager.watch(() => ctx.logger.warn("multi-router: invalid config change; keeping last valid configuration"));
472
+ startup = (async () => {
473
+ await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
474
+ if (disposed) return;
475
+ await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
476
+ if (disposed) return;
477
+ manager.startup = { phase: "ready" };
478
+ manager.checkins.tick();
479
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
480
+ void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
481
+ })();
482
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
483
+ manager.runtime.start();
484
+ ctx.on("plugin-manager/changed", () => manager.runtime.schedule());
485
+ monitorTimer = setInterval(() => {
486
+ void manager.syncLocalAccounts().then(() => {
487
+ if (!disposed) return manager.monitorAll();
488
+ }).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
489
+ manager.checkins.tick();
490
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
491
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
492
+ void manager.runtime.check().catch(() => {
493
+ });
494
+ }, 6e4);
495
+ monitorTimer.unref();
496
+ ctx.emit("llm/adapters-updated");
497
+ } catch (error) {
498
+ clearInterval(monitorTimer);
499
+ stopWatch?.();
500
+ manager?.runtime?.stop();
501
+ for (const release of releases) release();
502
+ await Promise.all(shims.map((shim) => shim.close()));
503
+ ctx.logger.error("multi-router: initialization failed", error.code ?? error.message);
504
+ throw error;
505
+ }
506
+ }
507
+ export {
508
+ Config,
509
+ apply,
510
+ defaultConfigFile,
511
+ inject,
512
+ name
513
+ };
514
+ //# sourceMappingURL=index.js.map