@ynhcj/xiaoyi-channel 0.0.14-beta → 0.0.15-beta
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
3
|
import { getLatestSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
4
5
|
/**
|
|
5
6
|
* XY upload photo tool - uploads local photos to get publicly accessible URLs.
|
|
6
7
|
* Requires mediaUris from search_photo_gallery tool as prerequisite.
|
|
@@ -12,59 +13,92 @@ import { getLatestSessionContext } from "./session-manager.js";
|
|
|
12
13
|
export const uploadPhotoTool = {
|
|
13
14
|
name: "upload_photo",
|
|
14
15
|
label: "Upload Photo",
|
|
15
|
-
description: "将手机本地照片回传并获取可公网访问的 URL。使用前必须先调用 search_photo_gallery 工具获取照片的 mediaUri。参数说明:mediaUris 是照片在手机本地的 URI
|
|
16
|
+
description: "将手机本地照片回传并获取可公网访问的 URL。使用前必须先调用 search_photo_gallery 工具获取照片的 mediaUri。参数说明:mediaUris 是照片在手机本地的 URI 数组或 JSON 字符串数组(从 search_photo_gallery 工具获取)。限制:每次最多支持传入 5 条 mediaUri。操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
16
17
|
parameters: {
|
|
17
18
|
type: "object",
|
|
18
19
|
properties: {
|
|
19
20
|
mediaUris: {
|
|
20
|
-
type
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
description: "照片在手机本地的 URI 数组,必须先通过 search_photo_gallery 工具获取。每次最多支持 5 条 URI。",
|
|
25
|
-
maxItems: 5,
|
|
26
|
-
minItems: 1,
|
|
21
|
+
// 不指定 type,允许传入数组或 JSON 字符串
|
|
22
|
+
// 具体的类型验证和转换在 execute 函数内部进行
|
|
23
|
+
description: "照片在手机本地的 URI 数组(或 JSON 字符串形式的数组),必须先通过 search_photo_gallery 工具获取。每次最多支持 5 条 URI。支持传入数组 [\"uri1\", \"uri2\"] 或 JSON 字符串 '[\"uri1\", \"uri2\"]'。",
|
|
27
24
|
},
|
|
28
25
|
},
|
|
29
26
|
required: ["mediaUris"],
|
|
30
27
|
},
|
|
31
28
|
async execute(toolCallId, params) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🚀 Starting execution`);
|
|
30
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - toolCallId: ${toolCallId}`);
|
|
31
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - params (raw):`, JSON.stringify(params));
|
|
32
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - params.mediaUris type:`, typeof params.mediaUris);
|
|
33
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
34
|
+
// ===== 参数规范化:兼容数组和 JSON 字符串 =====
|
|
35
|
+
let mediaUris = null;
|
|
36
|
+
if (!params.mediaUris) {
|
|
37
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Missing parameter: mediaUris`);
|
|
38
|
+
throw new Error("Missing required parameter: mediaUris");
|
|
39
|
+
}
|
|
40
|
+
// 情况1: 已经是数组
|
|
41
|
+
if (Array.isArray(params.mediaUris)) {
|
|
42
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ mediaUris is already an array`);
|
|
43
|
+
mediaUris = params.mediaUris;
|
|
44
|
+
}
|
|
45
|
+
// 情况2: 是字符串,尝试解析为 JSON 数组
|
|
46
|
+
else if (typeof params.mediaUris === 'string') {
|
|
47
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🔄 mediaUris is a string, attempting to parse as JSON...`);
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(params.mediaUris);
|
|
50
|
+
if (Array.isArray(parsed)) {
|
|
51
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ Successfully parsed JSON string to array`);
|
|
52
|
+
mediaUris = parsed;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Parsed JSON is not an array:`, typeof parsed);
|
|
56
|
+
throw new Error("mediaUris must be an array or a JSON string representing an array");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (parseError) {
|
|
60
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Failed to parse mediaUris as JSON:`, parseError);
|
|
61
|
+
throw new Error(`mediaUris must be a valid JSON array string. Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 情况3: 其他类型,报错
|
|
65
|
+
else {
|
|
66
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Invalid mediaUris type:`, typeof params.mediaUris);
|
|
67
|
+
throw new Error(`mediaUris must be an array or a JSON string, got ${typeof params.mediaUris}`);
|
|
68
|
+
}
|
|
69
|
+
// 验证数组非空
|
|
70
|
+
if (!mediaUris || mediaUris.length === 0) {
|
|
71
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ mediaUris array is empty`);
|
|
72
|
+
throw new Error("mediaUris array cannot be empty");
|
|
40
73
|
}
|
|
74
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ Normalized mediaUris:`, JSON.stringify(mediaUris));
|
|
41
75
|
// Validate maximum 5 URIs
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
throw new Error(`最多支持 5 条 mediaUri,当前提供了 ${
|
|
76
|
+
if (mediaUris.length > 5) {
|
|
77
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Too many mediaUris: ${mediaUris.length}`);
|
|
78
|
+
throw new Error(`最多支持 5 条 mediaUri,当前提供了 ${mediaUris.length} 条。请分批处理。`);
|
|
45
79
|
}
|
|
46
|
-
|
|
80
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - mediaUris count: ${mediaUris.length}`);
|
|
47
81
|
// Get session context
|
|
48
|
-
|
|
82
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🔍 Attempting to get session context...`);
|
|
49
83
|
const sessionContext = getLatestSessionContext();
|
|
50
84
|
if (!sessionContext) {
|
|
51
|
-
|
|
52
|
-
|
|
85
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ FAILED: No active session found!`);
|
|
86
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] - toolCallId: ${toolCallId}`);
|
|
53
87
|
throw new Error("No active XY session found. Upload photo tool can only be used during an active conversation.");
|
|
54
88
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ Session context found`);
|
|
90
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
91
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
92
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
59
93
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
60
94
|
// Get WebSocket manager
|
|
61
|
-
|
|
95
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🔌 Getting WebSocket manager...`);
|
|
62
96
|
const wsManager = getXYWebSocketManager(config);
|
|
63
|
-
|
|
97
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ WebSocket manager obtained`);
|
|
64
98
|
// Get public URLs for the photos
|
|
65
|
-
|
|
66
|
-
const imageUrls = await getPhotoUrls(wsManager, config, sessionId, taskId, messageId,
|
|
67
|
-
|
|
99
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🌐 Getting public URLs for photos...`);
|
|
100
|
+
const imageUrls = await getPhotoUrls(wsManager, config, sessionId, taskId, messageId, mediaUris);
|
|
101
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🎉 Successfully retrieved ${imageUrls.length} photo URLs`);
|
|
68
102
|
return {
|
|
69
103
|
content: [
|
|
70
104
|
{
|
|
@@ -84,7 +118,7 @@ export const uploadPhotoTool = {
|
|
|
84
118
|
* Returns array of publicly accessible image URLs
|
|
85
119
|
*/
|
|
86
120
|
async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, mediaUris) {
|
|
87
|
-
|
|
121
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 📦 Building ImageUploadForClaw command...`);
|
|
88
122
|
// Build imageInfos array from mediaUris
|
|
89
123
|
const imageInfos = mediaUris.map(mediaUri => ({ mediaUri }));
|
|
90
124
|
const command = {
|
|
@@ -122,19 +156,19 @@ async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, med
|
|
|
122
156
|
};
|
|
123
157
|
return new Promise((resolve, reject) => {
|
|
124
158
|
const timeout = setTimeout(() => {
|
|
125
|
-
|
|
159
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ⏰ Timeout: No response for ImageUploadForClaw within 60 seconds`);
|
|
126
160
|
wsManager.off("data-event", handler);
|
|
127
161
|
reject(new Error("获取照片URL超时(60秒)"));
|
|
128
162
|
}, 60000);
|
|
129
163
|
const handler = (event) => {
|
|
130
|
-
|
|
164
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
131
165
|
if (event.intentName === "ImageUploadForClaw") {
|
|
132
|
-
|
|
133
|
-
|
|
166
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🎯 ImageUploadForClaw event received`);
|
|
167
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] - status: ${event.status}`);
|
|
134
168
|
clearTimeout(timeout);
|
|
135
169
|
wsManager.off("data-event", handler);
|
|
136
170
|
if (event.status === "success" && event.outputs) {
|
|
137
|
-
|
|
171
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ Image URL retrieval completed successfully`);
|
|
138
172
|
const result = event.outputs.result;
|
|
139
173
|
let imageUrls = result?.imageUrls || [];
|
|
140
174
|
// Decode Unicode escape sequences in URLs
|
|
@@ -143,22 +177,22 @@ async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, med
|
|
|
143
177
|
const decodedUrl = url
|
|
144
178
|
.replace(/\\u003d/g, '=')
|
|
145
179
|
.replace(/\\u0026/g, '&');
|
|
146
|
-
|
|
180
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 🔄 Decoded URL: ${url} -> ${decodedUrl}`);
|
|
147
181
|
return decodedUrl;
|
|
148
182
|
});
|
|
149
|
-
|
|
183
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 📊 Retrieved and decoded ${imageUrls.length} image URLs`);
|
|
150
184
|
resolve(imageUrls);
|
|
151
185
|
}
|
|
152
186
|
else {
|
|
153
|
-
|
|
154
|
-
|
|
187
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Image URL retrieval failed`);
|
|
188
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] - status: ${event.status}`);
|
|
155
189
|
reject(new Error(`获取照片URL失败: ${event.status}`));
|
|
156
190
|
}
|
|
157
191
|
}
|
|
158
192
|
};
|
|
159
|
-
|
|
193
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 📡 Registering data-event handler for ImageUploadForClaw`);
|
|
160
194
|
wsManager.on("data-event", handler);
|
|
161
|
-
|
|
195
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] 📤 Sending ImageUploadForClaw command...`);
|
|
162
196
|
sendCommand({
|
|
163
197
|
config,
|
|
164
198
|
sessionId,
|
|
@@ -167,10 +201,10 @@ async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, med
|
|
|
167
201
|
command,
|
|
168
202
|
})
|
|
169
203
|
.then(() => {
|
|
170
|
-
|
|
204
|
+
logger.log(`[UPLOAD_PHOTO_TOOL] ✅ ImageUploadForClaw command sent successfully`);
|
|
171
205
|
})
|
|
172
206
|
.catch((error) => {
|
|
173
|
-
|
|
207
|
+
logger.error(`[UPLOAD_PHOTO_TOOL] ❌ Failed to send ImageUploadForClaw command:`, error);
|
|
174
208
|
clearTimeout(timeout);
|
|
175
209
|
wsManager.off("data-event", handler);
|
|
176
210
|
reject(error);
|