llm-simple-router 0.6.7 → 0.7.1
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/README.md +69 -0
- package/dist/admin/constants.d.ts +1 -1
- package/dist/admin/constants.js +2 -2
- package/dist/admin/logs.d.ts +2 -0
- package/dist/admin/logs.js +17 -1
- package/dist/admin/providers.d.ts +2 -2
- package/dist/admin/providers.js +29 -16
- package/dist/admin/proxy-enhancement.d.ts +2 -0
- package/dist/admin/proxy-enhancement.js +4 -10
- package/dist/admin/retry-rules.d.ts +2 -2
- package/dist/admin/retry-rules.js +4 -8
- package/dist/admin/routes.d.ts +5 -4
- package/dist/admin/routes.js +7 -7
- package/dist/admin/settings-import-export.d.ts +2 -4
- package/dist/admin/settings-import-export.js +9 -19
- package/dist/admin/settings.d.ts +1 -0
- package/dist/admin/settings.js +29 -1
- package/dist/admin/upgrade.d.ts +1 -0
- package/dist/admin/upgrade.js +37 -4
- package/dist/{constants.d.ts → core/constants.d.ts} +4 -1
- package/dist/{constants.js → core/constants.js} +21 -1
- package/dist/core/container.d.ts +31 -0
- package/dist/core/container.js +41 -0
- package/dist/core/errors.d.ts +26 -0
- package/dist/core/errors.js +42 -0
- package/dist/core/registry.d.ts +43 -0
- package/dist/core/registry.js +3 -0
- package/dist/core/types.d.ts +105 -0
- package/dist/core/types.js +3 -0
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/logs.d.ts +11 -24
- package/dist/db/logs.js +37 -38
- package/dist/db/metrics.js +1 -1
- package/dist/db/migrations/033_add_pipeline_snapshot.sql +1 -0
- package/dist/db/migrations/034_drop_redundant_log_columns.sql +13 -0
- package/dist/db/settings.d.ts +2 -0
- package/dist/db/settings.js +9 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +196 -108
- package/dist/metrics/metrics-extractor.d.ts +1 -24
- package/dist/metrics/metrics-extractor.js +1 -1
- package/dist/metrics/sse-metrics-transform.d.ts +1 -1
- package/dist/middleware/admin-auth.js +4 -0
- package/dist/middleware/auth.js +1 -2
- package/dist/monitor/request-tracker.d.ts +3 -4
- package/dist/monitor/request-tracker.js +6 -16
- package/dist/monitor/runtime-collector.js +1 -1
- package/dist/monitor/types.d.ts +8 -0
- package/dist/proxy/adaptive-controller.d.ts +4 -1
- package/dist/proxy/adaptive-controller.js +5 -0
- package/dist/proxy/enhancement/enhancement-handler.d.ts +19 -3
- package/dist/proxy/enhancement/enhancement-handler.js +80 -28
- package/dist/proxy/enhancement/index.d.ts +1 -0
- package/dist/proxy/handler/anthropic.d.ts +7 -0
- package/dist/proxy/{anthropic.js → handler/anthropic.js} +8 -7
- package/dist/proxy/handler/openai.d.ts +7 -0
- package/dist/proxy/{openai.js → handler/openai.js} +10 -9
- package/dist/proxy/handler/proxy-handler-utils.d.ts +9 -0
- package/dist/proxy/handler/proxy-handler-utils.js +63 -0
- package/dist/proxy/handler/proxy-handler.d.ts +13 -0
- package/dist/proxy/{proxy-handler.js → handler/proxy-handler.js} +104 -120
- package/dist/proxy/log-detail-policy.d.ts +12 -0
- package/dist/proxy/log-detail-policy.js +21 -0
- package/dist/proxy/log-helpers.d.ts +8 -0
- package/dist/proxy/log-helpers.js +16 -4
- package/dist/proxy/loop-prevention/tool-loop-guard.d.ts +1 -1
- package/dist/proxy/loop-prevention/tool-loop-guard.js +9 -12
- package/dist/proxy/{orchestrator.d.ts → orchestration/orchestrator.d.ts} +6 -4
- package/dist/proxy/{orchestrator.js → orchestration/orchestrator.js} +2 -1
- package/dist/proxy/{resilience.d.ts → orchestration/resilience.d.ts} +2 -14
- package/dist/proxy/{resilience.js → orchestration/resilience.js} +2 -2
- package/dist/proxy/{retry-rules.d.ts → orchestration/retry-rules.d.ts} +1 -1
- package/dist/proxy/{retry-rules.js → orchestration/retry-rules.js} +1 -1
- package/dist/proxy/{scope.d.ts → orchestration/scope.d.ts} +3 -3
- package/dist/proxy/{semaphore.d.ts → orchestration/semaphore.d.ts} +7 -15
- package/dist/proxy/{semaphore.js → orchestration/semaphore.js} +12 -26
- package/dist/proxy/patch/index.d.ts +8 -2
- package/dist/proxy/patch/index.js +5 -2
- package/dist/proxy/pipeline-snapshot.d.ts +37 -0
- package/dist/proxy/pipeline-snapshot.js +15 -0
- package/dist/proxy/proxy-core.d.ts +1 -1
- package/dist/proxy/proxy-core.js +1 -1
- package/dist/proxy/proxy-logging.d.ts +10 -2
- package/dist/proxy/proxy-logging.js +23 -9
- package/dist/proxy/response-transform.d.ts +7 -0
- package/dist/proxy/response-transform.js +15 -0
- package/dist/proxy/{enhancement-config.js → routing/enhancement-config.js} +1 -1
- package/dist/proxy/{mapping-resolver.d.ts → routing/mapping-resolver.d.ts} +1 -1
- package/dist/proxy/{mapping-resolver.js → routing/mapping-resolver.js} +1 -1
- package/dist/proxy/{model-state.js → routing/model-state.js} +1 -1
- package/dist/proxy/{overflow.d.ts → routing/overflow.d.ts} +1 -1
- package/dist/proxy/{overflow.js → routing/overflow.js} +3 -3
- package/dist/proxy/{usage-window-tracker.js → routing/usage-window-tracker.js} +3 -3
- package/dist/proxy/{transport.d.ts → transport/http.d.ts} +2 -2
- package/dist/proxy/{transport.js → transport/http.js} +3 -3
- package/dist/proxy/{stream-proxy.d.ts → transport/stream.d.ts} +4 -4
- package/dist/proxy/{stream-proxy.js → transport/stream.js} +25 -7
- package/dist/proxy/{transport-fn.d.ts → transport/transport-fn.d.ts} +5 -5
- package/dist/proxy/{transport-fn.js → transport/transport-fn.js} +11 -9
- package/dist/proxy/types.d.ts +3 -64
- package/dist/proxy/types.js +5 -34
- package/dist/storage/log-file-compressor.d.ts +15 -0
- package/dist/storage/log-file-compressor.js +83 -0
- package/dist/storage/log-file-writer.d.ts +21 -0
- package/dist/storage/log-file-writer.js +103 -0
- package/dist/storage/types.d.ts +16 -0
- package/dist/storage/types.js +5 -0
- package/dist/upgrade/deployment.d.ts +13 -0
- package/dist/upgrade/deployment.js +40 -0
- package/frontend-dist/assets/{CardContent-jQcfCC7J.js → CardContent-CxOF1feY.js} +1 -1
- package/frontend-dist/assets/{CardTitle-BrCTvULL.js → CardTitle-BSEFcEOM.js} +1 -1
- package/frontend-dist/assets/{CascadingModelSelect-BFh67j5d.js → CascadingModelSelect-DTwksDPZ.js} +1 -1
- package/frontend-dist/assets/{Checkbox-Bbt7JpdE.js → Checkbox-RfsERG07.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-DMnEA0qC.js → CollapsibleTrigger-Dsjo7QlC.js} +1 -1
- package/frontend-dist/assets/{Collection-CVk3TPHc.js → Collection-rQ4eIYfa.js} +1 -1
- package/frontend-dist/assets/{Dashboard-Coftbg4B.js → Dashboard-YejfAPiB.js} +1 -1
- package/frontend-dist/assets/{DialogTitle-BbOAZzPQ.js → DialogTitle-DeFTnmgC.js} +1 -1
- package/frontend-dist/assets/{Input-DdHY9q0w.js → Input-CENz_g9t.js} +1 -1
- package/frontend-dist/assets/{Label-DRQv_Dr_.js → Label-BAciBrrd.js} +1 -1
- package/frontend-dist/assets/{Login-SV3ctFnJ.js → Login-DQkYFq7R.js} +1 -1
- package/frontend-dist/assets/{Logs-BG45kX6E.js → Logs-Dol8AX7z.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-DEaBnRU3.js → ModelMappings-VEYW1TrW.js} +1 -1
- package/frontend-dist/assets/{Monitor-ZHOt11n-.js → Monitor-C0r9WefB.js} +1 -1
- package/frontend-dist/assets/{PopoverTrigger-z-Z3EjBk.js → PopoverTrigger-Cyqik5SE.js} +1 -1
- package/frontend-dist/assets/{PopperContent-DPC-6a3n.js → PopperContent-B7IuAHeq.js} +1 -1
- package/frontend-dist/assets/{Providers-DpY6pAcg.js → Providers-D8Z97edN.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-D6KBDXMp.js → ProxyEnhancement-Kn8r2SN6.js} +1 -1
- package/frontend-dist/assets/{RetryRules-DWI7_WLZ.js → RetryRules-F0295m4_.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-CZ1657eX.js → RouterKeys-CFbPtUE_.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-BREE2YEV.js → RovingFocusItem-D291Vjh8.js} +1 -1
- package/frontend-dist/assets/{Schedules-BVPsBRPi.js → Schedules-DWhF3uod.js} +1 -1
- package/frontend-dist/assets/{SelectValue-H8hwQwbk.js → SelectValue-BWlgUZa3.js} +1 -1
- package/frontend-dist/assets/Settings-BnIzEF_k.js +6 -0
- package/frontend-dist/assets/{Setup-yOYNKkOG.js → Setup-BglKyQKq.js} +1 -1
- package/frontend-dist/assets/{Switch-CojD3rTH.js → Switch-DyCR-CPu.js} +1 -1
- package/frontend-dist/assets/{TableHeader-awoHTsWN.js → TableHeader-DVUlBL35.js} +1 -1
- package/frontend-dist/assets/{TabsTrigger-DTKSFj85.js → TabsTrigger-BU1DY-C8.js} +1 -1
- package/frontend-dist/assets/{Teleport-DehYAXud.js → Teleport-BQgusr9g.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-C2dl_dml.js → TooltipTrigger-Bv_QoBns.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-C8A-uSTR.js → UnifiedRequestDialog-f_evI835.js} +2 -2
- package/frontend-dist/assets/{VisuallyHidden-C8oaGi2S.js → VisuallyHidden-Con10z4F.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-BMc813t2.js → VisuallyHiddenInput-yrDtxucb.js} +1 -1
- package/frontend-dist/assets/{alert-dialog-C8TZQmU6.js → alert-dialog-2Db6Z7JQ.js} +1 -1
- package/frontend-dist/assets/arrow-down-WyouvE7T.js +1 -0
- package/frontend-dist/assets/{badge-BVh2WpA5.js → badge-DEhZfeI0.js} +1 -1
- package/frontend-dist/assets/button-Cnkbp_6J.js +12 -0
- package/frontend-dist/assets/check-BuqB5Nyb.js +1 -0
- package/frontend-dist/assets/{copy-DTOecxa9.js → copy-CwqZSuIG.js} +1 -1
- package/frontend-dist/assets/{dialog-kA7AUNoc.js → dialog-CVMKSdPr.js} +1 -1
- package/frontend-dist/assets/{file-text-DzZCFO7y.js → file-text-D0K8Hovo.js} +1 -1
- package/frontend-dist/assets/index-Ct718O93.js +1 -0
- package/frontend-dist/assets/{lib-ClDokUbt.js → lib-H3YI7EK4.js} +1 -1
- package/frontend-dist/assets/loader-circle-Be82FnVY.js +1 -0
- package/frontend-dist/assets/{useClipboard-DU1ne-Jw.js → useClipboard-Cd7k-5Yq.js} +1 -1
- package/frontend-dist/assets/{useFocusGuards-Btmdbg_F.js → useFocusGuards-luoLXnwV.js} +1 -1
- package/frontend-dist/assets/useFormControl-Da4ViGZF.js +1 -0
- package/frontend-dist/assets/{useLogRetention--EGNWXig.js → useLogRetention-DB4Iu6o_.js} +1 -1
- package/frontend-dist/assets/useNonce-DvAdQ48J.js +1 -0
- package/frontend-dist/assets/x-DB22csQl.js +1 -0
- package/frontend-dist/index.html +19 -19
- package/package.json +1 -1
- package/dist/proxy/anthropic.d.ts +0 -19
- package/dist/proxy/openai.d.ts +0 -19
- package/dist/proxy/proxy-handler.d.ts +0 -19
- package/dist/proxy/strategy/types.d.ts +0 -21
- package/dist/proxy/strategy/types.js +0 -1
- package/frontend-dist/assets/Settings-DHYaYRgU.js +0 -6
- package/frontend-dist/assets/arrow-down-D-cQXxau.js +0 -1
- package/frontend-dist/assets/button-N59D1BGa.js +0 -12
- package/frontend-dist/assets/check-dDgrw3T3.js +0 -1
- package/frontend-dist/assets/index-DVTeNVaa.js +0 -1
- package/frontend-dist/assets/loader-circle-DVHRL-38.js +0 -1
- package/frontend-dist/assets/useFormControl-C5Kjziuj.js +0 -1
- package/frontend-dist/assets/useNonce-Cp31yRzV.js +0 -1
- package/frontend-dist/assets/x-DMktsI_w.js +0 -1
- /package/dist/{config.d.ts → config/index.d.ts} +0 -0
- /package/dist/{config.js → config/index.js} +0 -0
- /package/dist/proxy/{scope.js → orchestration/scope.js} +0 -0
- /package/dist/proxy/{enhancement-config.d.ts → routing/enhancement-config.d.ts} +0 -0
- /package/dist/proxy/{model-state.d.ts → routing/model-state.d.ts} +0 -0
- /package/dist/proxy/{usage-window-tracker.d.ts → routing/usage-window-tracker.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -5,42 +5,74 @@ import { existsSync } from "node:fs";
|
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
6
|
import Fastify from "fastify";
|
|
7
7
|
import { insertRequestLog } from "./db/logs.js";
|
|
8
|
-
import { HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR, getProxyApiType } from "./constants.js";
|
|
8
|
+
import { HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR, getProxyApiType } from "./core/constants.js";
|
|
9
9
|
import { API_CODE, apiError, isAdminApiResponse, statusToApiCode } from "./admin/api-response.js";
|
|
10
10
|
const PROVIDER_DEFAULT_QUEUE_TIMEOUT_MS = 5000;
|
|
11
11
|
const PROVIDER_DEFAULT_MAX_QUEUE_SIZE = 100;
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = path.dirname(__filename);
|
|
14
|
-
import { getConfig } from "./config.js";
|
|
15
|
-
import { initDatabase, getAllProviders
|
|
14
|
+
import { getConfig, getBaseConfig } from "./config/index.js";
|
|
15
|
+
import { initDatabase, getAllProviders } from "./db/index.js";
|
|
16
16
|
import { loadRecommendedConfig } from "./config/recommended.js";
|
|
17
17
|
import { authMiddleware } from "./middleware/auth.js";
|
|
18
|
-
import { openaiProxy } from "./proxy/openai.js";
|
|
19
|
-
import { anthropicProxy } from "./proxy/anthropic.js";
|
|
18
|
+
import { openaiProxy } from "./proxy/handler/openai.js";
|
|
19
|
+
import { anthropicProxy } from "./proxy/handler/anthropic.js";
|
|
20
20
|
import { adminRoutes } from "./admin/routes.js";
|
|
21
|
-
import { RetryRuleMatcher } from "./proxy/retry-rules.js";
|
|
22
|
-
import { ProviderSemaphoreManager } from "./proxy/semaphore.js";
|
|
21
|
+
import { RetryRuleMatcher } from "./proxy/orchestration/retry-rules.js";
|
|
22
|
+
import { ProviderSemaphoreManager } from "./proxy/orchestration/semaphore.js";
|
|
23
23
|
import { AdaptiveConcurrencyController } from "./proxy/adaptive-controller.js";
|
|
24
|
+
import { loadEnhancementConfig } from "./proxy/routing/enhancement-config.js";
|
|
24
25
|
import { RequestTracker } from "./monitor/request-tracker.js";
|
|
25
|
-
import { modelState } from "./proxy/model-state.js";
|
|
26
|
-
import { UsageWindowTracker } from "./proxy/usage-window-tracker.js";
|
|
26
|
+
import { modelState } from "./proxy/routing/model-state.js";
|
|
27
|
+
import { UsageWindowTracker } from "./proxy/routing/usage-window-tracker.js";
|
|
27
28
|
import { SessionTracker } from "./proxy/loop-prevention/session-tracker.js";
|
|
28
29
|
import { DEFAULT_LOOP_PREVENTION_CONFIG } from "./proxy/loop-prevention/types.js";
|
|
29
30
|
import { scheduleLogCleanup } from "./db/log-cleaner.js";
|
|
30
31
|
import { scheduleDbSizeMonitor } from "./db/db-size-monitor.js";
|
|
31
32
|
import { startUpgradeChecker, stopUpgradeChecker } from "./admin/upgrade.js";
|
|
32
33
|
import fastifyStatic from "@fastify/static";
|
|
34
|
+
import { ServiceContainer, SERVICE_KEYS } from "./core/container.js";
|
|
35
|
+
import { LogFileWriter } from "./storage/log-file-writer.js";
|
|
36
|
+
import { scheduleLogFileMaintenance } from "./storage/log-file-compressor.js";
|
|
37
|
+
import { getDetailLogEnabled, getLogFileRetentionDays } from "./db/settings.js";
|
|
38
|
+
import { dirname, join } from "node:path";
|
|
39
|
+
/**
|
|
40
|
+
* 共享初始化逻辑 — 启动时和导入配置后都需要调用。
|
|
41
|
+
* 从 DB 读取所有 provider,初始化信号量/自适应并发/tracker 缓存。
|
|
42
|
+
*/
|
|
43
|
+
export function initializeProviderState(db, semaphoreManager, adaptiveController, tracker) {
|
|
44
|
+
const allProviders = getAllProviders(db);
|
|
45
|
+
for (const p of allProviders) {
|
|
46
|
+
if (p.adaptive_enabled) {
|
|
47
|
+
adaptiveController.init(p.id, { max: p.max_concurrency }, {
|
|
48
|
+
queueTimeoutMs: p.queue_timeout_ms,
|
|
49
|
+
maxQueueSize: p.max_queue_size,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else if (p.max_concurrency > 0) {
|
|
53
|
+
semaphoreManager.updateConfig(p.id, {
|
|
54
|
+
maxConcurrency: p.max_concurrency,
|
|
55
|
+
queueTimeoutMs: p.queue_timeout_ms,
|
|
56
|
+
maxQueueSize: p.max_queue_size,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
tracker.updateProviderConfig(p.id, {
|
|
60
|
+
name: p.name,
|
|
61
|
+
maxConcurrency: p.max_concurrency ?? 0,
|
|
62
|
+
queueTimeoutMs: p.queue_timeout_ms ?? PROVIDER_DEFAULT_QUEUE_TIMEOUT_MS,
|
|
63
|
+
maxQueueSize: p.max_queue_size ?? PROVIDER_DEFAULT_MAX_QUEUE_SIZE,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
33
67
|
export async function buildApp(options) {
|
|
34
68
|
const config = options?.config ?? getBaseConfig();
|
|
35
69
|
// 允许外部传入已初始化的 DB(测试用),否则自行创建
|
|
36
70
|
let db;
|
|
37
|
-
let shouldBackfill = false;
|
|
38
71
|
if (options?.db) {
|
|
39
72
|
db = options.db;
|
|
40
73
|
}
|
|
41
74
|
else {
|
|
42
75
|
db = initDatabase(config.DB_PATH);
|
|
43
|
-
shouldBackfill = true;
|
|
44
76
|
}
|
|
45
77
|
const isDev = process.env.NODE_ENV !== "production";
|
|
46
78
|
const MAX_BODY_SIZE_MB = 50;
|
|
@@ -96,19 +128,24 @@ export async function buildApp(options) {
|
|
|
96
128
|
const body = request.body;
|
|
97
129
|
const receivedAt = request.receivedAt;
|
|
98
130
|
const latencyMs = receivedAt ? Date.now() - receivedAt : 0;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
try {
|
|
132
|
+
insertRequestLog(db, {
|
|
133
|
+
id: randomUUID(),
|
|
134
|
+
api_type: proxyApiType,
|
|
135
|
+
model: body?.model || null,
|
|
136
|
+
provider_id: null,
|
|
137
|
+
status_code: status,
|
|
138
|
+
latency_ms: latencyMs,
|
|
139
|
+
is_stream: body?.stream === true ? 1 : 0,
|
|
140
|
+
error_message: fastifyError.message,
|
|
141
|
+
created_at: new Date().toISOString(),
|
|
142
|
+
client_request: JSON.stringify({ headers: request.headers, ...(body ? { body } : {}) }),
|
|
143
|
+
router_key_id: request.routerKey?.id ?? null,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (logErr) {
|
|
147
|
+
request.log.error({ err: logErr }, "Failed to log proxy error to request_logs");
|
|
148
|
+
}
|
|
112
149
|
}
|
|
113
150
|
return reply.code(status).send({ error: { message: fastifyError.message } });
|
|
114
151
|
}
|
|
@@ -125,91 +162,89 @@ export async function buildApp(options) {
|
|
|
125
162
|
if (typeof payload === 'string') {
|
|
126
163
|
try {
|
|
127
164
|
const parsed = JSON.parse(payload);
|
|
128
|
-
if ('code' in parsed)
|
|
165
|
+
if (parsed !== null && typeof parsed === 'object' && 'code' in parsed)
|
|
129
166
|
return payload; // errorHandler 或路由已手动包装
|
|
167
|
+
// 复用已解析结果,避免二次 JSON.parse
|
|
168
|
+
const wrapped = {
|
|
169
|
+
code: API_CODE.SUCCESS,
|
|
170
|
+
message: 'ok',
|
|
171
|
+
data: parsed,
|
|
172
|
+
};
|
|
173
|
+
return JSON.stringify(wrapped);
|
|
130
174
|
}
|
|
131
175
|
catch {
|
|
132
176
|
return payload;
|
|
133
177
|
}
|
|
134
178
|
}
|
|
135
|
-
|
|
136
|
-
const wrapped = {
|
|
137
|
-
code: API_CODE.SUCCESS,
|
|
138
|
-
message: 'ok',
|
|
139
|
-
data: typeof payload === 'string' ? JSON.parse(payload) : payload,
|
|
140
|
-
};
|
|
141
|
-
return JSON.stringify(wrapped);
|
|
179
|
+
return payload;
|
|
142
180
|
});
|
|
143
181
|
loadRecommendedConfig();
|
|
144
182
|
startUpgradeChecker(options?.upgradeCheckerOptions);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
183
|
+
const container = new ServiceContainer();
|
|
184
|
+
container.register(SERVICE_KEYS.db, () => db);
|
|
185
|
+
container.register(SERVICE_KEYS.matcher, (c) => { const m = new RetryRuleMatcher(); m.load(c.resolve(SERVICE_KEYS.db)); return m; });
|
|
186
|
+
container.register(SERVICE_KEYS.semaphoreManager, () => new ProviderSemaphoreManager());
|
|
187
|
+
container.register(SERVICE_KEYS.tracker, (c) => {
|
|
188
|
+
const t = new RequestTracker({ semaphoreManager: c.resolve(SERVICE_KEYS.semaphoreManager), logger: app.log });
|
|
189
|
+
t.startPushInterval();
|
|
190
|
+
return t;
|
|
191
|
+
});
|
|
192
|
+
container.register(SERVICE_KEYS.usageWindowTracker, (c) => {
|
|
193
|
+
const uwt = new UsageWindowTracker(c.resolve(SERVICE_KEYS.db));
|
|
194
|
+
uwt.reconcileOnStartup();
|
|
195
|
+
return uwt;
|
|
196
|
+
});
|
|
197
|
+
container.register(SERVICE_KEYS.sessionTracker, () => new SessionTracker(DEFAULT_LOOP_PREVENTION_CONFIG.sessionTracker));
|
|
198
|
+
// 文件日志写入器
|
|
199
|
+
const isMemoryDb = config.DB_PATH === ":memory:";
|
|
200
|
+
const logsDir = isMemoryDb ? "" : join(dirname(config.DB_PATH), "logs");
|
|
201
|
+
// :memory: 模式注册 null,避免 DB 日志记录被 isFileWriter 逻辑抑制
|
|
202
|
+
const logFileWriter = isMemoryDb
|
|
203
|
+
? null
|
|
204
|
+
: new LogFileWriter(logsDir, { enabled: getDetailLogEnabled(db) });
|
|
205
|
+
container.register(SERVICE_KEYS.logFileWriter, () => logFileWriter);
|
|
152
206
|
// 注入 DB 到 modelState 单例,启用会话级持久化
|
|
153
207
|
modelState.init(db);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
208
|
+
// 注册 AdaptiveConcurrencyController(依赖已注册的 semaphoreManager)
|
|
209
|
+
container.register(SERVICE_KEYS.adaptiveController, (c) => {
|
|
210
|
+
const ac = new AdaptiveConcurrencyController(c.resolve(SERVICE_KEYS.semaphoreManager), app.log);
|
|
211
|
+
return ac;
|
|
212
|
+
});
|
|
213
|
+
// 从容器解析所有服务
|
|
214
|
+
const matcher = container.resolve(SERVICE_KEYS.matcher);
|
|
215
|
+
const semaphoreManager = container.resolve(SERVICE_KEYS.semaphoreManager);
|
|
216
|
+
const tracker = container.resolve(SERVICE_KEYS.tracker);
|
|
217
|
+
const usageWindowTracker = container.resolve(SERVICE_KEYS.usageWindowTracker);
|
|
218
|
+
const adaptiveController = container.resolve(SERVICE_KEYS.adaptiveController);
|
|
219
|
+
// Wire adaptive controller to tracker
|
|
160
220
|
tracker.setAdaptiveController(adaptiveController);
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
usageWindowTracker.reconcileOnStartup();
|
|
164
|
-
// Session tracker(工具调用循环检测用),始终创建但检测受 proxy_enhancement 配置控制
|
|
165
|
-
const sessionTracker = new SessionTracker(DEFAULT_LOOP_PREVENTION_CONFIG.sessionTracker);
|
|
166
|
-
// 从 DB 读取已有 provider 的并发配置,初始化信号量管理器和 tracker
|
|
167
|
-
const allProviders = getAllProviders(db);
|
|
168
|
-
for (const p of allProviders) {
|
|
169
|
-
if (p.adaptive_enabled) {
|
|
170
|
-
adaptiveController.init(p.id, { max: p.max_concurrency }, {
|
|
171
|
-
queueTimeoutMs: p.queue_timeout_ms,
|
|
172
|
-
maxQueueSize: p.max_queue_size,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
else if (p.max_concurrency > 0) {
|
|
176
|
-
semaphoreManager.updateConfig(p.id, {
|
|
177
|
-
maxConcurrency: p.max_concurrency,
|
|
178
|
-
queueTimeoutMs: p.queue_timeout_ms,
|
|
179
|
-
maxQueueSize: p.max_queue_size,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
tracker.updateProviderConfig(p.id, {
|
|
183
|
-
name: p.name,
|
|
184
|
-
maxConcurrency: p.max_concurrency ?? 0,
|
|
185
|
-
queueTimeoutMs: p.queue_timeout_ms ?? PROVIDER_DEFAULT_QUEUE_TIMEOUT_MS,
|
|
186
|
-
maxQueueSize: p.max_queue_size ?? PROVIDER_DEFAULT_MAX_QUEUE_SIZE,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
221
|
+
// 从 DB 读取已有 provider 的并发配置,初始化信号量/adaptive/tracker(共享逻辑)
|
|
222
|
+
initializeProviderState(db, semaphoreManager, adaptiveController, tracker);
|
|
189
223
|
app.register(authMiddleware, { db });
|
|
190
|
-
app.register(openaiProxy, {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
matcher,
|
|
195
|
-
semaphoreManager,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
app.register(openaiProxy, { db, container });
|
|
225
|
+
app.register(anthropicProxy, { db, container });
|
|
226
|
+
// StateRegistry — Admin 层通过此接口触发 proxy 层状态刷新,消除 admin→proxy 依赖
|
|
227
|
+
const stateRegistry = {
|
|
228
|
+
refreshRetryRules: () => matcher.load(db),
|
|
229
|
+
updateProviderConcurrency: (providerId, cfg) => semaphoreManager.updateConfig(providerId, cfg),
|
|
230
|
+
removeProvider: (providerId) => semaphoreManager.remove(providerId),
|
|
231
|
+
removeAllProviders: () => semaphoreManager.removeAll(),
|
|
232
|
+
getProviderStatus: (providerId) => semaphoreManager.getStatus(providerId),
|
|
233
|
+
clearModelState: () => modelState.clearAll(),
|
|
234
|
+
deleteModelState: (keyId, sessionId) => modelState.delete(keyId, sessionId),
|
|
235
|
+
getEnhancementConfig: () => loadEnhancementConfig(db),
|
|
236
|
+
syncAdaptiveProvider: (providerId, cfg) => adaptiveController.syncProvider(providerId, cfg),
|
|
237
|
+
removeAdaptiveProvider: (providerId) => adaptiveController.remove(providerId),
|
|
238
|
+
getAdaptiveStatus: (providerId) => adaptiveController.getStatus(providerId),
|
|
239
|
+
reinitializeProviders: () => {
|
|
240
|
+
adaptiveController.removeAll();
|
|
241
|
+
initializeProviderState(db, semaphoreManager, adaptiveController, tracker);
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
// Late-bound close ref — close 函数在 adminRoutes 注册之后才定义,
|
|
245
|
+
// 但 restart API 需要在运行时调用它
|
|
246
|
+
const closeRef = { fn: async () => { } };
|
|
247
|
+
app.register(adminRoutes, { db, stateRegistry, tracker, adaptiveController, logFileWriter, logsDir, closeFn: () => closeRef.fn() });
|
|
213
248
|
// 前端静态文件服务(生产环境)
|
|
214
249
|
const frontendDist = path.resolve(process.env.FRONTEND_DIST || path.join(__dirname, "../frontend-dist"));
|
|
215
250
|
if (existsSync(frontendDist)) {
|
|
@@ -237,26 +272,79 @@ export async function buildApp(options) {
|
|
|
237
272
|
const dbSizeMonitor = scheduleDbSizeMonitor(db, config.DB_PATH, {
|
|
238
273
|
log: app.log,
|
|
239
274
|
});
|
|
275
|
+
let close = async () => {
|
|
276
|
+
stopUpgradeChecker();
|
|
277
|
+
logCleanup.stop();
|
|
278
|
+
dbSizeMonitor.stop();
|
|
279
|
+
tracker.stopPushInterval();
|
|
280
|
+
modelState.clearAll();
|
|
281
|
+
semaphoreManager.removeAll();
|
|
282
|
+
const sessionTracker = container.resolve(SERVICE_KEYS.sessionTracker);
|
|
283
|
+
sessionTracker.stop();
|
|
284
|
+
await app.close();
|
|
285
|
+
db.close();
|
|
286
|
+
};
|
|
287
|
+
// 文件压缩和清理任务(仅非 :memory: 模式)
|
|
288
|
+
if (!isMemoryDb) {
|
|
289
|
+
const logFileMaintenance = scheduleLogFileMaintenance(logsDir, {
|
|
290
|
+
retentionDays: getLogFileRetentionDays(db),
|
|
291
|
+
log: app.log,
|
|
292
|
+
});
|
|
293
|
+
// 注册到 close
|
|
294
|
+
const prevClose = close;
|
|
295
|
+
close = async () => {
|
|
296
|
+
logFileMaintenance.stop();
|
|
297
|
+
await prevClose();
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// 将最终版 close 函数绑定到 late-bound ref(供 restart API 运行时调用)
|
|
301
|
+
closeRef.fn = close;
|
|
240
302
|
return {
|
|
241
303
|
app,
|
|
242
304
|
db,
|
|
243
305
|
usageWindowTracker,
|
|
244
|
-
close
|
|
245
|
-
stopUpgradeChecker();
|
|
246
|
-
logCleanup.stop();
|
|
247
|
-
dbSizeMonitor.stop();
|
|
248
|
-
tracker.stopPushInterval();
|
|
249
|
-
sessionTracker.stop();
|
|
250
|
-
await app.close();
|
|
251
|
-
db.close();
|
|
252
|
-
},
|
|
306
|
+
close,
|
|
253
307
|
};
|
|
254
308
|
}
|
|
255
|
-
// index.ts 自身也需要 getBaseConfig,避免循环依赖
|
|
256
|
-
import { getBaseConfig } from "./config.js";
|
|
257
309
|
export async function main() {
|
|
258
|
-
const { app } = await buildApp();
|
|
310
|
+
const { app, close } = await buildApp();
|
|
259
311
|
const config = getConfig();
|
|
312
|
+
// 全局兜底:防止未捕获异常导致进程崩溃
|
|
313
|
+
process.on("uncaughtException", (err) => {
|
|
314
|
+
try {
|
|
315
|
+
app.log.fatal({ err }, "Uncaught exception");
|
|
316
|
+
/* eslint-disable taste/no-silent-catch -- app.log 可能已崩溃,console 是最后手段 */
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
console.error("FATAL: Uncaught exception:", err);
|
|
320
|
+
}
|
|
321
|
+
/* eslint-enable taste/no-silent-catch */
|
|
322
|
+
close().finally(() => process.exit(1));
|
|
323
|
+
});
|
|
324
|
+
process.on("unhandledRejection", (reason) => {
|
|
325
|
+
try {
|
|
326
|
+
app.log.error({ err: reason instanceof Error ? reason : new Error(String(reason)) }, "Unhandled rejection");
|
|
327
|
+
/* eslint-disable taste/no-silent-catch -- app.log 可能已崩溃,console 是最后手段 */
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
console.error("Unhandled rejection:", reason);
|
|
331
|
+
}
|
|
332
|
+
/* eslint-enable taste/no-silent-catch */
|
|
333
|
+
});
|
|
334
|
+
// 优雅关闭:SIGTERM(systemd/docker stop)和 SIGINT(Ctrl+C)
|
|
335
|
+
const shutdown = async (signal) => {
|
|
336
|
+
try {
|
|
337
|
+
app.log.info(`Received ${signal}, shutting down gracefully...`);
|
|
338
|
+
await close();
|
|
339
|
+
app.log.info("Shutdown complete");
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
app.log.error({ err }, "Error during shutdown");
|
|
343
|
+
}
|
|
344
|
+
process.exit(0);
|
|
345
|
+
};
|
|
346
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
347
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
260
348
|
try {
|
|
261
349
|
await app.listen({ port: config.PORT, host: "0.0.0.0" });
|
|
262
350
|
app.log.info(`Server listening on port ${config.PORT}`);
|
|
@@ -1,28 +1,5 @@
|
|
|
1
|
+
import type { MetricsResult } from "../core/types.js";
|
|
1
2
|
import type { SSEEvent } from "./sse-parser.js";
|
|
2
|
-
export interface MetricsResult {
|
|
3
|
-
input_tokens: number | null;
|
|
4
|
-
output_tokens: number | null;
|
|
5
|
-
cache_creation_tokens: number | null;
|
|
6
|
-
cache_read_tokens: number | null;
|
|
7
|
-
ttft_ms: number | null;
|
|
8
|
-
/** T6 - T0: proxy end-to-end streaming duration */
|
|
9
|
-
total_duration_ms: number | null;
|
|
10
|
-
/** @deprecated Use total_tps instead */
|
|
11
|
-
tokens_per_second: number | null;
|
|
12
|
-
stop_reason: string | null;
|
|
13
|
-
is_complete: number;
|
|
14
|
-
input_tokens_estimated?: number;
|
|
15
|
-
thinking_tokens: number | null;
|
|
16
|
-
/** T3 - T0: request start to last thinking delta */
|
|
17
|
-
thinking_duration_ms: number | null;
|
|
18
|
-
thinking_tps: number | null;
|
|
19
|
-
/** T6 - T3 (thinking) or T6 - T0 (non-thinking) */
|
|
20
|
-
non_thinking_duration_ms: number | null;
|
|
21
|
-
non_thinking_tps: number | null;
|
|
22
|
-
total_tps: number | null;
|
|
23
|
-
text_tokens: number | null;
|
|
24
|
-
tool_use_tokens: number | null;
|
|
25
|
-
}
|
|
26
3
|
export declare class MetricsExtractor {
|
|
27
4
|
private apiType;
|
|
28
5
|
private requestStartTime;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// TODO: 当文件超过 400 行时拆分为 metrics-streaming.ts(流式事件处理 + TPS 计算)和 metrics-extractor.ts(非流式 + 类型)
|
|
2
|
-
import { MS_PER_SECOND } from "../constants.js";
|
|
2
|
+
import { MS_PER_SECOND } from "../core/constants.js";
|
|
3
3
|
import { encode } from "gpt-tokenizer";
|
|
4
4
|
export class MetricsExtractor {
|
|
5
5
|
apiType;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transform, TransformCallback } from "stream";
|
|
2
2
|
import { MetricsExtractor } from "./metrics-extractor.js";
|
|
3
|
-
import type { MetricsResult } from "
|
|
3
|
+
import type { MetricsResult } from "../core/types.js";
|
|
4
4
|
export interface MetricsTransformOptions {
|
|
5
5
|
/** 每次处理 SSE 事件后触发的回调,附带当前指标快照 */
|
|
6
6
|
onMetrics?: (metrics: MetricsResult) => void;
|
|
@@ -53,6 +53,10 @@ export const adminLoginRoutes = (app, options, done) => {
|
|
|
53
53
|
return reply.code(HTTP_UNAUTHORIZED).send(apiError(API_CODE.WRONG_PASSWORD, "Invalid password"));
|
|
54
54
|
}
|
|
55
55
|
const secret = getSetting(options.db, "jwt_secret");
|
|
56
|
+
if (!secret) {
|
|
57
|
+
request.log.error("JWT secret not configured, cannot issue token");
|
|
58
|
+
return reply.code(HTTP_UNAUTHORIZED).send(apiError(API_CODE.TOKEN_INVALID, "JWT secret not configured"));
|
|
59
|
+
}
|
|
56
60
|
const token = jwt.sign({ role: "admin" }, secret, { expiresIn: TOKEN_EXPIRY_SECONDS });
|
|
57
61
|
reply.setCookie("admin_token", token, {
|
|
58
62
|
path: "/admin",
|
package/dist/middleware/auth.js
CHANGED
|
@@ -2,10 +2,9 @@ import { createHash, randomUUID } from "crypto";
|
|
|
2
2
|
import fp from "fastify-plugin";
|
|
3
3
|
import { isInitialized } from "../db/settings.js";
|
|
4
4
|
import { insertRequestLog } from "../db/logs.js";
|
|
5
|
-
import { getProxyApiType } from "../constants.js";
|
|
5
|
+
import { getProxyApiType, HTTP_SERVICE_UNAVAILABLE } from "../core/constants.js";
|
|
6
6
|
const SKIP_PATHS = ["/health", "/admin"];
|
|
7
7
|
const HTTP_UNAUTHORIZED = 401;
|
|
8
|
-
const HTTP_SERVICE_UNAVAILABLE = 503;
|
|
9
8
|
const BEARER_PREFIX_LENGTH = "Bearer ".length;
|
|
10
9
|
function shouldSkipAuth(url) {
|
|
11
10
|
const path = url.split("?")[0];
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { ServerResponse } from "node:http";
|
|
2
2
|
import { StatsAggregator } from "./stats-aggregator.js";
|
|
3
3
|
import { RuntimeCollector } from "./runtime-collector.js";
|
|
4
|
-
import type { ProviderSemaphoreManager } from "../proxy/semaphore.js";
|
|
5
4
|
import type { AdaptiveConcurrencyController } from "../proxy/adaptive-controller.js";
|
|
6
|
-
import type { ActiveRequest, AttemptSnapshot, ProviderConcurrencySnapshot, RuntimeMetrics, StatsSnapshot } from "./types.js";
|
|
5
|
+
import type { ActiveRequest, AttemptSnapshot, ISemaphoreStatus, ProviderConcurrencySnapshot, RuntimeMetrics, StatsSnapshot } from "./types.js";
|
|
7
6
|
export interface TrackerLogger {
|
|
8
7
|
debug(obj: Record<string, unknown>, msg: string): void;
|
|
9
8
|
warn(obj: Record<string, unknown>, msg: string): void;
|
|
@@ -25,7 +24,7 @@ export declare class RequestTracker {
|
|
|
25
24
|
private readonly semaphoreManager?;
|
|
26
25
|
private adaptiveController?;
|
|
27
26
|
constructor(deps?: {
|
|
28
|
-
semaphoreManager?:
|
|
27
|
+
semaphoreManager?: ISemaphoreStatus;
|
|
29
28
|
runtimeCollector?: RuntimeCollector;
|
|
30
29
|
logger?: TrackerLogger;
|
|
31
30
|
});
|
|
@@ -50,7 +49,7 @@ export declare class RequestTracker {
|
|
|
50
49
|
getConcurrency(): ProviderConcurrencySnapshot[];
|
|
51
50
|
getRuntime(): RuntimeMetrics;
|
|
52
51
|
addClient(res: ServerResponse): void;
|
|
53
|
-
/**
|
|
52
|
+
/** 向单个客户端发送当前活跃请求快照(保留 clientRequest 以便前端即时展示) */
|
|
54
53
|
private sendInitialSnapshot;
|
|
55
54
|
removeClient(res: ServerResponse): void;
|
|
56
55
|
startPushInterval(): void;
|
|
@@ -173,13 +173,9 @@ export class RequestTracker {
|
|
|
173
173
|
this.clients.delete(res);
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
|
-
/**
|
|
176
|
+
/** 向单个客户端发送当前活跃请求快照(保留 clientRequest 以便前端即时展示) */
|
|
177
177
|
sendInitialSnapshot(res) {
|
|
178
|
-
const active = this.getActive()
|
|
179
|
-
const copy = { ...req };
|
|
180
|
-
delete copy.clientRequest;
|
|
181
|
-
return copy;
|
|
182
|
-
});
|
|
178
|
+
const active = this.getActive();
|
|
183
179
|
const msg = `event: request_update\ndata: ${JSON.stringify(active)}\n\n`;
|
|
184
180
|
try {
|
|
185
181
|
if (!res.writableEnded)
|
|
@@ -224,17 +220,11 @@ export class RequestTracker {
|
|
|
224
220
|
this.runtimeCollector.stop();
|
|
225
221
|
}
|
|
226
222
|
broadcast(event, data) {
|
|
227
|
-
//
|
|
228
|
-
//
|
|
223
|
+
// request_update: 保留 clientRequest,前端 pending 请求需要即时展示内容
|
|
224
|
+
// request_start: 无需处理,已是原始数据
|
|
225
|
+
// request_complete: strip clientRequest(完成后从 DB 加载详情)
|
|
229
226
|
let payload = data;
|
|
230
|
-
if (event === "
|
|
231
|
-
payload = data.map((req) => {
|
|
232
|
-
const copy = { ...req };
|
|
233
|
-
delete copy.clientRequest;
|
|
234
|
-
return copy;
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
else if (event === "request_complete" && data && typeof data === "object") {
|
|
227
|
+
if (event === "request_complete" && data && typeof data === "object") {
|
|
238
228
|
const copy = { ...data };
|
|
239
229
|
delete copy.clientRequest;
|
|
240
230
|
payload = copy;
|
package/dist/monitor/types.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface ActiveRequest {
|
|
|
29
29
|
clientIp?: string;
|
|
30
30
|
sessionId?: string;
|
|
31
31
|
clientRequest?: string;
|
|
32
|
+
upstreamRequest?: string;
|
|
32
33
|
completedAt?: number;
|
|
33
34
|
}
|
|
34
35
|
export interface AttemptSnapshot {
|
|
@@ -89,6 +90,13 @@ export interface ProviderStats {
|
|
|
89
90
|
count: number;
|
|
90
91
|
}>;
|
|
91
92
|
}
|
|
93
|
+
/** request-tracker 需要的信号量状态查询接口 */
|
|
94
|
+
export interface ISemaphoreStatus {
|
|
95
|
+
getStatus(providerId: string): {
|
|
96
|
+
active: number;
|
|
97
|
+
queued: number;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
92
100
|
export interface RuntimeMetrics {
|
|
93
101
|
uptimeMs: number;
|
|
94
102
|
memoryUsage: NodeJS.MemoryUsage;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderSemaphoreManager } from "./semaphore.js";
|
|
1
|
+
import type { ProviderSemaphoreManager } from "./orchestration/semaphore.js";
|
|
2
2
|
export interface AdaptiveState {
|
|
3
3
|
currentLimit: number;
|
|
4
4
|
probeActive: boolean;
|
|
@@ -31,7 +31,10 @@ export declare class AdaptiveConcurrencyController {
|
|
|
31
31
|
queueTimeoutMs: number;
|
|
32
32
|
maxQueueSize: number;
|
|
33
33
|
}): void;
|
|
34
|
+
/** 移除 provider 的自适应并发状态。调用方还需调用 semaphoreManager.remove() 或 updateConfig() 清理信号量配置。 */
|
|
34
35
|
remove(providerId: string): void;
|
|
36
|
+
/** 清除所有 provider 的自适应并发状态(导入配置后重建前调用) */
|
|
37
|
+
removeAll(): void;
|
|
35
38
|
onRequestComplete(providerId: string, result: AdaptiveResult): void;
|
|
36
39
|
getStatus(providerId: string): AdaptiveState | undefined;
|
|
37
40
|
syncProvider(providerId: string, p: ProviderAdaptiveConfig): void;
|
|
@@ -29,9 +29,14 @@ export class AdaptiveConcurrencyController {
|
|
|
29
29
|
});
|
|
30
30
|
this.syncToSemaphore(providerId);
|
|
31
31
|
}
|
|
32
|
+
/** 移除 provider 的自适应并发状态。调用方还需调用 semaphoreManager.remove() 或 updateConfig() 清理信号量配置。 */
|
|
32
33
|
remove(providerId) {
|
|
33
34
|
this.entries.delete(providerId);
|
|
34
35
|
}
|
|
36
|
+
/** 清除所有 provider 的自适应并发状态(导入配置后重建前调用) */
|
|
37
|
+
removeAll() {
|
|
38
|
+
this.entries.clear();
|
|
39
|
+
}
|
|
35
40
|
onRequestComplete(providerId, result) {
|
|
36
41
|
const entry = this.entries.get(providerId);
|
|
37
42
|
if (!entry)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { FastifyRequest } from "fastify";
|
|
2
1
|
import Database from "better-sqlite3";
|
|
3
|
-
import { type EnhancementConfig } from "../enhancement-config.js";
|
|
2
|
+
import { type EnhancementConfig } from "../routing/enhancement-config.js";
|
|
4
3
|
export interface InterceptResponse {
|
|
5
4
|
statusCode: number;
|
|
6
5
|
body: unknown;
|
|
@@ -10,15 +9,32 @@ export interface InterceptResponse {
|
|
|
10
9
|
detail?: string;
|
|
11
10
|
};
|
|
12
11
|
}
|
|
12
|
+
export interface EnhancementMeta {
|
|
13
|
+
router_tags_stripped: number;
|
|
14
|
+
directive: {
|
|
15
|
+
type: "select_model" | "router_model" | "router_command";
|
|
16
|
+
value: string;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
13
19
|
export interface EnhancementResult {
|
|
20
|
+
body: Record<string, unknown>;
|
|
14
21
|
effectiveModel: string;
|
|
15
22
|
originalModel: string | null;
|
|
16
23
|
interceptResponse: InterceptResponse | null;
|
|
24
|
+
meta: EnhancementMeta;
|
|
25
|
+
}
|
|
26
|
+
/** 调用方传入的 routerKey 信息,从 FastifyRequest.routerKey 解耦 */
|
|
27
|
+
export interface RouterKeyInfo {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
allowed_models: string | null;
|
|
17
31
|
}
|
|
18
32
|
/**
|
|
19
33
|
* 在代理转发前应用代理增强逻辑(指令解析 + 会话记忆 + 模型替换 + 命令拦截)。
|
|
20
34
|
* 仅当 proxy_enhancement.claude_code_enabled 开启时生效。
|
|
35
|
+
*
|
|
36
|
+
* 纯函数:不修改输入 body,返回变换后的新 body + 元数据。
|
|
21
37
|
*/
|
|
22
|
-
export declare function applyEnhancement(db: Database.Database,
|
|
38
|
+
export declare function applyEnhancement(db: Database.Database, body: Record<string, unknown>, clientModel: string, sessionId: string | undefined, routerKey: RouterKeyInfo | undefined, enhancementConfig?: EnhancementConfig): EnhancementResult;
|
|
23
39
|
/** 生成注入到非流式响应中的模型信息标签 */
|
|
24
40
|
export declare function buildModelInfoTag(effectiveModel: string): string;
|