@agentmarketpro/connector 1.0.1 → 2.0.0

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/index.js CHANGED
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -6,233 +39,533 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.Connector = void 0;
7
40
  exports.startConnector = startConnector;
8
41
  const ws_1 = __importDefault(require("ws"));
9
- const child_process_1 = require("child_process");
42
+ const http = __importStar(require("http"));
43
+ const https = __importStar(require("https"));
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
47
+ // ============================================================
48
+ // 工具函数
49
+ // ============================================================
50
+ /** 查找 OpenClaw 配置文件路径 */
51
+ function findOpenClawConfig() {
52
+ const home = os.homedir();
53
+ const candidates = [
54
+ path.join(home, '.openclaw', 'openclaw.json'),
55
+ path.join(home, '.config', 'openclaw', 'openclaw.json'),
56
+ ];
57
+ if (process.platform === 'win32') {
58
+ const appData = process.env.APPDATA;
59
+ if (appData) {
60
+ candidates.push(path.join(appData, 'openclaw', 'openclaw.json'));
61
+ }
62
+ }
63
+ for (const p of candidates) {
64
+ if (fs.existsSync(p))
65
+ return p;
66
+ }
67
+ return null;
68
+ }
69
+ /** 读取 OpenClaw 配置 */
70
+ function loadOpenClawConfig() {
71
+ const configPath = findOpenClawConfig();
72
+ if (!configPath)
73
+ return null;
74
+ try {
75
+ const raw = fs.readFileSync(configPath, 'utf-8');
76
+ const config = JSON.parse(raw);
77
+ config._path = configPath;
78
+ return config;
79
+ }
80
+ catch (e) {
81
+ const msg = e instanceof Error ? e.message : String(e);
82
+ console.log(`⚠️ 读取 OpenClaw 配置失败: ${msg}`);
83
+ return null;
84
+ }
85
+ }
86
+ /** 检查并启用 OpenClaw HTTP API */
87
+ function ensureHttpApiEnabled(config) {
88
+ if (!config || !config._path)
89
+ return false;
90
+ const gw = config.gateway || {};
91
+ const enabled = gw.http?.endpoints?.chatCompletions?.enabled;
92
+ if (enabled === true) {
93
+ console.log('✅ OpenClaw HTTP API 已启用');
94
+ return false;
95
+ }
96
+ console.log('🔧 OpenClaw HTTP API 未启用,正在自动配置...');
97
+ if (!config.gateway)
98
+ config.gateway = {};
99
+ if (!config.gateway.http)
100
+ config.gateway.http = {};
101
+ if (!config.gateway.http.endpoints)
102
+ config.gateway.http.endpoints = {};
103
+ if (!config.gateway.http.endpoints.chatCompletions)
104
+ config.gateway.http.endpoints.chatCompletions = {};
105
+ config.gateway.http.endpoints.chatCompletions.enabled = true;
106
+ try {
107
+ const backupPath = config._path + '.bak';
108
+ fs.copyFileSync(config._path, backupPath);
109
+ const toWrite = { ...config };
110
+ delete toWrite._path;
111
+ fs.writeFileSync(config._path, JSON.stringify(toWrite, null, 2), 'utf-8');
112
+ console.log('✅ 已自动启用 HTTP API 并保存配置');
113
+ console.log(` 备份: ${backupPath}`);
114
+ return true;
115
+ }
116
+ catch (e) {
117
+ const msg = e instanceof Error ? e.message : String(e);
118
+ console.log(`❌ 写入配置失败: ${msg}`);
119
+ console.log(' 请手动在 openclaw.json 的 gateway 节点添加:');
120
+ console.log(' "http": { "endpoints": { "chatCompletions": { "enabled": true } } }');
121
+ return false;
122
+ }
123
+ }
124
+ /** 解析自定义 API URL */
125
+ function parseApiUrl(url) {
126
+ const parsed = new URL(url);
127
+ let apiPath = parsed.pathname.replace(/\/$/, '');
128
+ if (!apiPath.endsWith('/chat/completions')) {
129
+ apiPath += '/chat/completions';
130
+ }
131
+ return {
132
+ protocol: parsed.protocol,
133
+ hostname: parsed.hostname,
134
+ port: parseInt(parsed.port) || (parsed.protocol === 'https:' ? 443 : 80),
135
+ path: apiPath,
136
+ };
137
+ }
138
+ // ============================================================
139
+ // Connector 核心类
140
+ // ============================================================
10
141
  class Connector {
11
142
  constructor(config) {
12
143
  this.ws = null;
13
- this.reconnectTimer = null;
14
- this.pingTimer = null;
15
- this.config = {
16
- openclawPort: 18789,
17
- logLevel: 'info',
18
- ...config,
19
- };
144
+ this.reconnectInterval = 5000;
145
+ this.heartbeatTimer = null;
146
+ this.isConnected = false;
147
+ this.isAiReady = false;
148
+ this.messageQueue = [];
149
+ this.parsedApiUrl = null;
150
+ // OpenClaw 配置
151
+ this.openclawConfig = null;
152
+ this.config = config;
153
+ this.isCustomMode = !!config.apiUrl;
154
+ this.model = config.model || 'openclaw:main';
155
+ this.openclawPort = config.openclawPort || 18789;
156
+ this.openclawToken = config.openclawToken || '';
157
+ // 自定义后端模式:解析 URL
158
+ if (this.isCustomMode && config.apiUrl) {
159
+ try {
160
+ this.parsedApiUrl = parseApiUrl(config.apiUrl);
161
+ }
162
+ catch {
163
+ throw new Error(`无效的 API URL: ${config.apiUrl}`);
164
+ }
165
+ }
20
166
  }
21
- /**
22
- * 设置消息处理函数
23
- */
167
+ /** 设置消息处理函数(高级用法,覆盖默认 AI 调用) */
24
168
  onMessage(handler) {
25
169
  this.messageHandler = handler;
26
170
  return this;
27
171
  }
28
- /**
29
- * 设置状态变化回调
30
- */
172
+ /** 设置状态变化回调 */
31
173
  onStatusChange(handler) {
32
174
  this.statusHandler = handler;
33
175
  return this;
34
176
  }
35
- /**
36
- * 设置错误处理
37
- */
177
+ /** 设置错误处理回调 */
38
178
  onError(handler) {
39
179
  this.errorHandler = handler;
40
180
  return this;
41
181
  }
42
- /**
43
- * 启动连接器
44
- */
182
+ /** 启动连接器 */
45
183
  async start() {
46
- this.log('info', '🚀 启动连接器...');
47
- this.log('info', ` 平台: ${this.config.platformUrl}`);
48
- this.log('info', ` 连接ID: ${this.config.connectionId}`);
184
+ // OpenClaw 模式:初始化配置
185
+ if (!this.isCustomMode) {
186
+ await this.initOpenClawConfig();
187
+ }
188
+ this.printStartInfo();
49
189
  this.connect();
50
190
  }
51
- /**
52
- * 停止连接器
53
- */
191
+ /** 停止连接器 */
54
192
  async stop() {
55
- this.log('info', '🛑 停止连接器...');
56
- if (this.pingTimer) {
57
- clearInterval(this.pingTimer);
58
- this.pingTimer = null;
59
- }
60
- if (this.reconnectTimer) {
61
- clearTimeout(this.reconnectTimer);
62
- this.reconnectTimer = null;
63
- }
193
+ this.log('info', '🛑 正在关闭...');
194
+ this.stopHeartbeat();
64
195
  if (this.ws) {
65
196
  this.ws.close();
66
197
  this.ws = null;
67
198
  }
199
+ this.isConnected = false;
68
200
  this.statusHandler?.({ connected: false });
69
201
  }
70
- /**
71
- * 连接到平台
72
- */
202
+ // ============================================================
203
+ // OpenClaw 配置初始化
204
+ // ============================================================
205
+ async initOpenClawConfig() {
206
+ this.openclawConfig = loadOpenClawConfig();
207
+ if (this.openclawConfig) {
208
+ this.log('info', `📂 找到 OpenClaw 配置: ${this.openclawConfig._path}`);
209
+ }
210
+ else {
211
+ this.log('info', '⚠️ 未找到 OpenClaw 配置文件');
212
+ }
213
+ // 端口:命令行 > 配置文件 > 默认
214
+ if (!this.config.openclawPort) {
215
+ this.openclawPort = this.openclawConfig?.gateway?.port || 18789;
216
+ }
217
+ // Token:命令行 > 环境变量 > 配置文件
218
+ if (!this.openclawToken) {
219
+ this.openclawToken =
220
+ process.env.OPENCLAW_TOKEN ||
221
+ this.openclawConfig?.gateway?.auth?.token ||
222
+ '';
223
+ }
224
+ if (!this.openclawToken) {
225
+ throw new Error('无法获取 OpenClaw Token。\n' +
226
+ '解决方法:\n' +
227
+ ' 1. 确保 OpenClaw 已安装并运行过(会生成配置文件)\n' +
228
+ ' 2. 或手动指定: --openclaw-token <YOUR_TOKEN>\n' +
229
+ ' 3. 或设置环境变量: OPENCLAW_TOKEN=<YOUR_TOKEN>\n' +
230
+ ' 4. 或使用自定义后端: --api-url <URL> --model <MODEL>');
231
+ }
232
+ // 自动启用 HTTP API
233
+ if (this.openclawConfig) {
234
+ const configChanged = ensureHttpApiEnabled(this.openclawConfig);
235
+ if (configChanged) {
236
+ throw new Error('配置已更新,请重启 OpenClaw 后重新运行此连接器。\n' +
237
+ '运行: openclaw restart');
238
+ }
239
+ }
240
+ }
241
+ // ============================================================
242
+ // WebSocket 连接
243
+ // ============================================================
73
244
  connect() {
74
- const wsUrl = this.config.platformUrl.replace(/^http/, 'ws') + '/ws/connector';
75
- this.ws = new ws_1.default(wsUrl, {
76
- headers: {
77
- 'X-Connection-ID': this.config.connectionId,
78
- 'X-Token': this.config.token,
79
- },
80
- });
81
- this.ws.on('open', () => {
82
- this.log('info', '✅ 已连接到平台');
83
- this.statusHandler?.({ connected: true, agentId: this.config.connectionId });
84
- // 启动心跳
85
- this.pingTimer = setInterval(() => {
86
- this.sendPing();
87
- }, 30000);
88
- });
89
- this.ws.on('message', (data) => {
90
- this.handleMessage(data);
91
- });
92
- this.ws.on('close', () => {
93
- this.log('info', '🔌 连接断开');
94
- this.statusHandler?.({ connected: false });
95
- // 清理心跳
96
- if (this.pingTimer) {
97
- clearInterval(this.pingTimer);
98
- this.pingTimer = null;
99
- }
100
- // 自动重连
245
+ const wsUrl = this.config.gatewayUrl || 'wss://www.agentmarketpro.ai/ws/connector';
246
+ this.log('info', '🔗 正在连接到平台 Gateway...');
247
+ try {
248
+ this.ws = new ws_1.default(wsUrl, {
249
+ headers: {
250
+ 'X-Connection-ID': this.config.connectionId,
251
+ 'X-Token': this.config.token,
252
+ },
253
+ });
254
+ this.ws.on('open', () => {
255
+ this.log('info', '✅ 已连接到 Gateway');
256
+ this.isConnected = true;
257
+ this.statusHandler?.({ connected: true, agentId: this.config.connectionId });
258
+ this.startHeartbeat();
259
+ // 发送队列中的消息
260
+ while (this.messageQueue.length > 0) {
261
+ const msg = this.messageQueue.shift();
262
+ this.send(msg);
263
+ }
264
+ });
265
+ this.ws.on('message', (data) => this.handleGatewayMessage(data));
266
+ this.ws.on('close', (code) => {
267
+ this.log('info', `🔌 Gateway 连接断开 (code: ${code})`);
268
+ this.isConnected = false;
269
+ this.statusHandler?.({ connected: false });
270
+ this.stopHeartbeat();
271
+ this.scheduleReconnect();
272
+ });
273
+ this.ws.on('error', (error) => {
274
+ this.log('error', `❌ Gateway 连接错误: ${error.message}`);
275
+ this.errorHandler?.(error);
276
+ });
277
+ }
278
+ catch (error) {
279
+ const msg = error instanceof Error ? error.message : String(error);
280
+ this.log('error', `❌ 连接失败: ${msg}`);
101
281
  this.scheduleReconnect();
102
- });
103
- this.ws.on('error', (error) => {
104
- this.log('error', `❌ 连接错误: ${error.message}`);
105
- this.errorHandler?.(error);
106
- });
282
+ }
107
283
  }
108
- /**
109
- * 处理收到的消息
110
- */
111
- async handleMessage(data) {
284
+ // ============================================================
285
+ // 消息处理
286
+ // ============================================================
287
+ async handleGatewayMessage(data) {
112
288
  try {
113
- const msg = JSON.parse(data.toString());
114
- // 处理不同类型的消息
115
- switch (msg.type) {
289
+ const message = JSON.parse(data.toString());
290
+ switch (message.type) {
291
+ case 'connected':
292
+ this.log('info', '');
293
+ this.log('info', '═══════════════════════════════════════════════════════════════');
294
+ this.log('info', '✅ Agent 已在线');
295
+ this.log('info', '═══════════════════════════════════════════════════════════════');
296
+ this.log('info', '');
297
+ await this.checkAiBackend();
298
+ break;
116
299
  case 'ping':
117
- this.ws?.send(JSON.stringify({ type: 'pong' }));
300
+ this.send({ type: 'pong', timestamp: Date.now() });
118
301
  break;
119
302
  case 'pong':
120
- // 心跳响应
121
303
  break;
122
304
  case 'message':
123
- // 收到用户消息,需要处理并回复
124
- await this.processMessage(msg.payload);
305
+ await this.handleUserMessage(message.payload);
125
306
  break;
126
307
  case 'error':
127
- this.log('error', `❌ 平台错误: ${msg.message}`);
308
+ this.log('error', `❌ 平台错误: ${message.message}`);
128
309
  break;
129
310
  default:
130
- this.log('debug', `📨 收到未知消息: ${msg.type}`);
311
+ this.log('debug', `📥 收到消息: ${message.type}`);
131
312
  }
132
313
  }
133
314
  catch (error) {
134
- this.log('error', `❌ 消息解析失败: ${error}`);
135
- }
136
- }
137
- /**
138
- * 处理用户消息
139
- */
140
- async processMessage(payload) {
141
- const { sessionId, content, messageId, senderId, senderName } = payload;
142
- this.log('info', `\n📨 收到消息:`);
143
- this.log('info', ` 会话: ${sessionId}`);
144
- this.log('info', ` 用户: ${senderName || 'Unknown'}`);
145
- this.log('info', ` 内容: ${content.substring(0, 50)}...`);
146
- try {
147
- let reply;
148
- if (this.messageHandler) {
149
- // 使用自定义处理函数
150
- reply = await this.messageHandler({
151
- sessionId,
152
- content,
153
- senderId,
154
- senderName,
155
- });
315
+ const msg = error instanceof Error ? error.message : String(error);
316
+ this.log('error', `❌ 消息解析错误: ${msg}`);
317
+ }
318
+ }
319
+ /** 检测 AI 后端可用性 */
320
+ async checkAiBackend() {
321
+ const ok = await this.checkApiAvailable();
322
+ if (ok) {
323
+ const backend = this.isCustomMode
324
+ ? this.config.apiUrl
325
+ : `localhost:${this.openclawPort}`;
326
+ this.log('info', `✅ AI 后端就绪: ${backend}`);
327
+ this.isAiReady = true;
328
+ }
329
+ else {
330
+ this.log('info', '⚠️ AI 后端不可用,30秒后重试');
331
+ setTimeout(() => this.checkAiBackend(), 30000);
332
+ }
333
+ }
334
+ /** 检查 API 是否可用 */
335
+ checkApiAvailable() {
336
+ return new Promise((resolve) => {
337
+ if (this.isCustomMode && this.parsedApiUrl) {
338
+ const mod = this.parsedApiUrl.protocol === 'https:' ? https : http;
339
+ const req = mod.get(`${this.parsedApiUrl.protocol}//${this.parsedApiUrl.hostname}:${this.parsedApiUrl.port}/`, () => resolve(true));
340
+ req.on('error', () => resolve(false));
341
+ req.setTimeout(10000, () => { req.destroy(); resolve(false); });
156
342
  }
157
343
  else {
158
- // 默认调用 OpenClaw
159
- reply = await this.callOpenClaw(content, sessionId);
344
+ const req = http.get(`http://localhost:${this.openclawPort}/`, () => resolve(true));
345
+ req.on('error', () => resolve(false));
346
+ req.setTimeout(10000, () => { req.destroy(); resolve(false); });
347
+ }
348
+ });
349
+ }
350
+ /** 处理用户消息 */
351
+ async handleUserMessage(payload) {
352
+ const { sessionId, messageId, content, senderId, senderName } = payload;
353
+ this.log('info', '');
354
+ this.log('info', `📥 用户消息 [${sessionId}]: ${content}`);
355
+ // 如果有自定义消息处理函数
356
+ if (this.messageHandler) {
357
+ try {
358
+ const reply = await this.messageHandler({ sessionId, messageId, content, senderId, senderName });
359
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: reply } });
360
+ return;
361
+ }
362
+ catch (error) {
363
+ const msg = error instanceof Error ? error.message : String(error);
364
+ this.log('error', `❌ 自定义处理失败: ${msg}`);
365
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: '抱歉,处理失败,请重试。' } });
366
+ return;
160
367
  }
161
- this.log('info', ` 回复: ${reply.substring(0, 50)}...`);
162
- // 发送回复
163
- this.sendReply(sessionId, messageId, reply);
164
368
  }
165
- catch (error) {
166
- this.log('error', `❌ 处理消息失败: ${error}`);
167
- this.sendReply(sessionId, messageId, '抱歉,我暂时无法处理你的请求。');
369
+ if (!this.isAiReady) {
370
+ this.log('info', '⚠️ AI 后端未就绪,返回错误');
371
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: '抱歉,AI 引擎未就绪,请稍后重试。' } });
372
+ return;
168
373
  }
374
+ await this.callAiBackend(sessionId, messageId, content);
169
375
  }
170
- /**
171
- * 调用本地 OpenClaw
172
- */
173
- async callOpenClaw(message, sessionId) {
174
- try {
175
- const cleanMessage = message.replace(/"/g, '\\"');
176
- const result = (0, child_process_1.execSync)(`openclaw agent --message "${cleanMessage}" --session-id ${sessionId}`, {
177
- encoding: 'utf-8',
178
- timeout: 60000,
179
- maxBuffer: 10 * 1024 * 1024,
376
+ // ============================================================
377
+ // AI 后端调用(流式)
378
+ // ============================================================
379
+ /** 流式调用 AI 后端 */
380
+ callAiBackend(sessionId, messageId, content) {
381
+ return new Promise((resolve) => {
382
+ const postData = JSON.stringify({
383
+ model: this.model,
384
+ messages: [{ role: 'user', content }],
385
+ stream: true,
386
+ user: `agentmarket-${sessionId}`,
180
387
  });
181
- // 过滤日志行
182
- const lines = result.split('\n');
183
- const replyLines = lines.filter(line => !line.startsWith('[') && line.trim());
184
- const reply = replyLines.join('\n').trim();
185
- return reply || '收到消息';
388
+ let options;
389
+ let reqModule;
390
+ if (this.isCustomMode && this.parsedApiUrl) {
391
+ reqModule = this.parsedApiUrl.protocol === 'https:' ? https : http;
392
+ options = {
393
+ hostname: this.parsedApiUrl.hostname,
394
+ port: this.parsedApiUrl.port,
395
+ path: this.parsedApiUrl.path,
396
+ method: 'POST',
397
+ headers: {
398
+ 'Content-Type': 'application/json',
399
+ 'Content-Length': Buffer.byteLength(postData),
400
+ ...(this.config.apiKey ? { 'Authorization': `Bearer ${this.config.apiKey}` } : {}),
401
+ },
402
+ timeout: 120000,
403
+ };
404
+ }
405
+ else {
406
+ reqModule = http;
407
+ options = {
408
+ hostname: 'localhost',
409
+ port: this.openclawPort,
410
+ path: '/v1/chat/completions',
411
+ method: 'POST',
412
+ headers: {
413
+ 'Content-Type': 'application/json',
414
+ 'Authorization': `Bearer ${this.openclawToken}`,
415
+ 'Content-Length': Buffer.byteLength(postData),
416
+ },
417
+ timeout: 120000,
418
+ };
419
+ }
420
+ const req = reqModule.request(options, (res) => {
421
+ const isStream = res.headers['content-type']?.includes('text/event-stream') ||
422
+ res.headers['transfer-encoding'] === 'chunked';
423
+ if (isStream) {
424
+ this.handleStreamResponse(res, sessionId, messageId, resolve);
425
+ }
426
+ else {
427
+ this.handleNormalResponse(res, sessionId, messageId, resolve);
428
+ }
429
+ });
430
+ req.on('error', (error) => {
431
+ this.log('error', `❌ HTTP 请求失败: ${error.message}`);
432
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: '抱歉,AI 引擎连接失败,请重试。' } });
433
+ resolve();
434
+ });
435
+ req.on('timeout', () => {
436
+ req.destroy();
437
+ this.log('info', '⚠️ 请求超时');
438
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: '抱歉,处理超时,请重试。' } });
439
+ resolve();
440
+ });
441
+ req.write(postData);
442
+ req.end();
443
+ });
444
+ }
445
+ /** 处理流式响应 (SSE) */
446
+ handleStreamResponse(res, sessionId, messageId, resolve) {
447
+ let fullContent = '';
448
+ let buffer = '';
449
+ let chunkCount = 0;
450
+ const CHUNK_THRESHOLD = 50;
451
+ let lastSentLength = 0;
452
+ res.on('data', (chunk) => {
453
+ buffer += chunk.toString();
454
+ const lines = buffer.split('\n');
455
+ buffer = lines.pop() || '';
456
+ for (const line of lines) {
457
+ if (!line.startsWith('data: '))
458
+ continue;
459
+ const data = line.slice(6).trim();
460
+ if (data === '[DONE]')
461
+ continue;
462
+ try {
463
+ const parsed = JSON.parse(data);
464
+ const delta = parsed.choices?.[0]?.delta?.content;
465
+ if (delta) {
466
+ fullContent += delta;
467
+ chunkCount++;
468
+ // 定期发送中间结果(流式推送到前端)
469
+ if (fullContent.length - lastSentLength >= CHUNK_THRESHOLD) {
470
+ this.send({
471
+ type: 'stream',
472
+ payload: { sessionId, messageId, content: fullContent, done: false },
473
+ });
474
+ lastSentLength = fullContent.length;
475
+ }
476
+ }
477
+ }
478
+ catch {
479
+ // 忽略解析错误的行
480
+ }
481
+ }
482
+ });
483
+ res.on('end', () => {
484
+ fullContent = fullContent.replace(/<final>/g, '').trim();
485
+ if (fullContent) {
486
+ this.log('info', `✅ 流式回复完成 (${chunkCount} chunks): ${fullContent.substring(0, 50)}...`);
487
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: fullContent } });
488
+ }
489
+ else {
490
+ this.log('info', '⚠️ 流式响应无内容');
491
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: '抱歉,未能获取回复,请重试。' } });
492
+ }
493
+ resolve();
494
+ });
495
+ res.on('error', (error) => {
496
+ this.log('error', `❌ 流式读取错误: ${error.message}`);
497
+ if (fullContent) {
498
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: fullContent.replace(/<final>/g, '').trim() } });
499
+ }
500
+ resolve();
501
+ });
502
+ }
503
+ /** 处理普通响应(兜底,当 stream 不被支持时) */
504
+ handleNormalResponse(res, sessionId, messageId, resolve) {
505
+ let data = '';
506
+ res.on('data', (chunk) => { data += chunk; });
507
+ res.on('end', () => {
508
+ try {
509
+ const result = JSON.parse(data);
510
+ let replyContent = '';
511
+ if (result.choices?.[0]) {
512
+ replyContent = result.choices[0].message?.content || '';
513
+ }
514
+ else if (result.error) {
515
+ this.log('error', `❌ API 错误: ${JSON.stringify(result.error)}`);
516
+ replyContent = '抱歉,处理失败,请重试。';
517
+ }
518
+ replyContent = replyContent.replace(/<final>/g, '').trim();
519
+ if (replyContent) {
520
+ this.log('info', `✅ 回复: ${replyContent.substring(0, 50)}...`);
521
+ }
522
+ else {
523
+ replyContent = '抱歉,未能获取回复,请重试。';
524
+ }
525
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: replyContent } });
526
+ }
527
+ catch (e) {
528
+ const msg = e instanceof Error ? e.message : String(e);
529
+ this.log('error', `❌ 响应解析失败: ${msg}`);
530
+ this.send({ type: 'reply', payload: { sessionId, messageId, content: '抱歉,处理失败,请重试。' } });
531
+ }
532
+ resolve();
533
+ });
534
+ }
535
+ // ============================================================
536
+ // 通信与心跳
537
+ // ============================================================
538
+ send(message) {
539
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
540
+ this.ws.send(JSON.stringify(message));
186
541
  }
187
- catch (error) {
188
- this.log('error', `❌ OpenClaw 调用失败: ${error}`);
189
- throw error;
542
+ else {
543
+ this.messageQueue.push(message);
190
544
  }
191
545
  }
192
- /**
193
- * 发送回复到平台
194
- */
195
- sendReply(sessionId, messageId, reply) {
196
- if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
197
- this.log('error', '❌ 连接未建立,无法发送回复');
198
- return;
199
- }
200
- this.ws.send(JSON.stringify({
201
- type: 'reply',
202
- payload: {
203
- sessionId,
204
- messageId,
205
- content: reply,
206
- },
207
- }));
208
- this.log('info', '✅ 回复已发送');
209
- }
210
- /**
211
- * 发送心跳
212
- */
213
- sendPing() {
214
- if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
215
- this.ws.send(JSON.stringify({ type: 'ping' }));
546
+ startHeartbeat() {
547
+ this.heartbeatTimer = setInterval(() => {
548
+ if (this.isConnected) {
549
+ this.send({ type: 'ping', timestamp: Date.now() });
550
+ }
551
+ }, 25000);
552
+ }
553
+ stopHeartbeat() {
554
+ if (this.heartbeatTimer) {
555
+ clearInterval(this.heartbeatTimer);
556
+ this.heartbeatTimer = null;
216
557
  }
217
558
  }
218
- /**
219
- * 安排重连
220
- */
221
559
  scheduleReconnect() {
222
- if (this.reconnectTimer)
223
- return;
224
- this.log('info', '⏳ 5秒后尝试重连...');
225
- this.reconnectTimer = setTimeout(() => {
226
- this.reconnectTimer = null;
227
- this.connect();
228
- }, 5000);
229
- }
230
- /**
231
- * 日志
232
- */
560
+ this.log('info', `⏱️ ${this.reconnectInterval / 1000}秒后重连...`);
561
+ setTimeout(() => this.connect(), this.reconnectInterval);
562
+ }
563
+ // ============================================================
564
+ // 日志与信息
565
+ // ============================================================
233
566
  log(level, message) {
234
- const prefix = '[Connector]';
235
567
  const timestamp = new Date().toISOString();
568
+ const prefix = '[Connector]';
236
569
  if (level === 'error') {
237
570
  console.error(`${timestamp} ${prefix} ${message}`);
238
571
  }
@@ -240,11 +573,41 @@ class Connector {
240
573
  console.log(`${timestamp} ${prefix} ${message}`);
241
574
  }
242
575
  }
576
+ printStartInfo() {
577
+ const gatewayUrl = this.config.gatewayUrl || 'wss://www.agentmarketpro.ai/ws/connector';
578
+ console.log('');
579
+ console.log('╔═══════════════════════════════════════════════════════════════════╗');
580
+ console.log('║ AgentMarketPro Connector v2.0.0 ║');
581
+ console.log('╚═══════════════════════════════════════════════════════════════════╝');
582
+ console.log('');
583
+ console.log('📋 配置信息:');
584
+ console.log(` ├── 平台网关: ${gatewayUrl}`);
585
+ console.log(` ├── 连接ID: ${this.config.connectionId}`);
586
+ if (this.isCustomMode) {
587
+ const keyPreview = this.config.apiKey
588
+ ? `${this.config.apiKey.substring(0, 6)}...`
589
+ : '(无)';
590
+ console.log(` ├── AI 后端: ${this.config.apiUrl}`);
591
+ console.log(` ├── 模型: ${this.model}`);
592
+ console.log(` └── API Key: ${keyPreview}`);
593
+ }
594
+ else {
595
+ const tokenPreview = this.openclawToken.length > 12
596
+ ? `${this.openclawToken.substring(0, 8)}...${this.openclawToken.substring(this.openclawToken.length - 4)}`
597
+ : '***';
598
+ const tokenSource = this.config.openclawToken ? '命令行'
599
+ : process.env.OPENCLAW_TOKEN ? '环境变量'
600
+ : '自动读取';
601
+ console.log(` ├── OpenClaw: localhost:${this.openclawPort} (HTTP API)`);
602
+ console.log(` └── Token: ${tokenPreview} (${tokenSource})`);
603
+ }
604
+ console.log('');
605
+ }
243
606
  }
244
607
  exports.Connector = Connector;
245
- /**
246
- * 便捷启动函数
247
- */
608
+ // ============================================================
609
+ // 便捷启动函数
610
+ // ============================================================
248
611
  async function startConnector(config) {
249
612
  const connector = new Connector(config);
250
613
  await connector.start();