aicodeswitch 2.0.10 → 2.0.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/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### 2.0.11 (2026-02-03)
6
+
5
7
  ### 2.0.10 (2026-02-03)
6
8
 
7
9
  ### 2.0.9 (2026-02-02)
package/CLAUDE.md CHANGED
@@ -139,7 +139,7 @@ aicos version # Show current version information
139
139
  - `image-understanding`: Requests with image content
140
140
  - `thinking`: Requests with reasoning/thinking signals
141
141
  - `long-context`: Requests with large context (≥12000 chars or ≥8000 max tokens)
142
- - `background`: Background/priority requests
142
+ - `background`: Background/priority requests, including `/count_tokens` endpoint requests and token counting requests with `{"role": "user", "content": "count"}`
143
143
  - `default`: All other requests
144
144
 
145
145
  ### Request Transformation
@@ -164,6 +164,9 @@ aicos version # Show current version information
164
164
  - Request logs: Detailed API call records with token usage
165
165
  - Access logs: System access records
166
166
  - Error logs: Error and exception records (includes upstream request information when available)
167
+ - **Data Sanitization**:
168
+ - Sensitive authentication fields (api_key, authorization, password, secret, etc.) are automatically masked in the UI
169
+ - Technical fields like `max_tokens`, `input_tokens`, `output_tokens` are NOT masked - they are legitimate API parameters
167
170
  - **Session Management**:
168
171
  - Tracks user sessions based on session ID (Claude Code: `metadata.user_id`, Codex: `headers.session_id`)
169
172
  - Auto-generates session title from first user message content:
@@ -25,6 +25,9 @@ const os_1 = __importDefault(require("os"));
25
25
  const auth_1 = require("./auth");
26
26
  const version_check_1 = require("./version-check");
27
27
  const utils_1 = require("./utils");
28
+ const tools_service_1 = require("./tools-service");
29
+ const websocket_service_1 = require("./websocket-service");
30
+ const rules_status_service_1 = require("./rules-status-service");
28
31
  const config_metadata_1 = require("./config-metadata");
29
32
  const config_1 = require("./config");
30
33
  const appDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch');
@@ -1356,6 +1359,19 @@ ${instruction}
1356
1359
  res.json({ success: false });
1357
1360
  }
1358
1361
  })));
1362
+ // 工具安装检测相关路由
1363
+ app.get('/api/tools/status', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
1364
+ console.log('[API] GET /api/tools/status - 获取工具安装状态');
1365
+ try {
1366
+ const status = yield (0, tools_service_1.getToolsInstallationStatus)();
1367
+ console.log('[API] 工具安装状态:', status);
1368
+ res.json(status);
1369
+ }
1370
+ catch (error) {
1371
+ console.error('[API] 获取工具状态失败:', error);
1372
+ res.status(500).json({ error: '获取工具状态失败' });
1373
+ }
1374
+ })));
1359
1375
  };
1360
1376
  const start = () => __awaiter(void 0, void 0, void 0, function* () {
1361
1377
  fs_1.default.mkdirSync(dataDir, { recursive: true });
@@ -1377,6 +1393,28 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
1377
1393
  const server = app.listen(port, host, () => {
1378
1394
  console.log(`Admin server running on http://${host}:${port}`);
1379
1395
  });
1396
+ // 创建 WebSocket 服务器用于工具安装
1397
+ const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
1398
+ // 创建 WebSocket 服务器用于规则状态
1399
+ const rulesStatusWss = (0, rules_status_service_1.createRulesStatusWSServer)();
1400
+ // 将 WebSocket 服务器附加到 HTTP 服务器
1401
+ server.on('upgrade', (request, socket, head) => {
1402
+ if (request.url === '/api/tools/install') {
1403
+ toolInstallWss.handleUpgrade(request, socket, head, (ws) => {
1404
+ toolInstallWss.emit('connection', ws, request);
1405
+ });
1406
+ }
1407
+ else if (request.url === '/api/rules/status') {
1408
+ rulesStatusWss.handleUpgrade(request, socket, head, (ws) => {
1409
+ rulesStatusWss.emit('connection', ws, request);
1410
+ });
1411
+ }
1412
+ else {
1413
+ socket.destroy();
1414
+ }
1415
+ });
1416
+ console.log(`WebSocket server for tool installation attached to ws://${host}:${port}/api/tools/install`);
1417
+ console.log(`WebSocket server for rules status attached to ws://${host}:${port}/api/rules/status`);
1380
1418
  const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
1381
1419
  console.log('Shutting down server...');
1382
1420
  dbManager.close();
@@ -51,6 +51,7 @@ const stream_1 = require("stream");
51
51
  const crypto_1 = __importDefault(require("crypto"));
52
52
  const streaming_1 = require("./transformers/streaming");
53
53
  const chunk_collector_1 = require("./transformers/chunk-collector");
54
+ const rules_status_service_1 = require("./rules-status-service");
54
55
  const claude_openai_1 = require("./transformers/claude-openai");
55
56
  const SUPPORTED_TARGETS = ['claude-code', 'codex'];
56
57
  class ProxyServer {
@@ -762,6 +763,10 @@ class ProxyServer {
762
763
  const body = req.body;
763
764
  if (!body)
764
765
  return 'default';
766
+ // 检查是否为 count_tokens 请求(后台类型)
767
+ if (req.path.includes('/count_tokens')) {
768
+ return 'background';
769
+ }
765
770
  const explicitType = this.getExplicitContentType(req, body);
766
771
  if (explicitType) {
767
772
  return explicitType;
@@ -890,6 +895,17 @@ class ProxyServer {
890
895
  }
891
896
  hasBackgroundSignal(body) {
892
897
  var _a, _b, _c;
898
+ // 检测 count tokens 请求:messages 只有一条,role 为 "user",content 为 "count"
899
+ const messages = body === null || body === void 0 ? void 0 : body.messages;
900
+ if (Array.isArray(messages) && messages.length === 1) {
901
+ const firstMessage = messages[0];
902
+ if ((firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.role) === 'user' &&
903
+ ((firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.content) === 'count' ||
904
+ (typeof (firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.content) === 'string' && firstMessage.content.trim() === 'count'))) {
905
+ return true;
906
+ }
907
+ }
908
+ // 检测其他后台信号
893
909
  const candidates = [
894
910
  body === null || body === void 0 ? void 0 : body.background,
895
911
  (_a = body === null || body === void 0 ? void 0 : body.metadata) === null || _a === void 0 ? void 0 : _a.background,
@@ -1359,6 +1375,8 @@ class ProxyServer {
1359
1375
  let streamChunksForLog;
1360
1376
  let upstreamRequestForLog;
1361
1377
  let actuallyUsedProxy = false; // 标记是否实际使用了代理
1378
+ // 标记规则正在使用
1379
+ rules_status_service_1.rulesStatusBroadcaster.markRuleInUse(route.id, rule.id);
1362
1380
  const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
1363
1381
  var _a, _b;
1364
1382
  if (logged)
@@ -1428,6 +1446,11 @@ class ProxyServer {
1428
1446
  const totalTokens = (usageForLog.inputTokens || 0) + (usageForLog.outputTokens || 0);
1429
1447
  if (totalTokens > 0) {
1430
1448
  this.dbManager.incrementRuleTokenUsage(rule.id, totalTokens);
1449
+ // 获取更新后的规则数据并广播
1450
+ const updatedRule = this.dbManager.getRule(rule.id);
1451
+ if (updatedRule) {
1452
+ rules_status_service_1.rulesStatusBroadcaster.broadcastUsageUpdate(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
1453
+ }
1431
1454
  }
1432
1455
  }
1433
1456
  // 更新规则的请求次数(只在成功请求时更新)
@@ -1437,6 +1460,11 @@ class ProxyServer {
1437
1460
  // 检查是否是重复请求(如网络重试)
1438
1461
  if (!this.isRequestProcessed(requestHash)) {
1439
1462
  this.dbManager.incrementRuleRequestCount(rule.id, 1);
1463
+ // 获取更新后的规则数据并广播
1464
+ const updatedRule = this.dbManager.getRule(rule.id);
1465
+ if (updatedRule) {
1466
+ rules_status_service_1.rulesStatusBroadcaster.broadcastUsageUpdate(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
1467
+ }
1440
1468
  }
1441
1469
  // 定期清理过期缓存
1442
1470
  if (Math.random() < 0.01) { // 1%概率清理,避免每次都清理
@@ -1698,7 +1726,6 @@ class ProxyServer {
1698
1726
  });
1699
1727
  res.on('finish', () => {
1700
1728
  streamChunksForLog = eventCollector.getChunks();
1701
- console.log('[Proxy] Default stream request finished, collected chunks:', (streamChunksForLog === null || streamChunksForLog === void 0 ? void 0 : streamChunksForLog.length) || 0);
1702
1729
  // 尝试从event collector中提取usage信息
1703
1730
  const extractedUsage = eventCollector.extractUsage();
1704
1731
  if (extractedUsage) {
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RulesStatusWS = exports.rulesStatusBroadcaster = exports.RulesStatusBroadcaster = void 0;
4
+ exports.createRulesStatusWSServer = createRulesStatusWSServer;
5
+ // @ts-ignore - ws 类型声明可能需要手动安装 @types/ws
6
+ const ws_1 = require("ws");
7
+ /**
8
+ * 规则状态 WebSocket 连接管理
9
+ */
10
+ class RulesStatusWS {
11
+ constructor(ws, req) {
12
+ Object.defineProperty(this, "ws", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: void 0
17
+ });
18
+ this.ws = ws;
19
+ console.log(`[RulesStatusWS] 新的 WebSocket 连接: ${req.socket.remoteAddress}`);
20
+ this.ws.on('close', () => {
21
+ console.log(`[RulesStatusWS] WebSocket 连接关闭`);
22
+ });
23
+ this.ws.on('error', (err) => {
24
+ console.error(`[RulesStatusWS] WebSocket 错误:`, err);
25
+ });
26
+ }
27
+ /**
28
+ * 发送规则状态消息到客户端
29
+ */
30
+ sendStatus(message) {
31
+ if (this.ws.readyState === ws_1.WebSocket.OPEN) {
32
+ this.ws.send(JSON.stringify(message));
33
+ }
34
+ }
35
+ }
36
+ exports.RulesStatusWS = RulesStatusWS;
37
+ /**
38
+ * 规则状态广播服务
39
+ * 管理所有连接的客户端,负责广播规则使用状态
40
+ */
41
+ class RulesStatusBroadcaster {
42
+ constructor() {
43
+ Object.defineProperty(this, "clients", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: new Set()
48
+ });
49
+ Object.defineProperty(this, "activeRules", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: new Map()
54
+ }); // routeId -> Set of ruleIds
55
+ Object.defineProperty(this, "ruleTimeouts", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: new Map()
60
+ });
61
+ Object.defineProperty(this, "INACTIVITY_TIMEOUT", {
62
+ enumerable: true,
63
+ configurable: true,
64
+ writable: true,
65
+ value: 30000
66
+ }); // 30秒无活动后标记为空闲
67
+ }
68
+ /**
69
+ * 添加客户端
70
+ */
71
+ addClient(client) {
72
+ this.clients.add(client);
73
+ console.log(`[RulesStatusBroadcaster] 客户端已连接,当前客户端数: ${this.clients.size}`);
74
+ }
75
+ /**
76
+ * 移除客户端
77
+ */
78
+ removeClient(client) {
79
+ this.clients.delete(client);
80
+ console.log(`[RulesStatusBroadcaster] 客户端已断开,当前客户端数: ${this.clients.size}`);
81
+ }
82
+ /**
83
+ * 标记规则正在使用
84
+ */
85
+ markRuleInUse(routeId, ruleId) {
86
+ // 添加到活动规则集合
87
+ if (!this.activeRules.has(routeId)) {
88
+ this.activeRules.set(routeId, new Set());
89
+ }
90
+ this.activeRules.get(routeId).add(ruleId);
91
+ // 清除之前的超时定时器
92
+ const timeoutKey = `${routeId}:${ruleId}`;
93
+ const existingTimeout = this.ruleTimeouts.get(timeoutKey);
94
+ if (existingTimeout) {
95
+ clearTimeout(existingTimeout);
96
+ }
97
+ // 广播状态
98
+ this.broadcastStatus({
99
+ type: 'rule_status',
100
+ data: {
101
+ ruleId,
102
+ status: 'in_use',
103
+ timestamp: Date.now(),
104
+ },
105
+ });
106
+ // 设置超时定时器,如果30秒内没有新活动则标记为空闲
107
+ const timeout = setTimeout(() => {
108
+ this.markRuleIdle(routeId, ruleId);
109
+ }, this.INACTIVITY_TIMEOUT);
110
+ this.ruleTimeouts.set(timeoutKey, timeout);
111
+ }
112
+ /**
113
+ * 标记规则空闲
114
+ */
115
+ markRuleIdle(routeId, ruleId) {
116
+ const timeoutKey = `${routeId}:${ruleId}`;
117
+ // 清除超时定时器
118
+ const existingTimeout = this.ruleTimeouts.get(timeoutKey);
119
+ if (existingTimeout) {
120
+ clearTimeout(existingTimeout);
121
+ this.ruleTimeouts.delete(timeoutKey);
122
+ }
123
+ // 从活动规则集合中移除
124
+ if (this.activeRules.has(routeId)) {
125
+ this.activeRules.get(routeId).delete(ruleId);
126
+ if (this.activeRules.get(routeId).size === 0) {
127
+ this.activeRules.delete(routeId);
128
+ }
129
+ }
130
+ // 广播空闲状态
131
+ this.broadcastStatus({
132
+ type: 'rule_status',
133
+ data: {
134
+ ruleId,
135
+ status: 'idle',
136
+ timestamp: Date.now(),
137
+ },
138
+ });
139
+ }
140
+ /**
141
+ * 广播规则使用量更新
142
+ */
143
+ broadcastUsageUpdate(ruleId, totalTokensUsed, totalRequestsUsed) {
144
+ this.broadcastStatus({
145
+ type: 'rule_status',
146
+ data: {
147
+ ruleId,
148
+ status: 'in_use',
149
+ totalTokensUsed,
150
+ totalRequestsUsed,
151
+ timestamp: Date.now(),
152
+ },
153
+ });
154
+ }
155
+ /**
156
+ * 广播消息到所有客户端
157
+ */
158
+ broadcastStatus(message) {
159
+ const deadClients = [];
160
+ this.clients.forEach((client) => {
161
+ try {
162
+ client.sendStatus(message);
163
+ }
164
+ catch (error) {
165
+ console.error('[RulesStatusBroadcaster] 发送消息失败:', error);
166
+ deadClients.push(client);
167
+ }
168
+ });
169
+ // 清理断开的客户端
170
+ deadClients.forEach((client) => {
171
+ this.removeClient(client);
172
+ });
173
+ }
174
+ /**
175
+ * 获取当前活动的规则列表
176
+ */
177
+ getActiveRules() {
178
+ return new Map(this.activeRules);
179
+ }
180
+ }
181
+ exports.RulesStatusBroadcaster = RulesStatusBroadcaster;
182
+ // 全局单例
183
+ exports.rulesStatusBroadcaster = new RulesStatusBroadcaster();
184
+ /**
185
+ * 创建 WebSocket 服务器用于规则状态
186
+ */
187
+ function createRulesStatusWSServer() {
188
+ const wss = new ws_1.WebSocketServer({ noServer: true });
189
+ wss.on('connection', (ws, req) => {
190
+ const wsHandler = new RulesStatusWS(ws, req);
191
+ exports.rulesStatusBroadcaster.addClient(wsHandler);
192
+ ws.on('close', () => {
193
+ exports.rulesStatusBroadcaster.removeClient(wsHandler);
194
+ });
195
+ });
196
+ return wss;
197
+ }
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getToolsInstallationStatus = getToolsInstallationStatus;
16
+ exports.installTool = installTool;
17
+ const child_process_1 = require("child_process");
18
+ const os_1 = __importDefault(require("os"));
19
+ /**
20
+ * 检测工具是否已安装
21
+ */
22
+ function checkToolInstalled(toolName) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ console.log(`[ToolsService] 开始检测工具: ${toolName}`);
25
+ return new Promise((resolve) => {
26
+ var _a, _b;
27
+ const command = toolName === 'claude-code' ? 'claude' : 'codex';
28
+ console.log(`[ToolsService] 执行命令: ${command} --version`);
29
+ const child = (0, child_process_1.spawn)(command, ['--version'], {
30
+ shell: true,
31
+ stdio: ['ignore', 'pipe', 'pipe'],
32
+ });
33
+ let stdout = '';
34
+ let stderr = '';
35
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
36
+ stdout += data.toString();
37
+ console.log(`[ToolsService] ${toolName} stdout:`, data.toString());
38
+ });
39
+ (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
40
+ stderr += data.toString();
41
+ console.log(`[ToolsService] ${toolName} stderr:`, data.toString());
42
+ });
43
+ child.on('close', (code) => {
44
+ console.log(`[ToolsService] ${toolName} 进程退出,退出码: ${code}`);
45
+ if (code === 0) {
46
+ // 尝试从输出中提取版本号
47
+ const versionMatch = stdout.match(/(\d+\.\d+\.\d+)/) || stderr.match(/(\d+\.\d+\.\d+)/);
48
+ resolve({
49
+ installed: true,
50
+ version: versionMatch ? versionMatch[1] : 'unknown',
51
+ });
52
+ }
53
+ else {
54
+ resolve({ installed: false });
55
+ }
56
+ });
57
+ child.on('error', (err) => {
58
+ console.log(`[ToolsService] ${toolName} 检测出错:`, err);
59
+ resolve({ installed: false });
60
+ });
61
+ // 10秒超时
62
+ setTimeout(() => {
63
+ console.log(`[ToolsService] ${toolName} 检测超时`);
64
+ child.kill();
65
+ resolve({ installed: false });
66
+ }, 10000);
67
+ });
68
+ });
69
+ }
70
+ /**
71
+ * 获取工具的安装命令
72
+ */
73
+ function getInstallCommand(toolName) {
74
+ const platform = os_1.default.platform();
75
+ const tool = toolName === 'claude-code' ? '@anthropic-ai/claude-code' : '@openai/codex';
76
+ if (platform === 'win32') {
77
+ return `npm install -g ${tool}`;
78
+ }
79
+ else {
80
+ // macOS 和 Linux 需要 sudo
81
+ return `sudo npm install -g ${tool}`;
82
+ }
83
+ }
84
+ /**
85
+ * 检测所有工具的安装状态
86
+ */
87
+ function getToolsInstallationStatus() {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ const [claudeCodeStatus, codexStatus] = yield Promise.all([
90
+ checkToolInstalled('claude-code'),
91
+ checkToolInstalled('codex'),
92
+ ]);
93
+ return {
94
+ claudeCode: Object.assign(Object.assign({}, claudeCodeStatus), { installCommand: claudeCodeStatus.installed ? undefined : getInstallCommand('claude-code') }),
95
+ codex: Object.assign(Object.assign({}, codexStatus), { installCommand: codexStatus.installed ? undefined : getInstallCommand('codex') }),
96
+ };
97
+ });
98
+ }
99
+ /**
100
+ * 执行工具安装
101
+ */
102
+ function installTool(toolName, callbacks) {
103
+ var _a, _b;
104
+ const command = getInstallCommand(toolName);
105
+ const platform = os_1.default.platform();
106
+ console.log(`[ToolsService] ========== 开始安装 ${toolName} ==========`);
107
+ console.log(`[ToolsService] 操作系统: ${platform}`);
108
+ console.log(`[ToolsService] 安装命令: ${command}`);
109
+ // 立即发送启动消息,让前端快速进入 terminal 界面
110
+ callbacks.onData(`\n========== 开始安装 ${toolName} ==========\n`);
111
+ callbacks.onData(`操作系统: ${platform}\n`);
112
+ callbacks.onData(`执行命令: ${command}\n`);
113
+ callbacks.onData(`进程启动中...\n\n`);
114
+ // 在 macOS/Linux 上,需要使用 sudo 并且可能需要输入密码
115
+ // 在 Windows 上直接使用 cmd 执行 npm 命令
116
+ let child;
117
+ if (platform === 'win32') {
118
+ // Windows: 使用 cmd.exe 执行命令
119
+ console.log(`[ToolsService] Windows 环境,使用 cmd.exe`);
120
+ child = (0, child_process_1.spawn)('cmd.exe', ['/c', command], {
121
+ stdio: ['pipe', 'pipe', 'pipe'],
122
+ shell: false,
123
+ windowsHide: true, // 隐藏命令行窗口
124
+ });
125
+ }
126
+ else {
127
+ // Unix: 使用 sh 执行命令
128
+ console.log(`[ToolsService] Unix 环境,使用 sh`);
129
+ child = (0, child_process_1.spawn)('sh', ['-c', command], {
130
+ stdio: ['pipe', 'pipe', 'pipe'],
131
+ shell: false,
132
+ });
133
+ }
134
+ console.log(`[ToolsService] 子进程 PID: ${child.pid}`);
135
+ // 立即发送进程信息
136
+ callbacks.onData(`子进程已创建 (PID: ${child.pid})\n`);
137
+ callbacks.onData(`等待 npm 输出...\n\n`);
138
+ // 设置超时:30分钟后自动终止
139
+ const timeout = 30 * 60 * 1000;
140
+ const timeoutHandle = setTimeout(() => {
141
+ console.error(`[ToolsService] ${toolName} 安装超时 (${timeout}ms),终止进程`);
142
+ child.kill('SIGTERM');
143
+ callbacks.onError(`\n安装超时 (${timeout / 60000} 分钟),请检查网络连接或手动执行命令:\n${command}\n`);
144
+ }, timeout);
145
+ let hasReceivedData = false;
146
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
147
+ const output = data.toString();
148
+ if (!hasReceivedData) {
149
+ console.log(`[ToolsService] ${toolName} 首次收到 stdout 数据`);
150
+ hasReceivedData = true;
151
+ }
152
+ console.log(`[ToolsService] ${toolName} stdout:`, output.slice(0, 200)); // 只记录前200字符
153
+ callbacks.onData(output);
154
+ });
155
+ (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
156
+ const output = data.toString();
157
+ if (!hasReceivedData) {
158
+ console.log(`[ToolsService] ${toolName} 首次收到 stderr 数据`);
159
+ hasReceivedData = true;
160
+ }
161
+ console.log(`[ToolsService] ${toolName} stderr:`, output.slice(0, 200)); // 只记录前200字符
162
+ callbacks.onError(output);
163
+ });
164
+ child.on('close', (code) => {
165
+ clearTimeout(timeoutHandle);
166
+ console.log(`[ToolsService] ${toolName} 安装进程关闭,退出码: ${code}`);
167
+ if (code === 0) {
168
+ callbacks.onData(`\n========== 安装完成 ==========\n`);
169
+ }
170
+ else if (code === null) {
171
+ callbacks.onError(`\n========== 安装被终止 ==========\n`);
172
+ }
173
+ else {
174
+ callbacks.onError(`\n========== 安装失败 (退出码: ${code}) ==========\n`);
175
+ }
176
+ callbacks.onClose(code);
177
+ });
178
+ child.on('error', (err) => {
179
+ clearTimeout(timeoutHandle);
180
+ console.error(`[ToolsService] ${toolName} 安装进程错误:`, err);
181
+ callbacks.onError(`启动安装进程失败: ${err.message}\n`);
182
+ callbacks.onError(`错误详情: ${err}\n`);
183
+ callbacks.onClose(null);
184
+ });
185
+ // 处理进程退出信号
186
+ child.on('exit', (_code, signal) => {
187
+ clearTimeout(timeoutHandle);
188
+ if (signal) {
189
+ console.log(`[ToolsService] ${toolName} 进程被信号终止: ${signal}`);
190
+ callbacks.onError(`\n安装进程被信号终止: ${signal}\n`);
191
+ }
192
+ });
193
+ // 如果10秒后还没有收到任何数据,发送提示
194
+ setTimeout(() => {
195
+ if (!hasReceivedData) {
196
+ console.log(`[ToolsService] ${toolName} 10秒内未收到输出,发送等待提示`);
197
+ callbacks.onData(`[提示] 正在连接 npm 服务器,这可能需要一些时间...\n`);
198
+ callbacks.onData(`[提示] 如果持续无响应,请检查网络连接或 npm 配置\n\n`);
199
+ }
200
+ }, 10000);
201
+ return child;
202
+ }