@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 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: message.content.text || "",
225
- From: message.sender.id,
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.messageId,
230
- Timestamp: message.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.name,
235
- SenderId: message.sender.id,
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
- messageId: string;
6
- timestamp: number;
7
- sender: {
6
+ method: "message/stream";
7
+ params: {
8
8
  id: string;
9
- name?: string;
10
- type: "user" | "agent";
11
- };
12
- content: {
13
- type: "text" | "image" | "audio" | "video" | "file";
14
- text?: string;
15
- mediaUrl?: string;
16
- fileName?: string;
17
- fileSize?: number;
18
- mimeType?: string;
19
- };
20
- context?: {
21
- conversationId?: string;
22
- threadId?: string;
23
- replyToMessageId?: string;
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;
@@ -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(response),
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
- const response = {
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
- const response = {
262
- sessionId: sessionId,
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
- typeof data.messageId === "string" &&
290
- typeof data.timestamp === "number" &&
291
- data.sender &&
292
- data.content;
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",