@ynhcj/xiaoyi-channel 0.0.14-beta → 0.0.16-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.
@@ -102,9 +102,30 @@ export const searchContactTool = {
102
102
  if (event.status === "success" && event.outputs) {
103
103
  logger.log(`[SEARCH_CONTACT_TOOL] ✅ Contact search completed successfully`);
104
104
  logger.log(`[SEARCH_CONTACT_TOOL] - outputs:`, JSON.stringify(event.outputs));
105
- // Return the result directly as requested
105
+ // Check for error code first
106
+ if (event.outputs.retErrCode && event.outputs.retErrCode !== "0") {
107
+ logger.error(`[SEARCH_CONTACT_TOOL] ❌ Search failed with error code: ${event.outputs.retErrCode}`);
108
+ logger.error(`[SEARCH_CONTACT_TOOL] - errMsg: ${event.outputs.errMsg}`);
109
+ reject(new Error(`搜索联系人失败: ${event.outputs.errMsg || '未知错误'} (错误码: ${event.outputs.retErrCode})`));
110
+ return;
111
+ }
112
+ // Get the result
106
113
  const result = event.outputs.result;
114
+ // Check if result exists
115
+ if (!result) {
116
+ logger.warn(`[SEARCH_CONTACT_TOOL] ⚠️ No result found for name "${params.name}"`);
117
+ resolve({
118
+ content: [
119
+ {
120
+ type: "text",
121
+ text: JSON.stringify({ items: [], message: "未找到匹配的联系人" }),
122
+ },
123
+ ],
124
+ });
125
+ return;
126
+ }
107
127
  logger.log(`[SEARCH_CONTACT_TOOL] 📊 Contacts found: ${result?.items?.length || 0} results for name "${params.name}"`);
128
+ // Return the result with valid string content
108
129
  resolve({
109
130
  content: [
110
131
  {
@@ -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 数组(从 search_photo_gallery 工具获取)。限制:每次最多支持传入 5 条 mediaUri。操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
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: "array",
21
- items: {
22
- type: "string",
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
- console.log(`[UPLOAD_PHOTO_TOOL] 🚀 Starting execution`);
33
- console.log(`[UPLOAD_PHOTO_TOOL] - toolCallId: ${toolCallId}`);
34
- console.log(`[UPLOAD_PHOTO_TOOL] - params:`, JSON.stringify(params));
35
- console.log(`[UPLOAD_PHOTO_TOOL] - timestamp: ${new Date().toISOString()}`);
36
- // Validate parameters
37
- if (!params.mediaUris || !Array.isArray(params.mediaUris) || params.mediaUris.length === 0) {
38
- console.error(`[UPLOAD_PHOTO_TOOL] Missing or invalid parameter: mediaUris`);
39
- throw new Error("Missing or invalid parameter: mediaUris must be a non-empty array");
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 (params.mediaUris.length > 5) {
43
- console.error(`[UPLOAD_PHOTO_TOOL] ❌ Too many mediaUris: ${params.mediaUris.length}`);
44
- throw new Error(`最多支持 5 条 mediaUri,当前提供了 ${params.mediaUris.length} 条。请分批处理。`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] - mediaUris count: ${params.mediaUris.length}`);
80
+ logger.log(`[UPLOAD_PHOTO_TOOL] - mediaUris count: ${mediaUris.length}`);
47
81
  // Get session context
48
- console.log(`[UPLOAD_PHOTO_TOOL] 🔍 Attempting to get session context...`);
82
+ logger.log(`[UPLOAD_PHOTO_TOOL] 🔍 Attempting to get session context...`);
49
83
  const sessionContext = getLatestSessionContext();
50
84
  if (!sessionContext) {
51
- console.error(`[UPLOAD_PHOTO_TOOL] ❌ FAILED: No active session found!`);
52
- console.error(`[UPLOAD_PHOTO_TOOL] - toolCallId: ${toolCallId}`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] ✅ Session context found`);
56
- console.log(`[UPLOAD_PHOTO_TOOL] - sessionId: ${sessionContext.sessionId}`);
57
- console.log(`[UPLOAD_PHOTO_TOOL] - taskId: ${sessionContext.taskId}`);
58
- console.log(`[UPLOAD_PHOTO_TOOL] - messageId: ${sessionContext.messageId}`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] 🔌 Getting WebSocket manager...`);
95
+ logger.log(`[UPLOAD_PHOTO_TOOL] 🔌 Getting WebSocket manager...`);
62
96
  const wsManager = getXYWebSocketManager(config);
63
- console.log(`[UPLOAD_PHOTO_TOOL] ✅ WebSocket manager obtained`);
97
+ logger.log(`[UPLOAD_PHOTO_TOOL] ✅ WebSocket manager obtained`);
64
98
  // Get public URLs for the photos
65
- console.log(`[UPLOAD_PHOTO_TOOL] 🌐 Getting public URLs for photos...`);
66
- const imageUrls = await getPhotoUrls(wsManager, config, sessionId, taskId, messageId, params.mediaUris);
67
- console.log(`[UPLOAD_PHOTO_TOOL] 🎉 Successfully retrieved ${imageUrls.length} photo URLs`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] 📦 Building ImageUploadForClaw command...`);
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
- console.error(`[UPLOAD_PHOTO_TOOL] ⏰ Timeout: No response for ImageUploadForClaw within 60 seconds`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] 📨 Received data event:`, JSON.stringify(event));
164
+ logger.log(`[UPLOAD_PHOTO_TOOL] 📨 Received data event:`, JSON.stringify(event));
131
165
  if (event.intentName === "ImageUploadForClaw") {
132
- console.log(`[UPLOAD_PHOTO_TOOL] 🎯 ImageUploadForClaw event received`);
133
- console.log(`[UPLOAD_PHOTO_TOOL] - status: ${event.status}`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] ✅ Image URL retrieval completed successfully`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] 🔄 Decoded URL: ${url} -> ${decodedUrl}`);
180
+ logger.log(`[UPLOAD_PHOTO_TOOL] 🔄 Decoded URL: ${url} -> ${decodedUrl}`);
147
181
  return decodedUrl;
148
182
  });
149
- console.log(`[UPLOAD_PHOTO_TOOL] 📊 Retrieved and decoded ${imageUrls.length} image URLs`);
183
+ logger.log(`[UPLOAD_PHOTO_TOOL] 📊 Retrieved and decoded ${imageUrls.length} image URLs`);
150
184
  resolve(imageUrls);
151
185
  }
152
186
  else {
153
- console.error(`[UPLOAD_PHOTO_TOOL] ❌ Image URL retrieval failed`);
154
- console.error(`[UPLOAD_PHOTO_TOOL] - status: ${event.status}`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] 📡 Registering data-event handler for ImageUploadForClaw`);
193
+ logger.log(`[UPLOAD_PHOTO_TOOL] 📡 Registering data-event handler for ImageUploadForClaw`);
160
194
  wsManager.on("data-event", handler);
161
- console.log(`[UPLOAD_PHOTO_TOOL] 📤 Sending ImageUploadForClaw command...`);
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
- console.log(`[UPLOAD_PHOTO_TOOL] ✅ ImageUploadForClaw command sent successfully`);
204
+ logger.log(`[UPLOAD_PHOTO_TOOL] ✅ ImageUploadForClaw command sent successfully`);
171
205
  })
172
206
  .catch((error) => {
173
- console.error(`[UPLOAD_PHOTO_TOOL] ❌ Failed to send ImageUploadForClaw command:`, error);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.14-beta",
3
+ "version": "0.0.16-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",