@ynhcj/xiaoyi 2.0.9 → 2.1.1

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
@@ -245,16 +245,37 @@ exports.xiaoyiPlugin = {
245
245
  SenderId: senderId,
246
246
  OriginatingChannel: "xiaoyi",
247
247
  };
248
- // Use the correct API to dispatch the message (non-streaming mode)
248
+ // Use the correct API to dispatch the message (streaming mode enabled)
249
249
  try {
250
+ console.log("\n" + "=".repeat(60));
251
+ console.log(`XiaoYi: [STREAMING] Starting message processing`);
252
+ console.log(` Session: ${message.sessionId}`);
253
+ console.log(` Task ID: ${message.params.id}`);
254
+ console.log(` User input: ${bodyText.substring(0, 50)}${bodyText.length > 50 ? "..." : ""}`);
255
+ console.log("=".repeat(60) + "\n");
256
+ // Track response state for streaming
257
+ let accumulatedText = "";
258
+ let taskId = runtime.getTaskIdForSession(message.sessionId) || `task_${Date.now()}`;
259
+ let frameCount = 0;
260
+ let partialCount = 0;
261
+ const startTime = Date.now();
250
262
  await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
251
263
  ctx: msgContext,
252
264
  cfg: config,
253
265
  dispatcherOptions: {
254
266
  deliver: async (payload) => {
255
- console.log("XiaoYi: Delivering final response:", payload.text?.substring(0, 100) + "...");
256
- // Send final response back through WebSocket (non-streaming, complete message)
257
- const taskId = runtime.getTaskIdForSession(message.sessionId) || `task_${Date.now()}`;
267
+ const elapsed = Date.now() - startTime;
268
+ const completeText = payload.text || "";
269
+ accumulatedText = completeText;
270
+ console.log("\n" + "-".repeat(60));
271
+ console.log(`XiaoYi: [STREAMING] DELIVER callback triggered`);
272
+ console.log(` Elapsed: ${elapsed}ms`);
273
+ console.log(` Frame: #${++frameCount}`);
274
+ console.log(` Total length: ${completeText.length} chars`);
275
+ console.log(` Partial frames sent: ${partialCount}`);
276
+ console.log(` Content preview: ${completeText.substring(0, 100)}...`);
277
+ console.log("-".repeat(60) + "\n");
278
+ // Send FINAL frame with complete content
258
279
  const response = {
259
280
  sessionId: message.sessionId,
260
281
  messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -267,25 +288,76 @@ exports.xiaoyiPlugin = {
267
288
  },
268
289
  content: {
269
290
  type: "text",
270
- text: payload.text || "",
291
+ text: accumulatedText, // Complete content
271
292
  },
272
293
  status: "success",
273
294
  };
274
295
  const conn = runtime.getConnection();
275
296
  if (conn) {
276
- await conn.sendResponse(response, taskId, message.sessionId);
277
- console.log("XiaoYi: Final response sent successfully (non-streaming mode)");
297
+ // Use append=false for final frame (complete content)
298
+ await conn.sendResponse(response, taskId, message.sessionId, true, false);
299
+ console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false, length=${accumulatedText.length})\n`);
278
300
  }
279
301
  },
302
+ onIdle: async () => {
303
+ const elapsed = Date.now() - startTime;
304
+ console.log("\n" + "=".repeat(60));
305
+ console.log(`XiaoYi: [STREAMING] Processing complete`);
306
+ console.log(` Total time: ${elapsed}ms`);
307
+ console.log(` Total frames: ${frameCount}`);
308
+ console.log(` Partial frames: ${partialCount}`);
309
+ console.log(` Final length: ${accumulatedText.length} chars`);
310
+ console.log("=".repeat(60) + "\n");
311
+ },
280
312
  },
281
313
  replyOptions: {
282
- // Disable streaming by removing onPartialReply callback
283
- // This ensures all responses are buffered and delivered only once at the end
314
+ // Enable streaming responses through onPartialReply callback
315
+ onPartialReply: async (payload) => {
316
+ const elapsed = Date.now() - startTime;
317
+ const newText = payload.text || "";
318
+ if (!newText) {
319
+ console.log(`XiaoYi: [STREAMING] Skipping empty partial response at ${elapsed}ms`);
320
+ return;
321
+ }
322
+ // Calculate delta text (增量部分)
323
+ const previousLength = accumulatedText.length;
324
+ accumulatedText = newText;
325
+ const deltaText = newText.slice(previousLength);
326
+ console.log(`\n>>> XiaoYi: [PARTIAL] Frame #${++partialCount} at ${elapsed}ms`);
327
+ console.log(` Previous length: ${previousLength}`);
328
+ console.log(` New length: ${newText.length}`);
329
+ console.log(` Delta length: ${deltaText.length}`);
330
+ console.log(` Delta content: "${deltaText}"`);
331
+ console.log(` Accumulated so far: "${newText}"`);
332
+ // Send PARTIAL frame with delta content
333
+ const partialResponse = {
334
+ sessionId: message.sessionId,
335
+ messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
336
+ timestamp: Date.now(),
337
+ agentId: resolvedAccount.config.agentId,
338
+ sender: {
339
+ id: resolvedAccount.config.agentId,
340
+ name: "OpenClaw Agent",
341
+ type: "agent",
342
+ },
343
+ content: {
344
+ type: "text",
345
+ text: deltaText || newText, // Send delta only (or full if delta is empty)
346
+ },
347
+ status: "processing", // Mark as processing
348
+ };
349
+ const conn = runtime.getConnection();
350
+ if (conn) {
351
+ // Use append=true for streaming frames (服务端会追加)
352
+ await conn.sendResponse(partialResponse, taskId, message.sessionId, false, true);
353
+ console.log(` ✓ SENT (isFinal=false, append=true)\n`);
354
+ }
355
+ },
284
356
  },
285
357
  });
286
358
  }
287
359
  catch (error) {
288
- console.error("Error dispatching message:", error);
360
+ console.error("XiaoYi: [ERROR] Error dispatching message:", error);
289
361
  }
290
362
  });
291
363
  // Setup cancel handler
@@ -25,8 +25,13 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
25
25
  /**
26
26
  * Send A2A response message (converts to JSON-RPC 2.0 format)
27
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)
28
33
  */
29
- sendResponse(response: A2AResponseMessage, taskId: string, sessionId: string): Promise<void>;
34
+ sendResponse(response: A2AResponseMessage, taskId: string, sessionId: string, isFinal?: boolean, append?: boolean): Promise<void>;
30
35
  /**
31
36
  * Send A2A clear context response (uses specific clear context format)
32
37
  * Reference: https://developer.huawei.com/consumer/cn/doc/service/clear-context-0000002537681163
@@ -39,6 +44,10 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
39
44
  sendTasksCancelResponse(requestId: string, sessionId: string, success?: boolean): Promise<void>;
40
45
  /**
41
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)
42
51
  */
43
52
  private convertToJsonRpcFormat;
44
53
  /**
package/dist/websocket.js CHANGED
@@ -92,13 +92,18 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
92
92
  /**
93
93
  * Send A2A response message (converts to JSON-RPC 2.0 format)
94
94
  * This method is for regular agent responses only
95
+ * @param response - The response message
96
+ * @param taskId - The task ID
97
+ * @param sessionId - The session ID
98
+ * @param isFinal - Whether this is the final frame (default: true)
99
+ * @param append - Whether to append to previous content (default: false for complete content)
95
100
  */
96
- async sendResponse(response, taskId, sessionId) {
101
+ async sendResponse(response, taskId, sessionId, isFinal = true, append = false) {
97
102
  if (!this.isReady()) {
98
103
  throw new Error("WebSocket not ready");
99
104
  }
100
105
  // Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
101
- const jsonRpcResponse = this.convertToJsonRpcFormat(response, taskId);
106
+ const jsonRpcResponse = this.convertToJsonRpcFormat(response, taskId, isFinal, append);
102
107
  const message = {
103
108
  msgType: "agent_response",
104
109
  agentId: this.config.agentId,
@@ -162,8 +167,12 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
162
167
  }
163
168
  /**
164
169
  * Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
170
+ * @param response - The response message
171
+ * @param taskId - The task ID
172
+ * @param isFinal - Whether this is the final frame (default: true)
173
+ * @param append - Whether to append to previous content (default: false)
165
174
  */
166
- convertToJsonRpcFormat(response, taskId) {
175
+ convertToJsonRpcFormat(response, taskId, isFinal = true, append = false) {
167
176
  // Generate artifact ID
168
177
  const artifactId = `artifact_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
169
178
  // Check if there's an error
@@ -195,13 +204,13 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
195
204
  },
196
205
  });
197
206
  }
198
- // Create TaskArtifactUpdateEvent
207
+ // Create TaskArtifactUpdateEvent with configurable flags
199
208
  const artifactEvent = {
200
209
  taskId: taskId,
201
210
  kind: "artifact-update",
202
- append: false,
203
- lastChunk: true,
204
- final: true, // Mark as final since this is the complete response
211
+ append: append, // Controls whether to append or replace content
212
+ lastChunk: isFinal, // Set based on isFinal parameter
213
+ final: isFinal, // Set based on isFinal parameter
205
214
  artifact: {
206
215
  artifactId: artifactId,
207
216
  parts: parts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.0.9",
3
+ "version": "2.1.1",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",