@ynhcj/xiaoyi-channel 0.0.159-beta → 0.0.159-next
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/src/bot.js +59 -5
- package/dist/src/cron-query-handler.d.ts +1 -11
- package/dist/src/cron-query-handler.js +89 -2
- package/dist/src/cspl/call_api.d.ts +1 -1
- package/dist/src/cspl/call_api.js +2 -2
- package/dist/src/cspl/config.js +30 -10
- package/dist/src/cspl/constants.d.ts +3 -0
- package/dist/src/cspl/constants.js +5 -0
- package/dist/src/cspl/sentinel_hook.js +26 -7
- package/dist/src/cspl/utils.d.ts +9 -3
- package/dist/src/cspl/utils.js +17 -11
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +102 -0
- package/dist/src/formatter.d.ts +30 -0
- package/dist/src/formatter.js +65 -10
- package/dist/src/monitor.js +35 -23
- package/dist/src/parser.d.ts +6 -0
- package/dist/src/parser.js +47 -1
- package/dist/src/provider.js +51 -17
- package/dist/src/reply-dispatcher.js +67 -21
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/self-evolution-handler.js +12 -1
- package/dist/src/tools/calendar-tool.js +1 -1
- package/dist/src/tools/call-device-tool.js +0 -3
- package/dist/src/tools/call-phone-tool.js +1 -1
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
- package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
- package/dist/src/tools/create-alarm-tool.js +1 -1
- package/dist/src/tools/create-all-tools.js +8 -8
- package/dist/src/tools/delete-alarm-tool.js +1 -1
- package/dist/src/tools/device-tool-map.js +1 -0
- package/dist/src/tools/discover-cross-devices-tool.js +1 -1
- package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
- package/dist/src/tools/display-a2ui-card-tool.js +85 -0
- package/dist/src/tools/get-device-file-tool-schema.js +2 -3
- package/dist/src/tools/location-tool.js +1 -1
- package/dist/src/tools/modify-alarm-tool.js +1 -1
- package/dist/src/tools/modify-note-tool.js +1 -1
- package/dist/src/tools/note-tool.js +1 -1
- package/dist/src/tools/query-app-message-tool.js +1 -1
- package/dist/src/tools/query-memory-data-tool.js +1 -1
- package/dist/src/tools/query-todo-task-tool.js +1 -1
- package/dist/src/tools/save-file-to-phone-tool.js +1 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +1 -1
- package/dist/src/tools/schema-tool-factory.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +1 -1
- package/dist/src/tools/search-calendar-tool.js +1 -1
- package/dist/src/tools/search-contact-tool.js +1 -1
- package/dist/src/tools/search-email-tool.js +1 -1
- package/dist/src/tools/search-file-tool.js +5 -10
- package/dist/src/tools/search-note-tool.js +1 -1
- package/dist/src/tools/search-photo-gallery-tool.js +1 -1
- package/dist/src/tools/send-cross-device-task-tool.js +18 -22
- package/dist/src/tools/send-email-tool.js +1 -1
- package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +35 -6
- package/dist/src/tools/send-html-card-tool.d.ts +7 -0
- package/dist/src/tools/send-html-card-tool.js +113 -0
- package/dist/src/tools/session-manager.d.ts +6 -2
- package/dist/src/tools/session-manager.js +42 -7
- package/dist/src/tools/upload-file-tool.d.ts +1 -1
- package/dist/src/tools/upload-file-tool.js +3 -17
- package/dist/src/tools/upload-photo-tool.js +1 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +2 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +6 -1
- package/dist/src/types.d.ts +10 -3
- package/dist/src/websocket.js +29 -8
- package/package.json +1 -1
package/dist/src/file-upload.js
CHANGED
|
@@ -235,6 +235,108 @@ export class XYFileUploadService {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Upload a file and return a preview-able URL (needPreview=true).
|
|
240
|
+
* Same as uploadFileAndGetUrl but adds needPreview flag to get a directly viewable URL.
|
|
241
|
+
*/
|
|
242
|
+
async uploadFileAndGetPreviewUrl(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
|
|
243
|
+
let localFilePath = filePath;
|
|
244
|
+
let isTempFile = false;
|
|
245
|
+
try {
|
|
246
|
+
// Handle remote URLs by downloading first
|
|
247
|
+
if (isRemoteUrl(filePath)) {
|
|
248
|
+
localFilePath = await downloadToTempFile(filePath);
|
|
249
|
+
isTempFile = true;
|
|
250
|
+
}
|
|
251
|
+
// Read file
|
|
252
|
+
const fileBuffer = await fs.readFile(localFilePath);
|
|
253
|
+
const fileName = path.basename(localFilePath);
|
|
254
|
+
const fileSha256 = calculateSHA256(fileBuffer);
|
|
255
|
+
const fileSize = fileBuffer.length;
|
|
256
|
+
// Phase 1: Prepare
|
|
257
|
+
logger.log(`[XY File Upload] Phase 1 (preview): Prepare upload for ${fileName}`);
|
|
258
|
+
const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: {
|
|
261
|
+
"Content-Type": "application/json",
|
|
262
|
+
"x-uid": this.uid,
|
|
263
|
+
"x-api-key": this.apiKey,
|
|
264
|
+
"x-request-from": "openclaw",
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({
|
|
267
|
+
objectType,
|
|
268
|
+
fileName,
|
|
269
|
+
fileSha256,
|
|
270
|
+
fileSize,
|
|
271
|
+
fileOwnerInfo: {
|
|
272
|
+
uid: this.uid,
|
|
273
|
+
teamId: this.uid,
|
|
274
|
+
},
|
|
275
|
+
useEdge: false,
|
|
276
|
+
}),
|
|
277
|
+
});
|
|
278
|
+
if (!prepareResp.ok) {
|
|
279
|
+
throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
|
|
280
|
+
}
|
|
281
|
+
const prepareData = await prepareResp.json();
|
|
282
|
+
if (prepareData.code !== "0") {
|
|
283
|
+
throw new Error(`Prepare failed: ${prepareData.desc}`);
|
|
284
|
+
}
|
|
285
|
+
const { objectId, draftId, uploadInfos } = prepareData;
|
|
286
|
+
logger.log(`[XY File Upload] Prepare (preview) complete: objectId=${objectId}, draftId=${draftId}`);
|
|
287
|
+
// Phase 2: Upload
|
|
288
|
+
logger.log(`[XY File Upload] Phase 2 (preview): Upload file data`);
|
|
289
|
+
const uploadInfo = uploadInfos[0];
|
|
290
|
+
const uploadResp = await fetch(uploadInfo.url, {
|
|
291
|
+
method: uploadInfo.method,
|
|
292
|
+
headers: uploadInfo.headers,
|
|
293
|
+
body: fileBuffer,
|
|
294
|
+
});
|
|
295
|
+
if (!uploadResp.ok) {
|
|
296
|
+
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
297
|
+
}
|
|
298
|
+
logger.log(`[XY File Upload] Upload (preview) complete`);
|
|
299
|
+
// Phase 3: CompleteAndQuery with needPreview=true
|
|
300
|
+
logger.log(`[XY File Upload] Phase 3 (preview): CompleteAndQuery with needPreview=true`);
|
|
301
|
+
const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/completeAndQuery`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
"Content-Type": "application/json",
|
|
305
|
+
"x-uid": this.uid,
|
|
306
|
+
"x-api-key": this.apiKey,
|
|
307
|
+
"x-request-from": "openclaw",
|
|
308
|
+
},
|
|
309
|
+
body: JSON.stringify({
|
|
310
|
+
objectId,
|
|
311
|
+
draftId,
|
|
312
|
+
needPreview: true,
|
|
313
|
+
expireTime: 259200,
|
|
314
|
+
}),
|
|
315
|
+
});
|
|
316
|
+
if (!completeResp.ok) {
|
|
317
|
+
throw new Error(`CompleteAndQuery (preview) failed: HTTP ${completeResp.status}`);
|
|
318
|
+
}
|
|
319
|
+
const completeData = await completeResp.json();
|
|
320
|
+
const fileUrl = completeData?.fileDetailInfo?.url || "";
|
|
321
|
+
if (!fileUrl) {
|
|
322
|
+
throw new Error("No file URL returned from completeAndQuery (preview)");
|
|
323
|
+
}
|
|
324
|
+
logger.log(`[XY File Upload] File upload with preview URL successful`);
|
|
325
|
+
return fileUrl;
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
logger.error(`[XY File Upload] File upload with preview URL failed for ${filePath}:`, error);
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
if (isTempFile) {
|
|
333
|
+
try {
|
|
334
|
+
await fs.unlink(localFilePath);
|
|
335
|
+
}
|
|
336
|
+
catch { }
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
238
340
|
/**
|
|
239
341
|
* Upload multiple files and return their file IDs.
|
|
240
342
|
*/
|
package/dist/src/formatter.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface SendA2AResponseParams {
|
|
|
17
17
|
}>;
|
|
18
18
|
errorCode?: number | string;
|
|
19
19
|
errorMessage?: string;
|
|
20
|
+
log?: boolean;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
23
|
* Send an A2A artifact update response.
|
|
@@ -81,6 +82,35 @@ export interface SendCommandParams {
|
|
|
81
82
|
* listening in the calling tool works unchanged.
|
|
82
83
|
*/
|
|
83
84
|
export declare function sendCommand(params: SendCommandParams): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Parameters for sending a card (e.g., HTML H5 card).
|
|
87
|
+
*/
|
|
88
|
+
export interface SendCardParams {
|
|
89
|
+
config: XYChannelConfig;
|
|
90
|
+
sessionId: string;
|
|
91
|
+
taskId: string;
|
|
92
|
+
messageId: string;
|
|
93
|
+
/** toolCallId from the tool's execute() — used for cron detection via hook-set Map. */
|
|
94
|
+
toolCallId?: string;
|
|
95
|
+
/** When true, the artifact-update is sent with final=true. Default: false. */
|
|
96
|
+
final?: boolean;
|
|
97
|
+
/** Array of card data objects to send. */
|
|
98
|
+
cardsInfo: CardDataObject[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Card data object for sending display cards.
|
|
102
|
+
*/
|
|
103
|
+
export interface CardDataObject {
|
|
104
|
+
cardName: string;
|
|
105
|
+
cardData: Record<string, any>;
|
|
106
|
+
displayType: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Send a card (e.g., HTML H5 card) as an artifact update (final=false).
|
|
110
|
+
*
|
|
111
|
+
* Cron-aware: same routing logic as sendCommand.
|
|
112
|
+
*/
|
|
113
|
+
export declare function sendCard(params: SendCardParams): Promise<void>;
|
|
84
114
|
/**
|
|
85
115
|
* Parameters for sending a clearContext response.
|
|
86
116
|
*/
|
package/dist/src/formatter.js
CHANGED
|
@@ -42,7 +42,7 @@ function buildTextPreview(text) {
|
|
|
42
42
|
* Send an A2A artifact update response.
|
|
43
43
|
*/
|
|
44
44
|
export async function sendA2AResponse(params) {
|
|
45
|
-
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
|
|
45
|
+
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, log: shouldLog = true } = params;
|
|
46
46
|
const log = logger.withContext(sessionId, taskId);
|
|
47
47
|
// 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
|
|
48
48
|
const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
|
|
@@ -97,11 +97,14 @@ export async function sendA2AResponse(params) {
|
|
|
97
97
|
taskId,
|
|
98
98
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
99
99
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
if (shouldLog) {
|
|
101
|
+
const redactedText = redactSensitiveText(bridgedText ?? "");
|
|
102
|
+
log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
|
|
103
|
+
}
|
|
103
104
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
104
|
-
|
|
105
|
+
if (shouldLog) {
|
|
106
|
+
log.log(`[A2A_RESPONSE] Message sent successfully`);
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
110
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -214,12 +217,11 @@ export async function sendCommand(params) {
|
|
|
214
217
|
if (commands.length === 0) {
|
|
215
218
|
throw new Error("sendCommand requires command or commands.");
|
|
216
219
|
}
|
|
217
|
-
// ── Cron mode:
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
+
// ── Cron mode: disabled ────────────────────────────────────────
|
|
221
|
+
// sendCommandViaPush is disabled in this version. Cron-triggered
|
|
222
|
+
// tool calls that try to send commands will be rejected.
|
|
220
223
|
if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
|
|
221
|
-
|
|
222
|
-
return sendCommandViaPush({ config, command: commands[0] });
|
|
224
|
+
throw new Error("sendCommandViaPush is disabled in this version");
|
|
223
225
|
}
|
|
224
226
|
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
225
227
|
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
@@ -269,6 +271,59 @@ export async function sendCommand(params) {
|
|
|
269
271
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
270
272
|
log.log(`[A2A_COMMAND] Command sent successfully`);
|
|
271
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Send a card (e.g., HTML H5 card) as an artifact update (final=false).
|
|
276
|
+
*
|
|
277
|
+
* Cron-aware: same routing logic as sendCommand.
|
|
278
|
+
*/
|
|
279
|
+
export async function sendCard(params) {
|
|
280
|
+
const { config, sessionId, taskId, messageId, toolCallId } = params;
|
|
281
|
+
// ── Cron mode: route through push channel ──────────────────────
|
|
282
|
+
if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
|
|
283
|
+
throw new Error("sendCard does not support cron mode");
|
|
284
|
+
}
|
|
285
|
+
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
286
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
287
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
288
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
289
|
+
// Build artifact update with cardsInfo as data
|
|
290
|
+
const artifact = {
|
|
291
|
+
taskId: currentTaskId,
|
|
292
|
+
kind: "artifact-update",
|
|
293
|
+
append: false,
|
|
294
|
+
lastChunk: true,
|
|
295
|
+
final: params.final ?? false,
|
|
296
|
+
artifact: {
|
|
297
|
+
artifactId: uuidv4(),
|
|
298
|
+
parts: [
|
|
299
|
+
{
|
|
300
|
+
kind: "data",
|
|
301
|
+
data: {
|
|
302
|
+
cardsInfo: params.cardsInfo,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
// Build JSON-RPC response
|
|
309
|
+
const jsonRpcResponse = {
|
|
310
|
+
jsonrpc: "2.0",
|
|
311
|
+
id: currentMessageId,
|
|
312
|
+
result: artifact,
|
|
313
|
+
};
|
|
314
|
+
// Send via WebSocket
|
|
315
|
+
const wsManager = getXYWebSocketManager(config);
|
|
316
|
+
const outboundMessage = {
|
|
317
|
+
msgType: "agent_response",
|
|
318
|
+
agentId: config.agentId,
|
|
319
|
+
sessionId,
|
|
320
|
+
taskId: currentTaskId,
|
|
321
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
322
|
+
};
|
|
323
|
+
log.log(`[A2A_CARD] Sending card`);
|
|
324
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
325
|
+
log.log(`[A2A_CARD] Card sent successfully`);
|
|
326
|
+
}
|
|
272
327
|
/**
|
|
273
328
|
* Send a clearContext response.
|
|
274
329
|
*/
|
package/dist/src/monitor.js
CHANGED
|
@@ -116,35 +116,45 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
118
|
// 🔑 核心改造:检测steer模式
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
// clearContext / tasks/cancel 没有 params,跳过 parseA2AMessage 直接入队
|
|
120
|
+
const messageMethod = message.method;
|
|
121
|
+
if (messageMethod === "clearContext" || messageMethod === "clear_context"
|
|
122
|
+
|| messageMethod === "tasks/cancel" || messageMethod === "tasks_cancel") {
|
|
123
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
124
|
+
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
125
|
+
activeMessages.delete(messageKey);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
const parsed = parseA2AMessage(message);
|
|
131
|
+
const steerMode = cfg.messages?.queue?.mode === "steer";
|
|
132
|
+
const hasActiveRun = hasActiveTask(parsed.sessionId);
|
|
133
|
+
if (steerMode && hasActiveRun) {
|
|
134
|
+
// Steer模式且有活跃任务:不入队列,直接并发执行
|
|
135
|
+
logger.log(`[MONITOR-HANDLER] STEER MODE: Executing concurrently for messageKey=${messageKey}`);
|
|
136
|
+
void task().catch((err) => {
|
|
137
|
+
logger.error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
|
|
138
|
+
activeMessages.delete(messageKey);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// 正常模式:入队列串行执行
|
|
143
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
144
|
+
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
145
|
+
activeMessages.delete(messageKey);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
131
148
|
}
|
|
132
|
-
|
|
133
|
-
//
|
|
149
|
+
catch (parseErr) {
|
|
150
|
+
// 解析失败,回退到正常队列模式
|
|
151
|
+
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
134
152
|
void enqueue(sessionId, task).catch((err) => {
|
|
135
153
|
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
136
154
|
activeMessages.delete(messageKey);
|
|
137
155
|
});
|
|
138
156
|
}
|
|
139
157
|
}
|
|
140
|
-
catch (parseErr) {
|
|
141
|
-
// 解析失败,回退到正常队列模式
|
|
142
|
-
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
143
|
-
void enqueue(sessionId, task).catch((err) => {
|
|
144
|
-
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
145
|
-
activeMessages.delete(messageKey);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
158
|
};
|
|
149
159
|
const connectedHandler = (serverId) => {
|
|
150
160
|
if (!loggedServers.has(serverId)) {
|
|
@@ -175,7 +185,9 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
175
185
|
};
|
|
176
186
|
const selfEvolutionHandler = (context) => {
|
|
177
187
|
logger.log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
|
|
178
|
-
handleSelfEvolutionEvent(context,
|
|
188
|
+
handleSelfEvolutionEvent(context, cfg).catch((err) => {
|
|
189
|
+
logger.error(`[MONITOR] Failed to handle self-evolution-event:`, err);
|
|
190
|
+
});
|
|
179
191
|
};
|
|
180
192
|
const selfEvolutionStateGetHandler = (context) => {
|
|
181
193
|
logger.log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -50,6 +50,12 @@ export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
|
50
50
|
* (same level as push_id).
|
|
51
51
|
*/
|
|
52
52
|
export declare function extractDeviceType(parts: A2AMessagePart[]): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Extract modelName from message parts.
|
|
55
|
+
* Looks for modelName in data parts under variables.clientVariables.modelName
|
|
56
|
+
* (same level as systemVariables).
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractModelName(parts: A2AMessagePart[]): string | null;
|
|
53
59
|
/**
|
|
54
60
|
* Extract Trigger event data from message parts.
|
|
55
61
|
* Looks for Trigger events with pushDataId in data parts.
|
package/dist/src/parser.js
CHANGED
|
@@ -46,6 +46,36 @@ export function extractDataEvents(parts) {
|
|
|
46
46
|
.filter((event) => event !== undefined);
|
|
47
47
|
}
|
|
48
48
|
export function extractRunCrossTaskContext(parts) {
|
|
49
|
+
const normalizeSentFiles = (value) => {
|
|
50
|
+
if (!Array.isArray(value)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return value
|
|
54
|
+
.map((item) => {
|
|
55
|
+
if (!item || typeof item !== "object") {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const candidate = item;
|
|
59
|
+
const fileLocalUrls = Array.isArray(candidate.fileLocalUrls)
|
|
60
|
+
? candidate.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
61
|
+
: [];
|
|
62
|
+
const fileRemoteUrls = Array.isArray(candidate.fileRemoteUrls)
|
|
63
|
+
? candidate.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
64
|
+
: [];
|
|
65
|
+
const fileNames = Array.isArray(candidate.fileNames)
|
|
66
|
+
? candidate.fileNames.filter((name) => typeof name === "string" && name.length > 0)
|
|
67
|
+
: [];
|
|
68
|
+
if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
73
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
74
|
+
...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
.filter((item) => item !== null);
|
|
78
|
+
};
|
|
49
79
|
for (const part of parts) {
|
|
50
80
|
if (part.kind !== "data" || !part.data) {
|
|
51
81
|
continue;
|
|
@@ -64,7 +94,7 @@ export function extractRunCrossTaskContext(parts) {
|
|
|
64
94
|
isDistributed: context.isDistributed === true,
|
|
65
95
|
networkId,
|
|
66
96
|
isSupportAgent: context.isSupportAgent !== false,
|
|
67
|
-
|
|
97
|
+
sentFiles: normalizeSentFiles(context.sentFiles),
|
|
68
98
|
rawContext: context,
|
|
69
99
|
};
|
|
70
100
|
}
|
|
@@ -113,6 +143,22 @@ export function extractDeviceType(parts) {
|
|
|
113
143
|
}
|
|
114
144
|
return null;
|
|
115
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Extract modelName from message parts.
|
|
148
|
+
* Looks for modelName in data parts under variables.clientVariables.modelName
|
|
149
|
+
* (same level as systemVariables).
|
|
150
|
+
*/
|
|
151
|
+
export function extractModelName(parts) {
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (part.kind === "data" && part.data) {
|
|
154
|
+
const modelName = part.data.variables?.clientVariables?.modelName;
|
|
155
|
+
if (modelName && typeof modelName === "string" && modelName.trim() !== "" && modelName.toLowerCase() !== "none") {
|
|
156
|
+
return modelName;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
116
162
|
/**
|
|
117
163
|
* Extract Trigger event data from message parts.
|
|
118
164
|
* Looks for Trigger events with pushDataId in data parts.
|
package/dist/src/provider.js
CHANGED
|
@@ -417,6 +417,32 @@ export const xiaoyiProvider = {
|
|
|
417
417
|
docsPath: "/providers/models",
|
|
418
418
|
auth: [],
|
|
419
419
|
isCacheTtlEligible: () => true,
|
|
420
|
+
/**
|
|
421
|
+
* Dynamic model resolution for A2A-specified model names.
|
|
422
|
+
* A2A messages carry a dynamic modelName that isn't in any static catalog.
|
|
423
|
+
* This hook lets OpenClaw's resolveModelAsync accept any model ID under
|
|
424
|
+
* xiaoyiprovider as long as the provider has a configured baseUrl.
|
|
425
|
+
*/
|
|
426
|
+
resolveDynamicModel: (ctx) => {
|
|
427
|
+
const baseUrl = ctx.providerConfig?.baseUrl;
|
|
428
|
+
if (!baseUrl || typeof baseUrl !== "string")
|
|
429
|
+
return null;
|
|
430
|
+
return {
|
|
431
|
+
id: ctx.modelId,
|
|
432
|
+
name: ctx.modelId,
|
|
433
|
+
api: ctx.providerConfig?.api ?? "openai-completions",
|
|
434
|
+
provider: "xiaoyiprovider",
|
|
435
|
+
baseUrl,
|
|
436
|
+
reasoning: false,
|
|
437
|
+
input: ["text"],
|
|
438
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
439
|
+
contextWindow: 128_000,
|
|
440
|
+
maxTokens: 8192,
|
|
441
|
+
...(ctx.providerConfig?.headers && typeof ctx.providerConfig.headers === "object"
|
|
442
|
+
? { headers: ctx.providerConfig.headers }
|
|
443
|
+
: {}),
|
|
444
|
+
};
|
|
445
|
+
},
|
|
420
446
|
/**
|
|
421
447
|
* Store uid-based fallback prefix for lazy timestamp generation in wrapStreamFn.
|
|
422
448
|
* Session-level headers (traceId / sessionId / interactionId) are resolved
|
|
@@ -536,26 +562,28 @@ export const xiaoyiProvider = {
|
|
|
536
562
|
const beforeLen = sp.length;
|
|
537
563
|
// 删除 ## Tooling 与 TOOLS.md 声明之间的内容
|
|
538
564
|
sp = sp.replace(/(## Tooling)[\s\S]*?(TOOLS\.md does not control tool availability; it is user guidance for how to use external tools\.)/, "$1\n\n$2");
|
|
539
|
-
// (1)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
565
|
+
// (1) Skills 部分:移动到 ## Runtime 之前
|
|
566
|
+
if (sp.includes('## Runtime')) {
|
|
567
|
+
// 提取 ## Skills (mandatory) 到 </available_skills> 作为第一部分
|
|
568
|
+
const skillsMatch = sp.match(/(## Skills \(mandatory\)[\s\S]*?<\/available_skills>)/);
|
|
569
|
+
if (skillsMatch) {
|
|
570
|
+
const part1 = skillsMatch[0];
|
|
571
|
+
sp = sp.replace(part1, '');
|
|
572
|
+
sp = sp.replace('## Runtime', part1 + '\n\n## Runtime');
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// (2) SOUL.md 部分:移动到 ## Silent Replies 之前
|
|
576
|
+
if (sp.includes('## Silent Replies')) {
|
|
577
|
+
// 提取 ## /home/sandbox/.openclaw/workspace/SOUL.md 到 其特定脚注结束标志 的内容作为第二部分
|
|
578
|
+
const soulMatch = sp.match(/(## \/home\/sandbox\/\.openclaw\/workspace\/SOUL\.md[\s\S]*?_This file is yours to evolve\. As you learn who you are, update it\._)/);
|
|
579
|
+
if (soulMatch) {
|
|
580
|
+
const part2 = soulMatch[1].trim();
|
|
550
581
|
sp = sp.replace(soulMatch[1], '');
|
|
551
|
-
|
|
552
|
-
sp = sp.replace(/\n{3,}/g, '\n\n');
|
|
553
|
-
// (3) 将 第二部分 + 第一部分 插入到 ## Runtime 上面
|
|
554
|
-
const combined = (part2 + '\n\n' + part1).trim();
|
|
555
|
-
if (combined && sp.includes('## Runtime')) {
|
|
556
|
-
sp = sp.replace('## Runtime', combined + '\n\n## Runtime');
|
|
582
|
+
sp = sp.replace('## Silent Replies', part2 + '\n\n## Silent Replies');
|
|
557
583
|
}
|
|
558
584
|
}
|
|
585
|
+
// 清理多余空行
|
|
586
|
+
sp = sp.replace(/\n{3,}/g, '\n\n');
|
|
559
587
|
logger.log(`[xiaoyiprovider] system prompt optimized: ${beforeLen} -> ${sp.length}`);
|
|
560
588
|
context.systemPrompt = sp;
|
|
561
589
|
}
|
|
@@ -585,6 +613,12 @@ export const xiaoyiProvider = {
|
|
|
585
613
|
}
|
|
586
614
|
}
|
|
587
615
|
}
|
|
616
|
+
// ── Override model.id if A2A message specified modelName ──
|
|
617
|
+
const modelNameOverride = getCurrentSessionContext()?.modelName;
|
|
618
|
+
if (modelNameOverride && modelNameOverride.trim() !== "" && modelNameOverride.toLowerCase() !== "none") {
|
|
619
|
+
logger.log(`[xiaoyiprovider] overriding model.id: ${model.id} → ${modelNameOverride}`);
|
|
620
|
+
model = { ...model, id: modelNameOverride };
|
|
621
|
+
}
|
|
588
622
|
// ── Retry-capable streaming ──────────────────────────────
|
|
589
623
|
const cronJob = isCronTriggered(context.messages);
|
|
590
624
|
if (cronJob)
|