llm-simple-router 0.9.23 → 0.9.24
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 +4 -1
- package/dist/db/log-cleaner.js +4 -1
- package/dist/db/migrations/041_create_tool_error_logs.sql +28 -0
- package/dist/db/tool-error-logs.d.ts +6 -0
- package/dist/db/tool-error-logs.js +8 -0
- package/dist/proxy/handler/proxy-handler-utils.d.ts +24 -0
- package/dist/proxy/handler/proxy-handler-utils.js +81 -0
- package/dist/proxy/handler/proxy-handler.js +21 -1
- package/dist/proxy/routing/enhancement-config.d.ts +1 -0
- package/dist/proxy/routing/enhancement-config.js +2 -0
- package/dist/proxy/tool-error-logger.d.ts +16 -0
- package/dist/proxy/tool-error-logger.js +39 -0
- package/frontend-dist/assets/{CardContent-Cn7OKkf4.js → CardContent-rBUVGzRR.js} +1 -1
- package/frontend-dist/assets/{CardTitle-4FM9jzOA.js → CardTitle-DhlJArbg.js} +1 -1
- package/frontend-dist/assets/{Checkbox-peXPi_YE.js → Checkbox-uHKeZW1P.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-CgqAq4Jf.js → CollapsibleContent-B1v6hP95.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-DNaZoRW8.js → CollapsibleTrigger-BmuPdmL7.js} +1 -1
- package/frontend-dist/assets/{Dashboard-Cm4JcnYb.js → Dashboard-DzhKhSr8.js} +1 -1
- package/frontend-dist/assets/{Input-Br6PVSFy.js → Input-CjLowMgU.js} +1 -1
- package/frontend-dist/assets/{Label-GZl32pWd.js → Label-UyzkNwCC.js} +1 -1
- package/frontend-dist/assets/{Login-DoFRYUFk.js → Login-DkFE8Cwv.js} +1 -1
- package/frontend-dist/assets/{Logs-3m2BEWDo.js → Logs-BpPwbhfY.js} +1 -1
- package/frontend-dist/assets/{MappingEntryEditor-Wpkde3VI.js → MappingEntryEditor-CCRjU4OE.js} +1 -1
- package/frontend-dist/assets/{ModelCard-od6KpofN.js → ModelCard-BVDPemId.js} +1 -1
- package/frontend-dist/assets/{ModelMappings-RLgKMlDj.js → ModelMappings-YZj4_coo.js} +1 -1
- package/frontend-dist/assets/{Monitor-DWQbOIYI.js → Monitor-ufpuUH7M.js} +1 -1
- package/frontend-dist/assets/{Providers-OKgt7MLq.js → Providers-C1WoYsgN.js} +1 -1
- package/frontend-dist/assets/ProxyEnhancement-YzWODq0h.js +1 -0
- package/frontend-dist/assets/{QuickSetup-Cy7xPzpP.js → QuickSetup-DotjBKIE.js} +1 -1
- package/frontend-dist/assets/{RetryRules-D6zhJ-YT.js → RetryRules-CWWOgcSI.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-V4BYi2Xr.js → RouterKeys-fB8t51xY.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-DcZ3wJIC.js → RovingFocusItem-BpkzLwee.js} +1 -1
- package/frontend-dist/assets/{Schedules-romgDz2G.js → Schedules-CO4oIdFI.js} +1 -1
- package/frontend-dist/assets/{Settings-C4kcd3m9.js → Settings--ALxwdhl.js} +1 -1
- package/frontend-dist/assets/{Setup-Df7MMOO9.js → Setup-BdyHuJPP.js} +1 -1
- package/frontend-dist/assets/{Switch-CzzBLx2L.js → Switch-BIriILc9.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-UwQzn5Jd.js → TooltipTrigger-Qtuo87Qk.js} +1 -1
- package/frontend-dist/assets/{TransformRulesForm-CwHjFf_8.js → TransformRulesForm-Be1kdxM3.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-BbafY6jV.js → UnifiedRequestDialog-B_xm5uVS.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-h1_JJr8A.js → VisuallyHiddenInput-BMd5Xv9A.js} +1 -1
- package/frontend-dist/assets/{button-BwrBFTfJ.js → button-CU50mAde.js} +2 -2
- package/frontend-dist/assets/{copy-ZvTZUKRc.js → copy-5_1ty9pl.js} +1 -1
- package/frontend-dist/assets/{dialog-CVXqy9Fc.js → dialog-BP3UfrX6.js} +1 -1
- package/frontend-dist/assets/{index-C8xJ1Vhe.js → index-CgpVhJTP.js} +1 -1
- package/frontend-dist/assets/{proxyEnhancement-mmsextmb.js → proxyEnhancement-BlhJq5sA.js} +1 -1
- package/frontend-dist/assets/{proxyEnhancement-Rllg4r9y.js → proxyEnhancement-Cx7MC-ly.js} +1 -1
- package/frontend-dist/assets/{trash-2-DSx-MkZy.js → trash-2-BtGlrJP_.js} +1 -1
- package/frontend-dist/assets/{useClipboard-CszJHvCV.js → useClipboard-eO8uMHXU.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-BZZYkmu1.js → useLogRetention-Dz-o7K7g.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend-dist/assets/ProxyEnhancement-BG3lHGzz.js +0 -1
|
@@ -4,12 +4,13 @@ const UpdateProxyEnhancementSchema = Type.Object({
|
|
|
4
4
|
tool_call_loop_enabled: Type.Boolean(),
|
|
5
5
|
stream_loop_enabled: Type.Boolean(),
|
|
6
6
|
tool_round_limit_enabled: Type.Boolean(),
|
|
7
|
+
tool_error_logging_enabled: Type.Boolean(),
|
|
7
8
|
});
|
|
8
9
|
export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
9
10
|
const { db } = options;
|
|
10
11
|
app.get("/admin/api/proxy-enhancement", async (_request, reply) => {
|
|
11
12
|
const raw = getSetting(db, "proxy_enhancement");
|
|
12
|
-
const defaults = { tool_call_loop_enabled: false, stream_loop_enabled: false, tool_round_limit_enabled: true };
|
|
13
|
+
const defaults = { tool_call_loop_enabled: false, stream_loop_enabled: false, tool_round_limit_enabled: true, tool_error_logging_enabled: false };
|
|
13
14
|
let config = defaults;
|
|
14
15
|
if (raw) {
|
|
15
16
|
try {
|
|
@@ -18,6 +19,7 @@ export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
|
18
19
|
tool_call_loop_enabled: parsed.tool_call_loop_enabled ?? false,
|
|
19
20
|
stream_loop_enabled: parsed.stream_loop_enabled ?? false,
|
|
20
21
|
tool_round_limit_enabled: parsed.tool_round_limit_enabled ?? true,
|
|
22
|
+
tool_error_logging_enabled: parsed.tool_error_logging_enabled ?? false,
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
catch { /* eslint-disable-line taste/no-silent-catch -- invalid JSON, return defaults */ }
|
|
@@ -30,6 +32,7 @@ export const adminProxyEnhancementRoutes = (app, options, done) => {
|
|
|
30
32
|
tool_call_loop_enabled: body.tool_call_loop_enabled,
|
|
31
33
|
stream_loop_enabled: body.stream_loop_enabled,
|
|
32
34
|
tool_round_limit_enabled: body.tool_round_limit_enabled,
|
|
35
|
+
tool_error_logging_enabled: body.tool_error_logging_enabled,
|
|
33
36
|
};
|
|
34
37
|
setSetting(db, "proxy_enhancement", JSON.stringify(config));
|
|
35
38
|
return reply.send({ success: true });
|
package/dist/db/log-cleaner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { deleteLogsBefore } from "./logs.js";
|
|
2
2
|
import { getLogRetentionDays } from "./settings.js";
|
|
3
|
+
import { deleteToolErrorLogsBefore } from "./tool-error-logs.js";
|
|
3
4
|
const MS_PER_DAY = 86_400_000;
|
|
4
5
|
const CLEANUP_INTERVAL_MS = 3_600_000; // 1 小时
|
|
5
6
|
/** 运行一次清理,返回删除条数 */
|
|
@@ -8,7 +9,9 @@ export function runLogCleanup(db) {
|
|
|
8
9
|
if (days <= 0)
|
|
9
10
|
return 0;
|
|
10
11
|
const cutoff = new Date(Date.now() - days * MS_PER_DAY).toISOString();
|
|
11
|
-
|
|
12
|
+
const logDeleted = deleteLogsBefore(db, cutoff);
|
|
13
|
+
const toolErrorDeleted = deleteToolErrorLogsBefore(db, cutoff);
|
|
14
|
+
return logDeleted + toolErrorDeleted;
|
|
12
15
|
}
|
|
13
16
|
/** 启动定时清理,返回 handle 用于停止 */
|
|
14
17
|
export function scheduleLogCleanup(db, log) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS tool_error_logs (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
request_log_id TEXT REFERENCES request_logs(id) ON DELETE SET NULL,
|
|
4
|
+
provider_id TEXT NOT NULL,
|
|
5
|
+
backend_model TEXT NOT NULL,
|
|
6
|
+
client_agent_type TEXT NOT NULL DEFAULT 'unknown'
|
|
7
|
+
CHECK(client_agent_type IN ('claude-code', 'pi', 'unknown')),
|
|
8
|
+
tool_name TEXT NOT NULL,
|
|
9
|
+
tool_use_id TEXT,
|
|
10
|
+
tool_input TEXT,
|
|
11
|
+
error_content TEXT,
|
|
12
|
+
router_key_id TEXT,
|
|
13
|
+
session_id TEXT,
|
|
14
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_tool_error_logs_time
|
|
18
|
+
ON tool_error_logs(created_at);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_tool_error_logs_provider
|
|
20
|
+
ON tool_error_logs(provider_id, created_at);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_tool_error_logs_model
|
|
22
|
+
ON tool_error_logs(backend_model, created_at);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_tool_error_logs_tool
|
|
24
|
+
ON tool_error_logs(tool_name);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_tool_error_logs_agent
|
|
26
|
+
ON tool_error_logs(client_agent_type);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_tool_error_logs_session
|
|
28
|
+
ON tool_error_logs(session_id);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
/**
|
|
3
|
+
* 删除 created_at 早于 beforeDate 的 tool_error_logs 记录。
|
|
4
|
+
* 外键 ON DELETE SET NULL 确保 request_logs 被删后 tool_error_logs 仍保留。
|
|
5
|
+
*/
|
|
6
|
+
export declare function deleteToolErrorLogsBefore(db: Database.Database, beforeDate: string): number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 删除 created_at 早于 beforeDate 的 tool_error_logs 记录。
|
|
3
|
+
* 外键 ON DELETE SET NULL 确保 request_logs 被删后 tool_error_logs 仍保留。
|
|
4
|
+
*/
|
|
5
|
+
export function deleteToolErrorLogsBefore(db, beforeDate) {
|
|
6
|
+
const changes = db.prepare("DELETE FROM tool_error_logs WHERE created_at < ?").run(beforeDate).changes;
|
|
7
|
+
return changes;
|
|
8
|
+
}
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { ContentBlock } from "@llm-router/core/monitor";
|
|
2
2
|
import type { ToolCallRecord } from "@llm-router/core/loop-prevention";
|
|
3
3
|
import type { TransportResult } from "../types.js";
|
|
4
|
+
import type { RawHeaders } from "../types.js";
|
|
5
|
+
export type ClientAgentType = "claude-code" | "pi" | "unknown";
|
|
6
|
+
export interface FailedToolResult {
|
|
7
|
+
toolName: string;
|
|
8
|
+
toolUseId: string | undefined;
|
|
9
|
+
toolInput: string | undefined;
|
|
10
|
+
errorContent: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 根据请求头识别客户端类型。
|
|
14
|
+
* - Claude Code 独有 x-claude-code-session-id 头
|
|
15
|
+
* - pi 的 User-Agent 包含 "pi-coding-agent"
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectClientAgentType(headers: RawHeaders): ClientAgentType;
|
|
18
|
+
/**
|
|
19
|
+
* 从请求体 messages 中提取本条请求新产生的失败 tool_result 块。
|
|
20
|
+
*
|
|
21
|
+
* 只扫描最后一条 role = "user" 且有 tool_result 的消息,
|
|
22
|
+
* 避免重复记录前轮请求已记录的 tool 失败。
|
|
23
|
+
*
|
|
24
|
+
* 通过向前扫描 assistant 消息中的 tool_use 块
|
|
25
|
+
* 关联对应的 tool_name 和 tool_input。
|
|
26
|
+
*/
|
|
27
|
+
export declare function extractFailedToolResults(body: Record<string, unknown>): FailedToolResult[];
|
|
4
28
|
/** 从 TransportResult 中提取最终 HTTP status code */
|
|
5
29
|
export declare function getTransportStatusCode(result: TransportResult): number | null;
|
|
6
30
|
/** 将 tracker blocks 序列化为前端 tryDirectParse 可解析的 JSON */
|
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
import { createHash } from "crypto";
|
|
2
2
|
import { parseToolArguments } from "../transform/sanitize.js";
|
|
3
3
|
const HASH_DIGEST_LENGTH = 16;
|
|
4
|
+
/**
|
|
5
|
+
* 根据请求头识别客户端类型。
|
|
6
|
+
* - Claude Code 独有 x-claude-code-session-id 头
|
|
7
|
+
* - pi 的 User-Agent 包含 "pi-coding-agent"
|
|
8
|
+
*/
|
|
9
|
+
export function detectClientAgentType(headers) {
|
|
10
|
+
if (headers["x-claude-code-session-id"])
|
|
11
|
+
return "claude-code";
|
|
12
|
+
const ua = String(headers["user-agent"] ?? "").toLowerCase();
|
|
13
|
+
if (ua.includes("pi-coding-agent"))
|
|
14
|
+
return "pi";
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 从请求体 messages 中提取本条请求新产生的失败 tool_result 块。
|
|
19
|
+
*
|
|
20
|
+
* 只扫描最后一条 role = "user" 且有 tool_result 的消息,
|
|
21
|
+
* 避免重复记录前轮请求已记录的 tool 失败。
|
|
22
|
+
*
|
|
23
|
+
* 通过向前扫描 assistant 消息中的 tool_use 块
|
|
24
|
+
* 关联对应的 tool_name 和 tool_input。
|
|
25
|
+
*/
|
|
26
|
+
export function extractFailedToolResults(body) {
|
|
27
|
+
const messages = body.messages;
|
|
28
|
+
if (!messages || messages.length === 0)
|
|
29
|
+
return [];
|
|
30
|
+
// 第一步:向后往前找最后一个包含 tool_result 的 user 消息
|
|
31
|
+
let lastUserIndex = -1;
|
|
32
|
+
const resultBlocks = [];
|
|
33
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
34
|
+
const msg = messages[i];
|
|
35
|
+
if (msg.role !== "user")
|
|
36
|
+
continue;
|
|
37
|
+
const content = msg.content;
|
|
38
|
+
if (!Array.isArray(content))
|
|
39
|
+
continue;
|
|
40
|
+
for (const block of content) {
|
|
41
|
+
if (block.type === "tool_result") {
|
|
42
|
+
resultBlocks.push(block);
|
|
43
|
+
lastUserIndex = i;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (resultBlocks.length > 0)
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (lastUserIndex < 0)
|
|
50
|
+
return [];
|
|
51
|
+
// 第二步:在整个 messages 中建立 tool_use_id → { name, input } 映射
|
|
52
|
+
const toolUseMap = new Map();
|
|
53
|
+
for (let i = 0; i < lastUserIndex; i++) {
|
|
54
|
+
const msg = messages[i];
|
|
55
|
+
if (msg.role !== "assistant")
|
|
56
|
+
continue;
|
|
57
|
+
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
58
|
+
for (const block of content) {
|
|
59
|
+
if (block.type === "tool_use" && block.id) {
|
|
60
|
+
const inputText = block.input ? JSON.stringify(block.input) : "";
|
|
61
|
+
toolUseMap.set(block.id, { name: block.name ?? "unknown", input: inputText });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 第三步:提取 is_error === true 的 tool_result
|
|
66
|
+
const failures = [];
|
|
67
|
+
for (const block of resultBlocks) {
|
|
68
|
+
if (block.is_error !== true)
|
|
69
|
+
continue;
|
|
70
|
+
const toolUse = block.tool_use_id && typeof block.tool_use_id === "string"
|
|
71
|
+
? toolUseMap.get(block.tool_use_id)
|
|
72
|
+
: undefined;
|
|
73
|
+
const errorContent = typeof block.content === "string"
|
|
74
|
+
? block.content
|
|
75
|
+
: JSON.stringify(block.content ?? "");
|
|
76
|
+
failures.push({
|
|
77
|
+
toolName: toolUse?.name ?? "unknown",
|
|
78
|
+
toolUseId: block.tool_use_id,
|
|
79
|
+
toolInput: toolUse?.input,
|
|
80
|
+
errorContent,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return failures;
|
|
84
|
+
}
|
|
4
85
|
/** 从 TransportResult 中提取最终 HTTP status code */
|
|
5
86
|
export function getTransportStatusCode(result) {
|
|
6
87
|
if (result.kind === "success" || result.kind === "error" || result.kind === "stream_error")
|
|
@@ -18,7 +18,8 @@ import { applyProviderPatches } from "../patch/index.js";
|
|
|
18
18
|
import { PipelineSnapshot } from "../pipeline-snapshot.js";
|
|
19
19
|
import { applyToolRoundLimit } from "../patch/tool-round-limiter.js";
|
|
20
20
|
import { loadEnhancementConfig } from "../routing/enhancement-config.js";
|
|
21
|
-
import { getTransportStatusCode, serializeBlocksForStorage, extractLastToolUse } from "./proxy-handler-utils.js";
|
|
21
|
+
import { getTransportStatusCode, serializeBlocksForStorage, extractLastToolUse, extractFailedToolResults, detectClientAgentType } from "./proxy-handler-utils.js";
|
|
22
|
+
import { logToolErrors } from "../tool-error-logger.js";
|
|
22
23
|
const HTTP_ERROR_THRESHOLD = 400;
|
|
23
24
|
const MAX_LOG_FIELD_LENGTH = 80;
|
|
24
25
|
const UPSTREAM_ERROR_STATUS = 502;
|
|
@@ -125,8 +126,10 @@ async function executeFailoverLoop(ctx) {
|
|
|
125
126
|
const config = getConfig();
|
|
126
127
|
const excludeTargets = [];
|
|
127
128
|
let rootLogId = null;
|
|
129
|
+
let toolErrorsLogged = false;
|
|
128
130
|
// TransformCoordinator 无状态,只需创建一次
|
|
129
131
|
const coordinator = new TransformCoordinator();
|
|
132
|
+
const enhancementConfig = loadEnhancementConfig(deps.db);
|
|
130
133
|
while (true) {
|
|
131
134
|
const startTime = Date.now();
|
|
132
135
|
const logId = randomUUID();
|
|
@@ -177,6 +180,23 @@ async function executeFailoverLoop(ctx) {
|
|
|
177
180
|
if (!provider || !provider.is_active) {
|
|
178
181
|
return rejectAndReply(reply, rCtx, errors.providerUnavailable(), `Provider '${resolved.provider_id}' unavailable`, resolved.provider_id);
|
|
179
182
|
}
|
|
183
|
+
// 工具错误日志记录 — 首次迭代时执行,记录本轮请求中的 is_error tool_result
|
|
184
|
+
if (enhancementConfig.tool_error_logging_enabled && !toolErrorsLogged) {
|
|
185
|
+
toolErrorsLogged = true;
|
|
186
|
+
const failures = extractFailedToolResults(pipelineBody);
|
|
187
|
+
if (failures.length > 0) {
|
|
188
|
+
request.log.info({ failures: failures.length, sessionId }, "Tool error results detected");
|
|
189
|
+
logToolErrors(failures, {
|
|
190
|
+
db: deps.db,
|
|
191
|
+
providerId: resolved.provider_id,
|
|
192
|
+
backendModel: resolved.backend_model ?? effectiveModel,
|
|
193
|
+
clientAgentType: detectClientAgentType(cliHdrs),
|
|
194
|
+
requestLogId: logId,
|
|
195
|
+
routerKeyId,
|
|
196
|
+
sessionId,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
180
200
|
// --- 溢出重定向:上下文超出时切换到更大模型(必须在 transform 之前,确保使用正确的 api_type) ---
|
|
181
201
|
const overflowResult = applyOverflowRedirect(resolved, deps.db, currentBody);
|
|
182
202
|
if (overflowResult) {
|
|
@@ -3,6 +3,7 @@ export interface EnhancementConfig {
|
|
|
3
3
|
tool_call_loop_enabled: boolean;
|
|
4
4
|
stream_loop_enabled: boolean;
|
|
5
5
|
tool_round_limit_enabled: boolean;
|
|
6
|
+
tool_error_logging_enabled: boolean;
|
|
6
7
|
}
|
|
7
8
|
/** 集中加载 proxy_enhancement 配置,避免多处重复 getSetting + JSON.parse */
|
|
8
9
|
export declare function loadEnhancementConfig(db: Database.Database): EnhancementConfig;
|
|
@@ -3,6 +3,7 @@ const DEFAULT_CONFIG = {
|
|
|
3
3
|
tool_call_loop_enabled: false,
|
|
4
4
|
stream_loop_enabled: false,
|
|
5
5
|
tool_round_limit_enabled: true,
|
|
6
|
+
tool_error_logging_enabled: false,
|
|
6
7
|
};
|
|
7
8
|
/** 集中加载 proxy_enhancement 配置,避免多处重复 getSetting + JSON.parse */
|
|
8
9
|
export function loadEnhancementConfig(db) {
|
|
@@ -15,6 +16,7 @@ export function loadEnhancementConfig(db) {
|
|
|
15
16
|
tool_call_loop_enabled: parsed.tool_call_loop_enabled ?? false,
|
|
16
17
|
stream_loop_enabled: parsed.stream_loop_enabled ?? false,
|
|
17
18
|
tool_round_limit_enabled: parsed.tool_round_limit_enabled ?? true,
|
|
19
|
+
tool_error_logging_enabled: parsed.tool_error_logging_enabled ?? false,
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
catch {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import type { FailedToolResult, ClientAgentType } from "./handler/proxy-handler-utils.js";
|
|
3
|
+
export interface ToolErrorLogContext {
|
|
4
|
+
db: Database.Database;
|
|
5
|
+
providerId: string;
|
|
6
|
+
backendModel: string;
|
|
7
|
+
clientAgentType: ClientAgentType;
|
|
8
|
+
requestLogId: string;
|
|
9
|
+
routerKeyId: string | null;
|
|
10
|
+
sessionId: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 将失败的 tool_result 批量写入 tool_error_logs 表。
|
|
14
|
+
* 每条失败记录独立一行。
|
|
15
|
+
*/
|
|
16
|
+
export declare function logToolErrors(failures: FailedToolResult[], ctx: ToolErrorLogContext): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
/**
|
|
3
|
+
* 将失败的 tool_result 批量写入 tool_error_logs 表。
|
|
4
|
+
* 每条失败记录独立一行。
|
|
5
|
+
*/
|
|
6
|
+
export function logToolErrors(failures, ctx) {
|
|
7
|
+
if (failures.length === 0)
|
|
8
|
+
return;
|
|
9
|
+
const stmt = ctx.db.prepare(`
|
|
10
|
+
INSERT INTO tool_error_logs
|
|
11
|
+
(id, request_log_id, provider_id, backend_model, client_agent_type,
|
|
12
|
+
tool_name, tool_use_id, tool_input, error_content,
|
|
13
|
+
router_key_id, session_id, created_at)
|
|
14
|
+
VALUES
|
|
15
|
+
(@id, @request_log_id, @provider_id, @backend_model, @client_agent_type,
|
|
16
|
+
@tool_name, @tool_use_id, @tool_input, @error_content,
|
|
17
|
+
@router_key_id, @session_id, @created_at)
|
|
18
|
+
`);
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
const insertMany = ctx.db.transaction(() => {
|
|
21
|
+
for (const f of failures) {
|
|
22
|
+
stmt.run({
|
|
23
|
+
id: randomUUID(),
|
|
24
|
+
request_log_id: ctx.requestLogId,
|
|
25
|
+
provider_id: ctx.providerId,
|
|
26
|
+
backend_model: ctx.backendModel,
|
|
27
|
+
client_agent_type: ctx.clientAgentType,
|
|
28
|
+
tool_name: f.toolName,
|
|
29
|
+
tool_use_id: f.toolUseId ?? null,
|
|
30
|
+
tool_input: f.toolInput ?? null,
|
|
31
|
+
error_content: f.errorContent,
|
|
32
|
+
router_key_id: ctx.routerKeyId,
|
|
33
|
+
session_id: ctx.sessionId ?? null,
|
|
34
|
+
created_at: now,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
insertMany();
|
|
39
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-
|
|
1
|
+
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-CU50mAde.js";var s=[`data-size`],c=e({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(e){let c=e;return(l,u)=>(r(),n(`div`,{"data-slot":`card`,"data-size":e.size,class:t(o(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=e({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-content`,class:t(o(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(e.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-
|
|
1
|
+
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-CU50mAde.js";var s=e({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-header`,class:t(o(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(e.$slots,`default`)],2))}}),c=e({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-title`,class:t(o(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(e.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,H as t,Ht as n,J as r,K as i,Q as a,U as o,Y as s,dt as c,gt as l,i as u,m as d,mt as f,o as p,ot as m,r as h,tt as g,wt as _,x as v,zt as y}from"./button-
|
|
1
|
+
import{$ as e,H as t,Ht as n,J as r,K as i,Q as a,U as o,Y as s,dt as c,gt as l,i as u,m as d,mt as f,o as p,ot as m,r as h,tt as g,wt as _,x as v,zt as y}from"./button-CU50mAde.js";import{t as b}from"./VisuallyHiddenInput-BMd5Xv9A.js";import{t as x}from"./RovingFocusItem-BpkzLwee.js";import{B as S,G as C,H as w,L as T,Y as E,nt as D,q as O}from"./index-CgpVhJTP.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=O(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=O(`CheckboxRoot`),I=e({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:n}){let a=e,h=n,{forwardRef:g,currentElement:v}=p(),S=A(null),T=d(a,`modelValue`,h,{defaultValue:a.defaultValue??a.falseValue,passive:a.modelValue===void 0}),D=i(()=>S?.disabled.value||a.disabled),O=i(()=>E(T.value,a.trueValue)),j=i(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,a.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=a.trueValue:T.value=O.value?a.falseValue:a.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,a.value)){let t=e.findIndex(e=>E(e,a.value));e.splice(t,1)}else e.push(a.value);S.modelValue.value=e}}let I=w(v),L=i(()=>a.id&&v.value?document.querySelector(`[for="${a.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,n)=>(c(),r(l(y(S)?.rovingFocus.value?y(x):y(u)),m(e.$attrs,{id:e.id,ref:y(g),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":y(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":y(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:y(S)?.rovingFocus.value?!D.value:void 0,onKeydown:t(o(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:_(()=>[f(e.$slots,`default`,{modelValue:y(T),state:j.value}),y(I)&&e.name&&!y(S)?(c(),r(y(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`])):s(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=e({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:t}=p(),n=P();return(e,i)=>(c(),r(y(T),{present:e.forceMount||y(M)(y(n).state.value)||y(n).state.value===!0},{default:_(()=>[a(y(u),m({ref:y(t),"data-state":y(N)(y(n).state.value),"data-disabled":y(n).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:_(()=>[f(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=e({__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(e,{emit:t}){let i=e,o=t,s=S(v(i,`class`),o);return(e,t)=>(c(),r(y(I),m({"data-slot":`checkbox`},y(s),{class:y(h)(`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`,i.class)}),{default:_(t=>[a(y(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:_(()=>[f(e.$slots,`default`,n(g(t)),()=>[a(y(D))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
package/frontend-dist/assets/{CollapsibleContent-CgqAq4Jf.js → CollapsibleContent-B1v6hP95.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Ht as t,J as n,K as r,Lt as i,Mt as a,Q as o,Y as s,d as c,dt as l,i as u,lt as d,m as f,mt as p,o as m,ot as h,st as g,tt as _,wt as v,xt as y,zt as b}from"./button-
|
|
1
|
+
import{$ as e,Ht as t,J as n,K as r,Lt as i,Mt as a,Q as o,Y as s,d as c,dt as l,i as u,lt as d,m as f,mt as p,o as m,ot as h,st as g,tt as _,wt as v,xt as y,zt as b}from"./button-CU50mAde.js";import{B as x,L as S,q as C,z as w}from"./index-CgpVhJTP.js";var[T,E]=C(`CollapsibleRoot`),D=e({__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 a=e,o=f(a,`open`,r,{defaultValue:a.defaultOpen,passive:a.open===void 0}),{disabled:s,unmountOnHide:c}=i(a);return E({contentId:``,disabled:s,open:o,unmountOnHide:c,onOpenToggle:()=>{s.value||(o.value=!o.value)}}),t({open:o}),m(),(e,t)=>(l(),n(b(u),{as:e.as,"as-child":a.asChild,"data-state":b(o)?`open`:`closed`,"data-disabled":b(s)?``:void 0},{default:v(()=>[p(e.$slots,`default`,{open:b(o)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=e({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(e,{emit:t}){let i=e,f=t,_=T();_.contentId||=w(void 0,`reka-collapsible-content`);let x=a(),{forwardRef:C,currentElement:E}=m(),D=a(0),O=a(0),k=r(()=>_.open.value),A=a(k.value),j=a();y(()=>[k.value,x.value?.present],async()=>{await g();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=r(()=>A.value&&_.open.value);return d(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{_.onOpenToggle(),f(`contentFound`)})}),(e,t)=>(l(),n(b(S),{ref_key:`presentRef`,ref:x,present:e.forceMount||b(_).open.value,"force-mount":!0},{default:v(({present:t})=>[o(b(u),h(e.$attrs,{id:b(_).contentId,ref:b(C),"as-child":i.asChild,as:e.as,hidden:t?void 0:b(_).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:b(_).open.value?`open`:`closed`,"data-disabled":b(_).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:v(()=>[!b(_).unmountOnHide.value||t?p(e.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=e({__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:r}){let i=x(e,r);return(e,r)=>(l(),n(b(D),h({"data-slot":`collapsible`},b(i)),{default:v(n=>[p(e.$slots,`default`,t(_(n)))]),_:3},16))}}),A=e({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(l(),n(b(O),h({"data-slot":`collapsible-content`},t),{default:v(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
package/frontend-dist/assets/{CollapsibleTrigger-DNaZoRW8.js → CollapsibleTrigger-BmuPdmL7.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,J as t,dt as n,i as r,mt as i,o as a,ot as o,wt as s,zt as c}from"./button-
|
|
1
|
+
import{$ as e,J as t,dt as n,i as r,mt as i,o as a,ot as o,wt as s,zt as c}from"./button-CU50mAde.js";import{r as l}from"./CollapsibleContent-B1v6hP95.js";var u=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let o=e;a();let u=l();return(e,a)=>(n(),t(c(r),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":o.asChild,"aria-controls":c(u).contentId,"aria-expanded":c(u).open.value,"data-state":c(u).open.value?`open`:`closed`,"data-disabled":c(u).disabled?.value?``:void 0,disabled:c(u).disabled?.value,onClick:c(u).onOpenToggle},{default:s(()=>[i(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(e){let r=e;return(e,a)=>(n(),t(c(u),o({"data-slot":`collapsible-trigger`},r),{default:s(()=>[i(e.$slots,`default`)]),_:3},16))}});export{d as t};
|