@ynhcj/xiaoyi-channel 0.0.101-next → 0.0.102-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/provider.js
CHANGED
|
@@ -463,10 +463,18 @@ export const xiaoyiProvider = {
|
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
465
|
else {
|
|
466
|
-
// Session mode:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
// Session mode: get session context at request time via ALS.
|
|
467
|
+
// OpenClaw caches prepareExtraParams by provider/modelId, so
|
|
468
|
+
// ctx.extraParams holds the first session's values. We must
|
|
469
|
+
// call getCurrentSessionContext() here to get the correct
|
|
470
|
+
// sessionId/interactionId for the current concurrent request.
|
|
471
|
+
const sessionCtx = getCurrentSessionContext();
|
|
472
|
+
const traceId = sessionCtx?.taskId ?? ctx.extraParams[HEADER_TRACE_ID];
|
|
473
|
+
const sessionId = sessionCtx?.taskId?.split("&")[0]
|
|
474
|
+
?? ctx.extraParams[HEADER_SESSION_ID];
|
|
475
|
+
const interactionId = sessionCtx?.taskId?.split("&")[1]
|
|
476
|
+
?? ctx.extraParams[HEADER_INTERACTION_ID]
|
|
477
|
+
?? "";
|
|
470
478
|
if (typeof traceId === "string") {
|
|
471
479
|
const isCron = isCronTriggered(context.messages);
|
|
472
480
|
dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${traceId}_${Date.now()}` : traceId;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SessionContext } from "./session-manager.js";
|
|
2
2
|
/**
|
|
3
3
|
* XY Image Reading tool - performs image understanding using local or remote image URLs.
|
|
4
|
-
* Supports both local file paths and remote URLs.
|
|
4
|
+
* Supports both local file paths and remote URLs, up to 10 images at once.
|
|
5
5
|
*/
|
|
6
6
|
export declare function createImageReadingTool(ctx: SessionContext): any;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { XYFileUploadService } from "../file-upload.js";
|
|
3
3
|
import fetch from "node-fetch";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
|
-
import path from "path";
|
|
6
5
|
import { v4 as uuidv4 } from "uuid";
|
|
7
6
|
/**
|
|
8
7
|
* Check if value is a remote URL
|
|
@@ -29,64 +28,29 @@ async function isLocalFile(value) {
|
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
33
|
-
*/
|
|
34
|
-
async function downloadRemoteFile(url) {
|
|
35
|
-
try {
|
|
36
|
-
const response = await fetch(url);
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
39
|
-
}
|
|
40
|
-
// Get filename from URL or use default
|
|
41
|
-
let filename = url.split("/").pop() || "downloaded_image";
|
|
42
|
-
filename = filename.split("?")[0];
|
|
43
|
-
// Ensure temp directory exists
|
|
44
|
-
const tempDir = "/tmp/xy_channel";
|
|
45
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
46
|
-
// Generate unique filename to avoid conflicts
|
|
47
|
-
const timestamp = Date.now();
|
|
48
|
-
const ext = path.extname(filename) || ".jpg";
|
|
49
|
-
const baseName = path.basename(filename, ext);
|
|
50
|
-
const uniqueFilename = `${baseName}_${timestamp}${ext}`;
|
|
51
|
-
const localPath = path.join(tempDir, uniqueFilename);
|
|
52
|
-
// Save file to local temp directory
|
|
53
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
54
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
55
|
-
await fs.writeFile(localPath, buffer);
|
|
56
|
-
return localPath;
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
throw new Error(`Failed to download remote file: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Process image input: validate and convert local file to OBS URL, keep remote URL unchanged
|
|
31
|
+
* Process image input: remote URL passed directly, local file uploaded to OBS
|
|
64
32
|
*/
|
|
65
33
|
async function processImageInput(imageInput, uploadService) {
|
|
66
|
-
//
|
|
34
|
+
// Remote URL: pass directly
|
|
67
35
|
if (isRemoteUrl(imageInput)) {
|
|
68
|
-
|
|
69
|
-
const imageUrl = await uploadService.uploadFileAndGetUrl(localPath, "TEMPORARY_MATERIAL_DOC");
|
|
70
|
-
if (!imageUrl) {
|
|
71
|
-
throw new Error("图片上传失败:无法获取图片访问地址");
|
|
72
|
-
}
|
|
73
|
-
return { imageUrl, localPath };
|
|
36
|
+
return imageInput;
|
|
74
37
|
}
|
|
75
|
-
//
|
|
38
|
+
// Local file: upload to OBS
|
|
76
39
|
const isLocal = await isLocalFile(imageInput);
|
|
77
40
|
if (isLocal) {
|
|
78
41
|
const imageUrl = await uploadService.uploadFileAndGetUrl(imageInput, "TEMPORARY_MATERIAL_DOC");
|
|
79
42
|
if (!imageUrl) {
|
|
80
43
|
throw new Error("图片上传失败:无法获取图片访问地址");
|
|
81
44
|
}
|
|
82
|
-
return
|
|
45
|
+
return imageUrl;
|
|
83
46
|
}
|
|
84
47
|
throw new Error(`Invalid image input: must be a remote URL or local file path, got: ${imageInput}`);
|
|
85
48
|
}
|
|
86
49
|
/**
|
|
87
50
|
* Call image understanding API with streaming response
|
|
51
|
+
* Supports both single image and multiple images (imageUrls array)
|
|
88
52
|
*/
|
|
89
|
-
async function callImageUnderstandingAPI(
|
|
53
|
+
async function callImageUnderstandingAPI(imageUrls, text, apiKey, uid, fileUploadUrl) {
|
|
90
54
|
const apiUrl = `${fileUploadUrl}/celia-claw/v1/sse-api/skill/execute`;
|
|
91
55
|
const traceId = uuidv4();
|
|
92
56
|
const headers = {
|
|
@@ -128,7 +92,7 @@ async function callImageUnderstandingAPI(imageUrl, text, apiKey, uid, fileUpload
|
|
|
128
92
|
pluginId: "aeac4e92c32949c1b7fc02de262615e6",
|
|
129
93
|
agentState: "OnShelf",
|
|
130
94
|
actionName: "imageUnderStandStream",
|
|
131
|
-
content: {
|
|
95
|
+
content: { imageUrls, text },
|
|
132
96
|
},
|
|
133
97
|
},
|
|
134
98
|
],
|
|
@@ -198,85 +162,54 @@ async function callImageUnderstandingAPI(imageUrl, text, apiKey, uid, fileUpload
|
|
|
198
162
|
}
|
|
199
163
|
/**
|
|
200
164
|
* XY Image Reading tool - performs image understanding using local or remote image URLs.
|
|
201
|
-
* Supports both local file paths and remote URLs.
|
|
165
|
+
* Supports both local file paths and remote URLs, up to 10 images at once.
|
|
202
166
|
*/
|
|
203
167
|
export function createImageReadingTool(ctx) {
|
|
204
|
-
const { config
|
|
168
|
+
const { config } = ctx;
|
|
205
169
|
return {
|
|
206
170
|
name: "image_reading",
|
|
207
171
|
label: "Image Reading",
|
|
208
|
-
description:
|
|
209
|
-
工具使用场景:
|
|
210
|
-
【必须调用此工具的情况】
|
|
211
|
-
1. 用户消息中包含 mediaPath 字段且不为空(表示用户发送了图片)
|
|
212
|
-
2. 用户希望理解图片内容,询问图片是什么,例如:
|
|
213
|
-
- "这是什么?"
|
|
214
|
-
- "图片里有什么?"
|
|
215
|
-
- "帮我看看这张图"
|
|
216
|
-
- "描述一下这张图片"
|
|
217
|
-
- "分析一下这张照片"
|
|
218
|
-
- "这个图片是什么意思"
|
|
219
|
-
- "识别一下图片内容"
|
|
220
|
-
- 或任何关于图片内容的理解、识别、分析类询问
|
|
221
|
-
|
|
222
|
-
当同时满足以上两个条件时,必须优先调用此工具进行图像理解。
|
|
223
|
-
|
|
224
|
-
工具能力描述:对图片进行理解和分析,返回图片的描述内容。
|
|
225
|
-
|
|
226
|
-
工具参数说明:
|
|
227
|
-
localUrl 与 remoteUrl 任意一个不为空即可,优先使用 localUrl
|
|
228
|
-
|
|
229
|
-
注意事项:
|
|
230
|
-
a. 支持常见图片格式(jpg, png, gif等)
|
|
231
|
-
b. 远程图片会先下载到本地再处理
|
|
232
|
-
c. 操作超时时间为2分钟(120秒)
|
|
233
|
-
d. 返回图像理解的文本描述内容`,
|
|
172
|
+
description: `图片理解工具,支持单图/多图(最多10张),返回图片描述文本。调用条件:用户消息含 media 图片或询问图片内容时必须调用。`,
|
|
234
173
|
parameters: {
|
|
235
174
|
type: "object",
|
|
236
175
|
properties: {
|
|
237
|
-
|
|
238
|
-
type: "
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
remoteUrl: {
|
|
242
|
-
type: "string",
|
|
243
|
-
description: "公网图片地址(可选),公网图片地址(HTTP/HTTPS URL),注意不要对原始url做任何截断(例如裁减掉链接后面的鉴权信息或者修改域名后缀),必须使用上下文中完整的图片地址",
|
|
176
|
+
images: {
|
|
177
|
+
type: "array",
|
|
178
|
+
items: { type: "string" },
|
|
179
|
+
description: "图片路径数组,支持本地路径或公网URL,最多10张",
|
|
244
180
|
},
|
|
245
181
|
prompt: {
|
|
246
182
|
type: "string",
|
|
247
|
-
description: "
|
|
183
|
+
description: "提示词,默认'描述图片内容'。多图可用'对比这些图片'等",
|
|
248
184
|
},
|
|
249
185
|
},
|
|
186
|
+
required: ["images"],
|
|
250
187
|
},
|
|
251
188
|
async execute(toolCallId, params) {
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
189
|
+
// Normalize images param
|
|
190
|
+
const images = params.images
|
|
191
|
+
? (Array.isArray(params.images) ? params.images : [params.images])
|
|
192
|
+
: [];
|
|
193
|
+
// Validate that at least one image is provided
|
|
194
|
+
if (images.length === 0) {
|
|
195
|
+
throw new Error("images 参数不能为空");
|
|
196
|
+
}
|
|
197
|
+
// Validate max image count
|
|
198
|
+
if (images.length > 10) {
|
|
199
|
+
throw new Error("最多支持 10 张图片,当前提供了 " + images.length + " 张");
|
|
255
200
|
}
|
|
256
|
-
// Get prompt (default to "
|
|
257
|
-
const prompt = params.prompt || "
|
|
201
|
+
// Get prompt (default to "描述这些图片内容")
|
|
202
|
+
const prompt = params.prompt || "描述这些图片内容";
|
|
258
203
|
// Create upload service
|
|
259
204
|
const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
|
|
260
|
-
|
|
261
|
-
|
|
205
|
+
// Process images: local files upload to OBS, remote URLs pass directly
|
|
206
|
+
const allImageUrls = [];
|
|
262
207
|
try {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
processedImage = await processImageInput(imageInput, uploadService);
|
|
266
|
-
// Track downloaded file for cleanup
|
|
267
|
-
if (processedImage.localPath) {
|
|
268
|
-
downloadedFile = processedImage.localPath;
|
|
269
|
-
}
|
|
270
|
-
// Call image understanding API
|
|
271
|
-
const caption = await callImageUnderstandingAPI(processedImage.imageUrl, prompt, config.apiKey, config.uid, config.fileUploadUrl);
|
|
272
|
-
// Clean up downloaded file if any
|
|
273
|
-
if (downloadedFile) {
|
|
274
|
-
try {
|
|
275
|
-
await fs.unlink(downloadedFile);
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
}
|
|
208
|
+
for (const imageInput of images) {
|
|
209
|
+
allImageUrls.push(await processImageInput(imageInput, uploadService));
|
|
279
210
|
}
|
|
211
|
+
// Call image understanding API with all image URLs
|
|
212
|
+
const caption = await callImageUnderstandingAPI(allImageUrls, prompt, config.apiKey, config.uid, config.fileUploadUrl);
|
|
280
213
|
return {
|
|
281
214
|
content: [
|
|
282
215
|
{
|
|
@@ -284,7 +217,7 @@ d. 返回图像理解的文本描述内容`,
|
|
|
284
217
|
text: JSON.stringify({
|
|
285
218
|
caption,
|
|
286
219
|
prompt,
|
|
287
|
-
|
|
220
|
+
imageCount: allImageUrls.length,
|
|
288
221
|
success: true,
|
|
289
222
|
}),
|
|
290
223
|
},
|
|
@@ -292,16 +225,7 @@ d. 返回图像理解的文本描述内容`,
|
|
|
292
225
|
};
|
|
293
226
|
}
|
|
294
227
|
catch (error) {
|
|
295
|
-
// Clean up downloaded file on error
|
|
296
|
-
if (downloadedFile) {
|
|
297
|
-
try {
|
|
298
|
-
await fs.unlink(downloadedFile);
|
|
299
|
-
}
|
|
300
|
-
catch (cleanupError) {
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
228
|
const errorMessage = error instanceof Error ? error.message : "图片分析失败";
|
|
304
|
-
// Return error result instead of throwing
|
|
305
229
|
return {
|
|
306
230
|
content: [
|
|
307
231
|
{
|
|
@@ -309,7 +233,7 @@ d. 返回图像理解的文本描述内容`,
|
|
|
309
233
|
text: JSON.stringify({
|
|
310
234
|
error: errorMessage,
|
|
311
235
|
prompt,
|
|
312
|
-
|
|
236
|
+
imageCount: images.length,
|
|
313
237
|
success: false,
|
|
314
238
|
}),
|
|
315
239
|
},
|