@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 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
- const hasWsUrl = typeof config.wsUrl === 'string' && config.wsUrl.trim().length > 0;
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 && account.config?.ak && account.config?.sk && account.config?.agentId),
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 mode enabled)
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 (enableStreaming=false)"}`);
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: [STREAMING] DELIVER callback triggered`);
480
+ console.log(`XiaoYi: [DELIVER] Non-streaming mode`);
330
481
  console.log(` Elapsed: ${elapsed}ms`);
331
- console.log(` Frame: #${++frameCount}`);
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 FINAL frame with complete content
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, // Complete content
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 (isFinal=true, append=false, length=${accumulatedText.length})\n`);
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: [STREAMING] Processing complete`);
510
+ console.log(`XiaoYi: [IDLE] Processing complete`);
364
511
  console.log(` Total time: ${elapsed}ms`);
365
- console.log(` Total frames: ${frameCount}`);
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
- replyOptions: streamingEnabled ? {
372
- // Enable streaming responses through onPartialReply callback
373
- disableBlockStreaming: false, // CRITICAL: Enable block streaming
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
- } : undefined, // No replyOptions when streaming is disabled
416
- images: images.length > 0 ? images : undefined, // Pass images to AI processing
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
- * Single account mode only.
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
- * "wsUrl": "ws://localhost:8765/ws/link",
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
- * Single account mode only.
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
- * "wsUrl": "ws://localhost:8765/ws/link",
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: string;
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";
@@ -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 ws;
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 state;
8
- private protocolHeartbeatInterval;
9
- private appHeartbeatInterval;
10
- private reconnectTimeout;
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
- * Connect to XiaoYi WebSocket server with header authentication
19
+ * Resolve configuration with defaults and backward compatibility
15
20
  */
16
- connect(): Promise<void>;
21
+ private resolveConfig;
17
22
  /**
18
- * Disconnect from WebSocket server
23
+ * Connect to both WebSocket servers
19
24
  */
20
- disconnect(): void;
25
+ connect(): Promise<void>;
21
26
  /**
22
- * Send clawd_bot_init message on connection/reconnection
27
+ * Connect to server 1
23
28
  */
24
- private sendInitMessage;
29
+ private connectToServer1;
25
30
  /**
26
- * Send A2A response message (converts to JSON-RPC 2.0 format)
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
- sendResponse(response: A2AResponseMessage, taskId: string, sessionId: string, isFinal?: boolean, append?: boolean): Promise<void>;
33
+ private connectToServer2;
35
34
  /**
36
- * Send A2A clear context response (uses specific clear context format)
37
- * Reference: https://developer.huawei.com/consumer/cn/doc/service/clear-context-0000002537681163
35
+ * Disconnect from all servers
38
36
  */
39
- sendClearContextResponse(requestId: string, sessionId: string, success?: boolean): Promise<void>;
37
+ disconnect(): void;
40
38
  /**
41
- * Send A2A tasks cancel response (uses specific cancel format)
42
- * Reference: https://developer.huawei.com/consumer/cn/doc/service/tasks-cancel-0000002537561193
39
+ * Send init message to specific server
43
40
  */
44
- sendTasksCancelResponse(requestId: string, sessionId: string, success?: boolean): Promise<void>;
41
+ private sendInitMessage;
45
42
  /**
46
- * Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
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 convertToJsonRpcFormat;
45
+ private setupWebSocketHandlers;
53
46
  /**
54
- * Send generic outbound message
47
+ * Handle incoming message from specific server
55
48
  */
56
- private sendMessage;
49
+ private handleIncomingMessage;
57
50
  /**
58
- * Check if connection is ready for sending messages
51
+ * Send A2A response message with automatic routing
59
52
  */
60
- isReady(): boolean;
53
+ sendResponse(response: A2AResponseMessage, taskId: string, sessionId: string, isFinal?: boolean, append?: boolean): Promise<void>;
61
54
  /**
62
- * Get current connection state
55
+ * Send clear context response to specific server
63
56
  */
64
- getState(): WebSocketConnectionState;
57
+ sendClearContextResponse(requestId: string, sessionId: string, success?: boolean, targetServer?: ServerId): Promise<void>;
65
58
  /**
66
- * Setup WebSocket event handlers
59
+ * Send tasks cancel response to specific server
67
60
  */
68
- private setupWebSocketHandlers;
61
+ sendTasksCancelResponse(requestId: string, sessionId: string, success?: boolean, targetServer?: ServerId): Promise<void>;
69
62
  /**
70
- * Handle incoming WebSocket messages
63
+ * Handle clearContext method
71
64
  */
72
- private handleMessage;
65
+ private handleClearContext;
73
66
  /**
74
- * Handle A2A clear message
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 A2A tasks/cancel message
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
- * Send tasks/cancel success response
75
+ * Convert A2AResponseMessage to JSON-RPC 2.0 format
89
76
  */
90
- sendCancelSuccessResponse(sessionId: string, taskId: string, requestId: string): Promise<void>;
77
+ private convertToJsonRpcFormat;
91
78
  /**
92
- * Type guard for A2A request messages (JSON-RPC 2.0 format)
79
+ * Check if at least one server is ready
93
80
  */
94
- private isA2ARequestMessage;
81
+ isReady(): boolean;
82
+ /**
83
+ * Get combined connection state
84
+ */
85
+ getState(): WebSocketConnectionState;
95
86
  /**
96
- * Start protocol-level heartbeat (ping/pong)
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
- * Start application-level heartbeat
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 attempt with exponential backoff
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
  }