llm-simple-router 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/recommended-retry-rules.json +2 -1
- package/dist/admin/monitor.js +9 -6
- package/dist/config/model-context.js +15 -4
- package/dist/config/recommended-retry-rules.json +2 -1
- package/dist/core/monitor/stream-extractor.js +30 -2
- package/dist/index.js +1 -1
- package/dist/proxy/handler/create-proxy-handler.js +3 -64
- package/dist/proxy/orchestration/resilience.js +1 -1
- package/dist/proxy/transform/request-bridge-responses.js +9 -6
- package/dist/proxy/transform/request-transform.js +12 -8
- package/dist/proxy/transform/thinking-mapper.d.ts +1 -0
- package/dist/proxy/transform/thinking-mapper.js +1 -1
- package/dist/proxy/transform/thinking-resolver.d.ts +19 -0
- package/dist/proxy/transform/thinking-resolver.js +51 -0
- package/frontend-dist/assets/CardContent-D1CMiLP7.js +1 -0
- package/frontend-dist/assets/CardTitle-BqQP3-E2.js +1 -0
- package/frontend-dist/assets/Checkbox-DrDlKj-G.js +1 -0
- package/frontend-dist/assets/CollapsibleContent-uU516NPV.js +1 -0
- package/frontend-dist/assets/CollapsibleTrigger-BlnmToB3.js +1 -0
- package/frontend-dist/assets/Dashboard-DIh5ljvA.js +3 -0
- package/frontend-dist/assets/{Input-DEfnoFS3.js → Input-CdfjEjbl.js} +1 -1
- package/frontend-dist/assets/Label-8BU9m_xR.js +1 -0
- package/frontend-dist/assets/Login-RzXg2ypt.js +1 -0
- package/frontend-dist/assets/Logs-CIpseYy0.js +1 -0
- package/frontend-dist/assets/MappingEntryEditor-BjeYox3a.js +1 -0
- package/frontend-dist/assets/ModelMappings-BcSQmvr-.js +1 -0
- package/frontend-dist/assets/Monitor-6qk9XIoS.js +1 -0
- package/frontend-dist/assets/Providers-BfXjRs4P.js +1 -0
- package/frontend-dist/assets/ProxyEnhancement-BDLb_u6a.js +1 -0
- package/frontend-dist/assets/QuickSetup-B3S4Favg.js +1 -0
- package/frontend-dist/assets/RetryRules-XoxQfjvu.js +1 -0
- package/frontend-dist/assets/RouterKeys-C6IJ-JiQ.js +1 -0
- package/frontend-dist/assets/RovingFocusItem-Brs0DuXq.js +1 -0
- package/frontend-dist/assets/Schedules-Xe8X0SdY.js +1 -0
- package/frontend-dist/assets/Settings-hNwSgyS8.js +6 -0
- package/frontend-dist/assets/Setup-C9Mwsbh-.js +1 -0
- package/frontend-dist/assets/Switch-Ctea7ISh.js +1 -0
- package/frontend-dist/assets/TooltipTrigger-DhF7HDYV.js +1 -0
- package/frontend-dist/assets/TransformRulesForm-CzMZxJ5E.js +1 -0
- package/frontend-dist/assets/UnifiedRequestDialog-Dt9EGhxH.js +3 -0
- package/frontend-dist/assets/VisuallyHiddenInput-DQTfxmA1.js +1 -0
- package/frontend-dist/assets/{button-DZwflOXO.js → button-lvbllMnE.js} +5 -5
- package/frontend-dist/assets/{copy-zQQvOqam.js → copy-CA0xNeW8.js} +1 -1
- package/frontend-dist/assets/dashboard-BpAReE3I.js +1 -0
- package/frontend-dist/assets/dashboard-K_VT51kT.js +1 -0
- package/frontend-dist/assets/dialog-D3UelwTP.js +1 -0
- package/frontend-dist/assets/{index-ClQS69Or.css → index-CogX4xoq.css} +1 -1
- package/frontend-dist/assets/index-NiDVXv3S.js +3 -0
- package/frontend-dist/assets/model-patches-Ca_KuITM.js +1 -0
- package/frontend-dist/assets/quickSetup-9__wUdpr.js +1 -0
- package/frontend-dist/assets/quickSetup-sDxsfeH3.js +1 -0
- package/frontend-dist/assets/{trash-2-CrcHK-G_.js → trash-2-Vyzwv3La.js} +1 -1
- package/frontend-dist/assets/{useClipboard-B4K3eogm.js → useClipboard-Dy0rnj9d.js} +1 -1
- package/frontend-dist/assets/useLogRetention-CaCCaDeG.js +1 -0
- package/frontend-dist/index.html +3 -3
- package/package.json +1 -1
- package/frontend-dist/assets/CardContent-yiYaxAko.js +0 -1
- package/frontend-dist/assets/CardTitle-CzqSlrtn.js +0 -1
- package/frontend-dist/assets/Checkbox-2voapLgE.js +0 -1
- package/frontend-dist/assets/CollapsibleContent-DHkVSWt2.js +0 -1
- package/frontend-dist/assets/CollapsibleTrigger-DbVCeTdD.js +0 -1
- package/frontend-dist/assets/Dashboard-xT1CEwOR.js +0 -3
- package/frontend-dist/assets/Label-CjUuzGNQ.js +0 -1
- package/frontend-dist/assets/Login-CJDEk-tO.js +0 -1
- package/frontend-dist/assets/Logs-CzdPCIYV.js +0 -1
- package/frontend-dist/assets/MappingEntryEditor-GejG6FYv.js +0 -1
- package/frontend-dist/assets/ModelCard-DdQtySPM.js +0 -1
- package/frontend-dist/assets/ModelMappings-DffY7Izx.js +0 -1
- package/frontend-dist/assets/Monitor-y6d6LInm.js +0 -1
- package/frontend-dist/assets/Providers-Cb-CB1yf.js +0 -1
- package/frontend-dist/assets/ProxyEnhancement-CywRxDop.js +0 -1
- package/frontend-dist/assets/QuickSetup-Nj_ysAdc.js +0 -1
- package/frontend-dist/assets/RetryRules-DRdeZUPt.js +0 -1
- package/frontend-dist/assets/RouterKeys-BHOhDgXZ.js +0 -1
- package/frontend-dist/assets/RovingFocusItem-NxZWBEpr.js +0 -1
- package/frontend-dist/assets/Schedules-C4jRCbnI.js +0 -1
- package/frontend-dist/assets/Settings-Cn0qnqMY.js +0 -6
- package/frontend-dist/assets/Setup-BjN6KU0y.js +0 -1
- package/frontend-dist/assets/Switch-bk3eQSZ_.js +0 -1
- package/frontend-dist/assets/TooltipTrigger-DmYucHtv.js +0 -1
- package/frontend-dist/assets/TransformRulesForm-Bo-zFABv.js +0 -1
- package/frontend-dist/assets/UnifiedRequestDialog-5-vBmVMH.js +0 -3
- package/frontend-dist/assets/VisuallyHiddenInput-BflIWQCW.js +0 -1
- package/frontend-dist/assets/dashboard-Cejt1wVQ.js +0 -1
- package/frontend-dist/assets/dashboard-DLTOR0fN.js +0 -1
- package/frontend-dist/assets/dialog-C7v6Gaak.js +0 -1
- package/frontend-dist/assets/index-PMAQyWJb.js +0 -3
- package/frontend-dist/assets/quickSetup-CSpWmAy-.js +0 -1
- package/frontend-dist/assets/quickSetup-D8ruRelW.js +0 -1
- package/frontend-dist/assets/useLogRetention-BNbFXLBO.js +0 -1
|
@@ -8,5 +8,6 @@
|
|
|
8
8
|
{ "name": "ZAI SSE 错误 (HTTP 200, code 500)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"500\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000, "providers": ["智谱"] },
|
|
9
9
|
{ "name": "ZAI SSE 错误 (HTTP 200, code 1234)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"1234\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000, "providers": ["智谱"] },
|
|
10
10
|
{ "name": "ZAI 模型过载 (HTTP 200, code 1305)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"1305\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000, "providers": ["智谱"] },
|
|
11
|
-
{ "name": "KIMI 401 认证错误", "status_code": 401, "body_pattern": ".*authentication_error.*", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 3, "max_delay_ms": 60000, "providers": ["月之暗面"] }
|
|
11
|
+
{ "name": "KIMI 401 认证错误", "status_code": 401, "body_pattern": ".*authentication_error.*", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 3, "max_delay_ms": 60000, "providers": ["月之暗面"] },
|
|
12
|
+
{ "name": "DeepSeek 并发限流 (429)", "status_code": 429, "body_pattern": "Too many requests.*concurrency", "retry_strategy": "exponential", "retry_delay_ms": 2000, "max_retries": 5, "max_delay_ms": 120000, "providers": ["DeepSeek", "OpenCode"] }
|
|
12
13
|
]
|
package/dist/admin/monitor.js
CHANGED
|
@@ -16,15 +16,12 @@ export const adminMonitorRoutes = (app, options, done) => {
|
|
|
16
16
|
app.get("/admin/api/monitor/stream", (request, reply) => {
|
|
17
17
|
// hijack() 让 Fastify 完全放弃响应管理,避免 onSend hook 向 SSE 流注入信封 JSON
|
|
18
18
|
reply.hijack();
|
|
19
|
-
const sseClient = adaptSSEClient(reply.raw);
|
|
20
|
-
tracker.addClient(sseClient);
|
|
21
|
-
// 在 writeHead 之前注册 close 处理器,避免竞态导致 tracker 泄漏
|
|
22
|
-
reply.raw.on("close", () => {
|
|
23
|
-
tracker.removeClient(sseClient);
|
|
24
|
-
});
|
|
25
19
|
// 客户端在 hijack 之前已断连,无需发送响应头
|
|
26
20
|
if (reply.raw.destroyed)
|
|
27
21
|
return;
|
|
22
|
+
// writeHead 必须在 addClient 之前调用,否则 sendInitialSnapshot 的 write()
|
|
23
|
+
// 会触发 Node.js 隐式 header 发送(Content-Type 默认非 text/event-stream),
|
|
24
|
+
// 导致浏览器 EventSource 解析失败、不断重连。
|
|
28
25
|
try {
|
|
29
26
|
reply.raw.writeHead(HTTP_OK, {
|
|
30
27
|
"Content-Type": "text/event-stream",
|
|
@@ -34,7 +31,13 @@ export const adminMonitorRoutes = (app, options, done) => {
|
|
|
34
31
|
}
|
|
35
32
|
catch {
|
|
36
33
|
request.log.debug("client disconnected before writeHead");
|
|
34
|
+
return;
|
|
37
35
|
}
|
|
36
|
+
const sseClient = adaptSSEClient(reply.raw);
|
|
37
|
+
tracker.addClient(sseClient);
|
|
38
|
+
reply.raw.on("close", () => {
|
|
39
|
+
tracker.removeClient(sseClient);
|
|
40
|
+
});
|
|
38
41
|
});
|
|
39
42
|
app.get("/admin/api/monitor/request/:id", async (request, reply) => {
|
|
40
43
|
const { id } = request.params;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
export const MODEL_CONTEXT_WINDOWS = {
|
|
4
5
|
// DeepSeek
|
|
5
6
|
"deepseek-chat": 1000000,
|
|
@@ -143,7 +144,17 @@ let directoryContextWindows = {};
|
|
|
143
144
|
*/
|
|
144
145
|
export function loadModelDirectory(configDir) {
|
|
145
146
|
try {
|
|
146
|
-
|
|
147
|
+
// 优先使用传入的 configDir,否则自动检测:
|
|
148
|
+
// - 生产 (dist/config/model-context.js): 上溯一级到 dist/ → dist/config/ (postbuild 已复制)
|
|
149
|
+
// - 开发 (src/config/model-context.ts): 上溯二级到包根 → config/
|
|
150
|
+
let dir = configDir;
|
|
151
|
+
if (!dir) {
|
|
152
|
+
const fileDir = path.dirname(fileURLToPath(import.meta.url));
|
|
153
|
+
const prodDir = path.resolve(fileDir, "..", "config");
|
|
154
|
+
dir = fs.existsSync(path.join(prodDir, "model-directory.json"))
|
|
155
|
+
? prodDir
|
|
156
|
+
: path.resolve(fileDir, "..", "..", "config");
|
|
157
|
+
}
|
|
147
158
|
const filePath = path.join(dir, "model-directory.json");
|
|
148
159
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
149
160
|
const data = JSON.parse(raw);
|
|
@@ -153,11 +164,11 @@ export function loadModelDirectory(configDir) {
|
|
|
153
164
|
if (data.context_windows && typeof data.context_windows === "object") {
|
|
154
165
|
directoryContextWindows = data.context_windows;
|
|
155
166
|
}
|
|
156
|
-
// eslint-disable-next-line taste/no-silent-catch -- 加载失败不影响启动,使用硬编码白名单兆底。但记录到 stderr 供诊断
|
|
157
167
|
}
|
|
158
168
|
catch (err) {
|
|
159
|
-
|
|
160
|
-
console.
|
|
169
|
+
const msg = err instanceof Error ? err.message : typeof err === 'string' ? err : JSON.stringify(err);
|
|
170
|
+
console.warn(`loadModelDirectory: failed to load (${msg}), using hardcoded fallback`);
|
|
171
|
+
console.debug(err);
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
/** 查询模型 capabilities:显式配置 > model-directory.json > 硬编码白名单 > ["text"] */
|
|
@@ -8,5 +8,6 @@
|
|
|
8
8
|
{ "name": "ZAI SSE 错误 (HTTP 200, code 500)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"500\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000, "providers": ["智谱"] },
|
|
9
9
|
{ "name": "ZAI SSE 错误 (HTTP 200, code 1234)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"1234\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000, "providers": ["智谱"] },
|
|
10
10
|
{ "name": "ZAI 模型过载 (HTTP 200, code 1305)", "status_code": 200, "body_pattern": "\"error\".*\"code\"\\s*:\\s*\"1305\"", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 10, "max_delay_ms": 60000, "providers": ["智谱"] },
|
|
11
|
-
{ "name": "KIMI 401 认证错误", "status_code": 401, "body_pattern": ".*authentication_error.*", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 3, "max_delay_ms": 60000, "providers": ["月之暗面"] }
|
|
11
|
+
{ "name": "KIMI 401 认证错误", "status_code": 401, "body_pattern": ".*authentication_error.*", "retry_strategy": "exponential", "retry_delay_ms": 5000, "max_retries": 3, "max_delay_ms": 60000, "providers": ["月之暗面"] },
|
|
12
|
+
{ "name": "DeepSeek 并发限流 (429)", "status_code": 429, "body_pattern": "Too many requests.*concurrency", "retry_strategy": "exponential", "retry_delay_ms": 2000, "max_retries": 5, "max_delay_ms": 120000, "providers": ["DeepSeek", "OpenCode"] }
|
|
12
13
|
]
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
const SSE_DATA_PREFIX = "data: ";
|
|
2
|
+
// OpenAI stream block index 分配:reasoning/text/tools 使用不同区间避免混合
|
|
3
|
+
const OPENAI_BLOCK_REASONING = 0;
|
|
4
|
+
const OPENAI_BLOCK_TEXT = 1;
|
|
5
|
+
const OPENAI_BLOCK_TOOLS = 2;
|
|
2
6
|
export function extractStreamText(line, apiType) {
|
|
3
7
|
const empty = { text: "", block: null };
|
|
4
8
|
if (!line.startsWith(SSE_DATA_PREFIX))
|
|
@@ -16,8 +20,32 @@ export function extractStreamText(line, apiType) {
|
|
|
16
20
|
if (apiType === "openai") {
|
|
17
21
|
const choices = obj.choices;
|
|
18
22
|
const delta = choices?.[0]?.delta;
|
|
19
|
-
const text = delta?.content ??
|
|
20
|
-
|
|
23
|
+
const text = delta?.content ?? "";
|
|
24
|
+
const reasoning = delta?.reasoning_content ?? "";
|
|
25
|
+
// OpenAI 不像 Anthropic 那样为不同 content type 分配独立 index。
|
|
26
|
+
// 策略:reasoning → OPENAI_BLOCK_REASONING, text → OPENAI_BLOCK_TEXT,
|
|
27
|
+
// tool_calls[N] → OPENAI_BLOCK_TOOLS + N。
|
|
28
|
+
// 这样不同类型的内容不会混在同一个 block 中。
|
|
29
|
+
if (reasoning) {
|
|
30
|
+
return { text: reasoning, block: { index: OPENAI_BLOCK_REASONING, type: "thinking", content: reasoning } };
|
|
31
|
+
}
|
|
32
|
+
if (text) {
|
|
33
|
+
return { text, block: { index: OPENAI_BLOCK_TEXT, type: "text", content: text } };
|
|
34
|
+
}
|
|
35
|
+
const toolCalls = delta?.tool_calls;
|
|
36
|
+
if (toolCalls) {
|
|
37
|
+
const tc = toolCalls[0];
|
|
38
|
+
if (tc) {
|
|
39
|
+
const tcIndex = tc.index ?? 0;
|
|
40
|
+
const fn = tc.function;
|
|
41
|
+
const args = fn?.arguments ?? "";
|
|
42
|
+
const name = fn?.name ?? "";
|
|
43
|
+
if (args || name) {
|
|
44
|
+
return { text: "", block: { index: OPENAI_BLOCK_TOOLS + tcIndex, type: "tool_use", content: args, name: name || undefined } };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return empty;
|
|
21
49
|
}
|
|
22
50
|
if (apiType === "openai-responses") {
|
|
23
51
|
// Responses SSE uses named events, but line format is "data: {json}" (same as Anthropic)
|
package/dist/index.js
CHANGED
|
@@ -322,7 +322,7 @@ export async function buildApp(options) {
|
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
324
|
else {
|
|
325
|
-
app.log.
|
|
325
|
+
app.log.debug(`Frontend dist not found at ${frontendDist}, skipping static serving`);
|
|
326
326
|
}
|
|
327
327
|
app.get("/health", async () => {
|
|
328
328
|
return { status: "ok" };
|
|
@@ -19,12 +19,7 @@ import { SERVICE_KEYS } from "../../core/container.js";
|
|
|
19
19
|
import { createPipelineContext } from "../pipeline/context.js";
|
|
20
20
|
import { proxyPipeline } from "../pipeline/pipeline.js";
|
|
21
21
|
import { executeFailoverLoop } from "./failover-loop.js";
|
|
22
|
-
import { loadEnhancementConfig } from "../routing/enhancement-config.js";
|
|
23
|
-
import { ToolLoopGuard } from "../../core/loop-prevention/index.js";
|
|
24
|
-
import { HTTP_UNPROCESSABLE_ENTITY } from "../../core/constants.js";
|
|
25
22
|
import { PipelineAbort } from "../pipeline/types.js";
|
|
26
|
-
import { applyToolRoundLimit } from "../patch/tool-round-limiter.js";
|
|
27
|
-
import { extractLastToolUse } from "./proxy-handler-utils.js";
|
|
28
23
|
// ---------- Models handler (shared across openai/anthropic) ----------
|
|
29
24
|
const ANTHROPIC_DEFAULT_PAGE_SIZE = 20;
|
|
30
25
|
const ANTHROPIC_MAX_PAGE_SIZE = 1000;
|
|
@@ -98,60 +93,6 @@ function handleModelsRequest(db) {
|
|
|
98
93
|
});
|
|
99
94
|
};
|
|
100
95
|
}
|
|
101
|
-
// ---------- Enhancement preprocessing (extracted from old handleProxyRequest) ----------
|
|
102
|
-
const TIER2_LOOP_THRESHOLD = 2;
|
|
103
|
-
function applyEnhancementPreprocess(request, reply, ctx, db, container) {
|
|
104
|
-
const enhancementConfig = loadEnhancementConfig(db);
|
|
105
|
-
const apiType = ctx.apiType;
|
|
106
|
-
const sessionId = ctx.metadata.get("session_id");
|
|
107
|
-
// 工具轮数限制
|
|
108
|
-
if (enhancementConfig.tool_round_limit_enabled) {
|
|
109
|
-
const roundResult = applyToolRoundLimit(ctx.body, apiType);
|
|
110
|
-
if (roundResult.injected) {
|
|
111
|
-
ctx.body = roundResult.body;
|
|
112
|
-
ctx.snapshot.add({ stage: "tool_round_limit", action: "inject_warning", rounds: roundResult.rounds });
|
|
113
|
-
request.log.info({ sessionId, rounds: roundResult.rounds }, "Tool round limit reached, injecting warning prompt");
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// 工具循环检测
|
|
117
|
-
if (!enhancementConfig.tool_call_loop_enabled || !sessionId)
|
|
118
|
-
return;
|
|
119
|
-
const sessionTracker = container.resolve(SERVICE_KEYS.sessionTracker);
|
|
120
|
-
if (!sessionTracker)
|
|
121
|
-
return;
|
|
122
|
-
const routerKeyId = request.routerKey?.id ?? null;
|
|
123
|
-
const sessionKey = routerKeyId ? `${routerKeyId}:${sessionId}` : sessionId;
|
|
124
|
-
const lastToolUse = extractLastToolUse(ctx.body);
|
|
125
|
-
if (!lastToolUse)
|
|
126
|
-
return;
|
|
127
|
-
const toolGuard = new ToolLoopGuard(sessionTracker, {
|
|
128
|
-
enabled: true,
|
|
129
|
-
minConsecutiveCount: 3,
|
|
130
|
-
detectorConfig: { n: 6, windowSize: 500, repeatThreshold: 5 },
|
|
131
|
-
});
|
|
132
|
-
const checkResult = toolGuard.check(sessionKey, lastToolUse);
|
|
133
|
-
if (!checkResult.detected)
|
|
134
|
-
return;
|
|
135
|
-
const loopCount = sessionTracker.getLoopCount(sessionKey);
|
|
136
|
-
if (loopCount === 1) {
|
|
137
|
-
ctx.body = toolGuard.injectLoopBreakPrompt(ctx.body, apiType, lastToolUse.toolName);
|
|
138
|
-
ctx.snapshot.add({ stage: "tool_guard", action: "inject_break_prompt", tool: lastToolUse.toolName });
|
|
139
|
-
request.log.warn({ sessionId, toolName: lastToolUse.toolName, loopCount }, "Tool call loop detected, injecting break prompt");
|
|
140
|
-
}
|
|
141
|
-
else if (loopCount === TIER2_LOOP_THRESHOLD) {
|
|
142
|
-
throw new PipelineAbort(HTTP_UNPROCESSABLE_ENTITY, {
|
|
143
|
-
error: {
|
|
144
|
-
type: "tool_call_loop_detected",
|
|
145
|
-
message: `检测到工具调用循环(连续重复调用 "${lastToolUse.toolName}")。请求已中断。`,
|
|
146
|
-
suggestion: "请回顾对话历史,停止重复调用工具,直接告知用户当前的进展和遇到的问题。",
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
request.log.warn({ sessionId, toolName: lastToolUse.toolName, loopCount }, "Tool call loop detected, hard disconnecting");
|
|
152
|
-
throw new PipelineAbort(HTTP_CLIENT_CLOSED, { _disconnect: true });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
96
|
// ---------- Factory ----------
|
|
156
97
|
export function createProxyHandler(config) {
|
|
157
98
|
const { apiType, paths } = config;
|
|
@@ -214,13 +155,10 @@ export function createProxyHandler(config) {
|
|
|
214
155
|
const ctx = createPipelineContext(request, reply, apiType);
|
|
215
156
|
// 注入 DB 到 metadata(hooks 需要访问 settings/写入数据)
|
|
216
157
|
ctx.metadata.set("db", db);
|
|
158
|
+
ctx.metadata.set("container", container);
|
|
217
159
|
// 执行 pre_route 阶段 hooks(client-detection 在此阶段设置 client_type / session_id)
|
|
218
|
-
await proxyPipeline.emit("pre_route", ctx).catch(err => {
|
|
219
|
-
request.log.error({ err }, "pre_route hook failed");
|
|
220
|
-
});
|
|
221
|
-
// 增强预处理(工具轮数限制 + 工具循环检测)
|
|
222
160
|
try {
|
|
223
|
-
|
|
161
|
+
await proxyPipeline.emit("pre_route", ctx);
|
|
224
162
|
}
|
|
225
163
|
catch (e) {
|
|
226
164
|
if (e instanceof PipelineAbort) {
|
|
@@ -230,6 +168,7 @@ export function createProxyHandler(config) {
|
|
|
230
168
|
}
|
|
231
169
|
return reply.code(e.statusCode).send(e.body);
|
|
232
170
|
}
|
|
171
|
+
request.log.error({ err: e }, "pre_route hook failed");
|
|
233
172
|
throw e;
|
|
234
173
|
}
|
|
235
174
|
const deps = {
|
|
@@ -172,7 +172,7 @@ export class ResilienceLayer {
|
|
|
172
172
|
transportResult = await fn(currentTarget);
|
|
173
173
|
}
|
|
174
174
|
catch (err) {
|
|
175
|
-
const errMsg = err instanceof Error ? err.message :
|
|
175
|
+
const errMsg = err instanceof Error ? err.message : JSON.stringify(err);
|
|
176
176
|
transportResult = { kind: "throw", error: err instanceof Error ? err : new Error(errMsg) };
|
|
177
177
|
}
|
|
178
178
|
lastResult = transportResult;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* cannot represent `previous_response_id`, built-in tools, or structured
|
|
8
8
|
* reasoning items.
|
|
9
9
|
*/
|
|
10
|
+
import { resolveThinkingParams } from "./thinking-resolver.js";
|
|
10
11
|
// ---------- Responses → Chat Completions ----------
|
|
11
12
|
/**
|
|
12
13
|
* Convert an OpenAI Responses API request body to an OpenAI Chat Completions
|
|
@@ -71,9 +72,10 @@ export function responsesToChatRequest(body) {
|
|
|
71
72
|
result.tool_choice = tc;
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
// reasoning
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// Thinking params: reasoning > thinking (deepseek compat) > reasoning_effort
|
|
76
|
+
const thinkingResult = resolveThinkingParams(body, req.reasoning);
|
|
77
|
+
if (thinkingResult.reasoning) {
|
|
78
|
+
result.reasoning = thinkingResult.reasoning;
|
|
77
79
|
}
|
|
78
80
|
// text.format → response_format (json_schema 结构差异需转换)
|
|
79
81
|
if (req.text?.format != null) {
|
|
@@ -331,9 +333,10 @@ export function chatToResponsesRequest(body) {
|
|
|
331
333
|
if (req.tool_choice != null) {
|
|
332
334
|
result.tool_choice = req.tool_choice;
|
|
333
335
|
}
|
|
334
|
-
// reasoning
|
|
335
|
-
|
|
336
|
-
|
|
336
|
+
// Thinking params: reasoning > thinking (deepseek compat) > reasoning_effort
|
|
337
|
+
const thinkingResult = resolveThinkingParams(body, req.reasoning);
|
|
338
|
+
if (thinkingResult.reasoning) {
|
|
339
|
+
result.reasoning = thinkingResult.reasoning;
|
|
337
340
|
}
|
|
338
341
|
// response_format → text.format (json_schema 结构差异需转换)
|
|
339
342
|
if (req.response_format != null) {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { convertMessagesOA2Ant, convertMessagesAnt2OA } from "./message-mapper.js";
|
|
2
2
|
import { convertToolsOA2Ant, convertToolsAnt2OA, mapToolChoiceOA2Ant, mapToolChoiceAnt2OA } from "./tool-mapper.js";
|
|
3
|
-
import {
|
|
3
|
+
import { mapThinkingToReasoning } from "./thinking-mapper.js";
|
|
4
|
+
import { resolveThinkingParams } from "./thinking-resolver.js";
|
|
4
5
|
import { stripProviderMeta } from "./provider-meta.js";
|
|
5
6
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
6
7
|
const OA_KNOWN_FIELDS = new Set([
|
|
7
8
|
"model", "messages", "max_completion_tokens", "max_tokens",
|
|
8
9
|
"stop", "temperature", "top_p", "stream", "tools", "tool_choice",
|
|
9
|
-
"parallel_tool_calls", "reasoning", "
|
|
10
|
-
"response_format", "provider_meta",
|
|
10
|
+
"parallel_tool_calls", "reasoning", "reasoning_effort", "thinking",
|
|
11
|
+
"user", "n", "stream_options", "response_format", "provider_meta",
|
|
12
|
+
"store",
|
|
11
13
|
]);
|
|
12
14
|
const ANT_KNOWN_FIELDS = new Set([
|
|
13
15
|
"model", "system", "messages", "max_tokens",
|
|
@@ -63,11 +65,13 @@ export function openaiToAnthropicRequest(body) {
|
|
|
63
65
|
result.tool_choice = { type: "auto", disable_parallel_tool_use: true };
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// Thinking params: reasoning > thinking (deepseek compat) > reasoning_effort
|
|
69
|
+
const thinkingResult = resolveThinkingParams(cleanedBody, req.reasoning);
|
|
70
|
+
if (thinkingResult.thinking) {
|
|
71
|
+
result.thinking = thinkingResult.thinking;
|
|
72
|
+
const budget = thinkingResult.thinking.budget_tokens;
|
|
73
|
+
if (budget != null && budget > 0 && result.max_tokens < budget) {
|
|
74
|
+
result.max_tokens = budget;
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
if (req.user) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一 thinking 参数解析。
|
|
3
|
+
* 优先级:reasoning (显式对象) > thinking (DeepSeek compat) > reasoning_effort (OpenAI standard)
|
|
4
|
+
*
|
|
5
|
+
* 所有转换函数都应使用此类函数确保行为一致,避免重复实现导致的不一致。
|
|
6
|
+
*/
|
|
7
|
+
export interface ThinkingResult {
|
|
8
|
+
/** Responses API 的 reasoning 参数 */
|
|
9
|
+
reasoning?: Record<string, unknown>;
|
|
10
|
+
/** Anthropic API 的 thinking 参数 */
|
|
11
|
+
thinking?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 从请求 body 中解析 thinking 参数。
|
|
15
|
+
* @param body 原始请求 body
|
|
16
|
+
* @param reqReasoning 已经从 body 解析出的 req.reasoning 字段
|
|
17
|
+
* @returns 解析结果,包含 reasoning (Responses API) 和 thinking (Anthropic API) 两种格式
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveThinkingParams(body: Record<string, unknown>, reqReasoning: Record<string, unknown> | undefined): ThinkingResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mapReasoningToThinking, EFFORT_BUDGET } from "./thinking-mapper.js";
|
|
2
|
+
/**
|
|
3
|
+
* 从请求 body 中解析 thinking 参数。
|
|
4
|
+
* @param body 原始请求 body
|
|
5
|
+
* @param reqReasoning 已经从 body 解析出的 req.reasoning 字段
|
|
6
|
+
* @returns 解析结果,包含 reasoning (Responses API) 和 thinking (Anthropic API) 两种格式
|
|
7
|
+
*/
|
|
8
|
+
export function resolveThinkingParams(body, reqReasoning) {
|
|
9
|
+
// 1. 显式 reasoning 对象(最高优先级)
|
|
10
|
+
if (reqReasoning != null) {
|
|
11
|
+
return {
|
|
12
|
+
reasoning: reqReasoning,
|
|
13
|
+
thinking: mapReasoningToThinking(reqReasoning),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
// 2. DeepSeek compat: thinking: {type: "enabled", budget_tokens?}
|
|
17
|
+
const thinkingParam = body.thinking;
|
|
18
|
+
if (thinkingParam?.type === "disabled") {
|
|
19
|
+
// 显式禁用 thinking,不执行任何转换
|
|
20
|
+
return { reasoning: undefined, thinking: undefined };
|
|
21
|
+
}
|
|
22
|
+
if (thinkingParam && thinkingParam.type === "enabled") {
|
|
23
|
+
const budget = thinkingParam.budget_tokens;
|
|
24
|
+
if (budget != null) {
|
|
25
|
+
// 有 budget: 转换为 reasoning 格式和 thinking 格式
|
|
26
|
+
return {
|
|
27
|
+
reasoning: { max_tokens: budget },
|
|
28
|
+
thinking: { type: "enabled", budget_tokens: budget },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// 无 budget: 透传 enabled 状态
|
|
32
|
+
return {
|
|
33
|
+
reasoning: undefined,
|
|
34
|
+
thinking: { type: "enabled" },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// 3. OpenAI standard: reasoning_effort: "high" | "medium" | "low"
|
|
38
|
+
const effort = body.reasoning_effort;
|
|
39
|
+
if (effort) {
|
|
40
|
+
return {
|
|
41
|
+
reasoning: { effort },
|
|
42
|
+
thinking: { type: "enabled", budget_tokens: effortToBudget(effort) },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { reasoning: undefined, thinking: undefined };
|
|
46
|
+
}
|
|
47
|
+
const DEFAULT_EFFORT_BUDGET = 8192;
|
|
48
|
+
/** Map reasoning_effort level to budget_tokens, 复用 thinking-mapper 的映射表 */
|
|
49
|
+
function effortToBudget(effort) {
|
|
50
|
+
return EFFORT_BUDGET[effort] ?? DEFAULT_EFFORT_BUDGET;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Kt as t,Wt as n,ht as r,r as i,rt as a,vt as o}from"./button-lvbllMnE.js";var s=[`data-size`],c=a({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(a){let c=a;return(l,u)=>(r(),e(`div`,{"data-slot":`card`,"data-size":a.size,class:t(n(i)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[o(l.$slots,`default`)],10,s))}}),l=a({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(r(),e(`div`,{"data-slot":`card-content`,class:t(n(i)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[o(a.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{$ as e,Kt as t,Wt as n,ht as r,r as i,rt as a,vt as o}from"./button-lvbllMnE.js";var s=a({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(r(),e(`div`,{"data-slot":`card-header`,class:t(n(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(a.$slots,`default`)],2))}}),c=a({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(a){let s=a;return(a,c)=>(r(),e(`div`,{"data-slot":`card-title`,class:t(n(i)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[o(a.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{G as e,K as t,Ot as n,Q as r,Wt as i,Y as a,Z as o,at as s,bt as c,ht as l,i as u,m as d,nt as f,o as p,qt as m,r as h,rt as g,ut as _,vt as v,x as y}from"./button-lvbllMnE.js";import{t as b}from"./VisuallyHiddenInput-DQTfxmA1.js";import{t as x}from"./RovingFocusItem-Brs0DuXq.js";import{J as S,K as C,R as w,U as T,V as E,X as D,ft as O}from"./index-NiDVXv3S.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>D(e,t)):D(e,t)}var[A,j]=S(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=S(`CheckboxRoot`),I=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(s,{emit:f}){let m=s,h=f,{forwardRef:g,currentElement:y}=p(),S=A(null),w=d(m,`modelValue`,h,{defaultValue:m.defaultValue??m.falseValue,passive:m.modelValue===void 0}),E=a(()=>S?.disabled.value||m.disabled),O=a(()=>D(w.value,m.trueValue)),j=a(()=>C(S?.modelValue.value)?w.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,m.value));function P(){if(C(S?.modelValue.value))w.value===`indeterminate`?w.value=m.trueValue:w.value=O.value?m.falseValue:m.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,m.value)){let t=e.findIndex(e=>D(e,m.value));e.splice(t,1)}else e.push(m.value);S.modelValue.value=e}}let I=T(y),L=a(()=>m.id&&y.value?document.querySelector(`[for="${m.id}"]`)?.innerText:void 0);return F({disabled:E,state:j}),(a,s)=>(l(),o(c(i(S)?.rovingFocus.value?i(x):i(u)),_(a.$attrs,{id:a.id,ref:i(g),role:`checkbox`,"as-child":a.asChild,as:a.as,type:a.as===`button`?`button`:void 0,"aria-checked":i(M)(j.value)?`mixed`:j.value,"aria-required":a.required,"aria-label":a.$attrs[`aria-label`]||L.value,"data-state":i(N)(j.value),"data-disabled":E.value?``:void 0,disabled:E.value,focusable:i(S)?.rovingFocus.value?!E.value:void 0,onKeydown:e(t(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:n(()=>[v(a.$slots,`default`,{modelValue:i(w),state:j.value}),i(I)&&a.name&&!i(S)?(l(),o(i(b),{key:0,type:`checkbox`,checked:!!j.value,name:a.name,value:a.value,disabled:E.value,required:a.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):r(`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:t}=p(),r=P();return(e,a)=>(l(),o(i(w),{present:e.forceMount||i(M)(i(r).state.value)||i(r).state.value===!0},{default:n(()=>[f(i(u),_({ref:i(t),"data-state":i(N)(i(r).state.value),"data-disabled":i(r).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:n(()=>[v(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:t}){let r=e,a=t,c=E(y(r,`class`),a);return(e,t)=>(l(),o(i(I),_({"data-slot":`checkbox`},i(c),{class:i(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`,r.class)}),{default:n(t=>[f(i(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:n(()=>[v(e.$slots,`default`,m(s(t)),()=>[f(i(O))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{It as e,Ot as t,Q as n,Tt as r,Vt as i,Wt as a,Y as o,Z as s,at as c,d as l,dt as u,ht as d,i as f,m as p,nt as m,o as h,pt as g,qt as _,rt as v,ut as y,vt as b}from"./button-lvbllMnE.js";import{B as x,J as S,R as C,V as w}from"./index-NiDVXv3S.js";var[T,E]=S(`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:n,emit:r}){let o=e,c=p(o,`open`,r,{defaultValue:o.defaultOpen,passive:o.open===void 0}),{disabled:l,unmountOnHide:u}=i(o);return E({contentId:``,disabled:l,open:c,unmountOnHide:u,onOpenToggle:()=>{l.value||(c.value=!c.value)}}),n({open:c}),h(),(e,n)=>(d(),s(a(f),{as:e.as,"as-child":o.asChild,"data-state":a(c)?`open`:`closed`,"data-disabled":a(l)?``:void 0},{default:t(()=>[b(e.$slots,`default`,{open:a(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(i,{emit:c}){let p=i,_=c,v=T();v.contentId||=x(void 0,`reka-collapsible-content`);let S=e(),{forwardRef:w,currentElement:E}=h(),D=e(0),O=e(0),k=o(()=>v.open.value),A=e(k.value),j=e();r(()=>[k.value,S.value?.present],async()=>{await u();let e=E.value;if(!e)return;j.value=j.value||{transitionDuration:e.style.transitionDuration,animationName:e.style.animationName},e.style.transitionDuration=`0s`,e.style.animationName=`none`;let t=e.getBoundingClientRect();O.value=t.height,D.value=t.width,A.value||(e.style.transitionDuration=j.value.transitionDuration,e.style.animationName=j.value.animationName)},{immediate:!0});let M=o(()=>A.value&&v.open.value);return g(()=>{requestAnimationFrame(()=>{A.value=!1})}),l(E,`beforematch`,e=>{requestAnimationFrame(()=>{v.onOpenToggle(),_(`contentFound`)})}),(e,r)=>(d(),s(a(C),{ref_key:`presentRef`,ref:S,present:e.forceMount||a(v).open.value,"force-mount":!0},{default:t(({present:r})=>[m(a(f),y(e.$attrs,{id:a(v).contentId,ref:a(w),"as-child":p.asChild,as:e.as,hidden:r?void 0:a(v).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:a(v).open.value?`open`:`closed`,"data-disabled":a(v).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:t(()=>[!a(v).unmountOnHide.value||r?b(e.$slots,`default`,{key:0}):n(`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:n}){let r=w(e,n);return(e,n)=>(d(),s(a(D),y({"data-slot":`collapsible`},a(r)),{default:t(t=>[b(e.$slots,`default`,_(c(t)))]),_:3},16))}}),A=v({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let n=e;return(e,r)=>(d(),s(a(O),y({"data-slot":`collapsible-content`},n),{default:t(()=>[b(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Ot as e,Wt as t,Z as n,ht as r,i,o as a,rt as o,ut as s,vt as c}from"./button-lvbllMnE.js";import{r as l}from"./CollapsibleContent-uU516NPV.js";var u=o({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(o){let s=o;a();let u=l();return(a,o)=>(r(),n(t(i),{type:a.as===`button`?`button`:void 0,as:a.as,"as-child":s.asChild,"aria-controls":t(u).contentId,"aria-expanded":t(u).open.value,"data-state":t(u).open.value?`open`:`closed`,"data-disabled":t(u).disabled?.value?``:void 0,disabled:t(u).disabled?.value,onClick:t(u).onOpenToggle},{default:e(()=>[c(a.$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(i){let a=i;return(i,o)=>(r(),n(t(u),s({"data-slot":`collapsible-trigger`},a),{default:e(()=>[c(i.$slots,`default`)]),_:3},16))}});export{d as t};
|