llm-simple-router 0.9.29 → 0.9.31
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/config/model-context.js +11 -1
- package/dist/db/migrations/042_simplify_tool_error_logs.sql +27 -0
- package/dist/proxy/handler/proxy-handler-utils.d.ts +2 -3
- package/dist/proxy/handler/proxy-handler-utils.js +9 -15
- package/dist/proxy/handler/proxy-handler.js +25 -14
- package/dist/proxy/patch/deepseek/index.d.ts +1 -13
- package/dist/proxy/patch/deepseek/index.js +3 -22
- package/dist/proxy/patch/deepseek/patch-thinking.d.ts +44 -0
- package/dist/proxy/patch/deepseek/patch-thinking.js +153 -0
- package/dist/proxy/patch/index.js +8 -10
- package/dist/proxy/tool-error-logger.d.ts +3 -1
- package/dist/proxy/tool-error-logger.js +8 -10
- package/dist/proxy/transform/message-mapper.js +1 -1
- package/frontend-dist/assets/{CardContent-B7Q1YCsK.js → CardContent-Rx3Yzr25.js} +1 -1
- package/frontend-dist/assets/{CardTitle-C5Bsezde.js → CardTitle-BfXABzCk.js} +1 -1
- package/frontend-dist/assets/{Checkbox-wJzvSjuY.js → Checkbox-CaCKbLQP.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-B5pPNqRd.js → CollapsibleContent-C3p4dMa-.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-CSexuQRq.js → CollapsibleTrigger-DRGRANMT.js} +1 -1
- package/frontend-dist/assets/{Dashboard-BqOhqB5M.js → Dashboard-DKeQ9jnE.js} +1 -1
- package/frontend-dist/assets/{Input-DMQL4BIA.js → Input-Zqewz_Ci.js} +1 -1
- package/frontend-dist/assets/{Label-hAkd1fSw.js → Label-B1wEVeX4.js} +1 -1
- package/frontend-dist/assets/{Login-n5g90q7F.js → Login-BWDT9nph.js} +1 -1
- package/frontend-dist/assets/{Logs-BdptXdAL.js → Logs-hyd2jEMr.js} +1 -1
- package/frontend-dist/assets/{MappingEntryEditor-DrLuHu98.js → MappingEntryEditor-By30pZoC.js} +1 -1
- package/frontend-dist/assets/ModelCard-C3DD_FZd.js +1 -0
- package/frontend-dist/assets/{ModelMappings-CYeT2Zks.js → ModelMappings-CrWlSNu8.js} +1 -1
- package/frontend-dist/assets/{Monitor-BUr4LnM-.js → Monitor-BN7oLW1v.js} +1 -1
- package/frontend-dist/assets/{Providers-CcfxF8po.js → Providers-Drx7U5Sq.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-BxGYl9qc.js → ProxyEnhancement-CYVFvqyn.js} +1 -1
- package/frontend-dist/assets/QuickSetup-Oom1ygGi.js +1 -0
- package/frontend-dist/assets/{RetryRules-2T_w2dsY.js → RetryRules-CpL1bYTq.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-C0iULDvk.js → RouterKeys-_AlXAmum.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-WInkGN79.js → RovingFocusItem-BcFr0scT.js} +1 -1
- package/frontend-dist/assets/{Schedules-DC3-3teO.js → Schedules-DU8r-48b.js} +1 -1
- package/frontend-dist/assets/{Settings-DGd7p1_A.js → Settings-CqsVr0kd.js} +1 -1
- package/frontend-dist/assets/{Setup-CfojnNyj.js → Setup-fotCrfUW.js} +1 -1
- package/frontend-dist/assets/{Switch-Bs4rq7IS.js → Switch-BQas2Rql.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-C-z-Bt9A.js → TooltipTrigger-BPsVa_Tw.js} +1 -1
- package/frontend-dist/assets/{TransformRulesForm-BWeHl75r.js → TransformRulesForm-O68V-z_P.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-B6aqWGh6.js → UnifiedRequestDialog-hVtwPhdB.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-CbZDCpY7.js → VisuallyHiddenInput-5jJ6xPkS.js} +1 -1
- package/frontend-dist/assets/{button-m0I0MyCI.js → button-CRqhqXwT.js} +2 -2
- package/frontend-dist/assets/{copy-JCnKoTiJ.js → copy-CBD-G6GC.js} +1 -1
- package/frontend-dist/assets/{dialog-Cq89SQbS.js → dialog-DKAgYLQJ.js} +1 -1
- package/frontend-dist/assets/{index-CjaxcyJj.js → index-CwvbWlRW.js} +2 -2
- package/frontend-dist/assets/quickSetup-CCxaqY3U.js +1 -0
- package/frontend-dist/assets/quickSetup-DgDENHE4.js +1 -0
- package/frontend-dist/assets/{trash-2-PnXybHnJ.js → trash-2-C88b94t5.js} +1 -1
- package/frontend-dist/assets/{useClipboard-C5CZy9d2.js → useClipboard-BJc6Gpft.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-CLjR8sEq.js → useLogRetention-ed_dDakq.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/proxy/patch/deepseek/patch-cache-control.d.ts +0 -6
- package/dist/proxy/patch/deepseek/patch-cache-control.js +0 -30
- package/dist/proxy/patch/deepseek/patch-non-deepseek-tools.d.ts +0 -20
- package/dist/proxy/patch/deepseek/patch-non-deepseek-tools.js +0 -81
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.d.ts +0 -10
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.js +0 -57
- package/dist/proxy/patch/deepseek/patch-thinking-param.d.ts +0 -6
- package/dist/proxy/patch/deepseek/patch-thinking-param.js +0 -32
- package/frontend-dist/assets/ModelCard-gEWqLgeY.js +0 -1
- package/frontend-dist/assets/QuickSetup-B3Hc_tfI.js +0 -1
- package/frontend-dist/assets/quickSetup-cCofuCNs.js +0 -1
- package/frontend-dist/assets/quickSetup-xHkKkA6i.js +0 -1
|
@@ -102,9 +102,19 @@ export function parseModels(raw) {
|
|
|
102
102
|
const obj = item;
|
|
103
103
|
if (!obj || !obj.name)
|
|
104
104
|
return null;
|
|
105
|
+
/** 旧 patch ID 到新 patch ID 的运行时迁移映射 */
|
|
106
|
+
const PATCH_ID_MIGRATION = {
|
|
107
|
+
thinking_param: "thinking_consistency",
|
|
108
|
+
thinking_blocks: "thinking_consistency",
|
|
109
|
+
non_ds_tools: "thinking_consistency",
|
|
110
|
+
cache_control: "thinking_consistency",
|
|
111
|
+
};
|
|
112
|
+
const rawPatches = (obj.patches ?? []).map(normalizePatchName);
|
|
113
|
+
const migrated = rawPatches.map(p => PATCH_ID_MIGRATION[p] ?? p);
|
|
114
|
+
const patches = [...new Set(migrated)];
|
|
105
115
|
const result = {
|
|
106
116
|
name: obj.name,
|
|
107
|
-
patches
|
|
117
|
+
patches,
|
|
108
118
|
};
|
|
109
119
|
if (obj.stream_timeout_ms != null)
|
|
110
120
|
result.stream_timeout_ms = obj.stream_timeout_ms;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- 042_simplify_tool_error_logs.sql
|
|
2
|
+
-- 精简 tool_error_logs:去掉 tool_input / error_content 大字段,
|
|
3
|
+
-- 改为通过 request_log_id + tool_use_id 从 request_logs 回溯完整数据。
|
|
4
|
+
-- SQLite 不支持 DROP COLUMN(需 3.35+),重建表。
|
|
5
|
+
|
|
6
|
+
DROP TABLE IF EXISTS tool_error_logs;
|
|
7
|
+
|
|
8
|
+
CREATE TABLE tool_error_logs (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
request_log_id TEXT REFERENCES request_logs(id) ON DELETE SET NULL,
|
|
11
|
+
tool_use_id TEXT NOT NULL,
|
|
12
|
+
provider_id TEXT NOT NULL,
|
|
13
|
+
backend_model TEXT NOT NULL,
|
|
14
|
+
client_agent_type TEXT NOT NULL DEFAULT 'unknown'
|
|
15
|
+
CHECK(client_agent_type IN ('claude-code', 'pi', 'unknown')),
|
|
16
|
+
tool_name TEXT NOT NULL,
|
|
17
|
+
router_key_id TEXT,
|
|
18
|
+
session_id TEXT,
|
|
19
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX idx_tool_error_logs_time
|
|
23
|
+
ON tool_error_logs(created_at);
|
|
24
|
+
CREATE INDEX idx_tool_error_logs_provider
|
|
25
|
+
ON tool_error_logs(provider_id, created_at);
|
|
26
|
+
CREATE INDEX idx_tool_error_logs_tool
|
|
27
|
+
ON tool_error_logs(tool_name);
|
|
@@ -6,8 +6,6 @@ export type ClientAgentType = "claude-code" | "pi" | "unknown";
|
|
|
6
6
|
export interface FailedToolResult {
|
|
7
7
|
toolName: string;
|
|
8
8
|
toolUseId: string | undefined;
|
|
9
|
-
toolInput: string | undefined;
|
|
10
|
-
errorContent: string;
|
|
11
9
|
}
|
|
12
10
|
/**
|
|
13
11
|
* 根据请求头识别客户端类型。
|
|
@@ -22,7 +20,8 @@ export declare function detectClientAgentType(headers: RawHeaders): ClientAgentT
|
|
|
22
20
|
* 避免重复记录前轮请求已记录的 tool 失败。
|
|
23
21
|
*
|
|
24
22
|
* 通过向前扫描 assistant 消息中的 tool_use 块
|
|
25
|
-
* 关联对应的 tool_name
|
|
23
|
+
* 关联对应的 tool_name。完整 input/error 内容通过
|
|
24
|
+
* request_log_id + tool_use_id 从 request_logs 回溯。
|
|
26
25
|
*/
|
|
27
26
|
export declare function extractFailedToolResults(body: Record<string, unknown>): FailedToolResult[];
|
|
28
27
|
/** 从 TransportResult 中提取最终 HTTP status code */
|
|
@@ -21,13 +21,14 @@ export function detectClientAgentType(headers) {
|
|
|
21
21
|
* 避免重复记录前轮请求已记录的 tool 失败。
|
|
22
22
|
*
|
|
23
23
|
* 通过向前扫描 assistant 消息中的 tool_use 块
|
|
24
|
-
* 关联对应的 tool_name
|
|
24
|
+
* 关联对应的 tool_name。完整 input/error 内容通过
|
|
25
|
+
* request_log_id + tool_use_id 从 request_logs 回溯。
|
|
25
26
|
*/
|
|
26
27
|
export function extractFailedToolResults(body) {
|
|
27
28
|
const messages = body.messages;
|
|
28
29
|
if (!messages || messages.length === 0)
|
|
29
30
|
return [];
|
|
30
|
-
//
|
|
31
|
+
// 第一步:从后往前找最后一个包含 tool_result 的 user 消息
|
|
31
32
|
let lastUserIndex = -1;
|
|
32
33
|
const resultBlocks = [];
|
|
33
34
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -48,7 +49,7 @@ export function extractFailedToolResults(body) {
|
|
|
48
49
|
}
|
|
49
50
|
if (lastUserIndex < 0)
|
|
50
51
|
return [];
|
|
51
|
-
//
|
|
52
|
+
// 第二步:建立 tool_use_id → tool_name 映射
|
|
52
53
|
const toolUseMap = new Map();
|
|
53
54
|
for (let i = 0; i < lastUserIndex; i++) {
|
|
54
55
|
const msg = messages[i];
|
|
@@ -57,8 +58,7 @@ export function extractFailedToolResults(body) {
|
|
|
57
58
|
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
58
59
|
for (const block of content) {
|
|
59
60
|
if (block.type === "tool_use" && block.id) {
|
|
60
|
-
|
|
61
|
-
toolUseMap.set(block.id, { name: block.name ?? "unknown", input: inputText });
|
|
61
|
+
toolUseMap.set(block.id, block.name ?? "unknown");
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -67,17 +67,11 @@ export function extractFailedToolResults(body) {
|
|
|
67
67
|
for (const block of resultBlocks) {
|
|
68
68
|
if (block.is_error !== true)
|
|
69
69
|
continue;
|
|
70
|
-
const
|
|
71
|
-
?
|
|
72
|
-
: undefined;
|
|
73
|
-
const errorContent = typeof block.content === "string"
|
|
74
|
-
? block.content
|
|
75
|
-
: JSON.stringify(block.content ?? "");
|
|
70
|
+
const toolUseId = block.tool_use_id && typeof block.tool_use_id === "string"
|
|
71
|
+
? block.tool_use_id : undefined;
|
|
76
72
|
failures.push({
|
|
77
|
-
toolName:
|
|
78
|
-
toolUseId
|
|
79
|
-
toolInput: toolUse?.input,
|
|
80
|
-
errorContent,
|
|
73
|
+
toolName: toolUseId ? (toolUseMap.get(toolUseId) ?? "unknown") : "unknown",
|
|
74
|
+
toolUseId,
|
|
81
75
|
});
|
|
82
76
|
}
|
|
83
77
|
return failures;
|
|
@@ -24,7 +24,7 @@ const HTTP_ERROR_THRESHOLD = 400;
|
|
|
24
24
|
const MAX_LOG_FIELD_LENGTH = 80;
|
|
25
25
|
const UPSTREAM_ERROR_STATUS = 502;
|
|
26
26
|
const TIER2_LOOP_THRESHOLD = 2;
|
|
27
|
-
function rejectAndReply(reply, params, error, errorMessage, providerId) {
|
|
27
|
+
function rejectAndReply(reply, params, error, errorMessage, providerId, afterLog) {
|
|
28
28
|
insertRejectedLog({
|
|
29
29
|
db: params.db, logId: params.logId, apiType: params.apiType, model: params.model,
|
|
30
30
|
statusCode: error.statusCode, errorMessage, startTime: params.startTime,
|
|
@@ -35,6 +35,10 @@ function rejectAndReply(reply, params, error, errorMessage, providerId) {
|
|
|
35
35
|
sessionId: params.sessionId, pipelineSnapshot: params.pipelineSnapshot,
|
|
36
36
|
matcher: params.matcher, logFileWriter: params.logFileWriter,
|
|
37
37
|
});
|
|
38
|
+
try {
|
|
39
|
+
afterLog?.();
|
|
40
|
+
}
|
|
41
|
+
catch { /* tool error log 写入失败不影响响应 */ } // eslint-disable-line taste/no-silent-catch
|
|
38
42
|
return reply.code(error.statusCode).send(error.body);
|
|
39
43
|
}
|
|
40
44
|
import { getConfig } from "../../config/index.js";
|
|
@@ -127,6 +131,14 @@ async function executeFailoverLoop(ctx) {
|
|
|
127
131
|
const excludeTargets = [];
|
|
128
132
|
let rootLogId = null;
|
|
129
133
|
let toolErrorsLogged = false;
|
|
134
|
+
let pendingToolErrors = null;
|
|
135
|
+
/** request_logs 写入后调用,将 pending 的 tool error logs 安全写入 DB */
|
|
136
|
+
const flushToolErrors = (providerId, model, reqLogId) => {
|
|
137
|
+
if (!pendingToolErrors)
|
|
138
|
+
return;
|
|
139
|
+
logToolErrors(pendingToolErrors, { db: deps.db, providerId, backendModel: model, clientAgentType: detectClientAgentType(request.headers), requestLogId: reqLogId, routerKeyId: request.routerKey?.id ?? null, sessionId });
|
|
140
|
+
pendingToolErrors = null;
|
|
141
|
+
};
|
|
130
142
|
// TransformCoordinator 无状态,只需创建一次
|
|
131
143
|
const coordinator = new TransformCoordinator();
|
|
132
144
|
const enhancementConfig = loadEnhancementConfig(deps.db);
|
|
@@ -180,21 +192,14 @@ async function executeFailoverLoop(ctx) {
|
|
|
180
192
|
if (!provider || !provider.is_active) {
|
|
181
193
|
return rejectAndReply(reply, rCtx, errors.providerUnavailable(), `Provider '${resolved.provider_id}' unavailable`, resolved.provider_id);
|
|
182
194
|
}
|
|
183
|
-
//
|
|
195
|
+
// 工具错误日志:首次迭代时提取 failures,延迟到 request_logs 写入后再记录
|
|
196
|
+
// 避免 request_log_id 外键引用尚未存在的 request_logs 记录
|
|
184
197
|
if (enhancementConfig.tool_error_logging_enabled && !toolErrorsLogged) {
|
|
185
198
|
toolErrorsLogged = true;
|
|
186
199
|
const failures = extractFailedToolResults(pipelineBody);
|
|
187
200
|
if (failures.length > 0) {
|
|
188
201
|
request.log.info({ failures: failures.length, sessionId }, "Tool error results detected");
|
|
189
|
-
|
|
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
|
-
});
|
|
202
|
+
pendingToolErrors = failures;
|
|
198
203
|
}
|
|
199
204
|
}
|
|
200
205
|
// --- 溢出重定向:上下文超出时切换到更大模型(必须在 transform 之前,确保使用正确的 api_type) ---
|
|
@@ -252,7 +257,7 @@ async function executeFailoverLoop(ctx) {
|
|
|
252
257
|
iterationSnapshot.add({ stage: "provider_patch", types: patchMeta.types });
|
|
253
258
|
const encryptionKey = getSetting(deps.db, "encryption_key");
|
|
254
259
|
if (!encryptionKey) {
|
|
255
|
-
return rejectAndReply(reply, rCtx, errors.providerUnavailable(), `Encryption key not configured`, provider.id);
|
|
260
|
+
return rejectAndReply(reply, rCtx, errors.providerUnavailable(), `Encryption key not configured`, provider.id, () => flushToolErrors(provider.id, resolved.backend_model ?? effectiveModel, logId));
|
|
256
261
|
}
|
|
257
262
|
const apiKey = decrypt(provider.api_key, encryptionKey);
|
|
258
263
|
options?.beforeSendProxy?.(patchedBody, isStream);
|
|
@@ -316,6 +321,8 @@ async function executeFailoverLoop(ctx) {
|
|
|
316
321
|
matcher, logFileWriter,
|
|
317
322
|
}, resilienceResult.attempts, resilienceResult.result, startTime);
|
|
318
323
|
collectTransportMetrics(deps.db, apiType, resilienceResult.result, isStream, lastLogId, provider.id, resolved.backend_model, request, routerKeyId, getTransportStatusCode(resilienceResult.result));
|
|
324
|
+
// request_logs 已写入,安全写入 pending tool error logs
|
|
325
|
+
flushToolErrors(resolved.provider_id, resolved.backend_model ?? effectiveModel, lastLogId);
|
|
319
326
|
// Stream timeout: send 408 error with API-specific body to client
|
|
320
327
|
if (resilienceResult.result.kind === "stream_abort" && resilienceResult.result.timeoutContext) {
|
|
321
328
|
const { modelId, providerId } = resilienceResult.result.timeoutContext;
|
|
@@ -389,14 +396,16 @@ async function executeFailoverLoop(ctx) {
|
|
|
389
396
|
}, e.attempts, fakeResult, startTime);
|
|
390
397
|
}
|
|
391
398
|
request.log.debug({ logId, action: "provider_switch", targetProviderId: e.targetProviderId });
|
|
399
|
+
// request_logs 已写入(logResilienceResult),flush pending tool errors
|
|
400
|
+
flushToolErrors(resolved.provider_id, resolved.backend_model ?? effectiveModel, logId);
|
|
392
401
|
excludeTargets.push(resolved);
|
|
393
402
|
continue;
|
|
394
403
|
}
|
|
395
404
|
if (e instanceof SemaphoreQueueFullError) {
|
|
396
|
-
return rejectAndReply(reply, rCtx, errors.concurrencyQueueFull(provider.id), `Concurrency queue full for provider '${provider.id}'`, provider.id);
|
|
405
|
+
return rejectAndReply(reply, rCtx, errors.concurrencyQueueFull(provider.id), `Concurrency queue full for provider '${provider.id}'`, provider.id, () => flushToolErrors(provider.id, resolved.backend_model ?? effectiveModel, logId));
|
|
397
406
|
}
|
|
398
407
|
if (e instanceof SemaphoreTimeoutError) {
|
|
399
|
-
return rejectAndReply(reply, rCtx, errors.concurrencyTimeout(provider.id, e.timeoutMs), `Concurrency wait timeout for provider '${provider.id}' (${e.timeoutMs}ms)`, provider.id);
|
|
408
|
+
return rejectAndReply(reply, rCtx, errors.concurrencyTimeout(provider.id, e.timeoutMs), `Concurrency wait timeout for provider '${provider.id}' (${e.timeoutMs}ms)`, provider.id, () => flushToolErrors(provider.id, resolved.backend_model ?? effectiveModel, logId));
|
|
400
409
|
}
|
|
401
410
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
402
411
|
request.log.debug({ logId, error: errMsg, action: "upstream_error" });
|
|
@@ -412,6 +421,8 @@ async function executeFailoverLoop(ctx) {
|
|
|
412
421
|
}, (matcher || logFileWriter) ? {
|
|
413
422
|
matcher, logFileWriter, responseBody: null,
|
|
414
423
|
} : undefined);
|
|
424
|
+
// request_logs 已写入,flush pending tool errors
|
|
425
|
+
flushToolErrors(provider.id, resolved.backend_model ?? effectiveModel, logId);
|
|
415
426
|
const err = errors.upstreamConnectionFailed();
|
|
416
427
|
return reply.code(err.statusCode).send(err.body);
|
|
417
428
|
}
|
|
@@ -2,18 +2,6 @@
|
|
|
2
2
|
* 按序执行所有 DeepSeek 特定补丁。
|
|
3
3
|
*
|
|
4
4
|
* Patch 在格式转换之后执行,body 已经是 provider 的 api_type 格式。
|
|
5
|
-
*
|
|
6
|
-
* 因此按 apiType 分发不同的 patch 流程。
|
|
7
|
-
*
|
|
8
|
-
* Anthropic 格式执行顺序:
|
|
9
|
-
* 1. patchThinkingParam — 注入 thinking 参数
|
|
10
|
-
* 2. stripCacheControl — 剥离 cache_control
|
|
11
|
-
* 3. patchMissingThinkingBlocks — 补 thinking block
|
|
12
|
-
* 4. patchOrphanToolResults — 清理孤儿 tool_result
|
|
13
|
-
*
|
|
14
|
-
* OpenAI 格式执行顺序(参考 docs/deepseek-patch-investigation.md §5.5):
|
|
15
|
-
* 1. patchThinkingParam — 检测历史 reasoning_content,注入 thinking 参数
|
|
16
|
-
* 2. patchNonDeepSeekToolMessages — 将非 DeepSeek 生成的 tool_calls 降级为 text
|
|
17
|
-
* 3. patchOrphanToolResultsOA — 处理孤儿 tool 消息
|
|
5
|
+
* thinking-consistency 是统一的 thinking 一致性处理,内部按 apiType 自动分发。
|
|
18
6
|
*/
|
|
19
7
|
export declare function applyDeepSeekPatches(body: Record<string, unknown>, apiType: "openai" | "openai-responses" | "anthropic"): void;
|
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { stripCacheControl } from "./patch-cache-control.js";
|
|
3
|
-
import { patchMissingThinkingBlocks } from "./patch-thinking-blocks.js";
|
|
4
|
-
import { patchNonDeepSeekToolMessages } from "./patch-non-deepseek-tools.js";
|
|
1
|
+
import { patchThinkingConsistency } from "./patch-thinking.js";
|
|
5
2
|
import { patchOrphanToolResults, patchOrphanToolResultsOA } from "./patch-orphan-tool-results.js";
|
|
6
3
|
/**
|
|
7
4
|
* 按序执行所有 DeepSeek 特定补丁。
|
|
8
5
|
*
|
|
9
6
|
* Patch 在格式转换之后执行,body 已经是 provider 的 api_type 格式。
|
|
10
|
-
*
|
|
11
|
-
* 因此按 apiType 分发不同的 patch 流程。
|
|
12
|
-
*
|
|
13
|
-
* Anthropic 格式执行顺序:
|
|
14
|
-
* 1. patchThinkingParam — 注入 thinking 参数
|
|
15
|
-
* 2. stripCacheControl — 剥离 cache_control
|
|
16
|
-
* 3. patchMissingThinkingBlocks — 补 thinking block
|
|
17
|
-
* 4. patchOrphanToolResults — 清理孤儿 tool_result
|
|
18
|
-
*
|
|
19
|
-
* OpenAI 格式执行顺序(参考 docs/deepseek-patch-investigation.md §5.5):
|
|
20
|
-
* 1. patchThinkingParam — 检测历史 reasoning_content,注入 thinking 参数
|
|
21
|
-
* 2. patchNonDeepSeekToolMessages — 将非 DeepSeek 生成的 tool_calls 降级为 text
|
|
22
|
-
* 3. patchOrphanToolResultsOA — 处理孤儿 tool 消息
|
|
7
|
+
* thinking-consistency 是统一的 thinking 一致性处理,内部按 apiType 自动分发。
|
|
23
8
|
*/
|
|
24
9
|
export function applyDeepSeekPatches(body, apiType) {
|
|
10
|
+
patchThinkingConsistency(body, apiType);
|
|
25
11
|
if (apiType === "anthropic") {
|
|
26
|
-
patchThinkingParam(body, apiType);
|
|
27
|
-
stripCacheControl(body);
|
|
28
|
-
patchMissingThinkingBlocks(body);
|
|
29
12
|
patchOrphanToolResults(body);
|
|
30
13
|
}
|
|
31
14
|
else {
|
|
32
|
-
patchThinkingParam(body, apiType);
|
|
33
|
-
patchNonDeepSeekToolMessages(body);
|
|
34
15
|
patchOrphanToolResultsOA(body);
|
|
35
16
|
}
|
|
36
17
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的 DeepSeek thinking 一致性处理。
|
|
3
|
+
*
|
|
4
|
+
* 解决的问题:DeepSeek thinking 模式激活后,要求历史 assistant 消息携带 thinking 信息。
|
|
5
|
+
* 跨模型切换(如 GLM → DeepSeek)或 DeepSeek 自身某些轮次未返回 thinking 时,
|
|
6
|
+
* 历史中会出现"有 tool_calls 但无 thinking"的 assistant 消息。
|
|
7
|
+
*
|
|
8
|
+
* 策略(借鉴 pi coding-agent):补空值,不降级消息。
|
|
9
|
+
* - OpenAI 格式:补 reasoning_content = ""
|
|
10
|
+
* - Anthropic 格式:补 thinking block(含 signature)
|
|
11
|
+
*
|
|
12
|
+
* 参考:pi-mono/packages/ai/src/providers/openai-completions.ts
|
|
13
|
+
* requiresReasoningContentOnAssistantMessages 配置
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 注入 thinking 参数。
|
|
17
|
+
* DeepSeek 开启 thinking 后,后续请求必须显式传 thinking 参数。
|
|
18
|
+
* 客户端可能在后续轮次省略此参数,检测历史自动补上。
|
|
19
|
+
*/
|
|
20
|
+
declare function injectThinkingParam(body: Record<string, unknown>, apiType: "openai" | "openai-responses" | "anthropic"): void;
|
|
21
|
+
/**
|
|
22
|
+
* Anthropic 格式:补空 thinking block。
|
|
23
|
+
* DeepSeek thinking 模式下含 tool_use 的 assistant 消息必须携带 thinking 块。
|
|
24
|
+
*/
|
|
25
|
+
declare function patchMissingThinkingBlocks(body: Record<string, unknown>): void;
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI 格式:给缺 reasoning_content 的 assistant 消息补空字符串。
|
|
28
|
+
*
|
|
29
|
+
* 借鉴 pi coding-agent 的 requiresReasoningContentOnAssistantMessages 策略:
|
|
30
|
+
* 不判断"谁生成的",不降级 tool_calls,只补空值。
|
|
31
|
+
* 补空字符串足以通过 DeepSeek 校验,且不会导致模型忽略 tool_calls(
|
|
32
|
+
* 那是 Anthropic 端点补空 thinking block 的问题,OpenAI 端点无此副作用)。
|
|
33
|
+
*/
|
|
34
|
+
declare function patchMissingReasoningContent(body: Record<string, unknown>): void;
|
|
35
|
+
/**
|
|
36
|
+
* 统一的 thinking 一致性入口。
|
|
37
|
+
*/
|
|
38
|
+
export declare function patchThinkingConsistency(body: Record<string, unknown>, apiType: "openai" | "openai-responses" | "anthropic"): void;
|
|
39
|
+
export declare const _internals: {
|
|
40
|
+
injectThinkingParam: typeof injectThinkingParam;
|
|
41
|
+
patchMissingThinkingBlocks: typeof patchMissingThinkingBlocks;
|
|
42
|
+
patchMissingReasoningContent: typeof patchMissingReasoningContent;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的 DeepSeek thinking 一致性处理。
|
|
3
|
+
*
|
|
4
|
+
* 解决的问题:DeepSeek thinking 模式激活后,要求历史 assistant 消息携带 thinking 信息。
|
|
5
|
+
* 跨模型切换(如 GLM → DeepSeek)或 DeepSeek 自身某些轮次未返回 thinking 时,
|
|
6
|
+
* 历史中会出现"有 tool_calls 但无 thinking"的 assistant 消息。
|
|
7
|
+
*
|
|
8
|
+
* 策略(借鉴 pi coding-agent):补空值,不降级消息。
|
|
9
|
+
* - OpenAI 格式:补 reasoning_content = ""
|
|
10
|
+
* - Anthropic 格式:补 thinking block(含 signature)
|
|
11
|
+
*
|
|
12
|
+
* 参考:pi-mono/packages/ai/src/providers/openai-completions.ts
|
|
13
|
+
* requiresReasoningContentOnAssistantMessages 配置
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 注入 thinking 参数。
|
|
17
|
+
* DeepSeek 开启 thinking 后,后续请求必须显式传 thinking 参数。
|
|
18
|
+
* 客户端可能在后续轮次省略此参数,检测历史自动补上。
|
|
19
|
+
*/
|
|
20
|
+
function injectThinkingParam(body, apiType) {
|
|
21
|
+
if (body.thinking)
|
|
22
|
+
return;
|
|
23
|
+
const messages = body.messages;
|
|
24
|
+
if (!messages)
|
|
25
|
+
return;
|
|
26
|
+
const hasThinking = messages.some(msg => {
|
|
27
|
+
if (msg.role !== "assistant")
|
|
28
|
+
return false;
|
|
29
|
+
if (apiType === "openai") {
|
|
30
|
+
return msg.reasoning_content !== undefined;
|
|
31
|
+
}
|
|
32
|
+
return Array.isArray(msg.content) &&
|
|
33
|
+
msg.content
|
|
34
|
+
.some(b => b?.type === "thinking");
|
|
35
|
+
});
|
|
36
|
+
if (!hasThinking)
|
|
37
|
+
return;
|
|
38
|
+
if (apiType === "openai") {
|
|
39
|
+
body.thinking = { type: "enabled" };
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
body.thinking = { type: "enabled", budget_tokens: 10000 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* DeepSeek Anthropic 端点不支持 cache_control,剥离以避免报错。
|
|
47
|
+
*/
|
|
48
|
+
function stripCacheControl(body) {
|
|
49
|
+
if (Array.isArray(body.system)) {
|
|
50
|
+
for (const block of body.system) {
|
|
51
|
+
delete block.cache_control;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const messages = body.messages;
|
|
55
|
+
if (!messages)
|
|
56
|
+
return;
|
|
57
|
+
for (const msg of messages) {
|
|
58
|
+
if (Array.isArray(msg.content)) {
|
|
59
|
+
for (const block of msg.content) {
|
|
60
|
+
delete block.cache_control;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(body.tools)) {
|
|
65
|
+
for (const tool of body.tools) {
|
|
66
|
+
delete tool.cache_control;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Anthropic 格式:补空 thinking block。
|
|
72
|
+
* DeepSeek thinking 模式下含 tool_use 的 assistant 消息必须携带 thinking 块。
|
|
73
|
+
*/
|
|
74
|
+
function patchMissingThinkingBlocks(body) {
|
|
75
|
+
if (!body.messages)
|
|
76
|
+
return;
|
|
77
|
+
const messages = body.messages;
|
|
78
|
+
const thinkingActive = !!body.thinking || messages.some((msg) => msg.role === "assistant" && Array.isArray(msg.content)
|
|
79
|
+
&& msg.content.some((b) => b && typeof b === "object" && b.type === "thinking"));
|
|
80
|
+
if (!thinkingActive)
|
|
81
|
+
return;
|
|
82
|
+
const needsSignature = detectSignatureUsage(messages);
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
85
|
+
continue;
|
|
86
|
+
const blocks = msg.content;
|
|
87
|
+
const thinkingIdx = blocks.findIndex((b) => b && typeof b === "object" && b.type === "thinking");
|
|
88
|
+
if (thinkingIdx === -1) {
|
|
89
|
+
const emptyThinking = { type: "thinking", thinking: "" };
|
|
90
|
+
if (needsSignature)
|
|
91
|
+
emptyThinking.signature = "";
|
|
92
|
+
blocks.unshift(emptyThinking);
|
|
93
|
+
}
|
|
94
|
+
else if (thinkingIdx > 0) {
|
|
95
|
+
const [thinkingBlock] = blocks.splice(thinkingIdx, 1);
|
|
96
|
+
blocks.unshift(thinkingBlock);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function detectSignatureUsage(messages) {
|
|
101
|
+
for (const msg of messages) {
|
|
102
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
103
|
+
continue;
|
|
104
|
+
for (const b of msg.content) {
|
|
105
|
+
if (b && typeof b === "object" && b.type === "thinking") {
|
|
106
|
+
return "signature" in b;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* OpenAI 格式:给缺 reasoning_content 的 assistant 消息补空字符串。
|
|
114
|
+
*
|
|
115
|
+
* 借鉴 pi coding-agent 的 requiresReasoningContentOnAssistantMessages 策略:
|
|
116
|
+
* 不判断"谁生成的",不降级 tool_calls,只补空值。
|
|
117
|
+
* 补空字符串足以通过 DeepSeek 校验,且不会导致模型忽略 tool_calls(
|
|
118
|
+
* 那是 Anthropic 端点补空 thinking block 的问题,OpenAI 端点无此副作用)。
|
|
119
|
+
*/
|
|
120
|
+
function patchMissingReasoningContent(body) {
|
|
121
|
+
if (!body.thinking && !body.reasoning)
|
|
122
|
+
return;
|
|
123
|
+
const messages = body.messages;
|
|
124
|
+
if (!messages)
|
|
125
|
+
return;
|
|
126
|
+
for (const msg of messages) {
|
|
127
|
+
if (msg.role === "assistant"
|
|
128
|
+
&& msg.tool_calls
|
|
129
|
+
&& msg.tool_calls.length > 0
|
|
130
|
+
&& msg.reasoning_content === undefined) {
|
|
131
|
+
msg.reasoning_content = "";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 统一的 thinking 一致性入口。
|
|
137
|
+
*/
|
|
138
|
+
export function patchThinkingConsistency(body, apiType) {
|
|
139
|
+
injectThinkingParam(body, apiType);
|
|
140
|
+
if (apiType === "anthropic") {
|
|
141
|
+
stripCacheControl(body);
|
|
142
|
+
patchMissingThinkingBlocks(body);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
patchMissingReasoningContent(body);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// 导出内部函数供测试使用
|
|
149
|
+
export const _internals = {
|
|
150
|
+
injectThinkingParam,
|
|
151
|
+
patchMissingThinkingBlocks,
|
|
152
|
+
patchMissingReasoningContent,
|
|
153
|
+
};
|
|
@@ -28,16 +28,14 @@ export function applyProviderPatches(body, provider) {
|
|
|
28
28
|
patchDeveloperRole(ensureCloned());
|
|
29
29
|
patches.push("developer_role");
|
|
30
30
|
}
|
|
31
|
-
// DeepSeek
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (dsOpenAIPatches.some(p => hasPatch(modelPatches, p)) && provider.api_type === "openai") {
|
|
40
|
-
applyDeepSeekPatches(ensureCloned(), "openai");
|
|
31
|
+
// DeepSeek 补丁:新旧 patch ID 都能触发,内部按 apiType 自动分发
|
|
32
|
+
const dsPatches = [
|
|
33
|
+
"thinking-consistency", "thinking-param", "thinking-blocks",
|
|
34
|
+
"non-ds-tools", "cache-control",
|
|
35
|
+
"orphan-tool-results", "orphan-tool-results-oa",
|
|
36
|
+
];
|
|
37
|
+
if (dsPatches.some(p => hasPatch(modelPatches, p))) {
|
|
38
|
+
applyDeepSeekPatches(ensureCloned(), provider.api_type);
|
|
41
39
|
patches.push("deepseek");
|
|
42
40
|
}
|
|
43
41
|
return { body: patched ?? body, meta: { types: patches } };
|
|
@@ -11,6 +11,8 @@ export interface ToolErrorLogContext {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* 将失败的 tool_result 批量写入 tool_error_logs 表。
|
|
14
|
-
*
|
|
14
|
+
* 每条失败记录独立一行。只存索引字段,
|
|
15
|
+
* 完整 tool_input / error_content 通过
|
|
16
|
+
* request_log_id + tool_use_id 从 request_logs 回溯。
|
|
15
17
|
*/
|
|
16
18
|
export declare function logToolErrors(failures: FailedToolResult[], ctx: ToolErrorLogContext): void;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
/**
|
|
3
3
|
* 将失败的 tool_result 批量写入 tool_error_logs 表。
|
|
4
|
-
*
|
|
4
|
+
* 每条失败记录独立一行。只存索引字段,
|
|
5
|
+
* 完整 tool_input / error_content 通过
|
|
6
|
+
* request_log_id + tool_use_id 从 request_logs 回溯。
|
|
5
7
|
*/
|
|
6
8
|
export function logToolErrors(failures, ctx) {
|
|
7
9
|
if (failures.length === 0)
|
|
8
10
|
return;
|
|
9
11
|
const stmt = ctx.db.prepare(`
|
|
10
12
|
INSERT INTO tool_error_logs
|
|
11
|
-
(id, request_log_id, provider_id, backend_model,
|
|
12
|
-
tool_name,
|
|
13
|
-
router_key_id, session_id, created_at)
|
|
13
|
+
(id, request_log_id, tool_use_id, provider_id, backend_model,
|
|
14
|
+
client_agent_type, tool_name, router_key_id, session_id, created_at)
|
|
14
15
|
VALUES
|
|
15
|
-
(@id, @request_log_id, @
|
|
16
|
-
@tool_name, @
|
|
17
|
-
@router_key_id, @session_id, @created_at)
|
|
16
|
+
(@id, @request_log_id, @tool_use_id, @provider_id, @backend_model,
|
|
17
|
+
@client_agent_type, @tool_name, @router_key_id, @session_id, @created_at)
|
|
18
18
|
`);
|
|
19
19
|
const now = new Date().toISOString();
|
|
20
20
|
const insertMany = ctx.db.transaction(() => {
|
|
@@ -22,13 +22,11 @@ export function logToolErrors(failures, ctx) {
|
|
|
22
22
|
stmt.run({
|
|
23
23
|
id: randomUUID(),
|
|
24
24
|
request_log_id: ctx.requestLogId,
|
|
25
|
+
tool_use_id: f.toolUseId ?? "unknown",
|
|
25
26
|
provider_id: ctx.providerId,
|
|
26
27
|
backend_model: ctx.backendModel,
|
|
27
28
|
client_agent_type: ctx.clientAgentType,
|
|
28
29
|
tool_name: f.toolName,
|
|
29
|
-
tool_use_id: f.toolUseId ?? null,
|
|
30
|
-
tool_input: f.toolInput ?? null,
|
|
31
|
-
error_content: f.errorContent,
|
|
32
30
|
router_key_id: ctx.routerKeyId,
|
|
33
31
|
session_id: ctx.sessionId ?? null,
|
|
34
32
|
created_at: now,
|
|
@@ -191,7 +191,7 @@ export function convertMessagesAnt2OA(system, messages) {
|
|
|
191
191
|
const toolBlocks = content.filter(b => b.type === "tool_use");
|
|
192
192
|
const oaiMsg = { role: "assistant" };
|
|
193
193
|
// thinking → reasoning_content(保留 DeepSeek 原生思考信息,
|
|
194
|
-
//
|
|
194
|
+
// 便于 patchThinkingConsistency 判断 thinking 模式是否激活)
|
|
195
195
|
const thinkingBlocks = content.filter(b => b.type === "thinking");
|
|
196
196
|
if (thinkingBlocks.length > 0) {
|
|
197
197
|
oaiMsg.reasoning_content = thinkingBlocks.map(b => b.thinking ?? "").join("");
|
|
@@ -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-CRqhqXwT.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-CRqhqXwT.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-CRqhqXwT.js";import{t as b}from"./VisuallyHiddenInput-5jJ6xPkS.js";import{t as x}from"./RovingFocusItem-BcFr0scT.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-CwvbWlRW.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-B5pPNqRd.js → CollapsibleContent-C3p4dMa-.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-CRqhqXwT.js";import{B as x,L as S,q as C,z as w}from"./index-CwvbWlRW.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};
|