llm-simple-router 0.10.8 → 0.10.10
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.js +2 -0
- package/dist/core/container.d.ts +0 -1
- package/dist/core/container.js +0 -1
- package/dist/core/monitor/request-tracker.d.ts +1 -1
- package/dist/core/monitor/request-tracker.js +14 -5
- package/dist/core/monitor/stream-content-accumulator.d.ts +2 -2
- package/dist/core/monitor/stream-content-accumulator.js +2 -2
- package/dist/core/monitor/stream-extractor.js +1 -1
- package/dist/db/logs.d.ts +0 -8
- package/dist/db/logs.js +2 -23
- package/dist/db/metrics.d.ts +0 -10
- package/dist/db/metrics.js +2 -19
- package/dist/index.js +0 -16
- package/dist/metrics/metrics-extractor.js +6 -5
- package/dist/metrics/sse-metrics-transform.js +2 -0
- package/dist/proxy/handler/failover-loop.js +2 -2
- package/frontend-dist/assets/{CardContent-B4vB3Kxw.js → CardContent-qcSyH2wC.js} +1 -1
- package/frontend-dist/assets/{CardTitle-BSki67ff.js → CardTitle-BiP1PaEJ.js} +1 -1
- package/frontend-dist/assets/{Checkbox-dvUbwMMH.js → Checkbox-DffzDfGN.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-DRIfmabo.js → CollapsibleContent-BLcA_5sz.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-jbSdCpiC.js → CollapsibleTrigger-CFNX8ZWv.js} +1 -1
- package/frontend-dist/assets/{Dashboard-eXf2n3wh.js → Dashboard-DuRjJaT-.js} +1 -1
- package/frontend-dist/assets/{Input-CbiXfK8n.js → Input-D_nV5NLw.js} +1 -1
- package/frontend-dist/assets/{Label-CoK65l1t.js → Label-DWvRM0Of.js} +1 -1
- package/frontend-dist/assets/{Login-NhjpJfPs.js → Login-CgiCloZ_.js} +1 -1
- package/frontend-dist/assets/{Logs-X2RH5tcC.js → Logs-XcxKaNRX.js} +1 -1
- package/frontend-dist/assets/{MappingEntryEditor-CjHMOGsf.js → MappingEntryEditor-DIoQxGfs.js} +1 -1
- package/frontend-dist/assets/{ModelCard-DJwfR5SC.js → ModelCard-Cm7JtPFg.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-C1yFuU0K.js → ModelMappings-CvNdZ4UC.js} +1 -1
- package/frontend-dist/assets/Monitor-_pspFE5H.js +1 -0
- package/frontend-dist/assets/{Providers-Cw9Ba8PT.js → Providers-B__y99yf.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-PXI_P4Da.js → ProxyEnhancement-Bsp6wbOU.js} +1 -1
- package/frontend-dist/assets/{QuickSetup-CAmUIwv2.js → QuickSetup-CLirza5Z.js} +1 -1
- package/frontend-dist/assets/{RetryRules-D2ykGSLa.js → RetryRules-D7TEvRsa.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-BvvL8qcy.js → RouterKeys-xsdh_HcW.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-gP6WU5WD.js → RovingFocusItem-D2UUj3n_.js} +1 -1
- package/frontend-dist/assets/{Schedules-DvfShW7S.js → Schedules-D7GDAZXY.js} +1 -1
- package/frontend-dist/assets/{Settings-BnUEzcvF.js → Settings-BI_ABQ8W.js} +1 -1
- package/frontend-dist/assets/{Setup-BIZZg-XO.js → Setup-C8a8Q9z2.js} +1 -1
- package/frontend-dist/assets/{Switch-CXemfUY5.js → Switch-DhyEtE7A.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-BwLkoBZK.js → TooltipTrigger-BVNmzsHP.js} +1 -1
- package/frontend-dist/assets/{TransformRulesForm-0xoRLIOz.js → TransformRulesForm--5C_xErR.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-CFrJXdaw.js → UnifiedRequestDialog-O6Ld5DZI.js} +2 -2
- package/frontend-dist/assets/{VisuallyHiddenInput-CvUbgFmg.js → VisuallyHiddenInput-CPQ662FS.js} +1 -1
- package/frontend-dist/assets/{button-BqYUfybJ.js → button-BWSfarSC.js} +2 -2
- package/frontend-dist/assets/{copy-AYfsh6Pt.js → copy-DOxEdrz9.js} +1 -1
- package/frontend-dist/assets/{dialog-CqEJe3JV.js → dialog--eSL5k5e.js} +1 -1
- package/frontend-dist/assets/{index-DcD6M87r.js → index-B9huoJLE.js} +2 -2
- package/frontend-dist/assets/{trash-2-BNKlpO30.js → trash-2-CNeuAYsm.js} +1 -1
- package/frontend-dist/assets/{useClipboard-ByGWVweg.js → useClipboard-CnSGwYPF.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-BASEOb38.js → useLogRetention-Dul_BUBe.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/db/log-write-buffer.d.ts +0 -43
- package/dist/db/log-write-buffer.js +0 -91
- package/frontend-dist/assets/Monitor-4HZvjdBX.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { getSetting, setSetting } from "../db/settings.js";
|
|
3
|
+
import { clearEnhancementConfigCache } from "../proxy/routing/enhancement-config.js";
|
|
3
4
|
const UpdateProxyEnhancementSchema = Type.Object({
|
|
4
5
|
tool_call_loop_enabled: Type.Boolean(),
|
|
5
6
|
stream_loop_enabled: Type.Boolean(),
|
|
@@ -35,6 +36,7 @@ export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
|
35
36
|
tool_error_logging_enabled: body.tool_error_logging_enabled,
|
|
36
37
|
};
|
|
37
38
|
setSetting(db, "proxy_enhancement", JSON.stringify(config));
|
|
39
|
+
clearEnhancementConfigCache();
|
|
38
40
|
return reply.send({ success: true });
|
|
39
41
|
});
|
|
40
42
|
done();
|
package/dist/core/container.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ export declare const SERVICE_KEYS: {
|
|
|
10
10
|
readonly pluginRegistry: "pluginRegistry";
|
|
11
11
|
readonly formatRegistry: "formatRegistry";
|
|
12
12
|
readonly logFileWriter: "logFileWriter";
|
|
13
|
-
readonly logWriteBuffer: "logWriteBuffer";
|
|
14
13
|
readonly proxyAgentFactory: "proxyAgentFactory";
|
|
15
14
|
};
|
|
16
15
|
export type ServiceKey = (typeof SERVICE_KEYS)[keyof typeof SERVICE_KEYS];
|
package/dist/core/container.js
CHANGED
|
@@ -67,7 +67,7 @@ export declare class RequestTracker {
|
|
|
67
67
|
getConcurrency(): ProviderConcurrencySnapshot[];
|
|
68
68
|
getRuntime(): RuntimeMetrics;
|
|
69
69
|
addClient(client: SSEClient): void;
|
|
70
|
-
/**
|
|
70
|
+
/** 向单个客户端发送当前活跃请求快照(strip 大字段以减少初始推送带宽) */
|
|
71
71
|
private sendInitialSnapshot;
|
|
72
72
|
removeClient(client: SSEClient): void;
|
|
73
73
|
/** 主动关闭所有 SSE 客户端连接,确保 app.close() 不会因长连接阻塞 */
|
|
@@ -58,7 +58,11 @@ export class RequestTracker {
|
|
|
58
58
|
for (const id of this.streamContentPending) {
|
|
59
59
|
const req = this.activeMap.get(id);
|
|
60
60
|
if (req) {
|
|
61
|
-
updates.push({
|
|
61
|
+
updates.push({
|
|
62
|
+
id,
|
|
63
|
+
totalChars: req.streamContent?.totalChars ?? 0,
|
|
64
|
+
streamMetrics: req.streamMetrics ?? null,
|
|
65
|
+
});
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
this.streamContentPending.clear();
|
|
@@ -125,6 +129,7 @@ export class RequestTracker {
|
|
|
125
129
|
status: wasKilled ? "failed" : result.status,
|
|
126
130
|
completedAt: now,
|
|
127
131
|
attempts: result.attempts ?? req.attempts,
|
|
132
|
+
// 保留 streamContent 最后 snapshot,供前端列表页点击已完成请求时展示
|
|
128
133
|
};
|
|
129
134
|
this.streamContentPending.delete(id);
|
|
130
135
|
this.activeMap.delete(id);
|
|
@@ -238,11 +243,13 @@ export class RequestTracker {
|
|
|
238
243
|
this.clients.delete(client);
|
|
239
244
|
});
|
|
240
245
|
}
|
|
241
|
-
/**
|
|
246
|
+
/** 向单个客户端发送当前活跃请求快照(strip 大字段以减少初始推送带宽) */
|
|
242
247
|
sendInitialSnapshot(client) {
|
|
243
248
|
const active = this.getActive().map((req) => {
|
|
244
249
|
const copy = { ...req };
|
|
245
250
|
delete copy.upstreamRequest;
|
|
251
|
+
delete copy.streamContent;
|
|
252
|
+
delete copy.streamMetrics;
|
|
246
253
|
return copy;
|
|
247
254
|
});
|
|
248
255
|
const msg = `event: request_update\ndata: ${JSON.stringify(active)}\n\n`;
|
|
@@ -306,15 +313,16 @@ export class RequestTracker {
|
|
|
306
313
|
this.runtimeCollector.stop();
|
|
307
314
|
}
|
|
308
315
|
broadcast(event, data) {
|
|
309
|
-
// request_update:
|
|
310
|
-
// request_start:
|
|
311
|
-
// request_complete: strip clientRequest(完成后从 DB 加载详情)
|
|
316
|
+
// request_update: strip clientRequest/upstreamRequest/streamContent/streamMetrics
|
|
317
|
+
// request_start / request_complete: strip clientRequest/upstreamRequest/streamContent,保留 streamMetrics
|
|
312
318
|
let payload = data;
|
|
313
319
|
if (event === "request_update" && Array.isArray(data)) {
|
|
314
320
|
payload = data.map((req) => {
|
|
315
321
|
const copy = { ...req };
|
|
316
322
|
delete copy.clientRequest;
|
|
317
323
|
delete copy.upstreamRequest;
|
|
324
|
+
delete copy.streamContent;
|
|
325
|
+
delete copy.streamMetrics;
|
|
318
326
|
return copy;
|
|
319
327
|
});
|
|
320
328
|
}
|
|
@@ -322,6 +330,7 @@ export class RequestTracker {
|
|
|
322
330
|
const copy = { ...data };
|
|
323
331
|
delete copy.clientRequest;
|
|
324
332
|
delete copy.upstreamRequest;
|
|
333
|
+
delete copy.streamContent;
|
|
325
334
|
payload = copy;
|
|
326
335
|
}
|
|
327
336
|
const msg = `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StreamContentSnapshot } from "./types.js";
|
|
2
|
-
export declare const DEFAULT_MAX_RAW =
|
|
3
|
-
export declare const DEFAULT_MAX_TEXT =
|
|
2
|
+
export declare const DEFAULT_MAX_RAW = 32768;
|
|
3
|
+
export declare const DEFAULT_MAX_TEXT = 16384;
|
|
4
4
|
export declare class StreamContentAccumulator {
|
|
5
5
|
private readonly maxRaw;
|
|
6
6
|
private readonly maxText;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extractStreamText } from "./stream-extractor.js";
|
|
2
|
-
export const DEFAULT_MAX_RAW =
|
|
3
|
-
export const DEFAULT_MAX_TEXT =
|
|
2
|
+
export const DEFAULT_MAX_RAW = 32768;
|
|
3
|
+
export const DEFAULT_MAX_TEXT = 16384;
|
|
4
4
|
export class StreamContentAccumulator {
|
|
5
5
|
maxRaw;
|
|
6
6
|
maxText;
|
|
@@ -16,7 +16,7 @@ export function extractStreamText(line, apiType) {
|
|
|
16
16
|
if (apiType === "openai") {
|
|
17
17
|
const choices = obj.choices;
|
|
18
18
|
const delta = choices?.[0]?.delta;
|
|
19
|
-
const text = delta?.content ?? "";
|
|
19
|
+
const text = delta?.content ?? delta?.reasoning_content ?? "";
|
|
20
20
|
return { text, block: text ? { index: 0, type: "text", content: text } : null };
|
|
21
21
|
}
|
|
22
22
|
if (apiType === "openai-responses") {
|
package/dist/db/logs.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
import type { LogFileWriter } from "../storage/log-file-writer.js";
|
|
3
3
|
import { type RetryMatcher } from "../proxy/log-detail-policy.js";
|
|
4
|
-
import type { LogWriteBuffer } from "./log-write-buffer.js";
|
|
5
4
|
export interface RequestLog {
|
|
6
5
|
id: string;
|
|
7
6
|
api_type: string;
|
|
@@ -55,13 +54,6 @@ export interface LogWriteContext {
|
|
|
55
54
|
logFileWriter?: LogFileWriter | null;
|
|
56
55
|
responseBody?: string | null;
|
|
57
56
|
}
|
|
58
|
-
/** 初始化日志缓冲(buildApp 时调用) */
|
|
59
|
-
export declare function initLogBuffer(buffer: LogWriteBuffer): void;
|
|
60
|
-
/** 停止日志缓冲,同步 flush 剩余数据(close 时调用) */
|
|
61
|
-
export declare function stopLogBuffer(): void;
|
|
62
|
-
/** 原始 DB INSERT 逻辑(无缓冲) */
|
|
63
|
-
declare function rawInsertRequestLog(db: Database.Database, log: RequestLogInsert, writeContext?: LogWriteContext): void;
|
|
64
|
-
export { rawInsertRequestLog };
|
|
65
57
|
export declare function insertRequestLog(db: Database.Database, log: RequestLogInsert, writeContext?: LogWriteContext): void;
|
|
66
58
|
export declare function getRequestLogs(db: Database.Database, options: {
|
|
67
59
|
page: number;
|
package/dist/db/logs.js
CHANGED
|
@@ -10,20 +10,7 @@ const LOG_LIST_SELECT = `rl.id, rl.api_type, rl.model, rl.provider_id, rl.status
|
|
|
10
10
|
rm.input_tokens_estimated, rm.client_type, rm.cache_read_tokens_estimated,
|
|
11
11
|
COALESCE(p.name, rl.provider_id) AS provider_name`;
|
|
12
12
|
const LOG_LIST_JOIN = `LEFT JOIN providers p ON p.id = rl.provider_id LEFT JOIN request_metrics rm ON rm.request_log_id = rl.id`;
|
|
13
|
-
/**
|
|
14
|
-
let logBuffer = null;
|
|
15
|
-
/** 初始化日志缓冲(buildApp 时调用) */
|
|
16
|
-
export function initLogBuffer(buffer) {
|
|
17
|
-
logBuffer = buffer;
|
|
18
|
-
}
|
|
19
|
-
/** 停止日志缓冲,同步 flush 剩余数据(close 时调用) */
|
|
20
|
-
export function stopLogBuffer() {
|
|
21
|
-
if (logBuffer) {
|
|
22
|
-
logBuffer.stop();
|
|
23
|
-
logBuffer = null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/** 原始 DB INSERT 逻辑(无缓冲) */
|
|
13
|
+
/** DB INSERT 逻辑 */
|
|
27
14
|
function rawInsertRequestLog(db, log, writeContext) {
|
|
28
15
|
// 详情保留判定
|
|
29
16
|
const preserveDetail = shouldPreserveDetail(log.status_code, writeContext?.responseBody ?? null, writeContext?.matcher ?? null, !!writeContext?.logFileWriter);
|
|
@@ -32,8 +19,6 @@ function rawInsertRequestLog(db, log, writeContext) {
|
|
|
32
19
|
is_retry, is_failover, original_request_id, router_key_id, original_model, session_id, pipeline_snapshot)
|
|
33
20
|
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, preserveDetail ? (log.client_request ?? null) : null, preserveDetail ? (log.upstream_request ?? null) : null, preserveDetail ? (log.upstream_response ?? null) : 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, log.pipeline_snapshot ?? null);
|
|
34
21
|
}
|
|
35
|
-
// 导出给 LogWriteBuffer 的原始插入函数引用
|
|
36
|
-
export { rawInsertRequestLog };
|
|
37
22
|
export function insertRequestLog(db, log, writeContext) {
|
|
38
23
|
// 文件写入:始终同步调用(WriteStream 内部异步,不阻塞事件循环)
|
|
39
24
|
if (writeContext?.logFileWriter) {
|
|
@@ -49,13 +34,7 @@ export function insertRequestLog(db, log, writeContext) {
|
|
|
49
34
|
pipeline_snapshot: log.pipeline_snapshot ?? null,
|
|
50
35
|
});
|
|
51
36
|
}
|
|
52
|
-
|
|
53
|
-
if (logBuffer) {
|
|
54
|
-
logBuffer.pushLog(log, writeContext);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
rawInsertRequestLog(db, log, writeContext);
|
|
58
|
-
}
|
|
37
|
+
rawInsertRequestLog(db, log, writeContext);
|
|
59
38
|
}
|
|
60
39
|
function buildLogWhereClause(options, baseCondition) {
|
|
61
40
|
let where = baseCondition;
|
package/dist/db/metrics.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
-
import type { LogWriteBuffer } from "./log-write-buffer.js";
|
|
3
2
|
export type MetricsPeriod = "1h" | "5h" | "6h" | "24h" | "7d" | "30d";
|
|
4
3
|
export type MetricsMetric = "ttft" | "tps" | "text_tps" | "thinking_tps" | "tool_use_tps" | "non_thinking_tps" | "total_tps" | "tokens" | "cache_rate" | "request_count" | "input_tokens" | "output_tokens" | "cache_hit_tokens";
|
|
5
4
|
export interface MetricsRow {
|
|
@@ -49,15 +48,6 @@ export type MetricsInsert = {
|
|
|
49
48
|
non_thinking_tps?: number | null;
|
|
50
49
|
total_tps?: number | null;
|
|
51
50
|
};
|
|
52
|
-
/** 设置缓冲实例(由 index.ts buildApp 调用,传入与 logs.ts 共享的 buffer) */
|
|
53
|
-
export declare function setLogBuffer(buffer: LogWriteBuffer): void;
|
|
54
|
-
/** 清除缓冲引用(由 stopLogBuffer 调用) */
|
|
55
|
-
export declare function clearLogBuffer(): void;
|
|
56
|
-
/** 原始 DB INSERT 逻辑(无缓冲) */
|
|
57
|
-
declare function rawInsertMetrics(db: Database.Database, m: MetricsInsert & {
|
|
58
|
-
id: string;
|
|
59
|
-
}): void;
|
|
60
|
-
export { rawInsertMetrics };
|
|
61
51
|
export declare function insertMetrics(db: Database.Database, m: MetricsInsert): string;
|
|
62
52
|
export interface MetricsSummaryRow {
|
|
63
53
|
provider_id: string;
|
package/dist/db/metrics.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { MS_PER_SECOND } from "../core/constants.js";
|
|
3
3
|
import { getCachedStmt } from "./helpers.js";
|
|
4
|
-
/**
|
|
5
|
-
let logBuffer = null;
|
|
6
|
-
/** 设置缓冲实例(由 index.ts buildApp 调用,传入与 logs.ts 共享的 buffer) */
|
|
7
|
-
export function setLogBuffer(buffer) {
|
|
8
|
-
logBuffer = buffer;
|
|
9
|
-
}
|
|
10
|
-
/** 清除缓冲引用(由 stopLogBuffer 调用) */
|
|
11
|
-
export function clearLogBuffer() {
|
|
12
|
-
logBuffer = null;
|
|
13
|
-
}
|
|
14
|
-
/** 原始 DB INSERT 逻辑(无缓冲) */
|
|
4
|
+
/** DB INSERT 逻辑 */
|
|
15
5
|
function rawInsertMetrics(db, m) {
|
|
16
6
|
getCachedStmt(db, `INSERT INTO request_metrics (id, request_log_id, provider_id, backend_model, api_type, router_key_id, status_code,
|
|
17
7
|
input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens, ttft_ms, total_duration_ms, tokens_per_second, stop_reason, is_complete, input_tokens_estimated,
|
|
@@ -20,16 +10,9 @@ function rawInsertMetrics(db, m) {
|
|
|
20
10
|
thinking_tps, total_tps, non_thinking_duration_ms, non_thinking_tps)
|
|
21
11
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(m.id, m.request_log_id, m.provider_id, m.backend_model, m.api_type, m.router_key_id ?? null, m.status_code ?? null, m.input_tokens ?? null, m.output_tokens ?? null, m.cache_creation_tokens ?? null, m.cache_read_tokens ?? null, m.ttft_ms ?? null, m.total_duration_ms ?? null, m.tokens_per_second ?? null, m.stop_reason ?? null, m.is_complete ?? 1, m.input_tokens_estimated ?? 0, m.client_type ?? 'unknown', m.cache_read_tokens_estimated ?? 0, m.thinking_tokens ?? null, m.text_tokens ?? null, m.tool_use_tokens ?? null, m.thinking_duration_ms ?? null, m.thinking_tps ?? null, m.total_tps ?? null, m.non_thinking_duration_ms ?? null, m.non_thinking_tps ?? null);
|
|
22
12
|
}
|
|
23
|
-
// 导出给 LogWriteBuffer 的原始插入函数引用
|
|
24
|
-
export { rawInsertMetrics };
|
|
25
13
|
export function insertMetrics(db, m) {
|
|
26
14
|
const id = randomUUID();
|
|
27
|
-
|
|
28
|
-
logBuffer.pushMetrics({ ...m, id });
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
rawInsertMetrics(db, { ...m, id });
|
|
32
|
-
}
|
|
15
|
+
rawInsertMetrics(db, { ...m, id });
|
|
33
16
|
return id;
|
|
34
17
|
}
|
|
35
18
|
const PERIOD_OFFSET = {
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,6 @@ 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 { initLogBuffer, stopLogBuffer } from "./db/logs.js";
|
|
9
|
-
import { setLogBuffer, clearLogBuffer } from "./db/metrics.js";
|
|
10
|
-
import { LogWriteBuffer } from "./db/log-write-buffer.js";
|
|
11
|
-
import { rawInsertRequestLog } from "./db/logs.js";
|
|
12
|
-
import { rawInsertMetrics } from "./db/metrics.js";
|
|
13
8
|
import { HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR, getProxyApiType } from "./core/constants.js";
|
|
14
9
|
import { API_CODE, apiError, isAdminApiResponse, statusToApiCode } from "./admin/api-response.js";
|
|
15
10
|
const PROVIDER_DEFAULT_QUEUE_TIMEOUT_MS = 5000;
|
|
@@ -219,14 +214,6 @@ export async function buildApp(options) {
|
|
|
219
214
|
? null
|
|
220
215
|
: new LogFileWriter(logsDir, { enabled: getDetailLogEnabled(db) });
|
|
221
216
|
container.register(SERVICE_KEYS.logFileWriter, () => logFileWriter);
|
|
222
|
-
// 日志 DB 写入缓冲(非 :memory: 模式)
|
|
223
|
-
const logWriteBuffer = isMemoryDb
|
|
224
|
-
? null
|
|
225
|
-
: new LogWriteBuffer(db, rawInsertRequestLog, rawInsertMetrics);
|
|
226
|
-
if (logWriteBuffer) {
|
|
227
|
-
initLogBuffer(logWriteBuffer);
|
|
228
|
-
setLogBuffer(logWriteBuffer);
|
|
229
|
-
}
|
|
230
217
|
// 注册 AdaptiveController(依赖已注册的 semaphoreManager)
|
|
231
218
|
container.register(SERVICE_KEYS.adaptiveController, (c) => {
|
|
232
219
|
const ac = new AdaptiveController(c.resolve(SERVICE_KEYS.semaphoreManager), app.log);
|
|
@@ -343,9 +330,6 @@ export async function buildApp(options) {
|
|
|
343
330
|
proxyAgentFactory.invalidateAll();
|
|
344
331
|
const sessionTracker = container.resolve(SERVICE_KEYS.sessionTracker);
|
|
345
332
|
sessionTracker.stop();
|
|
346
|
-
// 同步 flush DB 日志缓冲(在 flush 文件缓冲之前)
|
|
347
|
-
stopLogBuffer();
|
|
348
|
-
clearLogBuffer();
|
|
349
333
|
// Flush LogFileWriter 的 WriteStream 缓冲数据到磁盘
|
|
350
334
|
await logFileWriter?.stop();
|
|
351
335
|
// 等待活跃代理请求自然完成,超时后强制关闭所有连接。
|
|
@@ -256,15 +256,16 @@ export class MetricsExtractor {
|
|
|
256
256
|
const delta = choice.delta;
|
|
257
257
|
if (!this.firstContentReceived &&
|
|
258
258
|
delta &&
|
|
259
|
-
delta.content !== undefined &&
|
|
260
|
-
|
|
259
|
+
((delta.content !== undefined && delta.content !== "") ||
|
|
260
|
+
(delta.reasoning_content !== undefined && delta.reasoning_content !== ""))) {
|
|
261
261
|
this.firstContentReceived = true;
|
|
262
262
|
this.ttftMs = Date.now() - this.requestStartTime;
|
|
263
263
|
this.textStreamStartTime = Date.now();
|
|
264
264
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.
|
|
265
|
+
const contentText = delta?.content || delta?.reasoning_content || "";
|
|
266
|
+
if (contentText && this.textTotalLength < MetricsExtractor.MAX_BUFFER_SIZE) {
|
|
267
|
+
this.textChunks.push(contentText);
|
|
268
|
+
this.textTotalLength += contentText.length;
|
|
268
269
|
}
|
|
269
270
|
if (choice.finish_reason) {
|
|
270
271
|
this.stopReason = choice.finish_reason;
|
|
@@ -103,6 +103,8 @@ export class SSEMetricsTransform extends Transform {
|
|
|
103
103
|
const delta = first.delta;
|
|
104
104
|
if (typeof delta.content === "string")
|
|
105
105
|
return delta.content;
|
|
106
|
+
if (typeof delta.reasoning_content === "string")
|
|
107
|
+
return delta.reasoning_content;
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
catch { /* 非 JSON 数据行,跳过 */ } // eslint-disable-line taste/no-silent-catch
|
|
@@ -393,10 +393,10 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
393
393
|
return reply;
|
|
394
394
|
}
|
|
395
395
|
// 其他未知错误
|
|
396
|
-
const errMsg = e instanceof Error ? e.message :
|
|
396
|
+
const errMsg = e instanceof Error ? e.message : JSON.stringify(e);
|
|
397
397
|
request.log.debug({ logId, error: errMsg, action: "upstream_error" });
|
|
398
398
|
insertRequestLog(db, {
|
|
399
|
-
id:
|
|
399
|
+
id: randomUUID(), api_type: clientApiType,
|
|
400
400
|
model: clientModel, provider_id: provider.id,
|
|
401
401
|
status_code: UPSTREAM_ERROR_STATUS, latency_ms: Date.now() - startTime, is_stream: isStream ? 1 : 0,
|
|
402
402
|
error_message: errMsg || "Upstream connection failed", created_at: new Date().toISOString(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-
|
|
1
|
+
import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-BWSfarSC.js";var s=[`data-size`],c=r({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(r){let c=r;return(l,u)=>(i(),n(`div`,{"data-slot":`card`,"data-size":r.size,class:e(t(o)(`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))},[a(l.$slots,`default`)],10,s))}}),l=r({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-content`,class:e(t(o)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[a(r.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-
|
|
1
|
+
import{Ut as e,Vt as t,Z as n,et as r,ft as i,ht as a,r as o}from"./button-BWSfarSC.js";var s=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-header`,class:e(t(o)(`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))},[a(r.$slots,`default`)],2))}}),c=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-title`,class:e(t(o)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[a(r.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Tt as t,U as n,Vt as r,W as i,Wt as a,X as o,Y as s,_t as c,et as l,ft as u,ht as d,i as f,m as p,nt as m,o as h,q as g,r as _,st as v,x as y}from"./button-
|
|
1
|
+
import{$ as e,Tt as t,U as n,Vt as r,W as i,Wt as a,X as o,Y as s,_t as c,et as l,ft as u,ht as d,i as f,m as p,nt as m,o as h,q as g,r as _,st as v,x as y}from"./button-BWSfarSC.js";import{t as b}from"./VisuallyHiddenInput-CPQ662FS.js";import{t as x}from"./RovingFocusItem-D2UUj3n_.js";import{B as S,G as C,H as w,L as T,Y as E,q as D,ut as O}from"./index-B9huoJLE.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=D(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=D(`CheckboxRoot`),I=l({inheritAttrs:!1,__name:`CheckboxRoot`,props:{defaultValue:{type:null,required:!1},modelValue:{type:null,required:!1,default:void 0},disabled:{type:Boolean,required:!1},value:{type:null,required:!1,default:`on`},id:{type:String,required:!1},trueValue:{type:null,required:!1,default:()=>!0},falseValue:{type:null,required:!1,default:()=>!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`},name:{type:String,required:!1},required:{type:Boolean,required:!1}},emits:[`update:modelValue`],setup(e,{emit:a}){let l=e,m=a,{forwardRef:_,currentElement:y}=h(),S=A(null),T=p(l,`modelValue`,m,{defaultValue:l.defaultValue??l.falseValue,passive:l.modelValue===void 0}),D=g(()=>S?.disabled.value||l.disabled),O=g(()=>E(T.value,l.trueValue)),j=g(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,l.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=l.trueValue:T.value=O.value?l.falseValue:l.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,l.value)){let t=e.findIndex(e=>E(e,l.value));e.splice(t,1)}else e.push(l.value);S.modelValue.value=e}}let I=w(y),L=g(()=>l.id&&y.value?document.querySelector(`[for="${l.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,a)=>(u(),s(c(r(S)?.rovingFocus.value?r(x):r(f)),v(e.$attrs,{id:e.id,ref:r(_),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":r(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":r(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:r(S)?.rovingFocus.value?!D.value:void 0,onKeydown:n(i(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:t(()=>[d(e.$slots,`default`,{modelValue:r(T),state:j.value}),r(I)&&e.name&&!r(S)?(u(),s(r(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):o(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=l({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(n){let{forwardRef:i}=h(),a=P();return(n,o)=>(u(),s(r(T),{present:n.forceMount||r(M)(r(a).state.value)||r(a).state.value===!0},{default:t(()=>[e(r(f),v({ref:r(i),"data-state":r(N)(r(a).state.value),"data-disabled":r(a).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":n.asChild,as:n.as},n.$attrs),{default:t(()=>[d(n.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=l({__name:`Checkbox`,props:{defaultValue:{},modelValue:{},disabled:{type:Boolean},value:{},id:{},trueValue:{},falseValue:{},asChild:{type:Boolean},as:{},name:{},required:{type:Boolean},class:{type:[Boolean,null,String,Object,Array]}},emits:[`update:modelValue`],setup(n,{emit:i}){let o=n,c=i,l=S(y(o,`class`),c);return(n,i)=>(u(),s(r(I),v({"data-slot":`checkbox`},r(l),{class:r(_)(`border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-md border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50`,o.class)}),{default:t(i=>[e(r(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:t(()=>[d(n.$slots,`default`,a(m(i)),()=>[e(r(O))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
package/frontend-dist/assets/{CollapsibleContent-DRIfmabo.js → CollapsibleContent-BLcA_5sz.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Nt as t,Rt as n,St as r,Tt as i,Vt as a,Wt as o,X as s,Y as c,ct as l,d as u,et as d,ft as f,ht as p,i as m,m as h,nt as g,o as _,q as v,st as y,ut as b}from"./button-
|
|
1
|
+
import{$ as e,Nt as t,Rt as n,St as r,Tt as i,Vt as a,Wt as o,X as s,Y as c,ct as l,d as u,et as d,ft as f,ht as p,i as m,m as h,nt as g,o as _,q as v,st as y,ut as b}from"./button-BWSfarSC.js";import{B as x,L as S,q as C,z as w}from"./index-B9huoJLE.js";var[T,E]=C(`CollapsibleRoot`),D=d({__name:`CollapsibleRoot`,props:{defaultOpen:{type:Boolean,required:!1,default:!1},open:{type:Boolean,required:!1,default:void 0},disabled:{type:Boolean,required:!1},unmountOnHide:{type:Boolean,required:!1,default:!0},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`update:open`],setup(e,{expose:t,emit:r}){let o=e,s=h(o,`open`,r,{defaultValue:o.defaultOpen,passive:o.open===void 0}),{disabled:l,unmountOnHide:u}=n(o);return E({contentId:``,disabled:l,open:s,unmountOnHide:u,onOpenToggle:()=>{l.value||(s.value=!s.value)}}),t({open:s}),_(),(e,t)=>(f(),c(a(m),{as:e.as,"as-child":o.asChild,"data-state":a(s)?`open`:`closed`,"data-disabled":a(l)?``:void 0},{default:i(()=>[p(e.$slots,`default`,{open:a(s)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=d({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(n,{emit:o}){let d=n,h=o,g=T();g.contentId||=w(void 0,`reka-collapsible-content`);let x=t(),{forwardRef:C,currentElement:E}=_(),D=t(0),O=t(0),k=v(()=>g.open.value),A=t(k.value),j=t();r(()=>[k.value,x.value?.present],async()=>{await l();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=v(()=>A.value&&g.open.value);return b(()=>{requestAnimationFrame(()=>{A.value=!1})}),u(E,`beforematch`,e=>{requestAnimationFrame(()=>{g.onOpenToggle(),h(`contentFound`)})}),(t,n)=>(f(),c(a(S),{ref_key:`presentRef`,ref:x,present:t.forceMount||a(g).open.value,"force-mount":!0},{default:i(({present:n})=>[e(a(m),y(t.$attrs,{id:a(g).contentId,ref:a(C),"as-child":d.asChild,as:t.as,hidden:n?void 0:a(g).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:a(g).open.value?`open`:`closed`,"data-disabled":a(g).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:i(()=>[!a(g).unmountOnHide.value||n?p(t.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=d({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(e,{emit:t}){let n=x(e,t);return(e,t)=>(f(),c(a(D),y({"data-slot":`collapsible`},a(n)),{default:i(t=>[p(e.$slots,`default`,o(g(t)))]),_:3},16))}}),A=d({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(f(),c(a(O),y({"data-slot":`collapsible-content`},t),{default:i(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
package/frontend-dist/assets/{CollapsibleTrigger-jbSdCpiC.js → CollapsibleTrigger-CFNX8ZWv.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Tt as e,Vt as t,Y as n,et as r,ft as i,ht as a,i as o,o as s,st as c}from"./button-
|
|
1
|
+
import{Tt as e,Vt as t,Y as n,et as r,ft as i,ht as a,i as o,o as s,st as c}from"./button-BWSfarSC.js";import{r as l}from"./CollapsibleContent-BLcA_5sz.js";var u=r({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(r){let c=r;s();let u=l();return(r,s)=>(i(),n(t(o),{type:r.as===`button`?`button`:void 0,as:r.as,"as-child":c.asChild,"aria-controls":t(u).contentId,"aria-expanded":t(u).open.value,"data-state":t(u).open.value?`open`:`closed`,"data-disabled":t(u).disabled?.value?``:void 0,disabled:t(u).disabled?.value,onClick:t(u).onOpenToggle},{default:e(()=>[a(r.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=r({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(r){let o=r;return(r,s)=>(i(),n(t(u),c({"data-slot":`collapsible-trigger`},o),{default:e(()=>[a(r.$slots,`default`)]),_:3},16))}});export{d as t};
|