@ynhcj/xiaoyi 2.2.2 → 2.2.4
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/channel.js +196 -60
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -3,6 +3,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.xiaoyiPlugin = void 0;
|
|
4
4
|
const runtime_1 = require("./runtime");
|
|
5
5
|
const file_handler_1 = require("./file-handler");
|
|
6
|
+
// Import agent event functions - will be resolved at runtime
|
|
7
|
+
// Use dynamic require for compatibility
|
|
8
|
+
let onAgentEvent = null;
|
|
9
|
+
let registerAgentRunContext = null;
|
|
10
|
+
try {
|
|
11
|
+
// Try multiple import paths
|
|
12
|
+
let agentEvents = null;
|
|
13
|
+
// Try path 1: openclaw/dist/infra/agent-events
|
|
14
|
+
try {
|
|
15
|
+
agentEvents = require("openclaw/dist/infra/agent-events");
|
|
16
|
+
console.log("XiaoYi: [AGENT EVENT] Imported from openclaw/dist/infra/agent-events");
|
|
17
|
+
}
|
|
18
|
+
catch (e1) {
|
|
19
|
+
console.log("XiaoYi: [AGENT EVENT] Path 1 failed:", e1?.message || e1);
|
|
20
|
+
// Try path 2: ../openclaw/dist/infra/agent-events
|
|
21
|
+
try {
|
|
22
|
+
agentEvents = require("../openclaw/dist/infra/agent-events");
|
|
23
|
+
console.log("XiaoYi: [AGENT EVENT] Imported from ../openclaw/dist/infra/agent-events");
|
|
24
|
+
}
|
|
25
|
+
catch (e2) {
|
|
26
|
+
console.log("XiaoYi: [AGENT EVENT] Path 2 failed:", e2?.message || e2);
|
|
27
|
+
// Try path 3: ../../openclaw/dist/infra/agent-events
|
|
28
|
+
try {
|
|
29
|
+
agentEvents = require("../../openclaw/dist/infra/agent-events");
|
|
30
|
+
console.log("XiaoYi: [AGENT EVENT] Imported from ../../openclaw/dist/infra/agent-events");
|
|
31
|
+
}
|
|
32
|
+
catch (e3) {
|
|
33
|
+
console.error("XiaoYi: [AGENT EVENT] All import paths failed:");
|
|
34
|
+
console.error(" Path 1 (openclaw/dist/infra/agent-events):", e1?.message || e1);
|
|
35
|
+
console.error(" Path 2 (../openclaw/dist/infra/agent-events):", e2?.message || e2);
|
|
36
|
+
console.error(" Path 3 (../../openclaw/dist/infra/agent-events):", e3?.message || e3);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (agentEvents) {
|
|
41
|
+
onAgentEvent = agentEvents.onAgentEvent;
|
|
42
|
+
registerAgentRunContext = agentEvents.registerAgentRunContext;
|
|
43
|
+
if (typeof onAgentEvent === "function") {
|
|
44
|
+
console.log("XiaoYi: [AGENT EVENT] ✓ onAgentEvent function imported successfully");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.error("XiaoYi: [AGENT EVENT] ✗ onAgentEvent is not a function, type:", typeof onAgentEvent);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.warn("XiaoYi: [AGENT EVENT] Could not import agent event module");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error("XiaoYi: [AGENT EVENT] Fatal import error:", error?.message || error);
|
|
56
|
+
}
|
|
6
57
|
/**
|
|
7
58
|
* XiaoYi Channel Plugin
|
|
8
59
|
* Implements OpenClaw ChannelPlugin interface for XiaoYi A2A protocol
|
|
@@ -307,15 +358,18 @@ exports.xiaoyiPlugin = {
|
|
|
307
358
|
SenderId: senderId,
|
|
308
359
|
OriginatingChannel: "xiaoyi",
|
|
309
360
|
};
|
|
310
|
-
// Use the correct API to dispatch the message (streaming
|
|
361
|
+
// Use the correct API to dispatch the message (streaming via onAgentEvent)
|
|
311
362
|
try {
|
|
312
363
|
const streamingEnabled = resolvedAccount.config.enableStreaming === true;
|
|
364
|
+
const hasAgentEventSupport = typeof onAgentEvent === "function";
|
|
313
365
|
console.log("\n" + "=".repeat(60));
|
|
314
366
|
console.log(`XiaoYi: [STREAMING] Starting message processing`);
|
|
315
367
|
console.log(` Session: ${message.sessionId}`);
|
|
316
368
|
console.log(` Task ID: ${message.params.id}`);
|
|
317
369
|
console.log(` User input: ${bodyText.substring(0, 50)}${bodyText.length > 50 ? "..." : ""}`);
|
|
318
|
-
console.log(` Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED
|
|
370
|
+
console.log(` Config Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED"}`);
|
|
371
|
+
console.log(` Agent Event Support: ${hasAgentEventSupport ? "AVAILABLE" : "NOT AVAILABLE"}`);
|
|
372
|
+
console.log(` Effective Streaming: ${streamingEnabled && hasAgentEventSupport ? "ENABLED (via onAgentEvent)" : "DISABLED"}`);
|
|
319
373
|
console.log("=".repeat(60) + "\n");
|
|
320
374
|
// Track response state for streaming
|
|
321
375
|
let accumulatedText = "";
|
|
@@ -323,23 +377,145 @@ exports.xiaoyiPlugin = {
|
|
|
323
377
|
let frameCount = 0;
|
|
324
378
|
let partialCount = 0;
|
|
325
379
|
const startTime = Date.now();
|
|
380
|
+
// Register agent event listener for streaming (NEW METHOD)
|
|
381
|
+
let unsubscribeAgentEvents;
|
|
382
|
+
let runId;
|
|
383
|
+
let isCompleted = false;
|
|
384
|
+
if (streamingEnabled && hasAgentEventSupport) {
|
|
385
|
+
// Subscribe to agent events BEFORE dispatching the message
|
|
386
|
+
unsubscribeAgentEvents = onAgentEvent((evt) => {
|
|
387
|
+
if (isCompleted)
|
|
388
|
+
return;
|
|
389
|
+
// Get runId from first event
|
|
390
|
+
if (!runId && evt.runId) {
|
|
391
|
+
runId = evt.runId;
|
|
392
|
+
console.log(`XiaoYi: [AGENT EVENT] Registered runId: ${runId}`);
|
|
393
|
+
}
|
|
394
|
+
// Only process events for this run
|
|
395
|
+
if (runId && evt.runId !== runId)
|
|
396
|
+
return;
|
|
397
|
+
const elapsed = Date.now() - startTime;
|
|
398
|
+
// Handle streaming text events
|
|
399
|
+
if (evt.stream === "assistant" && typeof evt.data?.text === "string") {
|
|
400
|
+
const newText = evt.data.text;
|
|
401
|
+
if (!newText)
|
|
402
|
+
return;
|
|
403
|
+
// Calculate delta text
|
|
404
|
+
const previousLength = accumulatedText.length;
|
|
405
|
+
accumulatedText = newText;
|
|
406
|
+
const deltaText = newText.slice(previousLength);
|
|
407
|
+
console.log(`\n>>> XiaoYi: [AGENT EVENT] Stream frame #${++partialCount} at ${elapsed}ms`);
|
|
408
|
+
console.log(` Run ID: ${evt.runId}`);
|
|
409
|
+
console.log(` Delta length: ${deltaText.length} chars`);
|
|
410
|
+
console.log(` Total length: ${newText.length} chars`);
|
|
411
|
+
console.log(` Delta content: "${deltaText}"`);
|
|
412
|
+
// Send PARTIAL frame with delta content
|
|
413
|
+
const partialResponse = {
|
|
414
|
+
sessionId: message.sessionId,
|
|
415
|
+
messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
416
|
+
timestamp: Date.now(),
|
|
417
|
+
agentId: resolvedAccount.config.agentId,
|
|
418
|
+
sender: {
|
|
419
|
+
id: resolvedAccount.config.agentId,
|
|
420
|
+
name: "OpenClaw Agent",
|
|
421
|
+
type: "agent",
|
|
422
|
+
},
|
|
423
|
+
content: {
|
|
424
|
+
type: "text",
|
|
425
|
+
text: deltaText || newText,
|
|
426
|
+
},
|
|
427
|
+
status: "processing",
|
|
428
|
+
};
|
|
429
|
+
const conn = runtime.getConnection();
|
|
430
|
+
if (conn) {
|
|
431
|
+
conn.sendResponse(partialResponse, taskId, message.sessionId, false, true)
|
|
432
|
+
.then(() => {
|
|
433
|
+
console.log(` ✓ SENT (isFinal=false, append=true)\n`);
|
|
434
|
+
})
|
|
435
|
+
.catch((err) => {
|
|
436
|
+
console.error(` ✗ Send failed: ${err}\n`);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Handle lifecycle events (completion/error)
|
|
441
|
+
if (evt.stream === "lifecycle") {
|
|
442
|
+
const phase = evt.data?.phase;
|
|
443
|
+
if (phase === "end") {
|
|
444
|
+
isCompleted = true;
|
|
445
|
+
console.log("\n" + "-".repeat(60));
|
|
446
|
+
console.log(`XiaoYi: [AGENT EVENT] Lifecycle phase: end`);
|
|
447
|
+
console.log(` Run ID: ${evt.runId}`);
|
|
448
|
+
console.log(` Elapsed: ${elapsed}ms`);
|
|
449
|
+
console.log(` Total frames: ${partialCount}`);
|
|
450
|
+
console.log(` Final length: ${accumulatedText.length} chars`);
|
|
451
|
+
console.log("-".repeat(60) + "\n");
|
|
452
|
+
// Send FINAL frame
|
|
453
|
+
const response = {
|
|
454
|
+
sessionId: message.sessionId,
|
|
455
|
+
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
456
|
+
timestamp: Date.now(),
|
|
457
|
+
agentId: resolvedAccount.config.agentId,
|
|
458
|
+
sender: {
|
|
459
|
+
id: resolvedAccount.config.agentId,
|
|
460
|
+
name: "OpenClaw Agent",
|
|
461
|
+
type: "agent",
|
|
462
|
+
},
|
|
463
|
+
content: {
|
|
464
|
+
type: "text",
|
|
465
|
+
text: accumulatedText,
|
|
466
|
+
},
|
|
467
|
+
status: "success",
|
|
468
|
+
};
|
|
469
|
+
const conn = runtime.getConnection();
|
|
470
|
+
if (conn) {
|
|
471
|
+
conn.sendResponse(response, taskId, message.sessionId, true, false)
|
|
472
|
+
.then(() => {
|
|
473
|
+
console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false)\n`);
|
|
474
|
+
})
|
|
475
|
+
.catch((err) => {
|
|
476
|
+
console.error(`✗ Final send failed: ${err}\n`);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
// Unsubscribe from events
|
|
480
|
+
if (unsubscribeAgentEvents) {
|
|
481
|
+
unsubscribeAgentEvents();
|
|
482
|
+
unsubscribeAgentEvents = undefined;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else if (phase === "error") {
|
|
486
|
+
isCompleted = true;
|
|
487
|
+
console.error(`XiaoYi: [AGENT EVENT] Error phase:`, evt.data?.error);
|
|
488
|
+
// Unsubscribe from events
|
|
489
|
+
if (unsubscribeAgentEvents) {
|
|
490
|
+
unsubscribeAgentEvents();
|
|
491
|
+
unsubscribeAgentEvents = undefined;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
console.log(`XiaoYi: [AGENT EVENT] Listener registered for streaming`);
|
|
497
|
+
}
|
|
498
|
+
else if (streamingEnabled && !hasAgentEventSupport) {
|
|
499
|
+
console.warn(`XiaoYi: [WARN] Streaming enabled but onAgentEvent not available, falling back to non-streaming mode`);
|
|
500
|
+
}
|
|
326
501
|
await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
327
502
|
ctx: msgContext,
|
|
328
503
|
cfg: config,
|
|
329
504
|
dispatcherOptions: {
|
|
330
505
|
deliver: async (payload) => {
|
|
506
|
+
// NOTE: onAgentEvent does NOT emit "assistant" stream events in production
|
|
507
|
+
// Only lifecycle and tool events are emitted
|
|
508
|
+
// So we must handle the final result here
|
|
331
509
|
const elapsed = Date.now() - startTime;
|
|
332
510
|
const completeText = payload.text || "";
|
|
333
511
|
accumulatedText = completeText;
|
|
334
512
|
console.log("\n" + "-".repeat(60));
|
|
335
|
-
console.log(`XiaoYi: [
|
|
513
|
+
console.log(`XiaoYi: [DELIVER] Final result received`);
|
|
336
514
|
console.log(` Elapsed: ${elapsed}ms`);
|
|
337
|
-
console.log(`
|
|
338
|
-
console.log(`
|
|
339
|
-
console.log(` Partial frames sent: ${partialCount}`);
|
|
340
|
-
console.log(` Content preview: ${completeText.substring(0, 100)}...`);
|
|
515
|
+
console.log(` Length: ${completeText.length} chars`);
|
|
516
|
+
console.log(` Streaming frames sent: ${partialCount}`);
|
|
341
517
|
console.log("-".repeat(60) + "\n");
|
|
342
|
-
// Send FINAL frame
|
|
518
|
+
// Send FINAL frame
|
|
343
519
|
const response = {
|
|
344
520
|
sessionId: message.sessionId,
|
|
345
521
|
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
@@ -352,74 +528,34 @@ exports.xiaoyiPlugin = {
|
|
|
352
528
|
},
|
|
353
529
|
content: {
|
|
354
530
|
type: "text",
|
|
355
|
-
text: accumulatedText,
|
|
531
|
+
text: accumulatedText,
|
|
356
532
|
},
|
|
357
533
|
status: "success",
|
|
358
534
|
};
|
|
359
535
|
const conn = runtime.getConnection();
|
|
360
536
|
if (conn) {
|
|
361
|
-
// Use append=false for final frame (complete content)
|
|
362
537
|
await conn.sendResponse(response, taskId, message.sessionId, true, false);
|
|
363
|
-
console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false
|
|
538
|
+
console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false)\n`);
|
|
364
539
|
}
|
|
365
540
|
},
|
|
366
541
|
onIdle: async () => {
|
|
367
542
|
const elapsed = Date.now() - startTime;
|
|
368
543
|
console.log("\n" + "=".repeat(60));
|
|
369
|
-
console.log(`XiaoYi: [
|
|
544
|
+
console.log(`XiaoYi: [IDLE] Processing complete`);
|
|
370
545
|
console.log(` Total time: ${elapsed}ms`);
|
|
371
|
-
console.log(`
|
|
372
|
-
console.log(` Partial frames: ${partialCount}`);
|
|
546
|
+
console.log(` Streaming frames: ${partialCount}`);
|
|
373
547
|
console.log(` Final length: ${accumulatedText.length} chars`);
|
|
374
548
|
console.log("=".repeat(60) + "\n");
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
onPartialReply: async (payload) => {
|
|
381
|
-
const elapsed = Date.now() - startTime;
|
|
382
|
-
const newText = payload.text || "";
|
|
383
|
-
if (!newText) {
|
|
384
|
-
console.log(`XiaoYi: [STREAMING] Skipping empty partial response at ${elapsed}ms`);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
// Calculate delta text (增量部分)
|
|
388
|
-
const previousLength = accumulatedText.length;
|
|
389
|
-
accumulatedText = newText;
|
|
390
|
-
const deltaText = newText.slice(previousLength);
|
|
391
|
-
console.log(`\n>>> XiaoYi: [PARTIAL] Frame #${++partialCount} at ${elapsed}ms`);
|
|
392
|
-
console.log(` Previous length: ${previousLength}`);
|
|
393
|
-
console.log(` New length: ${newText.length}`);
|
|
394
|
-
console.log(` Delta length: ${deltaText.length}`);
|
|
395
|
-
console.log(` Delta content: "${deltaText}"`);
|
|
396
|
-
console.log(` Accumulated so far: "${newText}"`);
|
|
397
|
-
// Send PARTIAL frame with delta content
|
|
398
|
-
const partialResponse = {
|
|
399
|
-
sessionId: message.sessionId,
|
|
400
|
-
messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
401
|
-
timestamp: Date.now(),
|
|
402
|
-
agentId: resolvedAccount.config.agentId,
|
|
403
|
-
sender: {
|
|
404
|
-
id: resolvedAccount.config.agentId,
|
|
405
|
-
name: "OpenClaw Agent",
|
|
406
|
-
type: "agent",
|
|
407
|
-
},
|
|
408
|
-
content: {
|
|
409
|
-
type: "text",
|
|
410
|
-
text: deltaText || newText, // Send delta only (or full if delta is empty)
|
|
411
|
-
},
|
|
412
|
-
status: "processing", // Mark as processing
|
|
413
|
-
};
|
|
414
|
-
const conn = runtime.getConnection();
|
|
415
|
-
if (conn) {
|
|
416
|
-
// Use append=true for streaming frames (服务端会追加)
|
|
417
|
-
await conn.sendResponse(partialResponse, taskId, message.sessionId, false, true);
|
|
418
|
-
console.log(` ✓ SENT (isFinal=false, append=true)\n`);
|
|
549
|
+
// Cleanup event listener if still active
|
|
550
|
+
if (unsubscribeAgentEvents && !isCompleted) {
|
|
551
|
+
console.log(`XiaoYi: [IDLE] Cleaning up event listener`);
|
|
552
|
+
unsubscribeAgentEvents();
|
|
553
|
+
unsubscribeAgentEvents = undefined;
|
|
419
554
|
}
|
|
420
555
|
},
|
|
421
|
-
}
|
|
422
|
-
|
|
556
|
+
},
|
|
557
|
+
replyOptions: undefined, // onPartialReply is not used
|
|
558
|
+
images: images.length > 0 ? images : undefined,
|
|
423
559
|
});
|
|
424
560
|
}
|
|
425
561
|
catch (error) {
|