llm-simple-router 0.11.13 → 0.11.14
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/core/types.d.ts +5 -0
- package/dist/db/logs.d.ts +8 -0
- package/dist/db/logs.js +4 -2
- package/dist/db/migrations/048_add_diagnostic_columns.sql +9 -0
- package/dist/proxy/handler/failover-loop.js +25 -7
- package/dist/proxy/log-helpers.d.ts +16 -0
- package/dist/proxy/log-helpers.js +18 -2
- package/dist/proxy/orchestration/resilience.d.ts +2 -0
- package/dist/proxy/orchestration/resilience.js +7 -2
- package/dist/proxy/proxy-logging.d.ts +4 -0
- package/dist/proxy/proxy-logging.js +26 -1
- package/dist/proxy/transport/stream.js +5 -6
- package/frontend-dist/assets/CardContent-B5FrveYZ.js +1 -0
- package/frontend-dist/assets/CardTitle-CKWCcEb3.js +1 -0
- package/frontend-dist/assets/CascadingModelSelect-Pwir2Ztj.js +1 -0
- package/frontend-dist/assets/Checkbox-CGpRvnhQ.js +1 -0
- package/frontend-dist/assets/CollapsibleContent-D8Ge0Io5.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-DNG-BtpZ.js +1 -0
- package/frontend-dist/assets/Dashboard-CXj3a5wc.js +3 -0
- package/frontend-dist/assets/Input-ClARiZlY.js +1 -0
- package/frontend-dist/assets/Label-CnOVQAdS.js +1 -0
- package/frontend-dist/assets/Login-TISE309S.js +1 -0
- package/frontend-dist/assets/Logs-WrNxphm9.js +1 -0
- package/frontend-dist/assets/MappingEntryEditor-C7Qgr2t7.js +1 -0
- package/frontend-dist/assets/ModelMappings-DU-s4H4e.js +1 -0
- package/frontend-dist/assets/Monitor-Dm4gFmNN.js +1 -0
- package/frontend-dist/assets/Providers-Zg9i7Ink.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-D7XU53vA.js +1 -0
- package/frontend-dist/assets/QuickSetup-BhysqJmj.js +1 -0
- package/frontend-dist/assets/RetryRules-CSGpW5xq.js +1 -0
- package/frontend-dist/assets/RouterKeys-K4CBckqe.js +1 -0
- package/frontend-dist/assets/{RovingFocusItem-Dg-GYaYf.js → RovingFocusItem-CINTW0LN.js} +1 -1
- package/frontend-dist/assets/Schedules-wqyBXR_N.js +1 -0
- package/frontend-dist/assets/Settings-BiLu5um1.js +6 -0
- package/frontend-dist/assets/Setup-Cmmcejy8.js +1 -0
- package/frontend-dist/assets/{Switch-CqF_csS2.js → Switch-C3xZC3qo.js} +1 -1
- package/frontend-dist/assets/TooltipTrigger-D7Jz417n.js +1 -0
- package/frontend-dist/assets/TransformRulesForm-CCscyaIh.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-CnxgAkKt.js +3 -0
- package/frontend-dist/assets/VisuallyHiddenInput-_XDrpRgf.js +1 -0
- package/frontend-dist/assets/{button-BaNRw0cF.js → button-DUeiLnX9.js} +2 -2
- package/frontend-dist/assets/{copy-Dbl7J0Cg.js → copy-ruY9rh8q.js} +1 -1
- package/frontend-dist/assets/dialog-Db6Gh_Ri.js +1 -0
- package/frontend-dist/assets/index-D_GZgCIM.js +3 -0
- package/frontend-dist/assets/model-patches-BW-4laSi.js +1 -0
- package/frontend-dist/assets/{sparkles-D3u86oss.js → sparkles-BlcFkntD.js} +1 -1
- package/frontend-dist/assets/{trash-2-DvEWr8UB.js → trash-2-JsjrDYRS.js} +1 -1
- package/frontend-dist/assets/{useClipboard-D_jpOvnE.js → useClipboard-D3YY24Me.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-DnSxnecs.js → useLogRetention-DSfc7AWK.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend-dist/assets/CardContent-POdM9knm.js +0 -1
- package/frontend-dist/assets/CardTitle-mi00cvHk.js +0 -1
- package/frontend-dist/assets/CascadingModelSelect-CubFNL5Z.js +0 -1
- package/frontend-dist/assets/Checkbox-BcnVK5BE.js +0 -1
- package/frontend-dist/assets/CollapsibleContent-CMuY_0ps.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-KRXgNeSC.js +0 -1
- package/frontend-dist/assets/Dashboard-Bh4nXWLM.js +0 -3
- package/frontend-dist/assets/Input-CQAFlthI.js +0 -1
- package/frontend-dist/assets/Label-_o2ZnCRD.js +0 -1
- package/frontend-dist/assets/Login-BPqzTIaZ.js +0 -1
- package/frontend-dist/assets/Logs-ZJszhjOb.js +0 -1
- package/frontend-dist/assets/MappingEntryEditor--Icmf_he.js +0 -1
- package/frontend-dist/assets/ModelMappings-Bj32YLQZ.js +0 -1
- package/frontend-dist/assets/Monitor-SBlmRPTQ.js +0 -1
- package/frontend-dist/assets/Providers-Biuxv2W9.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-BTUqx-hj.js +0 -1
- package/frontend-dist/assets/QuickSetup-B8-u2sZa.js +0 -1
- package/frontend-dist/assets/RetryRules-C8mGFYBh.js +0 -1
- package/frontend-dist/assets/RouterKeys-DgzXJsL8.js +0 -1
- package/frontend-dist/assets/Schedules-CQQX55LC.js +0 -1
- package/frontend-dist/assets/Settings-B7-f_Pei.js +0 -6
- package/frontend-dist/assets/Setup-aJfZU8RD.js +0 -1
- package/frontend-dist/assets/TooltipTrigger-CpHXgQxI.js +0 -1
- package/frontend-dist/assets/TransformRulesForm-C0elmJO6.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-DWUXdfWY.js +0 -3
- package/frontend-dist/assets/VisuallyHiddenInput-HnGHuxTe.js +0 -1
- package/frontend-dist/assets/dialog-D_OkVkI9.js +0 -1
- package/frontend-dist/assets/index-COoySiAr.js +0 -3
- package/frontend-dist/assets/model-patches-D5Tyous2.js +0 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -89,6 +89,7 @@ export type TransportResult = {
|
|
|
89
89
|
providerId: string;
|
|
90
90
|
};
|
|
91
91
|
timeoutMs?: number;
|
|
92
|
+
abortReason?: "idle_timeout" | "client_disconnect" | "loop_detection";
|
|
92
93
|
} | {
|
|
93
94
|
kind: "error";
|
|
94
95
|
statusCode: number;
|
|
@@ -113,6 +114,10 @@ export interface ResilienceAttempt {
|
|
|
113
114
|
responseHeaders: Record<string, string> | null;
|
|
114
115
|
/** TransportResult.kind,用于区分 stream_error 等特殊类型 */
|
|
115
116
|
resultKind: TransportResult["kind"];
|
|
117
|
+
/** error.code(如 ETIMEDOUT / ECONNRESET / ECONNREFUSED),仅 throw 时有值 */
|
|
118
|
+
error_code?: string | null;
|
|
119
|
+
/** response headers 是否已发送,影响重试/failover 决策 */
|
|
120
|
+
headers_sent?: boolean | null;
|
|
116
121
|
}
|
|
117
122
|
/** 流式传输阶段状态 */
|
|
118
123
|
export type StreamState = "BUFFERING" | "STREAMING" | "COMPLETED" | "EARLY_ERROR" | "ABORTED";
|
package/dist/db/logs.d.ts
CHANGED
|
@@ -48,6 +48,14 @@ export interface RequestLogInsert {
|
|
|
48
48
|
session_id?: string | null;
|
|
49
49
|
client_status_code?: number | null;
|
|
50
50
|
pipeline_snapshot?: string | null;
|
|
51
|
+
transport_kind?: string | null;
|
|
52
|
+
abort_reason?: string | null;
|
|
53
|
+
error_code?: string | null;
|
|
54
|
+
headers_sent?: number | null;
|
|
55
|
+
resilience_action?: string | null;
|
|
56
|
+
resilience_reason?: string | null;
|
|
57
|
+
mapping_reason?: string | null;
|
|
58
|
+
failover_trigger?: string | null;
|
|
51
59
|
}
|
|
52
60
|
export interface LogWriteContext {
|
|
53
61
|
matcher?: RetryMatcher | null;
|
package/dist/db/logs.js
CHANGED
|
@@ -16,8 +16,10 @@ function rawInsertRequestLog(db, log, writeContext) {
|
|
|
16
16
|
const preserveDetail = shouldPreserveDetail(log.status_code, writeContext?.responseBody ?? null, writeContext?.matcher ?? null, !!writeContext?.logFileWriter);
|
|
17
17
|
getCachedStmt(db, `INSERT INTO request_logs (id, api_type, model, provider_id, status_code, client_status_code, latency_ms,
|
|
18
18
|
is_stream, error_message, created_at, client_request, upstream_request, upstream_response,
|
|
19
|
-
is_retry, is_failover, original_request_id, router_key_id, original_model, session_id, pipeline_snapshot
|
|
20
|
-
|
|
19
|
+
is_retry, is_failover, original_request_id, router_key_id, original_model, session_id, pipeline_snapshot,
|
|
20
|
+
transport_kind, abort_reason, error_code, headers_sent, resilience_action, resilience_reason, mapping_reason, failover_trigger)
|
|
21
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
22
|
+
?, ?, ?, ?, ?, ?, ?, ?)`).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, log.transport_kind ?? null, log.abort_reason ?? null, log.error_code ?? null, log.headers_sent ?? null, log.resilience_action ?? null, log.resilience_reason ?? null, log.mapping_reason ?? null, log.failover_trigger ?? null);
|
|
21
23
|
}
|
|
22
24
|
export function insertRequestLog(db, log, writeContext) {
|
|
23
25
|
// 文件写入:始终同步调用(WriteStream 内部异步,不阻塞事件循环)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- 048: Add diagnostic columns to request_logs for runtime observability
|
|
2
|
+
ALTER TABLE request_logs ADD COLUMN transport_kind TEXT;
|
|
3
|
+
ALTER TABLE request_logs ADD COLUMN abort_reason TEXT;
|
|
4
|
+
ALTER TABLE request_logs ADD COLUMN error_code TEXT;
|
|
5
|
+
ALTER TABLE request_logs ADD COLUMN headers_sent INTEGER;
|
|
6
|
+
ALTER TABLE request_logs ADD COLUMN resilience_action TEXT;
|
|
7
|
+
ALTER TABLE request_logs ADD COLUMN resilience_reason TEXT;
|
|
8
|
+
ALTER TABLE request_logs ADD COLUMN mapping_reason TEXT;
|
|
9
|
+
ALTER TABLE request_logs ADD COLUMN failover_trigger TEXT;
|
|
@@ -63,6 +63,7 @@ function rejectAndReply(reply, params, error, errorMessage, providerId, afterLog
|
|
|
63
63
|
isFailover: params.isFailover, originalRequestId: params.originalRequestId,
|
|
64
64
|
sessionId: params.sessionId, pipelineSnapshot: params.pipelineSnapshot,
|
|
65
65
|
matcher: params.matcher, logFileWriter: params.logFileWriter,
|
|
66
|
+
mapping_reason: params.mappingReason ?? null,
|
|
66
67
|
});
|
|
67
68
|
try {
|
|
68
69
|
afterLog?.();
|
|
@@ -184,6 +185,7 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
184
185
|
}
|
|
185
186
|
// === while(true):纯执行循环 ===
|
|
186
187
|
let failoverIteration = 0;
|
|
188
|
+
let lastFailoverTrigger = null;
|
|
187
189
|
while (true) {
|
|
188
190
|
// 请求被 kill 后 reply 已销毁,直接退出避免浪费 failover 迭代
|
|
189
191
|
if (reply.raw.destroyed)
|
|
@@ -218,6 +220,14 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
218
220
|
}
|
|
219
221
|
const resolved = filtered[0];
|
|
220
222
|
const isFailover = cachedTargets.length > 1;
|
|
223
|
+
// effectiveMappingReason: 首次迭代用 resolveResult.reason,溢出时覆盖
|
|
224
|
+
let effectiveMappingReason = isFailoverIteration ? "failover_retry" : resolveResult.mappingReason;
|
|
225
|
+
// 只有当前 target 是 overflow 扩展产生的才标记
|
|
226
|
+
const resolvedIdx = cachedTargets.findIndex(t => t.provider_id === resolved.provider_id && t.backend_model === resolved.backend_model);
|
|
227
|
+
if (overflowIndices.has(resolvedIdx))
|
|
228
|
+
effectiveMappingReason = "overflow_redirect";
|
|
229
|
+
// 将 mappingReason 注入 rCtx,使后续 rejectAndReply 能写入诊断字段
|
|
230
|
+
rCtx.mappingReason = effectiveMappingReason;
|
|
221
231
|
const provider = getProviderById(db, resolved.provider_id);
|
|
222
232
|
if (!provider || !provider.is_active) {
|
|
223
233
|
return rejectAndReply(reply, rCtx, errors.providerUnavailable(), `Provider '${resolved.provider_id}' unavailable`, resolved.provider_id);
|
|
@@ -230,12 +240,6 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
230
240
|
const effectiveApiType = resolvedPath.effectiveApiType;
|
|
231
241
|
const effectiveUpstreamPath = resolvedPath.effectiveUpstreamPath;
|
|
232
242
|
const needsTransform = resolvedPath.needsTransform;
|
|
233
|
-
// effectiveMappingReason: 首次迭代用 resolveResult.reason,溢出时覆盖
|
|
234
|
-
let effectiveMappingReason = isFailoverIteration ? "failover_retry" : resolveResult.mappingReason;
|
|
235
|
-
// 只有当前 target 是 overflow 扩展产生的才标记
|
|
236
|
-
const resolvedIdx = cachedTargets.findIndex(t => t.provider_id === resolved.provider_id && t.backend_model === resolved.backend_model);
|
|
237
|
-
if (overflowIndices.has(resolvedIdx))
|
|
238
|
-
effectiveMappingReason = "overflow_redirect";
|
|
239
243
|
// --- routing ---
|
|
240
244
|
currentBody = { ...currentBody, model: resolved.backend_model };
|
|
241
245
|
iterationSnapshot.add({ stage: "routing", client_model: clientModel, backend_model: resolved.backend_model, provider_id: resolved.provider_id, strategy: cachedTargets.length > 1 ? "failover" : "scheduled", mapping_reason: effectiveMappingReason });
|
|
@@ -325,6 +329,12 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
325
329
|
failover: { isFailoverIteration, rootLogId: rootLogId },
|
|
326
330
|
pipelineSnapshot,
|
|
327
331
|
matcher, logFileWriter,
|
|
332
|
+
resilienceAction: resilienceResult.finalDecision?.action,
|
|
333
|
+
resilienceReason: resilienceResult.finalDecision?.action === "abort"
|
|
334
|
+
? resilienceResult.finalDecision.reason
|
|
335
|
+
: null,
|
|
336
|
+
mappingReason: effectiveMappingReason,
|
|
337
|
+
failoverTrigger: lastFailoverTrigger,
|
|
328
338
|
}, resilienceResult.attempts, resilienceResult.result, startTime);
|
|
329
339
|
collectTransportMetrics(db, clientApiType, resilienceResult.result, isStream, lastLogId, provider.id, resolved.backend_model, request, routerKeyId, getTransportStatusCode(resilienceResult.result), ctx.metadata.get("client_type"), ctx.metadata.get("session_id"), tracker, ctx.metadata);
|
|
330
340
|
// flush tool errors
|
|
@@ -366,6 +376,7 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
366
376
|
const failed = tr.kind === "throw"
|
|
367
377
|
|| ("statusCode" in tr && tr.statusCode >= HTTP_ERROR_THRESHOLD);
|
|
368
378
|
if (failed) {
|
|
379
|
+
lastFailoverTrigger = tr.kind === "throw" ? "throw" : `status_${("statusCode" in tr ? tr.statusCode : 0)}`;
|
|
369
380
|
excludeTargets.push(resolved);
|
|
370
381
|
continue;
|
|
371
382
|
}
|
|
@@ -402,9 +413,14 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
402
413
|
failover: { isFailoverIteration, rootLogId: rootLogId },
|
|
403
414
|
pipelineSnapshot,
|
|
404
415
|
matcher, logFileWriter,
|
|
416
|
+
resilienceAction: "failover",
|
|
417
|
+
resilienceReason: "provider_switch_needed",
|
|
418
|
+
mappingReason: effectiveMappingReason,
|
|
419
|
+
failoverTrigger: e.constructor.name,
|
|
405
420
|
}, e.attempts, fakeResult, startTime);
|
|
406
421
|
}
|
|
407
422
|
flushCurrentErrors();
|
|
423
|
+
lastFailoverTrigger = e.constructor.name;
|
|
408
424
|
excludeTargets.push(resolved);
|
|
409
425
|
continue;
|
|
410
426
|
}
|
|
@@ -422,7 +438,7 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
422
438
|
const errMsg = e instanceof Error ? e.message : JSON.stringify(e);
|
|
423
439
|
request.log.debug({ logId, error: errMsg, action: "upstream_error" });
|
|
424
440
|
insertRequestLog(db, {
|
|
425
|
-
id:
|
|
441
|
+
id: logId, api_type: clientApiType,
|
|
426
442
|
model: clientModel, provider_id: provider.id,
|
|
427
443
|
status_code: UPSTREAM_ERROR_STATUS, latency_ms: Date.now() - startTime, is_stream: isStream ? 1 : 0,
|
|
428
444
|
error_message: errMsg || "Upstream connection failed", created_at: new Date().toISOString(),
|
|
@@ -431,6 +447,8 @@ export async function executeFailoverLoop(ctx, errors, deps, upstreamPath, adapt
|
|
|
431
447
|
router_key_id: routerKeyId, original_model: null,
|
|
432
448
|
session_id: ctx.metadata.get("session_id"),
|
|
433
449
|
pipeline_snapshot: pipelineSnapshot,
|
|
450
|
+
transport_kind: "throw",
|
|
451
|
+
mapping_reason: rCtx.mappingReason ?? null,
|
|
434
452
|
}, (matcher || logFileWriter) ? {
|
|
435
453
|
matcher, logFileWriter, responseBody: null,
|
|
436
454
|
} : undefined);
|
|
@@ -30,6 +30,14 @@ export interface RequestLogParams extends LogRetryMeta {
|
|
|
30
30
|
pipelineSnapshot?: string | null;
|
|
31
31
|
matcher?: RetryMatcher | null;
|
|
32
32
|
logFileWriter?: LogFileWriter | null;
|
|
33
|
+
transport_kind?: string | null;
|
|
34
|
+
abort_reason?: string | null;
|
|
35
|
+
error_code?: string | null;
|
|
36
|
+
headers_sent?: number | null;
|
|
37
|
+
resilience_action?: string | null;
|
|
38
|
+
resilience_reason?: string | null;
|
|
39
|
+
mapping_reason?: string | null;
|
|
40
|
+
failover_trigger?: string | null;
|
|
33
41
|
}
|
|
34
42
|
/** 插入成功请求日志,供 openai/anthropic 插件共享 */
|
|
35
43
|
export declare function insertSuccessLog(db: Database.Database, params: RequestLogParams): void;
|
|
@@ -51,6 +59,14 @@ export interface RejectedLogParams extends LogRetryMeta {
|
|
|
51
59
|
pipelineSnapshot?: string | null;
|
|
52
60
|
matcher?: RetryMatcher | null;
|
|
53
61
|
logFileWriter?: LogFileWriter | null;
|
|
62
|
+
mapping_reason?: string | null;
|
|
63
|
+
transport_kind?: string | null;
|
|
64
|
+
abort_reason?: string | null;
|
|
65
|
+
error_code?: string | null;
|
|
66
|
+
headers_sent?: number | null;
|
|
67
|
+
resilience_action?: string | null;
|
|
68
|
+
resilience_reason?: string | null;
|
|
69
|
+
failover_trigger?: string | null;
|
|
54
70
|
}
|
|
55
71
|
/** Log a request rejected before reaching upstream */
|
|
56
72
|
export declare function insertRejectedLog(params: RejectedLogParams): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { insertRequestLog } from "../db/index.js";
|
|
2
2
|
/** 插入成功请求日志,供 openai/anthropic 插件共享 */
|
|
3
3
|
export function insertSuccessLog(db, params) {
|
|
4
|
-
const { id: logId, apiType, model, provider, isStream, startTime, clientReq, upstreamReq, status, respBody, upHdrs, isRetry = false, isFailover = false, originalRequestId = null, routerKeyId = null, originalModel = null, sessionId = null, pipelineSnapshot = null, matcher, logFileWriter } = params;
|
|
4
|
+
const { id: logId, apiType, model, provider, isStream, startTime, clientReq, upstreamReq, status, respBody, upHdrs, isRetry = false, isFailover = false, originalRequestId = null, routerKeyId = null, originalModel = null, sessionId = null, pipelineSnapshot = null, matcher, logFileWriter, transport_kind, abort_reason, error_code, headers_sent, resilience_action, resilience_reason, mapping_reason, failover_trigger } = params;
|
|
5
5
|
const writeContext = (matcher || logFileWriter) ? {
|
|
6
6
|
matcher,
|
|
7
7
|
logFileWriter,
|
|
@@ -18,11 +18,19 @@ export function insertSuccessLog(db, params) {
|
|
|
18
18
|
router_key_id: routerKeyId, original_model: originalModel,
|
|
19
19
|
session_id: sessionId,
|
|
20
20
|
pipeline_snapshot: pipelineSnapshot ?? null,
|
|
21
|
+
transport_kind: transport_kind ?? null,
|
|
22
|
+
abort_reason: abort_reason ?? null,
|
|
23
|
+
error_code: error_code ?? null,
|
|
24
|
+
headers_sent: headers_sent ?? null,
|
|
25
|
+
resilience_action: resilience_action ?? null,
|
|
26
|
+
resilience_reason: resilience_reason ?? null,
|
|
27
|
+
mapping_reason: mapping_reason ?? null,
|
|
28
|
+
failover_trigger: failover_trigger ?? null,
|
|
21
29
|
}, writeContext);
|
|
22
30
|
}
|
|
23
31
|
/** Log a request rejected before reaching upstream */
|
|
24
32
|
export function insertRejectedLog(params) {
|
|
25
|
-
const { db, logId, apiType, model, statusCode, errorMessage, startTime, isStream, routerKeyId, originalBody, clientHeaders, providerId = null, isFailover = false, originalRequestId = null, originalModel = null, sessionId = null, pipelineSnapshot = null, matcher, logFileWriter } = params;
|
|
33
|
+
const { db, logId, apiType, model, statusCode, errorMessage, startTime, isStream, routerKeyId, originalBody, clientHeaders, providerId = null, isFailover = false, originalRequestId = null, originalModel = null, sessionId = null, pipelineSnapshot = null, matcher, logFileWriter, mapping_reason, transport_kind, abort_reason, error_code, headers_sent, resilience_action, resilience_reason, failover_trigger } = params;
|
|
26
34
|
const writeContext = (matcher || logFileWriter) ? {
|
|
27
35
|
matcher,
|
|
28
36
|
logFileWriter,
|
|
@@ -45,5 +53,13 @@ export function insertRejectedLog(params) {
|
|
|
45
53
|
original_model: originalModel,
|
|
46
54
|
session_id: sessionId,
|
|
47
55
|
pipeline_snapshot: pipelineSnapshot ?? null,
|
|
56
|
+
transport_kind: transport_kind ?? null,
|
|
57
|
+
abort_reason: abort_reason ?? null,
|
|
58
|
+
error_code: error_code ?? null,
|
|
59
|
+
headers_sent: headers_sent ?? null,
|
|
60
|
+
resilience_action: resilience_action ?? null,
|
|
61
|
+
resilience_reason: resilience_reason ?? null,
|
|
62
|
+
mapping_reason: mapping_reason ?? null,
|
|
63
|
+
failover_trigger: failover_trigger ?? null,
|
|
48
64
|
}, writeContext);
|
|
49
65
|
}
|
|
@@ -32,6 +32,8 @@ export interface ResilienceResult {
|
|
|
32
32
|
result: TransportResult;
|
|
33
33
|
attempts: ResilienceAttempt[];
|
|
34
34
|
excludedTargets: Target[];
|
|
35
|
+
/** 最终 resilience 决策,用于诊断日志 */
|
|
36
|
+
finalDecision?: ResilienceDecision;
|
|
35
37
|
}
|
|
36
38
|
export type ResilienceDecision = {
|
|
37
39
|
action: "done";
|
|
@@ -153,6 +153,7 @@ export class ResilienceLayer {
|
|
|
153
153
|
result: lastResult ?? { kind: "error", statusCode: 502, body: "Iteration cap exceeded", headers: {}, sentHeaders: {}, sentBody: "" },
|
|
154
154
|
attempts: allAttempts,
|
|
155
155
|
excludedTargets,
|
|
156
|
+
finalDecision: { action: "abort", reason: "iteration_cap_exceeded" },
|
|
156
157
|
};
|
|
157
158
|
}
|
|
158
159
|
const excludedSet = new Set(excludedTargets.map(e => `${e.provider_id}:${e.backend_model}`));
|
|
@@ -162,6 +163,7 @@ export class ResilienceLayer {
|
|
|
162
163
|
result: lastResult ?? { kind: "error", statusCode: 502, body: "All targets exhausted", headers: {}, sentHeaders: {}, sentBody: "" },
|
|
163
164
|
attempts: allAttempts,
|
|
164
165
|
excludedTargets,
|
|
166
|
+
finalDecision: { action: "abort", reason: "all_targets_exhausted" },
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
169
|
const currentTarget = available[0];
|
|
@@ -183,6 +185,8 @@ export class ResilienceLayer {
|
|
|
183
185
|
statusCode: null, error: (throwErr instanceof Error ? throwErr.message : JSON.stringify(throwErr)),
|
|
184
186
|
latencyMs: Date.now() - start, responseBody: null,
|
|
185
187
|
responseHeaders: null, resultKind: transportResult.kind,
|
|
188
|
+
error_code: (throwErr instanceof Error && 'code' in throwErr) ? throwErr.code ?? null : null,
|
|
189
|
+
headers_sent: transportResult.headersSent ?? null,
|
|
186
190
|
});
|
|
187
191
|
}
|
|
188
192
|
else {
|
|
@@ -192,6 +196,7 @@ export class ResilienceLayer {
|
|
|
192
196
|
latencyMs: Date.now() - start, responseBody: extractBody(transportResult),
|
|
193
197
|
responseHeaders: extractHeaders(transportResult) ?? null,
|
|
194
198
|
resultKind: transportResult.kind,
|
|
199
|
+
headers_sent: transportResult.kind === "stream_error" ? transportResult.headersSent ?? null : null,
|
|
195
200
|
});
|
|
196
201
|
}
|
|
197
202
|
const state = {
|
|
@@ -202,7 +207,7 @@ export class ResilienceLayer {
|
|
|
202
207
|
const decision = this.decide(transportResult, state, config);
|
|
203
208
|
switch (decision.action) {
|
|
204
209
|
case "done":
|
|
205
|
-
return { result: transportResult, attempts: allAttempts, excludedTargets };
|
|
210
|
+
return { result: transportResult, attempts: allAttempts, excludedTargets, finalDecision: decision };
|
|
206
211
|
case "retry":
|
|
207
212
|
globalAttemptIndex++;
|
|
208
213
|
await sleep(decision.delayMs);
|
|
@@ -218,7 +223,7 @@ export class ResilienceLayer {
|
|
|
218
223
|
}
|
|
219
224
|
continue;
|
|
220
225
|
case "abort":
|
|
221
|
-
return { result: transportResult, attempts: allAttempts, excludedTargets };
|
|
226
|
+
return { result: transportResult, attempts: allAttempts, excludedTargets, finalDecision: decision };
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
}
|
|
@@ -24,5 +24,9 @@ export declare function logResilienceResult(db: Database.Database, params: {
|
|
|
24
24
|
test: (statusCode: number, body: string) => boolean;
|
|
25
25
|
} | null;
|
|
26
26
|
logFileWriter?: LogFileWriter | null;
|
|
27
|
+
resilienceAction?: string | null;
|
|
28
|
+
resilienceReason?: string | null;
|
|
29
|
+
mappingReason?: string | null;
|
|
30
|
+
failoverTrigger?: string | null;
|
|
27
31
|
}, attempts: ResilienceAttempt[], result: TransportResult, startTime: number): string;
|
|
28
32
|
export declare function collectTransportMetrics(db: Database.Database, apiType: "openai" | "openai-responses" | "anthropic", result: TransportResult, isStream: boolean, lastSuccessLogId: string, providerId: string, backendModel: string, request: FastifyRequest, routerKeyId?: string | null, statusCode?: number | null, clientType?: string, sessionId?: string, tracker?: RequestTracker, metadata?: Map<string, unknown>): void;
|
|
@@ -53,11 +53,32 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
53
53
|
const isFailoverIteration = params.failover?.isFailoverIteration ?? false;
|
|
54
54
|
const rootLogId = params.failover?.rootLogId ?? params.logId;
|
|
55
55
|
let lastSuccessLogId = params.logId;
|
|
56
|
-
for (
|
|
56
|
+
for (let attemptIdx = 0; attemptIdx < attempts.length; attemptIdx++) {
|
|
57
|
+
const attempt = attempts[attemptIdx];
|
|
57
58
|
const isOriginal = attempt.attemptIndex === 0;
|
|
58
59
|
const attemptLogId = isOriginal ? params.logId : randomUUID();
|
|
59
60
|
const isFailoverLog = isOriginal && isFailoverIteration;
|
|
60
61
|
const parentId = isOriginal ? (isFailoverIteration ? rootLogId : null) : params.logId;
|
|
62
|
+
// 中间 attempt 的 resilience_action 为 "retry"(否则不会继续尝试)
|
|
63
|
+
const isLastAttempt = attemptIdx === attempts.length - 1;
|
|
64
|
+
const attemptResilienceAction = isLastAttempt
|
|
65
|
+
? (params.resilienceAction ?? null)
|
|
66
|
+
: "retry";
|
|
67
|
+
// 中间 attempt 的 resilience_reason 为 null(retry 不携带 reason,只有 abort 才有)
|
|
68
|
+
const attemptResilienceReason = isLastAttempt
|
|
69
|
+
? (params.resilienceReason ?? null)
|
|
70
|
+
: null;
|
|
71
|
+
// 诊断字段:每次 attempt 通用的计算
|
|
72
|
+
const diagnosticFields = {
|
|
73
|
+
transport_kind: attempt.resultKind,
|
|
74
|
+
abort_reason: attempt.resultKind === "stream_abort" && result.kind === "stream_abort" ? result.abortReason ?? null : null,
|
|
75
|
+
error_code: attempt.error_code ?? null,
|
|
76
|
+
headers_sent: attempt.headers_sent != null ? (attempt.headers_sent ? 1 : 0) : null,
|
|
77
|
+
resilience_action: attemptResilienceAction,
|
|
78
|
+
resilience_reason: attemptResilienceReason,
|
|
79
|
+
mapping_reason: params.mappingReason ?? null,
|
|
80
|
+
failover_trigger: params.failoverTrigger ?? null,
|
|
81
|
+
};
|
|
61
82
|
// 构建 writeContext(所有路径共享,error/stream_error 路径 status >= 400 所以 preserveDetail=true,但文件写入仍需执行)
|
|
62
83
|
const attemptWriteContext = (params.matcher || params.logFileWriter) ? {
|
|
63
84
|
matcher: params.matcher,
|
|
@@ -81,6 +102,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
81
102
|
router_key_id: params.routerKeyId, original_model: params.originalModel,
|
|
82
103
|
session_id: params.sessionId,
|
|
83
104
|
pipeline_snapshot: params.pipelineSnapshot ?? null,
|
|
105
|
+
...diagnosticFields,
|
|
84
106
|
}, attemptWriteContext);
|
|
85
107
|
}
|
|
86
108
|
else if (attempt.error) {
|
|
@@ -99,6 +121,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
99
121
|
router_key_id: params.routerKeyId, original_model: params.originalModel,
|
|
100
122
|
session_id: params.sessionId,
|
|
101
123
|
pipeline_snapshot: params.pipelineSnapshot ?? null,
|
|
124
|
+
...diagnosticFields,
|
|
102
125
|
}, attemptWriteContext);
|
|
103
126
|
}
|
|
104
127
|
else if (attempt.statusCode !== UPSTREAM_SUCCESS) {
|
|
@@ -115,6 +138,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
115
138
|
router_key_id: params.routerKeyId, original_model: params.originalModel,
|
|
116
139
|
session_id: params.sessionId,
|
|
117
140
|
pipeline_snapshot: params.pipelineSnapshot ?? null,
|
|
141
|
+
...diagnosticFields,
|
|
118
142
|
}, attemptWriteContext);
|
|
119
143
|
}
|
|
120
144
|
else {
|
|
@@ -136,6 +160,7 @@ export function logResilienceResult(db, params, attempts, result, startTime) {
|
|
|
136
160
|
pipelineSnapshot: params.pipelineSnapshot,
|
|
137
161
|
matcher: params.matcher,
|
|
138
162
|
logFileWriter: params.logFileWriter,
|
|
163
|
+
...diagnosticFields,
|
|
139
164
|
});
|
|
140
165
|
lastSuccessLogId = attemptLogId;
|
|
141
166
|
}
|
|
@@ -85,7 +85,7 @@ class StreamProxy {
|
|
|
85
85
|
result = { kind: "stream_error", ...base, body: extra.body, headers: this.sseHeaders, headersSent: this.headersSent || undefined };
|
|
86
86
|
break;
|
|
87
87
|
case "stream_abort":
|
|
88
|
-
result = { kind: "stream_abort", ...base, metrics: extra.metrics, timeoutContext: extra.timeoutContext, timeoutMs: extra.timeoutMs };
|
|
88
|
+
result = { kind: "stream_abort", ...base, metrics: extra.metrics, timeoutContext: extra.timeoutContext, timeoutMs: extra.timeoutMs, abortReason: extra.abortReason };
|
|
89
89
|
break;
|
|
90
90
|
}
|
|
91
91
|
// deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中执行,
|
|
@@ -155,7 +155,7 @@ class StreamProxy {
|
|
|
155
155
|
}
|
|
156
156
|
catch { /* reply may be destroyed */ } // eslint-disable-line taste/no-silent-catch
|
|
157
157
|
}
|
|
158
|
-
this.terminal("stream_abort", { metrics: this.collectMetrics(false), timeoutContext: this.timeoutContext, timeoutMs: this.timeoutMs });
|
|
158
|
+
this.terminal("stream_abort", { metrics: this.collectMetrics(false), timeoutContext: this.timeoutContext, timeoutMs: this.timeoutMs, abortReason: "idle_timeout" });
|
|
159
159
|
}, this.timeoutMs);
|
|
160
160
|
}
|
|
161
161
|
startStreaming() {
|
|
@@ -175,7 +175,7 @@ class StreamProxy {
|
|
|
175
175
|
}
|
|
176
176
|
catch {
|
|
177
177
|
// 客户端在 state transition 和 writeHead 之间断连,可安全忽略
|
|
178
|
-
this.terminal("stream_abort");
|
|
178
|
+
this.terminal("stream_abort", { abortReason: "client_disconnect" });
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
if (this.metricsTransform) {
|
|
@@ -209,7 +209,7 @@ class StreamProxy {
|
|
|
209
209
|
if (this.state === "BUFFERING" || this.state === "STREAMING") {
|
|
210
210
|
this.transition("ABORTED");
|
|
211
211
|
}
|
|
212
|
-
this.terminal("stream_abort", { metrics: this.collectMetrics(false) });
|
|
212
|
+
this.terminal("stream_abort", { metrics: this.collectMetrics(false), abortReason: "client_disconnect" });
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
215
|
onData(chunk) {
|
|
@@ -279,8 +279,7 @@ class StreamProxy {
|
|
|
279
279
|
}
|
|
280
280
|
this.pipeEntry.write(chunk);
|
|
281
281
|
if (this.loopGuard?.isTriggered()) {
|
|
282
|
-
this.terminal("stream_abort", { metrics: this.collectMetrics(false) });
|
|
283
|
-
return;
|
|
282
|
+
this.terminal("stream_abort", { metrics: this.collectMetrics(false), abortReason: "loop_detection" });
|
|
284
283
|
}
|
|
285
284
|
}
|
|
286
285
|
onEnd() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Gt as e,et as t,gt as n,it as r,qt as i,r as a,yt as o}from"./button-DUeiLnX9.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)=>(n(),t(`div`,{"data-slot":`card`,"data-size":r.size,class:i(e(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))},[o(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)=>(n(),t(`div`,{"data-slot":`card-content`,class:i(e(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[o(r.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Gt as e,et as t,gt as n,it as r,qt as i,r as a,yt as o}from"./button-DUeiLnX9.js";var s=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(n(),t(`div`,{"data-slot":`card-header`,class:i(e(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))},[o(r.$slots,`default`)],2))}}),c=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(n(),t(`div`,{"data-slot":`card-title`,class:i(e(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[o(r.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Gt as t,H as n,J as r,L as i,Lt as a,Q as o,X as s,Xt as c,Z as l,et as u,gt as d,it as f,kt as p,qt as m,rt as h,vt as g}from"./button-DUeiLnX9.js";import{b as _,ft as v,ht as y,v as b,y as x}from"./index-D_GZgCIM.js";var S=i(`chevron-right`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),C=i(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),w=[`onMouseenter`],T={class:`truncate max-w-40`},E={key:0,class:`ml-1 text-[10px] px-1 py-px rounded bg-emerald-500/15 text-emerald-400 shrink-0`},D=[`onMouseenter`],O=[`onClick`],k={class:`truncate`},A={key:0,class:`shrink-0 text-xs text-muted-foreground`},j={key:0,class:`px-2 py-1.5 text-sm text-muted-foreground`},M=f({__name:`CascadingSelect`,props:{groups:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(i,{emit:f}){let{t:y}=n(),C=i,M=s(()=>C.placeholder||y(`common.selectPlaceholder`)),N=f,P=a(!1),F=a(null),I=s(()=>{if(!C.modelValue)return``;let e=C.groups.find(e=>e.key===C.modelValue.groupKey);if(!e)return``;let t=e.options.find(e=>e.value===C.modelValue.value);return t?`${e.label} / ${t.label}`:``});function L(e,t){N(`update:modelValue`,{groupKey:e,value:t}),P.value=!1}function R(e){P.value=e,e||(F.value=null)}return(n,a)=>(d(),o(t(_),{open:P.value,"onUpdate:open":R},{default:p(()=>[h(t(b),{"as-child":``},{default:p(()=>[l(`div`,{class:m([`flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background cursor-pointer hover:bg-accent hover:text-accent-foreground`,[i.compact?`h-8 text-xs px-2.5 py-1`:`h-10 text-sm px-3 py-2`,{"ring-2 ring-ring ring-offset-2":P.value}]])},[l(`span`,{class:m([`truncate`,i.modelValue?`text-foreground`:`text-muted-foreground`])},c(I.value||M.value),3),h(t(v),{class:`h-4 w-4 shrink-0 opacity-50`})],2)]),_:1}),h(t(x),{align:`start`,"side-offset":4,class:`z-[200] w-auto min-w-56 overflow-visible p-1`},{default:p(()=>[(d(!0),u(r,null,g(i.groups,n=>(d(),u(`div`,{key:n.key,class:m([`relative flex cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground`,{"bg-accent text-accent-foreground z-10":F.value===n.key}]),onMouseenter:e=>F.value=n.key},[l(`span`,T,c(n.label),1),n.badge?(d(),u(`span`,E,c(n.badge),1)):e(``,!0),h(t(S),{class:`ml-1 h-4 w-4 shrink-0 opacity-50`}),F.value===n.key&&n.options.length>0?(d(),u(`div`,{key:1,class:`absolute left-full top-0 ml-0.5 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md`,onMouseenter:e=>F.value=n.key},[(d(!0),u(r,null,g(n.options,t=>(d(),u(`div`,{key:t.value,class:m([`flex cursor-pointer items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground`,{"bg-accent text-accent-foreground":i.modelValue?.groupKey===n.key&&i.modelValue?.value===t.value}]),onClick:e=>L(n.key,t.value)},[l(`span`,k,c(t.label),1),t.tag?(d(),u(`span`,A,c(t.tag),1)):e(``,!0)],10,O))),128))],40,D)):e(``,!0)],42,w))),128)),i.groups.length===0?(d(),u(`div`,j,c(t(y)(`common.noOptions`)),1)):e(``,!0)]),_:1})]),_:1},8,[`open`]))}}),N=f({__name:`CascadingModelSelect`,props:{providers:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean}},emits:[`update:modelValue`],setup(e,{emit:t}){let{t:r}=n(),i=e,a=s(()=>i.placeholder||r(`mappings.selectProviderModel`)),c=t,l=s(()=>i.providers.map(e=>({key:e.provider.id,label:e.provider.name,badge:e.isNew?r(`common.new`):void 0,options:e.models.map(e=>({value:e.name,label:e.name,tag:y(e.contextWindow)}))}))),u=s(()=>i.modelValue?{groupKey:i.modelValue.provider_id,value:i.modelValue.model}:void 0);function f(e){c(`update:modelValue`,{provider_id:e.groupKey,model:e.value})}return(t,n)=>(d(),o(M,{groups:l.value,"model-value":u.value,placeholder:a.value,compact:e.compact,"onUpdate:modelValue":f},null,8,[`groups`,`model-value`,`placeholder`,`compact`]))}});export{C as n,N as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Gt as t,Jt as n,K as r,Q as i,X as a,dt as o,gt as s,i as c,it as l,kt as u,m as d,o as f,ot as p,q as m,r as h,rt as g,x as _,xt as v,yt as y}from"./button-DUeiLnX9.js";import{t as b}from"./VisuallyHiddenInput-_XDrpRgf.js";import{t as x}from"./RovingFocusItem-CINTW0LN.js";import{J as S,K as C,R as w,U as T,V as E,X as D,pt as O}from"./index-D_GZgCIM.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>D(e,t)):D(e,t)}var[A,j]=S(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=S(`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(n,{emit:l}){let p=n,h=l,{forwardRef:g,currentElement:_}=f(),S=A(null),w=d(p,`modelValue`,h,{defaultValue:p.defaultValue??p.falseValue,passive:p.modelValue===void 0}),E=a(()=>S?.disabled.value||p.disabled),O=a(()=>D(w.value,p.trueValue)),j=a(()=>C(S?.modelValue.value)?w.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,p.value));function P(){if(C(S?.modelValue.value))w.value===`indeterminate`?w.value=p.trueValue:w.value=O.value?p.falseValue:p.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,p.value)){let t=e.findIndex(e=>D(e,p.value));e.splice(t,1)}else e.push(p.value);S.modelValue.value=e}}let I=T(_),L=a(()=>p.id&&_.value?document.querySelector(`[for="${p.id}"]`)?.innerText:void 0);return F({disabled:E,state:j}),(n,a)=>(s(),i(v(t(S)?.rovingFocus.value?t(x):t(c)),o(n.$attrs,{id:n.id,ref:t(g),role:`checkbox`,"as-child":n.asChild,as:n.as,type:n.as===`button`?`button`:void 0,"aria-checked":t(M)(j.value)?`mixed`:j.value,"aria-required":n.required,"aria-label":n.$attrs[`aria-label`]||L.value,"data-state":t(N)(j.value),"data-disabled":E.value?``:void 0,disabled:E.value,focusable:t(S)?.rovingFocus.value?!E.value:void 0,onKeydown:r(m(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:u(()=>[y(n.$slots,`default`,{modelValue:t(w),state:j.value}),t(I)&&n.name&&!t(S)?(s(),i(t(b),{key:0,type:`checkbox`,checked:!!j.value,name:n.name,value:n.value,disabled:E.value,required:n.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):e(`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(e){let{forwardRef:n}=f(),r=P();return(e,a)=>(s(),i(t(w),{present:e.forceMount||t(M)(t(r).state.value)||t(r).state.value===!0},{default:u(()=>[g(t(c),o({ref:t(n),"data-state":t(N)(t(r).state.value),"data-disabled":t(r).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:u(()=>[y(e.$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(e,{emit:r}){let a=e,c=r,l=E(_(a,`class`),c);return(e,r)=>(s(),i(t(I),o({"data-slot":`checkbox`},t(l),{class:t(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`,a.class)}),{default:u(r=>[g(t(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:u(()=>[y(e.$slots,`default`,n(p(r)),()=>[g(t(O))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Et as t,Gt as n,Ht as r,Jt as i,Lt as a,Q as o,X as s,d as c,dt as l,ft as u,gt as d,i as f,it as p,kt as m,m as h,mt as g,o as _,ot as v,rt as y,yt as b}from"./button-DUeiLnX9.js";import{B as x,J as S,R as C,V as w}from"./index-D_GZgCIM.js";var[T,E]=S(`CollapsibleRoot`),D=p({__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:i}){let a=e,s=h(a,`open`,i,{defaultValue:a.defaultOpen,passive:a.open===void 0}),{disabled:c,unmountOnHide:l}=r(a);return E({contentId:``,disabled:c,open:s,unmountOnHide:l,onOpenToggle:()=>{c.value||(s.value=!s.value)}}),t({open:s}),_(),(e,t)=>(d(),o(n(f),{as:e.as,"as-child":a.asChild,"data-state":n(s)?`open`:`closed`,"data-disabled":n(c)?``:void 0},{default:m(()=>[b(e.$slots,`default`,{open:n(s)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=p({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(r,{emit:i}){let p=r,h=i,v=T();v.contentId||=x(void 0,`reka-collapsible-content`);let S=a(),{forwardRef:w,currentElement:E}=_(),D=a(0),O=a(0),k=s(()=>v.open.value),A=a(k.value),j=a();t(()=>[k.value,S.value?.present],async()=>{await u();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=s(()=>A.value&&v.open.value);return g(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{v.onOpenToggle(),h(`contentFound`)})}),(t,r)=>(d(),o(n(C),{ref_key:`presentRef`,ref:S,present:t.forceMount||n(v).open.value,"force-mount":!0},{default:m(({present:r})=>[y(n(f),l(t.$attrs,{id:n(v).contentId,ref:n(w),"as-child":p.asChild,as:t.as,hidden:r?void 0:n(v).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:n(v).open.value?`open`:`closed`,"data-disabled":n(v).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:m(()=>[!n(v).unmountOnHide.value||r?b(t.$slots,`default`,{key:0}):e(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=p({__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 r=w(e,t);return(e,t)=>(d(),o(n(D),l({"data-slot":`collapsible`},n(r)),{default:m(t=>[b(e.$slots,`default`,i(v(t)))]),_:3},16))}}),A=p({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(d(),o(n(O),l({"data-slot":`collapsible-content`},t),{default:m(()=>[b(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Gt as e,Q as t,dt as n,gt as r,i,it as a,kt as o,o as s,yt as c}from"./button-DUeiLnX9.js";import{r as l}from"./CollapsibleContent-D8Ge0Io5.js";var u=a({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(n){let a=n;s();let u=l();return(n,s)=>(r(),t(e(i),{type:n.as===`button`?`button`:void 0,as:n.as,"as-child":a.asChild,"aria-controls":e(u).contentId,"aria-expanded":e(u).open.value,"data-state":e(u).open.value?`open`:`closed`,"data-disabled":e(u).disabled?.value?``:void 0,disabled:e(u).disabled?.value,onClick:e(u).onOpenToggle},{default:o(()=>[c(n.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=a({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(i){let a=i;return(i,s)=>(r(),t(e(u),n({"data-slot":`collapsible-trigger`},a),{default:o(()=>[c(i.$slots,`default`)]),_:3},16))}});export{d as t};
|