llm-simple-router 0.5.2 → 0.5.4

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 (149) hide show
  1. package/config/recommended-providers.json +234 -19
  2. package/dist/admin/api-response.d.ts +0 -1
  3. package/dist/admin/api-response.js +8 -4
  4. package/dist/admin/groups.js +35 -0
  5. package/dist/admin/monitor.js +2 -0
  6. package/dist/admin/providers.js +188 -22
  7. package/dist/admin/proxy-enhancement.js +9 -9
  8. package/dist/config/model-context.d.ts +10 -0
  9. package/dist/config/model-context.js +105 -0
  10. package/dist/db/index.d.ts +3 -1
  11. package/dist/db/index.js +2 -1
  12. package/dist/db/logs.d.ts +4 -0
  13. package/dist/db/logs.js +7 -3
  14. package/dist/db/mappings.d.ts +2 -1
  15. package/dist/db/mappings.js +2 -2
  16. package/dist/db/migrations/023_create_provider_model_info.sql +8 -0
  17. package/dist/db/migrations/024_add_mapping_groups_is_active.sql +1 -0
  18. package/dist/db/migrations/025_add_client_status_code.sql +3 -0
  19. package/dist/db/model-info.d.ts +14 -0
  20. package/dist/db/model-info.js +27 -0
  21. package/dist/db/providers.d.ts +1 -0
  22. package/dist/db/providers.js +1 -1
  23. package/dist/index.js +15 -3
  24. package/dist/middleware/auth.js +1 -1
  25. package/dist/monitor/request-tracker.d.ts +2 -0
  26. package/dist/monitor/request-tracker.js +18 -0
  27. package/dist/proxy/anthropic.js +13 -0
  28. package/dist/proxy/enhancement/directive-parser.d.ts +8 -2
  29. package/dist/proxy/enhancement/directive-parser.js +44 -17
  30. package/dist/proxy/enhancement/enhancement-handler.js +184 -54
  31. package/dist/proxy/enhancement/index.d.ts +1 -1
  32. package/dist/proxy/enhancement/index.js +1 -1
  33. package/dist/proxy/enhancement-config.d.ts +6 -0
  34. package/dist/proxy/enhancement-config.js +19 -0
  35. package/dist/proxy/openai.js +40 -3
  36. package/dist/proxy/overflow.d.ts +18 -0
  37. package/dist/proxy/overflow.js +128 -0
  38. package/dist/proxy/patch/deepseek/index.d.ts +6 -0
  39. package/dist/proxy/patch/deepseek/index.js +11 -0
  40. package/dist/proxy/patch/deepseek/patch-orphan-tool-results.d.ts +12 -0
  41. package/dist/proxy/patch/deepseek/patch-orphan-tool-results.js +90 -0
  42. package/dist/proxy/patch/deepseek/patch-thinking-blocks.d.ts +6 -0
  43. package/dist/proxy/patch/deepseek/patch-thinking-blocks.js +24 -0
  44. package/dist/proxy/patch/index.d.ts +9 -0
  45. package/dist/proxy/patch/index.js +17 -0
  46. package/dist/proxy/proxy-core.d.ts +9 -2
  47. package/dist/proxy/proxy-core.js +24 -2
  48. package/dist/proxy/proxy-handler.js +34 -9
  49. package/dist/proxy/proxy-logging.js +23 -2
  50. package/dist/proxy/resilience.d.ts +4 -0
  51. package/dist/proxy/resilience.js +8 -1
  52. package/dist/proxy/strategy/types.d.ts +2 -0
  53. package/dist/proxy/stream-proxy.js +2 -1
  54. package/dist/proxy/transport-fn.js +3 -2
  55. package/dist/proxy/transport.js +3 -2
  56. package/dist/proxy/types.d.ts +3 -1
  57. package/dist/proxy/types.js +5 -1
  58. package/dist/upgrade/checker.js +5 -2
  59. package/dist/utils/time-range.js +28 -13
  60. package/frontend-dist/assets/CardContent-GNY_j_L3.js +1 -0
  61. package/frontend-dist/assets/CardTitle-BhXJbSoh.js +1 -0
  62. package/frontend-dist/assets/Checkbox-n_sh6Lvx.js +1 -0
  63. package/frontend-dist/assets/CollapsibleTrigger-DDCUOXDR.js +1 -0
  64. package/frontend-dist/assets/Collection-DbtqQ1jF.js +1 -0
  65. package/frontend-dist/assets/Dashboard-Dy9frcgO.js +3 -0
  66. package/frontend-dist/assets/DialogTitle-BEWUnuJQ.js +1 -0
  67. package/frontend-dist/assets/{Input-O0ebU-Va.js → Input-CmibY9Fx.js} +1 -1
  68. package/frontend-dist/assets/Label-Cs__wFH0.js +1 -0
  69. package/frontend-dist/assets/Login-BciEc1TW.js +1 -0
  70. package/frontend-dist/assets/Logs-BkqwWW0-.js +1 -0
  71. package/frontend-dist/assets/ModelMappings-DrCJ_TCf.js +1 -0
  72. package/frontend-dist/assets/Monitor-C-b4qyuI.js +1 -0
  73. package/frontend-dist/assets/PopoverTrigger-DaKOMSVs.js +1 -0
  74. package/frontend-dist/assets/PopperContent-DZ6plcjf.js +1 -0
  75. package/frontend-dist/assets/Providers-u8utX74M.js +1 -0
  76. package/frontend-dist/assets/ProxyEnhancement-8_xhndGt.js +5 -0
  77. package/frontend-dist/assets/RetryRules-D1psYDEP.js +1 -0
  78. package/frontend-dist/assets/RouterKeys-ovPFGhjy.js +1 -0
  79. package/frontend-dist/assets/RovingFocusItem-Dsv9AkP7.js +1 -0
  80. package/frontend-dist/assets/SelectValue-BoUWfZAg.js +1 -0
  81. package/frontend-dist/assets/Settings-DXF-6A8C.js +6 -0
  82. package/frontend-dist/assets/Setup-rVLqiz0d.js +1 -0
  83. package/frontend-dist/assets/Switch-po5ZVBE3.js +1 -0
  84. package/frontend-dist/assets/TableHeader-Zyvq_0p2.js +1 -0
  85. package/frontend-dist/assets/{TabsTrigger-CPCi2HIa.js → TabsTrigger-CgDhZGkT.js} +1 -1
  86. package/frontend-dist/assets/Teleport-CgTHarey.js +3 -0
  87. package/frontend-dist/assets/TooltipTrigger-C2qO21dQ.js +1 -0
  88. package/frontend-dist/assets/UnifiedRequestDialog-Dksad8eN.js +3 -0
  89. package/frontend-dist/assets/{VisuallyHidden-Cyk-jWwh.js → VisuallyHidden-fbPmoMwi.js} +1 -1
  90. package/frontend-dist/assets/VisuallyHiddenInput-7j8wkPrW.js +1 -0
  91. package/frontend-dist/assets/alert-dialog-DbT3PzoF.js +1 -0
  92. package/frontend-dist/assets/badge-BVxnlnsH.js +1 -0
  93. package/frontend-dist/assets/{button-BQ3s7yNh.js → button-BCrIpNwA.js} +2 -2
  94. package/frontend-dist/assets/chevron-down-CWBwGxSp.js +1 -0
  95. package/frontend-dist/assets/circle-question-mark-DRkkqjgG.js +1 -0
  96. package/frontend-dist/assets/dialog-BNlCZpHK.js +1 -0
  97. package/frontend-dist/assets/file-text-BavS6SrF.js +1 -0
  98. package/frontend-dist/assets/format-K3VR67cG.js +1 -0
  99. package/frontend-dist/assets/index-BP4imfye.css +1 -0
  100. package/frontend-dist/assets/index-DrBJPq6d.js +1 -0
  101. package/frontend-dist/assets/lib-CGpNhf06.js +1 -0
  102. package/frontend-dist/assets/loader-circle-Cpd89XQ7.js +1 -0
  103. package/frontend-dist/assets/ohash.D__AXeF1-DkJnWU8a.js +1 -0
  104. package/frontend-dist/assets/{useClipboard-Cnnz6AAN.js → useClipboard-Bq8yZunx.js} +1 -1
  105. package/frontend-dist/assets/useLogRetention-BWPm3G_A.js +1 -0
  106. package/frontend-dist/assets/useNonce-D5lpSPNk.js +1 -0
  107. package/frontend-dist/assets/x-BFIp7DLt.js +1 -0
  108. package/frontend-dist/index.html +20 -17
  109. package/package.json +2 -1
  110. package/frontend-dist/assets/CardContent-WrBnGhTg.js +0 -1
  111. package/frontend-dist/assets/CardTitle-BcDYk7cq.js +0 -1
  112. package/frontend-dist/assets/Checkbox-MZf0YsDG.js +0 -1
  113. package/frontend-dist/assets/CollapsibleTrigger-CrOH9HlW.js +0 -1
  114. package/frontend-dist/assets/Collection-DcTx_Y54.js +0 -1
  115. package/frontend-dist/assets/Dashboard-D0oDrSLr.js +0 -3
  116. package/frontend-dist/assets/DialogTitle-Cl5Cd7QH.js +0 -1
  117. package/frontend-dist/assets/Label-C_S0y7Um.js +0 -1
  118. package/frontend-dist/assets/Login-DGY7uF8P.js +0 -1
  119. package/frontend-dist/assets/Logs-ls8pv89b.js +0 -1
  120. package/frontend-dist/assets/ModelMappings-DGlf0S4s.js +0 -1
  121. package/frontend-dist/assets/Monitor-BSI87grz.js +0 -1
  122. package/frontend-dist/assets/PopperContent-C6Q7hDmf.js +0 -1
  123. package/frontend-dist/assets/Providers-ZkRpj8_m.js +0 -1
  124. package/frontend-dist/assets/ProxyEnhancement-DFPI1W6Z.js +0 -5
  125. package/frontend-dist/assets/RetryRules-DtM31qsl.js +0 -1
  126. package/frontend-dist/assets/RouterKeys-D63tRFKm.js +0 -1
  127. package/frontend-dist/assets/RovingFocusItem-BJoylAKU.js +0 -1
  128. package/frontend-dist/assets/SelectValue-CLp5z6_I.js +0 -1
  129. package/frontend-dist/assets/Settings-DSgRKbTQ.js +0 -6
  130. package/frontend-dist/assets/Setup-BDmj6CRk.js +0 -1
  131. package/frontend-dist/assets/Switch-Wz-t_zkv.js +0 -1
  132. package/frontend-dist/assets/TableHeader-DGtcqGkw.js +0 -1
  133. package/frontend-dist/assets/Teleport-DdjYHlNK.js +0 -3
  134. package/frontend-dist/assets/TooltipTrigger-H_QoPY1n.js +0 -1
  135. package/frontend-dist/assets/UnifiedRequestDialog-BAAfMJJl.js +0 -3
  136. package/frontend-dist/assets/VisuallyHiddenInput-CYjNe_H8.js +0 -1
  137. package/frontend-dist/assets/alert-dialog-Bi3dliLl.js +0 -1
  138. package/frontend-dist/assets/badge-Kkta3e9W.js +0 -1
  139. package/frontend-dist/assets/createLucideIcon-D1tkPDOQ.js +0 -1
  140. package/frontend-dist/assets/dialog-DoIATUYw.js +0 -1
  141. package/frontend-dist/assets/file-text-Dt6QP1bZ.js +0 -1
  142. package/frontend-dist/assets/format-DOVIVsQC.js +0 -1
  143. package/frontend-dist/assets/index-BY0E7CHR.js +0 -1
  144. package/frontend-dist/assets/index-Bnrh1mFY.css +0 -1
  145. package/frontend-dist/assets/lib-CxwxnlwW.js +0 -1
  146. package/frontend-dist/assets/ohash.D__AXeF1-b0PiKZB_.js +0 -1
  147. package/frontend-dist/assets/useLogRetention-DYP5LOAc.js +0 -1
  148. package/frontend-dist/assets/useNonce-DKbOCfgM.js +0 -1
  149. package/frontend-dist/assets/x-CAoitXRt.js +0 -1
@@ -1,10 +1,93 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { getAllProviders, getProviderById, createProvider, updateProvider, deleteProvider, getAllMappingGroups, PROVIDER_CONCURRENCY_DEFAULTS } from "../db/index.js";
2
+ import { getAllProviders, getProviderById, createProvider, updateProvider, deleteProvider, getAllMappingGroups, getAllModelMappings, 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
5
  import { HTTP_CREATED, HTTP_NOT_FOUND, HTTP_CONFLICT, HTTP_BAD_REQUEST } from "./constants.js";
6
6
  import { API_CODE, apiError } from "./api-response.js";
7
+ import { parseModels, buildModelInfoList } from "../config/model-context.js";
8
+ import { getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider } from "../db/model-info.js";
7
9
  const API_KEY_PREVIEW_MIN_LENGTH = 8;
10
+ function cascadeProviderDisable(db, providerId) {
11
+ const result = { updatedGroups: [] };
12
+ const groups = getAllMappingGroups(db);
13
+ for (const g of groups) {
14
+ if (!g.is_active)
15
+ continue;
16
+ let rule;
17
+ try {
18
+ rule = JSON.parse(g.rule);
19
+ }
20
+ catch {
21
+ continue;
22
+ }
23
+ let modified = false;
24
+ let shouldDisable = false;
25
+ if (g.strategy === "scheduled") {
26
+ const def = rule.default;
27
+ if (def) {
28
+ if (def.provider_id === providerId) {
29
+ shouldDisable = true;
30
+ modified = true;
31
+ }
32
+ if (def.overflow_provider_id === providerId) {
33
+ delete def.overflow_provider_id;
34
+ delete def.overflow_model;
35
+ modified = true;
36
+ }
37
+ }
38
+ const windows = rule.windows;
39
+ if (Array.isArray(windows)) {
40
+ const filtered = windows.filter((w) => {
41
+ if (w.target?.provider_id === providerId) {
42
+ modified = true;
43
+ return false;
44
+ }
45
+ if (w.target?.overflow_provider_id === providerId) {
46
+ delete w.target.overflow_provider_id;
47
+ delete w.target.overflow_model;
48
+ modified = true;
49
+ }
50
+ return true;
51
+ });
52
+ rule.windows = filtered;
53
+ }
54
+ }
55
+ else {
56
+ const targets = rule.targets;
57
+ if (Array.isArray(targets)) {
58
+ const filtered = targets.filter((t) => {
59
+ if (t.provider_id === providerId) {
60
+ modified = true;
61
+ return false;
62
+ }
63
+ if (t.overflow_provider_id === providerId) {
64
+ delete t.overflow_provider_id;
65
+ delete t.overflow_model;
66
+ modified = true;
67
+ }
68
+ return true;
69
+ });
70
+ rule.targets = filtered;
71
+ if (filtered.length === 0 && modified) {
72
+ shouldDisable = true;
73
+ }
74
+ }
75
+ }
76
+ if (modified) {
77
+ const fields = { rule: JSON.stringify(rule) };
78
+ if (shouldDisable)
79
+ fields.is_active = 0;
80
+ updateMappingGroup(db, g.id, fields);
81
+ result.updatedGroups.push({ id: g.id, client_model: g.client_model, disabled: shouldDisable });
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+ function extractModelOverrides(models) {
87
+ const names = models.map(m => typeof m === "string" ? m : m.name);
88
+ const overrides = models.filter((m) => typeof m !== "string" && m.context_window != null);
89
+ return { names, overrides };
90
+ }
8
91
  const API_KEY_PREVIEW_PREFIX_LEN = 4;
9
92
  const PROVIDER_NAME_RE = /^[a-zA-Z0-9_-]+$/;
10
93
  const CreateProviderSchema = Type.Object({
@@ -12,7 +95,10 @@ const CreateProviderSchema = Type.Object({
12
95
  api_type: Type.Union([Type.Literal("openai"), Type.Literal("anthropic")]),
13
96
  base_url: Type.String({ minLength: 1 }),
14
97
  api_key: Type.String({ minLength: 1 }),
15
- models: Type.Optional(Type.Array(Type.String())),
98
+ models: Type.Optional(Type.Array(Type.Union([
99
+ Type.String(),
100
+ Type.Object({ name: Type.String(), context_window: Type.Optional(Type.Number()) })
101
+ ]))),
16
102
  is_active: Type.Optional(Type.Number()),
17
103
  max_concurrency: Type.Optional(Type.Integer({ minimum: 0 })),
18
104
  queue_timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
@@ -23,7 +109,10 @@ const UpdateProviderSchema = Type.Object({
23
109
  api_type: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("anthropic")])),
24
110
  base_url: Type.Optional(Type.String({ minLength: 1 })),
25
111
  api_key: Type.Optional(Type.String({ minLength: 1 })),
26
- models: Type.Optional(Type.Array(Type.String())),
112
+ models: Type.Optional(Type.Array(Type.Union([
113
+ Type.String(),
114
+ Type.Object({ name: Type.String(), context_window: Type.Optional(Type.Number()) })
115
+ ]))),
27
116
  is_active: Type.Optional(Type.Number()),
28
117
  max_concurrency: Type.Optional(Type.Integer({ minimum: 0 })),
29
118
  queue_timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
@@ -34,21 +123,25 @@ export const adminProviderRoutes = (app, options, done) => {
34
123
  app.get("/admin/api/providers", async (_request, reply) => {
35
124
  const encryptionKey = getSetting(db, "encryption_key");
36
125
  const providers = getAllProviders(db);
37
- return reply.send(providers.map((s) => ({
38
- id: s.id,
39
- name: s.name,
40
- api_type: s.api_type,
41
- base_url: s.base_url,
42
- api_key: s.api_key ? decrypt(s.api_key, encryptionKey) : "",
43
- models: JSON.parse(s.models || "[]"),
44
- is_active: s.is_active,
45
- max_concurrency: s.max_concurrency,
46
- queue_timeout_ms: s.queue_timeout_ms,
47
- max_queue_size: s.max_queue_size,
48
- concurrency_status: semaphoreManager?.getStatus(s.id) ?? { active: 0, queued: 0 },
49
- created_at: s.created_at,
50
- updated_at: s.updated_at,
51
- })));
126
+ return reply.send(providers.map((s) => {
127
+ const modelNames = parseModels(s.models || "[]");
128
+ const overrides = new Map(getModelInfoForProvider(db, s.id).map(m => [m.model_name, m.context_window]));
129
+ return {
130
+ id: s.id,
131
+ name: s.name,
132
+ api_type: s.api_type,
133
+ base_url: s.base_url,
134
+ api_key: s.api_key ? decrypt(s.api_key, encryptionKey) : "",
135
+ models: buildModelInfoList(modelNames, overrides),
136
+ is_active: s.is_active,
137
+ max_concurrency: s.max_concurrency,
138
+ queue_timeout_ms: s.queue_timeout_ms,
139
+ max_queue_size: s.max_queue_size,
140
+ concurrency_status: semaphoreManager?.getStatus(s.id) ?? { active: 0, queued: 0 },
141
+ created_at: s.created_at,
142
+ updated_at: s.updated_at,
143
+ };
144
+ }));
52
145
  });
53
146
  app.post("/admin/api/providers", { schema: { body: CreateProviderSchema } }, async (request, reply) => {
54
147
  const body = request.body;
@@ -60,18 +153,22 @@ export const adminProviderRoutes = (app, options, done) => {
60
153
  return reply.code(HTTP_CONFLICT).send(apiError(API_CODE.CONFLICT_NAME, `Provider 名称 '${body.name}' 已存在`));
61
154
  }
62
155
  const encryptedKey = encrypt(body.api_key, getSetting(db, "encryption_key"));
156
+ const { names: normalizedModels, overrides: contextOverrides } = extractModelOverrides((body.models ?? []));
63
157
  const id = createProvider(db, {
64
158
  name: body.name,
65
159
  api_type: body.api_type,
66
160
  base_url: body.base_url,
67
161
  api_key: encryptedKey,
68
162
  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)}` : "****",
69
- models: JSON.stringify(body.models ?? []),
163
+ models: JSON.stringify(normalizedModels),
70
164
  is_active: body.is_active ?? 1,
71
165
  max_concurrency: body.max_concurrency ?? PROVIDER_CONCURRENCY_DEFAULTS.max_concurrency,
72
166
  queue_timeout_ms: body.queue_timeout_ms ?? PROVIDER_CONCURRENCY_DEFAULTS.queue_timeout_ms,
73
167
  max_queue_size: body.max_queue_size ?? PROVIDER_CONCURRENCY_DEFAULTS.max_queue_size,
74
168
  });
169
+ if (contextOverrides.length > 0) {
170
+ setModelInfoForProvider(db, id, contextOverrides.map(o => ({ model_name: o.name, context_window: o.context_window })));
171
+ }
75
172
  semaphoreManager?.updateConfig(id, {
76
173
  maxConcurrency: body.max_concurrency ?? PROVIDER_CONCURRENCY_DEFAULTS.max_concurrency,
77
174
  queueTimeoutMs: body.queue_timeout_ms ?? PROVIDER_CONCURRENCY_DEFAULTS.queue_timeout_ms,
@@ -104,8 +201,16 @@ export const adminProviderRoutes = (app, options, done) => {
104
201
  fields.base_url = body.base_url;
105
202
  if (body.is_active !== undefined)
106
203
  fields.is_active = body.is_active;
107
- if (body.models !== undefined)
108
- fields.models = JSON.stringify(body.models);
204
+ if (body.models !== undefined) {
205
+ const { names, overrides } = extractModelOverrides(body.models);
206
+ fields.models = JSON.stringify(names);
207
+ if (overrides.length > 0) {
208
+ setModelInfoForProvider(db, id, overrides.map(o => ({ model_name: o.name, context_window: o.context_window })));
209
+ }
210
+ else {
211
+ deleteAllModelInfoForProvider(db, id);
212
+ }
213
+ }
109
214
  if (body.max_concurrency !== undefined)
110
215
  fields.max_concurrency = body.max_concurrency;
111
216
  if (body.queue_timeout_ms !== undefined)
@@ -118,6 +223,10 @@ export const adminProviderRoutes = (app, options, done) => {
118
223
  }
119
224
  updateProvider(db, id, fields);
120
225
  const updated = getProviderById(db, id);
226
+ let cascade;
227
+ if (existing.is_active === 1 && body.is_active === 0) {
228
+ cascade = cascadeProviderDisable(db, id);
229
+ }
121
230
  if (body.max_concurrency !== undefined || body.queue_timeout_ms !== undefined || body.max_queue_size !== undefined) {
122
231
  semaphoreManager?.updateConfig(id, {
123
232
  maxConcurrency: updated.max_concurrency,
@@ -131,7 +240,64 @@ export const adminProviderRoutes = (app, options, done) => {
131
240
  queueTimeoutMs: updated.queue_timeout_ms,
132
241
  maxQueueSize: updated.max_queue_size,
133
242
  });
134
- return reply.send({ success: true });
243
+ return reply.send({ success: true, cascadedGroups: cascade?.updatedGroups ?? [] });
244
+ });
245
+ app.get("/admin/api/providers/:id/dependencies", async (request, reply) => {
246
+ const { id } = request.params;
247
+ const existing = getProviderById(db, id);
248
+ if (!existing) {
249
+ return reply.code(HTTP_NOT_FOUND).send(apiError(API_CODE.NOT_FOUND, "Provider not found"));
250
+ }
251
+ const references = [];
252
+ const groups = getAllMappingGroups(db);
253
+ for (const g of groups) {
254
+ if (!g.is_active)
255
+ continue;
256
+ const refs = [];
257
+ try {
258
+ const rule = JSON.parse(g.rule);
259
+ if (rule.default?.provider_id === id) {
260
+ refs.push(`默认模型 (${rule.default.backend_model})`);
261
+ }
262
+ if (rule.default?.overflow_provider_id === id) {
263
+ refs.push(`默认溢出模型 (${rule.default.overflow_model || "-"})`);
264
+ }
265
+ if (Array.isArray(rule.windows)) {
266
+ for (const w of rule.windows) {
267
+ if (w.target?.provider_id === id) {
268
+ refs.push(`时间窗口 ${w.start}-${w.end} (${w.target.backend_model})`);
269
+ }
270
+ if (w.target?.overflow_provider_id === id) {
271
+ refs.push(`时间窗口 ${w.start}-${w.end} 溢出 (${w.target.overflow_model || "-"})`);
272
+ }
273
+ }
274
+ }
275
+ if (Array.isArray(rule.targets)) {
276
+ for (let i = 0; i < rule.targets.length; i++) {
277
+ const t = rule.targets[i];
278
+ if (t.provider_id === id) {
279
+ refs.push(`目标 ${i + 1} (${t.backend_model})`);
280
+ }
281
+ if (t.overflow_provider_id === id) {
282
+ refs.push(`目标 ${i + 1} 溢出 (${t.overflow_model || "-"})`);
283
+ }
284
+ }
285
+ }
286
+ }
287
+ catch {
288
+ continue;
289
+ }
290
+ for (const ref of refs) {
291
+ references.push(`映射分组「${g.client_model}」: ${ref}`);
292
+ }
293
+ }
294
+ const mappings = getAllModelMappings(db);
295
+ for (const m of mappings) {
296
+ if (m.provider_id === id) {
297
+ references.push(`旧版映射「${m.client_model}」→ ${m.backend_model}`);
298
+ }
299
+ }
300
+ return reply.send({ references });
135
301
  });
136
302
  app.delete("/admin/api/providers/:id", async (request, reply) => {
137
303
  const { id } = request.params;
@@ -1,5 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { getSetting, setSetting } from "../db/settings.js";
2
+ import { setSetting } from "../db/settings.js";
3
+ import { loadEnhancementConfig } from "../proxy/enhancement-config.js";
3
4
  const UpdateProxyEnhancementSchema = Type.Object({
4
5
  claude_code_enabled: Type.Boolean(),
5
6
  });
@@ -11,15 +12,14 @@ import { getSessionStates, getSessionHistory, } from "../db/session-states.js";
11
12
  import { modelState } from "../proxy/model-state.js";
12
13
  export const adminProxyEnhancementRoutes = (app, options, done) => {
13
14
  const { db } = options;
14
- app.get("/admin/api/proxy-enhancement", async (_req, reply) => {
15
- const raw = getSetting(db, "proxy_enhancement");
16
- const config = raw
17
- ? JSON.parse(raw)
18
- : { claude_code_enabled: false };
19
- return reply.send(config);
15
+ app.get("/admin/api/proxy-enhancement", async (_request, reply) => {
16
+ const config = loadEnhancementConfig(db);
17
+ return reply.send({
18
+ claude_code_enabled: config.claude_code_enabled,
19
+ });
20
20
  });
21
- app.put("/admin/api/proxy-enhancement", { schema: { body: UpdateProxyEnhancementSchema } }, async (req, reply) => {
22
- const body = req.body;
21
+ app.put("/admin/api/proxy-enhancement", { schema: { body: UpdateProxyEnhancementSchema } }, async (request, reply) => {
22
+ const body = request.body;
23
23
  const config = {
24
24
  claude_code_enabled: body.claude_code_enabled,
25
25
  };
@@ -0,0 +1,10 @@
1
+ export interface ModelInfo {
2
+ name: string;
3
+ context_window: number | null;
4
+ }
5
+ export declare const MODEL_CONTEXT_WINDOWS: Record<string, number>;
6
+ export declare const DEFAULT_CONTEXT_WINDOW = 200000;
7
+ export declare const OVERFLOW_THRESHOLD = 1000000;
8
+ export declare function lookupContextWindow(modelName: string): number;
9
+ export declare function parseModels(raw: string): string[];
10
+ export declare function buildModelInfoList(modelNames: string[], overrides: Map<string, number>): ModelInfo[];
@@ -0,0 +1,105 @@
1
+ export const MODEL_CONTEXT_WINDOWS = {
2
+ // DeepSeek
3
+ "deepseek-chat": 1000000,
4
+ "deepseek-reasoner": 1000000,
5
+ "deepseek-v3": 1000000,
6
+ "deepseek-r1": 1000000,
7
+ "deepseek-v4-flash": 1000000,
8
+ "deepseek-v4-pro": 1000000,
9
+ "deepseek-v3.2": 128000,
10
+ // 智谱
11
+ "glm-5.1": 200000,
12
+ "glm-5": 200000,
13
+ "glm-5-turbo": 200000,
14
+ "glm-4.7": 200000,
15
+ "glm-4.7-flash": 200000,
16
+ "glm-4.6": 200000,
17
+ "glm-4.5-air": 128000,
18
+ // KIMI
19
+ "kimi-for-coding": 256000,
20
+ "kimi-k2.6": 256000,
21
+ "kimi-k2.5": 256000,
22
+ "kimi-k2-turbo-preview": 256000,
23
+ "kimi-k2-thinking": 256000,
24
+ "moonshot-v1-128k": 128000,
25
+ // 阿里云 Qwen
26
+ "qwen3.6-plus": 1000000,
27
+ "qwen3.5-plus": 1000000,
28
+ "qwen3-max": 256000,
29
+ "qwen3.5-flash": 1000000,
30
+ "qwen3-coder-plus": 1000000,
31
+ "qwen3-coder-next": 256000,
32
+ // MiniMax
33
+ "MiniMax-M2.7": 200000,
34
+ "MiniMax-M2.7-highspeed": 200000,
35
+ "MiniMax-M2.5": 200000,
36
+ "MiniMax-M2.5-highspeed": 200000,
37
+ "MiniMax-M2.1": 200000,
38
+ "MiniMax-M2": 200000,
39
+ // 百度千帆
40
+ "ernie-4.0-8k": 8000,
41
+ "ernie-4.0-turbo-8k": 8000,
42
+ "ernie-3.5-8k": 8000,
43
+ "ernie-speed-8k": 8000,
44
+ "ernie-lite-8k": 8000,
45
+ "ernie-x1-32k-preview": 32000,
46
+ // 科大讯飞
47
+ "4.0Ultra": 32000,
48
+ "generalv3.5": 8000,
49
+ "max-32k": 32000,
50
+ "generalv3": 8000,
51
+ "pro-128k": 128000,
52
+ "lite": 8000,
53
+ // 火山引擎
54
+ "ark-code-latest": 256000,
55
+ "doubao-seed-2.0-code": 256000,
56
+ "doubao-seed-2-0-pro-260215": 256000,
57
+ "doubao-seed-1-8-251228": 256000,
58
+ "doubao-seed-code-preview-251028": 256000,
59
+ // 腾讯云
60
+ "tc-code-latest": 256000,
61
+ "hunyuan-2.0-instruct": 128000,
62
+ "hunyuan-2.0-thinking": 128000,
63
+ "hunyuan-turbos": 32000,
64
+ "hunyuan-t1": 32000,
65
+ "hunyuan-a13b": 256000,
66
+ // 阶跃星辰
67
+ "step-3.5-flash": 256000,
68
+ "step-3.5-flash-2603": 256000,
69
+ "step-3": 64000,
70
+ "step-2-16k": 16000,
71
+ "step-1-8k": 8000,
72
+ "step-1-32k": 32000,
73
+ // 硅基流动
74
+ "deepseek-ai/DeepSeek-V3.2-Exp": 128000,
75
+ "deepseek-ai/DeepSeek-R1": 128000,
76
+ "Qwen/Qwen3-8B": 128000,
77
+ "Qwen/Qwen2.5-72B-Instruct": 128000,
78
+ "Qwen/Qwen2.5-Coder-32B-Instruct": 128000,
79
+ "moonshotai/Kimi-K2-Instruct": 128000,
80
+ "moonshotai/Kimi-K2.5": 256000,
81
+ };
82
+ export const DEFAULT_CONTEXT_WINDOW = 200000;
83
+ export const OVERFLOW_THRESHOLD = 1000000;
84
+ export function lookupContextWindow(modelName) {
85
+ return MODEL_CONTEXT_WINDOWS[modelName] ?? DEFAULT_CONTEXT_WINDOW;
86
+ }
87
+ export function parseModels(raw) {
88
+ if (!raw)
89
+ return [];
90
+ try {
91
+ const parsed = JSON.parse(raw);
92
+ if (!Array.isArray(parsed))
93
+ return [];
94
+ return parsed.map((item) => typeof item === 'string' ? item : item?.name ?? '').filter(Boolean);
95
+ }
96
+ catch {
97
+ return [];
98
+ }
99
+ }
100
+ export function buildModelInfoList(modelNames, overrides) {
101
+ return modelNames.map(name => ({
102
+ name,
103
+ context_window: overrides.get(name) ?? lookupContextWindow(name),
104
+ }));
105
+ }
@@ -6,7 +6,7 @@ export { getModelMapping, getAllModelMappings, createModelMapping, updateModelMa
6
6
  export type { ModelMapping, MappingGroup, ProviderModelEntry } from "./mappings.js";
7
7
  export { getActiveRetryRules, getAllRetryRules, getRetryRuleById, createRetryRule, updateRetryRule, deleteRetryRule, } from "./retry-rules.js";
8
8
  export type { RetryRule } from "./retry-rules.js";
9
- export { insertRequestLog, getRequestLogs, getRequestLogById, deleteLogsBefore, getRequestLogChildren, getRequestLogsGrouped, updateLogMetrics, updateLogStreamContent, backfillMetricsFromRequestMetrics, estimateLogTableSize, deleteOldestLogs, getLogCount, } from "./logs.js";
9
+ export { insertRequestLog, getRequestLogs, getRequestLogById, deleteLogsBefore, getRequestLogChildren, getRequestLogsGrouped, updateLogMetrics, updateLogStreamContent, updateLogClientStatus, backfillMetricsFromRequestMetrics, estimateLogTableSize, deleteOldestLogs, getLogCount, } from "./logs.js";
10
10
  export type { RequestLog, RequestLogGroupedRow, RequestLogListRow } from "./logs.js";
11
11
  export { getRouterKeyByHash, getAllRouterKeys, getRouterKeyById, createRouterKey, updateRouterKey, deleteRouterKey, getAvailableModels, } from "./router-keys.js";
12
12
  export type { RouterKey } from "./router-keys.js";
@@ -20,5 +20,7 @@ export { getSessionStates, getSessionState, getSessionHistory, upsertSessionStat
20
20
  export type { SessionModelState, SessionModelHistory, UpsertSessionStateInput, InsertSessionHistoryInput } from "./session-states.js";
21
21
  export { insertWindow, getLatestWindow, getWindowsInRange, getWindowUsage, } from "./usage-windows.js";
22
22
  export type { UsageWindow, WindowUsage } from "./usage-windows.js";
23
+ export { getModelContextWindowOverride, getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider, getAllModelInfo, } from "./model-info.js";
24
+ export type { ProviderModelInfo } from "./model-info.js";
23
25
  export { collectDbSizeInfo, runSizeBasedCleanup, scheduleDbSizeMonitor, } from "./db-size-monitor.js";
24
26
  export type { DbSizeInfo, SizeThresholds, DbSizeMonitorHandle } from "./db-size-monitor.js";
package/dist/db/index.js CHANGED
@@ -56,7 +56,7 @@ export function initDatabase(dbPath) {
56
56
  export { getActiveProviders, getAllProviders, getProviderById, getActiveProviderByName, getActiveProvidersWithModels, createProvider, updateProvider, deleteProvider, PROVIDER_CONCURRENCY_DEFAULTS, } from "./providers.js";
57
57
  export { getModelMapping, getAllModelMappings, createModelMapping, updateModelMapping, deleteModelMapping, getMappingGroup, getMappingGroupById, getAllMappingGroups, createMappingGroup, updateMappingGroup, deleteMappingGroup, getActiveProviderModels, resolveByProviderModel, } from "./mappings.js";
58
58
  export { getActiveRetryRules, getAllRetryRules, getRetryRuleById, createRetryRule, updateRetryRule, deleteRetryRule, } from "./retry-rules.js";
59
- export { insertRequestLog, getRequestLogs, getRequestLogById, deleteLogsBefore, getRequestLogChildren, getRequestLogsGrouped, updateLogMetrics, updateLogStreamContent, backfillMetricsFromRequestMetrics, estimateLogTableSize, deleteOldestLogs, getLogCount, } from "./logs.js";
59
+ export { insertRequestLog, getRequestLogs, getRequestLogById, deleteLogsBefore, getRequestLogChildren, getRequestLogsGrouped, updateLogMetrics, updateLogStreamContent, updateLogClientStatus, backfillMetricsFromRequestMetrics, estimateLogTableSize, deleteOldestLogs, getLogCount, } from "./logs.js";
60
60
  export { getRouterKeyByHash, getAllRouterKeys, getRouterKeyById, createRouterKey, updateRouterKey, deleteRouterKey, getAvailableModels, } from "./router-keys.js";
61
61
  export { getMetricsSummary, getMetricsTimeseries, insertMetrics } from "./metrics.js";
62
62
  export { getStats } from "./stats.js";
@@ -64,4 +64,5 @@ export { getSetting, setSetting, isInitialized } from "./settings.js";
64
64
  export { getDbMaxSizeMb, setDbMaxSizeMb, getLogTableMaxSizeMb, setLogTableMaxSizeMb, } from "./settings.js";
65
65
  export { getSessionStates, getSessionState, getSessionHistory, upsertSessionState, insertSessionHistory, deleteSessionState, } from "./session-states.js";
66
66
  export { insertWindow, getLatestWindow, getWindowsInRange, getWindowUsage, } from "./usage-windows.js";
67
+ export { getModelContextWindowOverride, getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider, getAllModelInfo, } from "./model-info.js";
67
68
  export { collectDbSizeInfo, runSizeBasedCleanup, scheduleDbSizeMonitor, } from "./db-size-monitor.js";
package/dist/db/logs.d.ts CHANGED
@@ -5,6 +5,7 @@ export interface RequestLog {
5
5
  model: string | null;
6
6
  provider_id: string | null;
7
7
  status_code: number | null;
8
+ client_status_code: number | null;
8
9
  latency_ms: number | null;
9
10
  is_stream: number;
10
11
  error_message: string | null;
@@ -51,6 +52,7 @@ export interface RequestLogInsert {
51
52
  router_key_id?: string | null;
52
53
  original_model?: string | null;
53
54
  session_id?: string | null;
55
+ client_status_code?: number | null;
54
56
  }
55
57
  export declare function insertRequestLog(db: Database.Database, log: RequestLogInsert): void;
56
58
  export declare function getRequestLogs(db: Database.Database, options: {
@@ -80,6 +82,8 @@ type MetricsUpdate = {
80
82
  export declare function updateLogMetrics(db: Database.Database, logId: string, m: MetricsUpdate): void;
81
83
  /** 流式请求完成后,将 tracker 中累积的文本内容写入 request_logs */
82
84
  export declare function updateLogStreamContent(db: Database.Database, logId: string, textContent: string): void;
85
+ /** 当 router 返回给客户端的 status code 与上游不同时,记录实际发送的 status */
86
+ export declare function updateLogClientStatus(db: Database.Database, logId: string, clientStatusCode: number): void;
83
87
  /** 启动时回填:从 request_metrics 补齐 metrics_complete = 0 但实际有指标的行 */
84
88
  export declare function backfillMetricsFromRequestMetrics(db: Database.Database): number;
85
89
  export declare function deleteLogsBefore(db: Database.Database, beforeDate: string): number;
package/dist/db/logs.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // --- request_logs ---
2
2
  /** 日志列表查询共享的 SELECT 列 + JOIN 子句(metrics 已冗余到 request_logs,无需 JOIN request_metrics) */
3
- const LOG_LIST_SELECT = `rl.id, rl.api_type, rl.model, rl.provider_id, rl.status_code, rl.latency_ms,
3
+ const LOG_LIST_SELECT = `rl.id, rl.api_type, rl.model, rl.provider_id, rl.status_code, rl.client_status_code, rl.latency_ms,
4
4
  rl.is_stream, rl.error_message, rl.created_at, rl.is_retry, rl.is_failover, rl.original_request_id, rl.original_model,
5
5
  CASE WHEN rl.provider_id = 'router' THEN rl.upstream_request ELSE NULL END AS upstream_request,
6
6
  rl.input_tokens, rl.output_tokens, rl.cache_read_tokens, rl.ttft_ms, rl.tokens_per_second, rl.stop_reason,
@@ -8,10 +8,10 @@ const LOG_LIST_SELECT = `rl.id, rl.api_type, rl.model, rl.provider_id, rl.status
8
8
  COALESCE(p.name, rl.provider_id) AS provider_name`;
9
9
  const LOG_LIST_JOIN = `LEFT JOIN providers p ON p.id = rl.provider_id`;
10
10
  export function insertRequestLog(db, log) {
11
- db.prepare(`INSERT INTO request_logs (id, api_type, model, provider_id, status_code, latency_ms,
11
+ db.prepare(`INSERT INTO request_logs (id, api_type, model, provider_id, status_code, client_status_code, latency_ms,
12
12
  is_stream, error_message, created_at, client_request, upstream_request, upstream_response,
13
13
  is_retry, is_failover, original_request_id, router_key_id, original_model, session_id)
14
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(log.id, log.api_type, log.model, log.provider_id, log.status_code, log.latency_ms, log.is_stream, log.error_message, log.created_at, log.client_request ?? null, log.upstream_request ?? null, log.upstream_response ?? null, log.is_retry ?? 0, log.is_failover ?? 0, log.original_request_id ?? null, log.router_key_id ?? null, log.original_model ?? null, log.session_id ?? null);
14
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(log.id, log.api_type, log.model, log.provider_id, log.status_code, log.client_status_code ?? null, log.latency_ms, log.is_stream, log.error_message, log.created_at, log.client_request ?? null, log.upstream_request ?? null, log.upstream_response ?? null, log.is_retry ?? 0, log.is_failover ?? 0, log.original_request_id ?? null, log.router_key_id ?? null, log.original_model ?? null, log.session_id ?? null);
15
15
  }
16
16
  function buildLogWhereClause(options, baseCondition) {
17
17
  let where = baseCondition;
@@ -72,6 +72,10 @@ export function updateLogMetrics(db, logId, m) {
72
72
  export function updateLogStreamContent(db, logId, textContent) {
73
73
  db.prepare("UPDATE request_logs SET stream_text_content = ? WHERE id = ?").run(textContent, logId);
74
74
  }
75
+ /** 当 router 返回给客户端的 status code 与上游不同时,记录实际发送的 status */
76
+ export function updateLogClientStatus(db, logId, clientStatusCode) {
77
+ db.prepare("UPDATE request_logs SET client_status_code = ? WHERE id = ?").run(clientStatusCode, logId);
78
+ }
75
79
  /** 启动时回填:从 request_metrics 补齐 metrics_complete = 0 但实际有指标的行 */
76
80
  export function backfillMetricsFromRequestMetrics(db) {
77
81
  return db.prepare(`
@@ -12,6 +12,7 @@ export interface MappingGroup {
12
12
  client_model: string;
13
13
  strategy: string;
14
14
  rule: string;
15
+ is_active: number;
15
16
  created_at: string;
16
17
  }
17
18
  export declare function getModelMapping(db: Database.Database, clientModel: string): ModelMapping | undefined;
@@ -32,7 +33,7 @@ export declare function createMappingGroup(db: Database.Database, mapping: {
32
33
  strategy: string;
33
34
  rule: string;
34
35
  }): string;
35
- export declare function updateMappingGroup(db: Database.Database, id: string, fields: Partial<Pick<MappingGroup, "client_model" | "strategy" | "rule">>): void;
36
+ export declare function updateMappingGroup(db: Database.Database, id: string, fields: Partial<Pick<MappingGroup, "client_model" | "strategy" | "rule" | "is_active">>): void;
36
37
  export declare function deleteMappingGroup(db: Database.Database, id: string): void;
37
38
  export interface ProviderModelEntry {
38
39
  provider_name: string;
@@ -2,7 +2,7 @@ import { randomUUID } from "crypto";
2
2
  import { isTarget } from "../proxy/strategy/targets-rule.js";
3
3
  import { buildUpdateQuery, deleteById } from "./helpers.js";
4
4
  const MAPPING_FIELDS = new Set(["client_model", "backend_model", "provider_id", "is_active"]);
5
- const GROUP_FIELDS = new Set(["client_model", "strategy", "rule"]);
5
+ const GROUP_FIELDS = new Set(["client_model", "strategy", "rule", "is_active"]);
6
6
  // --- ModelMapping CRUD ---
7
7
  export function getModelMapping(db, clientModel) {
8
8
  return db
@@ -28,7 +28,7 @@ export function deleteModelMapping(db, id) {
28
28
  // --- MappingGroups CRUD ---
29
29
  export function getMappingGroup(db, clientModel) {
30
30
  return db
31
- .prepare("SELECT * FROM mapping_groups WHERE client_model = ?")
31
+ .prepare("SELECT * FROM mapping_groups WHERE client_model = ? AND is_active = 1")
32
32
  .get(clientModel);
33
33
  }
34
34
  export function getMappingGroupById(db, id) {
@@ -0,0 +1,8 @@
1
+ -- 为每个 provider 的每个 model 单独存储 context_window
2
+ CREATE TABLE IF NOT EXISTS provider_model_info (
3
+ provider_id TEXT NOT NULL,
4
+ model_name TEXT NOT NULL,
5
+ context_window INTEGER NOT NULL,
6
+ PRIMARY KEY (provider_id, model_name),
7
+ FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE
8
+ );
@@ -0,0 +1 @@
1
+ ALTER TABLE mapping_groups ADD COLUMN is_active INTEGER NOT NULL DEFAULT 1;
@@ -0,0 +1,3 @@
1
+ -- 记录 router 实际发送给客户端的 HTTP status code
2
+ -- 当与 status_code(上游原始值)不同时有值,用于追溯 status 转换
3
+ ALTER TABLE request_logs ADD COLUMN client_status_code INTEGER;
@@ -0,0 +1,14 @@
1
+ import Database from "better-sqlite3";
2
+ export interface ProviderModelInfo {
3
+ provider_id: string;
4
+ model_name: string;
5
+ context_window: number;
6
+ }
7
+ export declare function getModelContextWindowOverride(db: Database.Database, providerId: string, modelName: string): number | null;
8
+ export declare function getModelInfoForProvider(db: Database.Database, providerId: string): ProviderModelInfo[];
9
+ export declare function setModelInfoForProvider(db: Database.Database, providerId: string, entries: Array<{
10
+ model_name: string;
11
+ context_window: number;
12
+ }>): void;
13
+ export declare function deleteAllModelInfoForProvider(db: Database.Database, providerId: string): void;
14
+ export declare function getAllModelInfo(db: Database.Database): ProviderModelInfo[];
@@ -0,0 +1,27 @@
1
+ export function getModelContextWindowOverride(db, providerId, modelName) {
2
+ const row = db
3
+ .prepare("SELECT context_window FROM provider_model_info WHERE provider_id = ? AND model_name = ?")
4
+ .get(providerId, modelName);
5
+ return row?.context_window ?? null;
6
+ }
7
+ export function getModelInfoForProvider(db, providerId) {
8
+ return db
9
+ .prepare("SELECT * FROM provider_model_info WHERE provider_id = ?")
10
+ .all(providerId);
11
+ }
12
+ export function setModelInfoForProvider(db, providerId, entries) {
13
+ const del = db.prepare("DELETE FROM provider_model_info WHERE provider_id = ?");
14
+ const ins = db.prepare("INSERT INTO provider_model_info (provider_id, model_name, context_window) VALUES (?, ?, ?)");
15
+ db.transaction(() => {
16
+ del.run(providerId);
17
+ for (const e of entries) {
18
+ ins.run(providerId, e.model_name, e.context_window);
19
+ }
20
+ })();
21
+ }
22
+ export function deleteAllModelInfoForProvider(db, providerId) {
23
+ db.prepare("DELETE FROM provider_model_info WHERE provider_id = ?").run(providerId);
24
+ }
25
+ export function getAllModelInfo(db) {
26
+ return db.prepare("SELECT * FROM provider_model_info").all();
27
+ }
@@ -42,5 +42,6 @@ export declare function getActiveProviderByName(db: Database.Database, name: str
42
42
  } | undefined;
43
43
  export declare function getActiveProvidersWithModels(db: Database.Database): {
44
44
  id: string;
45
+ name: string;
45
46
  models: string;
46
47
  }[];
@@ -36,5 +36,5 @@ export function getActiveProviderByName(db, name) {
36
36
  return db.prepare("SELECT id, models FROM providers WHERE name = ? AND is_active = 1").get(name);
37
37
  }
38
38
  export function getActiveProvidersWithModels(db) {
39
- return db.prepare("SELECT id, models FROM providers WHERE is_active = 1").all();
39
+ return db.prepare("SELECT id, name, models FROM providers WHERE is_active = 1").all();
40
40
  }