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.
Files changed (124) hide show
  1. package/dist/admin/providers.js +4 -6
  2. package/dist/admin/quick-setup.js +0 -2
  3. package/dist/app/register-routes.js +0 -2
  4. package/dist/config/model-context.d.ts +0 -2
  5. package/dist/config/model-context.js +0 -4
  6. package/dist/core/concurrency/semaphore.d.ts +2 -15
  7. package/dist/core/concurrency/semaphore.js +4 -36
  8. package/dist/core/constants.d.ts +0 -1
  9. package/dist/core/constants.js +0 -3
  10. package/dist/core/monitor/request-tracker.d.ts +0 -6
  11. package/dist/core/monitor/request-tracker.js +0 -15
  12. package/dist/core/types.d.ts +1 -1
  13. package/dist/db/providers.d.ts +3 -12
  14. package/dist/db/providers.js +12 -23
  15. package/dist/index.js +0 -2
  16. package/dist/proxy/handler/iteration-setup.js +2 -3
  17. package/dist/proxy/orchestration/orchestrator.d.ts +1 -1
  18. package/dist/proxy/orchestration/orchestrator.js +11 -28
  19. package/dist/proxy/orchestration/resilience.d.ts +1 -3
  20. package/dist/proxy/orchestration/resilience.js +2 -17
  21. package/dist/proxy/orchestration/scope.d.ts +1 -1
  22. package/dist/proxy/orchestration/scope.js +2 -2
  23. package/dist/proxy/transport/http.d.ts +2 -14
  24. package/dist/proxy/transport/http.js +2 -24
  25. package/dist/proxy/transport/stream.d.ts +3 -56
  26. package/dist/proxy/transport/stream.js +44 -97
  27. package/dist/proxy/transport/transport-fn.d.ts +1 -2
  28. package/dist/proxy/transport/transport-fn.js +3 -3
  29. package/frontend-dist/assets/AuthLayout-jELzICkx.js +1 -0
  30. package/frontend-dist/assets/Card-uC_v0CEa.js +1 -0
  31. package/frontend-dist/assets/CardContent-CP3OiCj4.js +1 -0
  32. package/frontend-dist/assets/CardTitle-DGxuW5DZ.js +1 -0
  33. package/frontend-dist/assets/CascadingModelSelect-Dzk7rxIN.js +1 -0
  34. package/frontend-dist/assets/Checkbox-C1aVqGdC.js +1 -0
  35. package/frontend-dist/assets/CollapsibleContent-pBG4UkLo.js +1 -0
  36. package/frontend-dist/assets/CollapsibleTrigger-D5TkgXmz.js +1 -0
  37. package/frontend-dist/assets/ConcurrencyControl-5GweS-rY.js +1 -0
  38. package/frontend-dist/assets/Dashboard-wjd3d3qk.js +3 -0
  39. package/frontend-dist/assets/{Input-DRF4ONbF.js → Input-DMDfXTXB.js} +1 -1
  40. package/frontend-dist/assets/Label-BQXea0mo.js +1 -0
  41. package/frontend-dist/assets/Login-DNpCjxrY.js +1 -0
  42. package/frontend-dist/assets/Logs-BuL2Z0sF.js +1 -0
  43. package/frontend-dist/assets/ModelMappings-DeRhu-2N.js +1 -0
  44. package/frontend-dist/assets/Monitor-V30dnACo.js +1 -0
  45. package/frontend-dist/assets/Providers-BkkQhSTb.js +1 -0
  46. package/frontend-dist/assets/ProxyEnhancement-DjgebwfU.js +1 -0
  47. package/frontend-dist/assets/QuickSetup-BTVjEiU7.js +1 -0
  48. package/frontend-dist/assets/RetryRules-DFBHBG-B.js +1 -0
  49. package/frontend-dist/assets/RouterKeys-DZADvMfh.js +1 -0
  50. package/frontend-dist/assets/{RovingFocusItem-BIoz-v9k.js → RovingFocusItem-5H5eE6G2.js} +1 -1
  51. package/frontend-dist/assets/Schedules-DUubZ2uN.js +1 -0
  52. package/frontend-dist/assets/Separator-BqIs_Dy3.js +1 -0
  53. package/frontend-dist/assets/Settings-CO59WLOZ.js +6 -0
  54. package/frontend-dist/assets/Setup-Br7JZKNp.js +1 -0
  55. package/frontend-dist/assets/Skeleton-gVyjaP-y.js +1 -0
  56. package/frontend-dist/assets/Switch-D1ER0j6H.js +1 -0
  57. package/frontend-dist/assets/TableHeader-MqyrNSsx.js +1 -0
  58. package/frontend-dist/assets/TabsTrigger-DdjWJbUq.js +1 -0
  59. package/frontend-dist/assets/UnifiedRequestDialog-DRthlI6j.js +3 -0
  60. package/frontend-dist/assets/{VisuallyHiddenInput-Dz0c537Q.js → VisuallyHiddenInput-DrNFhnVL.js} +1 -1
  61. package/frontend-dist/assets/arrow-down-BFgGYafs.js +1 -0
  62. package/frontend-dist/assets/badge-Db4OYMEf.js +1 -0
  63. package/frontend-dist/assets/{button-DhbTyZqO.js → button-BBiWml8B.js} +2 -2
  64. package/frontend-dist/assets/chevron-right-DYwStkJr.js +1 -0
  65. package/frontend-dist/assets/dialog-DRYeWncC.js +1 -0
  66. package/frontend-dist/assets/{image-BAKOQH7r.js → image-zYdpUIEA.js} +1 -1
  67. package/frontend-dist/assets/index-DTujoAWx.js +58 -0
  68. package/frontend-dist/assets/index-DyQ39g4W.css +1 -0
  69. package/frontend-dist/assets/model-patches-DIy-rFuq.js +1 -0
  70. package/frontend-dist/assets/{pencil-CQiZ13On.js → pencil-C3-MFg-d.js} +1 -1
  71. package/frontend-dist/assets/plus-xmIDnujf.js +1 -0
  72. package/frontend-dist/assets/quickSetup-CqxQRMCR.js +1 -0
  73. package/frontend-dist/assets/quickSetup-DplqYrvf.js +1 -0
  74. package/frontend-dist/assets/search-BxNrTsG8.js +1 -0
  75. package/frontend-dist/assets/{sparkles-o5rM_OIC.js → sparkles-B5RWZZuf.js} +1 -1
  76. package/frontend-dist/assets/transform-domain-KBixlLXR.js +1 -0
  77. package/frontend-dist/assets/{trash-2-DfV00PQC.js → trash-2-Dn3T5-Z1.js} +1 -1
  78. package/frontend-dist/assets/{useClipboard-BEGcEu2g.js → useClipboard-Bx3CrPal.js} +1 -1
  79. package/frontend-dist/assets/useLogRetention-CdccNhYN.js +1 -0
  80. package/frontend-dist/assets/{useProviderGroups-CpH380OR.js → useProviderGroups-Og5FpCPe.js} +1 -1
  81. package/frontend-dist/index.html +3 -3
  82. package/package.json +1 -1
  83. package/frontend-dist/assets/AuthLayout-CNJ18LiO.js +0 -1
  84. package/frontend-dist/assets/Card-DHdWC9Sr.js +0 -1
  85. package/frontend-dist/assets/CardContent-D3XiSLZZ.js +0 -1
  86. package/frontend-dist/assets/CardTitle-ouEnIzFR.js +0 -1
  87. package/frontend-dist/assets/CascadingModelSelect-uCR_78CB.js +0 -1
  88. package/frontend-dist/assets/Checkbox-D4B5Gy-L.js +0 -1
  89. package/frontend-dist/assets/CollapsibleContent-D5uuYzAy.js +0 -1
  90. package/frontend-dist/assets/CollapsibleTrigger-7lOShWej.js +0 -1
  91. package/frontend-dist/assets/ConcurrencyControl-DSpn8cI_.js +0 -1
  92. package/frontend-dist/assets/Dashboard-DaMz3m0K.js +0 -3
  93. package/frontend-dist/assets/Label-muHpCvRx.js +0 -1
  94. package/frontend-dist/assets/Login-CuQA8bbc.js +0 -1
  95. package/frontend-dist/assets/Logs-CKO3germ.js +0 -1
  96. package/frontend-dist/assets/ModelMappings-De98UugX.js +0 -1
  97. package/frontend-dist/assets/Monitor-BzeTXX7u.js +0 -1
  98. package/frontend-dist/assets/Providers-C7TMCyLZ.js +0 -1
  99. package/frontend-dist/assets/ProxyEnhancement-CaXVxlxk.js +0 -1
  100. package/frontend-dist/assets/QuickSetup-9OsD4eib.js +0 -1
  101. package/frontend-dist/assets/RetryRules-CZtQYpr0.js +0 -1
  102. package/frontend-dist/assets/RouterKeys-WAbHBrzu.js +0 -1
  103. package/frontend-dist/assets/Schedules-DgxMv_wm.js +0 -1
  104. package/frontend-dist/assets/Separator-_gcj_6Dd.js +0 -1
  105. package/frontend-dist/assets/Settings-w33AM_0F.js +0 -6
  106. package/frontend-dist/assets/Setup-D6c5svHh.js +0 -1
  107. package/frontend-dist/assets/Skeleton-wP01Qhzi.js +0 -1
  108. package/frontend-dist/assets/Switch-BnSoCbqK.js +0 -1
  109. package/frontend-dist/assets/TableHeader-oSu5tayf.js +0 -1
  110. package/frontend-dist/assets/TabsTrigger-CzLaOeN9.js +0 -1
  111. package/frontend-dist/assets/UnifiedRequestDialog-lCG2BxB4.js +0 -3
  112. package/frontend-dist/assets/arrow-down-DBlAmBEr.js +0 -1
  113. package/frontend-dist/assets/badge-qilWWy3M.js +0 -1
  114. package/frontend-dist/assets/chevron-right-B1eJ5usR.js +0 -1
  115. package/frontend-dist/assets/dialog-J16vWuXK.js +0 -1
  116. package/frontend-dist/assets/index-BoaXEmuZ.js +0 -58
  117. package/frontend-dist/assets/index-Db9D0WPf.css +0 -1
  118. package/frontend-dist/assets/model-patches-B47btKty.js +0 -1
  119. package/frontend-dist/assets/plus-BVJPX-4z.js +0 -1
  120. package/frontend-dist/assets/quickSetup-jgJgPUcH.js +0 -1
  121. package/frontend-dist/assets/quickSetup-qTjp3Z6J.js +0 -1
  122. package/frontend-dist/assets/search-DzYxIOWW.js +0 -1
  123. package/frontend-dist/assets/transform-domain-DczpyJVT.js +0 -1
  124. 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, opts) {
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, opts) {
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 { Transform } from "stream";
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, type TransportCallOpts } from "./http.js";
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, opts?: StreamCallOpts): Promise<TransportResult>;
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 扫描缓冲(跨 chunk 边界匹配)
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, upstreamRes, upstreamReq) {
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 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中执行,
100
- // cleanup 由调用方在 setImmediate(macrotask)中处理。
91
+ // deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中执行,
92
+ // cleanup 由调用方在 setImmediatemacrotask)中处理。
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(非 deferred)且 headers 已发送时,立即 end reply 避免客户端挂起
111
- // idle_timeout 使用 deferred 模式,由 resetIdleTimer 的 setImmediate 负责 end reply
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
- // cleanup 透传 cause:让上游 destroy(err) emit 'error' 事件链,与 CLAUDE.md
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(cause) {
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 缓冲区,确保 [DONE] / message_stop
153
- // 等末尾事件已被处理。否则 extractor.complete 可能为 false,导致 is_complete=0。
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,确保 inject() 能正确收集响应体
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 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中完成,
174
- // reply.raw.end() 延迟到 setImmediate(macrotask),保证 inject() 返回时日志已写入。
156
+ // deferred 模式:先 resolve 让 handler 链路(日志写入等)在 microtask 中完成,
157
+ // reply.raw.end() 延迟到 setImmediatemacrotask),保证 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 已由其他代码路径(如前一次 retry 的 StreamProxy)发送,
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,避免 Node.js 在 dest 上自动注册 close/finish handler
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
- // 客户端已断开,写已销毁的 socket 会抛出异常,可安全忽略
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,reply.raw.on("error") 无法拦截。
215
+ // EPIPE 从底层 socket 的 WriteWrap.onWriteComplete emitreply.raw.on("error") 无法拦截。
243
216
  // 必须直接在 socket 上注册 error handler 防止冒泡到 process uncaughtException。
244
- // Node.js HTTP server 内置的 socketOnError 只处理第一次 socket error,
245
- // 如果第一次被其他错误消耗,后续 EPIPE 可能无 handler 导致进程崩溃。
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
- // 快速路径:检查大小限制(无需 concat)
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
- // 快速路径:检查当前 chunk 是否包含 \n\n
261
+ // 快速路径:检查当前 chunk 是否包含 \n\n
289
262
  const chunkStr = chunk.toString("utf-8");
290
263
  const hasNewlinePair = chunkStr.includes("\n\n");
291
- // 跨 chunk 边界检测:上一个 chunk 以 \n 结尾 + 当前以 \n 开头
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 阶段:扫描 SSE error event(处理跨 chunk 边界)
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
- // 快速启发式:只在扫描窗口出现 SSE error 标记时才执行正则匹配
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 已发送:必须结束 reply 避免 client hang
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,让 handler 链路(日志写入等)在 microtask 中执行。
358
- // reply.raw.end() 延迟到 setImmediate(macrotask),确保 microtask 先完成。
359
- // light-my-request 监听 reply.raw 的 end 事件判定响应完成,
329
+ // 通过 terminal 的 deferred 模式统一 resolve
330
+ // 先 resolve Promise,让 handler 链路(日志写入等)在 microtask 中执行。
331
+ // reply.raw.end() 延迟到 setImmediatemacrotask),确保 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
- // 延迟结束管道和响应,属于 reply 层面操作,不属于 StreamProxy 状态管理。
336
+ // 延迟结束管道和响应,属于 reply 层面操作,不属于 StreamProxy 状态管理。
364
337
  //
365
- // 当 formatTransform 存在时(如 OpenAI→Anthropic 转换),Transform 链的 flush
366
- // 通过 process.nextTick 异步传播。pipeEntry.end() 后不能同步调用 reply.raw.end(),
367
- // 否则 formatTransform._flush() 中的 ensureTerminated()(发送 message_stop)
368
- // 尚未执行,连接就被关闭,导致客户端收到 "stream ended before message_stop"。
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 事件触发(整个 transform 链 flush 完成后),再关闭 reply
356
+ // 等 passThrough end 事件触发(整个 transform 链 flush 完成后),再关闭 reply
384
357
  this.passThrough.once("end", endReply);
385
- // 安全超时兜底,防止 end 事件未触发导致连接挂起
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, opts) {
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, upstreamRes, upstreamReq);
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, signal?: AbortSignal) => Promise<TransportResult>;
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, signal) => {
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, { signal, connectTimeoutMs: p.streamTimeoutMs });
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, { signal, timeoutMs: p.nonStreamTimeoutMs });
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};