@ynhcj/xiaoyi-channel 0.0.2 → 0.0.3-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 +115 -35
- package/dist/src/channel.js +22 -1
- package/dist/src/client.d.ts +15 -0
- package/dist/src/client.js +97 -2
- package/dist/src/file-download.js +10 -1
- package/dist/src/file-upload.js +1 -1
- package/dist/src/formatter.d.ts +17 -0
- package/dist/src/formatter.js +86 -6
- package/dist/src/heartbeat.d.ts +2 -1
- package/dist/src/heartbeat.js +9 -1
- package/dist/src/monitor.d.ts +5 -0
- package/dist/src/monitor.js +131 -25
- package/dist/src/onboarding.js +7 -7
- package/dist/src/outbound.js +150 -71
- package/dist/src/parser.d.ts +5 -0
- package/dist/src/parser.js +15 -0
- package/dist/src/push.d.ts +7 -1
- package/dist/src/push.js +110 -19
- package/dist/src/reply-dispatcher.d.ts +1 -0
- package/dist/src/reply-dispatcher.js +210 -57
- package/dist/src/task-manager.d.ts +55 -0
- package/dist/src/task-manager.js +136 -0
- package/dist/src/tools/calendar-tool.d.ts +6 -0
- package/dist/src/tools/calendar-tool.js +167 -0
- package/dist/src/tools/call-phone-tool.d.ts +5 -0
- package/dist/src/tools/call-phone-tool.js +183 -0
- package/dist/src/tools/create-alarm-tool.d.ts +7 -0
- package/dist/src/tools/create-alarm-tool.js +444 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
- package/dist/src/tools/delete-alarm-tool.js +238 -0
- package/dist/src/tools/location-tool.js +48 -9
- package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
- package/dist/src/tools/modify-alarm-tool.js +474 -0
- package/dist/src/tools/modify-note-tool.d.ts +9 -0
- package/dist/src/tools/modify-note-tool.js +163 -0
- package/dist/src/tools/note-tool.d.ts +5 -0
- package/dist/src/tools/note-tool.js +146 -0
- package/dist/src/tools/search-alarm-tool.d.ts +8 -0
- package/dist/src/tools/search-alarm-tool.js +389 -0
- package/dist/src/tools/search-calendar-tool.d.ts +12 -0
- package/dist/src/tools/search-calendar-tool.js +259 -0
- package/dist/src/tools/search-contact-tool.d.ts +5 -0
- package/dist/src/tools/search-contact-tool.js +168 -0
- package/dist/src/tools/search-file-tool.d.ts +5 -0
- package/dist/src/tools/search-file-tool.js +185 -0
- package/dist/src/tools/search-message-tool.d.ts +5 -0
- package/dist/src/tools/search-message-tool.js +173 -0
- package/dist/src/tools/search-note-tool.d.ts +5 -0
- package/dist/src/tools/search-note-tool.js +130 -0
- package/dist/src/tools/search-photo-gallery-tool.d.ts +8 -0
- package/dist/src/tools/search-photo-gallery-tool.js +184 -0
- package/dist/src/tools/search-photo-tool.d.ts +9 -0
- package/dist/src/tools/search-photo-tool.js +270 -0
- package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
- package/dist/src/tools/send-file-to-user-tool.js +318 -0
- package/dist/src/tools/send-message-tool.d.ts +5 -0
- package/dist/src/tools/send-message-tool.js +189 -0
- package/dist/src/tools/session-manager.d.ts +15 -0
- package/dist/src/tools/session-manager.js +126 -6
- package/dist/src/tools/upload-file-tool.d.ts +13 -0
- package/dist/src/tools/upload-file-tool.js +265 -0
- package/dist/src/tools/upload-photo-tool.d.ts +9 -0
- package/dist/src/tools/upload-photo-tool.js +223 -0
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +6 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +151 -0
- package/dist/src/types.d.ts +5 -9
- package/dist/src/utils/config-manager.d.ts +26 -0
- package/dist/src/utils/config-manager.js +56 -0
- package/dist/src/websocket.d.ts +41 -0
- package/dist/src/websocket.js +192 -9
- package/package.json +1 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY search message tool - searches SMS messages on user's device.
|
|
7
|
+
* Returns matching messages based on content keyword search.
|
|
8
|
+
*/
|
|
9
|
+
export const searchMessageTool = {
|
|
10
|
+
name: "search_message",
|
|
11
|
+
label: "Search Message",
|
|
12
|
+
description: "搜索手机短信。根据关键词搜索短信内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
content: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "搜索关键词,用于在短信内容中进行匹配",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ["content"],
|
|
22
|
+
},
|
|
23
|
+
async execute(toolCallId, params) {
|
|
24
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🚀 Starting execution`);
|
|
25
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
26
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - params:`, JSON.stringify(params));
|
|
27
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
28
|
+
// Validate content parameter
|
|
29
|
+
if (!params.content || typeof params.content !== "string" || params.content.trim() === "") {
|
|
30
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Missing or invalid content parameter`);
|
|
31
|
+
throw new Error("Missing required parameter: content must be a non-empty string");
|
|
32
|
+
}
|
|
33
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🔍 Searching for messages with keyword: ${params.content}`);
|
|
34
|
+
// Get session context
|
|
35
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🔍 Attempting to get session context...`);
|
|
36
|
+
const sessionContext = getCurrentSessionContext();
|
|
37
|
+
if (!sessionContext) {
|
|
38
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ FAILED: No active session found!`);
|
|
39
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
40
|
+
throw new Error("No active XY session found. Search message tool can only be used during an active conversation.");
|
|
41
|
+
}
|
|
42
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ Session context found`);
|
|
43
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
44
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
45
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
46
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
47
|
+
// Get WebSocket manager
|
|
48
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🔌 Getting WebSocket manager...`);
|
|
49
|
+
const wsManager = getXYWebSocketManager(config);
|
|
50
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ WebSocket manager obtained`);
|
|
51
|
+
// Build SearchMessage command
|
|
52
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📦 Building SearchMessage command...`);
|
|
53
|
+
const command = {
|
|
54
|
+
header: {
|
|
55
|
+
namespace: "Common",
|
|
56
|
+
name: "Action",
|
|
57
|
+
},
|
|
58
|
+
payload: {
|
|
59
|
+
cardParam: {},
|
|
60
|
+
executeParam: {
|
|
61
|
+
executeMode: "background",
|
|
62
|
+
intentName: "SearchMessage",
|
|
63
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
64
|
+
needUnlock: true,
|
|
65
|
+
actionResponse: true,
|
|
66
|
+
appType: "OHOS_APP",
|
|
67
|
+
timeOut: 5,
|
|
68
|
+
intentParam: {
|
|
69
|
+
content: params.content.trim(),
|
|
70
|
+
},
|
|
71
|
+
permissionId: [],
|
|
72
|
+
achieveType: "INTENT",
|
|
73
|
+
},
|
|
74
|
+
responses: [
|
|
75
|
+
{
|
|
76
|
+
resultCode: "",
|
|
77
|
+
displayText: "",
|
|
78
|
+
ttsText: "",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
needUploadResult: true,
|
|
82
|
+
noHalfPage: false,
|
|
83
|
+
pageControlRelated: false,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
|
|
87
|
+
// Send command and wait for response (60 second timeout)
|
|
88
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ⏳ Setting up promise to wait for message search response...`);
|
|
89
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - Timeout: 60 seconds`);
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
93
|
+
wsManager.off("data-event", handler);
|
|
94
|
+
reject(new Error("搜索短信超时(60秒)"));
|
|
95
|
+
}, 60000);
|
|
96
|
+
// Listen for data events from WebSocket
|
|
97
|
+
const handler = (event) => {
|
|
98
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
99
|
+
if (event.intentName === "SearchMessage") {
|
|
100
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🎯 SearchMessage event received`);
|
|
101
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - status: ${event.status}`);
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
wsManager.off("data-event", handler);
|
|
104
|
+
if (event.status === "success" && event.outputs) {
|
|
105
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ Message search response received`);
|
|
106
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
107
|
+
// Check for error code in outputs
|
|
108
|
+
const code = event.outputs.code !== undefined ? event.outputs.code : null;
|
|
109
|
+
if (code !== null && code !== 0) {
|
|
110
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Device returned error`);
|
|
111
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - code: ${code}`);
|
|
112
|
+
const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
|
|
113
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - errorMsg: ${errorMsg}`);
|
|
114
|
+
reject(new Error(`搜索短信失败: ${errorMsg} (错误代码: ${code})`));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Extract result.items with safe checks
|
|
118
|
+
const result = event.outputs.result;
|
|
119
|
+
let items = [];
|
|
120
|
+
if (result && typeof result === "object" && Array.isArray(result.items)) {
|
|
121
|
+
items = result.items;
|
|
122
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📋 Found ${items.length} message(s)`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger.warn(`[SEARCH_MESSAGE_TOOL] ⚠️ No items found in result or result is invalid`);
|
|
126
|
+
logger.warn(`[SEARCH_MESSAGE_TOOL] - result:`, JSON.stringify(result || {}));
|
|
127
|
+
}
|
|
128
|
+
// Return items array as JSON string
|
|
129
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🎉 Message search completed successfully`);
|
|
130
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - keyword: ${params.content}`);
|
|
131
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - result count: ${items.length}`);
|
|
132
|
+
resolve({
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: JSON.stringify(items),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Message search failed`);
|
|
143
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - status: ${event.status}`);
|
|
144
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - outputs:`, JSON.stringify(event.outputs || {}));
|
|
145
|
+
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
146
|
+
reject(new Error(`搜索短信失败: ${errorDetail}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
// Register event handler
|
|
151
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
152
|
+
wsManager.on("data-event", handler);
|
|
153
|
+
// Send the command
|
|
154
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📤 Sending SearchMessage command...`);
|
|
155
|
+
sendCommand({
|
|
156
|
+
config,
|
|
157
|
+
sessionId,
|
|
158
|
+
taskId,
|
|
159
|
+
messageId,
|
|
160
|
+
command,
|
|
161
|
+
})
|
|
162
|
+
.then(() => {
|
|
163
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
164
|
+
})
|
|
165
|
+
.catch((error) => {
|
|
166
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Failed to send command:`, error);
|
|
167
|
+
clearTimeout(timeout);
|
|
168
|
+
wsManager.off("data-event", handler);
|
|
169
|
+
reject(error);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY search note tool - searches notes on user's device.
|
|
7
|
+
* Returns matching notes based on query string.
|
|
8
|
+
*/
|
|
9
|
+
export const searchNoteTool = {
|
|
10
|
+
name: "search_notes",
|
|
11
|
+
label: "Search Notes",
|
|
12
|
+
description: "搜索用户设备上的备忘录内容。根据关键词在备忘录的标题、内容和附件名称中进行检索。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
query: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "搜索关键词,用于在备忘录中检索相关内容",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ["query"],
|
|
22
|
+
},
|
|
23
|
+
async execute(toolCallId, params) {
|
|
24
|
+
logger.debug("Executing search note tool, toolCallId:", toolCallId);
|
|
25
|
+
// Validate parameters
|
|
26
|
+
if (!params.query) {
|
|
27
|
+
throw new Error("Missing required parameter: query is required");
|
|
28
|
+
}
|
|
29
|
+
// Get session context
|
|
30
|
+
const sessionContext = getCurrentSessionContext();
|
|
31
|
+
if (!sessionContext) {
|
|
32
|
+
throw new Error("No active XY session found. Search note tool can only be used during an active conversation.");
|
|
33
|
+
}
|
|
34
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
35
|
+
// Get WebSocket manager
|
|
36
|
+
const wsManager = getXYWebSocketManager(config);
|
|
37
|
+
// Build SearchNote command
|
|
38
|
+
const command = {
|
|
39
|
+
header: {
|
|
40
|
+
namespace: "Common",
|
|
41
|
+
name: "Action",
|
|
42
|
+
},
|
|
43
|
+
payload: {
|
|
44
|
+
cardParam: {},
|
|
45
|
+
executeParam: {
|
|
46
|
+
executeMode: "background",
|
|
47
|
+
intentName: "SearchNote",
|
|
48
|
+
bundleName: "com.huawei.hmos.notepad",
|
|
49
|
+
dimension: "",
|
|
50
|
+
needUnlock: true,
|
|
51
|
+
actionResponse: true,
|
|
52
|
+
timeOut: 5,
|
|
53
|
+
intentParam: {
|
|
54
|
+
query: params.query,
|
|
55
|
+
},
|
|
56
|
+
achieveType: "INTENT",
|
|
57
|
+
},
|
|
58
|
+
responses: [
|
|
59
|
+
{
|
|
60
|
+
resultCode: "",
|
|
61
|
+
displayText: "",
|
|
62
|
+
ttsText: "",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
needUploadResult: true,
|
|
66
|
+
noHalfPage: false,
|
|
67
|
+
pageControlRelated: false,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
// Send command and wait for response (60 second timeout)
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
wsManager.off("data-event", handler);
|
|
74
|
+
reject(new Error("搜索备忘录超时(60秒)"));
|
|
75
|
+
}, 60000);
|
|
76
|
+
// Listen for data events from WebSocket
|
|
77
|
+
const handler = (event) => {
|
|
78
|
+
logger.debug("Received data event:", event);
|
|
79
|
+
if (event.intentName === "SearchNote") {
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
wsManager.off("data-event", handler);
|
|
82
|
+
if (event.status === "success" && event.outputs) {
|
|
83
|
+
const { result, code } = event.outputs;
|
|
84
|
+
const items = result?.items || [];
|
|
85
|
+
logger.log(`Notes found: ${items.length} results for query "${params.query}"`);
|
|
86
|
+
resolve({
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: JSON.stringify({
|
|
91
|
+
success: true,
|
|
92
|
+
query: params.query,
|
|
93
|
+
totalResults: items.length,
|
|
94
|
+
notes: items.map((item) => ({
|
|
95
|
+
entityId: item.entityId,
|
|
96
|
+
entityName: item.entityName,
|
|
97
|
+
title: item.title?.replace(/<\/?em>/g, ''), // Remove <em> tags
|
|
98
|
+
content: item.content,
|
|
99
|
+
createdDate: item.createdDate,
|
|
100
|
+
modifiedDate: item.modifiedDate,
|
|
101
|
+
})),
|
|
102
|
+
indexName: result?.indexName,
|
|
103
|
+
code,
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
reject(new Error(`搜索备忘录失败: ${event.status}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// Register event handler
|
|
115
|
+
wsManager.on("data-event", handler);
|
|
116
|
+
// Send the command
|
|
117
|
+
sendCommand({
|
|
118
|
+
config,
|
|
119
|
+
sessionId,
|
|
120
|
+
taskId,
|
|
121
|
+
messageId,
|
|
122
|
+
command,
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
wsManager.off("data-event", handler);
|
|
126
|
+
reject(error);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XY search photo gallery tool - searches photos in user's gallery.
|
|
3
|
+
* Returns local mediaUri strings that can be used with upload_photo tool.
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: The returned mediaUris are LOCAL URIs that cannot be downloaded directly.
|
|
6
|
+
* To get publicly accessible URLs, use the upload_photo tool with these URIs.
|
|
7
|
+
*/
|
|
8
|
+
export declare const searchPhotoGalleryTool: any;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY search photo gallery tool - searches photos in user's gallery.
|
|
7
|
+
* Returns local mediaUri strings that can be used with upload_photo tool.
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT: The returned mediaUris are LOCAL URIs that cannot be downloaded directly.
|
|
10
|
+
* To get publicly accessible URLs, use the upload_photo tool with these URIs.
|
|
11
|
+
*/
|
|
12
|
+
export const searchPhotoGalleryTool = {
|
|
13
|
+
name: "search_photo_gallery",
|
|
14
|
+
label: "Search Photo Gallery",
|
|
15
|
+
description: `插件功能描述:搜索用户手机图库中的照片
|
|
16
|
+
工具使用约束:如果用户说从手机图库中或者从相册中查询xx图片时调用此工具。
|
|
17
|
+
工具输入输出简介:
|
|
18
|
+
a. 根据图像描述语料检索匹配的照片,返回照片在手机本地的 mediaUri以及thumbnailUri。
|
|
19
|
+
b. 返回的 mediaUri以及thumbnailUri 是本地路径,无法直接下载或访问。如果需要下载、查看、使用或展示照片,请使用 upload_photo 工具将 mediaUri或者thumbnailUri 转换为可访问的公网 URL。
|
|
20
|
+
c. mediaUri代表手机相册中的图片原图路径,图片大小比较大,清晰度比较高
|
|
21
|
+
d. thumbnailUri代表手机相册中的图片缩略图路径,图片大小比较小,清晰度适中,建议在upload_photo 工具的入参中优先使用此路径,不容易引起上传超时等问题
|
|
22
|
+
|
|
23
|
+
注意事项:
|
|
24
|
+
a. 只有当用户明确表达从手机相册搜索或者从图库搜索时才执行此工具,如果用户仅表达要搜索xxx图片,并没有说明搜索数据源,则不要贸然调用此插件,可以优先尝试websearch或者询问用户是否要从手机图库中搜索。
|
|
25
|
+
b. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
|
|
26
|
+
`,
|
|
27
|
+
parameters: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
query: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "图像描述语料,用于检索匹配的照片(例如:'小狗的照片'、'带有键盘的图片'等)",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["query"],
|
|
36
|
+
},
|
|
37
|
+
async execute(toolCallId, params) {
|
|
38
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🚀 Starting execution`);
|
|
39
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - toolCallId: ${toolCallId}`);
|
|
40
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - params:`, JSON.stringify(params));
|
|
41
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
42
|
+
// Validate parameters
|
|
43
|
+
if (!params.query) {
|
|
44
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ Missing required parameter: query`);
|
|
45
|
+
throw new Error("Missing required parameter: query is required");
|
|
46
|
+
}
|
|
47
|
+
// Get session context
|
|
48
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🔍 Attempting to get session context...`);
|
|
49
|
+
const sessionContext = getCurrentSessionContext();
|
|
50
|
+
if (!sessionContext) {
|
|
51
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ FAILED: No active session found!`);
|
|
52
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] - toolCallId: ${toolCallId}`);
|
|
53
|
+
throw new Error("No active XY session found. Search photo gallery tool can only be used during an active conversation.");
|
|
54
|
+
}
|
|
55
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ Session context found`);
|
|
56
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
57
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
58
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
59
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
60
|
+
// Get WebSocket manager
|
|
61
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🔌 Getting WebSocket manager...`);
|
|
62
|
+
const wsManager = getXYWebSocketManager(config);
|
|
63
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ WebSocket manager obtained`);
|
|
64
|
+
// Search for photos
|
|
65
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📸 Searching for photos...`);
|
|
66
|
+
const items = await searchPhotos(wsManager, config, sessionId, taskId, messageId, params.query);
|
|
67
|
+
if (!items || items.length === 0) {
|
|
68
|
+
logger.warn(`[SEARCH_PHOTO_GALLERY_TOOL] ⚠️ No photos found for query: ${params.query}`);
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: JSON.stringify({
|
|
74
|
+
items: [],
|
|
75
|
+
count: 0,
|
|
76
|
+
message: "未找到匹配的照片"
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ Found ${items.length} photos`);
|
|
83
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - items:`, JSON.stringify(items));
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: JSON.stringify({
|
|
89
|
+
items,
|
|
90
|
+
count: items.length,
|
|
91
|
+
message: `找到 ${items.length} 张照片。注意:mediaUri 和 thumbnailUri 是本地路径,无法直接访问。如需下载或查看,请使用 upload_photo 工具。`
|
|
92
|
+
}),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Search for photos using query description
|
|
100
|
+
* Returns array of photo items with complete information
|
|
101
|
+
*/
|
|
102
|
+
async function searchPhotos(wsManager, config, sessionId, taskId, messageId, query) {
|
|
103
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📦 Building SearchPhotoVideo command...`);
|
|
104
|
+
const command = {
|
|
105
|
+
header: {
|
|
106
|
+
namespace: "Common",
|
|
107
|
+
name: "Action",
|
|
108
|
+
},
|
|
109
|
+
payload: {
|
|
110
|
+
cardParam: {},
|
|
111
|
+
executeParam: {
|
|
112
|
+
executeMode: "background",
|
|
113
|
+
intentName: "SearchPhotoVideo",
|
|
114
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
115
|
+
needUnlock: true,
|
|
116
|
+
actionResponse: true,
|
|
117
|
+
appType: "OHOS_APP",
|
|
118
|
+
timeOut: 5,
|
|
119
|
+
intentParam: {
|
|
120
|
+
query: query,
|
|
121
|
+
},
|
|
122
|
+
permissionId: [],
|
|
123
|
+
achieveType: "INTENT",
|
|
124
|
+
},
|
|
125
|
+
responses: [
|
|
126
|
+
{
|
|
127
|
+
resultCode: "",
|
|
128
|
+
displayText: "",
|
|
129
|
+
ttsText: "",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
needUploadResult: true,
|
|
133
|
+
noHalfPage: false,
|
|
134
|
+
pageControlRelated: false,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const timeout = setTimeout(() => {
|
|
139
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ⏰ Timeout: No response for SearchPhotoVideo within 60 seconds`);
|
|
140
|
+
wsManager.off("data-event", handler);
|
|
141
|
+
reject(new Error("搜索照片超时(60秒)"));
|
|
142
|
+
}, 60000);
|
|
143
|
+
const handler = (event) => {
|
|
144
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
145
|
+
if (event.intentName === "SearchPhotoVideo") {
|
|
146
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🎯 SearchPhotoVideo event received`);
|
|
147
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - status: ${event.status}`);
|
|
148
|
+
clearTimeout(timeout);
|
|
149
|
+
wsManager.off("data-event", handler);
|
|
150
|
+
if (event.status === "success" && event.outputs) {
|
|
151
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ Photo search completed successfully`);
|
|
152
|
+
const result = event.outputs.result;
|
|
153
|
+
const items = result?.items || [];
|
|
154
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📊 Found ${items.length} photo items`);
|
|
155
|
+
resolve(items);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ Photo search failed`);
|
|
159
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] - status: ${event.status}`);
|
|
160
|
+
reject(new Error(`搜索照片失败: ${event.status}`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📡 Registering data-event handler for SearchPhotoVideo`);
|
|
165
|
+
wsManager.on("data-event", handler);
|
|
166
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📤 Sending SearchPhotoVideo command...`);
|
|
167
|
+
sendCommand({
|
|
168
|
+
config,
|
|
169
|
+
sessionId,
|
|
170
|
+
taskId,
|
|
171
|
+
messageId,
|
|
172
|
+
command,
|
|
173
|
+
})
|
|
174
|
+
.then(() => {
|
|
175
|
+
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ SearchPhotoVideo command sent successfully`);
|
|
176
|
+
})
|
|
177
|
+
.catch((error) => {
|
|
178
|
+
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ Failed to send SearchPhotoVideo command:`, error);
|
|
179
|
+
clearTimeout(timeout);
|
|
180
|
+
wsManager.off("data-event", handler);
|
|
181
|
+
reject(error);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XY search photo tool - searches photos in user's gallery.
|
|
3
|
+
* Returns publicly accessible URLs of matching photos based on query description.
|
|
4
|
+
*
|
|
5
|
+
* This tool performs a two-step operation:
|
|
6
|
+
* 1. Search for photos using query description
|
|
7
|
+
* 2. Upload found photos to get publicly accessible URLs
|
|
8
|
+
*/
|
|
9
|
+
export declare const searchPhotoTool: any;
|