@ynhcj/xiaoyi 2.0.8 → 2.1.0
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 +52 -8
- package/dist/websocket.d.ts +10 -1
- package/dist/websocket.js +16 -12
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -245,16 +245,22 @@ 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
|
+
// Track response state for streaming
|
|
251
|
+
let accumulatedText = "";
|
|
252
|
+
let taskId = runtime.getTaskIdForSession(message.sessionId) || `task_${Date.now()}`;
|
|
253
|
+
let frameCount = 0;
|
|
250
254
|
await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
251
255
|
ctx: msgContext,
|
|
252
256
|
cfg: config,
|
|
253
257
|
dispatcherOptions: {
|
|
254
258
|
deliver: async (payload) => {
|
|
255
259
|
console.log("XiaoYi: Delivering final response:", payload.text?.substring(0, 100) + "...");
|
|
256
|
-
//
|
|
257
|
-
const
|
|
260
|
+
// Get the complete text
|
|
261
|
+
const completeText = payload.text || "";
|
|
262
|
+
accumulatedText = completeText;
|
|
263
|
+
// Send FINAL frame with complete content
|
|
258
264
|
const response = {
|
|
259
265
|
sessionId: message.sessionId,
|
|
260
266
|
messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
@@ -267,20 +273,58 @@ exports.xiaoyiPlugin = {
|
|
|
267
273
|
},
|
|
268
274
|
content: {
|
|
269
275
|
type: "text",
|
|
270
|
-
text:
|
|
276
|
+
text: accumulatedText, // Complete content
|
|
271
277
|
},
|
|
272
278
|
status: "success",
|
|
273
279
|
};
|
|
274
280
|
const conn = runtime.getConnection();
|
|
275
281
|
if (conn) {
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
// Use append=false for final frame (complete content)
|
|
283
|
+
await conn.sendResponse(response, taskId, message.sessionId, true, false);
|
|
284
|
+
console.log(`XiaoYi: Final frame sent (#${++frameCount}, isFinal=true, append=false, total length: ${accumulatedText.length})`);
|
|
278
285
|
}
|
|
279
286
|
},
|
|
287
|
+
onIdle: async () => {
|
|
288
|
+
console.log("XiaoYi: OpenClaw processing complete");
|
|
289
|
+
// All frames already sent, nothing more to do
|
|
290
|
+
},
|
|
280
291
|
},
|
|
281
292
|
replyOptions: {
|
|
282
|
-
|
|
283
|
-
|
|
293
|
+
// Enable streaming responses through onPartialReply callback
|
|
294
|
+
onPartialReply: async (payload) => {
|
|
295
|
+
const newText = payload.text || "";
|
|
296
|
+
if (!newText) {
|
|
297
|
+
return; // Skip empty responses
|
|
298
|
+
}
|
|
299
|
+
console.log("XiaoYi: Streaming partial response:", newText.substring(0, 30) + "...");
|
|
300
|
+
// Calculate delta text (增量部分)
|
|
301
|
+
const previousLength = accumulatedText.length;
|
|
302
|
+
accumulatedText = newText;
|
|
303
|
+
const deltaText = newText.slice(previousLength);
|
|
304
|
+
// Send PARTIAL frame with delta content
|
|
305
|
+
const partialResponse = {
|
|
306
|
+
sessionId: message.sessionId,
|
|
307
|
+
messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
agentId: resolvedAccount.config.agentId,
|
|
310
|
+
sender: {
|
|
311
|
+
id: resolvedAccount.config.agentId,
|
|
312
|
+
name: "OpenClaw Agent",
|
|
313
|
+
type: "agent",
|
|
314
|
+
},
|
|
315
|
+
content: {
|
|
316
|
+
type: "text",
|
|
317
|
+
text: deltaText || newText, // Send delta only (or full if delta is empty)
|
|
318
|
+
},
|
|
319
|
+
status: "processing", // Mark as processing
|
|
320
|
+
};
|
|
321
|
+
const conn = runtime.getConnection();
|
|
322
|
+
if (conn) {
|
|
323
|
+
// Use append=true for streaming frames (服务端会追加)
|
|
324
|
+
await conn.sendResponse(partialResponse, taskId, message.sessionId, false, true);
|
|
325
|
+
console.log(`XiaoYi: Partial frame sent (#${++frameCount}, isFinal=false, append=true, delta length: ${deltaText.length || newText.length})`);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
284
328
|
},
|
|
285
329
|
});
|
|
286
330
|
}
|
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
|
@@ -42,11 +42,7 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
42
42
|
});
|
|
43
43
|
this.setupWebSocketHandlers();
|
|
44
44
|
return new Promise((resolve, reject) => {
|
|
45
|
-
const timeout = setTimeout(() => {
|
|
46
|
-
reject(new Error("Connection timeout"));
|
|
47
|
-
}, 30000); // Increased timeout to 30 seconds
|
|
48
45
|
this.ws.once("open", () => {
|
|
49
|
-
clearTimeout(timeout);
|
|
50
46
|
this.state.connected = true;
|
|
51
47
|
this.state.authenticated = true; // Authenticated via headers
|
|
52
48
|
this.state.reconnectAttempts = 0;
|
|
@@ -59,7 +55,6 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
59
55
|
resolve();
|
|
60
56
|
});
|
|
61
57
|
this.ws.once("error", (error) => {
|
|
62
|
-
clearTimeout(timeout);
|
|
63
58
|
reject(error);
|
|
64
59
|
});
|
|
65
60
|
});
|
|
@@ -97,13 +92,18 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
97
92
|
/**
|
|
98
93
|
* Send A2A response message (converts to JSON-RPC 2.0 format)
|
|
99
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)
|
|
100
100
|
*/
|
|
101
|
-
async sendResponse(response, taskId, sessionId) {
|
|
101
|
+
async sendResponse(response, taskId, sessionId, isFinal = true, append = false) {
|
|
102
102
|
if (!this.isReady()) {
|
|
103
103
|
throw new Error("WebSocket not ready");
|
|
104
104
|
}
|
|
105
105
|
// Convert A2AResponseMessage to A2A JSON-RPC 2.0 format
|
|
106
|
-
const jsonRpcResponse = this.convertToJsonRpcFormat(response, taskId);
|
|
106
|
+
const jsonRpcResponse = this.convertToJsonRpcFormat(response, taskId, isFinal, append);
|
|
107
107
|
const message = {
|
|
108
108
|
msgType: "agent_response",
|
|
109
109
|
agentId: this.config.agentId,
|
|
@@ -167,8 +167,12 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
169
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)
|
|
170
174
|
*/
|
|
171
|
-
convertToJsonRpcFormat(response, taskId) {
|
|
175
|
+
convertToJsonRpcFormat(response, taskId, isFinal = true, append = false) {
|
|
172
176
|
// Generate artifact ID
|
|
173
177
|
const artifactId = `artifact_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
174
178
|
// Check if there's an error
|
|
@@ -200,13 +204,13 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
|
|
|
200
204
|
},
|
|
201
205
|
});
|
|
202
206
|
}
|
|
203
|
-
// Create TaskArtifactUpdateEvent
|
|
207
|
+
// Create TaskArtifactUpdateEvent with configurable flags
|
|
204
208
|
const artifactEvent = {
|
|
205
209
|
taskId: taskId,
|
|
206
210
|
kind: "artifact-update",
|
|
207
|
-
append:
|
|
208
|
-
lastChunk:
|
|
209
|
-
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
|
|
210
214
|
artifact: {
|
|
211
215
|
artifactId: artifactId,
|
|
212
216
|
parts: parts,
|