@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 +82 -10
- package/dist/websocket.d.ts +10 -1
- package/dist/websocket.js +16 -7
- package/package.json +1 -1
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 (
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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:
|
|
291
|
+
text: accumulatedText, // Complete content
|
|
271
292
|
},
|
|
272
293
|
status: "success",
|
|
273
294
|
};
|
|
274
295
|
const conn = runtime.getConnection();
|
|
275
296
|
if (conn) {
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
283
|
-
|
|
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
|
package/dist/websocket.d.ts
CHANGED
|
@@ -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:
|
|
203
|
-
lastChunk:
|
|
204
|
-
final:
|
|
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,
|