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.
- package/config/recommended-providers.json +234 -19
- package/dist/admin/api-response.d.ts +0 -1
- package/dist/admin/api-response.js +8 -4
- package/dist/admin/groups.js +35 -0
- package/dist/admin/monitor.js +2 -0
- package/dist/admin/providers.js +188 -22
- package/dist/admin/proxy-enhancement.js +9 -9
- package/dist/config/model-context.d.ts +10 -0
- package/dist/config/model-context.js +105 -0
- package/dist/db/index.d.ts +3 -1
- package/dist/db/index.js +2 -1
- package/dist/db/logs.d.ts +4 -0
- package/dist/db/logs.js +7 -3
- package/dist/db/mappings.d.ts +2 -1
- package/dist/db/mappings.js +2 -2
- package/dist/db/migrations/023_create_provider_model_info.sql +8 -0
- package/dist/db/migrations/024_add_mapping_groups_is_active.sql +1 -0
- package/dist/db/migrations/025_add_client_status_code.sql +3 -0
- package/dist/db/model-info.d.ts +14 -0
- package/dist/db/model-info.js +27 -0
- package/dist/db/providers.d.ts +1 -0
- package/dist/db/providers.js +1 -1
- package/dist/index.js +15 -3
- package/dist/middleware/auth.js +1 -1
- package/dist/monitor/request-tracker.d.ts +2 -0
- package/dist/monitor/request-tracker.js +18 -0
- package/dist/proxy/anthropic.js +13 -0
- package/dist/proxy/enhancement/directive-parser.d.ts +8 -2
- package/dist/proxy/enhancement/directive-parser.js +44 -17
- package/dist/proxy/enhancement/enhancement-handler.js +184 -54
- package/dist/proxy/enhancement/index.d.ts +1 -1
- package/dist/proxy/enhancement/index.js +1 -1
- package/dist/proxy/enhancement-config.d.ts +6 -0
- package/dist/proxy/enhancement-config.js +19 -0
- package/dist/proxy/openai.js +40 -3
- package/dist/proxy/overflow.d.ts +18 -0
- package/dist/proxy/overflow.js +128 -0
- package/dist/proxy/patch/deepseek/index.d.ts +6 -0
- package/dist/proxy/patch/deepseek/index.js +11 -0
- package/dist/proxy/patch/deepseek/patch-orphan-tool-results.d.ts +12 -0
- package/dist/proxy/patch/deepseek/patch-orphan-tool-results.js +90 -0
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.d.ts +6 -0
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.js +24 -0
- package/dist/proxy/patch/index.d.ts +9 -0
- package/dist/proxy/patch/index.js +17 -0
- package/dist/proxy/proxy-core.d.ts +9 -2
- package/dist/proxy/proxy-core.js +24 -2
- package/dist/proxy/proxy-handler.js +34 -9
- package/dist/proxy/proxy-logging.js +23 -2
- package/dist/proxy/resilience.d.ts +4 -0
- package/dist/proxy/resilience.js +8 -1
- package/dist/proxy/strategy/types.d.ts +2 -0
- package/dist/proxy/stream-proxy.js +2 -1
- package/dist/proxy/transport-fn.js +3 -2
- package/dist/proxy/transport.js +3 -2
- package/dist/proxy/types.d.ts +3 -1
- package/dist/proxy/types.js +5 -1
- package/dist/upgrade/checker.js +5 -2
- package/dist/utils/time-range.js +28 -13
- package/frontend-dist/assets/CardContent-GNY_j_L3.js +1 -0
- package/frontend-dist/assets/CardTitle-BhXJbSoh.js +1 -0
- package/frontend-dist/assets/Checkbox-n_sh6Lvx.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-DDCUOXDR.js +1 -0
- package/frontend-dist/assets/Collection-DbtqQ1jF.js +1 -0
- package/frontend-dist/assets/Dashboard-Dy9frcgO.js +3 -0
- package/frontend-dist/assets/DialogTitle-BEWUnuJQ.js +1 -0
- package/frontend-dist/assets/{Input-O0ebU-Va.js → Input-CmibY9Fx.js} +1 -1
- package/frontend-dist/assets/Label-Cs__wFH0.js +1 -0
- package/frontend-dist/assets/Login-BciEc1TW.js +1 -0
- package/frontend-dist/assets/Logs-BkqwWW0-.js +1 -0
- package/frontend-dist/assets/ModelMappings-DrCJ_TCf.js +1 -0
- package/frontend-dist/assets/Monitor-C-b4qyuI.js +1 -0
- package/frontend-dist/assets/PopoverTrigger-DaKOMSVs.js +1 -0
- package/frontend-dist/assets/PopperContent-DZ6plcjf.js +1 -0
- package/frontend-dist/assets/Providers-u8utX74M.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-8_xhndGt.js +5 -0
- package/frontend-dist/assets/RetryRules-D1psYDEP.js +1 -0
- package/frontend-dist/assets/RouterKeys-ovPFGhjy.js +1 -0
- package/frontend-dist/assets/RovingFocusItem-Dsv9AkP7.js +1 -0
- package/frontend-dist/assets/SelectValue-BoUWfZAg.js +1 -0
- package/frontend-dist/assets/Settings-DXF-6A8C.js +6 -0
- package/frontend-dist/assets/Setup-rVLqiz0d.js +1 -0
- package/frontend-dist/assets/Switch-po5ZVBE3.js +1 -0
- package/frontend-dist/assets/TableHeader-Zyvq_0p2.js +1 -0
- package/frontend-dist/assets/{TabsTrigger-CPCi2HIa.js → TabsTrigger-CgDhZGkT.js} +1 -1
- package/frontend-dist/assets/Teleport-CgTHarey.js +3 -0
- package/frontend-dist/assets/TooltipTrigger-C2qO21dQ.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-Dksad8eN.js +3 -0
- package/frontend-dist/assets/{VisuallyHidden-Cyk-jWwh.js → VisuallyHidden-fbPmoMwi.js} +1 -1
- package/frontend-dist/assets/VisuallyHiddenInput-7j8wkPrW.js +1 -0
- package/frontend-dist/assets/alert-dialog-DbT3PzoF.js +1 -0
- package/frontend-dist/assets/badge-BVxnlnsH.js +1 -0
- package/frontend-dist/assets/{button-BQ3s7yNh.js → button-BCrIpNwA.js} +2 -2
- package/frontend-dist/assets/chevron-down-CWBwGxSp.js +1 -0
- package/frontend-dist/assets/circle-question-mark-DRkkqjgG.js +1 -0
- package/frontend-dist/assets/dialog-BNlCZpHK.js +1 -0
- package/frontend-dist/assets/file-text-BavS6SrF.js +1 -0
- package/frontend-dist/assets/format-K3VR67cG.js +1 -0
- package/frontend-dist/assets/index-BP4imfye.css +1 -0
- package/frontend-dist/assets/index-DrBJPq6d.js +1 -0
- package/frontend-dist/assets/lib-CGpNhf06.js +1 -0
- package/frontend-dist/assets/loader-circle-Cpd89XQ7.js +1 -0
- package/frontend-dist/assets/ohash.D__AXeF1-DkJnWU8a.js +1 -0
- package/frontend-dist/assets/{useClipboard-Cnnz6AAN.js → useClipboard-Bq8yZunx.js} +1 -1
- package/frontend-dist/assets/useLogRetention-BWPm3G_A.js +1 -0
- package/frontend-dist/assets/useNonce-D5lpSPNk.js +1 -0
- package/frontend-dist/assets/x-BFIp7DLt.js +1 -0
- package/frontend-dist/index.html +20 -17
- package/package.json +2 -1
- package/frontend-dist/assets/CardContent-WrBnGhTg.js +0 -1
- package/frontend-dist/assets/CardTitle-BcDYk7cq.js +0 -1
- package/frontend-dist/assets/Checkbox-MZf0YsDG.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-CrOH9HlW.js +0 -1
- package/frontend-dist/assets/Collection-DcTx_Y54.js +0 -1
- package/frontend-dist/assets/Dashboard-D0oDrSLr.js +0 -3
- package/frontend-dist/assets/DialogTitle-Cl5Cd7QH.js +0 -1
- package/frontend-dist/assets/Label-C_S0y7Um.js +0 -1
- package/frontend-dist/assets/Login-DGY7uF8P.js +0 -1
- package/frontend-dist/assets/Logs-ls8pv89b.js +0 -1
- package/frontend-dist/assets/ModelMappings-DGlf0S4s.js +0 -1
- package/frontend-dist/assets/Monitor-BSI87grz.js +0 -1
- package/frontend-dist/assets/PopperContent-C6Q7hDmf.js +0 -1
- package/frontend-dist/assets/Providers-ZkRpj8_m.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-DFPI1W6Z.js +0 -5
- package/frontend-dist/assets/RetryRules-DtM31qsl.js +0 -1
- package/frontend-dist/assets/RouterKeys-D63tRFKm.js +0 -1
- package/frontend-dist/assets/RovingFocusItem-BJoylAKU.js +0 -1
- package/frontend-dist/assets/SelectValue-CLp5z6_I.js +0 -1
- package/frontend-dist/assets/Settings-DSgRKbTQ.js +0 -6
- package/frontend-dist/assets/Setup-BDmj6CRk.js +0 -1
- package/frontend-dist/assets/Switch-Wz-t_zkv.js +0 -1
- package/frontend-dist/assets/TableHeader-DGtcqGkw.js +0 -1
- package/frontend-dist/assets/Teleport-DdjYHlNK.js +0 -3
- package/frontend-dist/assets/TooltipTrigger-H_QoPY1n.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BAAfMJJl.js +0 -3
- package/frontend-dist/assets/VisuallyHiddenInput-CYjNe_H8.js +0 -1
- package/frontend-dist/assets/alert-dialog-Bi3dliLl.js +0 -1
- package/frontend-dist/assets/badge-Kkta3e9W.js +0 -1
- package/frontend-dist/assets/createLucideIcon-D1tkPDOQ.js +0 -1
- package/frontend-dist/assets/dialog-DoIATUYw.js +0 -1
- package/frontend-dist/assets/file-text-Dt6QP1bZ.js +0 -1
- package/frontend-dist/assets/format-DOVIVsQC.js +0 -1
- package/frontend-dist/assets/index-BY0E7CHR.js +0 -1
- package/frontend-dist/assets/index-Bnrh1mFY.css +0 -1
- package/frontend-dist/assets/lib-CxwxnlwW.js +0 -1
- package/frontend-dist/assets/ohash.D__AXeF1-b0PiKZB_.js +0 -1
- package/frontend-dist/assets/useLogRetention-DYP5LOAc.js +0 -1
- package/frontend-dist/assets/useNonce-DKbOCfgM.js +0 -1
- package/frontend-dist/assets/x-CAoitXRt.js +0 -1
package/dist/admin/providers.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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 (
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 (
|
|
22
|
-
const 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
|
+
}
|
package/dist/db/index.d.ts
CHANGED
|
@@ -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(`
|
package/dist/db/mappings.d.ts
CHANGED
|
@@ -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;
|
package/dist/db/mappings.js
CHANGED
|
@@ -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,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
|
+
}
|
package/dist/db/providers.d.ts
CHANGED
package/dist/db/providers.js
CHANGED
|
@@ -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
|
}
|