@ynhcj/xiaoyi 2.2.1 → 2.2.3
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 +171 -63
- package/dist/index.d.ts +7 -3
- package/dist/index.js +7 -3
- package/dist/types.d.ts +20 -1
- package/dist/types.js +4 -0
- package/dist/websocket.d.ts +71 -57
- package/dist/websocket.js +506 -313
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -3,6 +3,21 @@ 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 to import from the built openclaw package
|
|
12
|
+
const agentEvents = require("openclaw/dist/infra/agent-events");
|
|
13
|
+
onAgentEvent = agentEvents.onAgentEvent;
|
|
14
|
+
registerAgentRunContext = agentEvents.registerAgentRunContext;
|
|
15
|
+
console.log("XiaoYi: [AGENT EVENT] Successfully imported agent event functions");
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.warn("XiaoYi: [AGENT EVENT] Could not import agent event functions:", error);
|
|
19
|
+
console.warn("XiaoYi: [AGENT EVENT] Streaming will be disabled");
|
|
20
|
+
}
|
|
6
21
|
/**
|
|
7
22
|
* XiaoYi Channel Plugin
|
|
8
23
|
* Implements OpenClaw ChannelPlugin interface for XiaoYi A2A protocol
|
|
@@ -50,6 +65,8 @@ exports.xiaoyiPlugin = {
|
|
|
50
65
|
config: {
|
|
51
66
|
enabled: false,
|
|
52
67
|
wsUrl: "",
|
|
68
|
+
wsUrl1: "",
|
|
69
|
+
wsUrl2: "",
|
|
53
70
|
ak: "",
|
|
54
71
|
sk: "",
|
|
55
72
|
agentId: "",
|
|
@@ -78,7 +95,10 @@ exports.xiaoyiPlugin = {
|
|
|
78
95
|
}
|
|
79
96
|
const config = account.config;
|
|
80
97
|
// Check each field is a string and has content after trimming
|
|
81
|
-
|
|
98
|
+
// Support both old wsUrl and new wsUrl1/wsUrl2
|
|
99
|
+
const hasWsUrl = ((typeof config.wsUrl === 'string' && config.wsUrl.trim().length > 0) ||
|
|
100
|
+
(typeof config.wsUrl1 === 'string' && config.wsUrl1.trim().length > 0) ||
|
|
101
|
+
(typeof config.wsUrl2 === 'string' && config.wsUrl2.trim().length > 0));
|
|
82
102
|
const hasAk = typeof config.ak === 'string' && config.ak.trim().length > 0;
|
|
83
103
|
const hasSk = typeof config.sk === 'string' && config.sk.trim().length > 0;
|
|
84
104
|
const hasAgentId = typeof config.agentId === 'string' && config.agentId.trim().length > 0;
|
|
@@ -91,13 +111,14 @@ exports.xiaoyiPlugin = {
|
|
|
91
111
|
return "Channel is disabled in configuration";
|
|
92
112
|
},
|
|
93
113
|
unconfiguredReason: (account, cfg) => {
|
|
94
|
-
return "Missing required configuration: wsUrl, ak, sk, or agentId";
|
|
114
|
+
return "Missing required configuration: wsUrl/wsUrl1/wsUrl2, ak, sk, or agentId";
|
|
95
115
|
},
|
|
96
116
|
describeAccount: (account, cfg) => ({
|
|
97
117
|
accountId: account.accountId,
|
|
98
118
|
name: 'XiaoYi',
|
|
99
119
|
enabled: account.enabled,
|
|
100
|
-
configured: Boolean(account.config?.wsUrl
|
|
120
|
+
configured: Boolean((account.config?.wsUrl || account.config?.wsUrl1 || account.config?.wsUrl2) &&
|
|
121
|
+
account.config?.ak && account.config?.sk && account.config?.agentId),
|
|
101
122
|
}),
|
|
102
123
|
},
|
|
103
124
|
/**
|
|
@@ -301,15 +322,18 @@ exports.xiaoyiPlugin = {
|
|
|
301
322
|
SenderId: senderId,
|
|
302
323
|
OriginatingChannel: "xiaoyi",
|
|
303
324
|
};
|
|
304
|
-
// Use the correct API to dispatch the message (streaming
|
|
325
|
+
// Use the correct API to dispatch the message (streaming via onAgentEvent)
|
|
305
326
|
try {
|
|
306
327
|
const streamingEnabled = resolvedAccount.config.enableStreaming === true;
|
|
328
|
+
const hasAgentEventSupport = typeof onAgentEvent === "function";
|
|
307
329
|
console.log("\n" + "=".repeat(60));
|
|
308
330
|
console.log(`XiaoYi: [STREAMING] Starting message processing`);
|
|
309
331
|
console.log(` Session: ${message.sessionId}`);
|
|
310
332
|
console.log(` Task ID: ${message.params.id}`);
|
|
311
333
|
console.log(` User input: ${bodyText.substring(0, 50)}${bodyText.length > 50 ? "..." : ""}`);
|
|
312
|
-
console.log(` Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED
|
|
334
|
+
console.log(` Config Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED"}`);
|
|
335
|
+
console.log(` Agent Event Support: ${hasAgentEventSupport ? "AVAILABLE" : "NOT AVAILABLE"}`);
|
|
336
|
+
console.log(` Effective Streaming: ${streamingEnabled && hasAgentEventSupport ? "ENABLED (via onAgentEvent)" : "DISABLED"}`);
|
|
313
337
|
console.log("=".repeat(60) + "\n");
|
|
314
338
|
// Track response state for streaming
|
|
315
339
|
let accumulatedText = "";
|
|
@@ -317,23 +341,147 @@ exports.xiaoyiPlugin = {
|
|
|
317
341
|
let frameCount = 0;
|
|
318
342
|
let partialCount = 0;
|
|
319
343
|
const startTime = Date.now();
|
|
344
|
+
// Register agent event listener for streaming (NEW METHOD)
|
|
345
|
+
let unsubscribeAgentEvents;
|
|
346
|
+
let runId;
|
|
347
|
+
let isCompleted = false;
|
|
348
|
+
if (streamingEnabled && hasAgentEventSupport) {
|
|
349
|
+
// Subscribe to agent events BEFORE dispatching the message
|
|
350
|
+
unsubscribeAgentEvents = onAgentEvent((evt) => {
|
|
351
|
+
if (isCompleted)
|
|
352
|
+
return;
|
|
353
|
+
// Get runId from first event
|
|
354
|
+
if (!runId && evt.runId) {
|
|
355
|
+
runId = evt.runId;
|
|
356
|
+
console.log(`XiaoYi: [AGENT EVENT] Registered runId: ${runId}`);
|
|
357
|
+
}
|
|
358
|
+
// Only process events for this run
|
|
359
|
+
if (runId && evt.runId !== runId)
|
|
360
|
+
return;
|
|
361
|
+
const elapsed = Date.now() - startTime;
|
|
362
|
+
// Handle streaming text events
|
|
363
|
+
if (evt.stream === "assistant" && typeof evt.data?.text === "string") {
|
|
364
|
+
const newText = evt.data.text;
|
|
365
|
+
if (!newText)
|
|
366
|
+
return;
|
|
367
|
+
// Calculate delta text
|
|
368
|
+
const previousLength = accumulatedText.length;
|
|
369
|
+
accumulatedText = newText;
|
|
370
|
+
const deltaText = newText.slice(previousLength);
|
|
371
|
+
console.log(`\n>>> XiaoYi: [AGENT EVENT] Stream frame #${++partialCount} at ${elapsed}ms`);
|
|
372
|
+
console.log(` Run ID: ${evt.runId}`);
|
|
373
|
+
console.log(` Delta length: ${deltaText.length} chars`);
|
|
374
|
+
console.log(` Total length: ${newText.length} chars`);
|
|
375
|
+
console.log(` Delta content: "${deltaText}"`);
|
|
376
|
+
// Send PARTIAL frame with delta content
|
|
377
|
+
const partialResponse = {
|
|
378
|
+
sessionId: message.sessionId,
|
|
379
|
+
messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
380
|
+
timestamp: Date.now(),
|
|
381
|
+
agentId: resolvedAccount.config.agentId,
|
|
382
|
+
sender: {
|
|
383
|
+
id: resolvedAccount.config.agentId,
|
|
384
|
+
name: "OpenClaw Agent",
|
|
385
|
+
type: "agent",
|
|
386
|
+
},
|
|
387
|
+
content: {
|
|
388
|
+
type: "text",
|
|
389
|
+
text: deltaText || newText,
|
|
390
|
+
},
|
|
391
|
+
status: "processing",
|
|
392
|
+
};
|
|
393
|
+
const conn = runtime.getConnection();
|
|
394
|
+
if (conn) {
|
|
395
|
+
conn.sendResponse(partialResponse, taskId, message.sessionId, false, true)
|
|
396
|
+
.then(() => {
|
|
397
|
+
console.log(` ✓ SENT (isFinal=false, append=true)\n`);
|
|
398
|
+
})
|
|
399
|
+
.catch((err) => {
|
|
400
|
+
console.error(` ✗ Send failed: ${err}\n`);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Handle lifecycle events (completion/error)
|
|
405
|
+
if (evt.stream === "lifecycle") {
|
|
406
|
+
const phase = evt.data?.phase;
|
|
407
|
+
if (phase === "end") {
|
|
408
|
+
isCompleted = true;
|
|
409
|
+
console.log("\n" + "-".repeat(60));
|
|
410
|
+
console.log(`XiaoYi: [AGENT EVENT] Lifecycle phase: end`);
|
|
411
|
+
console.log(` Run ID: ${evt.runId}`);
|
|
412
|
+
console.log(` Elapsed: ${elapsed}ms`);
|
|
413
|
+
console.log(` Total frames: ${partialCount}`);
|
|
414
|
+
console.log(` Final length: ${accumulatedText.length} chars`);
|
|
415
|
+
console.log("-".repeat(60) + "\n");
|
|
416
|
+
// Send FINAL frame
|
|
417
|
+
const response = {
|
|
418
|
+
sessionId: message.sessionId,
|
|
419
|
+
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
420
|
+
timestamp: Date.now(),
|
|
421
|
+
agentId: resolvedAccount.config.agentId,
|
|
422
|
+
sender: {
|
|
423
|
+
id: resolvedAccount.config.agentId,
|
|
424
|
+
name: "OpenClaw Agent",
|
|
425
|
+
type: "agent",
|
|
426
|
+
},
|
|
427
|
+
content: {
|
|
428
|
+
type: "text",
|
|
429
|
+
text: accumulatedText,
|
|
430
|
+
},
|
|
431
|
+
status: "success",
|
|
432
|
+
};
|
|
433
|
+
const conn = runtime.getConnection();
|
|
434
|
+
if (conn) {
|
|
435
|
+
conn.sendResponse(response, taskId, message.sessionId, true, false)
|
|
436
|
+
.then(() => {
|
|
437
|
+
console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false)\n`);
|
|
438
|
+
})
|
|
439
|
+
.catch((err) => {
|
|
440
|
+
console.error(`✗ Final send failed: ${err}\n`);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
// Unsubscribe from events
|
|
444
|
+
if (unsubscribeAgentEvents) {
|
|
445
|
+
unsubscribeAgentEvents();
|
|
446
|
+
unsubscribeAgentEvents = undefined;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else if (phase === "error") {
|
|
450
|
+
isCompleted = true;
|
|
451
|
+
console.error(`XiaoYi: [AGENT EVENT] Error phase:`, evt.data?.error);
|
|
452
|
+
// Unsubscribe from events
|
|
453
|
+
if (unsubscribeAgentEvents) {
|
|
454
|
+
unsubscribeAgentEvents();
|
|
455
|
+
unsubscribeAgentEvents = undefined;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
console.log(`XiaoYi: [AGENT EVENT] Listener registered for streaming`);
|
|
461
|
+
}
|
|
462
|
+
else if (streamingEnabled && !hasAgentEventSupport) {
|
|
463
|
+
console.warn(`XiaoYi: [WARN] Streaming enabled but onAgentEvent not available, falling back to non-streaming mode`);
|
|
464
|
+
}
|
|
320
465
|
await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
321
466
|
ctx: msgContext,
|
|
322
467
|
cfg: config,
|
|
323
468
|
dispatcherOptions: {
|
|
324
469
|
deliver: async (payload) => {
|
|
470
|
+
// This is the fallback for when streaming is disabled
|
|
471
|
+
if (streamingEnabled) {
|
|
472
|
+
// Streaming mode: deliver is handled by onAgentEvent
|
|
473
|
+
console.log(`XiaoYi: [DELIVER] Skipping (handled by onAgentEvent)`);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
325
476
|
const elapsed = Date.now() - startTime;
|
|
326
477
|
const completeText = payload.text || "";
|
|
327
478
|
accumulatedText = completeText;
|
|
328
479
|
console.log("\n" + "-".repeat(60));
|
|
329
|
-
console.log(`XiaoYi: [
|
|
480
|
+
console.log(`XiaoYi: [DELIVER] Non-streaming mode`);
|
|
330
481
|
console.log(` Elapsed: ${elapsed}ms`);
|
|
331
|
-
console.log(`
|
|
332
|
-
console.log(` Total length: ${completeText.length} chars`);
|
|
333
|
-
console.log(` Partial frames sent: ${partialCount}`);
|
|
334
|
-
console.log(` Content preview: ${completeText.substring(0, 100)}...`);
|
|
482
|
+
console.log(` Length: ${completeText.length} chars`);
|
|
335
483
|
console.log("-".repeat(60) + "\n");
|
|
336
|
-
// Send
|
|
484
|
+
// Send final frame
|
|
337
485
|
const response = {
|
|
338
486
|
sessionId: message.sessionId,
|
|
339
487
|
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
@@ -346,74 +494,34 @@ exports.xiaoyiPlugin = {
|
|
|
346
494
|
},
|
|
347
495
|
content: {
|
|
348
496
|
type: "text",
|
|
349
|
-
text: accumulatedText,
|
|
497
|
+
text: accumulatedText,
|
|
350
498
|
},
|
|
351
499
|
status: "success",
|
|
352
500
|
};
|
|
353
501
|
const conn = runtime.getConnection();
|
|
354
502
|
if (conn) {
|
|
355
|
-
// Use append=false for final frame (complete content)
|
|
356
503
|
await conn.sendResponse(response, taskId, message.sessionId, true, false);
|
|
357
|
-
console.log(`✓ XiaoYi: FINAL frame sent (
|
|
504
|
+
console.log(`✓ XiaoYi: FINAL frame sent (non-streaming)\n`);
|
|
358
505
|
}
|
|
359
506
|
},
|
|
360
507
|
onIdle: async () => {
|
|
361
508
|
const elapsed = Date.now() - startTime;
|
|
362
509
|
console.log("\n" + "=".repeat(60));
|
|
363
|
-
console.log(`XiaoYi: [
|
|
510
|
+
console.log(`XiaoYi: [IDLE] Processing complete`);
|
|
364
511
|
console.log(` Total time: ${elapsed}ms`);
|
|
365
|
-
console.log(`
|
|
366
|
-
console.log(` Partial frames: ${partialCount}`);
|
|
512
|
+
console.log(` Streaming frames: ${partialCount}`);
|
|
367
513
|
console.log(` Final length: ${accumulatedText.length} chars`);
|
|
368
514
|
console.log("=".repeat(60) + "\n");
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
onPartialReply: async (payload) => {
|
|
375
|
-
const elapsed = Date.now() - startTime;
|
|
376
|
-
const newText = payload.text || "";
|
|
377
|
-
if (!newText) {
|
|
378
|
-
console.log(`XiaoYi: [STREAMING] Skipping empty partial response at ${elapsed}ms`);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
// Calculate delta text (增量部分)
|
|
382
|
-
const previousLength = accumulatedText.length;
|
|
383
|
-
accumulatedText = newText;
|
|
384
|
-
const deltaText = newText.slice(previousLength);
|
|
385
|
-
console.log(`\n>>> XiaoYi: [PARTIAL] Frame #${++partialCount} at ${elapsed}ms`);
|
|
386
|
-
console.log(` Previous length: ${previousLength}`);
|
|
387
|
-
console.log(` New length: ${newText.length}`);
|
|
388
|
-
console.log(` Delta length: ${deltaText.length}`);
|
|
389
|
-
console.log(` Delta content: "${deltaText}"`);
|
|
390
|
-
console.log(` Accumulated so far: "${newText}"`);
|
|
391
|
-
// Send PARTIAL frame with delta content
|
|
392
|
-
const partialResponse = {
|
|
393
|
-
sessionId: message.sessionId,
|
|
394
|
-
messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
395
|
-
timestamp: Date.now(),
|
|
396
|
-
agentId: resolvedAccount.config.agentId,
|
|
397
|
-
sender: {
|
|
398
|
-
id: resolvedAccount.config.agentId,
|
|
399
|
-
name: "OpenClaw Agent",
|
|
400
|
-
type: "agent",
|
|
401
|
-
},
|
|
402
|
-
content: {
|
|
403
|
-
type: "text",
|
|
404
|
-
text: deltaText || newText, // Send delta only (or full if delta is empty)
|
|
405
|
-
},
|
|
406
|
-
status: "processing", // Mark as processing
|
|
407
|
-
};
|
|
408
|
-
const conn = runtime.getConnection();
|
|
409
|
-
if (conn) {
|
|
410
|
-
// Use append=true for streaming frames (服务端会追加)
|
|
411
|
-
await conn.sendResponse(partialResponse, taskId, message.sessionId, false, true);
|
|
412
|
-
console.log(` ✓ SENT (isFinal=false, append=true)\n`);
|
|
515
|
+
// Cleanup event listener if still active
|
|
516
|
+
if (unsubscribeAgentEvents && !isCompleted) {
|
|
517
|
+
console.log(`XiaoYi: [IDLE] Cleaning up event listener`);
|
|
518
|
+
unsubscribeAgentEvents();
|
|
519
|
+
unsubscribeAgentEvents = undefined;
|
|
413
520
|
}
|
|
414
521
|
},
|
|
415
|
-
}
|
|
416
|
-
|
|
522
|
+
},
|
|
523
|
+
replyOptions: undefined, // NOT using onPartialReply anymore
|
|
524
|
+
images: images.length > 0 ? images : undefined,
|
|
417
525
|
});
|
|
418
526
|
}
|
|
419
527
|
catch (error) {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,20 +3,24 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
3
3
|
* XiaoYi Channel Plugin for OpenClaw
|
|
4
4
|
*
|
|
5
5
|
* This plugin enables integration with XiaoYi's A2A protocol via WebSocket.
|
|
6
|
-
*
|
|
6
|
+
* Supports dual server mode for high availability.
|
|
7
7
|
*
|
|
8
8
|
* Configuration example in openclaw.json:
|
|
9
9
|
* {
|
|
10
10
|
* "channels": {
|
|
11
11
|
* "xiaoyi": {
|
|
12
12
|
* "enabled": true,
|
|
13
|
-
* "
|
|
13
|
+
* "wsUrl1": "ws://localhost:8765/ws/link",
|
|
14
|
+
* "wsUrl2": "ws://localhost:8766/ws/link",
|
|
14
15
|
* "ak": "test_ak",
|
|
15
16
|
* "sk": "test_sk",
|
|
16
|
-
* "agentId": "your-agent-id"
|
|
17
|
+
* "agentId": "your-agent-id",
|
|
18
|
+
* "enableStreaming": true
|
|
17
19
|
* }
|
|
18
20
|
* }
|
|
19
21
|
* }
|
|
22
|
+
*
|
|
23
|
+
* Backward compatibility: Can use "wsUrl" instead of "wsUrl1" (wsUrl2 will use default)
|
|
20
24
|
*/
|
|
21
25
|
declare const plugin: {
|
|
22
26
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -7,20 +7,24 @@ const runtime_1 = require("./runtime");
|
|
|
7
7
|
* XiaoYi Channel Plugin for OpenClaw
|
|
8
8
|
*
|
|
9
9
|
* This plugin enables integration with XiaoYi's A2A protocol via WebSocket.
|
|
10
|
-
*
|
|
10
|
+
* Supports dual server mode for high availability.
|
|
11
11
|
*
|
|
12
12
|
* Configuration example in openclaw.json:
|
|
13
13
|
* {
|
|
14
14
|
* "channels": {
|
|
15
15
|
* "xiaoyi": {
|
|
16
16
|
* "enabled": true,
|
|
17
|
-
* "
|
|
17
|
+
* "wsUrl1": "ws://localhost:8765/ws/link",
|
|
18
|
+
* "wsUrl2": "ws://localhost:8766/ws/link",
|
|
18
19
|
* "ak": "test_ak",
|
|
19
20
|
* "sk": "test_sk",
|
|
20
|
-
* "agentId": "your-agent-id"
|
|
21
|
+
* "agentId": "your-agent-id",
|
|
22
|
+
* "enableStreaming": true
|
|
21
23
|
* }
|
|
22
24
|
* }
|
|
23
25
|
* }
|
|
26
|
+
*
|
|
27
|
+
* Backward compatibility: Can use "wsUrl" instead of "wsUrl1" (wsUrl2 will use default)
|
|
24
28
|
*/
|
|
25
29
|
const plugin = {
|
|
26
30
|
id: "xiaoyi",
|
package/dist/types.d.ts
CHANGED
|
@@ -141,7 +141,9 @@ export interface A2ATasksCancelMessage {
|
|
|
141
141
|
}
|
|
142
142
|
export interface XiaoYiChannelConfig {
|
|
143
143
|
enabled: boolean;
|
|
144
|
-
wsUrl
|
|
144
|
+
wsUrl?: string;
|
|
145
|
+
wsUrl1?: string;
|
|
146
|
+
wsUrl2?: string;
|
|
145
147
|
ak: string;
|
|
146
148
|
sk: string;
|
|
147
149
|
agentId: string;
|
|
@@ -161,3 +163,20 @@ export interface WebSocketConnectionState {
|
|
|
161
163
|
reconnectAttempts: number;
|
|
162
164
|
maxReconnectAttempts: number;
|
|
163
165
|
}
|
|
166
|
+
export declare const DEFAULT_WS_URL_1 = "ws://localhost:8080/ws";
|
|
167
|
+
export declare const DEFAULT_WS_URL_2 = "ws://localhost:8081/ws";
|
|
168
|
+
export interface InternalWebSocketConfig {
|
|
169
|
+
wsUrl1: string;
|
|
170
|
+
wsUrl2: string;
|
|
171
|
+
agentId: string;
|
|
172
|
+
ak: string;
|
|
173
|
+
sk: string;
|
|
174
|
+
enableStreaming?: boolean;
|
|
175
|
+
}
|
|
176
|
+
export type ServerId = 'server1' | 'server2';
|
|
177
|
+
export interface ServerConnectionState {
|
|
178
|
+
connected: boolean;
|
|
179
|
+
ready: boolean;
|
|
180
|
+
lastHeartbeat: number;
|
|
181
|
+
reconnectAttempts: number;
|
|
182
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -2,3 +2,7 @@
|
|
|
2
2
|
// A2A Message Structure Types
|
|
3
3
|
// Based on: https://developer.huawei.com/consumer/cn/doc/service/message-stream-0000002505761434
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.DEFAULT_WS_URL_2 = exports.DEFAULT_WS_URL_1 = void 0;
|
|
6
|
+
// Dual server configuration
|
|
7
|
+
exports.DEFAULT_WS_URL_1 = "ws://localhost:8080/ws";
|
|
8
|
+
exports.DEFAULT_WS_URL_2 = "ws://localhost:8081/ws";
|
package/dist/websocket.d.ts
CHANGED
|
@@ -1,113 +1,119 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
|
-
import { A2AResponseMessage, WebSocketConnectionState, XiaoYiChannelConfig } from "./types";
|
|
2
|
+
import { A2AResponseMessage, WebSocketConnectionState, XiaoYiChannelConfig, ServerId, ServerConnectionState } from "./types";
|
|
3
3
|
export declare class XiaoYiWebSocketManager extends EventEmitter {
|
|
4
|
-
private
|
|
4
|
+
private ws1;
|
|
5
|
+
private ws2;
|
|
6
|
+
private state1;
|
|
7
|
+
private state2;
|
|
8
|
+
private sessionServerMap;
|
|
5
9
|
private auth;
|
|
6
10
|
private config;
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
private appHeartbeatInterval
|
|
10
|
-
private
|
|
11
|
+
private heartbeatTimeout1?;
|
|
12
|
+
private heartbeatTimeout2?;
|
|
13
|
+
private appHeartbeatInterval?;
|
|
14
|
+
private reconnectTimeout1?;
|
|
15
|
+
private reconnectTimeout2?;
|
|
11
16
|
private activeTasks;
|
|
12
17
|
constructor(config: XiaoYiChannelConfig);
|
|
13
18
|
/**
|
|
14
|
-
*
|
|
19
|
+
* Resolve configuration with defaults and backward compatibility
|
|
15
20
|
*/
|
|
16
|
-
|
|
21
|
+
private resolveConfig;
|
|
17
22
|
/**
|
|
18
|
-
*
|
|
23
|
+
* Connect to both WebSocket servers
|
|
19
24
|
*/
|
|
20
|
-
|
|
25
|
+
connect(): Promise<void>;
|
|
21
26
|
/**
|
|
22
|
-
*
|
|
27
|
+
* Connect to server 1
|
|
23
28
|
*/
|
|
24
|
-
private
|
|
29
|
+
private connectToServer1;
|
|
25
30
|
/**
|
|
26
|
-
*
|
|
27
|
-
* This method is for regular agent responses only
|
|
28
|
-
* @param response - The response message
|
|
29
|
-
* @param taskId - The task ID
|
|
30
|
-
* @param sessionId - The session ID
|
|
31
|
-
* @param isFinal - Whether this is the final frame (default: true)
|
|
32
|
-
* @param append - Whether to append to previous content (default: false for complete content)
|
|
31
|
+
* Connect to server 2
|
|
33
32
|
*/
|
|
34
|
-
|
|
33
|
+
private connectToServer2;
|
|
35
34
|
/**
|
|
36
|
-
*
|
|
37
|
-
* Reference: https://developer.huawei.com/consumer/cn/doc/service/clear-context-0000002537681163
|
|
35
|
+
* Disconnect from all servers
|
|
38
36
|
*/
|
|
39
|
-
|
|
37
|
+
disconnect(): void;
|
|
40
38
|
/**
|
|
41
|
-
* Send
|
|
42
|
-
* Reference: https://developer.huawei.com/consumer/cn/doc/service/tasks-cancel-0000002537561193
|
|
39
|
+
* Send init message to specific server
|
|
43
40
|
*/
|
|
44
|
-
|
|
41
|
+
private sendInitMessage;
|
|
45
42
|
/**
|
|
46
|
-
*
|
|
47
|
-
* @param response - The response message
|
|
48
|
-
* @param taskId - The task ID
|
|
49
|
-
* @param isFinal - Whether this is the final frame (default: true)
|
|
50
|
-
* @param append - Whether to append to previous content (default: false)
|
|
43
|
+
* Setup WebSocket event handlers for specific server
|
|
51
44
|
*/
|
|
52
|
-
private
|
|
45
|
+
private setupWebSocketHandlers;
|
|
53
46
|
/**
|
|
54
|
-
*
|
|
47
|
+
* Handle incoming message from specific server
|
|
55
48
|
*/
|
|
56
|
-
private
|
|
49
|
+
private handleIncomingMessage;
|
|
57
50
|
/**
|
|
58
|
-
*
|
|
51
|
+
* Send A2A response message with automatic routing
|
|
59
52
|
*/
|
|
60
|
-
|
|
53
|
+
sendResponse(response: A2AResponseMessage, taskId: string, sessionId: string, isFinal?: boolean, append?: boolean): Promise<void>;
|
|
61
54
|
/**
|
|
62
|
-
*
|
|
55
|
+
* Send clear context response to specific server
|
|
63
56
|
*/
|
|
64
|
-
|
|
57
|
+
sendClearContextResponse(requestId: string, sessionId: string, success?: boolean, targetServer?: ServerId): Promise<void>;
|
|
65
58
|
/**
|
|
66
|
-
*
|
|
59
|
+
* Send tasks cancel response to specific server
|
|
67
60
|
*/
|
|
68
|
-
|
|
61
|
+
sendTasksCancelResponse(requestId: string, sessionId: string, success?: boolean, targetServer?: ServerId): Promise<void>;
|
|
69
62
|
/**
|
|
70
|
-
* Handle
|
|
63
|
+
* Handle clearContext method
|
|
71
64
|
*/
|
|
72
|
-
private
|
|
65
|
+
private handleClearContext;
|
|
73
66
|
/**
|
|
74
|
-
* Handle
|
|
75
|
-
* Reference: https://developer.huawei.com/consumer/cn/doc/service/clear-context-0000002537681163
|
|
67
|
+
* Handle clear message (legacy format)
|
|
76
68
|
*/
|
|
77
69
|
private handleClearMessage;
|
|
78
70
|
/**
|
|
79
|
-
* Handle
|
|
80
|
-
* Reference: https://developer.huawei.com/consumer/cn/doc/service/tasks-cancel-0000002537561193
|
|
81
|
-
*
|
|
82
|
-
* Simplified implementation similar to clearContext:
|
|
83
|
-
* 1. Send success response immediately
|
|
84
|
-
* 2. Emit cancel event for application to handle
|
|
71
|
+
* Handle tasks/cancel message
|
|
85
72
|
*/
|
|
86
73
|
private handleTasksCancelMessage;
|
|
87
74
|
/**
|
|
88
|
-
*
|
|
75
|
+
* Convert A2AResponseMessage to JSON-RPC 2.0 format
|
|
89
76
|
*/
|
|
90
|
-
|
|
77
|
+
private convertToJsonRpcFormat;
|
|
91
78
|
/**
|
|
92
|
-
*
|
|
79
|
+
* Check if at least one server is ready
|
|
93
80
|
*/
|
|
94
|
-
|
|
81
|
+
isReady(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Get combined connection state
|
|
84
|
+
*/
|
|
85
|
+
getState(): WebSocketConnectionState;
|
|
95
86
|
/**
|
|
96
|
-
*
|
|
87
|
+
* Get individual server states
|
|
88
|
+
*/
|
|
89
|
+
getServerStates(): {
|
|
90
|
+
server1: ServerConnectionState;
|
|
91
|
+
server2: ServerConnectionState;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Start protocol-level heartbeat for specific server
|
|
97
95
|
*/
|
|
98
96
|
private startProtocolHeartbeat;
|
|
99
97
|
/**
|
|
100
|
-
*
|
|
98
|
+
* Clear protocol heartbeat for specific server
|
|
99
|
+
*/
|
|
100
|
+
private clearProtocolHeartbeat;
|
|
101
|
+
/**
|
|
102
|
+
* Start application-level heartbeat (shared across both servers)
|
|
101
103
|
*/
|
|
102
104
|
private startAppHeartbeat;
|
|
103
105
|
/**
|
|
104
|
-
* Schedule reconnection
|
|
106
|
+
* Schedule reconnection for specific server
|
|
105
107
|
*/
|
|
106
108
|
private scheduleReconnect;
|
|
107
109
|
/**
|
|
108
110
|
* Clear all timers
|
|
109
111
|
*/
|
|
110
112
|
private clearTimers;
|
|
113
|
+
/**
|
|
114
|
+
* Type guard for A2A request messages
|
|
115
|
+
*/
|
|
116
|
+
private isA2ARequestMessage;
|
|
111
117
|
/**
|
|
112
118
|
* Get active tasks
|
|
113
119
|
*/
|
|
@@ -116,4 +122,12 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
|
|
|
116
122
|
* Remove task from active tasks
|
|
117
123
|
*/
|
|
118
124
|
removeActiveTask(taskId: string): void;
|
|
125
|
+
/**
|
|
126
|
+
* Get server for a specific session
|
|
127
|
+
*/
|
|
128
|
+
getServerForSession(sessionId: string): ServerId | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Remove session mapping
|
|
131
|
+
*/
|
|
132
|
+
removeSession(sessionId: string): void;
|
|
119
133
|
}
|