llm-simple-router 1.1.2-beta.2 → 1.1.2
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/providers.js +4 -6
- package/dist/admin/quick-setup.js +0 -2
- package/dist/app/register-routes.js +0 -2
- package/dist/config/model-context.d.ts +0 -2
- package/dist/config/model-context.js +0 -4
- package/dist/core/concurrency/semaphore.d.ts +2 -15
- package/dist/core/concurrency/semaphore.js +4 -36
- package/dist/core/constants.d.ts +0 -1
- package/dist/core/constants.js +0 -3
- package/dist/core/monitor/request-tracker.d.ts +0 -6
- package/dist/core/monitor/request-tracker.js +0 -15
- package/dist/core/types.d.ts +1 -1
- package/dist/db/providers.d.ts +3 -12
- package/dist/db/providers.js +12 -23
- package/dist/index.js +0 -2
- package/dist/proxy/handler/iteration-setup.js +2 -3
- package/dist/proxy/orchestration/orchestrator.d.ts +1 -1
- package/dist/proxy/orchestration/orchestrator.js +11 -28
- package/dist/proxy/orchestration/resilience.d.ts +1 -3
- package/dist/proxy/orchestration/resilience.js +2 -17
- package/dist/proxy/orchestration/scope.d.ts +1 -1
- package/dist/proxy/orchestration/scope.js +2 -2
- package/dist/proxy/transport/http.d.ts +2 -14
- package/dist/proxy/transport/http.js +2 -24
- package/dist/proxy/transport/stream.d.ts +3 -56
- package/dist/proxy/transport/stream.js +44 -97
- package/dist/proxy/transport/transport-fn.d.ts +1 -2
- package/dist/proxy/transport/transport-fn.js +3 -3
- package/frontend-dist/assets/AuthLayout-jELzICkx.js +1 -0
- package/frontend-dist/assets/Card-uC_v0CEa.js +1 -0
- package/frontend-dist/assets/CardContent-CP3OiCj4.js +1 -0
- package/frontend-dist/assets/CardTitle-DGxuW5DZ.js +1 -0
- package/frontend-dist/assets/CascadingModelSelect-Dzk7rxIN.js +1 -0
- package/frontend-dist/assets/Checkbox-C1aVqGdC.js +1 -0
- package/frontend-dist/assets/CollapsibleContent-pBG4UkLo.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-D5TkgXmz.js +1 -0
- package/frontend-dist/assets/ConcurrencyControl-5GweS-rY.js +1 -0
- package/frontend-dist/assets/Dashboard-wjd3d3qk.js +3 -0
- package/frontend-dist/assets/{Input-DRF4ONbF.js → Input-DMDfXTXB.js} +1 -1
- package/frontend-dist/assets/Label-BQXea0mo.js +1 -0
- package/frontend-dist/assets/Login-DNpCjxrY.js +1 -0
- package/frontend-dist/assets/Logs-BuL2Z0sF.js +1 -0
- package/frontend-dist/assets/ModelMappings-DeRhu-2N.js +1 -0
- package/frontend-dist/assets/Monitor-V30dnACo.js +1 -0
- package/frontend-dist/assets/Providers-BkkQhSTb.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-DjgebwfU.js +1 -0
- package/frontend-dist/assets/QuickSetup-BTVjEiU7.js +1 -0
- package/frontend-dist/assets/RetryRules-DFBHBG-B.js +1 -0
- package/frontend-dist/assets/RouterKeys-DZADvMfh.js +1 -0
- package/frontend-dist/assets/{RovingFocusItem-BIoz-v9k.js → RovingFocusItem-5H5eE6G2.js} +1 -1
- package/frontend-dist/assets/Schedules-DUubZ2uN.js +1 -0
- package/frontend-dist/assets/Separator-BqIs_Dy3.js +1 -0
- package/frontend-dist/assets/Settings-CO59WLOZ.js +6 -0
- package/frontend-dist/assets/Setup-Br7JZKNp.js +1 -0
- package/frontend-dist/assets/Skeleton-gVyjaP-y.js +1 -0
- package/frontend-dist/assets/Switch-D1ER0j6H.js +1 -0
- package/frontend-dist/assets/TableHeader-MqyrNSsx.js +1 -0
- package/frontend-dist/assets/TabsTrigger-DdjWJbUq.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-DRthlI6j.js +3 -0
- package/frontend-dist/assets/{VisuallyHiddenInput-Dz0c537Q.js → VisuallyHiddenInput-DrNFhnVL.js} +1 -1
- package/frontend-dist/assets/arrow-down-BFgGYafs.js +1 -0
- package/frontend-dist/assets/badge-Db4OYMEf.js +1 -0
- package/frontend-dist/assets/{button-DhbTyZqO.js → button-BBiWml8B.js} +2 -2
- package/frontend-dist/assets/chevron-right-DYwStkJr.js +1 -0
- package/frontend-dist/assets/dialog-DRYeWncC.js +1 -0
- package/frontend-dist/assets/{image-BAKOQH7r.js → image-zYdpUIEA.js} +1 -1
- package/frontend-dist/assets/index-DTujoAWx.js +58 -0
- package/frontend-dist/assets/index-DyQ39g4W.css +1 -0
- package/frontend-dist/assets/model-patches-DIy-rFuq.js +1 -0
- package/frontend-dist/assets/{pencil-CQiZ13On.js → pencil-C3-MFg-d.js} +1 -1
- package/frontend-dist/assets/plus-xmIDnujf.js +1 -0
- package/frontend-dist/assets/quickSetup-CqxQRMCR.js +1 -0
- package/frontend-dist/assets/quickSetup-DplqYrvf.js +1 -0
- package/frontend-dist/assets/search-BxNrTsG8.js +1 -0
- package/frontend-dist/assets/{sparkles-o5rM_OIC.js → sparkles-B5RWZZuf.js} +1 -1
- package/frontend-dist/assets/transform-domain-KBixlLXR.js +1 -0
- package/frontend-dist/assets/{trash-2-DfV00PQC.js → trash-2-Dn3T5-Z1.js} +1 -1
- package/frontend-dist/assets/{useClipboard-BEGcEu2g.js → useClipboard-Bx3CrPal.js} +1 -1
- package/frontend-dist/assets/useLogRetention-CdccNhYN.js +1 -0
- package/frontend-dist/assets/{useProviderGroups-CpH380OR.js → useProviderGroups-Og5FpCPe.js} +1 -1
- package/frontend-dist/index.html +3 -3
- package/package.json +1 -1
- package/frontend-dist/assets/AuthLayout-CNJ18LiO.js +0 -1
- package/frontend-dist/assets/Card-DHdWC9Sr.js +0 -1
- package/frontend-dist/assets/CardContent-D3XiSLZZ.js +0 -1
- package/frontend-dist/assets/CardTitle-ouEnIzFR.js +0 -1
- package/frontend-dist/assets/CascadingModelSelect-uCR_78CB.js +0 -1
- package/frontend-dist/assets/Checkbox-D4B5Gy-L.js +0 -1
- package/frontend-dist/assets/CollapsibleContent-D5uuYzAy.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-7lOShWej.js +0 -1
- package/frontend-dist/assets/ConcurrencyControl-DSpn8cI_.js +0 -1
- package/frontend-dist/assets/Dashboard-DaMz3m0K.js +0 -3
- package/frontend-dist/assets/Label-muHpCvRx.js +0 -1
- package/frontend-dist/assets/Login-CuQA8bbc.js +0 -1
- package/frontend-dist/assets/Logs-CKO3germ.js +0 -1
- package/frontend-dist/assets/ModelMappings-De98UugX.js +0 -1
- package/frontend-dist/assets/Monitor-BzeTXX7u.js +0 -1
- package/frontend-dist/assets/Providers-C7TMCyLZ.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-CaXVxlxk.js +0 -1
- package/frontend-dist/assets/QuickSetup-9OsD4eib.js +0 -1
- package/frontend-dist/assets/RetryRules-CZtQYpr0.js +0 -1
- package/frontend-dist/assets/RouterKeys-WAbHBrzu.js +0 -1
- package/frontend-dist/assets/Schedules-DgxMv_wm.js +0 -1
- package/frontend-dist/assets/Separator-_gcj_6Dd.js +0 -1
- package/frontend-dist/assets/Settings-w33AM_0F.js +0 -6
- package/frontend-dist/assets/Setup-D6c5svHh.js +0 -1
- package/frontend-dist/assets/Skeleton-wP01Qhzi.js +0 -1
- package/frontend-dist/assets/Switch-BnSoCbqK.js +0 -1
- package/frontend-dist/assets/TableHeader-oSu5tayf.js +0 -1
- package/frontend-dist/assets/TabsTrigger-CzLaOeN9.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-lCG2BxB4.js +0 -3
- package/frontend-dist/assets/arrow-down-DBlAmBEr.js +0 -1
- package/frontend-dist/assets/badge-qilWWy3M.js +0 -1
- package/frontend-dist/assets/chevron-right-B1eJ5usR.js +0 -1
- package/frontend-dist/assets/dialog-J16vWuXK.js +0 -1
- package/frontend-dist/assets/index-BoaXEmuZ.js +0 -58
- package/frontend-dist/assets/index-Db9D0WPf.css +0 -1
- package/frontend-dist/assets/model-patches-B47btKty.js +0 -1
- package/frontend-dist/assets/plus-BVJPX-4z.js +0 -1
- package/frontend-dist/assets/quickSetup-jgJgPUcH.js +0 -1
- package/frontend-dist/assets/quickSetup-qTjp3Z6J.js +0 -1
- package/frontend-dist/assets/search-DzYxIOWW.js +0 -1
- package/frontend-dist/assets/transform-domain-DczpyJVT.js +0 -1
- package/frontend-dist/assets/useLogRetention-BT7mqSLb.js +0 -1
|
@@ -2,7 +2,6 @@ import { request as httpRequestFn } from "http";
|
|
|
2
2
|
import { request as httpsRequestFn } from "https";
|
|
3
3
|
import { UPSTREAM_SUCCESS, filterHeaders } from "../types.js";
|
|
4
4
|
import { buildUpstreamUrl } from "../proxy-core.js";
|
|
5
|
-
import { DEFAULT_GET_TIMEOUT_MS } from "../../core/constants.js";
|
|
6
5
|
// Re-export callStream from stream-proxy.ts for external consumers
|
|
7
6
|
export { callStream } from "./stream.js";
|
|
8
7
|
// ---------- Constants ----------
|
|
@@ -29,31 +28,13 @@ export function buildRequestOptions(url, headers, method = "POST") {
|
|
|
29
28
|
};
|
|
30
29
|
}
|
|
31
30
|
// ---------- callNonStream ----------
|
|
32
|
-
export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders, agent
|
|
31
|
+
export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath, buildHeaders, agent) {
|
|
33
32
|
return new Promise((resolve) => {
|
|
34
33
|
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
35
34
|
const payload = JSON.stringify(body);
|
|
36
35
|
const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
|
|
37
36
|
const options = buildRequestOptions(url, upstreamHeaders);
|
|
38
37
|
const req = _transportInternals.createUpstreamRequest(url, options, agent);
|
|
39
|
-
// 上游无活动超时:0/Infinity 跳过(与 StreamProxy idleTimer 守卫对称)。
|
|
40
|
-
// destroy 必须带 error 参数,否则不 emit 'error' 事件,Promise 永挂。
|
|
41
|
-
const timeoutMs = opts?.timeoutMs;
|
|
42
|
-
if (timeoutMs !== undefined && Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
43
|
-
req.setTimeout(timeoutMs);
|
|
44
|
-
req.on("timeout", () => req.destroy(new Error("upstream inactivity timeout")));
|
|
45
|
-
}
|
|
46
|
-
// 客户端断连:abort 信号穿透到上游 socket,立即切断连接。
|
|
47
|
-
if (opts?.signal) {
|
|
48
|
-
const signal = opts.signal;
|
|
49
|
-
const abort = () => req.destroy(new Error("client aborted"));
|
|
50
|
-
if (signal.aborted) {
|
|
51
|
-
abort();
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
38
|
req.on("response", (res) => {
|
|
58
39
|
const chunks = [];
|
|
59
40
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -91,15 +72,12 @@ export function callNonStream(backend, apiKey, body, clientHeaders, upstreamPath
|
|
|
91
72
|
req.end();
|
|
92
73
|
});
|
|
93
74
|
}
|
|
94
|
-
export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeaders, agent
|
|
75
|
+
export function callGet(backend, apiKey, clientHeaders, upstreamPath, buildHeaders, agent) {
|
|
95
76
|
return new Promise((resolve, reject) => {
|
|
96
77
|
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
97
78
|
const headers = buildHeaders(clientHeaders, apiKey);
|
|
98
79
|
const options = buildRequestOptions(url, headers, "GET");
|
|
99
80
|
const req = _transportInternals.createUpstreamRequest(url, options, agent);
|
|
100
|
-
// GET 探测默认 30s 超时;destroy(error) 触发 'error' 事件 → reject。
|
|
101
|
-
req.setTimeout(opts?.timeoutMs ?? DEFAULT_GET_TIMEOUT_MS);
|
|
102
|
-
req.on("timeout", () => req.destroy(new Error("GET timeout")));
|
|
103
81
|
req.on("response", (res) => {
|
|
104
82
|
const chunks = [];
|
|
105
83
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -1,65 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { Agent, ClientRequest, IncomingMessage } from "http";
|
|
1
|
+
import type { Agent } from "http";
|
|
3
2
|
import type { FastifyReply } from "fastify";
|
|
4
3
|
import type { RawHeaders, TransportResult } from "../types.js";
|
|
5
4
|
import type { SSEMetricsTransform } from "../../metrics/sse-metrics-transform.js";
|
|
6
5
|
import type { StreamLoopGuard } from "../../core/loop-prevention/index.js";
|
|
7
|
-
import { type BuildHeadersFn
|
|
8
|
-
/** callStream 选项:connectTimeoutMs 为响应头前的无活动超时(复用 stream timeout 语义)。 */
|
|
9
|
-
export interface StreamCallOpts extends TransportCallOpts {
|
|
10
|
-
connectTimeoutMs?: number;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* SSE 流式代理状态机。导出仅为单元测试,非公开 API。
|
|
14
|
-
* 负责 buffering/streaming 状态转换、idle 超时、上游资源销毁。
|
|
15
|
-
*/
|
|
16
|
-
export declare class StreamProxy {
|
|
17
|
-
private readonly statusCode;
|
|
18
|
-
private readonly sentUpstreamHeaders;
|
|
19
|
-
private readonly reply;
|
|
20
|
-
private readonly metricsTransform;
|
|
21
|
-
private readonly checkEarlyError;
|
|
22
|
-
private readonly timeoutMs;
|
|
23
|
-
private readonly loopGuard;
|
|
24
|
-
private readonly timeoutContext?;
|
|
25
|
-
private readonly onTimeoutAbort?;
|
|
26
|
-
private readonly upstreamRes?;
|
|
27
|
-
private readonly upstreamReq?;
|
|
28
|
-
private state;
|
|
29
|
-
private resolved;
|
|
30
|
-
private resolveFn;
|
|
31
|
-
private pendingResult;
|
|
32
|
-
private readonly bufferChunks;
|
|
33
|
-
private totalBuffered;
|
|
34
|
-
private lastChunkEndedWithNewline;
|
|
35
|
-
private idleTimer;
|
|
36
|
-
private headersSent;
|
|
37
|
-
private closeHandlerRegistered;
|
|
38
|
-
private readonly sseHeaders;
|
|
39
|
-
private readonly passThrough;
|
|
40
|
-
private readonly pipeEntry;
|
|
41
|
-
private readonly formatTransform;
|
|
42
|
-
private sseScanBuffer;
|
|
43
|
-
private static readonly SSE_SCAN_MAX;
|
|
44
|
-
constructor(statusCode: number, rawUpstreamHeaders: RawHeaders, sentUpstreamHeaders: Record<string, string>, reply: FastifyReply, metricsTransform: SSEMetricsTransform | undefined, checkEarlyError: ((data: string) => boolean) | undefined, timeoutMs: number, loopGuard: StreamLoopGuard | undefined, formatTransform?: Transform, timeoutContext?: {
|
|
45
|
-
modelId: string;
|
|
46
|
-
providerId: string;
|
|
47
|
-
} | undefined, onTimeoutAbort?: ((timeoutMs: number) => void) | undefined, upstreamRes?: IncomingMessage | undefined, upstreamReq?: ClientRequest | undefined);
|
|
48
|
-
bindResolve(resolve: (result: TransportResult) => void): void;
|
|
49
|
-
private transition;
|
|
50
|
-
private terminal;
|
|
51
|
-
private cleanup;
|
|
52
|
-
private collectMetrics;
|
|
53
|
-
resetIdleTimer(): void;
|
|
54
|
-
startStreaming(): void;
|
|
55
|
-
registerCloseHandler(): void;
|
|
56
|
-
onData(chunk: Buffer): void;
|
|
57
|
-
onEnd(): void;
|
|
58
|
-
onUpstreamError(err: Error): void;
|
|
59
|
-
}
|
|
6
|
+
import { type BuildHeadersFn } from "./http.js";
|
|
60
7
|
export declare function callStream(backend: {
|
|
61
8
|
base_url: string;
|
|
62
9
|
}, apiKey: string, body: Record<string, unknown>, clientHeaders: RawHeaders, reply: FastifyReply, timeoutMs: number, upstreamPath: string, buildHeaders: BuildHeadersFn, metricsTransform?: SSEMetricsTransform, checkEarlyError?: (bufferedData: string) => boolean, compatResolve?: (result: TransportResult) => void, loopGuard?: StreamLoopGuard, formatTransform?: import("stream").Transform, timeoutContext?: {
|
|
63
10
|
modelId: string;
|
|
64
11
|
providerId: string;
|
|
65
|
-
}, onTimeoutAbort?: (timeoutMs: number) => void, agent?: Agent
|
|
12
|
+
}, onTimeoutAbort?: (timeoutMs: number) => void, agent?: Agent): Promise<TransportResult>;
|
|
@@ -5,11 +5,7 @@ import { _transportInternals, buildRequestOptions, } from "./http.js";
|
|
|
5
5
|
const UPSTREAM_BAD_GATEWAY = 502;
|
|
6
6
|
const BUFFER_SIZE_LIMIT = 4096;
|
|
7
7
|
const END_REPLY_TIMEOUT_MS = 1000;
|
|
8
|
-
|
|
9
|
-
* SSE 流式代理状态机。导出仅为单元测试,非公开 API。
|
|
10
|
-
* 负责 buffering/streaming 状态转换、idle 超时、上游资源销毁。
|
|
11
|
-
*/
|
|
12
|
-
export class StreamProxy {
|
|
8
|
+
class StreamProxy {
|
|
13
9
|
statusCode;
|
|
14
10
|
sentUpstreamHeaders;
|
|
15
11
|
reply;
|
|
@@ -19,8 +15,6 @@ export class StreamProxy {
|
|
|
19
15
|
loopGuard;
|
|
20
16
|
timeoutContext;
|
|
21
17
|
onTimeoutAbort;
|
|
22
|
-
upstreamRes;
|
|
23
|
-
upstreamReq;
|
|
24
18
|
state = "BUFFERING";
|
|
25
19
|
resolved = false;
|
|
26
20
|
resolveFn = null;
|
|
@@ -35,10 +29,10 @@ export class StreamProxy {
|
|
|
35
29
|
passThrough = new PassThrough();
|
|
36
30
|
pipeEntry;
|
|
37
31
|
formatTransform;
|
|
38
|
-
// 流式阶段 SSE error
|
|
32
|
+
// 流式阶段 SSE error 扫描缓冲(跨 chunk 边界匹配)
|
|
39
33
|
sseScanBuffer = "";
|
|
40
34
|
static SSE_SCAN_MAX = 8 * 1024; // eslint-disable-line no-magic-numbers -- 8KB scan buffer
|
|
41
|
-
constructor(statusCode, rawUpstreamHeaders, sentUpstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort
|
|
35
|
+
constructor(statusCode, rawUpstreamHeaders, sentUpstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort) {
|
|
42
36
|
this.statusCode = statusCode;
|
|
43
37
|
this.sentUpstreamHeaders = sentUpstreamHeaders;
|
|
44
38
|
this.reply = reply;
|
|
@@ -48,8 +42,6 @@ export class StreamProxy {
|
|
|
48
42
|
this.loopGuard = loopGuard;
|
|
49
43
|
this.timeoutContext = timeoutContext;
|
|
50
44
|
this.onTimeoutAbort = onTimeoutAbort;
|
|
51
|
-
this.upstreamRes = upstreamRes;
|
|
52
|
-
this.upstreamReq = upstreamReq;
|
|
53
45
|
this.formatTransform = formatTransform;
|
|
54
46
|
this.sseHeaders = filterHeaders(rawUpstreamHeaders);
|
|
55
47
|
this.sseHeaders["Content-Type"] = "text/event-stream";
|
|
@@ -96,8 +88,8 @@ export class StreamProxy {
|
|
|
96
88
|
result = { kind: "stream_abort", ...base, metrics: extra.metrics, timeoutContext: extra.timeoutContext, timeoutMs: extra.timeoutMs, abortReason: extra.abortReason };
|
|
97
89
|
break;
|
|
98
90
|
}
|
|
99
|
-
// deferred
|
|
100
|
-
// cleanup 由调用方在 setImmediate
|
|
91
|
+
// deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中执行,
|
|
92
|
+
// cleanup 由调用方在 setImmediate(macrotask)中处理。
|
|
101
93
|
if (deferred) {
|
|
102
94
|
if (this.resolveFn) {
|
|
103
95
|
this.resolveFn(result);
|
|
@@ -107,8 +99,8 @@ export class StreamProxy {
|
|
|
107
99
|
}
|
|
108
100
|
}
|
|
109
101
|
else {
|
|
110
|
-
// stream_abort
|
|
111
|
-
// idle_timeout 使用 deferred
|
|
102
|
+
// stream_abort(非 deferred)且 headers 已发送时,立即 end reply 避免客户端挂起
|
|
103
|
+
// idle_timeout 使用 deferred 模式,由 resetIdleTimer 的 setImmediate 负责 end reply
|
|
112
104
|
if (kind === "stream_abort" && this.headersSent) {
|
|
113
105
|
try {
|
|
114
106
|
this.reply.raw.end();
|
|
@@ -117,10 +109,7 @@ export class StreamProxy {
|
|
|
117
109
|
console.warn("[stream-proxy] reply.raw.end() failed, likely already destroyed");
|
|
118
110
|
}
|
|
119
111
|
}
|
|
120
|
-
|
|
121
|
-
// 「destroy 必须 error 参数」规范一致。无 cause 时 destroy(undefined) 等价 destroy(),
|
|
122
|
-
// 不 emit 'error',自然结束路径(onEnd/stream_success)行为不变。
|
|
123
|
-
this.cleanup(extra.error);
|
|
112
|
+
this.cleanup();
|
|
124
113
|
if (this.resolveFn) {
|
|
125
114
|
this.resolveFn(result);
|
|
126
115
|
}
|
|
@@ -129,16 +118,10 @@ export class StreamProxy {
|
|
|
129
118
|
}
|
|
130
119
|
}
|
|
131
120
|
}
|
|
132
|
-
cleanup(
|
|
121
|
+
cleanup() {
|
|
133
122
|
if (this.idleTimer)
|
|
134
123
|
clearTimeout(this.idleTimer);
|
|
135
124
|
this.idleTimer = null;
|
|
136
|
-
// 销毁上游资源,避免连接泄漏(幂等:destroyed 标志保护重复调用)。
|
|
137
|
-
// 传 cause 时 destroy(err) 会 emit 'error',触发 onUpstreamError(resolved 守卫保护下为 noop)。
|
|
138
|
-
if (this.upstreamRes && !this.upstreamRes.destroyed && typeof this.upstreamRes.destroy === "function")
|
|
139
|
-
this.upstreamRes.destroy(cause);
|
|
140
|
-
if (this.upstreamReq && !this.upstreamReq.destroyed && typeof this.upstreamReq.destroy === "function")
|
|
141
|
-
this.upstreamReq.destroy(cause);
|
|
142
125
|
if (this.formatTransform && !this.formatTransform.destroyed)
|
|
143
126
|
this.formatTransform.destroy();
|
|
144
127
|
if (!this.passThrough.destroyed)
|
|
@@ -149,8 +132,8 @@ export class StreamProxy {
|
|
|
149
132
|
collectMetrics(isComplete) {
|
|
150
133
|
if (!this.metricsTransform)
|
|
151
134
|
return undefined;
|
|
152
|
-
// 在读取 metrics 之前 flush SSE parser
|
|
153
|
-
// 等末尾事件已被处理。否则 extractor.complete 可能为 false
|
|
135
|
+
// 在读取 metrics 之前 flush SSE parser 缓冲区,确保 [DONE] / message_stop
|
|
136
|
+
// 等末尾事件已被处理。否则 extractor.complete 可能为 false,导致 is_complete=0。
|
|
154
137
|
this.metricsTransform.flushParser();
|
|
155
138
|
const result = this.metricsTransform.getExtractor().getMetrics();
|
|
156
139
|
return isComplete ? result : { ...result, is_complete: 0 };
|
|
@@ -163,15 +146,15 @@ export class StreamProxy {
|
|
|
163
146
|
this.idleTimer = setTimeout(() => {
|
|
164
147
|
if (this.resolved)
|
|
165
148
|
return;
|
|
166
|
-
// 在 terminal() 之前同步写入超时错误 SSE
|
|
149
|
+
// 在 terminal() 之前同步写入超时错误 SSE,确保 inject() 能正确收集响应体
|
|
167
150
|
if (this.onTimeoutAbort) {
|
|
168
151
|
try {
|
|
169
152
|
this.onTimeoutAbort(this.timeoutMs);
|
|
170
153
|
}
|
|
171
154
|
catch { /* reply may be destroyed */ } // eslint-disable-line taste/no-silent-catch
|
|
172
155
|
}
|
|
173
|
-
// deferred
|
|
174
|
-
// reply.raw.end() 延迟到 setImmediate
|
|
156
|
+
// deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中完成,
|
|
157
|
+
// reply.raw.end() 延迟到 setImmediate(macrotask),保证 inject() 返回时日志已写入。
|
|
175
158
|
this.terminal("stream_abort", { metrics: this.collectMetrics(false), timeoutContext: this.timeoutContext, timeoutMs: this.timeoutMs, abortReason: "idle_timeout" }, true);
|
|
176
159
|
setImmediate(() => {
|
|
177
160
|
if (this.headersSent) {
|
|
@@ -183,13 +166,12 @@ export class StreamProxy {
|
|
|
183
166
|
this.cleanup();
|
|
184
167
|
});
|
|
185
168
|
}, this.timeoutMs);
|
|
186
|
-
this.idleTimer.unref();
|
|
187
169
|
}
|
|
188
170
|
startStreaming() {
|
|
189
171
|
if (this.headersSent)
|
|
190
172
|
return;
|
|
191
173
|
if (this.reply.raw.headersSent) {
|
|
192
|
-
// headers
|
|
174
|
+
// headers 已由其他代码路径(如前一次 retry 的 StreamProxy)发送,
|
|
193
175
|
// 仅更新状态机避免 BUFFERING 阶段的重复逻辑
|
|
194
176
|
this.transition("STREAMING");
|
|
195
177
|
this.headersSent = true;
|
|
@@ -201,7 +183,7 @@ export class StreamProxy {
|
|
|
201
183
|
this.reply.raw.writeHead(this.statusCode, this.sseHeaders);
|
|
202
184
|
}
|
|
203
185
|
catch {
|
|
204
|
-
// 客户端在 state transition 和 writeHead
|
|
186
|
+
// 客户端在 state transition 和 writeHead 之间断连,可安全忽略
|
|
205
187
|
this.terminal("stream_abort", { abortReason: "client_disconnect" });
|
|
206
188
|
return;
|
|
207
189
|
}
|
|
@@ -211,26 +193,17 @@ export class StreamProxy {
|
|
|
211
193
|
if (this.formatTransform) {
|
|
212
194
|
this.formatTransform.pipe(this.passThrough, { end: true });
|
|
213
195
|
}
|
|
214
|
-
// 手动转发而非 pipe
|
|
196
|
+
// 手动转发而非 pipe,避免 Node.js 在 dest 上自动注册 close/finish handler
|
|
215
197
|
this.passThrough.on("data", (chunk) => {
|
|
216
198
|
try {
|
|
217
199
|
this.reply.raw.write(chunk);
|
|
218
200
|
}
|
|
219
201
|
catch { // eslint-disable-line taste/no-silent-catch
|
|
220
|
-
//
|
|
202
|
+
// 客户端已断开,写已销毁的 socket 会抛出异常,可安全忽略
|
|
221
203
|
}
|
|
222
204
|
});
|
|
223
|
-
// 不在 passThrough end 事件中调用 reply.raw.end()
|
|
205
|
+
// 不在 passThrough end 事件中调用 reply.raw.end(),
|
|
224
206
|
// 因为 onEnd() 统一管理响应结束时机,确保日志在 reply end 之前写入
|
|
225
|
-
// passThrough 异常若不处理会冒泡 uncaughtException;此处直接走 terminal 保证 Promise resolve,
|
|
226
|
-
// 避免 callStream 永挂(cleanup 的 upstreamRes.destroy() 无 error 时不 emit 'error',
|
|
227
|
-
// onUpstreamError 兜底不触发,是旧实现的隐藏 bug)。
|
|
228
|
-
this.passThrough.on("error", (err) => {
|
|
229
|
-
console.warn("[stream-proxy] passThrough error:", err.message);
|
|
230
|
-
if (!this.resolved) {
|
|
231
|
-
this.terminal("stream_abort", { abortReason: "pipe_error", error: err });
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
207
|
for (const c of this.bufferChunks)
|
|
235
208
|
this.pipeEntry.write(c);
|
|
236
209
|
this.bufferChunks.length = 0;
|
|
@@ -239,10 +212,10 @@ export class StreamProxy {
|
|
|
239
212
|
if (this.closeHandlerRegistered)
|
|
240
213
|
return;
|
|
241
214
|
this.closeHandlerRegistered = true;
|
|
242
|
-
// EPIPE 从底层 socket 的 WriteWrap.onWriteComplete emit
|
|
215
|
+
// EPIPE 从底层 socket 的 WriteWrap.onWriteComplete emit,reply.raw.on("error") 无法拦截。
|
|
243
216
|
// 必须直接在 socket 上注册 error handler 防止冒泡到 process uncaughtException。
|
|
244
|
-
// Node.js HTTP server 内置的 socketOnError 只处理第一次 socket error
|
|
245
|
-
//
|
|
217
|
+
// Node.js HTTP server 内置的 socketOnError 只处理第一次 socket error,
|
|
218
|
+
// 如果第一次被其他错误消耗,后续 EPIPE 可能无 handler 导致进程崩溃。
|
|
246
219
|
const sock = this.reply.raw.socket;
|
|
247
220
|
let sockErrorHandler;
|
|
248
221
|
if (sock) {
|
|
@@ -273,7 +246,7 @@ export class StreamProxy {
|
|
|
273
246
|
if (this.state === "BUFFERING") {
|
|
274
247
|
this.bufferChunks.push(chunk);
|
|
275
248
|
this.totalBuffered += chunk.length;
|
|
276
|
-
//
|
|
249
|
+
// 快速路径:检查大小限制(无需 concat)
|
|
277
250
|
if (this.totalBuffered >= BUFFER_SIZE_LIMIT) {
|
|
278
251
|
const buf = Buffer.concat(this.bufferChunks);
|
|
279
252
|
const text = buf.toString("utf-8");
|
|
@@ -285,10 +258,10 @@ export class StreamProxy {
|
|
|
285
258
|
this.startStreaming();
|
|
286
259
|
return;
|
|
287
260
|
}
|
|
288
|
-
//
|
|
261
|
+
// 快速路径:检查当前 chunk 是否包含 \n\n
|
|
289
262
|
const chunkStr = chunk.toString("utf-8");
|
|
290
263
|
const hasNewlinePair = chunkStr.includes("\n\n");
|
|
291
|
-
// 跨 chunk
|
|
264
|
+
// 跨 chunk 边界检测:上一个 chunk 以 \n 结尾 + 当前以 \n 开头
|
|
292
265
|
const crossBoundary = this.lastChunkEndedWithNewline && chunkStr.startsWith("\n");
|
|
293
266
|
this.lastChunkEndedWithNewline = chunkStr.endsWith("\n");
|
|
294
267
|
if (hasNewlinePair || crossBoundary) {
|
|
@@ -305,25 +278,25 @@ export class StreamProxy {
|
|
|
305
278
|
}
|
|
306
279
|
return;
|
|
307
280
|
}
|
|
308
|
-
// STREAMING
|
|
281
|
+
// STREAMING 阶段:扫描 SSE error event(处理跨 chunk 边界)
|
|
309
282
|
if (this.state === "STREAMING" && this.checkEarlyError) {
|
|
310
283
|
this.sseScanBuffer += chunk.toString("utf-8");
|
|
311
|
-
// 保留最近 SSE_SCAN_MAX
|
|
284
|
+
// 保留最近 SSE_SCAN_MAX 字符,避免无限增长
|
|
312
285
|
if (this.sseScanBuffer.length > StreamProxy.SSE_SCAN_MAX) {
|
|
313
286
|
this.sseScanBuffer = this.sseScanBuffer.slice(-StreamProxy.SSE_SCAN_MAX);
|
|
314
287
|
}
|
|
315
|
-
//
|
|
288
|
+
// 快速启发式:只在扫描窗口出现 SSE error 标记时才执行正则匹配
|
|
316
289
|
if (this.sseScanBuffer.includes("event: error") || this.sseScanBuffer.includes('"type":"error"')) {
|
|
317
290
|
if (this.checkEarlyError(this.sseScanBuffer)) {
|
|
318
291
|
this.terminal("stream_error", { body: this.sseScanBuffer });
|
|
319
|
-
// headers
|
|
292
|
+
// headers 已发送:必须结束 reply 避免 client hang
|
|
320
293
|
if (this.headersSent) {
|
|
321
294
|
setImmediate(() => {
|
|
322
295
|
try {
|
|
323
296
|
this.reply.raw.end();
|
|
324
297
|
}
|
|
325
298
|
catch { // eslint-disable-line taste/no-silent-catch
|
|
326
|
-
// reply 可能已 destroyed
|
|
299
|
+
// reply 可能已 destroyed,安全忽略
|
|
327
300
|
}
|
|
328
301
|
});
|
|
329
302
|
}
|
|
@@ -353,19 +326,19 @@ export class StreamProxy {
|
|
|
353
326
|
if (this.state === "STREAMING") {
|
|
354
327
|
this.transition("COMPLETED");
|
|
355
328
|
}
|
|
356
|
-
// 通过 terminal 的 deferred 模式统一 resolve
|
|
357
|
-
// 先 resolve Promise
|
|
358
|
-
// reply.raw.end() 延迟到 setImmediate
|
|
359
|
-
// light-my-request 监听 reply.raw 的 end
|
|
329
|
+
// 通过 terminal 的 deferred 模式统一 resolve:
|
|
330
|
+
// 先 resolve Promise,让 handler 链路(日志写入等)在 microtask 中执行。
|
|
331
|
+
// reply.raw.end() 延迟到 setImmediate(macrotask),确保 microtask 先完成。
|
|
332
|
+
// light-my-request 监听 reply.raw 的 end 事件判定响应完成,
|
|
360
333
|
// 这保证了 inject() 返回时日志已经写入 DB。
|
|
361
334
|
const metrics = this.collectMetrics(true);
|
|
362
335
|
this.terminal("stream_success", { metrics }, true);
|
|
363
|
-
//
|
|
336
|
+
// 延迟结束管道和响应,属于 reply 层面操作,不属于 StreamProxy 状态管理。
|
|
364
337
|
//
|
|
365
|
-
// 当 formatTransform
|
|
366
|
-
// 通过 process.nextTick 异步传播。pipeEntry.end() 后不能同步调用 reply.raw.end()
|
|
367
|
-
// 否则 formatTransform._flush() 中的 ensureTerminated()
|
|
368
|
-
//
|
|
338
|
+
// 当 formatTransform 存在时(如 OpenAI→Anthropic 转换),Transform 链的 flush
|
|
339
|
+
// 通过 process.nextTick 异步传播。pipeEntry.end() 后不能同步调用 reply.raw.end(),
|
|
340
|
+
// 否则 formatTransform._flush() 中的 ensureTerminated()(发送 message_stop)
|
|
341
|
+
// 尚未执行,连接就被关闭,导致客户端收到 "stream ended before message_stop"。
|
|
369
342
|
setImmediate(() => {
|
|
370
343
|
const endReply = () => {
|
|
371
344
|
if (this.headersSent) {
|
|
@@ -373,16 +346,16 @@ export class StreamProxy {
|
|
|
373
346
|
this.reply.raw.end();
|
|
374
347
|
}
|
|
375
348
|
catch { // eslint-disable-line taste/no-silent-catch
|
|
376
|
-
// reply 可能已 destroyed
|
|
349
|
+
// reply 可能已 destroyed,安全忽略
|
|
377
350
|
}
|
|
378
351
|
}
|
|
379
352
|
this.cleanup();
|
|
380
353
|
};
|
|
381
354
|
this.pipeEntry.end();
|
|
382
355
|
if (this.formatTransform) {
|
|
383
|
-
// 等 passThrough end
|
|
356
|
+
// 等 passThrough end 事件触发(整个 transform 链 flush 完成后),再关闭 reply
|
|
384
357
|
this.passThrough.once("end", endReply);
|
|
385
|
-
//
|
|
358
|
+
// 安全超时兜底,防止 end 事件未触发导致连接挂起
|
|
386
359
|
setTimeout(endReply, END_REPLY_TIMEOUT_MS).unref();
|
|
387
360
|
}
|
|
388
361
|
else {
|
|
@@ -393,10 +366,6 @@ export class StreamProxy {
|
|
|
393
366
|
onUpstreamError(err) {
|
|
394
367
|
if (this.resolved)
|
|
395
368
|
return;
|
|
396
|
-
// 状态机一致:上游错误视为中止(resolved=true 不保证 state 已转终态)
|
|
397
|
-
if (this.state === "BUFFERING" || this.state === "STREAMING") {
|
|
398
|
-
this.transition("ABORTED");
|
|
399
|
-
}
|
|
400
369
|
this.resolved = true;
|
|
401
370
|
this.cleanup();
|
|
402
371
|
const result = { kind: "throw", error: err, headersSent: this.headersSent };
|
|
@@ -409,7 +378,7 @@ export class StreamProxy {
|
|
|
409
378
|
}
|
|
410
379
|
}
|
|
411
380
|
// ---------- callStream ----------
|
|
412
|
-
export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, agent
|
|
381
|
+
export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, agent) {
|
|
413
382
|
return new Promise((resolve) => {
|
|
414
383
|
const effectiveResolve = compatResolve ?? resolve;
|
|
415
384
|
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
@@ -417,29 +386,7 @@ export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutM
|
|
|
417
386
|
const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
|
|
418
387
|
const options = buildRequestOptions(url, upstreamHeaders);
|
|
419
388
|
const upstreamReq = _transportInternals.createUpstreamRequest(url, options, agent);
|
|
420
|
-
// 响应头前无活动超时(拿到 upstreamRes 后改由 StreamProxy.idleTimer 接管)。
|
|
421
|
-
// destroy(error) 触发 'error' 事件 → effectiveResolve({kind:'throw'})。
|
|
422
|
-
const connectTimeoutMs = opts?.connectTimeoutMs;
|
|
423
|
-
const hasConnectTimeout = connectTimeoutMs !== undefined && Number.isFinite(connectTimeoutMs) && connectTimeoutMs > 0;
|
|
424
|
-
if (hasConnectTimeout) {
|
|
425
|
-
upstreamReq.setTimeout(connectTimeoutMs);
|
|
426
|
-
upstreamReq.on("timeout", () => upstreamReq.destroy(new Error("upstream inactivity timeout (pre-response)")));
|
|
427
|
-
}
|
|
428
|
-
// 客户端断连:abort 信号穿透到上游 socket,立即切断连接。
|
|
429
|
-
if (opts?.signal) {
|
|
430
|
-
const signal = opts.signal;
|
|
431
|
-
const abort = () => upstreamReq.destroy(new Error("client aborted"));
|
|
432
|
-
if (signal.aborted) {
|
|
433
|
-
abort();
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
389
|
upstreamReq.on("response", (upstreamRes) => {
|
|
440
|
-
// 响应头已到达:清除响应头前 socket 超时,避免与 StreamProxy.idleTimer 竞争
|
|
441
|
-
if (hasConnectTimeout)
|
|
442
|
-
upstreamReq.setTimeout(0);
|
|
443
390
|
const statusCode = upstreamRes.statusCode || UPSTREAM_BAD_GATEWAY;
|
|
444
391
|
if (statusCode !== UPSTREAM_SUCCESS) {
|
|
445
392
|
const chunks = [];
|
|
@@ -455,7 +402,7 @@ export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutM
|
|
|
455
402
|
});
|
|
456
403
|
return;
|
|
457
404
|
}
|
|
458
|
-
const proxy = new StreamProxy(statusCode, upstreamRes.headers, upstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort
|
|
405
|
+
const proxy = new StreamProxy(statusCode, upstreamRes.headers, upstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort);
|
|
459
406
|
proxy.bindResolve(effectiveResolve);
|
|
460
407
|
proxy.registerCloseHandler();
|
|
461
408
|
// 无 early error checker 时直接开始流式传输
|
|
@@ -18,7 +18,6 @@ export interface TransportFnParams {
|
|
|
18
18
|
logId: string;
|
|
19
19
|
effectiveModel: string;
|
|
20
20
|
streamTimeoutMs: number;
|
|
21
|
-
nonStreamTimeoutMs?: number;
|
|
22
21
|
tracker?: RequestTracker;
|
|
23
22
|
matcher?: RetryRuleMatcher;
|
|
24
23
|
request: FastifyRequest;
|
|
@@ -33,4 +32,4 @@ export interface TransportFnParams {
|
|
|
33
32
|
proxyAgentFactory?: ProxyAgentFactory;
|
|
34
33
|
resolvedBaseUrl: string;
|
|
35
34
|
}
|
|
36
|
-
export declare function buildTransportFn(p: TransportFnParams): (target: Target
|
|
35
|
+
export declare function buildTransportFn(p: TransportFnParams): (target: Target) => Promise<TransportResult>;
|
|
@@ -45,7 +45,7 @@ export function buildTransportFn(p) {
|
|
|
45
45
|
: undefined;
|
|
46
46
|
// _target 未使用 — resilience 层始终传入当前 resolved target;
|
|
47
47
|
// 跨 target failover 由外层 executeFailoverLoop 的 ProviderSwitchNeeded 处理
|
|
48
|
-
return async (_target
|
|
48
|
+
return async (_target) => {
|
|
49
49
|
if (p.isStream) {
|
|
50
50
|
let streamLoopGuard;
|
|
51
51
|
if (p.streamLoopEnabled) {
|
|
@@ -84,14 +84,14 @@ export function buildTransportFn(p) {
|
|
|
84
84
|
catch { /* reply may be destroyed */ } // eslint-disable-line taste/no-silent-catch
|
|
85
85
|
}
|
|
86
86
|
: undefined;
|
|
87
|
-
const streamResult = await callStream(effectiveBackend, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath, buildHeaders, metricsTransform, checkEarlyError, undefined, streamLoopGuard, p.formatTransform, p.timeoutContext, onTimeoutAbort, agent
|
|
87
|
+
const streamResult = await callStream(effectiveBackend, p.apiKey, p.body, p.cliHdrs, p.reply, p.streamTimeoutMs, p.upstreamPath, buildHeaders, metricsTransform, checkEarlyError, undefined, streamLoopGuard, p.formatTransform, p.timeoutContext, onTimeoutAbort, agent);
|
|
88
88
|
const m = (streamResult.kind === "stream_success" || streamResult.kind === "stream_abort")
|
|
89
89
|
? streamResult.metrics : undefined;
|
|
90
90
|
if (m)
|
|
91
91
|
p.tracker?.update(p.logId, { streamMetrics: toStreamMetrics(m) });
|
|
92
92
|
return streamResult;
|
|
93
93
|
}
|
|
94
|
-
let result = await callNonStream(effectiveBackend, p.apiKey, p.body, p.cliHdrs, p.upstreamPath, buildHeaders, agent
|
|
94
|
+
let result = await callNonStream(effectiveBackend, p.apiKey, p.body, p.cliHdrs, p.upstreamPath, buildHeaders, agent);
|
|
95
95
|
if (result.kind === "success") {
|
|
96
96
|
const mr = MetricsExtractor.fromNonStreamResponse(p.apiType, result.body);
|
|
97
97
|
if (mr)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Qt as t,at as n,et as r,jt as i,n as a,nt as o,ot as s,qt as c,vt as l,xt as u}from"./button-BBiWml8B.js";import{dt as d,ft as f,r as p}from"./index-DTujoAWx.js";import{t as m}from"./Card-uC_v0CEa.js";import{t as h}from"./CardContent-CP3OiCj4.js";var g={class:`min-h-screen flex items-center justify-center bg-background relative`},_={class:`text-center mb-6`},v={class:`text-sm text-muted-foreground mt-1`},y=s({__name:`AuthLayout`,props:{subtitle:{}},setup(s){let{isDark:y,toggleTheme:b}=p();return(p,x)=>(l(),o(`div`,g,[n(c(a),{variant:`ghost`,size:`icon`,class:`absolute top-4 right-4 text-muted-foreground hover:text-foreground`,onClick:c(b)},{default:i(()=>[c(y)?(l(),r(c(d),{key:1,class:`w-4 h-4`})):(l(),r(c(f),{key:0,class:`w-4 h-4`}))]),_:1},8,[`onClick`]),n(c(m),{class:`w-full max-w-sm shadow-lg`},{default:i(()=>[n(c(h),{class:`pt-6`},{default:i(()=>[e(`div`,_,[x[0]||=e(`div`,{class:`w-12 h-12 bg-primary rounded-lg mx-auto mb-3 flex items-center justify-center`},[e(`svg`,{class:`w-7 h-7 text-white`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[e(`path`,{"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`2`,d:`M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z`})])],-1),x[1]||=e(`h1`,{class:`text-xl font-semibold text-foreground`},` LLM Simple Router `,-1),e(`p`,v,t(s.subtitle),1)]),u(p.$slots,`default`)]),_:3})]),_:3})]))}});export{y as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Yt as e,nt as t,ot as n,qt as r,r as i,vt as a,xt as o}from"./button-BBiWml8B.js";var s=[`data-size`,`data-flush`],c=n({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`},flush:{type:Boolean,default:!1}},setup(n){let c=n;return(l,u)=>(a(),t(`div`,{"data-slot":`card`,"data-size":n.size,"data-flush":n.flush||void 0,class:e(r(i)(`border-border bg-card text-card-foreground rounded-lg border text-sm group/card flex flex-col`,n.flush?`overflow-hidden`:`overflow-hidden gap-4 py-4 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg`,`data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0`,c.class))},[o(l.$slots,`default`)],10,s))}});export{c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Yt as e,nt as t,ot as n,qt as r,r as i,vt as a,xt as o}from"./button-BBiWml8B.js";var s=n({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(n){let s=n;return(n,c)=>(a(),t(`div`,{"data-slot":`card-content`,class:e(r(i)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[o(n.$slots,`default`)],2))}});export{s as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Yt as e,nt as t,ot as n,qt as r,r as i,vt as a,xt as o}from"./button-BBiWml8B.js";var s=n({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(n){let s=n;return(n,c)=>(a(),t(`div`,{"data-slot":`card-header`,class:e(r(i)(`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(n.$slots,`default`)],2))}}),c=n({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(n){let s=n;return(n,c)=>(a(),t(`div`,{"data-slot":`card-title`,class:e(r(i)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[o(n.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Q as t,Qt as n,U as r,X as i,Yt as a,Z as o,Zt as s,at as c,bt as l,et as u,jt as d,mt as f,nt as p,ot as m,qt as h,tt as g,vt as _,zt as v}from"./button-BBiWml8B.js";import{t as y}from"./chevron-right-DYwStkJr.js";import{b,vt as x,x as S,xt as C,y as w}from"./index-DTujoAWx.js";var T=[`onMouseenter`],E={class:`truncate max-w-40`},D={key:0,class:`ml-1 text-[10px] px-1 py-px rounded bg-emerald-500/15 text-emerald-400 shrink-0`},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=150,N=2,P=m({__name:`CascadingSelect`,props:{groups:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean,default:!1},dashed:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(m,{emit:C}){let{t:P}=r(),F=m,I=t(()=>F.placeholder||P(`common.selectPlaceholder`)),L=C,R=v(!1),z=v(null),B=v(new Map),V=v({top:0,left:0}),H=null;function U(e,t){t?B.value.set(e,t):B.value.delete(e)}function W(e){H&&=(clearTimeout(H),null),z.value=e,f(()=>J(e))}function G(){H=setTimeout(()=>{z.value=null},M)}function K(){H&&=(clearTimeout(H),null)}function q(){z.value=null}function J(e){let t=B.value.get(e);if(!t)return;let n=t.getBoundingClientRect();V.value={top:n.top,left:n.right+N}}let Y=t(()=>{if(!F.modelValue)return``;let e=F.groups.find(e=>e.key===F.modelValue.groupKey);if(!e)return``;let t=e.options.find(e=>e.value===F.modelValue.value);return t?`${e.label} / ${t.label}`:``});function X(e,t){L(`update:modelValue`,{groupKey:e,value:t}),R.value=!1}function Z(e){R.value=e,e||(z.value=null)}return(t,r)=>(_(),u(h(S),{open:R.value,"onUpdate:open":Z},{default:d(()=>[c(h(w),{"as-child":``},{default:d(()=>[e(`div`,{class:a([`flex w-full items-center justify-between rounded-md bg-background ring-offset-background cursor-pointer hover:bg-accent hover:text-accent-foreground`,[m.compact?`h-7 text-xs px-2 py-1 rounded-md`:`h-10 text-sm px-3 py-2 border border-border rounded-md`,!m.compact&&m.dashed?`border-dashed border-primary/20`:``,{"ring-2 ring-ring ring-offset-2":R.value}]])},[e(`span`,{class:a([`truncate`,m.modelValue?`text-foreground`:`text-muted-foreground`])},n(Y.value||I.value),3),c(h(x),{class:`h-4 w-4 shrink-0 opacity-50`})],2)]),_:1}),c(h(b),{align:`start`,"side-offset":4,class:`z-[200] w-auto min-w-56 max-h-[80vh] overflow-y-auto p-1`},{default:d(()=>[(_(!0),p(i,null,l(m.groups,t=>(_(),p(`div`,{key:t.key,ref_for:!0,ref:e=>U(t.key,e),class:a([`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":z.value===t.key}]),onMouseenter:e=>W(t.key),onMouseleave:G},[e(`span`,E,n(t.label),1),t.badge?(_(),p(`span`,D,n(t.badge),1)):g(``,!0),c(h(y),{class:`ml-1 h-4 w-4 shrink-0 opacity-50`})],42,T))),128)),(_(),u(o,{to:`body`},[z.value&&m.groups.find(e=>e.key===z.value)?.options.length?(_(),p(`div`,{key:0,class:`fixed z-[201] min-w-48 max-h-[80vh] overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md`,style:s({top:`${V.value.top}px`,left:`${V.value.left}px`}),onMouseenter:K,onMouseleave:q},[(_(!0),p(i,null,l(m.groups.find(e=>e.key===z.value)?.options??[],t=>(_(),p(`div`,{key:t.value,class:a([`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":m.modelValue?.groupKey===z.value&&m.modelValue?.value===t.value}]),onClick:e=>X(z.value,t.value)},[e(`span`,k,n(t.label),1),t.tag?(_(),p(`span`,A,n(t.tag),1)):g(``,!0)],10,O))),128))],36)):g(``,!0)])),m.groups.length===0?(_(),p(`div`,j,n(h(P)(`common.noOptions`)),1)):g(``,!0)]),_:1})]),_:1},8,[`open`]))}}),F=m({__name:`CascadingModelSelect`,props:{providers:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean,default:!1},dashed:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(e,{emit:n}){let{t:i}=r(),a=e,o=t(()=>a.placeholder||i(`mappings.selectProviderModel`)),s=n,c=t(()=>a.providers.map(e=>({key:e.provider.id,label:e.provider.name,badge:e.isNew?i(`common.new`):void 0,options:e.models.map(e=>({value:e.name,label:e.name,tag:C(e.contextWindow)}))}))),l=t(()=>a.modelValue?{groupKey:a.modelValue.provider_id,value:a.modelValue.model}:void 0);function d(e){s(`update:modelValue`,{provider_id:e.groupKey,model:e.value})}return(t,n)=>(_(),u(P,{groups:c.value,"model-value":l.value,placeholder:o.value,compact:e.compact,dashed:e.dashed,"onUpdate:modelValue":d},null,8,[`groups`,`model-value`,`placeholder`,`compact`,`dashed`]))}});export{F as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ct as e,J as t,Q as n,S as r,Xt as i,Y as a,at as o,ct as s,et as c,h as l,i as u,jt as d,o as f,ot as p,pt as m,qt as h,r as g,tt as _,vt as v,xt as y}from"./button-BBiWml8B.js";import{t as b}from"./VisuallyHiddenInput-DrNFhnVL.js";import{t as x}from"./RovingFocusItem-5H5eE6G2.js";import{H as S,J as C,Q as w,W as T,X as E,yt as D,z as O}from"./index-DTujoAWx.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>w(e,t)):w(e,t)}var[A,j]=E(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=E(`CheckboxRoot`),I=p({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(r,{emit:i}){let o=r,s=i,{forwardRef:p,currentElement:g}=f(),S=A(null),E=l(o,`modelValue`,s,{defaultValue:o.defaultValue??o.falseValue,passive:o.modelValue===void 0}),D=n(()=>S?.disabled.value||o.disabled),O=n(()=>w(E.value,o.trueValue)),j=n(()=>C(S?.modelValue.value)?E.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,o.value));function P(){if(C(S?.modelValue.value))E.value===`indeterminate`?E.value=o.trueValue:E.value=O.value?o.falseValue:o.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,o.value)){let t=e.findIndex(e=>w(e,o.value));e.splice(t,1)}else e.push(o.value);S.modelValue.value=e}}let I=T(g),L=n(()=>o.id&&g.value?document.querySelector(`[for="${o.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(n,r)=>(v(),c(e(h(S)?.rovingFocus.value?h(x):h(u)),m(n.$attrs,{id:n.id,ref:h(p),role:`checkbox`,"as-child":n.asChild,as:n.as,type:n.as===`button`?`button`:void 0,"aria-checked":h(M)(j.value)?`mixed`:j.value,"aria-required":n.required,"aria-label":n.$attrs[`aria-label`]||L.value,"data-state":h(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:h(S)?.rovingFocus.value?!D.value:void 0,onKeydown:t(a(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:d(()=>[y(n.$slots,`default`,{modelValue:h(E),state:j.value}),h(I)&&n.name&&!h(S)?(v(),c(h(b),{key:0,type:`checkbox`,checked:!!j.value,name:n.name,value:n.value,disabled:D.value,required:n.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):_(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=p({__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}=f(),n=P();return(e,r)=>(v(),c(h(O),{present:e.forceMount||h(M)(h(n).state.value)||h(n).state.value===!0},{default:d(()=>[o(h(u),m({ref:h(t),"data-state":h(N)(h(n).state.value),"data-disabled":h(n).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:d(()=>[y(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=p({__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 n=e,a=t,l=S(r(n,`class`),a);return(e,t)=>(v(),c(h(I),m({"data-slot":`checkbox`},h(l),{class:h(g)(`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`,n.class)}),{default:d(t=>[o(h(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:d(()=>[y(e.$slots,`default`,i(s(t)),()=>[o(h(D))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ot as e,Q as t,Wt as n,Xt as r,at as i,ct as a,et as o,f as s,gt as c,h as l,i as u,jt as d,mt as f,o as p,ot as m,pt as h,qt as g,tt as _,vt as v,xt as y,zt as b}from"./button-BBiWml8B.js";import{H as x,V as S,X as C,z as w}from"./index-DTujoAWx.js";var[T,E]=C(`CollapsibleRoot`),D=m({__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 i=e,a=l(i,`open`,r,{defaultValue:i.defaultOpen,passive:i.open===void 0}),{disabled:s,unmountOnHide:c}=n(i);return E({contentId:``,disabled:s,open:a,unmountOnHide:c,onOpenToggle:()=>{s.value||(a.value=!a.value)}}),t({open:a}),p(),(e,t)=>(v(),o(g(u),{as:e.as,"as-child":i.asChild,"data-state":g(a)?`open`:`closed`,"data-disabled":g(s)?``:void 0},{default:d(()=>[y(e.$slots,`default`,{open:g(a)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=m({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(n,{emit:r}){let a=n,l=r,m=T();m.contentId||=S(void 0,`reka-collapsible-content`);let x=b(),{forwardRef:C,currentElement:E}=p(),D=b(0),O=b(0),k=t(()=>m.open.value),A=b(k.value),j=b();e(()=>[k.value,x.value?.present],async()=>{await f();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=t(()=>A.value&&m.open.value);return c(()=>{requestAnimationFrame(()=>{A.value=!1})}),s(E,`beforematch`,e=>{requestAnimationFrame(()=>{m.onOpenToggle(),l(`contentFound`)})}),(e,t)=>(v(),o(g(w),{ref_key:`presentRef`,ref:x,present:e.forceMount||g(m).open.value,"force-mount":!0},{default:d(({present:t})=>[i(g(u),h(e.$attrs,{id:g(m).contentId,ref:g(C),"as-child":a.asChild,as:e.as,hidden:t?void 0:g(m).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:g(m).open.value?`open`:`closed`,"data-disabled":g(m).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:d(()=>[!g(m).unmountOnHide.value||t?y(e.$slots,`default`,{key:0}):_(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=m({__name:`Collapsible`,props:{defaultOpen:{type:Boolean},open:{type:Boolean},disabled:{type:Boolean},unmountOnHide:{type:Boolean},asChild:{type:Boolean},as:{}},emits:[`update:open`],setup(e,{emit:t}){let n=x(e,t);return(e,t)=>(v(),o(g(D),h({"data-slot":`collapsible`},g(n)),{default:d(t=>[y(e.$slots,`default`,r(a(t)))]),_:3},16))}}),A=m({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(v(),o(g(O),h({"data-slot":`collapsible-content`},t),{default:d(()=>[y(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{et as e,i as t,jt as n,o as r,ot as i,pt as a,qt as o,vt as s,xt as c}from"./button-BBiWml8B.js";import{r as l}from"./CollapsibleContent-pBG4UkLo.js";var u=i({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(i){let a=i;r();let u=l();return(r,i)=>(s(),e(o(t),{type:r.as===`button`?`button`:void 0,as:r.as,"as-child":a.asChild,"aria-controls":o(u).contentId,"aria-expanded":o(u).open.value,"data-state":o(u).open.value?`open`:`closed`,"data-disabled":o(u).disabled?.value?``:void 0,disabled:o(u).disabled?.value,onClick:o(u).onOpenToggle},{default:n(()=>[c(r.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=i({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(t){let r=t;return(t,i)=>(s(),e(o(u),a({"data-slot":`collapsible-trigger`},r),{default:n(()=>[c(t.$slots,`default`)]),_:3},16))}});export{d as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,J as t,Q as n,Qt as r,R as i,U as a,X as o,Y as s,Yt as c,at as l,bt as u,et as d,it as f,jt as p,n as m,nt as h,ot as g,qt as _,tt as v,vt as y,zt as b}from"./button-BBiWml8B.js";import{t as x}from"./plus-xmIDnujf.js";import{t as S}from"./trash-2-Dn3T5-Z1.js";import{t as C}from"./Label-BQXea0mo.js";import{t as ee}from"./Switch-D1ER0j6H.js";import{_ as w,ct as te,g as T,h as E,m as D,ut as ne,v as O,vt as re}from"./index-DTujoAWx.js";import{t as ie}from"./badge-Db4OYMEf.js";import{t as k}from"./Input-DMDfXTXB.js";import{t as A}from"./CascadingModelSelect-Dzk7rxIN.js";import{r as j}from"./transform-domain-KBixlLXR.js";var M=i(`arrow-right`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`m12 5 7 7-7 7`,key:`xquz4c`}]]),N=i(`grid-3x3`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M3 9h18`,key:`1pudct`}],[`path`,{d:`M3 15h18`,key:`5xshup`}],[`path`,{d:`M9 3v18`,key:`fh3hqa`}],[`path`,{d:`M15 3v18`,key:`14nvp0`}]]),ae=[`title`],oe={class:`flex items-center gap-1 min-w-0 flex-1 flex-wrap`},se={key:0,class:`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-mono bg-primary/10 border border-border text-primary whitespace-nowrap`},P={key:0,class:`text-[10px] opacity-60`},F={class:`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-mono bg-muted/30 border border-border text-muted-foreground whitespace-nowrap`},I={class:`text-[10px] font-semibold`},L={class:`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-mono border border-dashed border-border text-primary/60 whitespace-nowrap`},R={class:`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-mono border border-dashed border-border text-blue-500/60 whitespace-nowrap`},z={class:`text-[10px] text-muted-foreground/30 hidden sm:inline shrink-0 whitespace-nowrap`},B={key:1,class:`space-y-2`},V={class:`flex items-center gap-2`},H={class:`text-[10px] text-muted-foreground/60 shrink-0 w-10 uppercase tracking-wider font-medium`},U={key:1,class:`flex-1 flex items-center gap-1.5`},W={class:`pt-0.5`},G={class:`flex items-center gap-1 mb-1`},K={class:`text-[10px] font-medium text-muted-foreground/70 uppercase tracking-wider`},ce={class:`flex items-center gap-2`},le={class:`flex-1`},ue={key:0,class:`flex items-center gap-1 pl-7 py-0.5`},de={class:`text-[9px] text-orange-400/50`},fe={class:`pt-2 mt-1 border-t border-dashed border-primary/15`},pe={class:`flex items-center gap-1.5 mb-1`},me={class:`text-[10px] font-medium text-primary/60 uppercase tracking-wider`},he={class:`text-[9px] text-primary/40 hidden sm:inline`},ge={key:0,class:`flex items-center gap-2`},_e={class:`flex-1`},ve={class:`pt-2 mt-1 border-t border-dashed border-blue-500/15`},ye={class:`flex items-center gap-1.5 mb-1`},be={class:`text-[10px] font-medium text-blue-500/60 uppercase tracking-wider`},xe={key:0,class:`flex items-center gap-2`},Se={class:`text-xs shrink-0 w-5 text-center px-0.5 py-0.5 rounded bg-blue-500/6 text-blue-500/60`},Ce={class:`flex-1`},we={key:2,class:`mt-2 px-2.5 py-2 rounded border-[1px] border-orange-400/25 bg-orange-400/5 flex gap-2`},Te={class:`text-[10px] leading-relaxed space-y-0.5`},Ee={class:`text-orange-400 font-medium`},De={class:`text-muted-foreground/60`},Oe={class:`text-muted-foreground/40`},ke=6,q=g({__name:`MappingEntryEditor`,props:{entry:{},providerGroups:{},expanded:{type:Boolean},editableClientModel:{type:Boolean,default:!1}},emits:[`update:targets`,`update:clientModel`,`update:multimodalFallback`,`toggle-active`,`remove`,`expand`],setup(i,{emit:g}){let{t:C}=a(),w=i,T=g,E=b(!1),D=b(``);function O(){w.editableClientModel&&(D.value=w.entry.clientModel,E.value=!0)}function j(){let e=D.value.trim();e&&e!==w.entry.clientModel&&T(`update:clientModel`,e),E.value=!1}function q(){E.value=!1}function J(e){return e===`__new__`?null:w.providerGroups.find(t=>t.provider.id===e)?.provider.name??e.slice(0,ke)}function Y(){let e=w.providerGroups[0];T(`update:targets`,[...w.entry.targets,{backend_model:e?.models[0]?.name??``,provider_id:e?.provider.id??``}])}function Ae(e){w.entry.targets.length<=1||T(`update:targets`,w.entry.targets.filter((t,n)=>n!==e))}function je(e,t){let n=[...w.entry.targets];n[e]={...n[e],provider_id:t.provider_id,backend_model:t.model},T(`update:targets`,n)}function Me(){let e=w.providerGroups[0];T(`update:targets`,w.entry.targets.map((t,n)=>n===0?{...t,overflow_provider_id:e?.provider.id??``,overflow_model:e?.models[0]?.name??``}:t))}function X(e){T(`update:targets`,w.entry.targets.map((t,n)=>{if(n===0){if(e)return{...t,overflow_provider_id:e.provider_id,overflow_model:e.model};let{overflow_provider_id:n,overflow_model:r,...i}=t;return i}return t}))}function Z(){let e=w.providerGroups[0];T(`update:multimodalFallback`,{provider_id:e?.provider.id??``,backend_model:``})}function Ne(e){T(`update:multimodalFallback`,{provider_id:e.provider_id,backend_model:e.model})}function Pe(){T(`update:multimodalFallback`,void 0)}let Fe=n(()=>{switch(w.entry.tag){case`def`:return`bg-primary/15 text-primary`;case`auto`:return`bg-green-500/15 text-green-500`;case`cust`:return`bg-blue-500/15 text-blue-500`;case`existing`:return`bg-muted/40 text-muted-foreground`;default:return`bg-muted/40 text-muted-foreground`}}),Ie=n(()=>{switch(w.entry.tag){case`def`:return C(`providers.shared.tagDefault`);case`auto`:return C(`providers.shared.tagAuto`);case`cust`:return C(`providers.shared.tagCustom`);case`existing`:return C(`providers.shared.tagExisting`);default:return``}}),Q=n(()=>!!w.entry.targets[0]?.overflow_model),$=n(()=>!!w.entry.multimodalFallback?.backend_model);return(n,a)=>(y(),h(`div`,null,[i.expanded?(y(),h(`div`,B,[e(`div`,V,[e(`span`,H,r(_(C)(`mappings.editor.clientLabel`)),1),E.value?(y(),h(`div`,{key:0,class:`flex-1 flex items-center gap-1`,onClick:a[5]||=s(()=>{},[`stop`])},[l(_(k),{modelValue:D.value,"onUpdate:modelValue":a[4]||=e=>D.value=e,class:`h-7 flex-1 text-xs font-mono border-[1px] border-border`,placeholder:_(C)(`mappings.editor.clientInputPlaceholder`),onKeydown:[t(s(j,[`prevent`]),[`enter`]),t(s(q,[`prevent`]),[`escape`])],onBlur:j,autofocus:``},null,8,[`modelValue`,`placeholder`,`onKeydown`])])):(y(),h(`div`,U,[e(`span`,{class:c([`font-mono text-xs font-semibold text-foreground`,{"cursor-pointer hover:text-primary transition-colors":i.editableClientModel}]),onClick:O},r(i.entry.clientModel),3),i.editableClientModel?(y(),d(_(m),{key:0,variant:`ghost`,size:`xs`,class:`text-[10px] text-muted-foreground/40`,onClick:s(O,[`stop`])},{default:p(()=>[f(r(_(C)(`mappings.editor.edit`)),1)]),_:1})):v(``,!0)]))]),e(`div`,W,[e(`div`,G,[l(_(te),{class:`size-3 text-muted-foreground/50`}),e(`span`,K,r(_(C)(`mappings.editor.failoverChain`)),1)]),(y(!0),h(o,null,u(i.entry.targets,(t,n)=>(y(),h(`div`,{key:n,class:`space-y-0`},[e(`div`,ce,[e(`span`,{class:c([`text-xs font-medium shrink-0 w-5 text-center px-0.5 py-0.5 rounded`,n===0?`bg-primary/10 text-primary`:`bg-muted/40 text-muted-foreground`])},r(n===0?`①`:n===1?`②`:n===2?`③`:`${n+1}`),3),e(`div`,le,[l(A,{providers:i.providerGroups,"model-value":{provider_id:t.provider_id,model:t.backend_model},compact:``,placeholder:_(C)(`providers.shared.selectModel`),"onUpdate:modelValue":e=>je(n,e)},null,8,[`providers`,`model-value`,`placeholder`,`onUpdate:modelValue`])]),n>0&&i.entry.targets.length>1?(y(),d(_(m),{key:0,variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/30 hover:text-destructive`,onClick:e=>Ae(n)},{default:p(()=>[l(_(S),{class:`size-3`})]),_:1},8,[`onClick`])):v(``,!0)]),n<i.entry.targets.length-1?(y(),h(`div`,ue,[a[13]||=e(`div`,{class:`w-px h-1.5 bg-orange-400/30`},null,-1),e(`span`,de,r(_(C)(`mappings.editor.failoverOnError`)),1)])):v(``,!0)]))),128)),l(_(m),{variant:`ghost`,class:`flex items-center justify-center gap-1 w-full py-1 mt-0.5 text-xs text-muted-foreground/40 border-[1px] border-dashed border-border rounded hover:text-primary hover:border-primary/40`,onClick:Y},{default:p(()=>[l(_(x),{class:`size-3`}),f(` `+r(_(C)(`mappings.addBackup`)),1)]),_:1})]),e(`div`,fe,[e(`div`,pe,[l(_(re),{class:`size-3 text-primary/50`}),e(`span`,me,r(_(C)(`mappings.editor.contextOverflow`)),1),e(`span`,he,r(_(C)(`mappings.editor.contextOverflowHint`)),1)]),Q.value?(y(),h(`div`,ge,[a[14]||=e(`span`,{class:`text-xs shrink-0 w-5 text-center px-0.5 py-0.5 rounded bg-primary/6 text-primary/60`},`↓`,-1),e(`div`,_e,[l(A,{providers:i.providerGroups,"model-value":i.entry.targets[0]?.overflow_provider_id&&i.entry.targets[0]?.overflow_model?{provider_id:i.entry.targets[0].overflow_provider_id,model:i.entry.targets[0].overflow_model}:void 0,compact:``,dashed:``,placeholder:_(C)(`providers.shared.overflowPlaceholder`),"onUpdate:modelValue":a[6]||=e=>X(e)},null,8,[`providers`,`model-value`,`placeholder`])]),l(_(m),{variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/30 hover:text-destructive`,onClick:a[7]||=e=>X(void 0)},{default:p(()=>[l(_(S),{class:`size-3`})]),_:1})])):(y(),d(_(m),{key:1,variant:`ghost`,size:`xs`,class:`text-xs text-muted-foreground/50 hover:text-primary/60`,onClick:Me},{default:p(()=>[l(_(x),{class:`size-3`}),f(` `+r(_(C)(`providers.shared.addOverflow`)),1)]),_:1}))]),e(`div`,ve,[e(`div`,ye,[l(_(N),{class:`size-3 text-blue-500/50`}),e(`span`,be,r(_(C)(`mappings.editor.multimodalFallback`)),1),$.value?(y(),d(_(ie),{key:0,variant:`outline`,class:`text-[9px] px-1.5 py-0 leading-none text-primary/60 border-[1px] border-primary/20`},{default:p(()=>[f(r(_(C)(`mappings.multimodalFallback.configured`)),1)]),_:1})):v(``,!0)]),$.value?(y(),h(`div`,xe,[e(`span`,Se,[l(_(N),{class:`size-2.5`})]),e(`div`,Ce,[l(A,{providers:i.providerGroups,"model-value":{provider_id:i.entry.multimodalFallback.provider_id,model:i.entry.multimodalFallback.backend_model},compact:``,dashed:``,placeholder:_(C)(`mappings.multimodalFallback.selectProviderModel`),"onUpdate:modelValue":a[8]||=e=>Ne(e)},null,8,[`providers`,`model-value`,`placeholder`])]),l(_(m),{variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/30 hover:text-destructive`,onClick:Pe},{default:p(()=>[l(_(S),{class:`size-3`})]),_:1})])):(y(),d(_(m),{key:1,variant:`ghost`,size:`xs`,class:`text-xs text-muted-foreground/50 hover:text-primary/60`,onClick:Z},{default:p(()=>[l(_(x),{class:`size-3`}),f(` `+r(_(C)(`mappings.multimodalFallback.add`)),1)]),_:1})),$.value?(y(),h(`div`,we,[l(_(ne),{class:`size-3.5 text-orange-400 shrink-0 mt-0.5`}),e(`div`,Te,[e(`div`,Ee,r(_(C)(`mappings.multimodalFallback.sessionLockWarning`)),1),e(`div`,De,r(_(C)(`mappings.multimodalFallback.sessionLockReason`)),1),e(`div`,Oe,r(_(C)(`mappings.multimodalFallback.costSuggestion`)),1)])])):v(``,!0)])])):(y(),h(`div`,{key:0,class:`flex items-center gap-2 cursor-pointer select-none min-h-[28px]`,onClick:a[3]||=e=>T(`expand`)},[e(`span`,{class:`font-mono text-xs font-semibold text-foreground min-w-[90px] truncate shrink-0`,title:i.entry.clientModel},r(i.entry.clientModel),9,ae),l(_(M),{class:`size-3 text-muted-foreground/30 shrink-0`}),e(`div`,oe,[i.entry.targets.length>0?(y(),h(`span`,se,[a[9]||=e(`span`,{class:`text-[10px] font-semibold`},`①`,-1),f(` `+r(i.entry.targets[0].backend_model)+` `,1),J(i.entry.targets[0].provider_id)?(y(),h(`span`,P,r(J(i.entry.targets[0].provider_id)),1)):v(``,!0)])):v(``,!0),(y(!0),h(o,null,u(i.entry.targets.slice(1),(t,n)=>(y(),h(o,{key:`fo-`+n},[a[10]||=e(`span`,{class:`text-orange-400/40 text-xs shrink-0`},`|`,-1),e(`span`,F,[e(`span`,I,r(n===0?`②`:n===1?`③`:`${n+2}`),1),f(` `+r(t.backend_model),1)])],64))),128)),Q.value?(y(),h(o,{key:1},[a[11]||=e(`span`,{class:`text-muted-foreground/30 text-xs shrink-0`},`|`,-1),e(`span`,L,` ↓ `+r(i.entry.targets[0].overflow_model),1)],64)):v(``,!0),$.value?(y(),h(o,{key:2},[a[12]||=e(`span`,{class:`text-muted-foreground/30 text-xs shrink-0`},`|`,-1),e(`span`,R,[l(_(N),{class:`size-2.5`}),f(` `+r(i.entry.multimodalFallback.backend_model),1)])],64)):v(``,!0)]),e(`span`,{class:c([`text-[10px] px-1.5 py-px rounded font-medium leading-none shrink-0`,Fe.value])},r(Ie.value),3),e(`div`,{class:`shrink-0 flex items-center`,onClick:a[1]||=s(()=>{},[`stop`])},[l(_(ee),{"model-value":i.entry.active,"onUpdate:modelValue":a[0]||=e=>T(`toggle-active`),class:`scale-75`},null,8,[`model-value`])]),l(_(m),{variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/20 hover:text-destructive`,onClick:a[2]||=s(e=>T(`remove`),[`stop`])},{default:p(()=>[l(_(S),{class:`size-3`})]),_:1}),e(`span`,z,r(_(C)(`mappings.editor.clickToEdit`)),1)]))]))}}),J={key:1,class:`text-[10px] text-muted-foreground leading-snug`},Y=g({__name:`ConcurrencyControl`,props:{modelValue:{},compact:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(t,{emit:i}){let{t:s}=a(),u=t,d=i;function m(e){d(`update:modelValue`,{...u.modelValue,...e})}let g=n(()=>u.modelValue.mode??j.mode),b=n(()=>u.modelValue.max_concurrency??j.max_concurrency),x=n(()=>u.modelValue.queue_timeout_ms??j.queue_timeout_ms),S=n(()=>u.modelValue.max_queue_size??j.max_queue_size);return(n,i)=>(y(),h(`div`,{class:c(t.compact?`space-y-2`:`flex items-end gap-2 flex-wrap`)},[e(`div`,{class:c([t.compact?``:`w-36`,`space-y-1`])},[l(_(C),{class:`text-xs text-muted-foreground`},{default:p(()=>[f(r(_(s)(`providers.concurrency.mode`)),1)]),_:1}),l(_(O),{"model-value":g.value,"onUpdate:modelValue":i[0]||=e=>m({mode:e})},{default:p(()=>[l(_(E),{class:`w-full text-xs data-[size=default]:h-7`},{default:p(()=>[l(_(D))]),_:1}),l(_(w),null,{default:p(()=>[l(_(T),{value:`auto`},{default:p(()=>[f(r(_(s)(`providers.concurrency.autoAdaptive`)),1)]),_:1}),l(_(T),{value:`manual`},{default:p(()=>[f(r(_(s)(`providers.concurrency.manual`)),1)]),_:1}),l(_(T),{value:`none`},{default:p(()=>[f(r(_(s)(`providers.concurrency.none`)),1)]),_:1})]),_:1})]),_:1},8,[`model-value`])],2),g.value===`none`?v(``,!0):(y(),h(o,{key:0},[e(`div`,{class:c([t.compact?``:`w-24`,`space-y-1`])},[l(_(C),{class:`text-xs text-muted-foreground`},{default:p(()=>[f(r(_(s)(`providers.concurrency.maxConcurrency`)),1)]),_:1}),l(_(k),{"model-value":b.value,type:`number`,min:`1`,max:`100`,class:`text-xs md:text-xs md:text-xs h-7`,"onUpdate:modelValue":i[1]||=e=>m({max_concurrency:Number(e)})},null,8,[`model-value`])],2),e(`div`,{class:c([t.compact?``:`w-30`,`space-y-1`])},[l(_(C),{class:`text-xs text-muted-foreground`},{default:p(()=>[f(r(_(s)(`providers.concurrency.queueTimeout`)),1)]),_:1}),l(_(k),{"model-value":x.value,type:`number`,min:`0`,placeholder:_(s)(`providers.shared.queueTimeoutPlaceholder`),class:`font-mono text-xs md:text-xs md:text-xs h-7`,"onUpdate:modelValue":i[2]||=e=>m({queue_timeout_ms:Number(e)})},null,8,[`model-value`,`placeholder`])],2),e(`div`,{class:c([t.compact?``:`w-30`,`space-y-1`])},[l(_(C),{class:`text-xs text-muted-foreground`},{default:p(()=>[f(r(_(s)(`providers.concurrency.maxQueueSize`)),1)]),_:1}),l(_(k),{"model-value":S.value,type:`number`,min:`1`,max:`1000`,class:`text-xs md:text-xs md:text-xs h-7`,"onUpdate:modelValue":i[3]||=e=>m({max_queue_size:Number(e)})},null,8,[`model-value`])],2)],64)),g.value===`auto`&&!t.compact?(y(),h(`div`,J,r(_(s)(`providers.shared.autoHint`)),1)):v(``,!0)],2))}});export{M as i,q as n,N as r,Y as t};
|