@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 +68 -5
- package/dist/file-handler.d.ts +36 -0
- package/dist/file-handler.js +113 -0
- 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
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
}
|