aicodeswitch 5.2.9 → 5.2.11
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 +8 -31
- package/bin/cli.js +3 -0
- package/bin/status.js +84 -0
- package/bin/utils/managed-fields.js +2 -0
- package/dist/server/config-managed-fields.js +2 -0
- package/dist/server/conversions/pairs/responses-claude/response.js +5 -21
- package/dist/server/conversions/pairs/responses-claude/streaming.js +18 -20
- package/dist/server/conversions/pairs/responses-completions/response.js +2 -9
- package/dist/server/conversions/pairs/responses-completions/streaming.js +3 -10
- package/dist/server/conversions/utils/usage.js +28 -8
- package/dist/server/main.js +51 -10
- package/dist/server/session-launcher.js +1 -1
- package/dist/server/tools-service.js +1 -0
- package/dist/server/utils.js +20 -6
- package/dist/ui/aicodeswitch-flow.svg +360 -0
- package/dist/ui/assets/{index-BKBriExY.js → index-DR6cZIa7.js} +23 -46
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,50 +35,26 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
|
|
|
35
35
|
* 数据完全本地,自主可控
|
|
36
36
|
* 特殊语法:在发送的提示词最前面添加`[!]`来直接切换为高智商模型服务(`[x]`关闭),简单快捷
|
|
37
37
|
|
|
38
|
+

|
|
39
|
+
|
|
38
40
|
## 桌面客户端
|
|
39
41
|
|
|
40
42
|
[进入下载](https://github.com/tangshuang/aicodeswitch/releases)
|
|
41
43
|
|
|
42
44
|
## 命令行工具
|
|
43
45
|
|
|
44
|
-
### 安装
|
|
45
|
-
|
|
46
46
|
```
|
|
47
|
+
# 安装
|
|
47
48
|
npm install -g aicodeswitch
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
### 使用方法
|
|
51
|
-
|
|
52
|
-
**启动服务**
|
|
53
|
-
|
|
54
|
-
```
|
|
49
|
+
# 启动服务
|
|
55
50
|
aicos start
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
或者直接运行
|
|
59
|
-
|
|
60
|
-
```
|
|
51
|
+
# http://127.0.0.1:4567
|
|
52
|
+
# 启动服务并打开管理界面
|
|
61
53
|
aicos ui
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
**停止服务**
|
|
65
|
-
|
|
66
|
-
```
|
|
54
|
+
# 停止服务
|
|
67
55
|
aicos stop
|
|
68
56
|
```
|
|
69
57
|
|
|
70
|
-
**进入管理界面**
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
# 自动启动服务和打开界面
|
|
74
|
-
aicos ui
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
# 手动在浏览器打开管理界面
|
|
79
|
-
http://127.0.0.1:4567
|
|
80
|
-
```
|
|
81
|
-
|
|
82
58
|
## 管理界面
|
|
83
59
|
|
|
84
60
|
**配置供应商**
|
|
@@ -257,6 +233,7 @@ PORT=4567
|
|
|
257
233
|
## 关联资源
|
|
258
234
|
|
|
259
235
|
* [Claude Code 深度教程](https://claudecode.tangshuang.net): 100%免费的Claude Code入门到精通教程
|
|
236
|
+
* [AICodingBus](https://aicodingbus.24x7.to): AI Tokens 交换平台
|
|
260
237
|
|
|
261
238
|
## 支持我
|
|
262
239
|
|
package/bin/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ const commands = {
|
|
|
7
7
|
start: require('./start'),
|
|
8
8
|
stop: require('./stop'),
|
|
9
9
|
restart: require('./restart'),
|
|
10
|
+
status: require('./status'),
|
|
10
11
|
upgrade: require('./upgrade'),
|
|
11
12
|
restore: require('./restore'),
|
|
12
13
|
version: require('./version'),
|
|
@@ -21,6 +22,7 @@ Commands:
|
|
|
21
22
|
start Start the AI Code Switch server
|
|
22
23
|
stop Stop the AI Code Switch server
|
|
23
24
|
restart Restart the AI Code Switch server
|
|
25
|
+
status Show server status, running address and port
|
|
24
26
|
ui Open the web UI in browser (starts server if needed)
|
|
25
27
|
upgrade Upgrade to the latest version and restart
|
|
26
28
|
restore Restore original configuration files
|
|
@@ -30,6 +32,7 @@ Example:
|
|
|
30
32
|
aicos start
|
|
31
33
|
aicos stop
|
|
32
34
|
aicos restart
|
|
35
|
+
aicos status
|
|
33
36
|
aicos ui
|
|
34
37
|
aicos upgrade
|
|
35
38
|
aicos restore
|
package/bin/status.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const boxen = require('boxen');
|
|
6
|
+
const { isServerRunning, getServerInfo } = require('./utils/get-server');
|
|
7
|
+
const { findPidByPort, getProcessInfo } = require('./utils/port-utils');
|
|
8
|
+
|
|
9
|
+
const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
|
|
10
|
+
const LOG_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.log');
|
|
11
|
+
|
|
12
|
+
const status = async () => {
|
|
13
|
+
console.log('\n');
|
|
14
|
+
|
|
15
|
+
// 读取配置的 host/port
|
|
16
|
+
const { host, port } = getServerInfo();
|
|
17
|
+
const url = `http://${host}:${port}`;
|
|
18
|
+
|
|
19
|
+
// 优先通过 PID 文件判断
|
|
20
|
+
let pidFromFile = null;
|
|
21
|
+
if (fs.existsSync(PID_FILE)) {
|
|
22
|
+
try {
|
|
23
|
+
pidFromFile = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
pidFromFile = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const runningByPidFile = isServerRunning();
|
|
30
|
+
// 通过端口检测(兜底:PID 文件丢失但服务仍在监听的情况)
|
|
31
|
+
const pidByPort = await findPidByPort(port);
|
|
32
|
+
const isRunning = runningByPidFile || !!pidByPort;
|
|
33
|
+
|
|
34
|
+
if (isRunning) {
|
|
35
|
+
// PID 优先使用 PID 文件记录的,其次使用端口检测到的
|
|
36
|
+
const pid = runningByPidFile ? pidFromFile : pidByPort;
|
|
37
|
+
const processInfo = await getProcessInfo(pid);
|
|
38
|
+
|
|
39
|
+
console.log(boxen(
|
|
40
|
+
chalk.green.bold('🟢 AI Code Switch Server\n\n') +
|
|
41
|
+
chalk.white('Status: ') + chalk.green.bold('● Running\n') +
|
|
42
|
+
chalk.white('Host: ') + chalk.cyan(host) + '\n' +
|
|
43
|
+
chalk.white('Port: ') + chalk.cyan.bold(port) + '\n' +
|
|
44
|
+
chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
|
|
45
|
+
chalk.white('PID: ') + chalk.yellow(pid) + '\n' +
|
|
46
|
+
chalk.white('Process: ') + chalk.gray(processInfo) + '\n' +
|
|
47
|
+
chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
|
|
48
|
+
chalk.gray('Open the URL in your browser to access the dashboard'),
|
|
49
|
+
{
|
|
50
|
+
padding: 1,
|
|
51
|
+
margin: 1,
|
|
52
|
+
borderStyle: 'double',
|
|
53
|
+
borderColor: 'green'
|
|
54
|
+
}
|
|
55
|
+
));
|
|
56
|
+
|
|
57
|
+
console.log(chalk.cyan('💡 Tips:\n'));
|
|
58
|
+
console.log(chalk.white(' • Open browser: ') + chalk.cyan(url));
|
|
59
|
+
console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
|
|
60
|
+
console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
|
|
61
|
+
console.log(chalk.white(' • Restart: ') + chalk.yellow('aicos restart'));
|
|
62
|
+
console.log('\n');
|
|
63
|
+
} else {
|
|
64
|
+
console.log(boxen(
|
|
65
|
+
chalk.gray('AI Code Switch Server\n\n') +
|
|
66
|
+
chalk.white('Status: ') + chalk.red('● Stopped\n\n') +
|
|
67
|
+
chalk.white('Host: ') + chalk.gray(host) + '\n' +
|
|
68
|
+
chalk.white('Port: ') + chalk.gray(port) + '\n' +
|
|
69
|
+
chalk.white('URL: ') + chalk.gray(url) + ' ' + chalk.gray('(not listening)'),
|
|
70
|
+
{
|
|
71
|
+
padding: 1,
|
|
72
|
+
margin: 1,
|
|
73
|
+
borderStyle: 'round',
|
|
74
|
+
borderColor: 'gray'
|
|
75
|
+
}
|
|
76
|
+
));
|
|
77
|
+
|
|
78
|
+
console.log(chalk.white('Use ') + chalk.cyan('aicos start') + chalk.white(' to start the server.\n'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.exit(0);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
module.exports = status;
|
|
@@ -41,6 +41,8 @@ exports.CODEX_CONFIG_MANAGED_FIELDS = [
|
|
|
41
41
|
{ path: ['enableRouteSelection'] },
|
|
42
42
|
{ path: ['model_providers', 'aicodeswitch'], isSection: true },
|
|
43
43
|
{ path: ['mcp_servers'], isSection: true, optional: true },
|
|
44
|
+
{ path: ['features'], isSection: true, optional: true },
|
|
45
|
+
{ path: ['memories'], isSection: true, optional: true },
|
|
44
46
|
];
|
|
45
47
|
/**
|
|
46
48
|
* Codex auth.json 管理字段定义
|
|
@@ -9,6 +9,7 @@ exports.claudeToResponsesResponse = claudeToResponsesResponse;
|
|
|
9
9
|
const id_js_1 = require("../../utils/id.js");
|
|
10
10
|
const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
|
|
11
11
|
const mapper_js_1 = require("../../thinking/mapper.js");
|
|
12
|
+
const usage_js_1 = require("../../utils/usage.js");
|
|
12
13
|
/**
|
|
13
14
|
* Convert a Claude Messages response to an OpenAI Responses API response.
|
|
14
15
|
*/
|
|
@@ -45,26 +46,9 @@ function claudeToResponsesResponse(response) {
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
const { status, incomplete_details } = (0, stop_reasons_js_1.claudeToResponsesStatus)(response.stop_reason);
|
|
48
|
-
|
|
49
|
+
// 上游无 usage 时省略 usage 字段(不伪造 0)
|
|
50
|
+
const usage = (0, usage_js_1.toResponsesUsage)(response.usage);
|
|
49
51
|
const responseId = response.id || (0, id_js_1.generateResponseId)();
|
|
50
|
-
return Object.assign(Object.assign({ id: responseId, object: 'response', status,
|
|
51
|
-
output, model: response.model || '', created_at: Math.floor(Date.now() / 1000), usage }, (incomplete_details ? { incomplete_details } : {})), { metadata: {} });
|
|
52
|
-
}
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
// Helpers
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
/**
|
|
57
|
-
* Map Claude usage to Responses API usage.
|
|
58
|
-
*/
|
|
59
|
-
function claudeToResponsesUsage(usage) {
|
|
60
|
-
var _a, _b;
|
|
61
|
-
if (!usage)
|
|
62
|
-
return { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
63
|
-
const input = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
|
|
64
|
-
const output = (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
65
|
-
return {
|
|
66
|
-
input_tokens: input,
|
|
67
|
-
output_tokens: output,
|
|
68
|
-
total_tokens: input + output,
|
|
69
|
-
};
|
|
52
|
+
return Object.assign(Object.assign(Object.assign({ id: responseId, object: 'response', status,
|
|
53
|
+
output, model: response.model || '', created_at: Math.floor(Date.now() / 1000) }, (usage ? { usage } : {})), (incomplete_details ? { incomplete_details } : {})), { metadata: {} });
|
|
70
54
|
}
|
|
@@ -10,6 +10,7 @@ exports.ClaudeToResponsesConverter = void 0;
|
|
|
10
10
|
const id_js_1 = require("../../utils/id.js");
|
|
11
11
|
const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
|
|
12
12
|
const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
|
|
13
|
+
const usage_js_1 = require("../../utils/usage.js");
|
|
13
14
|
/**
|
|
14
15
|
* ClaudeToResponsesConverter: Claude Messages SSE → Responses API SSE
|
|
15
16
|
*/
|
|
@@ -49,7 +50,7 @@ class ClaudeToResponsesConverter {
|
|
|
49
50
|
enumerable: true,
|
|
50
51
|
configurable: true,
|
|
51
52
|
writable: true,
|
|
52
|
-
value:
|
|
53
|
+
value: null
|
|
53
54
|
});
|
|
54
55
|
Object.defineProperty(this, "output", {
|
|
55
56
|
enumerable: true,
|
|
@@ -89,7 +90,7 @@ class ClaudeToResponsesConverter {
|
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
convertEvent(event) {
|
|
92
|
-
var _a, _b, _c, _d, _e;
|
|
93
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
93
94
|
if (!event.data)
|
|
94
95
|
return [];
|
|
95
96
|
const events = [];
|
|
@@ -99,6 +100,14 @@ class ClaudeToResponsesConverter {
|
|
|
99
100
|
case 'message_start': {
|
|
100
101
|
this.model = ((_a = data.message) === null || _a === void 0 ? void 0 : _a.model) || '';
|
|
101
102
|
this.responseId = ((_b = data.message) === null || _b === void 0 ? void 0 : _b.id) || this.responseId;
|
|
103
|
+
// Claude 的 prompt token 数在 message_start 的 message.usage.input_tokens
|
|
104
|
+
const startUsage = (_c = data.message) === null || _c === void 0 ? void 0 : _c.usage;
|
|
105
|
+
if (startUsage) {
|
|
106
|
+
this.usage = {
|
|
107
|
+
input_tokens: (_d = startUsage.input_tokens) !== null && _d !== void 0 ? _d : 0,
|
|
108
|
+
output_tokens: (_e = startUsage.output_tokens) !== null && _e !== void 0 ? _e : 0,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
102
111
|
events.push(this.makeSSE('response.created', {
|
|
103
112
|
id: this.responseId,
|
|
104
113
|
object: 'response',
|
|
@@ -236,11 +245,11 @@ class ClaudeToResponsesConverter {
|
|
|
236
245
|
break;
|
|
237
246
|
}
|
|
238
247
|
case 'message_delta': {
|
|
239
|
-
this.pendingStopReason = ((
|
|
248
|
+
this.pendingStopReason = ((_f = data.delta) === null || _f === void 0 ? void 0 : _f.stop_reason) || null;
|
|
240
249
|
if (data.usage) {
|
|
241
250
|
this.usage = {
|
|
242
|
-
input_tokens: this.usage.input_tokens,
|
|
243
|
-
output_tokens: (
|
|
251
|
+
input_tokens: (_j = (_g = data.usage.input_tokens) !== null && _g !== void 0 ? _g : (_h = this.usage) === null || _h === void 0 ? void 0 : _h.input_tokens) !== null && _j !== void 0 ? _j : 0,
|
|
252
|
+
output_tokens: (_o = (_l = (_k = data.usage.output_tokens) !== null && _k !== void 0 ? _k : data.usage.tokens) !== null && _l !== void 0 ? _l : (_m = this.usage) === null || _m === void 0 ? void 0 : _m.output_tokens) !== null && _o !== void 0 ? _o : 0,
|
|
244
253
|
};
|
|
245
254
|
}
|
|
246
255
|
break;
|
|
@@ -253,7 +262,7 @@ class ClaudeToResponsesConverter {
|
|
|
253
262
|
break;
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
|
-
catch (
|
|
265
|
+
catch (_p) {
|
|
257
266
|
// Ignore parse errors
|
|
258
267
|
}
|
|
259
268
|
return events;
|
|
@@ -315,20 +324,9 @@ class ClaudeToResponsesConverter {
|
|
|
315
324
|
this.closeText(events);
|
|
316
325
|
this.closeThinking(events);
|
|
317
326
|
const { status, incomplete_details } = (0, stop_reasons_js_1.claudeToResponsesStatus)(this.pendingStopReason);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
status,
|
|
322
|
-
output: this.output,
|
|
323
|
-
model: this.model,
|
|
324
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
325
|
-
usage: {
|
|
326
|
-
input_tokens: this.usage.input_tokens,
|
|
327
|
-
output_tokens: this.usage.output_tokens,
|
|
328
|
-
total_tokens: this.usage.input_tokens + this.usage.output_tokens,
|
|
329
|
-
},
|
|
330
|
-
metadata: {},
|
|
331
|
-
};
|
|
327
|
+
// 上游无 usage 时省略 usage 字段(不伪造 0,避免 Codex missing field input_tokens)
|
|
328
|
+
const responsesUsage = (0, usage_js_1.toResponsesUsage)(this.usage);
|
|
329
|
+
const responseObj = Object.assign({ id: this.responseId, object: 'response', status, output: this.output, model: this.model, created_at: Math.floor(Date.now() / 1000), metadata: {} }, (responsesUsage ? { usage: responsesUsage } : {}));
|
|
332
330
|
if (incomplete_details) {
|
|
333
331
|
responseObj.incomplete_details = incomplete_details;
|
|
334
332
|
}
|
|
@@ -80,15 +80,8 @@ function completionsToResponsesResponse(response) {
|
|
|
80
80
|
const finishReason = (0, stop_reasons_js_1.completionsToResponsesFinishReason)(choice.finish_reason);
|
|
81
81
|
const status = finishReason === 'incomplete' ? 'incomplete' : 'completed';
|
|
82
82
|
const usage = (0, usage_js_1.completionsToResponsesUsage)(response.usage);
|
|
83
|
-
const result = {
|
|
84
|
-
|
|
85
|
-
object: 'response',
|
|
86
|
-
status,
|
|
87
|
-
output,
|
|
88
|
-
model: response.model,
|
|
89
|
-
usage,
|
|
90
|
-
created_at: response.created || Math.floor(Date.now() / 1000),
|
|
91
|
-
};
|
|
83
|
+
const result = Object.assign({ id: ((_m = response.id) === null || _m === void 0 ? void 0 : _m.startsWith('resp_')) ? response.id : (0, id_js_1.generateResponseId)(), object: 'response', status,
|
|
84
|
+
output, model: response.model, created_at: response.created || Math.floor(Date.now() / 1000) }, (usage ? { usage } : {}));
|
|
92
85
|
if (status === 'incomplete') {
|
|
93
86
|
result.incomplete_details = { reason: 'max_output_tokens' };
|
|
94
87
|
}
|
|
@@ -320,16 +320,9 @@ class CompletionsToResponsesConverter {
|
|
|
320
320
|
}
|
|
321
321
|
// Build full response object
|
|
322
322
|
const status = this.finishReason === 'length' ? 'incomplete' : 'completed';
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
status,
|
|
327
|
-
output: this.output,
|
|
328
|
-
model: this.model,
|
|
329
|
-
usage: this.usage ? (0, usage_js_1.completionsToResponsesUsage)(this.usage) : {},
|
|
330
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
331
|
-
metadata: {},
|
|
332
|
-
};
|
|
323
|
+
// 上游无 usage 时省略 usage 字段(不伪造 0,避免 Codex missing field input_tokens)
|
|
324
|
+
const responsesUsage = (0, usage_js_1.completionsToResponsesUsage)(this.usage);
|
|
325
|
+
const responseObj = Object.assign({ id: this.responseId, object: 'response', status, output: this.output, model: this.model, created_at: Math.floor(Date.now() / 1000), metadata: {} }, (responsesUsage ? { usage: responsesUsage } : {}));
|
|
333
326
|
if (status === 'incomplete') {
|
|
334
327
|
responseObj.incomplete_details = { reason: 'max_output_tokens' };
|
|
335
328
|
}
|
|
@@ -8,6 +8,7 @@ exports.claudeToCompletionsUsage = claudeToCompletionsUsage;
|
|
|
8
8
|
exports.geminiToClaudeUsage = geminiToClaudeUsage;
|
|
9
9
|
exports.claudeToGeminiUsage = claudeToGeminiUsage;
|
|
10
10
|
exports.responsesToClaudeUsage = responsesToClaudeUsage;
|
|
11
|
+
exports.toResponsesUsage = toResponsesUsage;
|
|
11
12
|
exports.completionsToResponsesUsage = completionsToResponsesUsage;
|
|
12
13
|
/** Map OpenAI Chat usage to Claude usage */
|
|
13
14
|
function completionsToClaudeUsage(usage) {
|
|
@@ -69,14 +70,33 @@ function responsesToClaudeUsage(usage) {
|
|
|
69
70
|
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
/**
|
|
74
|
+
* 构造标准 Responses API usage 对象(转换层兼容入口)。
|
|
75
|
+
*
|
|
76
|
+
* 真实值优先:input_tokens ?? prompt_tokens、output_tokens ?? completion_tokens、total_tokens。
|
|
77
|
+
* 覆盖上游 chat completions / claude / gemini 三种格式的字段命名归一。
|
|
78
|
+
*
|
|
79
|
+
* 仅当上游确实提供了任意 token 字段时返回归一化对象;否则返回 null(调用方应省略 usage 字段,
|
|
80
|
+
* 不伪造 0)。这是避免 Codex `ResponseCompleted: missing field input_tokens` 的关键——
|
|
81
|
+
* 既不吐空 `{}`,也不吐伪造的 `{0,0,0}`。
|
|
82
|
+
*/
|
|
83
|
+
function toResponsesUsage(usage) {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
if (!usage || typeof usage !== 'object')
|
|
86
|
+
return null;
|
|
87
|
+
const input_tokens = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : usage.prompt_tokens;
|
|
88
|
+
const output_tokens = (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : usage.completion_tokens;
|
|
89
|
+
const total_tokens = usage.total_tokens;
|
|
90
|
+
// 上游没返回任何 token 字段 → 不伪造,返回 null
|
|
91
|
+
if (input_tokens == null && output_tokens == null && total_tokens == null)
|
|
92
|
+
return null;
|
|
77
93
|
return {
|
|
78
|
-
input_tokens:
|
|
79
|
-
output_tokens:
|
|
80
|
-
total_tokens:
|
|
94
|
+
input_tokens: input_tokens !== null && input_tokens !== void 0 ? input_tokens : 0,
|
|
95
|
+
output_tokens: output_tokens !== null && output_tokens !== void 0 ? output_tokens : 0,
|
|
96
|
+
total_tokens: total_tokens !== null && total_tokens !== void 0 ? total_tokens : ((input_tokens !== null && input_tokens !== void 0 ? input_tokens : 0) + (output_tokens !== null && output_tokens !== void 0 ? output_tokens : 0)),
|
|
81
97
|
};
|
|
82
98
|
}
|
|
99
|
+
/** Map OpenAI Chat usage to Responses API usage(薄封装,保持现有调用方不变) */
|
|
100
|
+
function completionsToResponsesUsage(usage) {
|
|
101
|
+
return toResponsesUsage(usage);
|
|
102
|
+
}
|
package/dist/server/main.js
CHANGED
|
@@ -383,7 +383,7 @@ const DEFAULT_CODEX_REASONING_EFFORT = 'high';
|
|
|
383
383
|
const isCodexReasoningEffort = (value) => {
|
|
384
384
|
return typeof value === 'string' && VALID_CODEX_REASONING_EFFORTS.includes(value);
|
|
385
385
|
};
|
|
386
|
-
const writeCodexConfig = (_dbManager_1, ...args_1) => __awaiter(void 0, [_dbManager_1, ...args_1], void 0, function* (_dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT, codexDefaultModel, options = {}) {
|
|
386
|
+
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 = {}) {
|
|
387
387
|
var _a;
|
|
388
388
|
try {
|
|
389
389
|
const homeDir = os_1.default.homedir();
|
|
@@ -450,6 +450,17 @@ const writeCodexConfig = (_dbManager_1, ...args_1) => __awaiter(void 0, [_dbMana
|
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
};
|
|
453
|
+
// 记忆功能配置
|
|
454
|
+
if (enableMemories) {
|
|
455
|
+
proxyConfig.features = {
|
|
456
|
+
memories: true,
|
|
457
|
+
};
|
|
458
|
+
proxyConfig.memories = {
|
|
459
|
+
generate_memories: true,
|
|
460
|
+
use_memories: true,
|
|
461
|
+
disable_on_external_context: true,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
453
464
|
// 使用智能合并
|
|
454
465
|
const mergedConfig = (0, config_merge_1.mergeTomlConfig)(proxyConfig, currentConfig, config_managed_fields_1.CODEX_CONFIG_MANAGED_FIELDS);
|
|
455
466
|
// 原子性写入合并后的配置
|
|
@@ -681,7 +692,7 @@ const syncConfigsOnServerStartup = (dbManager) => __awaiter(void 0, void 0, void
|
|
|
681
692
|
const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
|
|
682
693
|
? config.codexModelReasoningEffort
|
|
683
694
|
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
684
|
-
const codexWritten = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel);
|
|
695
|
+
const codexWritten = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, config.codexEnableMemories);
|
|
685
696
|
console.log(`[Startup Config Sync] Codex config ${codexWritten ? 'written' : 'skipped'}`);
|
|
686
697
|
});
|
|
687
698
|
const syncConfigsOnGlobalConfigUpdate = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -694,7 +705,7 @@ const syncConfigsOnGlobalConfigUpdate = (dbManager) => __awaiter(void 0, void 0,
|
|
|
694
705
|
const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
|
|
695
706
|
? config.codexModelReasoningEffort
|
|
696
707
|
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
697
|
-
const codexUpdated = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, { allowOverwriteRefresh: true });
|
|
708
|
+
const codexUpdated = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, config.codexEnableMemories, { allowOverwriteRefresh: true });
|
|
698
709
|
console.log(`[Config Update Sync] Codex config ${codexUpdated ? 'written' : 'skipped'}`);
|
|
699
710
|
});
|
|
700
711
|
const getCentralSkillsDir = () => {
|
|
@@ -2038,7 +2049,11 @@ ${instruction}
|
|
|
2038
2049
|
: isCodexReasoningEffort(appConfig.codexModelReasoningEffort)
|
|
2039
2050
|
? appConfig.codexModelReasoningEffort
|
|
2040
2051
|
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
2041
|
-
const
|
|
2052
|
+
const requestedEnableMemories = req.body.enableMemories;
|
|
2053
|
+
const enableMemories = requestedEnableMemories !== undefined
|
|
2054
|
+
? !!requestedEnableMemories
|
|
2055
|
+
: !!appConfig.codexEnableMemories;
|
|
2056
|
+
const result = yield writeCodexConfig(dbManager, modelReasoningEffort, appConfig.codexDefaultModel, enableMemories);
|
|
2042
2057
|
applyWriteLocalRecords(proxyServer);
|
|
2043
2058
|
res.json(result);
|
|
2044
2059
|
})));
|
|
@@ -3288,21 +3303,23 @@ ${instruction}
|
|
|
3288
3303
|
}
|
|
3289
3304
|
}
|
|
3290
3305
|
});
|
|
3306
|
+
// listen 就绪标志:区分"启动阶段"与"运行阶段",启动期致命异常应让进程退出
|
|
3307
|
+
let listenReady = false;
|
|
3291
3308
|
const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
3292
3309
|
fs_1.default.mkdirSync(dataDir, { recursive: true });
|
|
3293
3310
|
// 自动检测数据库类型并执行迁移(如果需要)
|
|
3294
|
-
console.
|
|
3311
|
+
console.time('[Server] step "database-init"');
|
|
3295
3312
|
const dbManager = yield database_factory_1.DatabaseFactory.createAuto(dataDir, legacyDataDir);
|
|
3296
|
-
console.
|
|
3313
|
+
console.timeEnd('[Server] step "database-init"');
|
|
3297
3314
|
// 服务启动时自动同步配置文件(适用于 CLI 和 dev:server)
|
|
3298
|
-
console.
|
|
3315
|
+
console.time('[Server] step "sync-configs"');
|
|
3299
3316
|
try {
|
|
3300
3317
|
yield syncConfigsOnServerStartup(dbManager);
|
|
3301
|
-
console.log('[Server] Tool config sync completed');
|
|
3302
3318
|
}
|
|
3303
3319
|
catch (error) {
|
|
3304
3320
|
console.error('[Server] Tool config sync failed:', error);
|
|
3305
3321
|
}
|
|
3322
|
+
console.timeEnd('[Server] step "sync-configs"');
|
|
3306
3323
|
// 清理旧的迁移临时文件
|
|
3307
3324
|
try {
|
|
3308
3325
|
(0, session_launcher_1.cleanupOldTempFiles)();
|
|
@@ -3328,8 +3345,10 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
3328
3345
|
// Initialize proxy server and register proxy routes last
|
|
3329
3346
|
proxyServer.initialize();
|
|
3330
3347
|
// Register admin routes first
|
|
3348
|
+
console.time('[Server] step "register-routes"');
|
|
3331
3349
|
yield registerRoutes(dbManager, proxyServer);
|
|
3332
3350
|
yield proxyServer.registerProxyRoutes();
|
|
3351
|
+
console.timeEnd('[Server] step "register-routes"');
|
|
3333
3352
|
app.use(express_1.default.static(path_1.default.resolve(__dirname, '../ui')));
|
|
3334
3353
|
// 404 处理程序 - 确保返回 JSON 而不是 HTML(放在所有路由和静态文件之后)
|
|
3335
3354
|
app.use((_req, res) => {
|
|
@@ -3347,14 +3366,28 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
3347
3366
|
console.error(`端口 ${port} 已被占用,无法启动服务。请执行 aicos stop 后重启。`);
|
|
3348
3367
|
process.exit(1);
|
|
3349
3368
|
}
|
|
3369
|
+
console.time('[Server] step "listen"');
|
|
3350
3370
|
const server = app.listen(port, host, () => {
|
|
3371
|
+
listenReady = true;
|
|
3351
3372
|
console.log(`Admin server running on http://${host}:${port}`);
|
|
3373
|
+
console.timeEnd('[Server] step "listen"');
|
|
3352
3374
|
// 启动后异步执行延迟维护任务(分片校验/修复、日志清理、会话索引构建)
|
|
3353
3375
|
// 不阻塞服务启动,后台静默执行
|
|
3354
3376
|
dbManager.deferredMaintenance().catch(err => {
|
|
3355
3377
|
console.error('[Server] Deferred maintenance error:', err);
|
|
3356
3378
|
});
|
|
3357
3379
|
});
|
|
3380
|
+
// 显式处理 listen 错误(EADDRINUSE/权限不足等),打印明确日志并退出,
|
|
3381
|
+
// 避免被全局 uncaughtException 静默吞掉导致"进程在但不 listen"
|
|
3382
|
+
server.on('error', (err) => {
|
|
3383
|
+
if (err.code === 'EADDRINUSE') {
|
|
3384
|
+
console.error(`[Server] 端口 ${port} 已被占用(EADDRINUSE)。请执行 aicos stop 后重启,或更换端口(PORT 环境变量)。`);
|
|
3385
|
+
}
|
|
3386
|
+
else {
|
|
3387
|
+
console.error('[Server] 监听失败:', err);
|
|
3388
|
+
}
|
|
3389
|
+
setImmediate(() => process.exit(1));
|
|
3390
|
+
});
|
|
3358
3391
|
// 创建 WebSocket 服务器用于工具安装
|
|
3359
3392
|
const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
|
|
3360
3393
|
// 设置黑名单检查函数,用于在规则状态同步时检查黑名单是否已过期
|
|
@@ -3435,11 +3468,19 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
3435
3468
|
process.on('uncaughtException', (error) => {
|
|
3436
3469
|
console.error('[Uncaught Exception] 服务遇到未捕获的异常:', error);
|
|
3437
3470
|
console.error('[Uncaught Exception] 堆栈信息:', error.stack);
|
|
3438
|
-
//
|
|
3471
|
+
// 启动阶段(listen 之前)的异常通常是致命的(依赖加载失败、初始化崩溃等),
|
|
3472
|
+
// 静默吞掉会导致"进程在但不 listen",Tauri 只能干等超时;此时退出让上层重新探测/诊断。
|
|
3473
|
+
if (!listenReady) {
|
|
3474
|
+
console.error('[Uncaught Exception] 发生在服务监听之前,退出进程');
|
|
3475
|
+
process.exit(1);
|
|
3476
|
+
}
|
|
3439
3477
|
});
|
|
3440
3478
|
process.on('unhandledRejection', (reason) => {
|
|
3441
3479
|
console.error('[Unhandled Rejection] 服务遇到未处理的 Promise 拒绝:', reason);
|
|
3442
|
-
|
|
3480
|
+
if (!listenReady) {
|
|
3481
|
+
console.error('[Unhandled Rejection] 发生在服务监听之前,退出进程');
|
|
3482
|
+
process.exit(1);
|
|
3483
|
+
}
|
|
3443
3484
|
});
|
|
3444
3485
|
start().catch((error) => {
|
|
3445
3486
|
console.error('Failed to start server:', error);
|
|
@@ -22,7 +22,7 @@ const os_1 = require("os");
|
|
|
22
22
|
function which(cmd) {
|
|
23
23
|
try {
|
|
24
24
|
const command = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
|
|
25
|
-
(0, child_process_1.execSync)(command, { stdio: 'ignore' });
|
|
25
|
+
(0, child_process_1.execSync)(command, { stdio: 'ignore', windowsHide: true }); // 隐藏 Windows 命令行窗口,避免检测时闪窗
|
|
26
26
|
return true;
|
|
27
27
|
}
|
|
28
28
|
catch (_a) {
|
package/dist/server/utils.js
CHANGED
|
@@ -9,13 +9,27 @@ Object.defineProperty(exports, "isLastMessageCompact", { enumerable: true, get:
|
|
|
9
9
|
Object.defineProperty(exports, "isCodexCompactRequest", { enumerable: true, get: function () { return compact_1.isCodexCompactRequest; } });
|
|
10
10
|
function checkPortUsable(port) {
|
|
11
11
|
return new Promise((resolve) => {
|
|
12
|
+
let settled = false;
|
|
12
13
|
const server = net.createConnection({ port });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
const finish = (val) => {
|
|
15
|
+
if (settled)
|
|
16
|
+
return;
|
|
17
|
+
settled = true;
|
|
18
|
+
try {
|
|
19
|
+
server.destroy();
|
|
20
|
+
}
|
|
21
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
22
|
+
resolve(val);
|
|
23
|
+
};
|
|
24
|
+
// 正常:连得上 = 端口被占;连不上(ECONNREFUSED) = 端口可用
|
|
25
|
+
server.on('connect', () => finish(false));
|
|
26
|
+
server.on('error', () => finish(true));
|
|
27
|
+
// 兜底:网络栈异常(防火墙/杀软 hook)时 connect/error 可能都不触发,
|
|
28
|
+
// 1.5s 后强制按可用处理,避免 start() 永久卡死。误判由 app.listen 的 EADDRINUSE 兜底。
|
|
29
|
+
server.setTimeout(1500);
|
|
30
|
+
server.once('timeout', () => {
|
|
31
|
+
console.warn(`[checkPortUsable] 探测端口 ${port} 超时(1.5s),按可用处理`);
|
|
32
|
+
finish(true);
|
|
19
33
|
});
|
|
20
34
|
});
|
|
21
35
|
}
|