@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/README.md +82 -0
- package/dist/cli.js +95 -27
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +48 -55
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +536 -173
- package/dist/index.js.map +1 -1
- package/package.json +29 -7
- package/src/cli.ts +0 -127
- package/src/index.ts +0 -318
- package/tsconfig.json +0 -18
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
|
|
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.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
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
|
-
|
|
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.
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.
|
|
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
|
|
284
|
+
// ============================================================
|
|
285
|
+
// 消息处理
|
|
286
|
+
// ============================================================
|
|
287
|
+
async handleGatewayMessage(data) {
|
|
112
288
|
try {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
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.
|
|
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', `❌ 平台错误: ${
|
|
308
|
+
this.log('error', `❌ 平台错误: ${message.message}`);
|
|
128
309
|
break;
|
|
129
310
|
default:
|
|
130
|
-
this.log('debug',
|
|
311
|
+
this.log('debug', `📥 收到消息: ${message.type}`);
|
|
131
312
|
}
|
|
132
313
|
}
|
|
133
314
|
catch (error) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
166
|
-
this.log('
|
|
167
|
-
this.
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
this.
|
|
189
|
-
throw error;
|
|
542
|
+
else {
|
|
543
|
+
this.messageQueue.push(message);
|
|
190
544
|
}
|
|
191
545
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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();
|