llm-simple-router 0.9.30 → 0.9.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/upgrade.js +23 -10
- package/dist/config/model-context.js +11 -1
- package/dist/proxy/patch/deepseek/index.d.ts +1 -13
- package/dist/proxy/patch/deepseek/index.js +3 -22
- package/dist/proxy/patch/deepseek/patch-thinking.d.ts +44 -0
- package/dist/proxy/patch/deepseek/patch-thinking.js +153 -0
- package/dist/proxy/patch/index.js +8 -10
- package/dist/proxy/transform/message-mapper.js +1 -1
- package/frontend-dist/assets/{CardContent-CSzTFJFo.js → CardContent-D3x1v1V9.js} +1 -1
- package/frontend-dist/assets/{CardTitle-CuFtTb2C.js → CardTitle-DIgY93n2.js} +1 -1
- package/frontend-dist/assets/{Checkbox-Csq97GuG.js → Checkbox-BBj7YMSz.js} +1 -1
- package/frontend-dist/assets/{CollapsibleContent-Bkw5Nkei.js → CollapsibleContent-Cy8Zj7F7.js} +1 -1
- package/frontend-dist/assets/{CollapsibleTrigger-Ipe-hYf6.js → CollapsibleTrigger-Vdxg8CPQ.js} +1 -1
- package/frontend-dist/assets/{Dashboard-ChgBE0D0.js → Dashboard-BvUBgYaB.js} +1 -1
- package/frontend-dist/assets/{Input-CqOgrx6Z.js → Input-yOkodP1n.js} +1 -1
- package/frontend-dist/assets/{Label--rffSxX9.js → Label-XqpkYcVi.js} +1 -1
- package/frontend-dist/assets/{Login-DyJS3pSs.js → Login-BGagonui.js} +1 -1
- package/frontend-dist/assets/{Logs-DqQrxaOX.js → Logs-bfeOLJad.js} +1 -1
- package/frontend-dist/assets/{MappingEntryEditor-B0kwYMex.js → MappingEntryEditor-CMTbBK_d.js} +1 -1
- package/frontend-dist/assets/ModelCard-C81l2a_5.js +1 -0
- package/frontend-dist/assets/{ModelMappings-BM55q4HM.js → ModelMappings-CvDWSsDP.js} +1 -1
- package/frontend-dist/assets/{Monitor-CX8fZrcF.js → Monitor-BGH6sjdr.js} +1 -1
- package/frontend-dist/assets/{Providers-Dl3fFnpI.js → Providers-UK6LN6DF.js} +1 -1
- package/frontend-dist/assets/{ProxyEnhancement-BwKTjUS7.js → ProxyEnhancement-MX0PRADd.js} +1 -1
- package/frontend-dist/assets/QuickSetup-CajStS5C.js +1 -0
- package/frontend-dist/assets/{RetryRules-0phZYYTs.js → RetryRules-gpvpTTSc.js} +1 -1
- package/frontend-dist/assets/{RouterKeys-Cket7Ux-.js → RouterKeys-Dc8D6cEV.js} +1 -1
- package/frontend-dist/assets/{RovingFocusItem-DS-DstIs.js → RovingFocusItem-B3BLvyzD.js} +1 -1
- package/frontend-dist/assets/{Schedules-C9vhSJcd.js → Schedules-Criu9-NO.js} +1 -1
- package/frontend-dist/assets/{Settings-Dt30ZGlc.js → Settings-BiYdwrHf.js} +1 -1
- package/frontend-dist/assets/{Setup-BcmdpuSd.js → Setup-BVqlQCjs.js} +1 -1
- package/frontend-dist/assets/{Switch-B_AxabaZ.js → Switch-B7QaMMlY.js} +1 -1
- package/frontend-dist/assets/{TooltipTrigger-CnHEbo1F.js → TooltipTrigger-Bz3pu02g.js} +1 -1
- package/frontend-dist/assets/{TransformRulesForm-C58xEpM0.js → TransformRulesForm-DXB1ChZ2.js} +1 -1
- package/frontend-dist/assets/{UnifiedRequestDialog-BDBKI_rc.js → UnifiedRequestDialog-BbGcsFXP.js} +1 -1
- package/frontend-dist/assets/{VisuallyHiddenInput-DfrItQHC.js → VisuallyHiddenInput-MTmHNjqA.js} +1 -1
- package/frontend-dist/assets/{button-BYlLW16a.js → button-lv9v_6nd.js} +2 -2
- package/frontend-dist/assets/{copy-CEiO0ojz.js → copy-Dwbg7SpV.js} +1 -1
- package/frontend-dist/assets/{dialog-CPh0o197.js → dialog-ZNKLyaHg.js} +1 -1
- package/frontend-dist/assets/{index-D8gxFmha.js → index-CDEwS862.js} +2 -2
- package/frontend-dist/assets/quickSetup-CCxaqY3U.js +1 -0
- package/frontend-dist/assets/quickSetup-DgDENHE4.js +1 -0
- package/frontend-dist/assets/sidebar-3c8D7l60.js +1 -0
- package/frontend-dist/assets/sidebar-vj4kQ6t1.js +1 -0
- package/frontend-dist/assets/{trash-2-DiNZDqd9.js → trash-2-UQ53WECI.js} +1 -1
- package/frontend-dist/assets/{useClipboard-Dlgqqssg.js → useClipboard-CmrKRWL_.js} +1 -1
- package/frontend-dist/assets/{useLogRetention-DQFhIYfJ.js → useLogRetention-C59RXKSz.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/proxy/patch/deepseek/patch-cache-control.d.ts +0 -6
- package/dist/proxy/patch/deepseek/patch-cache-control.js +0 -30
- package/dist/proxy/patch/deepseek/patch-non-deepseek-tools.d.ts +0 -20
- package/dist/proxy/patch/deepseek/patch-non-deepseek-tools.js +0 -81
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.d.ts +0 -10
- package/dist/proxy/patch/deepseek/patch-thinking-blocks.js +0 -57
- package/dist/proxy/patch/deepseek/patch-thinking-param.d.ts +0 -6
- package/dist/proxy/patch/deepseek/patch-thinking-param.js +0 -32
- package/frontend-dist/assets/ModelCard-CLZ_dn_n.js +0 -1
- package/frontend-dist/assets/QuickSetup-0guGcXHC.js +0 -1
- package/frontend-dist/assets/quickSetup-cCofuCNs.js +0 -1
- package/frontend-dist/assets/quickSetup-xHkKkA6i.js +0 -1
- package/frontend-dist/assets/sidebar-BLkzG956.js +0 -1
- package/frontend-dist/assets/sidebar-Bw1Xt0-q.js +0 -1
package/dist/admin/upgrade.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getConfigSyncSource, setConfigSyncSource } from '../db/settings.js';
|
|
2
|
-
import { detectDeployment, hasProcessManager, getRestartMethod } from '../upgrade/deployment.js';
|
|
2
|
+
import { detectDeployment, hasProcessManager, resolveRestartBinPath, getRestartMethod } from '../upgrade/deployment.js';
|
|
3
3
|
import { createUpgradeChecker, fetchJson } from '../upgrade/checker.js';
|
|
4
4
|
import { reloadConfig } from '../config/recommended.js';
|
|
5
|
-
import { execSync } from 'node:child_process';
|
|
5
|
+
import { execSync, spawn } from 'node:child_process';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { HTTP_BAD_REQUEST, HTTP_INTERNAL_ERROR } from '../core/constants.js';
|
|
@@ -11,8 +11,22 @@ const GITHUB_CONFIG_BASE = 'https://raw.githubusercontent.com/zhushanwen321/llm-
|
|
|
11
11
|
const GITEE_CONFIG_BASE = 'https://gitee.com/zzzzswszzzz/llm-simple-router/raw/main/router/config';
|
|
12
12
|
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // eslint-disable-line no-magic-numbers
|
|
13
13
|
const JSON_INDENT = 2;
|
|
14
|
-
const RESTART_FORCE_EXIT_MS =
|
|
14
|
+
const RESTART_FORCE_EXIT_MS = 5_000;
|
|
15
15
|
const RESTART_RESPONSE_FLUSH_MS = 300;
|
|
16
|
+
function spawnDetached(req) {
|
|
17
|
+
const binPath = resolveRestartBinPath();
|
|
18
|
+
const args = process.argv.slice(2); // eslint-disable-line no-magic-numbers
|
|
19
|
+
req.log.info({ binPath, args }, 'Spawning new process');
|
|
20
|
+
const child = spawn(binPath, args, {
|
|
21
|
+
detached: true,
|
|
22
|
+
stdio: 'ignore',
|
|
23
|
+
env: { ...process.env },
|
|
24
|
+
});
|
|
25
|
+
child.on('error', (err) => {
|
|
26
|
+
req.log.error({ err, binPath }, 'Failed to spawn new process');
|
|
27
|
+
});
|
|
28
|
+
child.unref();
|
|
29
|
+
}
|
|
16
30
|
// 模块级单例:checker、configDir 和定时器
|
|
17
31
|
let checker = null;
|
|
18
32
|
let configDir = '';
|
|
@@ -86,13 +100,6 @@ export const adminUpgradeRoutes = (app, options, done) => {
|
|
|
86
100
|
app.post('/admin/api/upgrade/restart', async (req, reply) => {
|
|
87
101
|
const managed = hasProcessManager();
|
|
88
102
|
const method = getRestartMethod();
|
|
89
|
-
if (!managed) {
|
|
90
|
-
// 无进程管理器(npx / 手动 node)时无法安全自动重启:
|
|
91
|
-
// 1. spawn 路径不可靠(npx 不注册全局 bin)
|
|
92
|
-
// 2. 新旧进程端口竞态(EADDRINUSE)
|
|
93
|
-
// 3. 原始启动参数无法复现
|
|
94
|
-
return reply.code(HTTP_BAD_REQUEST).send(apiError(API_CODE.BAD_REQUEST, 'No process manager detected (PM2/systemd/Docker). Please restart manually.'));
|
|
95
|
-
}
|
|
96
103
|
// 先回复客户端,再执行重启(否则客户端收不到响应)
|
|
97
104
|
reply.send({ ok: true, method });
|
|
98
105
|
// 给响应发送窗口
|
|
@@ -102,12 +109,18 @@ export const adminUpgradeRoutes = (app, options, done) => {
|
|
|
102
109
|
// 强制退出兜底:即使 closeFn 卡住(如活跃代理 SSE 流),也能确保进程退出。
|
|
103
110
|
const forceExitTimer = setTimeout(() => {
|
|
104
111
|
req.log.warn('Graceful shutdown timed out during restart, forcing exit');
|
|
112
|
+
// 超时强制退出前仍尝试 spawn(无进程管理器时)
|
|
113
|
+
if (!managed)
|
|
114
|
+
spawnDetached(req);
|
|
105
115
|
process.exit(0);
|
|
106
116
|
}, RESTART_FORCE_EXIT_MS);
|
|
107
117
|
forceExitTimer.unref();
|
|
108
118
|
// 尝试优雅关闭(closeFn 内部有 2s 优雅等待 + closeAllConnections 兜底)
|
|
109
119
|
await options.closeFn();
|
|
110
120
|
clearTimeout(forceExitTimer);
|
|
121
|
+
// 无进程管理器时,先 closeFn 释放端口,再 spawn 新进程,避免 EADDRINUSE 竞态
|
|
122
|
+
if (!managed)
|
|
123
|
+
spawnDetached(req);
|
|
111
124
|
req.log.info('Exiting current process');
|
|
112
125
|
process.exit(0);
|
|
113
126
|
}
|
|
@@ -102,9 +102,19 @@ export function parseModels(raw) {
|
|
|
102
102
|
const obj = item;
|
|
103
103
|
if (!obj || !obj.name)
|
|
104
104
|
return null;
|
|
105
|
+
/** 旧 patch ID 到新 patch ID 的运行时迁移映射 */
|
|
106
|
+
const PATCH_ID_MIGRATION = {
|
|
107
|
+
thinking_param: "thinking_consistency",
|
|
108
|
+
thinking_blocks: "thinking_consistency",
|
|
109
|
+
non_ds_tools: "thinking_consistency",
|
|
110
|
+
cache_control: "thinking_consistency",
|
|
111
|
+
};
|
|
112
|
+
const rawPatches = (obj.patches ?? []).map(normalizePatchName);
|
|
113
|
+
const migrated = rawPatches.map(p => PATCH_ID_MIGRATION[p] ?? p);
|
|
114
|
+
const patches = [...new Set(migrated)];
|
|
105
115
|
const result = {
|
|
106
116
|
name: obj.name,
|
|
107
|
-
patches
|
|
117
|
+
patches,
|
|
108
118
|
};
|
|
109
119
|
if (obj.stream_timeout_ms != null)
|
|
110
120
|
result.stream_timeout_ms = obj.stream_timeout_ms;
|
|
@@ -2,18 +2,6 @@
|
|
|
2
2
|
* 按序执行所有 DeepSeek 特定补丁。
|
|
3
3
|
*
|
|
4
4
|
* Patch 在格式转换之后执行,body 已经是 provider 的 api_type 格式。
|
|
5
|
-
*
|
|
6
|
-
* 因此按 apiType 分发不同的 patch 流程。
|
|
7
|
-
*
|
|
8
|
-
* Anthropic 格式执行顺序:
|
|
9
|
-
* 1. patchThinkingParam — 注入 thinking 参数
|
|
10
|
-
* 2. stripCacheControl — 剥离 cache_control
|
|
11
|
-
* 3. patchMissingThinkingBlocks — 补 thinking block
|
|
12
|
-
* 4. patchOrphanToolResults — 清理孤儿 tool_result
|
|
13
|
-
*
|
|
14
|
-
* OpenAI 格式执行顺序(参考 docs/deepseek-patch-investigation.md §5.5):
|
|
15
|
-
* 1. patchThinkingParam — 检测历史 reasoning_content,注入 thinking 参数
|
|
16
|
-
* 2. patchNonDeepSeekToolMessages — 将非 DeepSeek 生成的 tool_calls 降级为 text
|
|
17
|
-
* 3. patchOrphanToolResultsOA — 处理孤儿 tool 消息
|
|
5
|
+
* thinking-consistency 是统一的 thinking 一致性处理,内部按 apiType 自动分发。
|
|
18
6
|
*/
|
|
19
7
|
export declare function applyDeepSeekPatches(body: Record<string, unknown>, apiType: "openai" | "openai-responses" | "anthropic"): void;
|
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { stripCacheControl } from "./patch-cache-control.js";
|
|
3
|
-
import { patchMissingThinkingBlocks } from "./patch-thinking-blocks.js";
|
|
4
|
-
import { patchNonDeepSeekToolMessages } from "./patch-non-deepseek-tools.js";
|
|
1
|
+
import { patchThinkingConsistency } from "./patch-thinking.js";
|
|
5
2
|
import { patchOrphanToolResults, patchOrphanToolResultsOA } from "./patch-orphan-tool-results.js";
|
|
6
3
|
/**
|
|
7
4
|
* 按序执行所有 DeepSeek 特定补丁。
|
|
8
5
|
*
|
|
9
6
|
* Patch 在格式转换之后执行,body 已经是 provider 的 api_type 格式。
|
|
10
|
-
*
|
|
11
|
-
* 因此按 apiType 分发不同的 patch 流程。
|
|
12
|
-
*
|
|
13
|
-
* Anthropic 格式执行顺序:
|
|
14
|
-
* 1. patchThinkingParam — 注入 thinking 参数
|
|
15
|
-
* 2. stripCacheControl — 剥离 cache_control
|
|
16
|
-
* 3. patchMissingThinkingBlocks — 补 thinking block
|
|
17
|
-
* 4. patchOrphanToolResults — 清理孤儿 tool_result
|
|
18
|
-
*
|
|
19
|
-
* OpenAI 格式执行顺序(参考 docs/deepseek-patch-investigation.md §5.5):
|
|
20
|
-
* 1. patchThinkingParam — 检测历史 reasoning_content,注入 thinking 参数
|
|
21
|
-
* 2. patchNonDeepSeekToolMessages — 将非 DeepSeek 生成的 tool_calls 降级为 text
|
|
22
|
-
* 3. patchOrphanToolResultsOA — 处理孤儿 tool 消息
|
|
7
|
+
* thinking-consistency 是统一的 thinking 一致性处理,内部按 apiType 自动分发。
|
|
23
8
|
*/
|
|
24
9
|
export function applyDeepSeekPatches(body, apiType) {
|
|
10
|
+
patchThinkingConsistency(body, apiType);
|
|
25
11
|
if (apiType === "anthropic") {
|
|
26
|
-
patchThinkingParam(body, apiType);
|
|
27
|
-
stripCacheControl(body);
|
|
28
|
-
patchMissingThinkingBlocks(body);
|
|
29
12
|
patchOrphanToolResults(body);
|
|
30
13
|
}
|
|
31
14
|
else {
|
|
32
|
-
patchThinkingParam(body, apiType);
|
|
33
|
-
patchNonDeepSeekToolMessages(body);
|
|
34
15
|
patchOrphanToolResultsOA(body);
|
|
35
16
|
}
|
|
36
17
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的 DeepSeek thinking 一致性处理。
|
|
3
|
+
*
|
|
4
|
+
* 解决的问题:DeepSeek thinking 模式激活后,要求历史 assistant 消息携带 thinking 信息。
|
|
5
|
+
* 跨模型切换(如 GLM → DeepSeek)或 DeepSeek 自身某些轮次未返回 thinking 时,
|
|
6
|
+
* 历史中会出现"有 tool_calls 但无 thinking"的 assistant 消息。
|
|
7
|
+
*
|
|
8
|
+
* 策略(借鉴 pi coding-agent):补空值,不降级消息。
|
|
9
|
+
* - OpenAI 格式:补 reasoning_content = ""
|
|
10
|
+
* - Anthropic 格式:补 thinking block(含 signature)
|
|
11
|
+
*
|
|
12
|
+
* 参考:pi-mono/packages/ai/src/providers/openai-completions.ts
|
|
13
|
+
* requiresReasoningContentOnAssistantMessages 配置
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 注入 thinking 参数。
|
|
17
|
+
* DeepSeek 开启 thinking 后,后续请求必须显式传 thinking 参数。
|
|
18
|
+
* 客户端可能在后续轮次省略此参数,检测历史自动补上。
|
|
19
|
+
*/
|
|
20
|
+
declare function injectThinkingParam(body: Record<string, unknown>, apiType: "openai" | "openai-responses" | "anthropic"): void;
|
|
21
|
+
/**
|
|
22
|
+
* Anthropic 格式:补空 thinking block。
|
|
23
|
+
* DeepSeek thinking 模式下含 tool_use 的 assistant 消息必须携带 thinking 块。
|
|
24
|
+
*/
|
|
25
|
+
declare function patchMissingThinkingBlocks(body: Record<string, unknown>): void;
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI 格式:给缺 reasoning_content 的 assistant 消息补空字符串。
|
|
28
|
+
*
|
|
29
|
+
* 借鉴 pi coding-agent 的 requiresReasoningContentOnAssistantMessages 策略:
|
|
30
|
+
* 不判断"谁生成的",不降级 tool_calls,只补空值。
|
|
31
|
+
* 补空字符串足以通过 DeepSeek 校验,且不会导致模型忽略 tool_calls(
|
|
32
|
+
* 那是 Anthropic 端点补空 thinking block 的问题,OpenAI 端点无此副作用)。
|
|
33
|
+
*/
|
|
34
|
+
declare function patchMissingReasoningContent(body: Record<string, unknown>): void;
|
|
35
|
+
/**
|
|
36
|
+
* 统一的 thinking 一致性入口。
|
|
37
|
+
*/
|
|
38
|
+
export declare function patchThinkingConsistency(body: Record<string, unknown>, apiType: "openai" | "openai-responses" | "anthropic"): void;
|
|
39
|
+
export declare const _internals: {
|
|
40
|
+
injectThinkingParam: typeof injectThinkingParam;
|
|
41
|
+
patchMissingThinkingBlocks: typeof patchMissingThinkingBlocks;
|
|
42
|
+
patchMissingReasoningContent: typeof patchMissingReasoningContent;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的 DeepSeek thinking 一致性处理。
|
|
3
|
+
*
|
|
4
|
+
* 解决的问题:DeepSeek thinking 模式激活后,要求历史 assistant 消息携带 thinking 信息。
|
|
5
|
+
* 跨模型切换(如 GLM → DeepSeek)或 DeepSeek 自身某些轮次未返回 thinking 时,
|
|
6
|
+
* 历史中会出现"有 tool_calls 但无 thinking"的 assistant 消息。
|
|
7
|
+
*
|
|
8
|
+
* 策略(借鉴 pi coding-agent):补空值,不降级消息。
|
|
9
|
+
* - OpenAI 格式:补 reasoning_content = ""
|
|
10
|
+
* - Anthropic 格式:补 thinking block(含 signature)
|
|
11
|
+
*
|
|
12
|
+
* 参考:pi-mono/packages/ai/src/providers/openai-completions.ts
|
|
13
|
+
* requiresReasoningContentOnAssistantMessages 配置
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 注入 thinking 参数。
|
|
17
|
+
* DeepSeek 开启 thinking 后,后续请求必须显式传 thinking 参数。
|
|
18
|
+
* 客户端可能在后续轮次省略此参数,检测历史自动补上。
|
|
19
|
+
*/
|
|
20
|
+
function injectThinkingParam(body, apiType) {
|
|
21
|
+
if (body.thinking)
|
|
22
|
+
return;
|
|
23
|
+
const messages = body.messages;
|
|
24
|
+
if (!messages)
|
|
25
|
+
return;
|
|
26
|
+
const hasThinking = messages.some(msg => {
|
|
27
|
+
if (msg.role !== "assistant")
|
|
28
|
+
return false;
|
|
29
|
+
if (apiType === "openai") {
|
|
30
|
+
return msg.reasoning_content !== undefined;
|
|
31
|
+
}
|
|
32
|
+
return Array.isArray(msg.content) &&
|
|
33
|
+
msg.content
|
|
34
|
+
.some(b => b?.type === "thinking");
|
|
35
|
+
});
|
|
36
|
+
if (!hasThinking)
|
|
37
|
+
return;
|
|
38
|
+
if (apiType === "openai") {
|
|
39
|
+
body.thinking = { type: "enabled" };
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
body.thinking = { type: "enabled", budget_tokens: 10000 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* DeepSeek Anthropic 端点不支持 cache_control,剥离以避免报错。
|
|
47
|
+
*/
|
|
48
|
+
function stripCacheControl(body) {
|
|
49
|
+
if (Array.isArray(body.system)) {
|
|
50
|
+
for (const block of body.system) {
|
|
51
|
+
delete block.cache_control;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const messages = body.messages;
|
|
55
|
+
if (!messages)
|
|
56
|
+
return;
|
|
57
|
+
for (const msg of messages) {
|
|
58
|
+
if (Array.isArray(msg.content)) {
|
|
59
|
+
for (const block of msg.content) {
|
|
60
|
+
delete block.cache_control;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(body.tools)) {
|
|
65
|
+
for (const tool of body.tools) {
|
|
66
|
+
delete tool.cache_control;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Anthropic 格式:补空 thinking block。
|
|
72
|
+
* DeepSeek thinking 模式下含 tool_use 的 assistant 消息必须携带 thinking 块。
|
|
73
|
+
*/
|
|
74
|
+
function patchMissingThinkingBlocks(body) {
|
|
75
|
+
if (!body.messages)
|
|
76
|
+
return;
|
|
77
|
+
const messages = body.messages;
|
|
78
|
+
const thinkingActive = !!body.thinking || messages.some((msg) => msg.role === "assistant" && Array.isArray(msg.content)
|
|
79
|
+
&& msg.content.some((b) => b && typeof b === "object" && b.type === "thinking"));
|
|
80
|
+
if (!thinkingActive)
|
|
81
|
+
return;
|
|
82
|
+
const needsSignature = detectSignatureUsage(messages);
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
85
|
+
continue;
|
|
86
|
+
const blocks = msg.content;
|
|
87
|
+
const thinkingIdx = blocks.findIndex((b) => b && typeof b === "object" && b.type === "thinking");
|
|
88
|
+
if (thinkingIdx === -1) {
|
|
89
|
+
const emptyThinking = { type: "thinking", thinking: "" };
|
|
90
|
+
if (needsSignature)
|
|
91
|
+
emptyThinking.signature = "";
|
|
92
|
+
blocks.unshift(emptyThinking);
|
|
93
|
+
}
|
|
94
|
+
else if (thinkingIdx > 0) {
|
|
95
|
+
const [thinkingBlock] = blocks.splice(thinkingIdx, 1);
|
|
96
|
+
blocks.unshift(thinkingBlock);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function detectSignatureUsage(messages) {
|
|
101
|
+
for (const msg of messages) {
|
|
102
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
103
|
+
continue;
|
|
104
|
+
for (const b of msg.content) {
|
|
105
|
+
if (b && typeof b === "object" && b.type === "thinking") {
|
|
106
|
+
return "signature" in b;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* OpenAI 格式:给缺 reasoning_content 的 assistant 消息补空字符串。
|
|
114
|
+
*
|
|
115
|
+
* 借鉴 pi coding-agent 的 requiresReasoningContentOnAssistantMessages 策略:
|
|
116
|
+
* 不判断"谁生成的",不降级 tool_calls,只补空值。
|
|
117
|
+
* 补空字符串足以通过 DeepSeek 校验,且不会导致模型忽略 tool_calls(
|
|
118
|
+
* 那是 Anthropic 端点补空 thinking block 的问题,OpenAI 端点无此副作用)。
|
|
119
|
+
*/
|
|
120
|
+
function patchMissingReasoningContent(body) {
|
|
121
|
+
if (!body.thinking && !body.reasoning)
|
|
122
|
+
return;
|
|
123
|
+
const messages = body.messages;
|
|
124
|
+
if (!messages)
|
|
125
|
+
return;
|
|
126
|
+
for (const msg of messages) {
|
|
127
|
+
if (msg.role === "assistant"
|
|
128
|
+
&& msg.tool_calls
|
|
129
|
+
&& msg.tool_calls.length > 0
|
|
130
|
+
&& msg.reasoning_content === undefined) {
|
|
131
|
+
msg.reasoning_content = "";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 统一的 thinking 一致性入口。
|
|
137
|
+
*/
|
|
138
|
+
export function patchThinkingConsistency(body, apiType) {
|
|
139
|
+
injectThinkingParam(body, apiType);
|
|
140
|
+
if (apiType === "anthropic") {
|
|
141
|
+
stripCacheControl(body);
|
|
142
|
+
patchMissingThinkingBlocks(body);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
patchMissingReasoningContent(body);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// 导出内部函数供测试使用
|
|
149
|
+
export const _internals = {
|
|
150
|
+
injectThinkingParam,
|
|
151
|
+
patchMissingThinkingBlocks,
|
|
152
|
+
patchMissingReasoningContent,
|
|
153
|
+
};
|
|
@@ -28,16 +28,14 @@ export function applyProviderPatches(body, provider) {
|
|
|
28
28
|
patchDeveloperRole(ensureCloned());
|
|
29
29
|
patches.push("developer_role");
|
|
30
30
|
}
|
|
31
|
-
// DeepSeek
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (dsOpenAIPatches.some(p => hasPatch(modelPatches, p)) && provider.api_type === "openai") {
|
|
40
|
-
applyDeepSeekPatches(ensureCloned(), "openai");
|
|
31
|
+
// DeepSeek 补丁:新旧 patch ID 都能触发,内部按 apiType 自动分发
|
|
32
|
+
const dsPatches = [
|
|
33
|
+
"thinking-consistency", "thinking-param", "thinking-blocks",
|
|
34
|
+
"non-ds-tools", "cache-control",
|
|
35
|
+
"orphan-tool-results", "orphan-tool-results-oa",
|
|
36
|
+
];
|
|
37
|
+
if (dsPatches.some(p => hasPatch(modelPatches, p))) {
|
|
38
|
+
applyDeepSeekPatches(ensureCloned(), provider.api_type);
|
|
41
39
|
patches.push("deepseek");
|
|
42
40
|
}
|
|
43
41
|
return { body: patched ?? body, meta: { types: patches } };
|
|
@@ -191,7 +191,7 @@ export function convertMessagesAnt2OA(system, messages) {
|
|
|
191
191
|
const toolBlocks = content.filter(b => b.type === "tool_use");
|
|
192
192
|
const oaiMsg = { role: "assistant" };
|
|
193
193
|
// thinking → reasoning_content(保留 DeepSeek 原生思考信息,
|
|
194
|
-
//
|
|
194
|
+
// 便于 patchThinkingConsistency 判断 thinking 模式是否激活)
|
|
195
195
|
const thinkingBlocks = content.filter(b => b.type === "thinking");
|
|
196
196
|
if (thinkingBlocks.length > 0) {
|
|
197
197
|
oaiMsg.reasoning_content = thinkingBlocks.map(b => b.thinking ?? "").join("");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-
|
|
1
|
+
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-lv9v_6nd.js";var s=[`data-size`],c=e({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]},size:{default:`default`}},setup(e){let c=e;return(l,u)=>(r(),n(`div`,{"data-slot":`card`,"data-size":e.size,class:t(o(a)(`ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col`,c.class))},[i(l.$slots,`default`)],10,s))}}),l=e({__name:`CardContent`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-content`,class:t(o(a)(`px-4 group-data-[size=sm]/card:px-3`,s.class))},[i(e.$slots,`default`)],2))}});export{c as n,l as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-
|
|
1
|
+
import{$ as e,Vt as t,X as n,dt as r,mt as i,r as a,zt as o}from"./button-lv9v_6nd.js";var s=e({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-header`,class:t(o(a)(`gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]`,s.class))},[i(e.$slots,`default`)],2))}}),c=e({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(e){let s=e;return(e,c)=>(r(),n(`div`,{"data-slot":`card-title`,class:t(o(a)(`text-base leading-snug font-medium group-data-[size=sm]/card:text-sm cn-font-heading`,s.class))},[i(e.$slots,`default`)],2))}});export{s as n,c as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,H as t,Ht as n,J as r,K as i,Q as a,U as o,Y as s,dt as c,gt as l,i as u,m as d,mt as f,o as p,ot as m,r as h,tt as g,wt as _,x as v,zt as y}from"./button-
|
|
1
|
+
import{$ as e,H as t,Ht as n,J as r,K as i,Q as a,U as o,Y as s,dt as c,gt as l,i as u,m as d,mt as f,o as p,ot as m,r as h,tt as g,wt as _,x as v,zt as y}from"./button-lv9v_6nd.js";import{t as b}from"./VisuallyHiddenInput-MTmHNjqA.js";import{t as x}from"./RovingFocusItem-B3BLvyzD.js";import{B as S,G as C,H as w,L as T,Y as E,nt as D,q as O}from"./index-CDEwS862.js";function k(e,t){return C(e)?!1:Array.isArray(e)?e.some(e=>E(e,t)):E(e,t)}var[A,j]=O(`CheckboxGroupRoot`);function M(e){return e===`indeterminate`}function N(e){return M(e)?`indeterminate`:e?`checked`:`unchecked`}var[P,F]=O(`CheckboxRoot`),I=e({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(e,{emit:n}){let a=e,h=n,{forwardRef:g,currentElement:v}=p(),S=A(null),T=d(a,`modelValue`,h,{defaultValue:a.defaultValue??a.falseValue,passive:a.modelValue===void 0}),D=i(()=>S?.disabled.value||a.disabled),O=i(()=>E(T.value,a.trueValue)),j=i(()=>C(S?.modelValue.value)?T.value===`indeterminate`?`indeterminate`:O.value:k(S.modelValue.value,a.value));function P(){if(C(S?.modelValue.value))T.value===`indeterminate`?T.value=a.trueValue:T.value=O.value?a.falseValue:a.trueValue;else{let e=[...S.modelValue.value||[]];if(k(e,a.value)){let t=e.findIndex(e=>E(e,a.value));e.splice(t,1)}else e.push(a.value);S.modelValue.value=e}}let I=w(v),L=i(()=>a.id&&v.value?document.querySelector(`[for="${a.id}"]`)?.innerText:void 0);return F({disabled:D,state:j}),(e,n)=>(c(),r(l(y(S)?.rovingFocus.value?y(x):y(u)),m(e.$attrs,{id:e.id,ref:y(g),role:`checkbox`,"as-child":e.asChild,as:e.as,type:e.as===`button`?`button`:void 0,"aria-checked":y(M)(j.value)?`mixed`:j.value,"aria-required":e.required,"aria-label":e.$attrs[`aria-label`]||L.value,"data-state":y(N)(j.value),"data-disabled":D.value?``:void 0,disabled:D.value,focusable:y(S)?.rovingFocus.value?!D.value:void 0,onKeydown:t(o(()=>{},[`prevent`]),[`enter`]),onClick:P}),{default:_(()=>[f(e.$slots,`default`,{modelValue:y(T),state:j.value}),y(I)&&e.name&&!y(S)?(c(),r(y(b),{key:0,type:`checkbox`,checked:!!j.value,name:e.name,value:e.value,disabled:D.value,required:e.required},null,8,[`checked`,`name`,`value`,`disabled`,`required`])):s(`v-if`,!0)]),_:3},16,[`id`,`as-child`,`as`,`type`,`aria-checked`,`aria-required`,`aria-label`,`data-state`,`data-disabled`,`disabled`,`focusable`,`onKeydown`]))}}),L=e({__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(),n=P();return(e,i)=>(c(),r(y(T),{present:e.forceMount||y(M)(y(n).state.value)||y(n).state.value===!0},{default:_(()=>[a(y(u),m({ref:y(t),"data-state":y(N)(y(n).state.value),"data-disabled":y(n).disabled.value?``:void 0,style:{pointerEvents:`none`},"as-child":e.asChild,as:e.as},e.$attrs),{default:_(()=>[f(e.$slots,`default`)]),_:3},16,[`data-state`,`data-disabled`,`as-child`,`as`])]),_:3},8,[`present`]))}}),R=e({__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 i=e,o=t,s=S(v(i,`class`),o);return(e,t)=>(c(),r(y(I),m({"data-slot":`checkbox`},y(s),{class:y(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`,i.class)}),{default:_(t=>[a(y(L),{"data-slot":`checkbox-indicator`,class:`[&>svg]:size-3.5 grid place-content-center text-current transition-none`},{default:_(()=>[f(e.$slots,`default`,n(g(t)),()=>[a(y(D))])]),_:2},1024)]),_:3},16,[`class`]))}});export{R as t};
|
package/frontend-dist/assets/{CollapsibleContent-Bkw5Nkei.js → CollapsibleContent-Cy8Zj7F7.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,Ht as t,J as n,K as r,Lt as i,Mt as a,Q as o,Y as s,d as c,dt as l,i as u,lt as d,m as f,mt as p,o as m,ot as h,st as g,tt as _,wt as v,xt as y,zt as b}from"./button-
|
|
1
|
+
import{$ as e,Ht as t,J as n,K as r,Lt as i,Mt as a,Q as o,Y as s,d as c,dt as l,i as u,lt as d,m as f,mt as p,o as m,ot as h,st as g,tt as _,wt as v,xt as y,zt as b}from"./button-lv9v_6nd.js";import{B as x,L as S,q as C,z as w}from"./index-CDEwS862.js";var[T,E]=C(`CollapsibleRoot`),D=e({__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 a=e,o=f(a,`open`,r,{defaultValue:a.defaultOpen,passive:a.open===void 0}),{disabled:s,unmountOnHide:c}=i(a);return E({contentId:``,disabled:s,open:o,unmountOnHide:c,onOpenToggle:()=>{s.value||(o.value=!o.value)}}),t({open:o}),m(),(e,t)=>(l(),n(b(u),{as:e.as,"as-child":a.asChild,"data-state":b(o)?`open`:`closed`,"data-disabled":b(s)?``:void 0},{default:v(()=>[p(e.$slots,`default`,{open:b(o)})]),_:3},8,[`as`,`as-child`,`data-state`,`data-disabled`]))}}),O=e({inheritAttrs:!1,__name:`CollapsibleContent`,props:{forceMount:{type:Boolean,required:!1},asChild:{type:Boolean,required:!1},as:{type:null,required:!1}},emits:[`contentFound`],setup(e,{emit:t}){let i=e,f=t,_=T();_.contentId||=w(void 0,`reka-collapsible-content`);let x=a(),{forwardRef:C,currentElement:E}=m(),D=a(0),O=a(0),k=r(()=>_.open.value),A=a(k.value),j=a();y(()=>[k.value,x.value?.present],async()=>{await g();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=r(()=>A.value&&_.open.value);return d(()=>{requestAnimationFrame(()=>{A.value=!1})}),c(E,`beforematch`,e=>{requestAnimationFrame(()=>{_.onOpenToggle(),f(`contentFound`)})}),(e,t)=>(l(),n(b(S),{ref_key:`presentRef`,ref:x,present:e.forceMount||b(_).open.value,"force-mount":!0},{default:v(({present:t})=>[o(b(u),h(e.$attrs,{id:b(_).contentId,ref:b(C),"as-child":i.asChild,as:e.as,hidden:t?void 0:b(_).unmountOnHide.value?``:`until-found`,"data-state":M.value?void 0:b(_).open.value?`open`:`closed`,"data-disabled":b(_).disabled?.value?``:void 0,style:{"--reka-collapsible-content-height":`${O.value}px`,"--reka-collapsible-content-width":`${D.value}px`}}),{default:v(()=>[!b(_).unmountOnHide.value||t?p(e.$slots,`default`,{key:0}):s(`v-if`,!0)]),_:2},1040,[`id`,`as-child`,`as`,`hidden`,`data-state`,`data-disabled`,`style`])]),_:3},8,[`present`]))}}),k=e({__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:r}){let i=x(e,r);return(e,r)=>(l(),n(b(D),h({"data-slot":`collapsible`},b(i)),{default:v(n=>[p(e.$slots,`default`,t(_(n)))]),_:3},16))}}),A=e({__name:`CollapsibleContent`,props:{forceMount:{type:Boolean},asChild:{type:Boolean},as:{}},setup(e){let t=e;return(e,r)=>(l(),n(b(O),h({"data-slot":`collapsible-content`},t),{default:v(()=>[p(e.$slots,`default`)]),_:3},16))}});export{k as n,T as r,A as t};
|
package/frontend-dist/assets/{CollapsibleTrigger-Ipe-hYf6.js → CollapsibleTrigger-Vdxg8CPQ.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,J as t,dt as n,i as r,mt as i,o as a,ot as o,wt as s,zt as c}from"./button-
|
|
1
|
+
import{$ as e,J as t,dt as n,i as r,mt as i,o as a,ot as o,wt as s,zt as c}from"./button-lv9v_6nd.js";import{r as l}from"./CollapsibleContent-Cy8Zj7F7.js";var u=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean,required:!1},as:{type:null,required:!1,default:`button`}},setup(e){let o=e;a();let u=l();return(e,a)=>(n(),t(c(r),{type:e.as===`button`?`button`:void 0,as:e.as,"as-child":o.asChild,"aria-controls":c(u).contentId,"aria-expanded":c(u).open.value,"data-state":c(u).open.value?`open`:`closed`,"data-disabled":c(u).disabled?.value?``:void 0,disabled:c(u).disabled?.value,onClick:c(u).onOpenToggle},{default:s(()=>[i(e.$slots,`default`)]),_:3},8,[`type`,`as`,`as-child`,`aria-controls`,`aria-expanded`,`data-state`,`data-disabled`,`disabled`,`onClick`]))}}),d=e({__name:`CollapsibleTrigger`,props:{asChild:{type:Boolean},as:{}},setup(e){let r=e;return(e,a)=>(n(),t(c(u),o({"data-slot":`collapsible-trigger`},r),{default:s(()=>[i(e.$slots,`default`)]),_:3},16))}});export{d as t};
|