llm-simple-router 0.9.27 → 0.9.28

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 (55) hide show
  1. package/config/recommended-providers.json +19 -0
  2. package/dist/admin/constants.d.ts +1 -1
  3. package/dist/admin/constants.js +1 -1
  4. package/dist/admin/providers.js +74 -26
  5. package/dist/admin/upgrade.js +9 -19
  6. package/dist/config/model-context.d.ts +2 -0
  7. package/dist/config/model-context.js +1 -1
  8. package/dist/config/recommended.d.ts +2 -0
  9. package/dist/proxy/patch/index.d.ts +1 -1
  10. package/dist/proxy/patch/index.js +11 -3
  11. package/frontend-dist/assets/{CardContent-D2E8XPMF.js → CardContent-CWAKELrs.js} +1 -1
  12. package/frontend-dist/assets/{CardTitle-Bvn47Yr0.js → CardTitle-BaEU1LKt.js} +1 -1
  13. package/frontend-dist/assets/{Checkbox-CHMJbyg6.js → Checkbox-DEjEG5IG.js} +1 -1
  14. package/frontend-dist/assets/{CollapsibleContent-5Mrc8gGt.js → CollapsibleContent-D6M9ckiV.js} +1 -1
  15. package/frontend-dist/assets/{CollapsibleTrigger-DaAYAs8_.js → CollapsibleTrigger-D7ytblFT.js} +1 -1
  16. package/frontend-dist/assets/{Dashboard-JuNvaAgL.js → Dashboard-DSjJiwNK.js} +1 -1
  17. package/frontend-dist/assets/{Input-D34hdiws.js → Input-Dr54RO9b.js} +1 -1
  18. package/frontend-dist/assets/{Label-NPWP7UVZ.js → Label-C1VeclKG.js} +1 -1
  19. package/frontend-dist/assets/{Login-CiMHu-aw.js → Login-COerl_FG.js} +1 -1
  20. package/frontend-dist/assets/{Logs-RRwgGUbN.js → Logs-ftRRcovp.js} +1 -1
  21. package/frontend-dist/assets/{MappingEntryEditor-B4fiJi8Q.js → MappingEntryEditor-Ee3Ry8SV.js} +1 -1
  22. package/frontend-dist/assets/{ModelCard-DSpT9oxm.js → ModelCard-6HxVZety.js} +1 -1
  23. package/frontend-dist/assets/{ModelMappings-CsgtxPOH.js → ModelMappings-DP-sSMpU.js} +1 -1
  24. package/frontend-dist/assets/{Monitor-KQ4-zFJ3.js → Monitor-Cr2YZJCL.js} +1 -1
  25. package/frontend-dist/assets/Providers-qtUyzL0Z.js +1 -0
  26. package/frontend-dist/assets/{ProxyEnhancement-Cczah5af.js → ProxyEnhancement-BtDp94lW.js} +1 -1
  27. package/frontend-dist/assets/{QuickSetup-BPa2psLw.js → QuickSetup-T77OBpL5.js} +1 -1
  28. package/frontend-dist/assets/{RetryRules-B4kfx7KE.js → RetryRules-DTvmG0lP.js} +1 -1
  29. package/frontend-dist/assets/{RouterKeys-B6gaOE5V.js → RouterKeys-Bt-w46oq.js} +1 -1
  30. package/frontend-dist/assets/{RovingFocusItem-ZGq4Eu8v.js → RovingFocusItem-DqFdv_fr.js} +1 -1
  31. package/frontend-dist/assets/{Schedules-tkI3OZrg.js → Schedules-nMIfQGMv.js} +1 -1
  32. package/frontend-dist/assets/{Settings-DRcVz0VH.js → Settings-DX97J988.js} +1 -1
  33. package/frontend-dist/assets/{Setup-CPx8uTQg.js → Setup-Dt5SDW8y.js} +1 -1
  34. package/frontend-dist/assets/{Switch-BgKvsuZd.js → Switch-D5SINcHm.js} +1 -1
  35. package/frontend-dist/assets/{TooltipTrigger-C1VLFDy4.js → TooltipTrigger-DKHvvL_R.js} +1 -1
  36. package/frontend-dist/assets/{TransformRulesForm-CxUVIzWH.js → TransformRulesForm-CfTPnICK.js} +1 -1
  37. package/frontend-dist/assets/{UnifiedRequestDialog-D-sQqFxg.js → UnifiedRequestDialog-7DjuW5T4.js} +1 -1
  38. package/frontend-dist/assets/{VisuallyHiddenInput-WLtFW_E8.js → VisuallyHiddenInput-3MxuNxns.js} +1 -1
  39. package/frontend-dist/assets/{button-CGbcdJgN.js → button-BNzpd1Az.js} +2 -2
  40. package/frontend-dist/assets/{copy-CpYWP1uM.js → copy-C2UqbqTe.js} +1 -1
  41. package/frontend-dist/assets/{dialog-CWAFinoK.js → dialog-D6gMAMVo.js} +1 -1
  42. package/frontend-dist/assets/{index-DDiesvp7.js → index-BrEL_SHx.js} +2 -2
  43. package/frontend-dist/assets/providers-Bcea72GK.js +1 -0
  44. package/frontend-dist/assets/providers-DNICB6Kg.js +1 -0
  45. package/frontend-dist/assets/{sidebar-2QJncvS6.js → sidebar-BLkzG956.js} +1 -1
  46. package/frontend-dist/assets/sidebar-Bw1Xt0-q.js +1 -0
  47. package/frontend-dist/assets/{trash-2-CAPUkICH.js → trash-2-Cyi3HM86.js} +1 -1
  48. package/frontend-dist/assets/{useClipboard-CLRvABjT.js → useClipboard-DiljKUdS.js} +1 -1
  49. package/frontend-dist/assets/{useLogRetention-B7v1HgoB.js → useLogRetention-Cfxr7tWP.js} +1 -1
  50. package/frontend-dist/index.html +2 -2
  51. package/package.json +1 -1
  52. package/frontend-dist/assets/Providers-B8kM2PFx.js +0 -1
  53. package/frontend-dist/assets/providers-BsmC9XuH.js +0 -1
  54. package/frontend-dist/assets/providers-DWTdDUnh.js +0 -1
  55. package/frontend-dist/assets/sidebar-D6_jVIzE.js +0 -1
@@ -7,6 +7,7 @@
7
7
  "presetName": "deepseek",
8
8
  "apiType": "anthropic",
9
9
  "baseUrl": "https://api.deepseek.com/anthropic",
10
+ "modelsEndpoint": "/v1/models",
10
11
  "models": [
11
12
  "deepseek-v4-flash",
12
13
  "deepseek-v4-pro"
@@ -17,6 +18,7 @@
17
18
  "presetName": "deepseek-openai",
18
19
  "apiType": "openai",
19
20
  "baseUrl": "https://api.deepseek.com",
21
+ "modelsEndpoint": "/v1/models",
20
22
  "models": [
21
23
  "deepseek-v4-flash",
22
24
  "deepseek-v4-pro"
@@ -32,6 +34,7 @@
32
34
  "presetName": "qianfan",
33
35
  "apiType": "openai",
34
36
  "baseUrl": "https://qianfan.baidubce.com/v2",
37
+ "modelsEndpoint": "/v1/models",
35
38
  "models": [
36
39
  "ernie-4.0-8k",
37
40
  "ernie-4.0-turbo-8k",
@@ -54,6 +57,7 @@
54
57
  "presetName": "iflytek-spark",
55
58
  "apiType": "openai",
56
59
  "baseUrl": "https://spark-api-open.xf-yun.com",
60
+ "modelsEndpoint": "/v1/models",
57
61
  "models": [
58
62
  "4.0Ultra",
59
63
  "generalv3.5",
@@ -73,6 +77,7 @@
73
77
  "presetName": "siliconflow",
74
78
  "apiType": "openai",
75
79
  "baseUrl": "https://api.siliconflow.cn",
80
+ "modelsEndpoint": "/v1/models",
76
81
  "models": [
77
82
  "deepseek-ai/DeepSeek-V3.2-Exp",
78
83
  "deepseek-ai/DeepSeek-R1",
@@ -93,6 +98,7 @@
93
98
  "presetName": "zhipu-coding-plan",
94
99
  "apiType": "anthropic",
95
100
  "baseUrl": "https://open.bigmodel.cn/api/anthropic",
101
+ "modelsEndpoint": "/v1/models",
96
102
  "models": [
97
103
  "glm-5.1",
98
104
  "glm-5",
@@ -106,6 +112,7 @@
106
112
  "presetName": "zhipu",
107
113
  "apiType": "openai",
108
114
  "baseUrl": "https://open.bigmodel.cn/api/paas/v4",
115
+ "modelsEndpoint": "/models",
109
116
  "models": [
110
117
  "glm-5.1",
111
118
  "glm-5",
@@ -125,6 +132,7 @@
125
132
  "presetName": "kimi-coding-plan",
126
133
  "apiType": "anthropic",
127
134
  "baseUrl": "https://api.kimi.com/coding",
135
+ "modelsEndpoint": "/v1/models",
128
136
  "models": [
129
137
  "kimi-for-coding",
130
138
  "kimi-k2.5"
@@ -135,6 +143,7 @@
135
143
  "presetName": "kimi",
136
144
  "apiType": "openai",
137
145
  "baseUrl": "https://api.moonshot.cn",
146
+ "modelsEndpoint": "/v1/models",
138
147
  "models": [
139
148
  "kimi-k2.6",
140
149
  "kimi-k2.5",
@@ -153,6 +162,7 @@
153
162
  "presetName": "minimax-token-plan",
154
163
  "apiType": "anthropic",
155
164
  "baseUrl": "https://api.minimaxi.com/anthropic",
165
+ "modelsEndpoint": "/v1/models",
156
166
  "models": [
157
167
  "MiniMax-M2.7"
158
168
  ]
@@ -162,6 +172,7 @@
162
172
  "presetName": "minimax",
163
173
  "apiType": "openai",
164
174
  "baseUrl": "https://api.minimax.chat",
175
+ "modelsEndpoint": "/v1/models",
165
176
  "models": [
166
177
  "MiniMax-M2.7",
167
178
  "MiniMax-M2.7-highspeed",
@@ -181,6 +192,7 @@
181
192
  "presetName": "volcengine-coding-plan",
182
193
  "apiType": "anthropic",
183
194
  "baseUrl": "https://ark.cn-beijing.volces.com/api/coding",
195
+ "modelsEndpoint": "/v1/models",
184
196
  "models": [
185
197
  "ark-code-latest",
186
198
  "doubao-seed-2.0-code",
@@ -194,6 +206,7 @@
194
206
  "presetName": "volcengine",
195
207
  "apiType": "openai",
196
208
  "baseUrl": "https://ark.cn-beijing.volces.com/api/v3",
209
+ "modelsEndpoint": "/models",
197
210
  "models": [
198
211
  "doubao-seed-2-0-pro-260215",
199
212
  "doubao-seed-1-8-251228",
@@ -224,6 +237,7 @@
224
237
  "presetName": "aliyun",
225
238
  "apiType": "openai",
226
239
  "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode",
240
+ "modelsEndpoint": "/v1/models",
227
241
  "models": [
228
242
  "qwen3.6-plus",
229
243
  "qwen3.5-plus",
@@ -243,6 +257,7 @@
243
257
  "presetName": "tencent-coding-plan",
244
258
  "apiType": "anthropic",
245
259
  "baseUrl": "https://api.lkeap.cloud.tencent.com/coding/anthropic",
260
+ "modelsEndpoint": "/v1/models",
246
261
  "models": [
247
262
  "tc-code-latest",
248
263
  "hunyuan-2.0-instruct",
@@ -258,6 +273,7 @@
258
273
  "presetName": "tencent",
259
274
  "apiType": "openai",
260
275
  "baseUrl": "https://api.hunyuan.cloud.tencent.com",
276
+ "modelsEndpoint": "/v1/models",
261
277
  "models": [
262
278
  "hunyuan-2.0-thinking",
263
279
  "hunyuan-2.0-instruct",
@@ -276,6 +292,7 @@
276
292
  "presetName": "opencode-go-openai",
277
293
  "apiType": "openai",
278
294
  "baseUrl": "https://opencode.ai/zen/go/v1/chat/completions",
295
+ "modelsEndpoint": "/models",
279
296
  "models": [
280
297
  "glm-5.1",
281
298
  "glm-5",
@@ -311,6 +328,7 @@
311
328
  "presetName": "stepfun-step-plan",
312
329
  "apiType": "anthropic",
313
330
  "baseUrl": "https://api.stepfun.com/step_plan",
331
+ "modelsEndpoint": "/v1/models",
314
332
  "models": [
315
333
  "step-3.5-flash-2603",
316
334
  "step-3.5-flash"
@@ -321,6 +339,7 @@
321
339
  "presetName": "stepfun",
322
340
  "apiType": "openai",
323
341
  "baseUrl": "https://api.stepfun.com",
342
+ "modelsEndpoint": "/v1/models",
324
343
  "models": [
325
344
  "step-3.5-flash",
326
345
  "step-3",
@@ -1 +1 @@
1
- export { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, } from "../core/constants.js";
1
+ export { HTTP_OK, HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, } from "../core/constants.js";
@@ -1,2 +1,2 @@
1
1
  // HTTP 状态码统一从 core/constants.ts 导入,避免重复定义
2
- export { HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, } from "../core/constants.js";
2
+ export { HTTP_OK, HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, } from "../core/constants.js";
@@ -2,11 +2,14 @@ import { Type } from "@sinclair/typebox";
2
2
  import { getAllProviders, getProviderById, createProvider, updateProvider, deleteProvider, getAllMappingGroups, updateMappingGroup, PROVIDER_CONCURRENCY_DEFAULTS } from "../db/index.js";
3
3
  import { encrypt, decrypt } from "../utils/crypto.js";
4
4
  import { getSetting } from "../db/settings.js";
5
- import { HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_BAD_REQUEST } from "./constants.js";
5
+ import { HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_BAD_REQUEST, HTTP_OK } from "./constants.js";
6
6
  import { API_CODE, apiError } from "./api-response.js";
7
7
  import { parseModels, buildModelInfoList } from "../config/model-context.js";
8
8
  import { getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider } from "../db/model-info.js";
9
+ import { buildUpstreamHeaders } from "../proxy/proxy-core.js";
10
+ import { callGet } from "../proxy/transport/http.js";
9
11
  const API_KEY_PREVIEW_MIN_LENGTH = 8;
12
+ const FETCH_MODELS_BODY_PREVIEW_LENGTH = 200;
10
13
  function cascadeProviderDisable(db, providerId) {
11
14
  const result = { updatedGroups: [] };
12
15
  const groups = getAllMappingGroups(db);
@@ -96,10 +99,10 @@ const CreateProviderSchema = Type.Object({
96
99
  queue_timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
97
100
  max_queue_size: Type.Optional(Type.Integer({ minimum: 1 })),
98
101
  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()),
102
+ proxy_type: Type.Optional(Type.Union([Type.Literal("http"), Type.Literal("socks5"), Type.Null()])),
103
+ proxy_url: Type.Optional(Type.Union([Type.String({ minLength: 1 }), Type.Null()])),
104
+ proxy_username: Type.Optional(Type.Union([Type.String(), Type.Null()])),
105
+ proxy_password: Type.Optional(Type.Union([Type.String(), Type.Null()])),
103
106
  });
104
107
  const UpdateProviderSchema = Type.Object({
105
108
  name: Type.Optional(Type.String({ minLength: 1 })),
@@ -117,10 +120,10 @@ const UpdateProviderSchema = Type.Object({
117
120
  queue_timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
118
121
  max_queue_size: Type.Optional(Type.Integer({ minimum: 1 })),
119
122
  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()),
123
+ proxy_type: Type.Optional(Type.Union([Type.Literal("http"), Type.Literal("socks5"), Type.Null()])),
124
+ proxy_url: Type.Optional(Type.Union([Type.String({ minLength: 1 }), Type.Null()])),
125
+ proxy_username: Type.Optional(Type.Union([Type.String(), Type.Null()])),
126
+ proxy_password: Type.Optional(Type.Union([Type.String(), Type.Null()])),
124
127
  });
125
128
  export const adminProviderRoutes = (app, options, done) => {
126
129
  const { db, stateRegistry, tracker, adaptiveController, proxyAgentFactory } = options;
@@ -164,11 +167,11 @@ export const adminProviderRoutes = (app, options, done) => {
164
167
  const encryptedKey = encrypt(body.api_key, getSetting(db, "encryption_key"));
165
168
  const { entries: normalizedModels, overrides: contextOverrides } = extractModelOverrides((body.models ?? []));
166
169
  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;
170
+ // 将空 proxy_url 视为不使用代理
171
+ const effectiveProxyType = body.proxy_url?.trim() ? body.proxy_type : null;
172
+ const effectiveProxyUrl = body.proxy_url?.trim() || null;
173
+ const encryptedProxyUsername = (effectiveProxyType && body.proxy_username) ? encrypt(body.proxy_username, getSetting(db, "encryption_key")) : null;
174
+ const encryptedProxyPassword = (effectiveProxyType && body.proxy_password) ? encrypt(body.proxy_password, getSetting(db, "encryption_key")) : null;
172
175
  const id = createProvider(db, {
173
176
  name: body.name,
174
177
  api_type: body.api_type,
@@ -182,8 +185,8 @@ export const adminProviderRoutes = (app, options, done) => {
182
185
  queue_timeout_ms: body.queue_timeout_ms ?? PROVIDER_CONCURRENCY_DEFAULTS.queue_timeout_ms,
183
186
  max_queue_size: body.max_queue_size ?? PROVIDER_CONCURRENCY_DEFAULTS.max_queue_size,
184
187
  adaptive_enabled: isAdaptiveEnabled,
185
- proxy_type: body.proxy_type ?? null,
186
- proxy_url: body.proxy_type ? body.proxy_url : null,
188
+ proxy_type: effectiveProxyType,
189
+ proxy_url: effectiveProxyUrl,
187
190
  proxy_username: encryptedProxyUsername,
188
191
  proxy_password: encryptedProxyPassword,
189
192
  });
@@ -255,22 +258,21 @@ export const adminProviderRoutes = (app, options, done) => {
255
258
  fields.api_key = encrypt(body.api_key, getSetting(db, "encryption_key"));
256
259
  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)}` : "****";
257
260
  }
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;
261
+ // Proxy field handling - 空URL视为不使用代理
262
+ const effectiveProxyUrl = body.proxy_url !== undefined ? (body.proxy_url?.trim() || null) : undefined;
263
+ const effectiveProxyType = effectiveProxyUrl !== undefined ? (effectiveProxyUrl ? body.proxy_type : null) : undefined;
264
+ if (effectiveProxyType !== undefined) {
265
+ fields.proxy_type = effectiveProxyType;
266
+ fields.proxy_url = effectiveProxyUrl;
267
+ if (!effectiveProxyType) {
263
268
  fields.proxy_username = null;
264
269
  fields.proxy_password = null;
265
270
  }
266
271
  }
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) {
272
+ if (body.proxy_username !== undefined && effectiveProxyType) {
271
273
  fields.proxy_username = body.proxy_username ? encrypt(body.proxy_username, getSetting(db, "encryption_key")) : null;
272
274
  }
273
- if (body.proxy_password !== undefined && body.proxy_type) {
275
+ if (body.proxy_password !== undefined && effectiveProxyType) {
274
276
  fields.proxy_password = body.proxy_password ? encrypt(body.proxy_password, getSetting(db, "encryption_key")) : null;
275
277
  }
276
278
  updateProvider(db, id, fields);
@@ -374,6 +376,52 @@ export const adminProviderRoutes = (app, options, done) => {
374
376
  tracker?.removeProviderConfig(id);
375
377
  return reply.send({ success: true });
376
378
  });
379
+ // --- 从上游 Provider 获取可用模型列表 ---
380
+ const FetchModelsSchema = Type.Object({
381
+ base_url: Type.String({ minLength: 1 }),
382
+ models_endpoint: Type.String({ minLength: 1 }),
383
+ api_key: Type.String({ minLength: 1 }),
384
+ api_type: Type.Union([Type.Literal("openai"), Type.Literal("anthropic")]),
385
+ });
386
+ app.post("/admin/api/providers/fetch-models", { schema: { body: FetchModelsSchema } }, async (request, reply) => {
387
+ const { base_url, models_endpoint, api_key, api_type } = request.body;
388
+ const backend = { base_url };
389
+ const clientHeaders = {};
390
+ try {
391
+ const result = await callGet(backend, api_key, clientHeaders, models_endpoint, (cliHdrs, key) => buildUpstreamHeaders(cliHdrs, key, undefined, api_type));
392
+ if (result.statusCode !== HTTP_OK) {
393
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, `上游返回 HTTP ${result.statusCode}: ${result.body.substring(0, FETCH_MODELS_BODY_PREVIEW_LENGTH)}`));
394
+ }
395
+ let parsed;
396
+ try {
397
+ parsed = JSON.parse(result.body);
398
+ }
399
+ catch {
400
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, "上游返回非 JSON 响应"));
401
+ }
402
+ // OpenAI 格式: { object: "list", data: [{ id: "model-name", ... }] }
403
+ // Anthropic 格式: { data: [{ type: "model", id: "model-name", ... }] }
404
+ const data = parsed?.data;
405
+ if (!Array.isArray(data)) {
406
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, "上游返回的模型列表格式不符合预期"));
407
+ }
408
+ const modelIds = data
409
+ .map((item) => {
410
+ if (typeof item === "string")
411
+ return item;
412
+ if (typeof item === "object" && item !== null && "id" in item)
413
+ return item.id;
414
+ return null;
415
+ })
416
+ .filter((id) => typeof id === "string")
417
+ .sort();
418
+ return reply.send(modelIds);
419
+ }
420
+ catch (err) {
421
+ const message = err instanceof Error ? err.message : String(err);
422
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, `连接上游失败: ${message}`));
423
+ }
424
+ });
377
425
  app.get("/admin/api/providers/:id/adaptive-status", async (request, reply) => {
378
426
  const { id } = request.params;
379
427
  const status = adaptiveController?.getStatus(id);
@@ -1,8 +1,8 @@
1
1
  import { getConfigSyncSource, setConfigSyncSource } from '../db/settings.js';
2
- import { detectDeployment, hasProcessManager, resolveRestartBinPath, getRestartMethod } from '../upgrade/deployment.js';
2
+ import { detectDeployment, hasProcessManager, getRestartMethod } from '../upgrade/deployment.js';
3
3
  import { createUpgradeChecker, fetchJson } from '../upgrade/checker.js';
4
4
  import { reloadConfig } from '../config/recommended.js';
5
- import { execSync, spawn } from 'node:child_process';
5
+ import { execSync } from 'node:child_process';
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
8
  import { HTTP_BAD_REQUEST, HTTP_INTERNAL_ERROR } from '../core/constants.js';
@@ -86,28 +86,19 @@ export const adminUpgradeRoutes = (app, options, done) => {
86
86
  app.post('/admin/api/upgrade/restart', async (req, reply) => {
87
87
  const managed = hasProcessManager();
88
88
  const method = getRestartMethod();
89
+ if (!managed) {
90
+ // 无进程管理器(npx / 手动 node)时无法安全自动重启:
91
+ // 1. spawn 路径不可靠(npx 不注册全局 bin)
92
+ // 2. 新旧进程端口竞态(EADDRINUSE)
93
+ // 3. 原始启动参数无法复现
94
+ return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, 'No process manager detected (PM2/systemd/Docker). Please restart manually.'));
95
+ }
89
96
  // 先回复客户端,再执行重启(否则客户端收不到响应)
90
97
  reply.send({ ok: true, method });
91
98
  // 给响应发送窗口
92
99
  await new Promise((resolve) => setTimeout(resolve, RESTART_RESPONSE_FLUSH_MS));
93
100
  try {
94
101
  req.log.info({ method, managed }, 'Restarting server...');
95
- if (!managed) {
96
- // 无进程管理器(npx / 手动 node):先 spawn 新进程,再关闭旧进程。
97
- // 必须在 closeFn 之前 spawn,否则 closeFn 可能因活跃 SSE 连接卡住导致新进程永远不启动。
98
- const binPath = resolveRestartBinPath();
99
- const args = process.argv.slice(2); // eslint-disable-line no-magic-numbers
100
- req.log.info({ binPath, args }, 'Spawning new process before exit');
101
- const child = spawn(binPath, args, {
102
- detached: true,
103
- stdio: 'ignore',
104
- env: { ...process.env },
105
- });
106
- child.on('error', (err) => {
107
- req.log.error({ err, binPath }, 'Failed to spawn new process');
108
- });
109
- child.unref();
110
- }
111
102
  // 强制退出兜底:即使 closeFn 卡住(如活跃代理 SSE 流),也能确保进程退出。
112
103
  const forceExitTimer = setTimeout(() => {
113
104
  req.log.warn('Graceful shutdown timed out during restart, forcing exit');
@@ -121,7 +112,6 @@ export const adminUpgradeRoutes = (app, options, done) => {
121
112
  process.exit(0);
122
113
  }
123
114
  catch (err) {
124
- // 优雅关闭失败时也必须退出:新进程已经 spawn,旧进程必须让出端口
125
115
  const msg = err instanceof Error ? err.message : String(err);
126
116
  req.log.error({ err }, `Restart failed: ${msg}`);
127
117
  process.exit(1);
@@ -14,5 +14,7 @@ export declare const MODEL_CONTEXT_WINDOWS: Record<string, number>;
14
14
  export declare const DEFAULT_CONTEXT_WINDOW = 200000;
15
15
  export declare const OVERFLOW_THRESHOLD = 1000000;
16
16
  export declare function lookupContextWindow(modelName: string): number;
17
+ /** 标准化 patch 名称:连字符 → 下划线 */
18
+ export declare function normalizePatchName(name: string): string;
17
19
  export declare function parseModels(raw: string): ModelEntry[];
18
20
  export declare function buildModelInfoList(modelEntries: ModelEntry[], overrides: Map<string, number>): ModelInfo[];
@@ -85,7 +85,7 @@ export function lookupContextWindow(modelName) {
85
85
  return MODEL_CONTEXT_WINDOWS[modelName] ?? DEFAULT_CONTEXT_WINDOW;
86
86
  }
87
87
  /** 标准化 patch 名称:连字符 → 下划线 */
88
- function normalizePatchName(name) {
88
+ export function normalizePatchName(name) {
89
89
  return name.replace(/-/g, "_");
90
90
  }
91
91
  export function parseModels(raw) {
@@ -4,6 +4,8 @@ export interface ProviderPreset {
4
4
  apiType: 'openai' | 'openai-responses' | 'anthropic';
5
5
  baseUrl: string;
6
6
  upstreamPath?: string;
7
+ /** 上游模型列表端点路径,如 /v1/models 或 /models;拼接在 baseUrl 后 */
8
+ modelsEndpoint?: string;
7
9
  models: string[];
8
10
  }
9
11
  export interface ProviderGroup {
@@ -1,4 +1,4 @@
1
- import type { ModelEntry } from "../../config/model-context.js";
1
+ import { type ModelEntry } from "../../config/model-context.js";
2
2
  export interface ProviderInfo {
3
3
  base_url: string;
4
4
  api_type: string;
@@ -1,3 +1,4 @@
1
+ import { normalizePatchName } from "../../config/model-context.js";
1
2
  import { applyDeepSeekPatches } from "./deepseek/index.js";
2
3
  const OPENAI_ORIGIN_HOSTS = ["api.openai.com", "openai.com"];
3
4
  /**
@@ -23,19 +24,19 @@ export function applyProviderPatches(body, provider) {
23
24
  const modelPatches = modelEntry?.patches ?? [];
24
25
  if (modelPatches.length > 0) {
25
26
  // developer_role 补丁(仅 openai 格式需要)
26
- if (modelPatches.includes("developer_role") && provider.api_type === "openai" && hasDeveloperRole(body)) {
27
+ if (hasPatch(modelPatches, "developer-role") && provider.api_type === "openai" && hasDeveloperRole(body)) {
27
28
  patchDeveloperRole(ensureCloned());
28
29
  patches.push("developer_role");
29
30
  }
30
31
  // DeepSeek Anthropic 补丁
31
32
  const dsAnthropicPatches = ["thinking-param", "cache-control", "thinking-blocks", "orphan-tool-results"];
32
- if (dsAnthropicPatches.some(p => modelPatches.includes(p)) && provider.api_type === "anthropic") {
33
+ if (dsAnthropicPatches.some(p => hasPatch(modelPatches, p)) && provider.api_type === "anthropic") {
33
34
  applyDeepSeekPatches(ensureCloned(), "anthropic");
34
35
  patches.push("deepseek");
35
36
  }
36
37
  // DeepSeek OpenAI 补丁
37
38
  const dsOpenAIPatches = ["non-ds-tools", "orphan-tool-results-oa"];
38
- if (dsOpenAIPatches.some(p => modelPatches.includes(p)) && provider.api_type === "openai") {
39
+ if (dsOpenAIPatches.some(p => hasPatch(modelPatches, p)) && provider.api_type === "openai") {
39
40
  applyDeepSeekPatches(ensureCloned(), "openai");
40
41
  patches.push("deepseek");
41
42
  }
@@ -92,3 +93,10 @@ function needsDeepSeekPatch(body, provider) {
92
93
  const model = body.model ?? "";
93
94
  return model.includes("deepseek");
94
95
  }
96
+ /**
97
+ * 格式无关的 patch 匹配:将比较值与 modelPatches(已由 parseModels 归一化)
98
+ * 通过同一个 normalizePatchName 函数比较,无论 DB 存的是连字符还是下划线都能匹配。
99
+ */
100
+ function hasPatch(modelPatches, name) {
101
+ return modelPatches.includes(normalizePatchName(name));
102
+ }
@@ -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-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
+ import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-BNzpd1Az.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-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
+ import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-BNzpd1Az.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-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
+ 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-BNzpd1Az.js";import{t as b}from"./VisuallyHiddenInput-3MxuNxns.js";import{t as x}from"./RovingFocusItem-DqFdv_fr.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-BrEL_SHx.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-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
+ 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-BNzpd1Az.js";import{B as x,L as S,q as C,z as w}from"./index-BrEL_SHx.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-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};
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-BNzpd1Az.js";import{r as l}from"./CollapsibleContent-D6M9ckiV.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};