llm-simple-router 0.9.14 → 0.9.16
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/dist/admin/proxy-enhancement.d.ts +0 -2
- package/dist/admin/proxy-enhancement.js +16 -24
- package/dist/admin/routes.js +1 -1
- package/dist/admin/settings-import-export.js +0 -3
- package/dist/core/constants.d.ts +1 -0
- package/dist/core/constants.js +1 -0
- package/dist/core/registry.d.ts +0 -11
- package/dist/db/index.d.ts +0 -2
- package/dist/db/index.js +0 -1
- package/dist/index.js +0 -8
- package/dist/proxy/enhancement/index.d.ts +1 -4
- package/dist/proxy/enhancement/index.js +3 -3
- package/dist/proxy/handler/openai.js +68 -49
- package/dist/proxy/handler/proxy-handler.js +15 -27
- package/dist/proxy/pipeline-snapshot.d.ts +0 -11
- package/dist/proxy/proxy-logging.d.ts +0 -7
- package/dist/proxy/proxy-logging.js +0 -22
- package/dist/proxy/routing/enhancement-config.d.ts +0 -1
- package/dist/proxy/routing/enhancement-config.js +0 -2
- package/dist/proxy/transport/transport-fn.d.ts +0 -1
- package/dist/proxy/transport/transport-fn.js +0 -13
- package/frontend-dist/assets/{CardContent-BTxpErx_.js → CardContent-Be6PJLTw.js} +1 -1
- package/frontend-dist/assets/{CardTitle-DuW6MHMC.js → CardTitle-CRJTZxQY.js} +1 -1
- package/frontend-dist/assets/{CascadingModelSelect-CAv9_cK6.js → CascadingModelSelect-BR8cT6D3.js} +1 -1
- package/frontend-dist/assets/{Checkbox-Bov0jC2d.js → Checkbox-CG6gSglN.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-ignsLSmA.js → CollapsibleContent-PqKToOXJ.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-K-O5aS5D.js → CollapsibleTrigger-54P86ben.js} +1 -1
- package/frontend-dist/assets/{Dashboard-Byh_loe7.js → Dashboard-BfQfZ9Kk.js} +1 -1
- package/frontend-dist/assets/{Input-DMYJg0mG.js → Input-m8uNSwlX.js} +1 -1
- package/frontend-dist/assets/{Label-Oc3EnENu.js → Label-B9qJOqmM.js} +1 -1
- package/frontend-dist/assets/{Login-CLYNuFun.js → Login-CaTB96MJ.js} +1 -1
- package/frontend-dist/assets/Logs-Cj1tfIqy.js +1 -0
- package/frontend-dist/assets/{MappingList-BDJ3PU77.js → MappingList-B50LdkcQ.js} +1 -1
- package/frontend-dist/assets/{ModelCard-DwWmKLT8.js → ModelCard-CkagDRjG.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-B9Kgw2uZ.js → ModelMappings-CYY5WVkD.js} +1 -1
- package/frontend-dist/assets/Monitor-KiI_k5-9.js +1 -0
- package/frontend-dist/assets/Providers-B4ZZLVlo.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-DWMm3hWL.js +1 -0
- package/frontend-dist/assets/{QuickSetup-DYVI5orW.js → QuickSetup-Bq1fyl-e.js} +1 -1
- package/frontend-dist/assets/RetryRules-mPS_yjl6.js +1 -0
- package/frontend-dist/assets/RouterKeys-CoBoji2w.js +1 -0
- package/frontend-dist/assets/{RovingFocusItem-BzUY2gsB.js → RovingFocusItem-D3o2cZhf.js} +1 -1
- package/frontend-dist/assets/Schedules-B__xbmnO.js +1 -0
- package/frontend-dist/assets/{Settings-P4uUO9v9.js → Settings-BsB8tW2L.js} +1 -1
- package/frontend-dist/assets/{Setup-CXwlz44a.js → Setup-BBBPhjkS.js} +1 -1
- package/frontend-dist/assets/{Switch-BdEmKbpM.js → Switch-C0YcvC2_.js} +1 -1
- package/frontend-dist/assets/TooltipTrigger-CSSSfg9G.js +3 -0
- package/frontend-dist/assets/{VisuallyHiddenInput-CaWxiN_J.js → VisuallyHiddenInput-DbZDRHYC.js} +1 -1
- package/frontend-dist/assets/{button-tBshdyhO.js → button-63IAyx7P.js} +2 -2
- package/frontend-dist/assets/{copy--LcebZO_.js → copy-BhmcyrsA.js} +1 -1
- package/frontend-dist/assets/dialog-D-rFpYq1.js +1 -0
- package/frontend-dist/assets/index-CGtIhjOG.css +1 -0
- package/frontend-dist/assets/{index-DLWlImkm.js → index-DPqLrV5W.js} +1 -1
- package/frontend-dist/assets/proxyEnhancement-B7EOptuN.js +1 -0
- package/frontend-dist/assets/proxyEnhancement-DyutPimF.js +1 -0
- package/frontend-dist/assets/{trash-2-BUsG5SFN.js → trash-2-MCpMHHuO.js} +1 -1
- package/frontend-dist/assets/{useClipboard-DoV11ZLg.js → useClipboard-DROrGh3V.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-_c8HObDe.js → useLogRetention-Dj2-JudQ.js} +1 -1
- package/frontend-dist/index.html +3 -3
- package/package.json +1 -1
- package/dist/db/session-states.d.ts +0 -40
- package/dist/db/session-states.js +0 -37
- package/dist/proxy/enhancement/directive-parser.d.ts +0 -24
- package/dist/proxy/enhancement/directive-parser.js +0 -141
- package/dist/proxy/enhancement/enhancement-handler.d.ts +0 -40
- package/dist/proxy/enhancement/enhancement-handler.js +0 -471
- package/dist/proxy/enhancement/response-cleaner.d.ts +0 -5
- package/dist/proxy/enhancement/response-cleaner.js +0 -82
- package/dist/proxy/patch/router-cleanup.d.ts +0 -13
- package/dist/proxy/patch/router-cleanup.js +0 -64
- package/dist/proxy/response-transform.d.ts +0 -7
- package/dist/proxy/response-transform.js +0 -28
- package/dist/proxy/routing/model-state.d.ts +0 -30
- package/dist/proxy/routing/model-state.js +0 -115
- package/frontend-dist/assets/Logs-DNDdoBjz.js +0 -1
- package/frontend-dist/assets/Monitor-BJeXYuVI.js +0 -1
- package/frontend-dist/assets/Providers-D_kpZeYw.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-CC8DiK4r.js +0 -5
- package/frontend-dist/assets/RetryRules-BYUok9Yi.js +0 -1
- package/frontend-dist/assets/RouterKeys-Byen_qRe.js +0 -1
- package/frontend-dist/assets/Schedules-B8GwTjC6.js +0 -1
- package/frontend-dist/assets/TableHeader-BwbPx6um.js +0 -1
- package/frontend-dist/assets/TooltipTrigger-C_zGJ_WM.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-BqPiKnUw.js +0 -3
- package/frontend-dist/assets/dialog-CFIfggFY.js +0 -1
- package/frontend-dist/assets/index-Cuaqp0bp.css +0 -1
- package/frontend-dist/assets/proxyEnhancement-Ce12tTE4.js +0 -1
- package/frontend-dist/assets/proxyEnhancement-DZAnOVgK.js +0 -1
- /package/frontend-dist/assets/{UnifiedRequestDialog-C4MTxb25.css → TooltipTrigger-C4MTxb25.css} +0 -0
- /package/frontend-dist/assets/{common-BpwAv-lj.js → common-B3qJKqtt.js} +0 -0
- /package/frontend-dist/assets/{common-D96jEq-h.js → common-Dks6EROG.js} +0 -0
- /package/frontend-dist/assets/{constants-CVHpKrRN.js → constants-yM0YwP2s.js} +0 -0
- /package/frontend-dist/assets/{dashboard-DzlE5uZS.js → dashboard-BDCzVaDH.js} +0 -0
- /package/frontend-dist/assets/{dashboard-DVDFmK36.js → dashboard-CX0XIavm.js} +0 -0
- /package/frontend-dist/assets/{login-Sef1i0de.js → login-CO6SrdUn.js} +0 -0
- /package/frontend-dist/assets/{login-BTNL5nN5.js → login-DmmSnjUg.js} +0 -0
- /package/frontend-dist/assets/{logs-B-6cgV12.js → logs-84vguvAn.js} +0 -0
- /package/frontend-dist/assets/{logs-CBRLywRw.js → logs-kHiiojHO.js} +0 -0
- /package/frontend-dist/assets/{mappings-DQRteuwa.js → mappings-BlVL5Io-.js} +0 -0
- /package/frontend-dist/assets/{mappings-Cazz3EF4.js → mappings-CFnc8ote.js} +0 -0
- /package/frontend-dist/assets/{monitor-DeInYpBf.js → monitor-D8Q-8Pyu.js} +0 -0
- /package/frontend-dist/assets/{monitor-Baldqd3x.js → monitor-EZaS3J_S.js} +0 -0
- /package/frontend-dist/assets/{providers-D6mXnhF_.js → providers-IaCJ0SNi.js} +0 -0
- /package/frontend-dist/assets/{providers-CxmHNt5G.js → providers-sxzMxLKs.js} +0 -0
- /package/frontend-dist/assets/{quickSetup-xHkKkA6i.js → quickSetup-B8PU1MnU.js} +0 -0
- /package/frontend-dist/assets/{quickSetup-cCofuCNs.js → quickSetup-C19Dqeqg.js} +0 -0
- /package/frontend-dist/assets/{requestDetail-bH5SerEV.js → requestDetail-C17DBoKU.js} +0 -0
- /package/frontend-dist/assets/{requestDetail-OsCIcS79.js → requestDetail-S8eKxElp.js} +0 -0
- /package/frontend-dist/assets/{retryRules-CToGC6cR.js → retryRules-BjLh7gly.js} +0 -0
- /package/frontend-dist/assets/{retryRules-BXrRL52J.js → retryRules-Dq4CT4FE.js} +0 -0
- /package/frontend-dist/assets/{routerKeys-DbTg4OP1.js → routerKeys-C2qOaTmZ.js} +0 -0
- /package/frontend-dist/assets/{routerKeys-Be7OZCn0.js → routerKeys-Mav64y7d.js} +0 -0
- /package/frontend-dist/assets/{schedules-DRizOKfa.js → schedules-CtbtvVKi.js} +0 -0
- /package/frontend-dist/assets/{schedules-CPV0fmb-.js → schedules-n-vnm7qH.js} +0 -0
- /package/frontend-dist/assets/{settings-DCS-RTKl.js → settings-Br3uhWN6.js} +0 -0
- /package/frontend-dist/assets/{settings-C4zZB9GY.js → settings-CFwQUG5b.js} +0 -0
- /package/frontend-dist/assets/{setup-DmgXvgkY.js → setup-CDQOZeVA.js} +0 -0
- /package/frontend-dist/assets/{setup-CrjgRrYP.js → setup-QxV8Fdng.js} +0 -0
- /package/frontend-dist/assets/{sidebar-CLHUZFGw.js → sidebar-BZdTUdd2.js} +0 -0
- /package/frontend-dist/assets/{sidebar-DwNPVJOB.js → sidebar-SjlkZ2ii.js} +0 -0
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { FastifyPluginCallback } from "fastify";
|
|
2
2
|
import Database from "better-sqlite3";
|
|
3
|
-
import type { StateRegistry } from "../core/registry.js";
|
|
4
3
|
interface ProxyEnhancementOptions {
|
|
5
4
|
db: Database.Database;
|
|
6
|
-
stateRegistry?: StateRegistry;
|
|
7
5
|
}
|
|
8
6
|
export declare const adminProxyEnhancementRoutes: FastifyPluginCallback<ProxyEnhancementOptions>;
|
|
9
7
|
export {};
|
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { setSetting } from "../db/settings.js";
|
|
2
|
+
import { getSetting, setSetting } from "../db/settings.js";
|
|
3
3
|
const UpdateProxyEnhancementSchema = Type.Object({
|
|
4
|
-
claude_code_enabled: Type.Boolean(),
|
|
5
4
|
tool_call_loop_enabled: Type.Boolean(),
|
|
6
5
|
stream_loop_enabled: Type.Boolean(),
|
|
7
6
|
tool_round_limit_enabled: Type.Boolean(),
|
|
8
7
|
});
|
|
9
|
-
const SessionParamsSchema = Type.Object({
|
|
10
|
-
keyId: Type.String(),
|
|
11
|
-
sessionId: Type.String(),
|
|
12
|
-
});
|
|
13
|
-
import { getSessionStates, getSessionHistory, } from "../db/session-states.js";
|
|
14
8
|
export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
15
|
-
const { db
|
|
9
|
+
const { db } = options;
|
|
16
10
|
app.get("/admin/api/proxy-enhancement", async (_request, reply) => {
|
|
17
|
-
const
|
|
11
|
+
const raw = getSetting(db, "proxy_enhancement");
|
|
12
|
+
const defaults = { tool_call_loop_enabled: false, stream_loop_enabled: false, tool_round_limit_enabled: true };
|
|
13
|
+
let config = defaults;
|
|
14
|
+
if (raw) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
config = {
|
|
18
|
+
tool_call_loop_enabled: parsed.tool_call_loop_enabled ?? false,
|
|
19
|
+
stream_loop_enabled: parsed.stream_loop_enabled ?? false,
|
|
20
|
+
tool_round_limit_enabled: parsed.tool_round_limit_enabled ?? true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch { /* eslint-disable-line taste/no-silent-catch -- invalid JSON, return defaults */ }
|
|
24
|
+
}
|
|
18
25
|
return reply.send(config);
|
|
19
26
|
});
|
|
20
27
|
app.put("/admin/api/proxy-enhancement", { schema: { body: UpdateProxyEnhancementSchema } }, async (request, reply) => {
|
|
21
28
|
const body = request.body;
|
|
22
29
|
const config = {
|
|
23
|
-
claude_code_enabled: body.claude_code_enabled,
|
|
24
30
|
tool_call_loop_enabled: body.tool_call_loop_enabled,
|
|
25
31
|
stream_loop_enabled: body.stream_loop_enabled,
|
|
26
32
|
tool_round_limit_enabled: body.tool_round_limit_enabled,
|
|
@@ -28,19 +34,5 @@ export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
|
28
34
|
setSetting(db, "proxy_enhancement", JSON.stringify(config));
|
|
29
35
|
return reply.send({ success: true });
|
|
30
36
|
});
|
|
31
|
-
app.get("/admin/api/session-states", async (_req, reply) => {
|
|
32
|
-
const states = getSessionStates(db);
|
|
33
|
-
return reply.send(states);
|
|
34
|
-
});
|
|
35
|
-
app.get("/admin/api/session-states/:keyId/:sessionId/history", { schema: { params: SessionParamsSchema } }, async (req, reply) => {
|
|
36
|
-
const { keyId, sessionId } = req.params;
|
|
37
|
-
const history = getSessionHistory(db, keyId, sessionId);
|
|
38
|
-
return reply.send(history);
|
|
39
|
-
});
|
|
40
|
-
app.delete("/admin/api/session-states/:keyId/:sessionId", { schema: { params: SessionParamsSchema } }, async (req, reply) => {
|
|
41
|
-
const { keyId, sessionId } = req.params;
|
|
42
|
-
stateRegistry?.deleteModelState(keyId, sessionId);
|
|
43
|
-
return reply.send({ success: true });
|
|
44
|
-
});
|
|
45
37
|
done();
|
|
46
38
|
};
|
package/dist/admin/routes.js
CHANGED
|
@@ -32,7 +32,7 @@ export const adminRoutes = (app, options, done) => {
|
|
|
32
32
|
app.register(adminRouterKeyRoutes, { db: options.db });
|
|
33
33
|
app.register(adminStatsRoutes, { db: options.db });
|
|
34
34
|
app.register(adminMetricsRoutes, { db: options.db });
|
|
35
|
-
app.register(adminProxyEnhancementRoutes, { db: options.db
|
|
35
|
+
app.register(adminProxyEnhancementRoutes, { db: options.db });
|
|
36
36
|
app.register(adminMonitorRoutes, { tracker: options.tracker });
|
|
37
37
|
app.register(adminSettingsRoutes, { db: options.db, logsDir: options.logsDir });
|
|
38
38
|
app.register(adminImportExportRoutes, { db: options.db, stateRegistry: options.stateRegistry, pluginRegistry: options.pluginRegistry });
|
|
@@ -9,10 +9,8 @@ const CONFIG_TABLES = [
|
|
|
9
9
|
"retry_rules",
|
|
10
10
|
"router_keys",
|
|
11
11
|
"settings",
|
|
12
|
-
"session_model_states",
|
|
13
12
|
"schedules",
|
|
14
13
|
"provider_model_info",
|
|
15
|
-
"session_model_history",
|
|
16
14
|
"provider_transform_rules",
|
|
17
15
|
];
|
|
18
16
|
// settings 表按 key 列的值过滤,不覆盖本地安全敏感配置
|
|
@@ -129,7 +127,6 @@ export const adminImportExportRoutes = (app, options, done) => {
|
|
|
129
127
|
stateRegistry.refreshRetryRules();
|
|
130
128
|
// 清除旧的 semaphore/adaptive/tracker 配置,按导入后的 DB 数据全量重建
|
|
131
129
|
stateRegistry.removeAllProviders();
|
|
132
|
-
stateRegistry.clearModelState();
|
|
133
130
|
stateRegistry.reinitializeProviders();
|
|
134
131
|
// 刷新 transform plugin 缓存(从 DB 重新加载规则 + 扫描插件目录)
|
|
135
132
|
if (options.pluginRegistry) {
|
package/dist/core/constants.d.ts
CHANGED
package/dist/core/constants.js
CHANGED
package/dist/core/registry.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { ConcurrencyConfig } from "./types.js";
|
|
2
2
|
export type { ConcurrencyConfig };
|
|
3
|
-
export interface EnhancementConfig {
|
|
4
|
-
claude_code_enabled: boolean;
|
|
5
|
-
tool_call_loop_enabled: boolean;
|
|
6
|
-
stream_loop_enabled: boolean;
|
|
7
|
-
}
|
|
8
3
|
/** Provider 自适应/手动并发配置(DB 字段) */
|
|
9
4
|
export interface ProviderConcurrencyParams {
|
|
10
5
|
adaptive_enabled: number;
|
|
@@ -26,12 +21,6 @@ export interface StateRegistry {
|
|
|
26
21
|
active: number;
|
|
27
22
|
queued: number;
|
|
28
23
|
};
|
|
29
|
-
/** 清空所有会话模型状态(modelState.clearAll) */
|
|
30
|
-
clearModelState(): void;
|
|
31
|
-
/** 删除指定会话模型状态(modelState.delete) */
|
|
32
|
-
deleteModelState(keyId: string, sessionId: string): void;
|
|
33
|
-
/** 读取 proxy enhancement 配置 */
|
|
34
|
-
getEnhancementConfig(): EnhancementConfig;
|
|
35
24
|
/** 同步 provider 的自适应并发配置(AdaptiveController.syncProvider) */
|
|
36
25
|
syncAdaptiveProvider(providerId: string, params: ProviderConcurrencyParams): void;
|
|
37
26
|
/** 移除 provider 的自适应并发状态(AdaptiveController.remove) */
|
package/dist/db/index.d.ts
CHANGED
|
@@ -16,8 +16,6 @@ export { getStats } from "./stats.js";
|
|
|
16
16
|
export type { Stats } from "./stats.js";
|
|
17
17
|
export { getSetting, setSetting, isInitialized } from "./settings.js";
|
|
18
18
|
export { getDbMaxSizeMb, setDbMaxSizeMb, getLogTableMaxSizeMb, setLogTableMaxSizeMb, } from "./settings.js";
|
|
19
|
-
export { getSessionStates, getSessionState, getSessionHistory, upsertSessionState, insertSessionHistory, deleteSessionState, } from "./session-states.js";
|
|
20
|
-
export type { SessionModelState, SessionModelHistory, UpsertSessionStateInput, InsertSessionHistoryInput } from "./session-states.js";
|
|
21
19
|
export { insertWindow, getLatestWindow, getWindowsInRange, getWindowUsage, } from "./usage-windows.js";
|
|
22
20
|
export type { UsageWindow, WindowUsage } from "./usage-windows.js";
|
|
23
21
|
export { getModelContextWindowOverride, getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider, getAllModelInfo, } from "./model-info.js";
|
package/dist/db/index.js
CHANGED
|
@@ -96,7 +96,6 @@ export { getMetricsSummary, getMetricsTimeseries, insertMetrics } from "./metric
|
|
|
96
96
|
export { getStats } from "./stats.js";
|
|
97
97
|
export { getSetting, setSetting, isInitialized } from "./settings.js";
|
|
98
98
|
export { getDbMaxSizeMb, setDbMaxSizeMb, getLogTableMaxSizeMb, setLogTableMaxSizeMb, } from "./settings.js";
|
|
99
|
-
export { getSessionStates, getSessionState, getSessionHistory, upsertSessionState, insertSessionHistory, deleteSessionState, } from "./session-states.js";
|
|
100
99
|
export { insertWindow, getLatestWindow, getWindowsInRange, getWindowUsage, } from "./usage-windows.js";
|
|
101
100
|
export { getModelContextWindowOverride, getModelInfoForProvider, setModelInfoForProvider, deleteAllModelInfoForProvider, getAllModelInfo, } from "./model-info.js";
|
|
102
101
|
export { getSchedulesByGroup, getActiveSchedulesForGroup, getScheduleById, getAllSchedules, createSchedule, updateSchedule, deleteSchedule, deleteSchedulesByGroup, } from "./schedules.js";
|
package/dist/index.js
CHANGED
|
@@ -22,9 +22,7 @@ import { adminRoutes } from "./admin/routes.js";
|
|
|
22
22
|
import { RetryRuleMatcher } from "./proxy/orchestration/retry-rules.js";
|
|
23
23
|
import { PluginRegistry } from "./proxy/transform/plugin-registry.js";
|
|
24
24
|
import { SemaphoreManager, AdaptiveController } from "@llm-router/core/concurrency";
|
|
25
|
-
import { loadEnhancementConfig } from "./proxy/routing/enhancement-config.js";
|
|
26
25
|
import { RequestTracker } from "@llm-router/core/monitor";
|
|
27
|
-
import { modelState } from "./proxy/routing/model-state.js";
|
|
28
26
|
import { UsageWindowTracker } from "./proxy/routing/usage-window-tracker.js";
|
|
29
27
|
import { SessionTracker, DEFAULT_LOOP_PREVENTION_CONFIG } from "@llm-router/core/loop-prevention";
|
|
30
28
|
import { scheduleLogCleanup } from "./db/log-cleaner.js";
|
|
@@ -206,8 +204,6 @@ export async function buildApp(options) {
|
|
|
206
204
|
? null
|
|
207
205
|
: new LogFileWriter(logsDir, { enabled: getDetailLogEnabled(db) });
|
|
208
206
|
container.register(SERVICE_KEYS.logFileWriter, () => logFileWriter);
|
|
209
|
-
// 注入 DB 到 modelState 单例,启用会话级持久化
|
|
210
|
-
modelState.init(db);
|
|
211
207
|
// 注册 AdaptiveController(依赖已注册的 semaphoreManager)
|
|
212
208
|
container.register(SERVICE_KEYS.adaptiveController, (c) => {
|
|
213
209
|
const ac = new AdaptiveController(c.resolve(SERVICE_KEYS.semaphoreManager), app.log);
|
|
@@ -240,9 +236,6 @@ export async function buildApp(options) {
|
|
|
240
236
|
removeProvider: (providerId) => semaphoreManager.remove(providerId),
|
|
241
237
|
removeAllProviders: () => semaphoreManager.removeAll(),
|
|
242
238
|
getProviderStatus: (providerId) => semaphoreManager.getStatus(providerId),
|
|
243
|
-
clearModelState: () => modelState.clearAll(),
|
|
244
|
-
deleteModelState: (keyId, sessionId) => modelState.delete(keyId, sessionId),
|
|
245
|
-
getEnhancementConfig: () => loadEnhancementConfig(db),
|
|
246
239
|
syncAdaptiveProvider: (providerId, cfg) => adaptiveController.syncProvider(providerId, cfg),
|
|
247
240
|
removeAdaptiveProvider: (providerId) => adaptiveController.remove(providerId),
|
|
248
241
|
getAdaptiveStatus: (providerId) => adaptiveController.getStatus(providerId),
|
|
@@ -293,7 +286,6 @@ export async function buildApp(options) {
|
|
|
293
286
|
tracker.stopPushInterval();
|
|
294
287
|
// 关闭所有 SSE 长连接,防止 app.close() 因 hijack 的连接无限等待
|
|
295
288
|
tracker.closeAllClients();
|
|
296
|
-
modelState.clearAll();
|
|
297
289
|
semaphoreManager.removeAll();
|
|
298
290
|
const sessionTracker = container.resolve(SERVICE_KEYS.sessionTracker);
|
|
299
291
|
sessionTracker.stop();
|
|
@@ -1,4 +1 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type { EnhancementResult, EnhancementMeta, RouterKeyInfo } from "./enhancement-handler.js";
|
|
3
|
-
export { parseDirective, parseToolResult, TOOL_USE_ID_PREFIX, TOOL_USE_ID_PROVIDER_PREFIX } from "./directive-parser.js";
|
|
4
|
-
export { cleanRouterResponses } from "./response-cleaner.js";
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export {};
|
|
2
|
+
// enhancement 模块已清空 — select-model 功能已整体移除
|
|
3
|
+
// 保留空模块以避免其他文件的 import 断裂(如有遗漏引用会在此暴露)
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import fp from "fastify-plugin";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { decrypt } from "../../utils/crypto.js";
|
|
6
|
-
import { proxyGetRequest, createErrorFormatter } from "../proxy-core.js";
|
|
3
|
+
import { getAllProviders, insertRequestLog } from "../../db/index.js";
|
|
4
|
+
import { createErrorFormatter } from "../proxy-core.js";
|
|
7
5
|
import { handleProxyRequest } from "./proxy-handler.js";
|
|
8
6
|
import { createOrchestrator } from "../orchestration/orchestrator.js";
|
|
9
|
-
import {
|
|
7
|
+
import { HTTP_OK, HTTP_BAD_GATEWAY, MS_PER_SECOND } from "../../core/constants.js";
|
|
10
8
|
import { SERVICE_KEYS } from "../../core/container.js";
|
|
11
9
|
const CHAT_COMPLETIONS_PATH = "/v1/chat/completions";
|
|
12
10
|
const MODELS_PATH = "/v1/models";
|
|
@@ -55,54 +53,75 @@ const openaiProxyRaw = (app, opts, done) => {
|
|
|
55
53
|
// 规范路径 + 兼容路径(不带 /v1 前缀)
|
|
56
54
|
app.post(CHAT_COMPLETIONS_PATH, handleChatCompletions);
|
|
57
55
|
app.post(CHAT_COMPLETIONS_COMPAT_PATH, handleChatCompletions);
|
|
56
|
+
const ANTHROPIC_DEFAULT_PAGE_SIZE = 20;
|
|
57
|
+
const ANTHROPIC_MAX_PAGE_SIZE = 1000;
|
|
58
58
|
const handleModels = async (request, reply) => {
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const provider = providers[0];
|
|
76
|
-
const apiKey = decrypt(provider.api_key, getSetting(db, "encryption_key"));
|
|
77
|
-
const cliHdrs = request.headers;
|
|
78
|
-
try {
|
|
79
|
-
const result = await proxyGetRequest(provider, apiKey, cliHdrs, MODELS_PATH);
|
|
80
|
-
insertRequestLog(db, {
|
|
81
|
-
id: randomUUID(), api_type: "openai", model: null,
|
|
82
|
-
provider_id: provider.id, status_code: result.statusCode, latency_ms: Date.now() - startTime, is_stream: 0,
|
|
83
|
-
error_message: null, created_at: new Date().toISOString(),
|
|
84
|
-
client_request: JSON.stringify({ headers: request.headers }),
|
|
85
|
-
router_key_id: request.routerKey?.id ?? null,
|
|
86
|
-
});
|
|
87
|
-
for (const [k, v] of Object.entries(result.headers))
|
|
88
|
-
reply.header(k, v);
|
|
89
|
-
return reply.code(result.statusCode).send(result.body);
|
|
59
|
+
// 聚合所有活跃 provider 的模型列表
|
|
60
|
+
const allProviders = getAllProviders(db).filter(p => p.is_active);
|
|
61
|
+
const modelMeta = new Map();
|
|
62
|
+
for (const p of allProviders) {
|
|
63
|
+
try {
|
|
64
|
+
const models = JSON.parse(p.models || '[]');
|
|
65
|
+
for (const m of models) {
|
|
66
|
+
if (!modelMeta.has(m))
|
|
67
|
+
modelMeta.set(m, { providerName: p.name, createdAt: p.created_at });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// providers.models 有 NOT NULL 约束默认 '[]',此处防御开发期误配的非法 JSON
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
90
74
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
75
|
+
const sortedIds = [...modelMeta.keys()].sort();
|
|
76
|
+
// 根据请求头判断响应格式:Anthropic 客户端发送 anthropic-version 头
|
|
77
|
+
const isAnthropicFormat = !!request.headers['anthropic-version'];
|
|
78
|
+
if (isAnthropicFormat) {
|
|
79
|
+
// Anthropic 格式: { data: [...], has_more, first_id, last_id }
|
|
80
|
+
const query = request.query;
|
|
81
|
+
const limit = Math.min(Math.max(parseInt(query.limit || String(ANTHROPIC_DEFAULT_PAGE_SIZE), 10) || ANTHROPIC_DEFAULT_PAGE_SIZE, 1), ANTHROPIC_MAX_PAGE_SIZE);
|
|
82
|
+
let sliced;
|
|
83
|
+
let hasMore;
|
|
84
|
+
if (query.after_id) {
|
|
85
|
+
const idx = sortedIds.indexOf(query.after_id);
|
|
86
|
+
const start = idx !== -1 ? idx + 1 : 0;
|
|
87
|
+
sliced = sortedIds.slice(start, start + limit);
|
|
88
|
+
hasMore = start + limit < sortedIds.length;
|
|
89
|
+
}
|
|
90
|
+
else if (query.before_id) {
|
|
91
|
+
const endIdx = sortedIds.indexOf(query.before_id);
|
|
92
|
+
const end = endIdx !== -1 ? endIdx : sortedIds.length;
|
|
93
|
+
const start = Math.max(0, end - limit);
|
|
94
|
+
sliced = sortedIds.slice(start, end);
|
|
95
|
+
hasMore = start > 0;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
sliced = sortedIds.slice(0, limit);
|
|
99
|
+
hasMore = limit < sortedIds.length;
|
|
100
|
+
}
|
|
101
|
+
const data = sliced.map(id => ({
|
|
102
|
+
type: 'model',
|
|
103
|
+
id,
|
|
104
|
+
display_name: id,
|
|
105
|
+
created_at: modelMeta.get(id).createdAt,
|
|
106
|
+
}));
|
|
107
|
+
return reply.code(HTTP_OK).send({
|
|
108
|
+
data,
|
|
109
|
+
has_more: hasMore,
|
|
110
|
+
first_id: data.length > 0 ? data[0].id : null,
|
|
111
|
+
last_id: data.length > 0 ? data[data.length - 1].id : null,
|
|
104
112
|
});
|
|
105
113
|
}
|
|
114
|
+
// OpenAI 格式: { object: "list", data: [...] }
|
|
115
|
+
const data = sortedIds.map(id => ({
|
|
116
|
+
id,
|
|
117
|
+
object: 'model',
|
|
118
|
+
created: Math.floor(new Date(modelMeta.get(id).createdAt).getTime() / MS_PER_SECOND),
|
|
119
|
+
owned_by: modelMeta.get(id).providerName,
|
|
120
|
+
}));
|
|
121
|
+
return reply.code(HTTP_OK).send({
|
|
122
|
+
object: 'list',
|
|
123
|
+
data,
|
|
124
|
+
});
|
|
106
125
|
};
|
|
107
126
|
// 规范路径 + 兼容路径
|
|
108
127
|
app.get(MODELS_PATH, handleModels);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { HTTP_UNPROCESSABLE_ENTITY } from "../../core/constants.js";
|
|
3
|
-
import { getProviderById, insertRequestLog,
|
|
3
|
+
import { getProviderById, insertRequestLog, updateLogStreamContent, updateLogClientStatus } from "../../db/index.js";
|
|
4
4
|
import { decrypt } from "../../utils/crypto.js";
|
|
5
5
|
import { getSetting } from "../../db/settings.js";
|
|
6
6
|
import { resolveMapping } from "../routing/mapping-resolver.js";
|
|
7
|
-
import { applyEnhancement } from "../enhancement/enhancement-handler.js";
|
|
8
7
|
import { SemaphoreQueueFullError, SemaphoreTimeoutError } from "@llm-router/core";
|
|
9
|
-
import { logResilienceResult, collectTransportMetrics,
|
|
8
|
+
import { logResilienceResult, collectTransportMetrics, sanitizeHeadersForLog, } from "../proxy-logging.js";
|
|
10
9
|
import { buildUpstreamHeaders, buildUpstreamUrl } from "../proxy-core.js";
|
|
11
10
|
import { ProviderSwitchNeeded } from "../types.js";
|
|
12
11
|
import { insertRejectedLog } from "../log-helpers.js";
|
|
@@ -16,7 +15,6 @@ import { applyOverflowRedirect } from "../routing/overflow.js";
|
|
|
16
15
|
import { parseModels } from "../../config/model-context.js";
|
|
17
16
|
import { applyProviderPatches } from "../patch/index.js";
|
|
18
17
|
import { PipelineSnapshot } from "../pipeline-snapshot.js";
|
|
19
|
-
import { maybeInjectModelInfoTag } from "../response-transform.js";
|
|
20
18
|
import { applyToolRoundLimit } from "../patch/tool-round-limiter.js";
|
|
21
19
|
import { loadEnhancementConfig } from "../routing/enhancement-config.js";
|
|
22
20
|
import { getTransportStatusCode, serializeBlocksForStorage, extractLastToolUse } from "./proxy-handler-utils.js";
|
|
@@ -30,7 +28,7 @@ function rejectAndReply(reply, params, error, errorMessage, providerId) {
|
|
|
30
28
|
statusCode: error.statusCode, errorMessage, startTime: params.startTime,
|
|
31
29
|
isStream: params.isStream, routerKeyId: params.routerKeyId,
|
|
32
30
|
originalBody: params.originalBody, clientHeaders: params.clientHeaders,
|
|
33
|
-
providerId, originalModel:
|
|
31
|
+
providerId, originalModel: null,
|
|
34
32
|
isFailover: params.isFailover, originalRequestId: params.originalRequestId,
|
|
35
33
|
sessionId: params.sessionId, pipelineSnapshot: params.pipelineSnapshot,
|
|
36
34
|
matcher: params.matcher, logFileWriter: params.logFileWriter,
|
|
@@ -57,13 +55,10 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
57
55
|
const reqBody = request.body;
|
|
58
56
|
const rawBody = reqBody ? JSON.parse(JSON.stringify(reqBody)) : {};
|
|
59
57
|
const snapshot = new PipelineSnapshot();
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
snapshot.add({ stage: "enhancement", router_tags_stripped: enhMeta.router_tags_stripped, directive: enhMeta.directive });
|
|
63
|
-
// tool round limiter 阶段 — 检测连续工具调用轮数,超阈值时注入提示词
|
|
64
|
-
let pipelineBody = enhancedBody;
|
|
58
|
+
let pipelineBody = request.body;
|
|
59
|
+
const effectiveModel = clientModel;
|
|
65
60
|
if (enhancementConfig.tool_round_limit_enabled) {
|
|
66
|
-
const roundResult = applyToolRoundLimit(
|
|
61
|
+
const roundResult = applyToolRoundLimit(pipelineBody, apiType);
|
|
67
62
|
if (roundResult.injected) {
|
|
68
63
|
pipelineBody = roundResult.body;
|
|
69
64
|
snapshot.add({ stage: "tool_round_limit", action: "inject_warning", rounds: roundResult.rounds });
|
|
@@ -110,14 +105,12 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
110
105
|
}
|
|
111
106
|
}
|
|
112
107
|
}
|
|
113
|
-
if (interceptResponse)
|
|
114
|
-
return handleIntercept(deps.db, apiType, request, reply, interceptResponse, clientModel, sessionId, snapshot.toJSON(), matcher, logFileWriter);
|
|
115
108
|
return executeFailoverLoop({
|
|
116
109
|
request, reply, apiType, upstreamPath, errors, deps, options,
|
|
117
|
-
effectiveModel,
|
|
110
|
+
effectiveModel,
|
|
118
111
|
pipelineBody,
|
|
119
112
|
rawBody,
|
|
120
|
-
baseStages:
|
|
113
|
+
baseStages: [],
|
|
121
114
|
sessionId,
|
|
122
115
|
streamLoopEnabled: enhancementConfig.stream_loop_enabled,
|
|
123
116
|
matcher, logFileWriter,
|
|
@@ -125,7 +118,7 @@ export async function handleProxyRequest(request, reply, apiType, upstreamPath,
|
|
|
125
118
|
}
|
|
126
119
|
// ---------- Failover loop ----------
|
|
127
120
|
async function executeFailoverLoop(ctx) {
|
|
128
|
-
const { request, reply, apiType, upstreamPath, errors, deps, options, effectiveModel,
|
|
121
|
+
const { request, reply, apiType, upstreamPath, errors, deps, options, effectiveModel, pipelineBody, rawBody, baseStages, sessionId, streamLoopEnabled, matcher, logFileWriter } = ctx;
|
|
129
122
|
const tracker = deps.container.resolve(SERVICE_KEYS.tracker);
|
|
130
123
|
const usageWindowTracker = deps.container.resolve(SERVICE_KEYS.usageWindowTracker);
|
|
131
124
|
const config = getConfig();
|
|
@@ -148,7 +141,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
148
141
|
const iterationSnapshot = new PipelineSnapshot(baseStages);
|
|
149
142
|
const rCtx = {
|
|
150
143
|
db: deps.db, logId, apiType, model: effectiveModel,
|
|
151
|
-
startTime, isStream, routerKeyId, originalBody: rawBody, clientHeaders: cliHdrs,
|
|
144
|
+
startTime, isStream, routerKeyId, originalBody: rawBody, clientHeaders: cliHdrs,
|
|
152
145
|
isFailover: isFailoverIteration, originalRequestId: isFailoverIteration ? rootLogId : null, sessionId,
|
|
153
146
|
pipelineSnapshot: iterationSnapshot.toJSON(),
|
|
154
147
|
matcher, logFileWriter,
|
|
@@ -285,7 +278,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
285
278
|
} : undefined;
|
|
286
279
|
const transportFn = buildTransportFn({
|
|
287
280
|
provider, apiKey, body: patchedBody, cliHdrs, reply, upstreamPath: effectiveUpstreamPath, apiType: effectiveApiType,
|
|
288
|
-
isStream, startTime, logId, effectiveModel,
|
|
281
|
+
isStream, startTime, logId, effectiveModel,
|
|
289
282
|
streamTimeoutMs: config.STREAM_TIMEOUT_MS, tracker, matcher, request,
|
|
290
283
|
streamLoopEnabled, formatTransform, responseTransform, injectedHeaders,
|
|
291
284
|
});
|
|
@@ -294,7 +287,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
294
287
|
const resilienceResult = await deps.orchestrator.handle(request, reply, apiType, { resolved, provider, clientModel: effectiveModel, isStream, trackerId: logId, sessionId, clientRequest: clientReq, upstreamRequest: upstreamReqBase, concurrencyOverride }, { retryBaseDelayMs: config.RETRY_BASE_DELAY_MS, isFailover, ruleMatcher: matcher, transportFn });
|
|
295
288
|
const lastLogId = logResilienceResult(deps.db, {
|
|
296
289
|
apiType, model: effectiveModel, providerId: provider.id, isStream,
|
|
297
|
-
clientReq, upstreamReqBase, logId, routerKeyId, originalModel, sessionId,
|
|
290
|
+
clientReq, upstreamReqBase, logId, routerKeyId, originalModel: null, sessionId,
|
|
298
291
|
failover: { isFailoverIteration, rootLogId: rootLogId },
|
|
299
292
|
pipelineSnapshot,
|
|
300
293
|
matcher, logFileWriter,
|
|
@@ -329,12 +322,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
329
322
|
const tr = resilienceResult.result;
|
|
330
323
|
if (tr.kind === "success") {
|
|
331
324
|
// response transform — 注入 model info tag
|
|
332
|
-
|
|
333
|
-
if (respMeta.model_info_tag_injected) {
|
|
334
|
-
iterationSnapshot.add({ stage: "response_transform", model_info_tag_injected: true });
|
|
335
|
-
updateLogPipelineSnapshot(deps.db, lastLogId, iterationSnapshot.toJSON());
|
|
336
|
-
}
|
|
337
|
-
return reply.code(tr.statusCode).send(finalBody);
|
|
325
|
+
return reply.code(tr.statusCode).send(tr.body);
|
|
338
326
|
}
|
|
339
327
|
if (tr.kind === "throw" || (tr.kind === "error" && tr.statusCode >= HTTP_ERROR_THRESHOLD)) {
|
|
340
328
|
const err = errors.upstreamConnectionFailed();
|
|
@@ -355,7 +343,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
355
343
|
const fakeResult = e.lastResult ?? { kind: "throw", error: new Error("provider switch") };
|
|
356
344
|
logResilienceResult(deps.db, {
|
|
357
345
|
apiType, model: effectiveModel, providerId: provider.id, isStream,
|
|
358
|
-
clientReq, upstreamReqBase, logId, routerKeyId, originalModel, sessionId,
|
|
346
|
+
clientReq, upstreamReqBase, logId, routerKeyId, originalModel: null, sessionId,
|
|
359
347
|
failover: { isFailoverIteration, rootLogId: rootLogId },
|
|
360
348
|
pipelineSnapshot,
|
|
361
349
|
matcher, logFileWriter,
|
|
@@ -379,7 +367,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
379
367
|
error_message: errMsg || "Upstream connection failed", created_at: new Date().toISOString(),
|
|
380
368
|
client_request: clientReq, upstream_request: upstreamReqBase,
|
|
381
369
|
is_failover: isFailoverIteration ? 1 : 0, original_request_id: isFailoverIteration ? rootLogId : null,
|
|
382
|
-
router_key_id: routerKeyId, original_model:
|
|
370
|
+
router_key_id: routerKeyId, original_model: null,
|
|
383
371
|
session_id: sessionId,
|
|
384
372
|
pipeline_snapshot: pipelineSnapshot,
|
|
385
373
|
}, (matcher || logFileWriter) ? {
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
export interface DirectiveMeta {
|
|
2
|
-
type: "select_model" | "router_model" | "router_command";
|
|
3
|
-
value: string;
|
|
4
|
-
}
|
|
5
1
|
export type StageRecord = {
|
|
6
|
-
stage: "enhancement";
|
|
7
|
-
router_tags_stripped: number;
|
|
8
|
-
directive: DirectiveMeta | null;
|
|
9
|
-
} | {
|
|
10
2
|
stage: "tool_round_limit";
|
|
11
3
|
action: string;
|
|
12
4
|
rounds: number;
|
|
@@ -28,9 +20,6 @@ export type StageRecord = {
|
|
|
28
20
|
} | {
|
|
29
21
|
stage: "provider_patch";
|
|
30
22
|
types: string[];
|
|
31
|
-
} | {
|
|
32
|
-
stage: "response_transform";
|
|
33
|
-
model_info_tag_injected: boolean;
|
|
34
23
|
};
|
|
35
24
|
export declare class PipelineSnapshot {
|
|
36
25
|
private readonly stages;
|
|
@@ -6,13 +6,6 @@ import type { ResilienceAttempt } from "../core/types.js";
|
|
|
6
6
|
import type { TransportResult } from "./types.js";
|
|
7
7
|
/** 日志存储前脱敏 Authorization / x-api-key header,避免 API Key 被持久化 */
|
|
8
8
|
export declare function sanitizeHeadersForLog(headers: Record<string, string>): Record<string, string>;
|
|
9
|
-
export declare function handleIntercept(db: Database.Database, apiType: "openai" | "openai-responses" | "anthropic", request: FastifyRequest, reply: import("fastify").FastifyReply, interceptResponse: {
|
|
10
|
-
statusCode: number;
|
|
11
|
-
body: unknown;
|
|
12
|
-
meta?: unknown;
|
|
13
|
-
}, clientModel: string, sessionId?: string, pipelineSnapshot?: string, matcher?: {
|
|
14
|
-
test: (statusCode: number, body: string) => boolean;
|
|
15
|
-
} | null, logFileWriter?: LogFileWriter | null): import("fastify").FastifyReply;
|
|
16
9
|
export declare function logResilienceResult(db: Database.Database, params: {
|
|
17
10
|
apiType: "openai" | "openai-responses" | "anthropic";
|
|
18
11
|
model: string;
|
|
@@ -17,28 +17,6 @@ export function sanitizeHeadersForLog(headers) {
|
|
|
17
17
|
return sanitized;
|
|
18
18
|
}
|
|
19
19
|
// ---------- Logging helpers (extracted from proxy-core) ----------
|
|
20
|
-
export function handleIntercept(db, apiType, request, reply, interceptResponse, clientModel, sessionId, pipelineSnapshot, matcher, logFileWriter) {
|
|
21
|
-
const logId = randomUUID();
|
|
22
|
-
const isStream = request.body.stream === true;
|
|
23
|
-
const respBody = JSON.stringify(interceptResponse.body);
|
|
24
|
-
const writeContext = (matcher || logFileWriter) ? {
|
|
25
|
-
matcher, logFileWriter, responseBody: respBody,
|
|
26
|
-
} : undefined;
|
|
27
|
-
insertRequestLog(db, {
|
|
28
|
-
id: logId, api_type: apiType, model: clientModel, provider_id: "router",
|
|
29
|
-
status_code: interceptResponse.statusCode, latency_ms: 0,
|
|
30
|
-
is_stream: isStream ? 1 : 0, error_message: null,
|
|
31
|
-
created_at: new Date().toISOString(),
|
|
32
|
-
client_request: JSON.stringify({ headers: request.headers, body: request.body }),
|
|
33
|
-
upstream_request: interceptResponse.meta ? JSON.stringify(interceptResponse.meta) : null,
|
|
34
|
-
upstream_response: JSON.stringify({ statusCode: interceptResponse.statusCode, body: respBody }),
|
|
35
|
-
is_retry: 0, is_failover: 0, original_request_id: null,
|
|
36
|
-
router_key_id: request.routerKey?.id ?? null, original_model: null,
|
|
37
|
-
session_id: sessionId,
|
|
38
|
-
pipeline_snapshot: pipelineSnapshot ?? null,
|
|
39
|
-
}, writeContext);
|
|
40
|
-
return reply.code(interceptResponse.statusCode).send(interceptResponse.body);
|
|
41
|
-
}
|
|
42
20
|
// ---------- New-architecture logging ----------
|
|
43
21
|
export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
44
22
|
const isFailoverIteration = params.failover?.isFailoverIteration ?? false;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getSetting } from "../../db/settings.js";
|
|
2
2
|
const DEFAULT_CONFIG = {
|
|
3
|
-
claude_code_enabled: false,
|
|
4
3
|
tool_call_loop_enabled: false,
|
|
5
4
|
stream_loop_enabled: false,
|
|
6
5
|
tool_round_limit_enabled: true,
|
|
@@ -13,7 +12,6 @@ export function loadEnhancementConfig(db) {
|
|
|
13
12
|
try {
|
|
14
13
|
const parsed = JSON.parse(raw);
|
|
15
14
|
return {
|
|
16
|
-
claude_code_enabled: parsed.claude_code_enabled ?? false,
|
|
17
15
|
tool_call_loop_enabled: parsed.tool_call_loop_enabled ?? false,
|
|
18
16
|
stream_loop_enabled: parsed.stream_loop_enabled ?? false,
|
|
19
17
|
tool_round_limit_enabled: parsed.tool_round_limit_enabled ?? true,
|
|
@@ -5,7 +5,6 @@ import { buildUpstreamHeaders } from "../proxy-core.js";
|
|
|
5
5
|
import { StreamLoopGuard } from "@llm-router/core/loop-prevention";
|
|
6
6
|
import { NGramLoopDetector } from "@llm-router/core/loop-prevention";
|
|
7
7
|
import { UPSTREAM_SUCCESS } from "../types.js";
|
|
8
|
-
import { buildModelInfoTag } from "../enhancement/enhancement-handler.js";
|
|
9
8
|
import { DEFAULT_MAX_RAW as STREAM_CONTENT_MAX_RAW, DEFAULT_MAX_TEXT as STREAM_CONTENT_MAX_TEXT } from "@llm-router/core/monitor";
|
|
10
9
|
const LOOP_DETECTOR_N = 6;
|
|
11
10
|
const LOOP_DETECTOR_WINDOW_SIZE = 1000;
|
|
@@ -70,18 +69,6 @@ export function buildTransportFn(p) {
|
|
|
70
69
|
if (p.responseTransform && "body" in result && result.body) {
|
|
71
70
|
result = { ...result, body: p.responseTransform(result.body) };
|
|
72
71
|
}
|
|
73
|
-
if (p.originalModel && result.kind === "success" && result.statusCode === UPSTREAM_SUCCESS) {
|
|
74
|
-
try {
|
|
75
|
-
const bodyObj = JSON.parse(result.body);
|
|
76
|
-
if (bodyObj.content?.[0]?.text) {
|
|
77
|
-
bodyObj.content[0].text += `\n\n${buildModelInfoTag(p.effectiveModel)}`;
|
|
78
|
-
return { ...result, body: JSON.stringify(bodyObj) };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
p.request.log.warn("Failed to inject model-info tag into non-JSON response");
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
72
|
return result;
|
|
86
73
|
};
|
|
87
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Bt as e,Q as t,Rt as n,Y as r,pt as i,r as a,ut as o}from"./button-
|
|
1
|
+
import{Bt as e,Q as t,Rt as n,Y as r,pt as i,r as a,ut as o}from"./button-63IAyx7P.js";var s=[`data-size`],c=t({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(t){let c=t;return(l,u)=>(o(),r(`div`,{"data-slot":`card`,"data-size":t.size,class:e(n(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=t({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(t){let s=t;return(t,c)=>(o(),r(`div`,{"data-slot":`card-content`,class:e(n(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(t.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Bt as e,Q as t,Rt as n,Y as r,pt as i,r as a,ut as o}from"./button-
|
|
1
|
+
import{Bt as e,Q as t,Rt as n,Y as r,pt as i,r as a,ut as o}from"./button-63IAyx7P.js";var s=t({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(t){let s=t;return(t,c)=>(o(),r(`div`,{"data-slot":`card-header`,class:e(n(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(t.$slots,`default`)],2))}}),c=t({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(t){let s=t;return(t,c)=>(o(),r(`div`,{"data-slot":`card-title`,class:e(n(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(t.$slots,`default`)],2))}});export{s as n,c as t};
|