llm-simple-router 1.1.2 → 1.1.4
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 +6 -4
- package/dist/admin/quick-setup.js +3 -1
- package/dist/app/register-routes.js +2 -0
- package/dist/config/model-context.d.ts +2 -0
- package/dist/config/model-context.js +4 -0
- package/dist/core/concurrency/semaphore.d.ts +15 -2
- package/dist/core/concurrency/semaphore.js +36 -4
- package/dist/core/constants.d.ts +1 -0
- package/dist/core/constants.js +3 -0
- package/dist/core/monitor/request-tracker.d.ts +6 -0
- package/dist/core/monitor/request-tracker.js +15 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/db/providers.d.ts +16 -3
- package/dist/db/providers.js +27 -12
- package/dist/index.js +2 -0
- package/dist/proxy/handler/iteration-setup.js +7 -3
- package/dist/proxy/orchestration/orchestrator.d.ts +1 -1
- package/dist/proxy/orchestration/orchestrator.js +37 -12
- package/dist/proxy/orchestration/resilience.d.ts +3 -1
- package/dist/proxy/orchestration/resilience.js +17 -2
- package/dist/proxy/orchestration/scope.d.ts +1 -1
- package/dist/proxy/orchestration/scope.js +2 -2
- package/dist/proxy/proxy-core.d.ts +0 -11
- package/dist/proxy/proxy-core.js +2 -63
- package/dist/proxy/transport/http.d.ts +11 -13
- package/dist/proxy/transport/http.js +40 -30
- package/dist/proxy/transport/shared.d.ts +23 -0
- package/dist/proxy/transport/shared.js +58 -0
- package/dist/proxy/transport/stream.d.ts +56 -3
- package/dist/proxy/transport/stream.js +128 -49
- package/dist/proxy/transport/transport-fn.d.ts +2 -1
- package/dist/proxy/transport/transport-fn.js +3 -3
- package/frontend-dist/assets/AuthLayout-CmG_8Ovs.js +1 -0
- package/frontend-dist/assets/Card-BlGAfXME.js +1 -0
- package/frontend-dist/assets/CardContent-DTKSvHkJ.js +1 -0
- package/frontend-dist/assets/CardTitle-Cv1xB3mR.js +1 -0
- package/frontend-dist/assets/CascadingModelSelect-ul9uB6Dy.js +1 -0
- package/frontend-dist/assets/Checkbox-DxV7pVKU.js +1 -0
- package/frontend-dist/assets/CollapsibleContent-BgO8VoPR.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-dG3CqCAV.js +1 -0
- package/frontend-dist/assets/ConcurrencyControl-BKR3IfV4.js +1 -0
- package/frontend-dist/assets/Dashboard-DCOF-zaF.js +3 -0
- package/frontend-dist/assets/{Input-DMDfXTXB.js → Input-BBtQpfsU.js} +1 -1
- package/frontend-dist/assets/Label-DARM_yCh.js +1 -0
- package/frontend-dist/assets/Login-qTUiO2Vc.js +1 -0
- package/frontend-dist/assets/Logs-BAywknrp.js +1 -0
- package/frontend-dist/assets/ModelMappings-DU06Tex1.js +1 -0
- package/frontend-dist/assets/Monitor-DR_u-5V1.js +1 -0
- package/frontend-dist/assets/Providers-DM5iF-Z5.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-CI-lDGff.js +1 -0
- package/frontend-dist/assets/QuickSetup-AWc9oZz4.js +1 -0
- package/frontend-dist/assets/RetryRules-DhA2ONMo.js +1 -0
- package/frontend-dist/assets/RouterKeys-CFILIP_P.js +1 -0
- package/frontend-dist/assets/{RovingFocusItem-5H5eE6G2.js → RovingFocusItem-mNZuQwzG.js} +1 -1
- package/frontend-dist/assets/Schedules-C8A9Dyry.js +1 -0
- package/frontend-dist/assets/Separator-CtbYW3SR.js +1 -0
- package/frontend-dist/assets/Settings-etFCYRt3.js +6 -0
- package/frontend-dist/assets/Setup-veKl98QO.js +1 -0
- package/frontend-dist/assets/Skeleton-aBg0O52j.js +1 -0
- package/frontend-dist/assets/Switch-K9syOZ4L.js +1 -0
- package/frontend-dist/assets/TableHeader-Dpn1Lnaz.js +1 -0
- package/frontend-dist/assets/TabsTrigger-C6L7-25Q.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-BQNd5d8M.js +3 -0
- package/frontend-dist/assets/{VisuallyHiddenInput-DrNFhnVL.js → VisuallyHiddenInput-CM0ZcPu6.js} +1 -1
- package/frontend-dist/assets/arrow-down-D7MkIKwy.js +1 -0
- package/frontend-dist/assets/badge-BVIIW0-Q.js +1 -0
- package/frontend-dist/assets/{button-BBiWml8B.js → button-CZXw3CE5.js} +2 -2
- package/frontend-dist/assets/chevron-right-XHFgIZAJ.js +1 -0
- package/frontend-dist/assets/dialog-C1UP6R9l.js +1 -0
- package/frontend-dist/assets/{image-zYdpUIEA.js → image-B1uUZwVK.js} +1 -1
- package/frontend-dist/assets/{index-DyQ39g4W.css → index-DGJSS9jI.css} +1 -1
- package/frontend-dist/assets/index-DU-d4dwG.js +58 -0
- package/frontend-dist/assets/model-patches-DdJLVJUH.js +1 -0
- package/frontend-dist/assets/{pencil-C3-MFg-d.js → pencil-HavpPvNF.js} +1 -1
- package/frontend-dist/assets/plus-BjrVWmRw.js +1 -0
- package/frontend-dist/assets/quickSetup-jgJgPUcH.js +1 -0
- package/frontend-dist/assets/quickSetup-qTjp3Z6J.js +1 -0
- package/frontend-dist/assets/search-q46OssNL.js +1 -0
- package/frontend-dist/assets/{sparkles-B5RWZZuf.js → sparkles-CCPKwVxK.js} +1 -1
- package/frontend-dist/assets/transform-domain-D0mVmoZd.js +1 -0
- package/frontend-dist/assets/{trash-2-Dn3T5-Z1.js → trash-2-CHuDHKxp.js} +1 -1
- package/frontend-dist/assets/{useClipboard-Bx3CrPal.js → useClipboard-C3_tZc-3.js} +1 -1
- package/frontend-dist/assets/useLogRetention-BiFJhaOm.js +1 -0
- package/frontend-dist/assets/{useProviderGroups-Og5FpCPe.js → useProviderGroups-CEb_RKrl.js} +1 -1
- package/frontend-dist/index.html +3 -3
- package/package.json +1 -1
- package/frontend-dist/assets/AuthLayout-jELzICkx.js +0 -1
- package/frontend-dist/assets/Card-uC_v0CEa.js +0 -1
- package/frontend-dist/assets/CardContent-CP3OiCj4.js +0 -1
- package/frontend-dist/assets/CardTitle-DGxuW5DZ.js +0 -1
- package/frontend-dist/assets/CascadingModelSelect-Dzk7rxIN.js +0 -1
- package/frontend-dist/assets/Checkbox-C1aVqGdC.js +0 -1
- package/frontend-dist/assets/CollapsibleContent-pBG4UkLo.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-D5TkgXmz.js +0 -1
- package/frontend-dist/assets/ConcurrencyControl-5GweS-rY.js +0 -1
- package/frontend-dist/assets/Dashboard-wjd3d3qk.js +0 -3
- package/frontend-dist/assets/Label-BQXea0mo.js +0 -1
- package/frontend-dist/assets/Login-DNpCjxrY.js +0 -1
- package/frontend-dist/assets/Logs-BuL2Z0sF.js +0 -1
- package/frontend-dist/assets/ModelMappings-DeRhu-2N.js +0 -1
- package/frontend-dist/assets/Monitor-V30dnACo.js +0 -1
- package/frontend-dist/assets/Providers-BkkQhSTb.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-DjgebwfU.js +0 -1
- package/frontend-dist/assets/QuickSetup-BTVjEiU7.js +0 -1
- package/frontend-dist/assets/RetryRules-DFBHBG-B.js +0 -1
- package/frontend-dist/assets/RouterKeys-DZADvMfh.js +0 -1
- package/frontend-dist/assets/Schedules-DUubZ2uN.js +0 -1
- package/frontend-dist/assets/Separator-BqIs_Dy3.js +0 -1
- package/frontend-dist/assets/Settings-CO59WLOZ.js +0 -6
- package/frontend-dist/assets/Setup-Br7JZKNp.js +0 -1
- package/frontend-dist/assets/Skeleton-gVyjaP-y.js +0 -1
- package/frontend-dist/assets/Switch-D1ER0j6H.js +0 -1
- package/frontend-dist/assets/TableHeader-MqyrNSsx.js +0 -1
- package/frontend-dist/assets/TabsTrigger-DdjWJbUq.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-DRthlI6j.js +0 -3
- package/frontend-dist/assets/arrow-down-BFgGYafs.js +0 -1
- package/frontend-dist/assets/badge-Db4OYMEf.js +0 -1
- package/frontend-dist/assets/chevron-right-DYwStkJr.js +0 -1
- package/frontend-dist/assets/dialog-DRYeWncC.js +0 -1
- package/frontend-dist/assets/index-DTujoAWx.js +0 -58
- package/frontend-dist/assets/model-patches-DIy-rFuq.js +0 -1
- package/frontend-dist/assets/plus-xmIDnujf.js +0 -1
- package/frontend-dist/assets/quickSetup-CqxQRMCR.js +0 -1
- package/frontend-dist/assets/quickSetup-DplqYrvf.js +0 -1
- package/frontend-dist/assets/search-BxNrTsG8.js +0 -1
- package/frontend-dist/assets/transform-domain-KBixlLXR.js +0 -1
- package/frontend-dist/assets/useLogRetention-CdccNhYN.js +0 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { PassThrough } from "stream";
|
|
2
2
|
import { UPSTREAM_SUCCESS, filterHeaders } from "../types.js";
|
|
3
|
-
import { buildUpstreamUrl } from "
|
|
4
|
-
import { _transportInternals, buildRequestOptions, } from "./http.js";
|
|
3
|
+
import { buildUpstreamUrl, _transportInternals, buildRequestOptions, } from "./shared.js";
|
|
5
4
|
const UPSTREAM_BAD_GATEWAY = 502;
|
|
6
5
|
const BUFFER_SIZE_LIMIT = 4096;
|
|
7
6
|
const END_REPLY_TIMEOUT_MS = 1000;
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* SSE 流式代理状态机。导出仅为单元测试,非公开 API。
|
|
9
|
+
* 负责 buffering/streaming 状态转换、idle 超时、上游资源销毁。
|
|
10
|
+
*/
|
|
11
|
+
export class StreamProxy {
|
|
9
12
|
statusCode;
|
|
10
13
|
sentUpstreamHeaders;
|
|
11
14
|
reply;
|
|
@@ -15,6 +18,8 @@ class StreamProxy {
|
|
|
15
18
|
loopGuard;
|
|
16
19
|
timeoutContext;
|
|
17
20
|
onTimeoutAbort;
|
|
21
|
+
upstreamRes;
|
|
22
|
+
upstreamReq;
|
|
18
23
|
state = "BUFFERING";
|
|
19
24
|
resolved = false;
|
|
20
25
|
resolveFn = null;
|
|
@@ -29,10 +34,10 @@ class StreamProxy {
|
|
|
29
34
|
passThrough = new PassThrough();
|
|
30
35
|
pipeEntry;
|
|
31
36
|
formatTransform;
|
|
32
|
-
// 流式阶段 SSE error
|
|
37
|
+
// 流式阶段 SSE error 扫描缓冲(跨 chunk 边界匹配)
|
|
33
38
|
sseScanBuffer = "";
|
|
34
39
|
static SSE_SCAN_MAX = 8 * 1024; // eslint-disable-line no-magic-numbers -- 8KB scan buffer
|
|
35
|
-
constructor(statusCode, rawUpstreamHeaders, sentUpstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort) {
|
|
40
|
+
constructor(statusCode, rawUpstreamHeaders, sentUpstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, upstreamRes, upstreamReq) {
|
|
36
41
|
this.statusCode = statusCode;
|
|
37
42
|
this.sentUpstreamHeaders = sentUpstreamHeaders;
|
|
38
43
|
this.reply = reply;
|
|
@@ -42,6 +47,8 @@ class StreamProxy {
|
|
|
42
47
|
this.loopGuard = loopGuard;
|
|
43
48
|
this.timeoutContext = timeoutContext;
|
|
44
49
|
this.onTimeoutAbort = onTimeoutAbort;
|
|
50
|
+
this.upstreamRes = upstreamRes;
|
|
51
|
+
this.upstreamReq = upstreamReq;
|
|
45
52
|
this.formatTransform = formatTransform;
|
|
46
53
|
this.sseHeaders = filterHeaders(rawUpstreamHeaders);
|
|
47
54
|
this.sseHeaders["Content-Type"] = "text/event-stream";
|
|
@@ -88,8 +95,8 @@ class StreamProxy {
|
|
|
88
95
|
result = { kind: "stream_abort", ...base, metrics: extra.metrics, timeoutContext: extra.timeoutContext, timeoutMs: extra.timeoutMs, abortReason: extra.abortReason };
|
|
89
96
|
break;
|
|
90
97
|
}
|
|
91
|
-
// deferred
|
|
92
|
-
// cleanup 由调用方在 setImmediate
|
|
98
|
+
// deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中执行,
|
|
99
|
+
// cleanup 由调用方在 setImmediate(macrotask)中处理。
|
|
93
100
|
if (deferred) {
|
|
94
101
|
if (this.resolveFn) {
|
|
95
102
|
this.resolveFn(result);
|
|
@@ -99,8 +106,8 @@ class StreamProxy {
|
|
|
99
106
|
}
|
|
100
107
|
}
|
|
101
108
|
else {
|
|
102
|
-
// stream_abort
|
|
103
|
-
// idle_timeout 使用 deferred
|
|
109
|
+
// stream_abort(非 deferred)且 headers 已发送时,立即 end reply 避免客户端挂起
|
|
110
|
+
// idle_timeout 使用 deferred 模式,由 resetIdleTimer 的 setImmediate 负责 end reply
|
|
104
111
|
if (kind === "stream_abort" && this.headersSent) {
|
|
105
112
|
try {
|
|
106
113
|
this.reply.raw.end();
|
|
@@ -109,7 +116,10 @@ class StreamProxy {
|
|
|
109
116
|
console.warn("[stream-proxy] reply.raw.end() failed, likely already destroyed");
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
|
-
|
|
119
|
+
// cleanup 透传 cause:让上游 destroy(err) emit 'error' 事件链,与 CLAUDE.md
|
|
120
|
+
// 「destroy 必须 error 参数」规范一致。无 cause 时 destroy(undefined) 等价 destroy(),
|
|
121
|
+
// 不 emit 'error',自然结束路径(onEnd/stream_success)行为不变。
|
|
122
|
+
this.cleanup(extra.error);
|
|
113
123
|
if (this.resolveFn) {
|
|
114
124
|
this.resolveFn(result);
|
|
115
125
|
}
|
|
@@ -118,10 +128,16 @@ class StreamProxy {
|
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
|
-
cleanup() {
|
|
131
|
+
cleanup(cause) {
|
|
122
132
|
if (this.idleTimer)
|
|
123
133
|
clearTimeout(this.idleTimer);
|
|
124
134
|
this.idleTimer = null;
|
|
135
|
+
// 销毁上游资源,避免连接泄漏(幂等:destroyed 标志保护重复调用)。
|
|
136
|
+
// 传 cause 时 destroy(err) 会 emit 'error',触发 onUpstreamError(resolved 守卫保护下为 noop)。
|
|
137
|
+
if (this.upstreamRes && !this.upstreamRes.destroyed && typeof this.upstreamRes.destroy === "function")
|
|
138
|
+
this.upstreamRes.destroy(cause);
|
|
139
|
+
if (this.upstreamReq && !this.upstreamReq.destroyed && typeof this.upstreamReq.destroy === "function")
|
|
140
|
+
this.upstreamReq.destroy(cause);
|
|
125
141
|
if (this.formatTransform && !this.formatTransform.destroyed)
|
|
126
142
|
this.formatTransform.destroy();
|
|
127
143
|
if (!this.passThrough.destroyed)
|
|
@@ -132,8 +148,8 @@ class StreamProxy {
|
|
|
132
148
|
collectMetrics(isComplete) {
|
|
133
149
|
if (!this.metricsTransform)
|
|
134
150
|
return undefined;
|
|
135
|
-
// 在读取 metrics 之前 flush SSE parser
|
|
136
|
-
// 等末尾事件已被处理。否则 extractor.complete 可能为 false
|
|
151
|
+
// 在读取 metrics 之前 flush SSE parser 缓冲区,确保 [DONE] / message_stop
|
|
152
|
+
// 等末尾事件已被处理。否则 extractor.complete 可能为 false,导致 is_complete=0。
|
|
137
153
|
this.metricsTransform.flushParser();
|
|
138
154
|
const result = this.metricsTransform.getExtractor().getMetrics();
|
|
139
155
|
return isComplete ? result : { ...result, is_complete: 0 };
|
|
@@ -146,15 +162,15 @@ class StreamProxy {
|
|
|
146
162
|
this.idleTimer = setTimeout(() => {
|
|
147
163
|
if (this.resolved)
|
|
148
164
|
return;
|
|
149
|
-
// 在 terminal() 之前同步写入超时错误 SSE
|
|
165
|
+
// 在 terminal() 之前同步写入超时错误 SSE,确保 inject() 能正确收集响应体
|
|
150
166
|
if (this.onTimeoutAbort) {
|
|
151
167
|
try {
|
|
152
168
|
this.onTimeoutAbort(this.timeoutMs);
|
|
153
169
|
}
|
|
154
170
|
catch { /* reply may be destroyed */ } // eslint-disable-line taste/no-silent-catch
|
|
155
171
|
}
|
|
156
|
-
// deferred
|
|
157
|
-
// reply.raw.end() 延迟到 setImmediate
|
|
172
|
+
// deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中完成,
|
|
173
|
+
// reply.raw.end() 延迟到 setImmediate(macrotask),保证 inject() 返回时日志已写入。
|
|
158
174
|
this.terminal("stream_abort", { metrics: this.collectMetrics(false), timeoutContext: this.timeoutContext, timeoutMs: this.timeoutMs, abortReason: "idle_timeout" }, true);
|
|
159
175
|
setImmediate(() => {
|
|
160
176
|
if (this.headersSent) {
|
|
@@ -166,12 +182,13 @@ class StreamProxy {
|
|
|
166
182
|
this.cleanup();
|
|
167
183
|
});
|
|
168
184
|
}, this.timeoutMs);
|
|
185
|
+
this.idleTimer.unref();
|
|
169
186
|
}
|
|
170
187
|
startStreaming() {
|
|
171
188
|
if (this.headersSent)
|
|
172
189
|
return;
|
|
173
190
|
if (this.reply.raw.headersSent) {
|
|
174
|
-
// headers
|
|
191
|
+
// headers 已由其他代码路径(如前一次 retry 的 StreamProxy)发送,
|
|
175
192
|
// 仅更新状态机避免 BUFFERING 阶段的重复逻辑
|
|
176
193
|
this.transition("STREAMING");
|
|
177
194
|
this.headersSent = true;
|
|
@@ -183,7 +200,7 @@ class StreamProxy {
|
|
|
183
200
|
this.reply.raw.writeHead(this.statusCode, this.sseHeaders);
|
|
184
201
|
}
|
|
185
202
|
catch {
|
|
186
|
-
// 客户端在 state transition 和 writeHead
|
|
203
|
+
// 客户端在 state transition 和 writeHead 之间断连,可安全忽略
|
|
187
204
|
this.terminal("stream_abort", { abortReason: "client_disconnect" });
|
|
188
205
|
return;
|
|
189
206
|
}
|
|
@@ -193,17 +210,26 @@ class StreamProxy {
|
|
|
193
210
|
if (this.formatTransform) {
|
|
194
211
|
this.formatTransform.pipe(this.passThrough, { end: true });
|
|
195
212
|
}
|
|
196
|
-
// 手动转发而非 pipe
|
|
213
|
+
// 手动转发而非 pipe,避免 Node.js 在 dest 上自动注册 close/finish handler
|
|
197
214
|
this.passThrough.on("data", (chunk) => {
|
|
198
215
|
try {
|
|
199
216
|
this.reply.raw.write(chunk);
|
|
200
217
|
}
|
|
201
218
|
catch { // eslint-disable-line taste/no-silent-catch
|
|
202
|
-
//
|
|
219
|
+
// 客户端已断开,写已销毁的 socket 会抛出异常,可安全忽略
|
|
203
220
|
}
|
|
204
221
|
});
|
|
205
|
-
// 不在 passThrough end 事件中调用 reply.raw.end()
|
|
222
|
+
// 不在 passThrough end 事件中调用 reply.raw.end(),
|
|
206
223
|
// 因为 onEnd() 统一管理响应结束时机,确保日志在 reply end 之前写入
|
|
224
|
+
// passThrough 异常若不处理会冒泡 uncaughtException;此处直接走 terminal 保证 Promise resolve,
|
|
225
|
+
// 避免 callStream 永挂(cleanup 的 upstreamRes.destroy() 无 error 时不 emit 'error',
|
|
226
|
+
// onUpstreamError 兜底不触发,是旧实现的隐藏 bug)。
|
|
227
|
+
this.passThrough.on("error", (err) => {
|
|
228
|
+
console.warn("[stream-proxy] passThrough error:", err.message);
|
|
229
|
+
if (!this.resolved) {
|
|
230
|
+
this.terminal("stream_abort", { abortReason: "pipe_error", error: err });
|
|
231
|
+
}
|
|
232
|
+
});
|
|
207
233
|
for (const c of this.bufferChunks)
|
|
208
234
|
this.pipeEntry.write(c);
|
|
209
235
|
this.bufferChunks.length = 0;
|
|
@@ -212,10 +238,10 @@ class StreamProxy {
|
|
|
212
238
|
if (this.closeHandlerRegistered)
|
|
213
239
|
return;
|
|
214
240
|
this.closeHandlerRegistered = true;
|
|
215
|
-
// EPIPE 从底层 socket 的 WriteWrap.onWriteComplete emit
|
|
241
|
+
// EPIPE 从底层 socket 的 WriteWrap.onWriteComplete emit,reply.raw.on("error") 无法拦截。
|
|
216
242
|
// 必须直接在 socket 上注册 error handler 防止冒泡到 process uncaughtException。
|
|
217
|
-
// Node.js HTTP server 内置的 socketOnError 只处理第一次 socket error
|
|
218
|
-
//
|
|
243
|
+
// Node.js HTTP server 内置的 socketOnError 只处理第一次 socket error,
|
|
244
|
+
// 如果第一次被其他错误消耗,后续 EPIPE 可能无 handler 导致进程崩溃。
|
|
219
245
|
const sock = this.reply.raw.socket;
|
|
220
246
|
let sockErrorHandler;
|
|
221
247
|
if (sock) {
|
|
@@ -246,7 +272,7 @@ class StreamProxy {
|
|
|
246
272
|
if (this.state === "BUFFERING") {
|
|
247
273
|
this.bufferChunks.push(chunk);
|
|
248
274
|
this.totalBuffered += chunk.length;
|
|
249
|
-
//
|
|
275
|
+
// 快速路径:检查大小限制(无需 concat)
|
|
250
276
|
if (this.totalBuffered >= BUFFER_SIZE_LIMIT) {
|
|
251
277
|
const buf = Buffer.concat(this.bufferChunks);
|
|
252
278
|
const text = buf.toString("utf-8");
|
|
@@ -258,10 +284,10 @@ class StreamProxy {
|
|
|
258
284
|
this.startStreaming();
|
|
259
285
|
return;
|
|
260
286
|
}
|
|
261
|
-
//
|
|
287
|
+
// 快速路径:检查当前 chunk 是否包含 \n\n
|
|
262
288
|
const chunkStr = chunk.toString("utf-8");
|
|
263
289
|
const hasNewlinePair = chunkStr.includes("\n\n");
|
|
264
|
-
// 跨 chunk
|
|
290
|
+
// 跨 chunk 边界检测:上一个 chunk 以 \n 结尾 + 当前以 \n 开头
|
|
265
291
|
const crossBoundary = this.lastChunkEndedWithNewline && chunkStr.startsWith("\n");
|
|
266
292
|
this.lastChunkEndedWithNewline = chunkStr.endsWith("\n");
|
|
267
293
|
if (hasNewlinePair || crossBoundary) {
|
|
@@ -278,25 +304,25 @@ class StreamProxy {
|
|
|
278
304
|
}
|
|
279
305
|
return;
|
|
280
306
|
}
|
|
281
|
-
// STREAMING
|
|
307
|
+
// STREAMING 阶段:扫描 SSE error event(处理跨 chunk 边界)
|
|
282
308
|
if (this.state === "STREAMING" && this.checkEarlyError) {
|
|
283
309
|
this.sseScanBuffer += chunk.toString("utf-8");
|
|
284
|
-
// 保留最近 SSE_SCAN_MAX
|
|
310
|
+
// 保留最近 SSE_SCAN_MAX 字符,避免无限增长
|
|
285
311
|
if (this.sseScanBuffer.length > StreamProxy.SSE_SCAN_MAX) {
|
|
286
312
|
this.sseScanBuffer = this.sseScanBuffer.slice(-StreamProxy.SSE_SCAN_MAX);
|
|
287
313
|
}
|
|
288
|
-
//
|
|
314
|
+
// 快速启发式:只在扫描窗口出现 SSE error 标记时才执行正则匹配
|
|
289
315
|
if (this.sseScanBuffer.includes("event: error") || this.sseScanBuffer.includes('"type":"error"')) {
|
|
290
316
|
if (this.checkEarlyError(this.sseScanBuffer)) {
|
|
291
317
|
this.terminal("stream_error", { body: this.sseScanBuffer });
|
|
292
|
-
// headers
|
|
318
|
+
// headers 已发送:必须结束 reply 避免 client hang
|
|
293
319
|
if (this.headersSent) {
|
|
294
320
|
setImmediate(() => {
|
|
295
321
|
try {
|
|
296
322
|
this.reply.raw.end();
|
|
297
323
|
}
|
|
298
324
|
catch { // eslint-disable-line taste/no-silent-catch
|
|
299
|
-
// reply 可能已 destroyed
|
|
325
|
+
// reply 可能已 destroyed,安全忽略
|
|
300
326
|
}
|
|
301
327
|
});
|
|
302
328
|
}
|
|
@@ -326,19 +352,19 @@ class StreamProxy {
|
|
|
326
352
|
if (this.state === "STREAMING") {
|
|
327
353
|
this.transition("COMPLETED");
|
|
328
354
|
}
|
|
329
|
-
// 通过 terminal 的 deferred 模式统一 resolve
|
|
330
|
-
// 先 resolve Promise
|
|
331
|
-
// reply.raw.end() 延迟到 setImmediate
|
|
332
|
-
// light-my-request 监听 reply.raw 的 end
|
|
355
|
+
// 通过 terminal 的 deferred 模式统一 resolve:
|
|
356
|
+
// 先 resolve Promise,让 handler 链路(日志写入等)在 microtask 中执行。
|
|
357
|
+
// reply.raw.end() 延迟到 setImmediate(macrotask),确保 microtask 先完成。
|
|
358
|
+
// light-my-request 监听 reply.raw 的 end 事件判定响应完成,
|
|
333
359
|
// 这保证了 inject() 返回时日志已经写入 DB。
|
|
334
360
|
const metrics = this.collectMetrics(true);
|
|
335
361
|
this.terminal("stream_success", { metrics }, true);
|
|
336
|
-
//
|
|
362
|
+
// 延迟结束管道和响应,属于 reply 层面操作,不属于 StreamProxy 状态管理。
|
|
337
363
|
//
|
|
338
|
-
// 当 formatTransform
|
|
339
|
-
// 通过 process.nextTick 异步传播。pipeEntry.end() 后不能同步调用 reply.raw.end()
|
|
340
|
-
// 否则 formatTransform._flush() 中的 ensureTerminated()
|
|
341
|
-
//
|
|
364
|
+
// 当 formatTransform 存在时(如 OpenAI→Anthropic 转换),Transform 链的 flush
|
|
365
|
+
// 通过 process.nextTick 异步传播。pipeEntry.end() 后不能同步调用 reply.raw.end(),
|
|
366
|
+
// 否则 formatTransform._flush() 中的 ensureTerminated()(发送 message_stop)
|
|
367
|
+
// 尚未执行,连接就被关闭,导致客户端收到 "stream ended before message_stop"。
|
|
342
368
|
setImmediate(() => {
|
|
343
369
|
const endReply = () => {
|
|
344
370
|
if (this.headersSent) {
|
|
@@ -346,16 +372,16 @@ class StreamProxy {
|
|
|
346
372
|
this.reply.raw.end();
|
|
347
373
|
}
|
|
348
374
|
catch { // eslint-disable-line taste/no-silent-catch
|
|
349
|
-
// reply 可能已 destroyed
|
|
375
|
+
// reply 可能已 destroyed,安全忽略
|
|
350
376
|
}
|
|
351
377
|
}
|
|
352
378
|
this.cleanup();
|
|
353
379
|
};
|
|
354
380
|
this.pipeEntry.end();
|
|
355
381
|
if (this.formatTransform) {
|
|
356
|
-
// 等 passThrough end
|
|
382
|
+
// 等 passThrough end 事件触发(整个 transform 链 flush 完成后),再关闭 reply
|
|
357
383
|
this.passThrough.once("end", endReply);
|
|
358
|
-
//
|
|
384
|
+
// 安全超时兜底,防止 end 事件未触发导致连接挂起
|
|
359
385
|
setTimeout(endReply, END_REPLY_TIMEOUT_MS).unref();
|
|
360
386
|
}
|
|
361
387
|
else {
|
|
@@ -366,6 +392,10 @@ class StreamProxy {
|
|
|
366
392
|
onUpstreamError(err) {
|
|
367
393
|
if (this.resolved)
|
|
368
394
|
return;
|
|
395
|
+
// 状态机一致:上游错误视为中止(resolved=true 不保证 state 已转终态)
|
|
396
|
+
if (this.state === "BUFFERING" || this.state === "STREAMING") {
|
|
397
|
+
this.transition("ABORTED");
|
|
398
|
+
}
|
|
369
399
|
this.resolved = true;
|
|
370
400
|
this.cleanup();
|
|
371
401
|
const result = { kind: "throw", error: err, headersSent: this.headersSent };
|
|
@@ -378,7 +408,7 @@ class StreamProxy {
|
|
|
378
408
|
}
|
|
379
409
|
}
|
|
380
410
|
// ---------- callStream ----------
|
|
381
|
-
export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, agent) {
|
|
411
|
+
export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutMs, upstreamPath, buildHeaders, metricsTransform, checkEarlyError, compatResolve, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, agent, opts) {
|
|
382
412
|
return new Promise((resolve) => {
|
|
383
413
|
const effectiveResolve = compatResolve ?? resolve;
|
|
384
414
|
const url = new URL(buildUpstreamUrl(backend.base_url, upstreamPath));
|
|
@@ -386,13 +416,59 @@ export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutM
|
|
|
386
416
|
const upstreamHeaders = buildHeaders(clientHeaders, apiKey, Buffer.byteLength(payload));
|
|
387
417
|
const options = buildRequestOptions(url, upstreamHeaders);
|
|
388
418
|
const upstreamReq = _transportInternals.createUpstreamRequest(url, options, agent);
|
|
419
|
+
// 响应头前无活动超时(拿到 upstreamRes 后改由 StreamProxy.idleTimer 接管)。
|
|
420
|
+
// destroy(error) 触发 'error' 事件 → effectiveResolve({kind:'throw'})。
|
|
421
|
+
const connectTimeoutMs = opts?.connectTimeoutMs;
|
|
422
|
+
const hasConnectTimeout = connectTimeoutMs !== undefined && Number.isFinite(connectTimeoutMs) && connectTimeoutMs > 0;
|
|
423
|
+
if (hasConnectTimeout) {
|
|
424
|
+
upstreamReq.setTimeout(connectTimeoutMs);
|
|
425
|
+
upstreamReq.on("timeout", () => upstreamReq.destroy(new Error("upstream inactivity timeout (pre-response)")));
|
|
426
|
+
}
|
|
427
|
+
// 客户端断连:abort 信号穿透到上游 socket,立即切断连接。
|
|
428
|
+
// resolveOnce 在 Promise settle 时移除 abort listener,避免重试累积残留
|
|
429
|
+
// (listener 受 ITERATION_CAP 上界约束,非进程级泄漏,此处显式清理保持干净)。
|
|
430
|
+
const clientSignal = opts?.signal;
|
|
431
|
+
const onClientAbort = clientSignal
|
|
432
|
+
? () => upstreamReq.destroy(new Error("client aborted"))
|
|
433
|
+
: undefined;
|
|
434
|
+
const resolveOnce = (r) => {
|
|
435
|
+
if (onClientAbort && clientSignal && !clientSignal.aborted) {
|
|
436
|
+
clientSignal.removeEventListener("abort", onClientAbort);
|
|
437
|
+
}
|
|
438
|
+
effectiveResolve(r);
|
|
439
|
+
};
|
|
440
|
+
if (onClientAbort && clientSignal) {
|
|
441
|
+
if (clientSignal.aborted) {
|
|
442
|
+
onClientAbort();
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
clientSignal.addEventListener("abort", onClientAbort, { once: true });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
389
448
|
upstreamReq.on("response", (upstreamRes) => {
|
|
449
|
+
// 响应头已到达:清除响应头前 socket 超时,避免与 StreamProxy.idleTimer 竞争
|
|
450
|
+
if (hasConnectTimeout)
|
|
451
|
+
upstreamReq.setTimeout(0);
|
|
390
452
|
const statusCode = upstreamRes.statusCode || UPSTREAM_BAD_GATEWAY;
|
|
391
453
|
if (statusCode !== UPSTREAM_SUCCESS) {
|
|
392
454
|
const chunks = [];
|
|
455
|
+
// 非 2xx body 兜底超时:connect timeout 已在响应头到达时清零(L474),
|
|
456
|
+
// 若上游发半截 body 后静默挂住(不 FIN 不 RST,CDN/反代常见故障),
|
|
457
|
+
// end/error 均不触发 → resolveOnce 永不调用 → slot 泄漏。
|
|
458
|
+
// 复用 connectTimeoutMs 作为 body 读取上限,与 200 路径 idleTimer 同源。
|
|
459
|
+
const bodyGuard = hasConnectTimeout
|
|
460
|
+
? setTimeout(() => upstreamRes.destroy(new Error("upstream error-body timeout")), connectTimeoutMs)
|
|
461
|
+
: null;
|
|
462
|
+
if (bodyGuard)
|
|
463
|
+
bodyGuard.unref();
|
|
464
|
+
const finishBody = (r) => {
|
|
465
|
+
if (bodyGuard)
|
|
466
|
+
clearTimeout(bodyGuard);
|
|
467
|
+
resolveOnce(r);
|
|
468
|
+
};
|
|
393
469
|
upstreamRes.on("data", (chunk) => chunks.push(chunk));
|
|
394
470
|
upstreamRes.on("end", () => {
|
|
395
|
-
|
|
471
|
+
finishBody({
|
|
396
472
|
kind: "stream_error",
|
|
397
473
|
statusCode,
|
|
398
474
|
body: Buffer.concat(chunks).toString("utf-8"),
|
|
@@ -400,10 +476,13 @@ export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutM
|
|
|
400
476
|
sentHeaders: upstreamHeaders,
|
|
401
477
|
});
|
|
402
478
|
});
|
|
479
|
+
// 非 2xx body 传输中连接中断:无 listener 会 uncaughtException + Promise 永挂(slot 泄漏)。
|
|
480
|
+
// 与 200 分支 proxy.onUpstreamError 对称,与 callNonStream(http.ts) 同路径一致。
|
|
481
|
+
upstreamRes.on("error", (err) => finishBody({ kind: "throw", error: err }));
|
|
403
482
|
return;
|
|
404
483
|
}
|
|
405
|
-
const proxy = new StreamProxy(statusCode, upstreamRes.headers, upstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort);
|
|
406
|
-
proxy.bindResolve(
|
|
484
|
+
const proxy = new StreamProxy(statusCode, upstreamRes.headers, upstreamHeaders, reply, metricsTransform, checkEarlyError, timeoutMs, loopGuard, formatTransform, timeoutContext, onTimeoutAbort, upstreamRes, upstreamReq);
|
|
485
|
+
proxy.bindResolve(resolveOnce);
|
|
407
486
|
proxy.registerCloseHandler();
|
|
408
487
|
// 无 early error checker 时直接开始流式传输
|
|
409
488
|
if (!checkEarlyError)
|
|
@@ -413,7 +492,7 @@ export function callStream(backend, apiKey, body, clientHeaders, reply, timeoutM
|
|
|
413
492
|
upstreamRes.on("end", () => proxy.onEnd());
|
|
414
493
|
upstreamRes.on("error", (err) => proxy.onUpstreamError(err));
|
|
415
494
|
});
|
|
416
|
-
upstreamReq.on("error", (error) =>
|
|
495
|
+
upstreamReq.on("error", (error) => resolveOnce({ kind: "throw", error }));
|
|
417
496
|
upstreamReq.write(payload);
|
|
418
497
|
upstreamReq.end();
|
|
419
498
|
});
|
|
@@ -18,6 +18,7 @@ export interface TransportFnParams {
|
|
|
18
18
|
logId: string;
|
|
19
19
|
effectiveModel: string;
|
|
20
20
|
streamTimeoutMs: number;
|
|
21
|
+
nonStreamTimeoutMs?: number;
|
|
21
22
|
tracker?: RequestTracker;
|
|
22
23
|
matcher?: RetryRuleMatcher;
|
|
23
24
|
request: FastifyRequest;
|
|
@@ -32,4 +33,4 @@ export interface TransportFnParams {
|
|
|
32
33
|
proxyAgentFactory?: ProxyAgentFactory;
|
|
33
34
|
resolvedBaseUrl: string;
|
|
34
35
|
}
|
|
35
|
-
export declare function buildTransportFn(p: TransportFnParams): (target: Target) => Promise<TransportResult>;
|
|
36
|
+
export declare function buildTransportFn(p: TransportFnParams): (target: Target, signal?: AbortSignal) => 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, signal) => {
|
|
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, { signal, connectTimeoutMs: p.streamTimeoutMs });
|
|
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, { signal, timeoutMs: p.nonStreamTimeoutMs });
|
|
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{$t as e,Jt as t,Mt as n,St as r,et as i,n as a,ot as o,rt as s,st as c,tt as l,yt as u}from"./button-CZXw3CE5.js";import{dt as d,ft as f,r as p}from"./index-DU-d4dwG.js";import{t as m}from"./Card-BlGAfXME.js";import{t as h}from"./CardContent-DTKSvHkJ.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=c({__name:`AuthLayout`,props:{subtitle:{}},setup(c){let{isDark:y,toggleTheme:b}=p();return(p,x)=>(u(),s(`div`,g,[o(t(a),{variant:`ghost`,size:`icon`,class:`absolute top-4 right-4 text-muted-foreground hover:text-foreground`,onClick:t(b)},{default:n(()=>[t(y)?(u(),l(t(d),{key:1,class:`w-4 h-4`})):(u(),l(t(f),{key:0,class:`w-4 h-4`}))]),_:1},8,[`onClick`]),o(t(m),{class:`w-full max-w-sm shadow-lg`},{default:n(()=>[o(t(h),{class:`pt-6`},{default:n(()=>[i(`div`,_,[x[0]||=i(`div`,{class:`w-12 h-12 bg-primary rounded-lg mx-auto mb-3 flex items-center justify-center`},[i(`svg`,{class:`w-7 h-7 text-white`,fill:`none`,stroke:`currentColor`,viewBox:`0 0 24 24`},[i(`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]||=i(`h1`,{class:`text-xl font-semibold text-foreground`},` LLM Simple Router `,-1),i(`p`,v,e(c.subtitle),1)]),r(p.$slots,`default`)]),_:3})]),_:3})]))}});export{y as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Jt as e,St as t,Xt as n,r,rt as i,st as a,yt as o}from"./button-CZXw3CE5.js";var s=[`data-size`,`data-flush`],c=a({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`},flush:{type:Boolean,default:!1}},setup(a){let c=a;return(l,u)=>(o(),i(`div`,{"data-slot":`card`,"data-size":a.size,"data-flush":a.flush||void 0,class:n(e(r)(`border-border bg-card text-card-foreground rounded-lg border text-sm group/card flex flex-col`,a.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))},[t(l.$slots,`default`)],10,s))}});export{c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Jt as e,St as t,Xt as n,r,rt as i,st as a,yt as o}from"./button-CZXw3CE5.js";var s=a({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(o(),i(`div`,{"data-slot":`card-content`,class:n(e(r)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[t(a.$slots,`default`)],2))}});export{s as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Jt as e,St as t,Xt as n,r,rt as i,st as a,yt as o}from"./button-CZXw3CE5.js";var s=a({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(o(),i(`div`,{"data-slot":`card-header`,class:n(e(r)(`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))},[t(a.$slots,`default`)],2))}}),c=a({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(o(),i(`div`,{"data-slot":`card-title`,class:n(e(r)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[t(a.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,$t as t,Bt as n,Jt as r,Mt as i,Q as a,Qt as o,W as s,Xt as c,Z as l,et as u,ht as d,nt as f,ot as p,rt as m,st as h,tt as g,xt as _,yt as v}from"./button-CZXw3CE5.js";import{t as y}from"./chevron-right-XHFgIZAJ.js";import{b,vt as x,x as S,xt as C,y as w}from"./index-DU-d4dwG.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=h({__name:`CascadingSelect`,props:{groups:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean,default:!1},dashed:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(h,{emit:C}){let{t:N}=s(),P=h,F=e(()=>P.placeholder||N(`common.selectPlaceholder`)),I=C,L=n(!1),R=n(null),z=n(new Map),B=n({top:0,left:0}),V=null;function H(e,t){t?z.value.set(e,t):z.value.delete(e)}function U(e){V&&=(clearTimeout(V),null),R.value=e,d(()=>q(e))}function W(){V=setTimeout(()=>{R.value=null},M)}function G(){V&&=(clearTimeout(V),null)}function K(){V=setTimeout(()=>{R.value=null},M)}function q(e){let t=z.value.get(e);if(!t)return;let n=t.getBoundingClientRect();B.value={top:n.top,left:n.right}}function J(e){e.detail.originalEvent.target?.closest(`[data-cascade-submenu]`)&&e.preventDefault()}let Y=e(()=>{if(!P.modelValue)return``;let e=P.groups.find(e=>e.key===P.modelValue.groupKey);if(!e)return``;let t=e.options.find(e=>e.value===P.modelValue.value);return t?`${e.label} / ${t.label}`:``});function X(e,t){I(`update:modelValue`,{groupKey:e,value:t}),L.value=!1}function Z(e){L.value=e,e||(R.value=null)}return(e,n)=>(v(),g(r(S),{open:L.value,"onUpdate:open":Z},{default:i(()=>[p(r(w),{"as-child":``},{default:i(()=>[u(`div`,{class:c([`flex w-full items-center justify-between rounded-md bg-background ring-offset-background cursor-pointer hover:bg-accent hover:text-accent-foreground`,[h.compact?`h-7 text-xs px-2 py-1 rounded-md`:`h-10 text-sm px-3 py-2 border border-border rounded-md`,!h.compact&&h.dashed?`border-dashed border-primary/20`:``,{"ring-2 ring-ring ring-offset-2":L.value}]])},[u(`span`,{class:c([`truncate`,h.modelValue?`text-foreground`:`text-muted-foreground`])},t(Y.value||F.value),3),p(r(x),{class:`h-4 w-4 shrink-0 opacity-50`})],2)]),_:1}),p(r(b),{align:`start`,"side-offset":4,class:`z-[200] w-auto min-w-56 max-h-[80vh] overflow-y-auto p-1`,onPointerDownOutside:J},{default:i(()=>[(v(!0),m(l,null,_(h.groups,e=>(v(),m(`div`,{key:e.key,ref_for:!0,ref:t=>H(e.key,t),class:c([`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":R.value===e.key}]),onMouseenter:t=>U(e.key),onMouseleave:W},[u(`span`,E,t(e.label),1),e.badge?(v(),m(`span`,D,t(e.badge),1)):f(``,!0),p(r(y),{class:`ml-1 h-4 w-4 shrink-0 opacity-50`})],42,T))),128)),(v(),g(a,{to:`body`},[R.value&&h.groups.find(e=>e.key===R.value)?.options.length?(v(),m(`div`,{key:0,"data-cascade-submenu":``,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:o({top:`${B.value.top}px`,left:`${B.value.left}px`}),onMouseenter:G,onMouseleave:K},[(v(!0),m(l,null,_(h.groups.find(e=>e.key===R.value)?.options??[],e=>(v(),m(`div`,{key:e.value,class:c([`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":h.modelValue?.groupKey===R.value&&h.modelValue?.value===e.value}]),onClick:t=>X(R.value,e.value)},[u(`span`,k,t(e.label),1),e.tag?(v(),m(`span`,A,t(e.tag),1)):f(``,!0)],10,O))),128))],36)):f(``,!0)])),h.groups.length===0?(v(),m(`div`,j,t(r(N)(`common.noOptions`)),1)):f(``,!0)]),_:1})]),_:1},8,[`open`]))}}),P=h({__name:`CascadingModelSelect`,props:{providers:{},modelValue:{},placeholder:{default:``},compact:{type:Boolean,default:!1},dashed:{type:Boolean,default:!1}},emits:[`update:modelValue`],setup(t,{emit:n}){let{t:r}=s(),i=t,a=e(()=>i.placeholder||r(`mappings.selectProviderModel`)),o=n,c=e(()=>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:C(e.contextWindow)}))}))),l=e(()=>i.modelValue?{groupKey:i.modelValue.provider_id,value:i.modelValue.model}:void 0);function u(e){o(`update:modelValue`,{provider_id:e.groupKey,model:e.value})}return(e,n)=>(v(),g(N,{groups:c.value,"model-value":l.value,placeholder:a.value,compact:t.compact,dashed:t.dashed,"onUpdate:modelValue":u},null,8,[`groups`,`model-value`,`placeholder`,`compact`,`dashed`]))}});export{P as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Jt as t,Mt as n,S as r,St as i,X as a,Y as o,Zt as s,h as c,i as l,lt as u,mt as d,nt as f,o as p,ot as m,r as h,st as g,tt as _,wt as v,yt as y}from"./button-CZXw3CE5.js";import{t as b}from"./VisuallyHiddenInput-CM0ZcPu6.js";import{t as x}from"./RovingFocusItem-mNZuQwzG.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-DU-d4dwG.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=g({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:s}){let u=r,m=s,{forwardRef:h,currentElement:g}=p(),S=A(null),E=c(u,`modelValue`,m,{defaultValue:u.defaultValue??u.falseValue,passive:u.modelValue===void 0}),D=e(()=>S?.disabled.value||u.disabled),O=e(()=>w(E.value,u.trueValue)),j=e(()=>C(S?.modelValue.value)?E.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,u.value));function P(){if(C(S?.modelValue.value))E.value===`indeterminate`?E.value=u.trueValue:E.value=O.value?u.falseValue:u.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,u.value)){let t=e.findIndex(e=>w(e,u.value));e.splice(t,1)}else e.push(u.value);S.modelValue.value=e}}let I=T(g),L=e(()=>u.id&&g.value?document.querySelector(`[for="${u.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,r)=>(y(),_(v(t(S)?.rovingFocus.value?t(x):t(l)),d(e.$attrs,{id:e.id,ref:t(h),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":t(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":t(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:t(S)?.rovingFocus.value?!D.value:void 0,onKeydown:o(a(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:n(()=>[i(e.$slots,`default`,{modelValue:t(E),state:j.value}),t(I)&&e.name&&!t(S)?(y(),_(t(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):f(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=g({__name:`CheckboxIndicator`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`span`}},setup(e){let{forwardRef:r}=p(),a=P();return(e,o)=>(y(),_(t(O),{present:e.forceMount||t(M)(t(a).state.value)||t(a).state.value===!0},{default:n(()=>[m(t(l),d({ref:t(r),"data-state":t(N)(t(a).state.value),"data-disabled":t(a).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:n(()=>[i(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=g({__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:a}){let o=e,c=a,l=S(r(o,`class`),c);return(e,r)=>(y(),_(t(I),d({"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`,o.class)}),{default:n(r=>[m(t(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:n(()=>[i(e.$slots,`default`,s(u(r)),()=>[m(t(D))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Bt as t,Gt as n,Jt as r,Mt as i,St as a,Zt as o,_t as s,f as c,h as l,ht as u,i as d,kt as f,lt as p,mt as m,nt as h,o as g,ot as _,st as v,tt as y,yt as b}from"./button-CZXw3CE5.js";import{H as x,V as S,X as C,z as w}from"./index-DU-d4dwG.js";var[T,E]=C(`CollapsibleRoot`),D=v({__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:o}){let s=e,c=l(s,`open`,o,{defaultValue:s.defaultOpen,passive:s.open===void 0}),{disabled:u,unmountOnHide:f}=n(s);return E({contentId:``,disabled:u,open:c,unmountOnHide:f,onOpenToggle:()=>{u.value||(c.value=!c.value)}}),t({open:c}),g(),(e,t)=>(b(),y(r(d),{as:e.as,"as-child":s.asChild,"data-state":r(c)?`open`:`closed`,"data-disabled":r(u)?``:void 0},{default:i(()=>[a(e.$slots,`default`,{open:r(c)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=v({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(n,{emit:o}){let l=n,p=o,v=T();v.contentId||=S(void 0,`reka-collapsible-content`);let x=t(),{forwardRef:C,currentElement:E}=g(),D=t(0),O=t(0),k=e(()=>v.open.value),A=t(k.value),j=t();f(()=>[k.value,x.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=e(()=>A.value&&v.open.value);return s(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{v.onOpenToggle(),p(`contentFound`)})}),(e,t)=>(b(),y(r(w),{ref_key:`presentRef`,ref:x,present:e.forceMount||r(v).open.value,"force-mount":!0},{default:i(({present:t})=>[_(r(d),m(e.$attrs,{id:r(v).contentId,ref:r(C),"as-child":l.asChild,as:e.as,hidden:t?void 0:r(v).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:r(v).open.value?`open`:`closed`,"data-disabled":r(v).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:i(()=>[!r(v).unmountOnHide.value||t?a(e.$slots,`default`,{key:0}):h(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=v({__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)=>(b(),y(r(D),m({"data-slot":`collapsible`},r(n)),{default:i(t=>[a(e.$slots,`default`,o(p(t)))]),_:3},16))}}),A=v({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,n)=>(b(),y(r(O),m({"data-slot":`collapsible-content`},t),{default:i(()=>[a(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Jt as e,Mt as t,St as n,i as r,mt as i,o as a,st as o,tt as s,yt as c}from"./button-CZXw3CE5.js";import{r as l}from"./CollapsibleContent-BgO8VoPR.js";var u=o({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(i){let o=i;a();let u=l();return(i,a)=>(c(),s(e(r),{type:i.as===`button`?`button`:void 0,as:i.as,"as-child":o.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:t(()=>[n(i.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=o({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(r){let a=r;return(r,o)=>(c(),s(e(u),i({"data-slot":`collapsible-trigger`},a),{default:t(()=>[n(r.$slots,`default`)]),_:3},16))}});export{d as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,$t as t,Bt as n,Jt as r,Mt as i,W as a,X as o,Xt as s,Y as c,Z as l,at as u,et as d,n as f,nt as p,ot as m,rt as h,st as g,tt as _,xt as v,yt as y,z as b}from"./button-CZXw3CE5.js";import{t as x}from"./plus-BjrVWmRw.js";import{t as S}from"./trash-2-CHuDHKxp.js";import{t as C}from"./Label-DARM_yCh.js";import{t as ee}from"./Switch-K9syOZ4L.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-DU-d4dwG.js";import{t as ie}from"./badge-BVIIW0-Q.js";import{t as k}from"./Input-BBtQpfsU.js";import{t as A}from"./CascadingModelSelect-ul9uB6Dy.js";import{r as j}from"./transform-domain-D0mVmoZd.js";var M=b(`arrow-right`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`m12 5 7 7-7 7`,key:`xquz4c`}]]),N=b(`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(g,{emit:b}){let{t:C}=a(),w=g,T=b,E=n(!1),D=n(``);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=e(()=>{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=e(()=>{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=e(()=>!!w.entry.targets[0]?.overflow_model),$=e(()=>!!w.entry.multimodalFallback?.backend_model);return(e,n)=>(y(),h(`div`,null,[g.expanded?(y(),h(`div`,B,[d(`div`,V,[d(`span`,H,t(r(C)(`mappings.editor.clientLabel`)),1),E.value?(y(),h(`div`,{key:0,class:`flex-1 flex items-center gap-1`,onClick:n[5]||=o(()=>{},[`stop`])},[m(r(k),{modelValue:D.value,"onUpdate:modelValue":n[4]||=e=>D.value=e,class:`h-7 flex-1 text-xs font-mono border-[1px] border-border`,placeholder:r(C)(`mappings.editor.clientInputPlaceholder`),onKeydown:[c(o(j,[`prevent`]),[`enter`]),c(o(q,[`prevent`]),[`escape`])],onBlur:j,autofocus:``},null,8,[`modelValue`,`placeholder`,`onKeydown`])])):(y(),h(`div`,U,[d(`span`,{class:s([`font-mono text-xs font-semibold text-foreground`,{"cursor-pointer hover:text-primary transition-colors":g.editableClientModel}]),onClick:O},t(g.entry.clientModel),3),g.editableClientModel?(y(),_(r(f),{key:0,variant:`ghost`,size:`xs`,class:`text-[10px] text-muted-foreground/40`,onClick:o(O,[`stop`])},{default:i(()=>[u(t(r(C)(`mappings.editor.edit`)),1)]),_:1})):p(``,!0)]))]),d(`div`,W,[d(`div`,G,[m(r(te),{class:`size-3 text-muted-foreground/50`}),d(`span`,K,t(r(C)(`mappings.editor.failoverChain`)),1)]),(y(!0),h(l,null,v(g.entry.targets,(e,a)=>(y(),h(`div`,{key:a,class:`space-y-0`},[d(`div`,ce,[d(`span`,{class:s([`text-xs font-medium shrink-0 w-5 text-center px-0.5 py-0.5 rounded`,a===0?`bg-primary/10 text-primary`:`bg-muted/40 text-muted-foreground`])},t(a===0?`①`:a===1?`②`:a===2?`③`:`${a+1}`),3),d(`div`,le,[m(A,{providers:g.providerGroups,"model-value":{provider_id:e.provider_id,model:e.backend_model},compact:``,placeholder:r(C)(`providers.shared.selectModel`),"onUpdate:modelValue":e=>je(a,e)},null,8,[`providers`,`model-value`,`placeholder`,`onUpdate:modelValue`])]),a>0&&g.entry.targets.length>1?(y(),_(r(f),{key:0,variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/30 hover:text-destructive`,onClick:e=>Ae(a)},{default:i(()=>[m(r(S),{class:`size-3`})]),_:1},8,[`onClick`])):p(``,!0)]),a<g.entry.targets.length-1?(y(),h(`div`,ue,[n[13]||=d(`div`,{class:`w-px h-1.5 bg-orange-400/30`},null,-1),d(`span`,de,t(r(C)(`mappings.editor.failoverOnError`)),1)])):p(``,!0)]))),128)),m(r(f),{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:i(()=>[m(r(x),{class:`size-3`}),u(` `+t(r(C)(`mappings.addBackup`)),1)]),_:1})]),d(`div`,fe,[d(`div`,pe,[m(r(re),{class:`size-3 text-primary/50`}),d(`span`,me,t(r(C)(`mappings.editor.contextOverflow`)),1),d(`span`,he,t(r(C)(`mappings.editor.contextOverflowHint`)),1)]),Q.value?(y(),h(`div`,ge,[n[14]||=d(`span`,{class:`text-xs shrink-0 w-5 text-center px-0.5 py-0.5 rounded bg-primary/6 text-primary/60`},`↓`,-1),d(`div`,_e,[m(A,{providers:g.providerGroups,"model-value":g.entry.targets[0]?.overflow_provider_id&&g.entry.targets[0]?.overflow_model?{provider_id:g.entry.targets[0].overflow_provider_id,model:g.entry.targets[0].overflow_model}:void 0,compact:``,dashed:``,placeholder:r(C)(`providers.shared.overflowPlaceholder`),"onUpdate:modelValue":n[6]||=e=>X(e)},null,8,[`providers`,`model-value`,`placeholder`])]),m(r(f),{variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/30 hover:text-destructive`,onClick:n[7]||=e=>X(void 0)},{default:i(()=>[m(r(S),{class:`size-3`})]),_:1})])):(y(),_(r(f),{key:1,variant:`ghost`,size:`xs`,class:`text-xs text-muted-foreground/50 hover:text-primary/60`,onClick:Me},{default:i(()=>[m(r(x),{class:`size-3`}),u(` `+t(r(C)(`providers.shared.addOverflow`)),1)]),_:1}))]),d(`div`,ve,[d(`div`,ye,[m(r(N),{class:`size-3 text-blue-500/50`}),d(`span`,be,t(r(C)(`mappings.editor.multimodalFallback`)),1),$.value?(y(),_(r(ie),{key:0,variant:`outline`,class:`text-[9px] px-1.5 py-0 leading-none text-primary/60 border-[1px] border-primary/20`},{default:i(()=>[u(t(r(C)(`mappings.multimodalFallback.configured`)),1)]),_:1})):p(``,!0)]),$.value?(y(),h(`div`,xe,[d(`span`,Se,[m(r(N),{class:`size-2.5`})]),d(`div`,Ce,[m(A,{providers:g.providerGroups,"model-value":{provider_id:g.entry.multimodalFallback.provider_id,model:g.entry.multimodalFallback.backend_model},compact:``,dashed:``,placeholder:r(C)(`mappings.multimodalFallback.selectProviderModel`),"onUpdate:modelValue":n[8]||=e=>Ne(e)},null,8,[`providers`,`model-value`,`placeholder`])]),m(r(f),{variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/30 hover:text-destructive`,onClick:Pe},{default:i(()=>[m(r(S),{class:`size-3`})]),_:1})])):(y(),_(r(f),{key:1,variant:`ghost`,size:`xs`,class:`text-xs text-muted-foreground/50 hover:text-primary/60`,onClick:Z},{default:i(()=>[m(r(x),{class:`size-3`}),u(` `+t(r(C)(`mappings.multimodalFallback.add`)),1)]),_:1})),$.value?(y(),h(`div`,we,[m(r(ne),{class:`size-3.5 text-orange-400 shrink-0 mt-0.5`}),d(`div`,Te,[d(`div`,Ee,t(r(C)(`mappings.multimodalFallback.sessionLockWarning`)),1),d(`div`,De,t(r(C)(`mappings.multimodalFallback.sessionLockReason`)),1),d(`div`,Oe,t(r(C)(`mappings.multimodalFallback.costSuggestion`)),1)])])):p(``,!0)])])):(y(),h(`div`,{key:0,class:`flex items-center gap-2 cursor-pointer select-none min-h-[28px]`,onClick:n[3]||=e=>T(`expand`)},[d(`span`,{class:`font-mono text-xs font-semibold text-foreground min-w-[90px] truncate shrink-0`,title:g.entry.clientModel},t(g.entry.clientModel),9,ae),m(r(M),{class:`size-3 text-muted-foreground/30 shrink-0`}),d(`div`,oe,[g.entry.targets.length>0?(y(),h(`span`,se,[n[9]||=d(`span`,{class:`text-[10px] font-semibold`},`①`,-1),u(` `+t(g.entry.targets[0].backend_model)+` `,1),J(g.entry.targets[0].provider_id)?(y(),h(`span`,P,t(J(g.entry.targets[0].provider_id)),1)):p(``,!0)])):p(``,!0),(y(!0),h(l,null,v(g.entry.targets.slice(1),(e,r)=>(y(),h(l,{key:`fo-`+r},[n[10]||=d(`span`,{class:`text-orange-400/40 text-xs shrink-0`},`|`,-1),d(`span`,F,[d(`span`,I,t(r===0?`②`:r===1?`③`:`${r+2}`),1),u(` `+t(e.backend_model),1)])],64))),128)),Q.value?(y(),h(l,{key:1},[n[11]||=d(`span`,{class:`text-muted-foreground/30 text-xs shrink-0`},`|`,-1),d(`span`,L,` ↓ `+t(g.entry.targets[0].overflow_model),1)],64)):p(``,!0),$.value?(y(),h(l,{key:2},[n[12]||=d(`span`,{class:`text-muted-foreground/30 text-xs shrink-0`},`|`,-1),d(`span`,R,[m(r(N),{class:`size-2.5`}),u(` `+t(g.entry.multimodalFallback.backend_model),1)])],64)):p(``,!0)]),d(`span`,{class:s([`text-[10px] px-1.5 py-px rounded font-medium leading-none shrink-0`,Fe.value])},t(Ie.value),3),d(`div`,{class:`shrink-0 flex items-center`,onClick:n[1]||=o(()=>{},[`stop`])},[m(r(ee),{"model-value":g.entry.active,"onUpdate:modelValue":n[0]||=e=>T(`toggle-active`),class:`scale-75`},null,8,[`model-value`])]),m(r(f),{variant:`ghost`,size:`icon-xs`,class:`shrink-0 text-muted-foreground/20 hover:text-destructive`,onClick:n[2]||=o(e=>T(`remove`),[`stop`])},{default:i(()=>[m(r(S),{class:`size-3`})]),_:1}),d(`span`,z,t(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(n,{emit:o}){let{t:c}=a(),f=n,g=o;function _(e){g(`update:modelValue`,{...f.modelValue,...e})}let v=e(()=>f.modelValue.mode??j.mode),b=e(()=>f.modelValue.max_concurrency??j.max_concurrency),x=e(()=>f.modelValue.queue_timeout_ms??j.queue_timeout_ms),S=e(()=>f.modelValue.max_queue_size??j.max_queue_size);return(e,a)=>(y(),h(`div`,{class:s(n.compact?`space-y-2`:`flex items-end gap-2 flex-wrap`)},[d(`div`,{class:s([n.compact?``:`w-36`,`space-y-1`])},[m(r(C),{class:`text-xs text-muted-foreground`},{default:i(()=>[u(t(r(c)(`providers.concurrency.mode`)),1)]),_:1}),m(r(O),{"model-value":v.value,"onUpdate:modelValue":a[0]||=e=>_({mode:e})},{default:i(()=>[m(r(E),{class:`w-full text-xs data-[size=default]:h-7`},{default:i(()=>[m(r(D))]),_:1}),m(r(w),null,{default:i(()=>[m(r(T),{value:`auto`},{default:i(()=>[u(t(r(c)(`providers.concurrency.autoAdaptive`)),1)]),_:1}),m(r(T),{value:`manual`},{default:i(()=>[u(t(r(c)(`providers.concurrency.manual`)),1)]),_:1}),m(r(T),{value:`none`},{default:i(()=>[u(t(r(c)(`providers.concurrency.none`)),1)]),_:1})]),_:1})]),_:1},8,[`model-value`])],2),v.value===`none`?p(``,!0):(y(),h(l,{key:0},[d(`div`,{class:s([n.compact?``:`w-24`,`space-y-1`])},[m(r(C),{class:`text-xs text-muted-foreground`},{default:i(()=>[u(t(r(c)(`providers.concurrency.maxConcurrency`)),1)]),_:1}),m(r(k),{"model-value":b.value,type:`number`,min:`1`,max:`100`,class:`text-xs md:text-xs md:text-xs h-7`,"onUpdate:modelValue":a[1]||=e=>_({max_concurrency:Number(e)})},null,8,[`model-value`])],2),d(`div`,{class:s([n.compact?``:`w-30`,`space-y-1`])},[m(r(C),{class:`text-xs text-muted-foreground`},{default:i(()=>[u(t(r(c)(`providers.concurrency.queueTimeout`)),1)]),_:1}),m(r(k),{"model-value":x.value,type:`number`,min:`0`,placeholder:r(c)(`providers.shared.queueTimeoutPlaceholder`),class:`font-mono text-xs md:text-xs md:text-xs h-7`,"onUpdate:modelValue":a[2]||=e=>_({queue_timeout_ms:Number(e)})},null,8,[`model-value`,`placeholder`])],2),d(`div`,{class:s([n.compact?``:`w-30`,`space-y-1`])},[m(r(C),{class:`text-xs text-muted-foreground`},{default:i(()=>[u(t(r(c)(`providers.concurrency.maxQueueSize`)),1)]),_:1}),m(r(k),{"model-value":S.value,type:`number`,min:`1`,max:`1000`,class:`text-xs md:text-xs md:text-xs h-7`,"onUpdate:modelValue":a[3]||=e=>_({max_queue_size:Number(e)})},null,8,[`model-value`])],2)],64)),v.value===`auto`&&!n.compact?(y(),h(`div`,J,t(r(c)(`providers.shared.autoHint`)),1)):p(``,!0)],2))}});export{M as i,q as n,N as r,Y as t};
|