aicodeswitch 6.0.1 → 6.0.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/README.md +4 -1
- package/dist/server/conversions/index.js +6 -4
- package/dist/server/conversions/server-tool/mapper.js +143 -30
- package/dist/server/fs-database.js +22 -0
- package/dist/server/main.js +55 -23
- package/dist/ui/assets/index-BOeOgctP.js +1190 -0
- package/dist/ui/index.html +1 -1
- package/package.json +69 -8
- package/dist/ui/assets/index-BeM4AIzn.js +0 -1187
- package/scripts/dev.js +0 -283
package/README.md
CHANGED
|
@@ -39,7 +39,10 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
|
|
|
39
39
|
|
|
40
40
|
## 桌面客户端
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
桌面版基于 Electron,内置了 Node 运行时,**无需另外安装 Node.js**。
|
|
43
|
+
|
|
44
|
+
- 下载安装包:[进入下载](https://github.com/tangshuang/aicodeswitch/releases)(提供 Windows `.exe`/`.msi`、macOS `.dmg`(arm64 与 x64)、Linux `.AppImage`/`.deb`)
|
|
45
|
+
- 本地构建:`npm install` 后执行 `npm run electron:build`,产物输出到 `release/` 目录
|
|
43
46
|
|
|
44
47
|
## 命令行工具
|
|
45
48
|
|
|
@@ -100,8 +100,8 @@ const effort_js_1 = require("./thinking/effort.js");
|
|
|
100
100
|
* Transform a request body from one format to another.
|
|
101
101
|
*/
|
|
102
102
|
function transformRequest(options) {
|
|
103
|
-
const { fromFormat, toFormat, body, sanitizeBody, providerConfig } = options;
|
|
104
|
-
const targetBody = buildTargetBody({ fromFormat, toFormat, body, sanitizeBody, providerConfig });
|
|
103
|
+
const { fromFormat, toFormat, body, sanitizeBody, providerConfig, serverToolConfig } = options;
|
|
104
|
+
const targetBody = buildTargetBody({ fromFormat, toFormat, body, sanitizeBody, providerConfig, serverToolConfig });
|
|
105
105
|
return { body: targetBody, headers: {} };
|
|
106
106
|
}
|
|
107
107
|
// ============================================================
|
|
@@ -205,11 +205,13 @@ function createStreamConverter(options) {
|
|
|
205
205
|
*/
|
|
206
206
|
function buildTargetBody(options) {
|
|
207
207
|
const { fromFormat, toFormat, sanitizeBody, providerConfig, serverToolConfig } = options;
|
|
208
|
-
// Pre-processing:
|
|
208
|
+
// Pre-processing: strip Anthropic server-tool artifacts (server_tool_use,
|
|
209
|
+
// web_search_tool_result, server_tool_result, advisor_tool_result, and server-tool
|
|
210
|
+
// definitions in `tools`) when the upstream doesn't support them.
|
|
209
211
|
// Must happen before format conversion so all pair transformers handle the blocks correctly.
|
|
210
212
|
let processedBody = options.body;
|
|
211
213
|
if (fromFormat === 'claude' && !(serverToolConfig === null || serverToolConfig === void 0 ? void 0 : serverToolConfig.supportsServerToolUse)) {
|
|
212
|
-
processedBody = (0, mapper_js_1.
|
|
214
|
+
processedBody = (0, mapper_js_1.sanitizeServerToolArtifacts)(processedBody);
|
|
213
215
|
}
|
|
214
216
|
// Dispatch to the correct conversion pair
|
|
215
217
|
const key = `${fromFormat}->${toFormat}`;
|
|
@@ -2,48 +2,161 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Server tool use content block transformation.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Anthropic 提供一类"服务端工具"(Web Search / Web Fetch / Computer Use / Code Execution 等),
|
|
6
|
+
* 其交互在内容块层面会用到 Anthropic 专有的类型:
|
|
7
|
+
* - `server_tool_use` (assistant 消息:模型发起的服务端工具调用)
|
|
8
|
+
* - `web_search_tool_result` (user 消息:Web Search 结果)
|
|
9
|
+
* - `server_tool_result` (user 消息:通用服务端工具结果)
|
|
10
|
+
* - `advisor_tool_result` (user 消息:advisor 工具结果)
|
|
11
|
+
* 此外 `tools` 数组里会带上服务端工具定义(如 `{ type: 'web_search_20250305' }`)。
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* 多数第三方 Claude 兼容端点(GLM、MiniMax 等)只实现了客户端 `tool_use`/`tool_result`,
|
|
14
|
+
* 遇到上述任意一种都会以 `Unsupported content type: server_tool_use` 之类报错拒绝。
|
|
15
|
+
*
|
|
16
|
+
* 本模块在转发到"不支持服务端工具"的上游前,彻底清理这些痕迹:
|
|
17
|
+
* 1. assistant 的 `server_tool_use` → 改名 `tool_use`(保留 id/name/input);
|
|
18
|
+
* 2. user 的 `web_search_tool_result` / `server_tool_result` / `advisor_tool_result`
|
|
19
|
+
* → 降级为标准 `tool_result`(引用同一 tool_use_id,内容拍平为文本),
|
|
20
|
+
* 以便与上一步改名的 `tool_use` 维持合法配对;
|
|
21
|
+
* 3. 顶层 `tools` 数组中删除所有服务端工具定义,仅保留客户端自定义工具。
|
|
22
|
+
*
|
|
23
|
+
* 返回浅拷贝,不修改传入 body。
|
|
12
24
|
*/
|
|
13
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.sanitizeServerToolArtifacts = sanitizeServerToolArtifacts;
|
|
14
27
|
exports.convertServerToolUseToToolUse = convertServerToolUseToToolUse;
|
|
28
|
+
/** user 消息里需要降级为 tool_result 的服务端结果块类型 */
|
|
29
|
+
const SERVER_RESULT_TYPES = new Set([
|
|
30
|
+
'web_search_tool_result',
|
|
31
|
+
'server_tool_result',
|
|
32
|
+
'advisor_tool_result',
|
|
33
|
+
]);
|
|
15
34
|
/**
|
|
16
|
-
*
|
|
35
|
+
* 判断 `tools` 数组中的某项是否为 Anthropic 服务端工具定义。
|
|
36
|
+
* 客户端自定义工具的 `type` 为 'custom' 或缺省,且通常带 `input_schema`;
|
|
37
|
+
* 服务端工具 `type` 形如 'web_search_20250305' / 'computer_20250124' / 'bash_20250124'
|
|
38
|
+
* / 'text_editor_20250124' / 'code_execution_20250522' 等,且无 `input_schema`。
|
|
39
|
+
*/
|
|
40
|
+
function isServerToolDefinition(tool) {
|
|
41
|
+
if (!tool || typeof tool !== 'object')
|
|
42
|
+
return false;
|
|
43
|
+
const type = tool.type;
|
|
44
|
+
// 自定义工具:type 缺省或 'custom'
|
|
45
|
+
if (type === undefined || type === null || type === 'custom')
|
|
46
|
+
return false;
|
|
47
|
+
if (typeof type !== 'string')
|
|
48
|
+
return true;
|
|
49
|
+
// 已知服务端工具类型前缀
|
|
50
|
+
const SERVER_TOOL_PREFIXES = [
|
|
51
|
+
'web_search',
|
|
52
|
+
'computer',
|
|
53
|
+
'bash',
|
|
54
|
+
'text_editor',
|
|
55
|
+
'code_execution',
|
|
56
|
+
];
|
|
57
|
+
if (SERVER_TOOL_PREFIXES.some((p) => type.startsWith(p)))
|
|
58
|
+
return true;
|
|
59
|
+
// 兜底:带非 custom 的 type 且没有 input_schema,视为服务端/私有工具
|
|
60
|
+
if (!tool.input_schema)
|
|
61
|
+
return true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/** 把任意值压平为纯文本字符串(用于降级服务端结果块内容) */
|
|
65
|
+
function flattenToText(value) {
|
|
66
|
+
if (value == null)
|
|
67
|
+
return '';
|
|
68
|
+
if (typeof value === 'string')
|
|
69
|
+
return value;
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value
|
|
72
|
+
.map((item) => {
|
|
73
|
+
if (!item || typeof item !== 'object')
|
|
74
|
+
return String(item);
|
|
75
|
+
// 常见形态:{ type: 'text', text } / { type: 'web_search_tool_result', content: [...] }
|
|
76
|
+
if (typeof item.text === 'string')
|
|
77
|
+
return item.text;
|
|
78
|
+
if (typeof item.title === 'string')
|
|
79
|
+
return item.title;
|
|
80
|
+
if (Array.isArray(item.content))
|
|
81
|
+
return flattenToText(item.content);
|
|
82
|
+
if (item.url)
|
|
83
|
+
return `${item.url}`;
|
|
84
|
+
return JSON.stringify(item);
|
|
85
|
+
})
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.join('\n');
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === 'object') {
|
|
90
|
+
if (typeof value.text === 'string')
|
|
91
|
+
return value.text;
|
|
92
|
+
if (Array.isArray(value.content))
|
|
93
|
+
return flattenToText(value.content);
|
|
94
|
+
return JSON.stringify(value);
|
|
95
|
+
}
|
|
96
|
+
return String(value);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 清理请求体中所有"服务端工具"痕迹(内容块 + tools 定义)。
|
|
17
100
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
101
|
+
* 扫描 body.messages 中所有 role 的内容块,以及顶层 body.tools。
|
|
102
|
+
* 返回浅拷贝;无改动时原样返回原 body。
|
|
20
103
|
*/
|
|
21
|
-
function
|
|
22
|
-
if (!
|
|
104
|
+
function sanitizeServerToolArtifacts(body) {
|
|
105
|
+
if (!body || typeof body !== 'object')
|
|
23
106
|
return body;
|
|
24
|
-
}
|
|
25
107
|
let modified = false;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
108
|
+
let newBody = body;
|
|
109
|
+
// --- 1 & 2:处理 messages 内容块 ---
|
|
110
|
+
if (Array.isArray(body.messages)) {
|
|
111
|
+
const newMessages = body.messages.map((msg) => {
|
|
112
|
+
if (!msg || typeof msg !== 'object' || !Array.isArray(msg.content)) {
|
|
113
|
+
return msg;
|
|
114
|
+
}
|
|
115
|
+
let msgModified = false;
|
|
116
|
+
const newContent = msg.content.map((block) => {
|
|
117
|
+
if (!block || typeof block !== 'object')
|
|
118
|
+
return block;
|
|
119
|
+
// assistant: server_tool_use → tool_use
|
|
120
|
+
if (block.type === 'server_tool_use') {
|
|
121
|
+
msgModified = true;
|
|
122
|
+
return Object.assign(Object.assign({}, block), { type: 'tool_use' });
|
|
123
|
+
}
|
|
124
|
+
// user: 服务端结果块 → 标准 tool_result(保持与上面改名的 tool_use 配对)
|
|
125
|
+
if (SERVER_RESULT_TYPES.has(block.type)) {
|
|
126
|
+
msgModified = true;
|
|
127
|
+
const text = flattenToText(block.content);
|
|
128
|
+
return {
|
|
129
|
+
type: 'tool_result',
|
|
130
|
+
tool_use_id: block.tool_use_id,
|
|
131
|
+
content: text || '[server tool result omitted]',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return block;
|
|
135
|
+
});
|
|
136
|
+
if (msgModified) {
|
|
137
|
+
modified = true;
|
|
138
|
+
return Object.assign(Object.assign({}, msg), { content: newContent });
|
|
36
139
|
}
|
|
37
|
-
return
|
|
140
|
+
return msg;
|
|
38
141
|
});
|
|
39
|
-
if (
|
|
142
|
+
if (modified) {
|
|
143
|
+
newBody = Object.assign(Object.assign({}, newBody), { messages: newMessages });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// --- 3:清理 tools 数组中的服务端工具定义 ---
|
|
147
|
+
if (Array.isArray(newBody.tools) && newBody.tools.length > 0) {
|
|
148
|
+
const filteredTools = newBody.tools.filter((tool) => !isServerToolDefinition(tool));
|
|
149
|
+
if (filteredTools.length !== newBody.tools.length) {
|
|
40
150
|
modified = true;
|
|
41
|
-
|
|
151
|
+
newBody = Object.assign(Object.assign({}, newBody), { tools: filteredTools });
|
|
42
152
|
}
|
|
43
|
-
return msg;
|
|
44
|
-
});
|
|
45
|
-
if (!modified) {
|
|
46
|
-
return body;
|
|
47
153
|
}
|
|
48
|
-
return
|
|
154
|
+
return modified ? newBody : body;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* @deprecated 别名,等价于 {@link sanitizeServerToolArtifacts}。
|
|
158
|
+
* 保留旧名以兼容既有导入;新代码请使用 sanitizeServerToolArtifacts。
|
|
159
|
+
*/
|
|
160
|
+
function convertServerToolUseToToolUse(body) {
|
|
161
|
+
return sanitizeServerToolArtifacts(body);
|
|
49
162
|
}
|
|
@@ -55,6 +55,10 @@ const isClaudePermissionDefaultMode = (value) => {
|
|
|
55
55
|
const isValidAutocompactPct = (v) => {
|
|
56
56
|
return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 100;
|
|
57
57
|
};
|
|
58
|
+
// 最大重试次数校验:正整数(1-20),非法值返回 undefined(由 write 函数兜底默认 5)
|
|
59
|
+
const isValidMaxRetries = (v) => {
|
|
60
|
+
return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 20;
|
|
61
|
+
};
|
|
58
62
|
const normalizeFailoverRecoverySeconds = (value) => {
|
|
59
63
|
const parsed = typeof value === 'number' ? value : Number(value);
|
|
60
64
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -836,6 +840,15 @@ class FileSystemDatabaseManager {
|
|
|
836
840
|
if (typeof this.config.autocompactPctOverride !== 'undefined' && !isValidAutocompactPct(this.config.autocompactPctOverride)) {
|
|
837
841
|
this.config.autocompactPctOverride = undefined;
|
|
838
842
|
}
|
|
843
|
+
if (typeof this.config.claudeMaxRetries !== 'undefined' && !isValidMaxRetries(this.config.claudeMaxRetries)) {
|
|
844
|
+
this.config.claudeMaxRetries = undefined;
|
|
845
|
+
}
|
|
846
|
+
if (typeof this.config.codexMaxRetries !== 'undefined' && !isValidMaxRetries(this.config.codexMaxRetries)) {
|
|
847
|
+
this.config.codexMaxRetries = undefined;
|
|
848
|
+
}
|
|
849
|
+
if (typeof this.config.opencodeMaxRetries !== 'undefined' && !isValidMaxRetries(this.config.opencodeMaxRetries)) {
|
|
850
|
+
this.config.opencodeMaxRetries = undefined;
|
|
851
|
+
}
|
|
839
852
|
this.config.failoverRecoverySeconds = normalizeFailoverRecoverySeconds(this.config.failoverRecoverySeconds);
|
|
840
853
|
if (typeof this.config.ruleGlobalTimeout !== 'number' || this.config.ruleGlobalTimeout <= 0) {
|
|
841
854
|
this.config.ruleGlobalTimeout = undefined;
|
|
@@ -1752,6 +1765,15 @@ class FileSystemDatabaseManager {
|
|
|
1752
1765
|
if (typeof merged.autocompactPctOverride !== 'undefined' && !isValidAutocompactPct(merged.autocompactPctOverride)) {
|
|
1753
1766
|
merged.autocompactPctOverride = undefined;
|
|
1754
1767
|
}
|
|
1768
|
+
if (typeof merged.claudeMaxRetries !== 'undefined' && !isValidMaxRetries(merged.claudeMaxRetries)) {
|
|
1769
|
+
merged.claudeMaxRetries = undefined;
|
|
1770
|
+
}
|
|
1771
|
+
if (typeof merged.codexMaxRetries !== 'undefined' && !isValidMaxRetries(merged.codexMaxRetries)) {
|
|
1772
|
+
merged.codexMaxRetries = undefined;
|
|
1773
|
+
}
|
|
1774
|
+
if (typeof merged.opencodeMaxRetries !== 'undefined' && !isValidMaxRetries(merged.opencodeMaxRetries)) {
|
|
1775
|
+
merged.opencodeMaxRetries = undefined;
|
|
1776
|
+
}
|
|
1755
1777
|
merged.failoverRecoverySeconds = normalizeFailoverRecoverySeconds(merged.failoverRecoverySeconds);
|
|
1756
1778
|
if (typeof merged.ruleGlobalTimeout !== 'number' || merged.ruleGlobalTimeout <= 0) {
|
|
1757
1779
|
merged.ruleGlobalTimeout = undefined;
|
package/dist/server/main.js
CHANGED
|
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.start = exports.gracefulShutdown = void 0;
|
|
15
16
|
const express_1 = __importDefault(require("express"));
|
|
16
17
|
const cors_1 = __importDefault(require("cors"));
|
|
17
18
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
@@ -263,7 +264,7 @@ const isClaudePermissionDefaultMode = (value) => {
|
|
|
263
264
|
const isValidAutocompactPct = (v) => {
|
|
264
265
|
return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 100;
|
|
265
266
|
};
|
|
266
|
-
const writeClaudeConfig = (_dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, permissionsDefaultMode_1, effortLevel_1, defaultModel_1, autocompactPctOverride_1, ...args_1) => __awaiter(void 0, [_dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, permissionsDefaultMode_1, effortLevel_1, defaultModel_1, autocompactPctOverride_1, ...args_1], void 0, function* (_dbManager, enableAgentTeams, enableBypassPermissionsSupport, permissionsDefaultMode, effortLevel, defaultModel, autocompactPctOverride, options = {}) {
|
|
267
|
+
const writeClaudeConfig = (_dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, permissionsDefaultMode_1, effortLevel_1, defaultModel_1, autocompactPctOverride_1, maxRetries_1, ...args_1) => __awaiter(void 0, [_dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, permissionsDefaultMode_1, effortLevel_1, defaultModel_1, autocompactPctOverride_1, maxRetries_1, ...args_1], void 0, function* (_dbManager, enableAgentTeams, enableBypassPermissionsSupport, permissionsDefaultMode, effortLevel, defaultModel, autocompactPctOverride, maxRetries, options = {}) {
|
|
267
268
|
var _a;
|
|
268
269
|
try {
|
|
269
270
|
const homeDir = os_1.default.homedir();
|
|
@@ -318,7 +319,7 @@ const writeClaudeConfig = (_dbManager_1, enableAgentTeams_1, enableBypassPermiss
|
|
|
318
319
|
ANTHROPIC_BASE_URL: `http://${clientHost}:${port}/claude-code`,
|
|
319
320
|
API_TIMEOUT_MS: "3000000",
|
|
320
321
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
|
|
321
|
-
CLAUDE_CODE_MAX_RETRIES:
|
|
322
|
+
CLAUDE_CODE_MAX_RETRIES: maxRetries !== null && maxRetries !== void 0 ? maxRetries : 5
|
|
322
323
|
};
|
|
323
324
|
// 如果启用Agent Teams功能,添加对应的环境变量
|
|
324
325
|
if (enableAgentTeams) {
|
|
@@ -416,7 +417,7 @@ const DEFAULT_CODEX_REASONING_EFFORT = 'high';
|
|
|
416
417
|
const isCodexReasoningEffort = (value) => {
|
|
417
418
|
return typeof value === 'string' && VALID_CODEX_REASONING_EFFORTS.includes(value);
|
|
418
419
|
};
|
|
419
|
-
const writeCodexConfig = (_dbManager_1, ...args_1) => __awaiter(void 0, [_dbManager_1, ...args_1], void 0, function* (_dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT, codexDefaultModel, enableMemories, options = {}) {
|
|
420
|
+
const writeCodexConfig = (_dbManager_1, ...args_1) => __awaiter(void 0, [_dbManager_1, ...args_1], void 0, function* (_dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT, codexDefaultModel, enableMemories, maxRetries, options = {}) {
|
|
420
421
|
var _a;
|
|
421
422
|
try {
|
|
422
423
|
const homeDir = os_1.default.homedir();
|
|
@@ -478,7 +479,7 @@ const writeCodexConfig = (_dbManager_1, ...args_1) => __awaiter(void 0, [_dbMana
|
|
|
478
479
|
name: "aicodeswitch",
|
|
479
480
|
base_url: `http://${clientHost}:${port}/codex`,
|
|
480
481
|
wire_api: "responses",
|
|
481
|
-
stream_max_retries:
|
|
482
|
+
stream_max_retries: maxRetries !== null && maxRetries !== void 0 ? maxRetries : 5,
|
|
482
483
|
stream_retry_backoff: "fixed"
|
|
483
484
|
}
|
|
484
485
|
}
|
|
@@ -697,7 +698,7 @@ const DEFAULT_OPENCODE_MODEL = 'claude-sonnet-4-20250514';
|
|
|
697
698
|
* 的 /opencode/v1 端点(OpenAI Chat Completions 格式)。仅托管 provider.aicodeswitch
|
|
698
699
|
* 段与 model/small_model/mcp 字段,其余用户配置(其它 provider、agent、command 等)保留。
|
|
699
700
|
*/
|
|
700
|
-
const writeOpencodeConfig = (_dbManager_1, defaultModel_1, ...args_1) => __awaiter(void 0, [_dbManager_1, defaultModel_1, ...args_1], void 0, function* (_dbManager, defaultModel, options = {}) {
|
|
701
|
+
const writeOpencodeConfig = (_dbManager_1, defaultModel_1, maxRetries_1, ...args_1) => __awaiter(void 0, [_dbManager_1, defaultModel_1, maxRetries_1, ...args_1], void 0, function* (_dbManager, defaultModel, maxRetries, options = {}) {
|
|
701
702
|
var _a;
|
|
702
703
|
try {
|
|
703
704
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
|
|
@@ -748,7 +749,8 @@ const writeOpencodeConfig = (_dbManager_1, defaultModel_1, ...args_1) => __await
|
|
|
748
749
|
name: 'AICodeSwitch',
|
|
749
750
|
options: {
|
|
750
751
|
baseURL: `http://${clientHost}:${port}/opencode/v1`,
|
|
751
|
-
apiKey: 'api_key'
|
|
752
|
+
apiKey: 'api_key',
|
|
753
|
+
maxRetries: maxRetries !== null && maxRetries !== void 0 ? maxRetries : 5
|
|
752
754
|
},
|
|
753
755
|
models: {
|
|
754
756
|
[model]: { name: model }
|
|
@@ -856,14 +858,14 @@ const syncConfigsOnServerStartup = (dbManager) => __awaiter(void 0, void 0, void
|
|
|
856
858
|
const claudeEffortLevel = isClaudeEffortLevel(config.claudeEffortLevel)
|
|
857
859
|
? config.claudeEffortLevel
|
|
858
860
|
: DEFAULT_CLAUDE_EFFORT_LEVEL;
|
|
859
|
-
const claudeWritten = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, config.claudePermissionsDefaultMode, claudeEffortLevel, config.claudeDefaultModel, config.autocompactPctOverride);
|
|
861
|
+
const claudeWritten = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, config.claudePermissionsDefaultMode, claudeEffortLevel, config.claudeDefaultModel, config.autocompactPctOverride, config.claudeMaxRetries);
|
|
860
862
|
console.log(`[Startup Config Sync] Claude Code config ${claudeWritten ? 'written' : 'skipped'}`);
|
|
861
863
|
const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
|
|
862
864
|
? config.codexModelReasoningEffort
|
|
863
865
|
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
864
|
-
const codexWritten = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, config.codexEnableMemories);
|
|
866
|
+
const codexWritten = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, config.codexEnableMemories, config.codexMaxRetries);
|
|
865
867
|
console.log(`[Startup Config Sync] Codex config ${codexWritten ? 'written' : 'skipped'}`);
|
|
866
|
-
const opencodeWritten = yield writeOpencodeConfig(dbManager, config.opencodeDefaultModel);
|
|
868
|
+
const opencodeWritten = yield writeOpencodeConfig(dbManager, config.opencodeDefaultModel, config.opencodeMaxRetries);
|
|
867
869
|
console.log(`[Startup Config Sync] OpenCode config ${opencodeWritten ? 'written' : 'skipped'}`);
|
|
868
870
|
});
|
|
869
871
|
const syncConfigsOnGlobalConfigUpdate = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -871,14 +873,14 @@ const syncConfigsOnGlobalConfigUpdate = (dbManager) => __awaiter(void 0, void 0,
|
|
|
871
873
|
const claudeEffortLevel = isClaudeEffortLevel(config.claudeEffortLevel)
|
|
872
874
|
? config.claudeEffortLevel
|
|
873
875
|
: DEFAULT_CLAUDE_EFFORT_LEVEL;
|
|
874
|
-
const claudeUpdated = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, config.claudePermissionsDefaultMode, claudeEffortLevel, config.claudeDefaultModel, config.autocompactPctOverride, { allowOverwriteRefresh: true });
|
|
876
|
+
const claudeUpdated = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, config.claudePermissionsDefaultMode, claudeEffortLevel, config.claudeDefaultModel, config.autocompactPctOverride, config.claudeMaxRetries, { allowOverwriteRefresh: true });
|
|
875
877
|
console.log(`[Config Update Sync] Claude Code config ${claudeUpdated ? 'written' : 'skipped'}`);
|
|
876
878
|
const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
|
|
877
879
|
? config.codexModelReasoningEffort
|
|
878
880
|
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
879
|
-
const codexUpdated = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, config.codexEnableMemories, { allowOverwriteRefresh: true });
|
|
881
|
+
const codexUpdated = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, config.codexEnableMemories, config.codexMaxRetries, { allowOverwriteRefresh: true });
|
|
880
882
|
console.log(`[Config Update Sync] Codex config ${codexUpdated ? 'written' : 'skipped'}`);
|
|
881
|
-
const opencodeUpdated = yield writeOpencodeConfig(dbManager, config.opencodeDefaultModel, { allowOverwriteRefresh: true });
|
|
883
|
+
const opencodeUpdated = yield writeOpencodeConfig(dbManager, config.opencodeDefaultModel, config.opencodeMaxRetries, { allowOverwriteRefresh: true });
|
|
882
884
|
console.log(`[Config Update Sync] OpenCode config ${opencodeUpdated ? 'written' : 'skipped'}`);
|
|
883
885
|
});
|
|
884
886
|
const getCentralSkillsDir = () => {
|
|
@@ -1317,7 +1319,7 @@ const listInstalledSkills = () => {
|
|
|
1317
1319
|
const registerRoutes = (dbManager, proxyServer) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1318
1320
|
updateProxyConfig(dbManager.getConfig());
|
|
1319
1321
|
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
|
|
1320
|
-
//
|
|
1322
|
+
// 数据就绪验证端点(供桌面端 Electron 启动阶段确认后端完全可用)
|
|
1321
1323
|
app.get('/api/ready', (_req, res) => {
|
|
1322
1324
|
const vendors = dbManager.getVendors();
|
|
1323
1325
|
const routes = dbManager.getRoutes();
|
|
@@ -2328,7 +2330,7 @@ ${instruction}
|
|
|
2328
2330
|
: isClaudePermissionDefaultMode(appConfig.claudePermissionsDefaultMode)
|
|
2329
2331
|
? appConfig.claudePermissionsDefaultMode
|
|
2330
2332
|
: DEFAULT_CLAUDE_PERMISSION_DEFAULT_MODE;
|
|
2331
|
-
const result = yield writeClaudeConfig(dbManager, enableAgentTeams, enableBypassPermissionsSupport, permissionsDefaultMode, undefined, appConfig.claudeDefaultModel, appConfig.autocompactPctOverride);
|
|
2333
|
+
const result = yield writeClaudeConfig(dbManager, enableAgentTeams, enableBypassPermissionsSupport, permissionsDefaultMode, undefined, appConfig.claudeDefaultModel, appConfig.autocompactPctOverride, appConfig.claudeMaxRetries);
|
|
2332
2334
|
applyWriteLocalRecords(proxyServer);
|
|
2333
2335
|
res.json(result);
|
|
2334
2336
|
})));
|
|
@@ -2344,7 +2346,7 @@ ${instruction}
|
|
|
2344
2346
|
const enableMemories = requestedEnableMemories !== undefined
|
|
2345
2347
|
? !!requestedEnableMemories
|
|
2346
2348
|
: !!appConfig.codexEnableMemories;
|
|
2347
|
-
const result = yield writeCodexConfig(dbManager, modelReasoningEffort, appConfig.codexDefaultModel, enableMemories);
|
|
2349
|
+
const result = yield writeCodexConfig(dbManager, modelReasoningEffort, appConfig.codexDefaultModel, enableMemories, appConfig.codexMaxRetries);
|
|
2348
2350
|
applyWriteLocalRecords(proxyServer);
|
|
2349
2351
|
res.json(result);
|
|
2350
2352
|
})));
|
|
@@ -2353,7 +2355,7 @@ ${instruction}
|
|
|
2353
2355
|
const appConfig = dbManager.getConfig();
|
|
2354
2356
|
const requestedModel = typeof ((_a = req.body) === null || _a === void 0 ? void 0 : _a.defaultModel) === 'string' ? req.body.defaultModel : undefined;
|
|
2355
2357
|
const defaultModel = requestedModel || appConfig.opencodeDefaultModel;
|
|
2356
|
-
const result = yield writeOpencodeConfig(dbManager, defaultModel);
|
|
2358
|
+
const result = yield writeOpencodeConfig(dbManager, defaultModel, appConfig.opencodeMaxRetries);
|
|
2357
2359
|
applyWriteLocalRecords(proxyServer);
|
|
2358
2360
|
res.json(result);
|
|
2359
2361
|
})));
|
|
@@ -3804,6 +3806,26 @@ ${instruction}
|
|
|
3804
3806
|
});
|
|
3805
3807
|
// listen 就绪标志:区分"启动阶段"与"运行阶段",启动期致命异常应让进程退出
|
|
3806
3808
|
let listenReady = false;
|
|
3809
|
+
/**
|
|
3810
|
+
* 是否以「内嵌进程」模式运行(例如被 Electron 主进程直接 require 并调用 start)。
|
|
3811
|
+
* 该模式下:
|
|
3812
|
+
* - shutdown 流程结束后不调用 process.exit,把退出时机交还给宿主(Electron)
|
|
3813
|
+
* - 模块被 require 时不自动执行 start(),由宿主显式调用导出的 start
|
|
3814
|
+
*/
|
|
3815
|
+
const IN_PROCESS = process.env.AIC_IN_PROCESS === '1';
|
|
3816
|
+
// 保存当前服务实例的优雅关闭函数,供宿主(Electron 主进程)在退出前显式调用。
|
|
3817
|
+
let _gracefulShutdown = null;
|
|
3818
|
+
/**
|
|
3819
|
+
* 供宿主进程调用的优雅关闭入口。
|
|
3820
|
+
* 仅在 start() 成功注册 shutdown 后可用;调用后会恢复工具配置、关闭 DB / 日志、释放端口,
|
|
3821
|
+
* 内嵌模式下不会触发 process.exit。
|
|
3822
|
+
*/
|
|
3823
|
+
const gracefulShutdown = (signal = 'HOST_QUIT') => {
|
|
3824
|
+
if (_gracefulShutdown)
|
|
3825
|
+
return _gracefulShutdown(signal);
|
|
3826
|
+
return Promise.resolve();
|
|
3827
|
+
};
|
|
3828
|
+
exports.gracefulShutdown = gracefulShutdown;
|
|
3807
3829
|
const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
3808
3830
|
fs_1.default.mkdirSync(dataDir, { recursive: true });
|
|
3809
3831
|
// 自动检测数据库类型并执行迁移(如果需要)
|
|
@@ -4021,25 +4043,31 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
4021
4043
|
})
|
|
4022
4044
|
]);
|
|
4023
4045
|
console.log('Server stopped.');
|
|
4024
|
-
|
|
4046
|
+
// 内嵌进程模式下不主动退出,交由宿主(Electron)控制进程生命周期
|
|
4047
|
+
if (!IN_PROCESS) {
|
|
4048
|
+
process.exit(0);
|
|
4049
|
+
}
|
|
4025
4050
|
}))();
|
|
4026
4051
|
return shutdownPromise;
|
|
4027
4052
|
});
|
|
4053
|
+
// 注册到模块级句柄,供宿主在退出前显式触发完整关闭流程
|
|
4054
|
+
_gracefulShutdown = shutdown;
|
|
4028
4055
|
process.on('SIGINT', () => { void shutdown('SIGINT'); });
|
|
4029
4056
|
process.on('SIGTERM', () => { void shutdown('SIGTERM'); });
|
|
4030
|
-
// 优雅关闭端点(供
|
|
4057
|
+
// 优雅关闭端点(供 Electron 等外部调用者触发服务端完整清理流程)
|
|
4031
4058
|
// 放在 shutdown 定义之后注册,确保闭包可引用
|
|
4032
4059
|
app.post('/api/shutdown', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4033
4060
|
res.json({ success: true });
|
|
4034
4061
|
setImmediate(() => { void shutdown('HTTP_SHUTDOWN'); });
|
|
4035
4062
|
})));
|
|
4036
4063
|
});
|
|
4064
|
+
exports.start = start;
|
|
4037
4065
|
// 全局未捕获异常处理 - 防止服务崩溃
|
|
4038
4066
|
process.on('uncaughtException', (error) => {
|
|
4039
4067
|
console.error('[Uncaught Exception] 服务遇到未捕获的异常:', error);
|
|
4040
4068
|
console.error('[Uncaught Exception] 堆栈信息:', error.stack);
|
|
4041
4069
|
// 启动阶段(listen 之前)的异常通常是致命的(依赖加载失败、初始化崩溃等),
|
|
4042
|
-
// 静默吞掉会导致"进程在但不 listen"
|
|
4070
|
+
// 静默吞掉会导致"进程在但不 listen",桌面端只能干等超时;此时退出让上层重新探测/诊断。
|
|
4043
4071
|
if (!listenReady) {
|
|
4044
4072
|
console.error('[Uncaught Exception] 发生在服务监听之前,退出进程');
|
|
4045
4073
|
process.exit(1);
|
|
@@ -4052,7 +4080,11 @@ process.on('unhandledRejection', (reason) => {
|
|
|
4052
4080
|
process.exit(1);
|
|
4053
4081
|
}
|
|
4054
4082
|
});
|
|
4055
|
-
start
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4083
|
+
// 仅在被直接运行时(如 aicos start → node dist/server/main.js)自动启动;
|
|
4084
|
+
// 被 Electron 主进程 require 时(require.main !== module 或 IN_PROCESS)由宿主显式调用 start()。
|
|
4085
|
+
if (!IN_PROCESS && require.main === module) {
|
|
4086
|
+
(0, exports.start)().catch((error) => {
|
|
4087
|
+
console.error('Failed to start server:', error);
|
|
4088
|
+
process.exit(1);
|
|
4089
|
+
});
|
|
4090
|
+
}
|