claw-subagent-service 0.0.67 → 0.0.69
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/package.json
CHANGED
|
@@ -13,6 +13,7 @@ class MessageHandler {
|
|
|
13
13
|
this.openclawClient = new OpenClawClient(log);
|
|
14
14
|
this.nodeId = config.accountId || '';
|
|
15
15
|
this.handleNormalMessage = handleNormalMessage;
|
|
16
|
+
this._streamQueue = Promise.resolve();
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -293,27 +294,33 @@ class MessageHandler {
|
|
|
293
294
|
this.log?.warn('[MessageHandler] _sendStreamChunk skipped: isStreamingEnabled=false');
|
|
294
295
|
return;
|
|
295
296
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
297
|
+
|
|
298
|
+
// 使用队列确保流式消息片段串行发送,避免并发导致后端处理错乱
|
|
299
|
+
this._streamQueue = this._streamQueue.then(async () => {
|
|
300
|
+
try {
|
|
301
|
+
const resp = await axios.post(
|
|
302
|
+
`${this.config.apiBaseUrl}/im/api/proxy/stream/publish`,
|
|
303
|
+
{
|
|
304
|
+
fromUserId,
|
|
305
|
+
targetId,
|
|
306
|
+
content,
|
|
307
|
+
streamId,
|
|
308
|
+
isFirstChunk,
|
|
309
|
+
isLastChunk,
|
|
310
|
+
conversationType,
|
|
311
|
+
seq
|
|
312
|
+
},
|
|
313
|
+
{ timeout: 10000 }
|
|
314
|
+
);
|
|
315
|
+
this.log?.info(`[MessageHandler] _sendStreamChunk 成功: status=${resp.status}, seq=${seq}`);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
const url = `${this.config.apiBaseUrl}/im/api/proxy/stream/publish`;
|
|
318
|
+
const status = err.response?.status;
|
|
319
|
+
this.log?.warn(`[MessageHandler] 发送流式消息失败: ${err.message}, url=${url}, status=${status || 'N/A'}, seq=${seq}`);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await this._streamQueue;
|
|
317
324
|
}
|
|
318
325
|
|
|
319
326
|
parseCommand(raw, senderId) {
|
|
@@ -418,6 +418,9 @@ class OpenClawClient {
|
|
|
418
418
|
|
|
419
419
|
let fullText = '';
|
|
420
420
|
let buffer = '';
|
|
421
|
+
let lastChunkData = null;
|
|
422
|
+
let hasError = false;
|
|
423
|
+
let errorMsg = '';
|
|
421
424
|
|
|
422
425
|
const response = await axios.post(apiUrl, payload, {
|
|
423
426
|
headers,
|
|
@@ -440,23 +443,49 @@ class OpenClawClient {
|
|
|
440
443
|
|
|
441
444
|
try {
|
|
442
445
|
const data = JSON.parse(dataStr);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
446
|
+
lastChunkData = data;
|
|
447
|
+
|
|
448
|
+
// 检测流式错误块
|
|
449
|
+
if (data.error) {
|
|
450
|
+
hasError = true;
|
|
451
|
+
errorMsg = typeof data.error === 'string' ? data.error : (data.error.message || JSON.stringify(data.error));
|
|
452
|
+
this.log?.error(`[OpenClawClient] SSE chunk error: ${errorMsg}`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 尝试多个可能的内容字段,兼容不同版本 OpenClaw / 不同模型提供商
|
|
457
|
+
let delta = null;
|
|
458
|
+
const choice = data.choices?.[0];
|
|
459
|
+
if (choice) {
|
|
460
|
+
delta = choice.delta?.content
|
|
461
|
+
?? choice.message?.content
|
|
462
|
+
?? choice.text
|
|
463
|
+
?? choice.delta?.text
|
|
464
|
+
?? choice.delta?.reasoning_content
|
|
465
|
+
?? null;
|
|
466
|
+
}
|
|
467
|
+
if (delta === null || delta === undefined) {
|
|
468
|
+
delta = data.content ?? data.delta ?? data.text ?? null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (typeof delta === 'string') {
|
|
472
|
+
if (delta.length > 0) {
|
|
473
|
+
fullText += delta;
|
|
474
|
+
try {
|
|
475
|
+
await onDelta?.(delta);
|
|
476
|
+
} catch (err) {
|
|
477
|
+
reject(err);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
456
480
|
}
|
|
481
|
+
// 空字符串 delta 是合法的(无新内容块),静默跳过
|
|
482
|
+
} else if (delta !== null && delta !== undefined) {
|
|
483
|
+
this.log?.info(`[OpenClawClient] SSE 原始数据(delta非字符串): ${JSON.stringify(data).substring(0, 200)}`);
|
|
457
484
|
} else {
|
|
458
|
-
//
|
|
459
|
-
|
|
485
|
+
// 无可识别的 content 字段,仅当不包含 finish_reason 时才打印调试日志
|
|
486
|
+
if (!data.choices?.[0]?.finish_reason) {
|
|
487
|
+
this.log?.info(`[OpenClawClient] SSE 原始数据(无content): ${JSON.stringify(data).substring(0, 200)}`);
|
|
488
|
+
}
|
|
460
489
|
}
|
|
461
490
|
} catch {
|
|
462
491
|
// 忽略无法解析的 JSON 行
|
|
@@ -466,8 +495,23 @@ class OpenClawClient {
|
|
|
466
495
|
|
|
467
496
|
response.data.on('end', async () => {
|
|
468
497
|
this.log?.info(`[OpenClawClient] SSE 流结束,总长度: ${fullText.length}`);
|
|
498
|
+
|
|
499
|
+
if (hasError) {
|
|
500
|
+
reject(new Error(`OpenClaw SSE 错误: ${errorMsg}`));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (fullText.length === 0) {
|
|
505
|
+
// 兜底:某些网关会在最后一个 chunk 的 message.content 中返回完整内容
|
|
506
|
+
const lastContent = lastChunkData?.choices?.[0]?.message?.content;
|
|
507
|
+
if (typeof lastContent === 'string' && lastContent.length > 0) {
|
|
508
|
+
fullText = lastContent;
|
|
509
|
+
this.log?.info(`[OpenClawClient] 从 last chunk message.content 提取内容,长度: ${fullText.length}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
469
513
|
if (fullText.length === 0) {
|
|
470
|
-
reject(new Error('OpenClaw SSE
|
|
514
|
+
reject(new Error('OpenClaw SSE 返回空内容,可能 LLM 网络异常或模型未配置'));
|
|
471
515
|
return;
|
|
472
516
|
}
|
|
473
517
|
try {
|