llm-simple-router 0.9.24 → 0.9.25

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 (65) hide show
  1. package/dist/admin/providers.d.ts +2 -0
  2. package/dist/admin/providers.js +42 -1
  3. package/dist/admin/routes.d.ts +2 -0
  4. package/dist/admin/routes.js +1 -1
  5. package/dist/core/container.d.ts +1 -0
  6. package/dist/core/container.js +1 -0
  7. package/dist/db/migrations/041_add_provider_proxy.sql +7 -0
  8. package/dist/db/providers.d.ts +9 -1
  9. package/dist/db/providers.js +3 -3
  10. package/dist/index.js +6 -1
  11. package/dist/proxy/handler/anthropic.js +1 -1
  12. package/dist/proxy/handler/openai.js +1 -1
  13. package/dist/proxy/handler/proxy-handler.d.ts +2 -0
  14. package/dist/proxy/handler/proxy-handler.js +1 -0
  15. package/dist/proxy/handler/responses.js +1 -1
  16. package/dist/proxy/transport/http.d.ts +4 -3
  17. package/dist/proxy/transport/http.js +8 -7
  18. package/dist/proxy/transport/proxy-agent.d.ts +16 -0
  19. package/dist/proxy/transport/proxy-agent.js +54 -0
  20. package/dist/proxy/transport/stream.d.ts +2 -1
  21. package/dist/proxy/transport/stream.js +2 -2
  22. package/dist/proxy/transport/transport-fn.d.ts +2 -0
  23. package/dist/proxy/transport/transport-fn.js +3 -2
  24. package/frontend-dist/assets/{CardContent-rBUVGzRR.js → CardContent-D2E8XPMF.js} +1 -1
  25. package/frontend-dist/assets/{CardTitle-DhlJArbg.js → CardTitle-Bvn47Yr0.js} +1 -1
  26. package/frontend-dist/assets/{Checkbox-uHKeZW1P.js → Checkbox-CHMJbyg6.js} +1 -1
  27. package/frontend-dist/assets/{CollapsibleContent-B1v6hP95.js → CollapsibleContent-5Mrc8gGt.js} +1 -1
  28. package/frontend-dist/assets/{CollapsibleTrigger-BmuPdmL7.js → CollapsibleTrigger-DaAYAs8_.js} +1 -1
  29. package/frontend-dist/assets/{Dashboard-DzhKhSr8.js → Dashboard-JuNvaAgL.js} +1 -1
  30. package/frontend-dist/assets/{Input-CjLowMgU.js → Input-D34hdiws.js} +1 -1
  31. package/frontend-dist/assets/{Label-UyzkNwCC.js → Label-NPWP7UVZ.js} +1 -1
  32. package/frontend-dist/assets/{Login-DkFE8Cwv.js → Login-CiMHu-aw.js} +1 -1
  33. package/frontend-dist/assets/{Logs-BpPwbhfY.js → Logs-RRwgGUbN.js} +1 -1
  34. package/frontend-dist/assets/{MappingEntryEditor-CCRjU4OE.js → MappingEntryEditor-B4fiJi8Q.js} +1 -1
  35. package/frontend-dist/assets/{ModelCard-BVDPemId.js → ModelCard-DSpT9oxm.js} +1 -1
  36. package/frontend-dist/assets/{ModelMappings-YZj4_coo.js → ModelMappings-CsgtxPOH.js} +1 -1
  37. package/frontend-dist/assets/{Monitor-ufpuUH7M.js → Monitor-KQ4-zFJ3.js} +1 -1
  38. package/frontend-dist/assets/Providers-B8kM2PFx.js +1 -0
  39. package/frontend-dist/assets/{ProxyEnhancement-YzWODq0h.js → ProxyEnhancement-Cczah5af.js} +1 -1
  40. package/frontend-dist/assets/{QuickSetup-DotjBKIE.js → QuickSetup-BPa2psLw.js} +1 -1
  41. package/frontend-dist/assets/{RetryRules-CWWOgcSI.js → RetryRules-B4kfx7KE.js} +1 -1
  42. package/frontend-dist/assets/{RouterKeys-fB8t51xY.js → RouterKeys-B6gaOE5V.js} +1 -1
  43. package/frontend-dist/assets/{RovingFocusItem-BpkzLwee.js → RovingFocusItem-ZGq4Eu8v.js} +1 -1
  44. package/frontend-dist/assets/{Schedules-CO4oIdFI.js → Schedules-tkI3OZrg.js} +1 -1
  45. package/frontend-dist/assets/{Settings--ALxwdhl.js → Settings-DRcVz0VH.js} +1 -1
  46. package/frontend-dist/assets/{Setup-BdyHuJPP.js → Setup-CPx8uTQg.js} +1 -1
  47. package/frontend-dist/assets/{Switch-BIriILc9.js → Switch-BgKvsuZd.js} +1 -1
  48. package/frontend-dist/assets/{TooltipTrigger-Qtuo87Qk.js → TooltipTrigger-C1VLFDy4.js} +1 -1
  49. package/frontend-dist/assets/{TransformRulesForm-Be1kdxM3.js → TransformRulesForm-CxUVIzWH.js} +1 -1
  50. package/frontend-dist/assets/{UnifiedRequestDialog-B_xm5uVS.js → UnifiedRequestDialog-D-sQqFxg.js} +1 -1
  51. package/frontend-dist/assets/{VisuallyHiddenInput-BMd5Xv9A.js → VisuallyHiddenInput-WLtFW_E8.js} +1 -1
  52. package/frontend-dist/assets/{button-CU50mAde.js → button-CGbcdJgN.js} +2 -2
  53. package/frontend-dist/assets/{copy-5_1ty9pl.js → copy-CpYWP1uM.js} +1 -1
  54. package/frontend-dist/assets/{dialog-BP3UfrX6.js → dialog-CWAFinoK.js} +1 -1
  55. package/frontend-dist/assets/{index-CgpVhJTP.js → index-DDiesvp7.js} +1 -1
  56. package/frontend-dist/assets/providers-BsmC9XuH.js +1 -0
  57. package/frontend-dist/assets/providers-DWTdDUnh.js +1 -0
  58. package/frontend-dist/assets/{trash-2-BtGlrJP_.js → trash-2-CAPUkICH.js} +1 -1
  59. package/frontend-dist/assets/{useClipboard-eO8uMHXU.js → useClipboard-CLRvABjT.js} +1 -1
  60. package/frontend-dist/assets/{useLogRetention-Dz-o7K7g.js → useLogRetention-B7v1HgoB.js} +1 -1
  61. package/frontend-dist/index.html +2 -2
  62. package/package.json +5 -3
  63. package/frontend-dist/assets/Providers-C1WoYsgN.js +0 -1
  64. package/frontend-dist/assets/providers-BS8I-elL.js +0 -1
  65. package/frontend-dist/assets/providers-Bgp5lP_R.js +0 -1
@@ -3,11 +3,13 @@ import Database from "better-sqlite3";
3
3
  import type { StateRegistry } from "../core/registry.js";
4
4
  import type { AdaptiveController } from "@llm-router/core/concurrency";
5
5
  import type { RequestTracker } from "@llm-router/core/monitor";
6
+ import type { ProxyAgentFactory } from "../proxy/transport/proxy-agent.js";
6
7
  interface ProviderRoutesOptions {
7
8
  db: Database.Database;
8
9
  stateRegistry?: StateRegistry;
9
10
  tracker?: RequestTracker;
10
11
  adaptiveController?: AdaptiveController;
12
+ proxyAgentFactory?: ProxyAgentFactory;
11
13
  }
12
14
  export declare const adminProviderRoutes: FastifyPluginCallback<ProviderRoutesOptions>;
13
15
  export {};
@@ -96,6 +96,10 @@ const CreateProviderSchema = Type.Object({
96
96
  queue_timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
97
97
  max_queue_size: Type.Optional(Type.Integer({ minimum: 1 })),
98
98
  adaptive_enabled: Type.Optional(Type.Integer({ minimum: 0, maximum: 1 })),
99
+ proxy_type: Type.Optional(Type.Union([Type.Literal("http"), Type.Literal("socks5")])),
100
+ proxy_url: Type.Optional(Type.String({ minLength: 1 })),
101
+ proxy_username: Type.Optional(Type.String()),
102
+ proxy_password: Type.Optional(Type.String()),
99
103
  });
100
104
  const UpdateProviderSchema = Type.Object({
101
105
  name: Type.Optional(Type.String({ minLength: 1 })),
@@ -113,9 +117,13 @@ const UpdateProviderSchema = Type.Object({
113
117
  queue_timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
114
118
  max_queue_size: Type.Optional(Type.Integer({ minimum: 1 })),
115
119
  adaptive_enabled: Type.Optional(Type.Integer({ minimum: 0, maximum: 1 })),
120
+ proxy_type: Type.Optional(Type.Union([Type.Literal("http"), Type.Literal("socks5")])),
121
+ proxy_url: Type.Optional(Type.String({ minLength: 1 })),
122
+ proxy_username: Type.Optional(Type.String()),
123
+ proxy_password: Type.Optional(Type.String()),
116
124
  });
117
125
  export const adminProviderRoutes = (app, options, done) => {
118
- const { db, stateRegistry, tracker, adaptiveController } = options;
126
+ const { db, stateRegistry, tracker, adaptiveController, proxyAgentFactory } = options;
119
127
  app.get("/admin/api/providers", async (_request, reply) => {
120
128
  const encryptionKey = getSetting(db, "encryption_key");
121
129
  const providers = getAllProviders(db);
@@ -134,6 +142,10 @@ export const adminProviderRoutes = (app, options, done) => {
134
142
  queue_timeout_ms: s.queue_timeout_ms,
135
143
  max_queue_size: s.max_queue_size,
136
144
  adaptive_enabled: s.adaptive_enabled,
145
+ proxy_type: s.proxy_type,
146
+ proxy_url: s.proxy_url,
147
+ proxy_username: s.proxy_username ? decrypt(s.proxy_username, encryptionKey) : null,
148
+ proxy_password: s.proxy_password ? decrypt(s.proxy_password, encryptionKey) : null,
137
149
  concurrency_status: stateRegistry?.getProviderStatus(s.id) ?? { active: 0, queued: 0 },
138
150
  created_at: s.created_at,
139
151
  updated_at: s.updated_at,
@@ -152,6 +164,11 @@ export const adminProviderRoutes = (app, options, done) => {
152
164
  const encryptedKey = encrypt(body.api_key, getSetting(db, "encryption_key"));
153
165
  const { entries: normalizedModels, overrides: contextOverrides } = extractModelOverrides((body.models ?? []));
154
166
  const isAdaptiveEnabled = body.adaptive_enabled ?? 0;
167
+ if (body.proxy_type && !body.proxy_url) {
168
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.VALIDATION_FAILED, "proxy_url is required when proxy_type is set"));
169
+ }
170
+ const encryptedProxyUsername = body.proxy_username ? encrypt(body.proxy_username, getSetting(db, "encryption_key")) : null;
171
+ const encryptedProxyPassword = body.proxy_password ? encrypt(body.proxy_password, getSetting(db, "encryption_key")) : null;
155
172
  const id = createProvider(db, {
156
173
  name: body.name,
157
174
  api_type: body.api_type,
@@ -165,6 +182,10 @@ export const adminProviderRoutes = (app, options, done) => {
165
182
  queue_timeout_ms: body.queue_timeout_ms ?? PROVIDER_CONCURRENCY_DEFAULTS.queue_timeout_ms,
166
183
  max_queue_size: body.max_queue_size ?? PROVIDER_CONCURRENCY_DEFAULTS.max_queue_size,
167
184
  adaptive_enabled: isAdaptiveEnabled,
185
+ proxy_type: body.proxy_type ?? null,
186
+ proxy_url: body.proxy_type ? body.proxy_url : null,
187
+ proxy_username: encryptedProxyUsername,
188
+ proxy_password: encryptedProxyPassword,
168
189
  });
169
190
  if (contextOverrides.length > 0) {
170
191
  setModelInfoForProvider(db, id, contextOverrides.map(o => ({ model_name: o.name, context_window: o.context_window })));
@@ -234,7 +255,26 @@ export const adminProviderRoutes = (app, options, done) => {
234
255
  fields.api_key = encrypt(body.api_key, getSetting(db, "encryption_key"));
235
256
  fields.api_key_preview = body.api_key.length > API_KEY_PREVIEW_MIN_LENGTH ? `${body.api_key.slice(0, API_KEY_PREVIEW_PREFIX_LEN)}...${body.api_key.slice(-API_KEY_PREVIEW_PREFIX_LEN)}` : "****";
236
257
  }
258
+ // Proxy field handling
259
+ if (body.proxy_type !== undefined) {
260
+ fields.proxy_type = body.proxy_type || null;
261
+ if (!body.proxy_type) {
262
+ fields.proxy_url = null;
263
+ fields.proxy_username = null;
264
+ fields.proxy_password = null;
265
+ }
266
+ }
267
+ if (body.proxy_url !== undefined && body.proxy_type) {
268
+ fields.proxy_url = body.proxy_url;
269
+ }
270
+ if (body.proxy_username !== undefined && body.proxy_type) {
271
+ fields.proxy_username = body.proxy_username ? encrypt(body.proxy_username, getSetting(db, "encryption_key")) : null;
272
+ }
273
+ if (body.proxy_password !== undefined && body.proxy_type) {
274
+ fields.proxy_password = body.proxy_password ? encrypt(body.proxy_password, getSetting(db, "encryption_key")) : null;
275
+ }
237
276
  updateProvider(db, id, fields);
277
+ proxyAgentFactory?.invalidate(id);
238
278
  const updated = getProviderById(db, id);
239
279
  let cascade;
240
280
  if (existing.is_active === 1 && body.is_active === 0) {
@@ -327,6 +367,7 @@ export const adminProviderRoutes = (app, options, done) => {
327
367
  continue;
328
368
  }
329
369
  }
370
+ proxyAgentFactory?.invalidate(id);
330
371
  deleteProvider(db, id);
331
372
  stateRegistry?.removeProvider(id);
332
373
  adaptiveController?.remove(id);
@@ -3,6 +3,7 @@ import Database from "better-sqlite3";
3
3
  import type { StateRegistry } from "../core/registry.js";
4
4
  import type { RequestTracker } from "@llm-router/core/monitor";
5
5
  import type { AdaptiveController } from "@llm-router/core/concurrency";
6
+ import type { ProxyAgentFactory } from "../proxy/transport/proxy-agent.js";
6
7
  interface AdminRoutesOptions {
7
8
  db: Database.Database;
8
9
  stateRegistry: StateRegistry;
@@ -12,6 +13,7 @@ interface AdminRoutesOptions {
12
13
  logsDir?: string;
13
14
  pluginRegistry?: import("../proxy/transform/plugin-registry.js").PluginRegistry;
14
15
  closeFn?: () => Promise<void>;
16
+ proxyAgentFactory?: ProxyAgentFactory;
15
17
  }
16
18
  export declare const adminRoutes: FastifyPluginCallback<AdminRoutesOptions>;
17
19
  export {};
@@ -23,7 +23,7 @@ export const adminRoutes = (app, options, done) => {
23
23
  app.register(adminSetupRoutes, { db: options.db });
24
24
  app.register(adminAuthPlugin, { db: options.db });
25
25
  app.register(adminLoginRoutes, { db: options.db });
26
- app.register(adminProviderRoutes, { db: options.db, stateRegistry: options.stateRegistry, tracker: options.tracker, adaptiveController: options.adaptiveController });
26
+ app.register(adminProviderRoutes, { db: options.db, stateRegistry: options.stateRegistry, tracker: options.tracker, adaptiveController: options.adaptiveController, proxyAgentFactory: options.proxyAgentFactory });
27
27
  app.register(adminMappingRoutes, { db: options.db });
28
28
  app.register(adminGroupRoutes, { db: options.db });
29
29
  app.register(adminScheduleRoutes, { db: options.db });
@@ -9,6 +9,7 @@ export declare const SERVICE_KEYS: {
9
9
  readonly adaptiveController: "adaptiveController";
10
10
  readonly pluginRegistry: "pluginRegistry";
11
11
  readonly logFileWriter: "logFileWriter";
12
+ readonly proxyAgentFactory: "proxyAgentFactory";
12
13
  };
13
14
  export type ServiceKey = (typeof SERVICE_KEYS)[keyof typeof SERVICE_KEYS];
14
15
  /**
@@ -9,6 +9,7 @@ export const SERVICE_KEYS = {
9
9
  adaptiveController: "adaptiveController",
10
10
  pluginRegistry: "pluginRegistry",
11
11
  logFileWriter: "logFileWriter",
12
+ proxyAgentFactory: "proxyAgentFactory",
12
13
  };
13
14
  /**
14
15
  * 轻量服务容器 — 懒加载单例工厂注册表。
@@ -0,0 +1,7 @@
1
+ -- 041_add_provider_proxy.sql
2
+ -- Add per-provider proxy support (SOCKS5 / HTTP CONNECT)
3
+
4
+ ALTER TABLE providers ADD COLUMN proxy_type TEXT DEFAULT NULL;
5
+ ALTER TABLE providers ADD COLUMN proxy_url TEXT DEFAULT NULL;
6
+ ALTER TABLE providers ADD COLUMN proxy_username TEXT DEFAULT NULL;
7
+ ALTER TABLE providers ADD COLUMN proxy_password TEXT DEFAULT NULL;
@@ -13,6 +13,10 @@ export interface Provider {
13
13
  queue_timeout_ms: number;
14
14
  max_queue_size: number;
15
15
  adaptive_enabled: number;
16
+ proxy_type: string | null;
17
+ proxy_url: string | null;
18
+ proxy_username: string | null;
19
+ proxy_password: string | null;
16
20
  created_at: string;
17
21
  updated_at: string;
18
22
  }
@@ -41,8 +45,12 @@ export declare function createProvider(db: Database.Database, provider: {
41
45
  queue_timeout_ms?: number;
42
46
  max_queue_size?: number;
43
47
  adaptive_enabled?: number;
48
+ proxy_type?: string | null;
49
+ proxy_url?: string | null;
50
+ proxy_username?: string | null;
51
+ proxy_password?: string | null;
44
52
  }): string;
45
- export declare function updateProvider(db: Database.Database, id: string, fields: Partial<Pick<Provider, "name" | "api_type" | "base_url" | "upstream_path" | "api_key" | "api_key_preview" | "models" | "is_active" | "max_concurrency" | "queue_timeout_ms" | "max_queue_size" | "adaptive_enabled">>): void;
53
+ export declare function updateProvider(db: Database.Database, id: string, fields: Partial<Pick<Provider, "name" | "api_type" | "base_url" | "upstream_path" | "api_key" | "api_key_preview" | "models" | "is_active" | "max_concurrency" | "queue_timeout_ms" | "max_queue_size" | "adaptive_enabled" | "proxy_type" | "proxy_url" | "proxy_username" | "proxy_password">>): void;
46
54
  export declare function deleteProvider(db: Database.Database, id: string): void;
47
55
  export declare function getActiveProviderByName(db: Database.Database, name: string): {
48
56
  id: string;
@@ -37,7 +37,7 @@ export const PROVIDER_CONCURRENCY_DEFAULTS = {
37
37
  max_queue_size: 100,
38
38
  };
39
39
  const PROVIDER_FIELDS = new Set([
40
- "name", "api_type", "base_url", "upstream_path", "api_key", "api_key_preview", "models", "is_active", "max_concurrency", "queue_timeout_ms", "max_queue_size", "adaptive_enabled",
40
+ "name", "api_type", "base_url", "upstream_path", "api_key", "api_key_preview", "models", "is_active", "max_concurrency", "queue_timeout_ms", "max_queue_size", "adaptive_enabled", "proxy_type", "proxy_url", "proxy_username", "proxy_password",
41
41
  ]);
42
42
  export function getActiveProviders(db, apiType) {
43
43
  return db
@@ -53,8 +53,8 @@ export function getProviderById(db, id) {
53
53
  export function createProvider(db, provider) {
54
54
  const id = randomUUID();
55
55
  const now = new Date().toISOString();
56
- db.prepare(`INSERT INTO providers (id, name, api_type, base_url, upstream_path, api_key, api_key_preview, models, is_active, max_concurrency, queue_timeout_ms, max_queue_size, adaptive_enabled, created_at, updated_at)
57
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, provider.name, provider.api_type, provider.base_url, provider.upstream_path ?? null, provider.api_key, provider.api_key_preview ?? null, provider.models ?? "[]", provider.is_active ?? 1, provider.max_concurrency ?? PROVIDER_CONCURRENCY_DEFAULTS.max_concurrency, provider.queue_timeout_ms ?? PROVIDER_CONCURRENCY_DEFAULTS.queue_timeout_ms, provider.max_queue_size ?? PROVIDER_CONCURRENCY_DEFAULTS.max_queue_size, provider.adaptive_enabled ?? 0, now, now);
56
+ db.prepare(`INSERT INTO providers (id, name, api_type, base_url, upstream_path, api_key, api_key_preview, models, is_active, max_concurrency, queue_timeout_ms, max_queue_size, adaptive_enabled, proxy_type, proxy_url, proxy_username, proxy_password, created_at, updated_at)
57
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, provider.name, provider.api_type, provider.base_url, provider.upstream_path ?? null, provider.api_key, provider.api_key_preview ?? null, provider.models ?? "[]", provider.is_active ?? 1, provider.max_concurrency ?? PROVIDER_CONCURRENCY_DEFAULTS.max_concurrency, provider.queue_timeout_ms ?? PROVIDER_CONCURRENCY_DEFAULTS.queue_timeout_ms, provider.max_queue_size ?? PROVIDER_CONCURRENCY_DEFAULTS.max_queue_size, provider.adaptive_enabled ?? 0, provider.proxy_type ?? null, provider.proxy_url ?? null, provider.proxy_username ?? null, provider.proxy_password ?? null, now, now);
58
58
  return id;
59
59
  }
60
60
  export function updateProvider(db, id, fields) {
package/dist/index.js CHANGED
@@ -31,6 +31,7 @@ import { startUpgradeChecker, stopUpgradeChecker } from "./admin/upgrade.js";
31
31
  import fastifyStatic from "@fastify/static";
32
32
  import { ServiceContainer, SERVICE_KEYS } from "./core/container.js";
33
33
  import { LogFileWriter } from "./storage/log-file-writer.js";
34
+ import { ProxyAgentFactory } from "./proxy/transport/proxy-agent.js";
34
35
  import { scheduleLogFileMaintenance } from "./storage/log-file-compressor.js";
35
36
  import { getDetailLogEnabled, getLogFileRetentionDays } from "./db/settings.js";
36
37
  import { dirname, join } from "node:path";
@@ -215,12 +216,15 @@ export async function buildApp(options) {
215
216
  const pluginsDir = path.resolve(__dirname, "../plugins/transform");
216
217
  pluginRegistry.scanPluginsDir(pluginsDir);
217
218
  container.register(SERVICE_KEYS.pluginRegistry, () => pluginRegistry);
219
+ // 注册 ProxyAgentFactory
220
+ container.register(SERVICE_KEYS.proxyAgentFactory, () => new ProxyAgentFactory());
218
221
  // 从容器解析所有服务
219
222
  const matcher = container.resolve(SERVICE_KEYS.matcher);
220
223
  const semaphoreManager = container.resolve(SERVICE_KEYS.semaphoreManager);
221
224
  const tracker = container.resolve(SERVICE_KEYS.tracker);
222
225
  const usageWindowTracker = container.resolve(SERVICE_KEYS.usageWindowTracker);
223
226
  const adaptiveController = container.resolve(SERVICE_KEYS.adaptiveController);
227
+ const proxyAgentFactory = container.resolve(SERVICE_KEYS.proxyAgentFactory);
224
228
  // Wire adaptive controller to tracker
225
229
  tracker.setAdaptiveStatusProvider(adaptiveController);
226
230
  // 从 DB 读取已有 provider 的并发配置,初始化信号量/adaptive/tracker(共享逻辑)
@@ -247,7 +251,7 @@ export async function buildApp(options) {
247
251
  // Late-bound close ref — close 函数在 adminRoutes 注册之后才定义,
248
252
  // 但 restart API 需要在运行时调用它
249
253
  const closeRef = { fn: async () => { } };
250
- app.register(adminRoutes, { db, stateRegistry, tracker, adaptiveController, logFileWriter, logsDir, closeFn: () => closeRef.fn(), pluginRegistry });
254
+ app.register(adminRoutes, { db, stateRegistry, tracker, adaptiveController, logFileWriter, logsDir, closeFn: () => closeRef.fn(), pluginRegistry, proxyAgentFactory });
251
255
  // 前端静态文件服务(生产环境)
252
256
  const frontendDist = path.resolve(process.env.FRONTEND_DIST || path.join(__dirname, "../frontend-dist"));
253
257
  if (existsSync(frontendDist)) {
@@ -287,6 +291,7 @@ export async function buildApp(options) {
287
291
  // 关闭所有 SSE 长连接,防止 app.close() 因 hijack 的连接无限等待
288
292
  tracker.closeAllClients();
289
293
  semaphoreManager.removeAll();
294
+ proxyAgentFactory.invalidateAll();
290
295
  const sessionTracker = container.resolve(SERVICE_KEYS.sessionTracker);
291
296
  sessionTracker.stop();
292
297
  await app.close();
@@ -35,7 +35,7 @@ const anthropicProxyRaw = (app, opts, done) => {
35
35
  const e = anthropicErrors.providerUnavailable();
36
36
  return reply.code(e.statusCode).send(e.body);
37
37
  }
38
- const deps = { db, orchestrator, container };
38
+ const deps = { db, orchestrator, container, proxyAgentFactory: container.resolve(SERVICE_KEYS.proxyAgentFactory) };
39
39
  return handleProxyRequest(request, reply, "anthropic", MESSAGES_PATH, anthropicErrors, deps);
40
40
  });
41
41
  done();
@@ -41,7 +41,7 @@ const openaiProxyRaw = (app, opts, done) => {
41
41
  });
42
42
  return sendError(reply, openaiErrors.providerUnavailable());
43
43
  }
44
- const deps = { db, orchestrator, container };
44
+ const deps = { db, orchestrator, container, proxyAgentFactory: container.resolve(SERVICE_KEYS.proxyAgentFactory) };
45
45
  return handleProxyRequest(request, reply, "openai", CHAT_COMPLETIONS_PATH, openaiErrors, deps, {
46
46
  beforeSendProxy: (body, isStream) => {
47
47
  if (isStream && !body.stream_options) {
@@ -2,10 +2,12 @@ import type { FastifyReply, FastifyRequest } from "fastify";
2
2
  import Database from "better-sqlite3";
3
3
  import type { ProxyOrchestrator } from "../orchestration/orchestrator.js";
4
4
  import type { ProxyErrorFormatter } from "../proxy-core.js";
5
+ import type { ProxyAgentFactory } from "../transport/proxy-agent.js";
5
6
  export interface RouteHandlerDeps {
6
7
  db: Database.Database;
7
8
  orchestrator: ProxyOrchestrator;
8
9
  container: ServiceContainer;
10
+ proxyAgentFactory?: ProxyAgentFactory;
9
11
  }
10
12
  import type { ServiceContainer } from "../../core/container.js";
11
13
  export declare function handleProxyRequest(request: FastifyRequest, reply: FastifyReply, apiType: "openai" | "openai-responses" | "anthropic", upstreamPath: string, errors: ProxyErrorFormatter, deps: RouteHandlerDeps, options?: {
@@ -303,6 +303,7 @@ async function executeFailoverLoop(ctx) {
303
303
  streamTimeoutMs: getModelStreamTimeout(provider, resolved.backend_model), tracker, matcher, request,
304
304
  streamLoopEnabled, formatTransform, responseTransform, injectedHeaders,
305
305
  timeoutContext: { modelId: resolved.backend_model, providerId: provider.id },
306
+ proxyAgentFactory: deps.proxyAgentFactory,
306
307
  });
307
308
  const pipelineSnapshot = iterationSnapshot.toJSON();
308
309
  try {
@@ -38,7 +38,7 @@ const responsesProxyRaw = (app, opts, done) => {
38
38
  });
39
39
  return sendError(reply, responsesErrors.providerUnavailable());
40
40
  }
41
- const deps = { db, orchestrator, container };
41
+ const deps = { db, orchestrator, container, proxyAgentFactory: container.resolve(SERVICE_KEYS.proxyAgentFactory) };
42
42
  return handleProxyRequest(request, reply, "openai-responses", RESPONSES_PATH, responsesErrors, deps);
43
43
  };
44
44
  app.post(RESPONSES_PATH, handleResponses);
@@ -1,3 +1,4 @@
1
+ import type { Agent } from "http";
1
2
  import type { RawHeaders, TransportResult } from "../types.js";
2
3
  export { callStream } from "./stream.js";
3
4
  export interface UpstreamRequestOptions {
@@ -8,13 +9,13 @@ export interface UpstreamRequestOptions {
8
9
  headers: Record<string, string>;
9
10
  }
10
11
  export declare const _transportInternals: {
11
- createUpstreamRequest(url: URL, options: UpstreamRequestOptions): import("http").ClientRequest;
12
+ createUpstreamRequest(url: URL, options: UpstreamRequestOptions, agent?: Agent): import("http").ClientRequest;
12
13
  };
13
14
  export declare function buildRequestOptions(url: URL, headers: Record<string, string>, method?: string): UpstreamRequestOptions;
14
15
  export type BuildHeadersFn = (cliHdrs: RawHeaders, key: string, bytes?: number) => Record<string, string>;
15
16
  export declare function callNonStream(backend: {
16
17
  base_url: string;
17
- }, apiKey: string, body: Record<string, unknown>, clientHeaders: RawHeaders, upstreamPath: string, buildHeaders: BuildHeadersFn): Promise<TransportResult>;
18
+ }, apiKey: string, body: Record<string, unknown>, clientHeaders: RawHeaders, upstreamPath: string, buildHeaders: BuildHeadersFn, agent?: Agent): Promise<TransportResult>;
18
19
  export interface GetTransportResult {
19
20
  statusCode: number;
20
21
  body: string;
@@ -22,4 +23,4 @@ export interface GetTransportResult {
22
23
  }
23
24
  export declare function callGet(backend: {
24
25
  base_url: string;
25
- }, apiKey: string, clientHeaders: RawHeaders, upstreamPath: string, buildHeaders: (cliHdrs: RawHeaders, key: string) => Record<string, string>): Promise<GetTransportResult>;
26
+ }, apiKey: string, clientHeaders: RawHeaders, upstreamPath: string, buildHeaders: (cliHdrs: RawHeaders, key: string) => Record<string, string>, agent?: Agent): Promise<GetTransportResult>;
@@ -10,10 +10,11 @@ const UPSTREAM_SUCCESS_RANGE = 100;
10
10
  const HTTPS_DEFAULT_PORT = 443;
11
11
  const HTTP_DEFAULT_PORT = 80;
12
12
  export const _transportInternals = {
13
- createUpstreamRequest(url, options) {
13
+ createUpstreamRequest(url, options, agent) {
14
+ const opts = agent ? { ...options, agent } : options;
14
15
  return url.protocol === "https:"
15
- ? httpsRequestFn(options)
16
- : httpRequestFn(options);
16
+ ? httpsRequestFn(opts)
17
+ : httpRequestFn(opts);
17
18
  },
18
19
  };
19
20
  export function buildRequestOptions(url, headers, method = "POST") {
@@ -27,13 +28,13 @@ export function buildRequestOptions(url, headers, method = "POST") {
27
28
  };
28
29
  }
29
30
  // ---------- callNonStream ----------
30
- export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders) {
31
+ export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders, agent) {
31
32
  return new Promise((resolve) => {
32
33
  const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
33
34
  const payload = JSON.stringify(body);
34
35
  const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
35
36
  const options = buildRequestOptions(url, upstreamHeaders);
36
- const req = _transportInternals.createUpstreamRequest(url, options);
37
+ const req = _transportInternals.createUpstreamRequest(url, options, agent);
37
38
  req.on("response", (res) => {
38
39
  const chunks = [];
39
40
  res.on("data", (chunk) => chunks.push(chunk));
@@ -68,12 +69,12 @@ export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath
68
69
  req.end();
69
70
  });
70
71
  }
71
- export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeaders) {
72
+ export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeaders, agent) {
72
73
  return new Promise((resolve, reject) => {
73
74
  const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
74
75
  const headers = buildHeaders(clientHeaders, apiKey);
75
76
  const options = buildRequestOptions(url, headers, "GET");
76
- const req = _transportInternals.createUpstreamRequest(url, options);
77
+ const req = _transportInternals.createUpstreamRequest(url, options, agent);
77
78
  req.on("response", (res) => {
78
79
  const chunks = [];
79
80
  res.on("data", (chunk) => chunks.push(chunk));
@@ -0,0 +1,16 @@
1
+ import type { Agent } from "http";
2
+ export interface ProxyConfig {
3
+ id: string;
4
+ proxy_type: string | null;
5
+ proxy_url: string | null;
6
+ proxy_username: string | null;
7
+ proxy_password: string | null;
8
+ }
9
+ export declare class ProxyAgentFactory {
10
+ private readonly cache;
11
+ getAgent(provider: ProxyConfig): Agent | undefined;
12
+ invalidate(providerId: string): void;
13
+ invalidateAll(): void;
14
+ private createAgent;
15
+ private buildProxyUrl;
16
+ }
@@ -0,0 +1,54 @@
1
+ import { HttpsProxyAgent } from "https-proxy-agent";
2
+ import { SocksProxyAgent } from "socks-proxy-agent";
3
+ export class ProxyAgentFactory {
4
+ cache = new Map();
5
+ getAgent(provider) {
6
+ if (!provider.proxy_type || !provider.proxy_url) {
7
+ return undefined;
8
+ }
9
+ const fullUrl = this.buildProxyUrl(provider);
10
+ const cached = this.cache.get(provider.id);
11
+ if (cached && cached.proxyUrl === fullUrl) {
12
+ return cached.agent;
13
+ }
14
+ if (cached) {
15
+ cached.agent.destroy();
16
+ this.cache.delete(provider.id);
17
+ }
18
+ const agent = this.createAgent(provider.proxy_type, fullUrl);
19
+ this.cache.set(provider.id, { agent, proxyUrl: fullUrl });
20
+ return agent;
21
+ }
22
+ invalidate(providerId) {
23
+ const cached = this.cache.get(providerId);
24
+ if (cached) {
25
+ cached.agent.destroy();
26
+ this.cache.delete(providerId);
27
+ }
28
+ }
29
+ invalidateAll() {
30
+ for (const cached of this.cache.values()) {
31
+ cached.agent.destroy();
32
+ }
33
+ this.cache.clear();
34
+ }
35
+ createAgent(proxyType, proxyUrl) {
36
+ if (proxyType === "socks5") {
37
+ return new SocksProxyAgent(proxyUrl);
38
+ }
39
+ return new HttpsProxyAgent(proxyUrl);
40
+ }
41
+ buildProxyUrl(provider) {
42
+ let url = provider.proxy_url;
43
+ const username = provider.proxy_username;
44
+ const password = provider.proxy_password;
45
+ if (username) {
46
+ const parsed = new URL(url);
47
+ parsed.username = username;
48
+ if (password)
49
+ parsed.password = password;
50
+ url = parsed.toString();
51
+ }
52
+ return url;
53
+ }
54
+ }
@@ -1,3 +1,4 @@
1
+ import type { Agent } from "http";
1
2
  import type { FastifyReply } from "fastify";
2
3
  import type { RawHeaders, TransportResult } from "../types.js";
3
4
  import type { SSEMetricsTransform } from "../../metrics/sse-metrics-transform.js";
@@ -8,4 +9,4 @@ export declare function callStream(backend: {
8
9
  }, apiKey: string, body: Record<string, unknown>, clientHeaders: RawHeaders, reply: FastifyReply, timeoutMs: number, upstreamPath: string, buildHeaders: BuildHeadersFn, metricsTransform?: SSEMetricsTransform, checkEarlyError?: (bufferedData: string) => boolean, compatResolve?: (result: TransportResult) => void, loopGuard?: StreamLoopGuard, formatTransform?: import("stream").Transform, timeoutContext?: {
9
10
  modelId: string;
10
11
  providerId: string;
11
- }, onTimeoutAbort?: () => void): Promise<TransportResult>;
12
+ }, onTimeoutAbort?: () => void, agent?: Agent): Promise<TransportResult>;
@@ -307,14 +307,14 @@ class StreamProxy {
307
307
  }
308
308
  }
309
309
  // ---------- callStream ----------
310
- export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve, loopGuard, formatTransform, timeoutContext, onTimeoutAbort) {
310
+ export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, agent) {
311
311
  return new Promise((resolve) => {
312
312
  const effectiveResolve = compatResolve ?? resolve;
313
313
  const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
314
314
  const payload = JSON.stringify(body);
315
315
  const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
316
316
  const options = buildRequestOptions(url, upstreamHeaders);
317
- const upstreamReq = _transportInternals.createUpstreamRequest(url, options);
317
+ const upstreamReq = _transportInternals.createUpstreamRequest(url, options, agent);
318
318
  upstreamReq.on("response", (upstreamRes) => {
319
319
  const statusCode = upstreamRes.statusCode || UPSTREAM_BAD_GATEWAY;
320
320
  if (statusCode !== UPSTREAM_SUCCESS) {
@@ -4,6 +4,7 @@ import type { RawHeaders, TransportResult } from "../types.js";
4
4
  import type { Target } from "../../core/types.js";
5
5
  import type { RequestTracker } from "@llm-router/core/monitor";
6
6
  import type { RetryRuleMatcher } from "../orchestration/retry-rules.js";
7
+ import type { ProxyAgentFactory } from "./proxy-agent.js";
7
8
  export interface TransportFnParams {
8
9
  provider: NonNullable<ReturnType<typeof getProviderById>>;
9
10
  apiKey: string;
@@ -28,5 +29,6 @@ export interface TransportFnParams {
28
29
  modelId: string;
29
30
  providerId: string;
30
31
  };
32
+ proxyAgentFactory?: ProxyAgentFactory;
31
33
  }
32
34
  export declare function buildTransportFn(p: TransportFnParams): (target: Target) => Promise<TransportResult>;
@@ -35,6 +35,7 @@ export function buildTransportFn(p) {
35
35
  const base = buildUpstreamHeaders(cliHdrs, key, bytes, p.apiType);
36
36
  return p.injectedHeaders ? { ...base, ...p.injectedHeaders } : base;
37
37
  };
38
+ const agent = p.proxyAgentFactory?.getAgent(p.provider);
38
39
  // _target 未使用 — resilience 层始终传入当前 resolved target;
39
40
  // 跨 target failover 由外层 executeFailoverLoop 的 ProviderSwitchNeeded 处理
40
41
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -52,14 +53,14 @@ export function buildTransportFn(p) {
52
53
  onContentDelta: streamLoopGuard ? (text) => streamLoopGuard.feed(text) : undefined,
53
54
  });
54
55
  const checkEarlyError = p.matcher ? (data) => p.matcher.test(UPSTREAM_SUCCESS, data) : undefined;
55
- const streamResult = await callStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath, buildHeaders, metricsTransform, checkEarlyError, undefined, streamLoopGuard, p.formatTransform, p.timeoutContext);
56
+ const streamResult = await callStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath, buildHeaders, metricsTransform, checkEarlyError, undefined, streamLoopGuard, p.formatTransform, p.timeoutContext, undefined, agent);
56
57
  const m = (streamResult.kind === "stream_success" || streamResult.kind === "stream_abort")
57
58
  ? streamResult.metrics : undefined;
58
59
  if (m)
59
60
  p.tracker?.update(p.logId, { streamMetrics: toStreamMetrics(m) });
60
61
  return streamResult;
61
62
  }
62
- let result = await callNonStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.upstreamPath, buildHeaders);
63
+ let result = await callNonStream(p.provider, p.apiKey, p.body, p.cliHdrs, p.upstreamPath, buildHeaders, agent);
63
64
  if (result.kind === "success") {
64
65
  const mr = MetricsExtractor.fromNonStreamResponse(p.apiType, result.body);
65
66
  if (mr)
@@ -1 +1 @@
1
- import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-CU50mAde.js";var s=[`data-size`],c=e({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(e){let c=e;return(l,u)=>(r(),n(`div`,{"data-slot":`card`,"data-size":e.size,class:t(o(a)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[i(l.$slots,`default`)],10,s))}}),l=e({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-content`,class:t(o(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(e.$slots,`default`)],2))}});export{c as n,l as t};
1
+ import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-CGbcdJgN.js";var s=[`data-size`],c=e({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(e){let c=e;return(l,u)=>(r(),n(`div`,{"data-slot":`card`,"data-size":e.size,class:t(o(a)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[i(l.$slots,`default`)],10,s))}}),l=e({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-content`,class:t(o(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(e.$slots,`default`)],2))}});export{c as n,l as t};
@@ -1 +1 @@
1
- import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-CU50mAde.js";var s=e({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-header`,class:t(o(a)(`gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]`,s.class))},[i(e.$slots,`default`)],2))}}),c=e({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-title`,class:t(o(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(e.$slots,`default`)],2))}});export{s as n,c as t};
1
+ import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-CGbcdJgN.js";var s=e({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-header`,class:t(o(a)(`gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]`,s.class))},[i(e.$slots,`default`)],2))}}),c=e({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-title`,class:t(o(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(e.$slots,`default`)],2))}});export{s as n,c as t};
@@ -1 +1 @@
1
- import{$ as e,H as t,Ht as n,J as r,K as i,Q as a,U as o,Y as s,dt as c,gt as l,i as u,m as d,mt as f,o as p,ot as m,r as h,tt as g,wt as _,x as v,zt as y}from"./button-CU50mAde.js";import{t as b}from"./VisuallyHiddenInput-BMd5Xv9A.js";import{t as x}from"./RovingFocusItem-BpkzLwee.js";import{B as S,G as C,H as w,L as T,Y as E,nt as D,q as O}from"./index-CgpVhJTP.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=O(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=O(`CheckboxRoot`),I=e({inheritAttrs:!1,__name:`CheckboxRoot`,props:{defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},disabled:{type:Boolean,required:!1},value:{type:null,required:!1,default:`on`},id:{type:String,required:!1},trueValue:{type:null,required:!1,default:()=>!0},falseValue:{type:null,required:!1,default:()=>!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`],setup(e,{emit:n}){let a=e,h=n,{forwardRef:g,currentElement:v}=p(),S=A(null),T=d(a,`modelValue`,h,{defaultValue:a.defaultValue??a.falseValue,passive:a.modelValue===void 0}),D=i(()=>S?.disabled.value||a.disabled),O=i(()=>E(T.value,a.trueValue)),j=i(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,a.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=a.trueValue:T.value=O.value?a.falseValue:a.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,a.value)){let t=e.findIndex(e=>E(e,a.value));e.splice(t,1)}else e.push(a.value);S.modelValue.value=e}}let I=w(v),L=i(()=>a.id&&v.value?document.querySelector(`[for="${a.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,n)=>(c(),r(l(y(S)?.rovingFocus.value?y(x):y(u)),m(e.$attrs,{id:e.id,ref:y(g),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":y(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":y(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:y(S)?.rovingFocus.value?!D.value:void 0,onKeydown:t(o(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:_(()=>[f(e.$slots,`default`,{modelValue:y(T),state:j.value}),y(I)&&e.name&&!y(S)?(c(),r(y(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):s(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=e({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:t}=p(),n=P();return(e,i)=>(c(),r(y(T),{present:e.forceMount||y(M)(y(n).state.value)||y(n).state.value===!0},{default:_(()=>[a(y(u),m({ref:y(t),"data-state":y(N)(y(n).state.value),"data-disabled":y(n).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:_(()=>[f(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=e({__name:`Checkbox`,props:{defaultValue:{},modelValue:{},disabled:{type:Boolean},value:{},id:{},trueValue:{},falseValue:{},asChild:{type:Boolean},as:{},name:{},required:{type:Boolean},class:{type:[Boolean,null,String,Object,Array]}},emits:[`update:modelValue`],setup(e,{emit:t}){let i=e,o=t,s=S(v(i,`class`),o);return(e,t)=>(c(),r(y(I),m({"data-slot":`checkbox`},y(s),{class:y(h)(`border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-md border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50`,i.class)}),{default:_(t=>[a(y(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:_(()=>[f(e.$slots,`default`,n(g(t)),()=>[a(y(D))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
1
+ import{$ as e,H as t,Ht as n,J as r,K as i,Q as a,U as o,Y as s,dt as c,gt as l,i as u,m as d,mt as f,o as p,ot as m,r as h,tt as g,wt as _,x as v,zt as y}from"./button-CGbcdJgN.js";import{t as b}from"./VisuallyHiddenInput-WLtFW_E8.js";import{t as x}from"./RovingFocusItem-ZGq4Eu8v.js";import{B as S,G as C,H as w,L as T,Y as E,nt as D,q as O}from"./index-DDiesvp7.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=O(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=O(`CheckboxRoot`),I=e({inheritAttrs:!1,__name:`CheckboxRoot`,props:{defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},disabled:{type:Boolean,required:!1},value:{type:null,required:!1,default:`on`},id:{type:String,required:!1},trueValue:{type:null,required:!1,default:()=>!0},falseValue:{type:null,required:!1,default:()=>!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`],setup(e,{emit:n}){let a=e,h=n,{forwardRef:g,currentElement:v}=p(),S=A(null),T=d(a,`modelValue`,h,{defaultValue:a.defaultValue??a.falseValue,passive:a.modelValue===void 0}),D=i(()=>S?.disabled.value||a.disabled),O=i(()=>E(T.value,a.trueValue)),j=i(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,a.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=a.trueValue:T.value=O.value?a.falseValue:a.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,a.value)){let t=e.findIndex(e=>E(e,a.value));e.splice(t,1)}else e.push(a.value);S.modelValue.value=e}}let I=w(v),L=i(()=>a.id&&v.value?document.querySelector(`[for="${a.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,n)=>(c(),r(l(y(S)?.rovingFocus.value?y(x):y(u)),m(e.$attrs,{id:e.id,ref:y(g),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":y(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":y(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:y(S)?.rovingFocus.value?!D.value:void 0,onKeydown:t(o(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:_(()=>[f(e.$slots,`default`,{modelValue:y(T),state:j.value}),y(I)&&e.name&&!y(S)?(c(),r(y(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):s(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=e({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:t}=p(),n=P();return(e,i)=>(c(),r(y(T),{present:e.forceMount||y(M)(y(n).state.value)||y(n).state.value===!0},{default:_(()=>[a(y(u),m({ref:y(t),"data-state":y(N)(y(n).state.value),"data-disabled":y(n).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:_(()=>[f(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=e({__name:`Checkbox`,props:{defaultValue:{},modelValue:{},disabled:{type:Boolean},value:{},id:{},trueValue:{},falseValue:{},asChild:{type:Boolean},as:{},name:{},required:{type:Boolean},class:{type:[Boolean,null,String,Object,Array]}},emits:[`update:modelValue`],setup(e,{emit:t}){let i=e,o=t,s=S(v(i,`class`),o);return(e,t)=>(c(),r(y(I),m({"data-slot":`checkbox`},y(s),{class:y(h)(`border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-md border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50`,i.class)}),{default:_(t=>[a(y(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:_(()=>[f(e.$slots,`default`,n(g(t)),()=>[a(y(D))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
@@ -1 +1 @@
1
- import{$ as e,Ht as t,J as n,K as r,Lt as i,Mt as a,Q as o,Y as s,d as c,dt as l,i as u,lt as d,m as f,mt as p,o as m,ot as h,st as g,tt as _,wt as v,xt as y,zt as b}from"./button-CU50mAde.js";import{B as x,L as S,q as C,z as w}from"./index-CgpVhJTP.js";var[T,E]=C(`CollapsibleRoot`),D=e({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:r}){let a=e,o=f(a,`open`,r,{defaultValue:a.defaultOpen,passive:a.open===void 0}),{disabled:s,unmountOnHide:c}=i(a);return E({contentId:``,disabled:s,open:o,unmountOnHide:c,onOpenToggle:()=>{s.value||(o.value=!o.value)}}),t({open:o}),m(),(e,t)=>(l(),n(b(u),{as:e.as,"as-child":a.asChild,"data-state":b(o)?`open`:`closed`,"data-disabled":b(s)?``:void 0},{default:v(()=>[p(e.$slots,`default`,{open:b(o)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=e({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(e,{emit:t}){let i=e,f=t,_=T();_.contentId||=w(void 0,`reka-collapsible-content`);let x=a(),{forwardRef:C,currentElement:E}=m(),D=a(0),O=a(0),k=r(()=>_.open.value),A=a(k.value),j=a();y(()=>[k.value,x.value?.present],async()=>{await g();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=r(()=>A.value&&_.open.value);return d(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{_.onOpenToggle(),f(`contentFound`)})}),(e,t)=>(l(),n(b(S),{ref_key:`presentRef`,ref:x,present:e.forceMount||b(_).open.value,"force-mount":!0},{default:v(({present:t})=>[o(b(u),h(e.$attrs,{id:b(_).contentId,ref:b(C),"as-child":i.asChild,as:e.as,hidden:t?void 0:b(_).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:b(_).open.value?`open`:`closed`,"data-disabled":b(_).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:v(()=>[!b(_).unmountOnHide.value||t?p(e.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=e({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(e,{emit:r}){let i=x(e,r);return(e,r)=>(l(),n(b(D),h({"data-slot":`collapsible`},b(i)),{default:v(n=>[p(e.$slots,`default`,t(_(n)))]),_:3},16))}}),A=e({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(l(),n(b(O),h({"data-slot":`collapsible-content`},t),{default:v(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
1
+ import{$ as e,Ht as t,J as n,K as r,Lt as i,Mt as a,Q as o,Y as s,d as c,dt as l,i as u,lt as d,m as f,mt as p,o as m,ot as h,st as g,tt as _,wt as v,xt as y,zt as b}from"./button-CGbcdJgN.js";import{B as x,L as S,q as C,z as w}from"./index-DDiesvp7.js";var[T,E]=C(`CollapsibleRoot`),D=e({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:r}){let a=e,o=f(a,`open`,r,{defaultValue:a.defaultOpen,passive:a.open===void 0}),{disabled:s,unmountOnHide:c}=i(a);return E({contentId:``,disabled:s,open:o,unmountOnHide:c,onOpenToggle:()=>{s.value||(o.value=!o.value)}}),t({open:o}),m(),(e,t)=>(l(),n(b(u),{as:e.as,"as-child":a.asChild,"data-state":b(o)?`open`:`closed`,"data-disabled":b(s)?``:void 0},{default:v(()=>[p(e.$slots,`default`,{open:b(o)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=e({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(e,{emit:t}){let i=e,f=t,_=T();_.contentId||=w(void 0,`reka-collapsible-content`);let x=a(),{forwardRef:C,currentElement:E}=m(),D=a(0),O=a(0),k=r(()=>_.open.value),A=a(k.value),j=a();y(()=>[k.value,x.value?.present],async()=>{await g();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=r(()=>A.value&&_.open.value);return d(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{_.onOpenToggle(),f(`contentFound`)})}),(e,t)=>(l(),n(b(S),{ref_key:`presentRef`,ref:x,present:e.forceMount||b(_).open.value,"force-mount":!0},{default:v(({present:t})=>[o(b(u),h(e.$attrs,{id:b(_).contentId,ref:b(C),"as-child":i.asChild,as:e.as,hidden:t?void 0:b(_).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:b(_).open.value?`open`:`closed`,"data-disabled":b(_).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:v(()=>[!b(_).unmountOnHide.value||t?p(e.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=e({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(e,{emit:r}){let i=x(e,r);return(e,r)=>(l(),n(b(D),h({"data-slot":`collapsible`},b(i)),{default:v(n=>[p(e.$slots,`default`,t(_(n)))]),_:3},16))}}),A=e({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(l(),n(b(O),h({"data-slot":`collapsible-content`},t),{default:v(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
@@ -1 +1 @@
1
- import{$ as e,J as t,dt as n,i as r,mt as i,o as a,ot as o,wt as s,zt as c}from"./button-CU50mAde.js";import{r as l}from"./CollapsibleContent-B1v6hP95.js";var u=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let o=e;a();let u=l();return(e,a)=>(n(),t(c(r),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":o.asChild,"aria-controls":c(u).contentId,"aria-expanded":c(u).open.value,"data-state":c(u).open.value?`open`:`closed`,"data-disabled":c(u).disabled?.value?``:void 0,disabled:c(u).disabled?.value,onClick:c(u).onOpenToggle},{default:s(()=>[i(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(e){let r=e;return(e,a)=>(n(),t(c(u),o({"data-slot":`collapsible-trigger`},r),{default:s(()=>[i(e.$slots,`default`)]),_:3},16))}});export{d as t};
1
+ import{$ as e,J as t,dt as n,i as r,mt as i,o as a,ot as o,wt as s,zt as c}from"./button-CGbcdJgN.js";import{r as l}from"./CollapsibleContent-5Mrc8gGt.js";var u=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let o=e;a();let u=l();return(e,a)=>(n(),t(c(r),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":o.asChild,"aria-controls":c(u).contentId,"aria-expanded":c(u).open.value,"data-state":c(u).open.value?`open`:`closed`,"data-disabled":c(u).disabled?.value?``:void 0,disabled:c(u).disabled?.value,onClick:c(u).onOpenToggle},{default:s(()=>[i(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(e){let r=e;return(e,a)=>(n(),t(c(u),o({"data-slot":`collapsible-trigger`},r),{default:s(()=>[i(e.$slots,`default`)]),_:3},16))}});export{d as t};