@ynhcj/xiaoyi 2.0.5 → 2.0.7
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 +18 -8
- package/dist/types.d.ts +74 -18
- package/dist/websocket.d.ts +17 -2
- package/dist/websocket.js +123 -43
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -211,28 +211,38 @@ exports.xiaoyiPlugin = {
|
|
|
211
211
|
const { getXiaoYiRuntime } = require("./runtime");
|
|
212
212
|
const runtime = getXiaoYiRuntime();
|
|
213
213
|
console.log(`XiaoYi: [Message Handler] Using runtime instance: ${runtime.getInstanceId()}`);
|
|
214
|
-
// Store sessionId -> taskId mapping in runtime
|
|
215
|
-
runtime.setTaskIdForSession(message.sessionId, message.id);
|
|
214
|
+
// Store sessionId -> taskId mapping in runtime (use params.id as taskId)
|
|
215
|
+
runtime.setTaskIdForSession(message.sessionId, message.params.id);
|
|
216
216
|
// Get PluginRuntime from our runtime wrapper
|
|
217
217
|
const pluginRuntime = runtime.getPluginRuntime();
|
|
218
218
|
if (!pluginRuntime) {
|
|
219
219
|
console.error("PluginRuntime not available");
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
|
+
// Extract text content from parts array (ignore kind: "data" as per user request)
|
|
223
|
+
let bodyText = "";
|
|
224
|
+
for (const part of message.params.message.parts) {
|
|
225
|
+
if (part.kind === "text" && part.text) {
|
|
226
|
+
bodyText += part.text;
|
|
227
|
+
}
|
|
228
|
+
// TODO: Handle file parts if needed in the future
|
|
229
|
+
}
|
|
230
|
+
// Determine sender ID from role
|
|
231
|
+
const senderId = message.params.message.role === "user" ? "user" : message.agentId;
|
|
222
232
|
// Build MsgContext for OpenClaw's message pipeline
|
|
223
233
|
const msgContext = {
|
|
224
|
-
Body:
|
|
225
|
-
From:
|
|
234
|
+
Body: bodyText,
|
|
235
|
+
From: senderId,
|
|
226
236
|
To: message.sessionId,
|
|
227
237
|
SessionKey: `xiaoyi:${resolvedAccount.accountId}:${message.sessionId}`,
|
|
228
238
|
AccountId: resolvedAccount.accountId,
|
|
229
|
-
MessageSid: message.
|
|
230
|
-
Timestamp:
|
|
239
|
+
MessageSid: message.id, // Use top-level id as message sequence number
|
|
240
|
+
Timestamp: Date.now(), // Generate timestamp since new format doesn't include it
|
|
231
241
|
Provider: "xiaoyi",
|
|
232
242
|
Surface: "xiaoyi",
|
|
233
243
|
ChatType: "direct",
|
|
234
|
-
SenderName: message.sender
|
|
235
|
-
SenderId:
|
|
244
|
+
SenderName: message.params.message.role, // Use role as sender name
|
|
245
|
+
SenderId: senderId,
|
|
236
246
|
OriginatingChannel: "xiaoyi",
|
|
237
247
|
};
|
|
238
248
|
// Use the correct API to dispatch the message
|
package/dist/types.d.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
export interface A2ARequestMessage {
|
|
2
2
|
agentId: string;
|
|
3
3
|
sessionId: string;
|
|
4
|
+
jsonrpc: "2.0";
|
|
4
5
|
id: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
sender: {
|
|
6
|
+
method: "message/stream";
|
|
7
|
+
params: {
|
|
8
8
|
id: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
sessionId: string;
|
|
10
|
+
agentLoginSessionId?: string;
|
|
11
|
+
message: {
|
|
12
|
+
role: "user" | "agent";
|
|
13
|
+
parts: Array<{
|
|
14
|
+
kind: "text" | "file" | "data";
|
|
15
|
+
text?: string;
|
|
16
|
+
file?: {
|
|
17
|
+
name: string;
|
|
18
|
+
mimeType: string;
|
|
19
|
+
bytes?: string;
|
|
20
|
+
uri?: string;
|
|
21
|
+
};
|
|
22
|
+
data?: any;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
export interface A2AResponseMessage {
|
|
@@ -52,6 +53,61 @@ export interface A2AResponseMessage {
|
|
|
52
53
|
message: string;
|
|
53
54
|
};
|
|
54
55
|
}
|
|
56
|
+
export interface A2AJsonRpcResponse {
|
|
57
|
+
jsonrpc: "2.0";
|
|
58
|
+
id: string;
|
|
59
|
+
result?: A2ATaskArtifactUpdateEvent | A2ATaskStatusUpdateEvent | A2AClearContextResult | A2ATasksCancelResult;
|
|
60
|
+
error?: {
|
|
61
|
+
code: number | string;
|
|
62
|
+
message: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface A2ATaskArtifactUpdateEvent {
|
|
66
|
+
taskId: string;
|
|
67
|
+
kind: "artifact-update";
|
|
68
|
+
append?: boolean;
|
|
69
|
+
lastChunk?: boolean;
|
|
70
|
+
final: boolean;
|
|
71
|
+
artifact: {
|
|
72
|
+
artifactId: string;
|
|
73
|
+
parts: Array<{
|
|
74
|
+
kind: "text" | "file" | "data";
|
|
75
|
+
text?: string;
|
|
76
|
+
file?: {
|
|
77
|
+
name: string;
|
|
78
|
+
mimeType: string;
|
|
79
|
+
bytes?: string;
|
|
80
|
+
uri?: string;
|
|
81
|
+
};
|
|
82
|
+
data?: any;
|
|
83
|
+
}>;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export interface A2ATaskStatusUpdateEvent {
|
|
87
|
+
taskId: string;
|
|
88
|
+
kind: "status-update";
|
|
89
|
+
final: boolean;
|
|
90
|
+
status: {
|
|
91
|
+
message: {
|
|
92
|
+
role: "agent";
|
|
93
|
+
parts: Array<{
|
|
94
|
+
kind: "text";
|
|
95
|
+
text: string;
|
|
96
|
+
}>;
|
|
97
|
+
};
|
|
98
|
+
state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export interface A2AClearContextResult {
|
|
102
|
+
status: {
|
|
103
|
+
state: "cleared" | "failed" | "unknown";
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export interface A2ATasksCancelResult {
|
|
107
|
+
status: {
|
|
108
|
+
state: "canceled" | "failed" | "unknown";
|
|
109
|
+
};
|
|
110
|
+
}
|
|
55
111
|
export interface A2AWebSocketMessage {
|
|
56
112
|
type: "message" | "heartbeat" | "auth" | "error";
|
|
57
113
|
data: A2ARequestMessage | A2AResponseMessage | any;
|
package/dist/websocket.d.ts
CHANGED
|
@@ -23,9 +23,24 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
|
|
|
23
23
|
*/
|
|
24
24
|
private sendInitMessage;
|
|
25
25
|
/**
|
|
26
|
-
* Send A2A response message
|
|
26
|
+
* Send A2A response message (converts to JSON-RPC 2.0 format)
|
|
27
|
+
* This method is for regular agent responses only
|
|
27
28
|
*/
|
|
28
29
|
sendResponse(response: A2AResponseMessage, taskId: string, sessionId: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Send A2A clear context response (uses specific clear context format)
|
|
32
|
+
* Reference: https://developer.huawei.com/consumer/cn/doc/service/clear-context-0000002537681163
|
|
33
|
+
*/
|
|
34
|
+
sendClearContextResponse(requestId: string, sessionId: string, success?: boolean): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Send A2A tasks cancel response (uses specific cancel format)
|
|
37
|
+
* Reference: https://developer.huawei.com/consumer/cn/doc/service/tasks-cancel-0000002537561193
|
|
38
|
+
*/
|
|
39
|
+
sendTasksCancelResponse(requestId: string, sessionId: string, success?: boolean): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
|
|
42
|
+
*/
|
|
43
|
+
private convertToJsonRpcFormat;
|
|
29
44
|
/**
|
|
30
45
|
* Send generic outbound message
|
|
31
46
|
*/
|
|
@@ -61,7 +76,7 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
|
|
|
61
76
|
*/
|
|
62
77
|
sendCancelSuccessResponse(sessionId: string, taskId: string, requestId: string): Promise<void>;
|
|
63
78
|
/**
|
|
64
|
-
* Type guard for A2A request messages
|
|
79
|
+
* Type guard for A2A request messages (JSON-RPC 2.0 format)
|
|
65
80
|
*/
|
|
66
81
|
private isA2ARequestMessage;
|
|
67
82
|
/**
|
package/dist/websocket.js
CHANGED
|
@@ -95,21 +95,129 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
95
95
|
console.log("Sent clawd_bot_init message");
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
* Send A2A response message
|
|
98
|
+
* Send A2A response message (converts to JSON-RPC 2.0 format)
|
|
99
|
+
* This method is for regular agent responses only
|
|
99
100
|
*/
|
|
100
101
|
async sendResponse(response, taskId, sessionId) {
|
|
101
102
|
if (!this.isReady()) {
|
|
102
103
|
throw new Error("WebSocket not ready");
|
|
103
104
|
}
|
|
105
|
+
// Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
|
|
106
|
+
const jsonRpcResponse = this.convertToJsonRpcFormat(response, taskId);
|
|
104
107
|
const message = {
|
|
105
108
|
msgType: "agent_response",
|
|
106
109
|
agentId: this.config.agentId,
|
|
107
110
|
sessionId: sessionId,
|
|
108
111
|
taskId: taskId,
|
|
109
|
-
msgDetail: JSON.stringify(
|
|
112
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
110
113
|
};
|
|
111
114
|
this.sendMessage(message);
|
|
112
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Send A2A clear context response (uses specific clear context format)
|
|
118
|
+
* Reference: https://developer.huawei.com/consumer/cn/doc/service/clear-context-0000002537681163
|
|
119
|
+
*/
|
|
120
|
+
async sendClearContextResponse(requestId, sessionId, success = true) {
|
|
121
|
+
if (!this.isReady()) {
|
|
122
|
+
throw new Error("WebSocket not ready");
|
|
123
|
+
}
|
|
124
|
+
const jsonRpcResponse = {
|
|
125
|
+
jsonrpc: "2.0",
|
|
126
|
+
id: requestId,
|
|
127
|
+
result: {
|
|
128
|
+
status: {
|
|
129
|
+
state: success ? "cleared" : "failed"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
const message = {
|
|
134
|
+
msgType: "agent_response",
|
|
135
|
+
agentId: this.config.agentId,
|
|
136
|
+
sessionId: sessionId,
|
|
137
|
+
taskId: requestId,
|
|
138
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
139
|
+
};
|
|
140
|
+
this.sendMessage(message);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Send A2A tasks cancel response (uses specific cancel format)
|
|
144
|
+
* Reference: https://developer.huawei.com/consumer/cn/doc/service/tasks-cancel-0000002537561193
|
|
145
|
+
*/
|
|
146
|
+
async sendTasksCancelResponse(requestId, sessionId, success = true) {
|
|
147
|
+
if (!this.isReady()) {
|
|
148
|
+
throw new Error("WebSocket not ready");
|
|
149
|
+
}
|
|
150
|
+
const jsonRpcResponse = {
|
|
151
|
+
jsonrpc: "2.0",
|
|
152
|
+
id: requestId,
|
|
153
|
+
result: {
|
|
154
|
+
status: {
|
|
155
|
+
state: success ? "canceled" : "failed"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
const message = {
|
|
160
|
+
msgType: "agent_response",
|
|
161
|
+
agentId: this.config.agentId,
|
|
162
|
+
sessionId: sessionId,
|
|
163
|
+
taskId: requestId,
|
|
164
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
165
|
+
};
|
|
166
|
+
this.sendMessage(message);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
|
|
170
|
+
*/
|
|
171
|
+
convertToJsonRpcFormat(response, taskId) {
|
|
172
|
+
// Generate artifact ID
|
|
173
|
+
const artifactId = `artifact_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
174
|
+
// Check if there's an error
|
|
175
|
+
if (response.status === "error" && response.error) {
|
|
176
|
+
return {
|
|
177
|
+
jsonrpc: "2.0",
|
|
178
|
+
id: response.messageId,
|
|
179
|
+
error: {
|
|
180
|
+
code: response.error.code,
|
|
181
|
+
message: response.error.message,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Convert content to artifact parts
|
|
186
|
+
const parts = [];
|
|
187
|
+
if (response.content.type === "text" && response.content.text) {
|
|
188
|
+
parts.push({
|
|
189
|
+
kind: "text",
|
|
190
|
+
text: response.content.text,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else if (response.content.type === "file") {
|
|
194
|
+
parts.push({
|
|
195
|
+
kind: "file",
|
|
196
|
+
file: {
|
|
197
|
+
name: response.content.fileName || "file",
|
|
198
|
+
mimeType: response.content.mimeType || "application/octet-stream",
|
|
199
|
+
uri: response.content.mediaUrl,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// Create TaskArtifactUpdateEvent
|
|
204
|
+
const artifactEvent = {
|
|
205
|
+
taskId: taskId,
|
|
206
|
+
kind: "artifact-update",
|
|
207
|
+
append: false,
|
|
208
|
+
lastChunk: true,
|
|
209
|
+
final: true, // Mark as final since this is the complete response
|
|
210
|
+
artifact: {
|
|
211
|
+
artifactId: artifactId,
|
|
212
|
+
parts: parts,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
jsonrpc: "2.0",
|
|
217
|
+
id: response.messageId,
|
|
218
|
+
result: artifactEvent,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
113
221
|
/**
|
|
114
222
|
* Send generic outbound message
|
|
115
223
|
*/
|
|
@@ -212,25 +320,8 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
212
320
|
*/
|
|
213
321
|
handleClearMessage(message) {
|
|
214
322
|
console.log(`Received clear message for session: ${message.sessionId}`);
|
|
215
|
-
// Send success response according to A2A spec
|
|
216
|
-
|
|
217
|
-
sessionId: message.sessionId,
|
|
218
|
-
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
219
|
-
timestamp: Date.now(),
|
|
220
|
-
agentId: this.config.agentId,
|
|
221
|
-
sender: {
|
|
222
|
-
id: this.config.agentId,
|
|
223
|
-
name: "OpenClaw Agent",
|
|
224
|
-
type: "agent",
|
|
225
|
-
},
|
|
226
|
-
content: {
|
|
227
|
-
type: "text",
|
|
228
|
-
text: "Context cleared successfully",
|
|
229
|
-
},
|
|
230
|
-
status: "success",
|
|
231
|
-
};
|
|
232
|
-
// Send response
|
|
233
|
-
this.sendResponse(response, message.id, message.sessionId).catch(error => {
|
|
323
|
+
// Send success response according to A2A spec using the correct format
|
|
324
|
+
this.sendClearContextResponse(message.id, message.sessionId, true).catch(error => {
|
|
234
325
|
console.error("Failed to send clear response:", error);
|
|
235
326
|
});
|
|
236
327
|
// Emit clear event for application to handle
|
|
@@ -258,38 +349,27 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
258
349
|
* Send tasks/cancel success response
|
|
259
350
|
*/
|
|
260
351
|
async sendCancelSuccessResponse(sessionId, taskId, requestId) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
264
|
-
timestamp: Date.now(),
|
|
265
|
-
agentId: this.config.agentId,
|
|
266
|
-
sender: {
|
|
267
|
-
id: this.config.agentId,
|
|
268
|
-
name: "OpenClaw Agent",
|
|
269
|
-
type: "agent",
|
|
270
|
-
},
|
|
271
|
-
content: {
|
|
272
|
-
type: "text",
|
|
273
|
-
text: "Task cancelled successfully",
|
|
274
|
-
},
|
|
275
|
-
status: "success",
|
|
276
|
-
};
|
|
277
|
-
await this.sendResponse(response, requestId, sessionId);
|
|
352
|
+
// Use the dedicated tasks cancel response method with correct format
|
|
353
|
+
await this.sendTasksCancelResponse(requestId, sessionId, true);
|
|
278
354
|
// Remove from active tasks
|
|
279
355
|
this.activeTasks.delete(taskId);
|
|
280
356
|
}
|
|
281
357
|
/**
|
|
282
|
-
* Type guard for A2A request messages
|
|
358
|
+
* Type guard for A2A request messages (JSON-RPC 2.0 format)
|
|
283
359
|
*/
|
|
284
360
|
isA2ARequestMessage(data) {
|
|
285
361
|
return data &&
|
|
286
362
|
typeof data.agentId === "string" &&
|
|
287
363
|
typeof data.sessionId === "string" &&
|
|
364
|
+
data.jsonrpc === "2.0" &&
|
|
288
365
|
typeof data.id === "string" &&
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
data.
|
|
292
|
-
data.
|
|
366
|
+
data.method === "message/stream" &&
|
|
367
|
+
data.params &&
|
|
368
|
+
typeof data.params.id === "string" &&
|
|
369
|
+
typeof data.params.sessionId === "string" &&
|
|
370
|
+
data.params.message &&
|
|
371
|
+
typeof data.params.message.role === "string" &&
|
|
372
|
+
Array.isArray(data.params.message.parts);
|
|
293
373
|
}
|
|
294
374
|
/**
|
|
295
375
|
* Start protocol-level heartbeat (ping/pong)
|