@ynhcj/xiaoyi 2.2.0 → 2.2.2

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
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.xiaoyiPlugin = void 0;
4
4
  const runtime_1 = require("./runtime");
5
+ const file_handler_1 = require("./file-handler");
5
6
  /**
6
7
  * XiaoYi Channel Plugin
7
8
  * Implements OpenClaw ChannelPlugin interface for XiaoYi A2A protocol
@@ -49,6 +50,8 @@ exports.xiaoyiPlugin = {
49
50
  config: {
50
51
  enabled: false,
51
52
  wsUrl: "",
53
+ wsUrl1: "",
54
+ wsUrl2: "",
52
55
  ak: "",
53
56
  sk: "",
54
57
  agentId: "",
@@ -77,7 +80,10 @@ exports.xiaoyiPlugin = {
77
80
  }
78
81
  const config = account.config;
79
82
  // Check each field is a string and has content after trimming
80
- const hasWsUrl = typeof config.wsUrl === 'string' && config.wsUrl.trim().length > 0;
83
+ // Support both old wsUrl and new wsUrl1/wsUrl2
84
+ const hasWsUrl = ((typeof config.wsUrl === 'string' && config.wsUrl.trim().length > 0) ||
85
+ (typeof config.wsUrl1 === 'string' && config.wsUrl1.trim().length > 0) ||
86
+ (typeof config.wsUrl2 === 'string' && config.wsUrl2.trim().length > 0));
81
87
  const hasAk = typeof config.ak === 'string' && config.ak.trim().length > 0;
82
88
  const hasSk = typeof config.sk === 'string' && config.sk.trim().length > 0;
83
89
  const hasAgentId = typeof config.agentId === 'string' && config.agentId.trim().length > 0;
@@ -90,13 +96,14 @@ exports.xiaoyiPlugin = {
90
96
  return "Channel is disabled in configuration";
91
97
  },
92
98
  unconfiguredReason: (account, cfg) => {
93
- return "Missing required configuration: wsUrl, ak, sk, or agentId";
99
+ return "Missing required configuration: wsUrl/wsUrl1/wsUrl2, ak, sk, or agentId";
94
100
  },
95
101
  describeAccount: (account, cfg) => ({
96
102
  accountId: account.accountId,
97
103
  name: 'XiaoYi',
98
104
  enabled: account.enabled,
99
- configured: Boolean(account.config?.wsUrl && account.config?.ak && account.config?.sk && account.config?.agentId),
105
+ configured: Boolean((account.config?.wsUrl || account.config?.wsUrl1 || account.config?.wsUrl2) &&
106
+ account.config?.ak && account.config?.sk && account.config?.agentId),
100
107
  }),
101
108
  },
102
109
  /**
@@ -219,13 +226,68 @@ exports.xiaoyiPlugin = {
219
226
  console.error("PluginRuntime not available");
220
227
  return;
221
228
  }
222
- // Extract text content from parts array (ignore kind: "data" as per user request)
229
+ // Extract text, file, and image content from parts array
223
230
  let bodyText = "";
231
+ let images = [];
232
+ let fileAttachments = [];
224
233
  for (const part of message.params.message.parts) {
225
234
  if (part.kind === "text" && part.text) {
235
+ // Handle text content
226
236
  bodyText += part.text;
227
237
  }
228
- // TODO: Handle file parts if needed in the future
238
+ else if (part.kind === "file" && part.file) {
239
+ // Handle file content
240
+ const { uri, mimeType, name } = part.file;
241
+ if (!uri) {
242
+ console.warn(`XiaoYi: File part without URI, skipping: ${name}`);
243
+ continue;
244
+ }
245
+ try {
246
+ // Handle image files
247
+ if ((0, file_handler_1.isImageMimeType)(mimeType)) {
248
+ console.log(`XiaoYi: Processing image file: ${name} (${mimeType})`);
249
+ const imageContent = await (0, file_handler_1.extractImageFromUrl)(uri, {
250
+ maxBytes: 10000000, // 10MB
251
+ timeoutMs: 30000, // 30 seconds
252
+ });
253
+ images.push(imageContent);
254
+ fileAttachments.push(`[图片: ${name}]`);
255
+ console.log(`XiaoYi: Successfully processed image: ${name}`);
256
+ }
257
+ // Handle PDF files - extract as text for now
258
+ else if ((0, file_handler_1.isPdfMimeType)(mimeType)) {
259
+ console.log(`XiaoYi: Processing PDF file: ${name}`);
260
+ // Note: PDF text extraction requires pdfjs-dist, for now just add a placeholder
261
+ fileAttachments.push(`[PDF文件: ${name} - PDF内容提取需要额外配置]`);
262
+ console.log(`XiaoYi: PDF file noted: ${name} (text extraction requires pdfjs-dist)`);
263
+ }
264
+ // Handle text-based files
265
+ else if ((0, file_handler_1.isTextMimeType)(mimeType)) {
266
+ console.log(`XiaoYi: Processing text file: ${name} (${mimeType})`);
267
+ const textContent = await (0, file_handler_1.extractTextFromUrl)(uri, 5000000, 30000);
268
+ bodyText += `\n\n[文件内容: ${name}]\n${textContent}`;
269
+ fileAttachments.push(`[文件: ${name}]`);
270
+ console.log(`XiaoYi: Successfully processed text file: ${name}`);
271
+ }
272
+ else {
273
+ console.warn(`XiaoYi: Unsupported file type: ${mimeType}, name: ${name}`);
274
+ fileAttachments.push(`[不支持的文件类型: ${name} (${mimeType})]`);
275
+ }
276
+ }
277
+ catch (error) {
278
+ const errorMsg = error instanceof Error ? error.message : String(error);
279
+ console.error(`XiaoYi: Failed to process file ${name}: ${errorMsg}`);
280
+ fileAttachments.push(`[文件处理失败: ${name} - ${errorMsg}]`);
281
+ }
282
+ }
283
+ // Ignore kind: "data" as per user request
284
+ }
285
+ // Log summary of processed attachments
286
+ if (fileAttachments.length > 0) {
287
+ console.log(`XiaoYi: Processed ${fileAttachments.length} file(s): ${fileAttachments.join(", ")}`);
288
+ }
289
+ if (images.length > 0) {
290
+ console.log(`XiaoYi: Total ${images.length} image(s) extracted for AI processing`);
229
291
  }
230
292
  // Determine sender ID from role
231
293
  const senderId = message.params.message.role === "user" ? "user" : message.agentId;
@@ -357,6 +419,7 @@ exports.xiaoyiPlugin = {
357
419
  }
358
420
  },
359
421
  } : undefined, // No replyOptions when streaming is disabled
422
+ images: images.length > 0 ? images : undefined, // Pass images to AI processing
360
423
  });
361
424
  }
362
425
  catch (error) {
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Simple file and image handler for XiaoYi Channel
3
+ * Handles downloading and extracting content from URIs
4
+ */
5
+ export interface InputImageContent {
6
+ type: "image";
7
+ data: string;
8
+ mimeType: string;
9
+ }
10
+ export interface ImageLimits {
11
+ allowUrl: boolean;
12
+ allowedMimes: Set<string>;
13
+ maxBytes: number;
14
+ maxRedirects: number;
15
+ timeoutMs: number;
16
+ }
17
+ /**
18
+ * Extract image content from URL
19
+ */
20
+ export declare function extractImageFromUrl(url: string, limits?: Partial<ImageLimits>): Promise<InputImageContent>;
21
+ /**
22
+ * Extract text content from URL (for text-based files)
23
+ */
24
+ export declare function extractTextFromUrl(url: string, maxBytes?: number, timeoutMs?: number): Promise<string>;
25
+ /**
26
+ * Check if a MIME type is an image
27
+ */
28
+ export declare function isImageMimeType(mimeType: string | undefined): boolean;
29
+ /**
30
+ * Check if a MIME type is a PDF
31
+ */
32
+ export declare function isPdfMimeType(mimeType: string | undefined): boolean;
33
+ /**
34
+ * Check if a MIME type is text-based
35
+ */
36
+ export declare function isTextMimeType(mimeType: string | undefined): boolean;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Simple file and image handler for XiaoYi Channel
4
+ * Handles downloading and extracting content from URIs
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.extractImageFromUrl = extractImageFromUrl;
8
+ exports.extractTextFromUrl = extractTextFromUrl;
9
+ exports.isImageMimeType = isImageMimeType;
10
+ exports.isPdfMimeType = isPdfMimeType;
11
+ exports.isTextMimeType = isTextMimeType;
12
+ // Default limits
13
+ const DEFAULT_IMAGE_MIMES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
14
+ const DEFAULT_MAX_BYTES = 10000000; // 10MB
15
+ const DEFAULT_TIMEOUT = 30000; // 30 seconds
16
+ const DEFAULT_MAX_REDIRECTS = 3;
17
+ /**
18
+ * Fetch content from URL with basic validation
19
+ */
20
+ async function fetchFromUrl(url, maxBytes, timeoutMs) {
21
+ const controller = new AbortController();
22
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
23
+ try {
24
+ const response = await fetch(url, {
25
+ signal: controller.signal,
26
+ headers: { "User-Agent": "XiaoYi-Channel/1.0" },
27
+ });
28
+ if (!response.ok) {
29
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
30
+ }
31
+ // Check content-length header if available
32
+ const contentLength = response.headers.get("content-length");
33
+ if (contentLength) {
34
+ const size = parseInt(contentLength, 10);
35
+ if (size > maxBytes) {
36
+ throw new Error(`File too large: ${size} bytes (limit: ${maxBytes})`);
37
+ }
38
+ }
39
+ const buffer = Buffer.from(await response.arrayBuffer());
40
+ if (buffer.byteLength > maxBytes) {
41
+ throw new Error(`File too large: ${buffer.byteLength} bytes (limit: ${maxBytes})`);
42
+ }
43
+ // Detect MIME type
44
+ const contentType = response.headers.get("content-type");
45
+ const mimeType = contentType?.split(";")[0]?.trim() || "application/octet-stream";
46
+ return { buffer, mimeType };
47
+ }
48
+ finally {
49
+ clearTimeout(timeout);
50
+ }
51
+ }
52
+ /**
53
+ * Extract image content from URL
54
+ */
55
+ async function extractImageFromUrl(url, limits) {
56
+ const finalLimits = {
57
+ allowUrl: limits?.allowUrl ?? true,
58
+ allowedMimes: limits?.allowedMimes ?? DEFAULT_IMAGE_MIMES,
59
+ maxBytes: limits?.maxBytes ?? DEFAULT_MAX_BYTES,
60
+ maxRedirects: limits?.maxRedirects ?? DEFAULT_MAX_REDIRECTS,
61
+ timeoutMs: limits?.timeoutMs ?? DEFAULT_TIMEOUT,
62
+ };
63
+ if (!finalLimits.allowUrl) {
64
+ throw new Error("URL sources are disabled");
65
+ }
66
+ const { buffer, mimeType } = await fetchFromUrl(url, finalLimits.maxBytes, finalLimits.timeoutMs);
67
+ if (!finalLimits.allowedMimes.has(mimeType)) {
68
+ throw new Error(`Unsupported image type: ${mimeType}`);
69
+ }
70
+ return {
71
+ type: "image",
72
+ data: buffer.toString("base64"),
73
+ mimeType,
74
+ };
75
+ }
76
+ /**
77
+ * Extract text content from URL (for text-based files)
78
+ */
79
+ async function extractTextFromUrl(url, maxBytes = 5000000, timeoutMs = 30000) {
80
+ const { buffer, mimeType } = await fetchFromUrl(url, maxBytes, timeoutMs);
81
+ // Only process text-based MIME types
82
+ const textMimes = ["text/plain", "text/markdown", "text/html", "text/csv", "application/json", "application/xml"];
83
+ if (!textMimes.some((tm) => mimeType.startsWith(tm) || mimeType === tm)) {
84
+ throw new Error(`Unsupported text type: ${mimeType}`);
85
+ }
86
+ // Try to decode as UTF-8
87
+ return buffer.toString("utf-8");
88
+ }
89
+ /**
90
+ * Check if a MIME type is an image
91
+ */
92
+ function isImageMimeType(mimeType) {
93
+ if (!mimeType)
94
+ return false;
95
+ return DEFAULT_IMAGE_MIMES.has(mimeType.toLowerCase());
96
+ }
97
+ /**
98
+ * Check if a MIME type is a PDF
99
+ */
100
+ function isPdfMimeType(mimeType) {
101
+ return mimeType?.toLowerCase() === "application/pdf" || false;
102
+ }
103
+ /**
104
+ * Check if a MIME type is text-based
105
+ */
106
+ function isTextMimeType(mimeType) {
107
+ if (!mimeType)
108
+ return false;
109
+ const lower = mimeType.toLowerCase();
110
+ return (lower.startsWith("text/") ||
111
+ lower === "application/json" ||
112
+ lower === "application/xml");
113
+ }
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
  }