@ynhcj/xiaoyi-channel 0.0.151-beta → 0.0.151-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/index.js +23 -0
- package/dist/src/bot.js +9 -1
- package/dist/src/channel.js +59 -5
- package/dist/src/cron-command.d.ts +15 -0
- package/dist/src/cron-command.js +49 -0
- package/dist/src/cron-query-handler.d.ts +7 -0
- package/dist/src/cron-query-handler.js +188 -0
- 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 +17 -3
- package/dist/src/cspl/utils.js +2 -2
- package/dist/src/formatter.d.ts +14 -1
- package/dist/src/formatter.js +31 -8
- package/dist/src/monitor.js +13 -1
- package/dist/src/parser.d.ts +2 -1
- package/dist/src/parser.js +55 -0
- package/dist/src/provider.js +19 -17
- package/dist/src/push.d.ts +11 -1
- package/dist/src/push.js +93 -2
- package/dist/src/reply-dispatcher.js +113 -14
- package/dist/src/self-evolution-handler.js +1 -1
- package/dist/src/tools/agent-as-skill-tool.js +56 -4
- package/dist/src/tools/calendar-tool.js +2 -1
- package/dist/src/tools/call-device-tool.js +0 -3
- package/dist/src/tools/call-phone-tool.js +2 -1
- package/dist/src/tools/create-alarm-tool.js +2 -1
- package/dist/src/tools/create-all-tools.js +8 -4
- package/dist/src/tools/delete-alarm-tool.js +2 -1
- package/dist/src/tools/device-tool-map.d.ts +1 -1
- package/dist/src/tools/device-tool-map.js +12 -5
- package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
- package/dist/src/tools/discover-cross-devices-tool.js +235 -0
- 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/find-pc-devices-tool.js +1 -0
- package/dist/src/tools/get-collection-tool-schema.js +1 -1
- package/dist/src/tools/get-device-file-tool-schema.js +2 -3
- package/dist/src/tools/location-tool.js +2 -1
- package/dist/src/tools/modify-alarm-tool.js +2 -1
- package/dist/src/tools/modify-note-tool.js +2 -1
- package/dist/src/tools/note-tool.js +2 -1
- package/dist/src/tools/query-app-message-tool.js +4 -3
- package/dist/src/tools/query-memory-data-tool.js +4 -3
- package/dist/src/tools/query-todo-task-tool.js +4 -3
- package/dist/src/tools/save-file-to-phone-tool.js +2 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +2 -1
- package/dist/src/tools/schema-tool-factory.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +2 -1
- package/dist/src/tools/search-calendar-tool.js +2 -1
- package/dist/src/tools/search-contact-tool.js +2 -1
- package/dist/src/tools/search-email-tool.js +4 -3
- package/dist/src/tools/search-file-tool.js +6 -10
- package/dist/src/tools/search-message-tool.js +1 -0
- package/dist/src/tools/search-note-tool.js +2 -1
- package/dist/src/tools/search-photo-gallery-tool.js +4 -3
- package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
- package/dist/src/tools/send-cross-device-task-tool.js +299 -0
- package/dist/src/tools/send-email-tool.js +4 -3
- 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-message-tool.js +1 -0
- package/dist/src/tools/session-manager.d.ts +14 -1
- package/dist/src/tools/session-manager.js +73 -0
- package/dist/src/tools/upload-file-tool.js +6 -14
- package/dist/src/tools/upload-photo-tool.js +4 -3
- package/dist/src/tools/xiaoyi-add-collection-tool.js +4 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +3 -2
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +3 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +6 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +207 -15
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { getXYWebSocketManager } from "../client.js";
|
|
|
3
3
|
import { sendCommand } from "../formatter.js";
|
|
4
4
|
import { getCurrentTaskId } from "../task-manager.js";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
|
+
import { XYFileUploadService } from "../file-upload.js";
|
|
6
7
|
/**
|
|
7
8
|
* Agent-as-skill tool - invokes a registered agent by agentId as a skill.
|
|
8
9
|
* The tool receives the agentId, query, and optional file attachments,
|
|
@@ -11,7 +12,7 @@ import { logger } from "../utils/logger.js";
|
|
|
11
12
|
export function createAgentAsSkillTool(ctx) {
|
|
12
13
|
const { config, sessionId, taskId, messageId } = ctx;
|
|
13
14
|
return {
|
|
14
|
-
name: "
|
|
15
|
+
name: "agent_as_a_tool",
|
|
15
16
|
label: "Agent as Skill Tool",
|
|
16
17
|
description: `智能体作为skill的执行元工具。当需要调用其他已注册的Agent来执行特定任务时使用此工具。
|
|
17
18
|
该工具会将用户请求和可选的附件文件转发给目标Agent执行,并返回执行结果。
|
|
@@ -37,8 +38,7 @@ export function createAgentAsSkillTool(ctx) {
|
|
|
37
38
|
description: "用户原始请求文本,原样转发给目标Agent执行",
|
|
38
39
|
},
|
|
39
40
|
filesInfo: {
|
|
40
|
-
|
|
41
|
-
description: "附件文件/图片信息列表,无文件时可传null或空数组",
|
|
41
|
+
description: "附件文件/图片信息列表,无文件时可传null或空数组,支持传入数组或JSON字符串",
|
|
42
42
|
items: {
|
|
43
43
|
type: "object",
|
|
44
44
|
properties: {
|
|
@@ -55,6 +55,10 @@ export function createAgentAsSkillTool(ctx) {
|
|
|
55
55
|
type: "string",
|
|
56
56
|
description: "文件可访问下载链接(完整HTTP/HTTPS地址)",
|
|
57
57
|
},
|
|
58
|
+
fileUrlLocal: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "文件本地路径,如果提供此字段,工具会自动上传文件并将公网URL填入fileUrl",
|
|
61
|
+
},
|
|
58
62
|
},
|
|
59
63
|
},
|
|
60
64
|
},
|
|
@@ -71,6 +75,53 @@ export function createAgentAsSkillTool(ctx) {
|
|
|
71
75
|
if (!params.query || typeof params.query !== "string") {
|
|
72
76
|
throw new Error("Missing or invalid required parameter: query must be a non-empty string");
|
|
73
77
|
}
|
|
78
|
+
// Robust parsing: normalize filesInfo from array or JSON string
|
|
79
|
+
let filesInfo = null;
|
|
80
|
+
if (params.filesInfo) {
|
|
81
|
+
if (Array.isArray(params.filesInfo)) {
|
|
82
|
+
filesInfo = params.filesInfo;
|
|
83
|
+
}
|
|
84
|
+
else if (typeof params.filesInfo === 'string') {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(params.filesInfo);
|
|
87
|
+
if (Array.isArray(parsed)) {
|
|
88
|
+
filesInfo = parsed;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw new Error("filesInfo must be an array or a JSON string representing an array");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (parseError) {
|
|
95
|
+
throw new Error(`filesInfo JSON解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
filesInfo = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Upload local files and fill fileUrl
|
|
103
|
+
if (filesInfo && filesInfo.length > 0) {
|
|
104
|
+
const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
|
|
105
|
+
for (const fileInfo of filesInfo) {
|
|
106
|
+
if (fileInfo.fileUrlLocal && !fileInfo.fileUrl) {
|
|
107
|
+
try {
|
|
108
|
+
const publicUrl = await uploadService.uploadFileAndGetUrl(fileInfo.fileUrlLocal, "TEMPORARY_MATERIAL_DOC");
|
|
109
|
+
if (publicUrl) {
|
|
110
|
+
fileInfo.fileUrl = publicUrl;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
logger.warn("[AGENT-AS-SKILL] 上传文件未返回公网URL", { fileUrlLocal: fileInfo.fileUrlLocal });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (uploadError) {
|
|
117
|
+
logger.error("[AGENT-AS-SKILL] 上传本地文件失败", { fileUrlLocal: fileInfo.fileUrlLocal, error: uploadError });
|
|
118
|
+
throw new Error(`上传本地文件失败 (${fileInfo.fileUrlLocal}): ${uploadError instanceof Error ? uploadError.message : String(uploadError)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Remove fileUrlLocal from the final payload
|
|
122
|
+
delete fileInfo.fileUrlLocal;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
74
125
|
// Get WebSocket manager
|
|
75
126
|
const wsManager = getXYWebSocketManager(config);
|
|
76
127
|
// Build ExecuteAgentAsSkill command
|
|
@@ -82,7 +133,7 @@ export function createAgentAsSkillTool(ctx) {
|
|
|
82
133
|
payload: {
|
|
83
134
|
agentId: params.agentId,
|
|
84
135
|
query: params.query,
|
|
85
|
-
filesInfo:
|
|
136
|
+
filesInfo: filesInfo || null,
|
|
86
137
|
},
|
|
87
138
|
};
|
|
88
139
|
// Send command and wait for response (5 minute timeout)
|
|
@@ -125,6 +176,7 @@ export function createAgentAsSkillTool(ctx) {
|
|
|
125
176
|
taskId: currentTaskId,
|
|
126
177
|
messageId,
|
|
127
178
|
command,
|
|
179
|
+
toolCallId,
|
|
128
180
|
}).then(() => {
|
|
129
181
|
logger.log("[AGENT-AS-SKILL] Command sent successfully", { agentId: params.agentId });
|
|
130
182
|
}).catch((error) => {
|
|
@@ -106,7 +106,7 @@ export function createCalendarTool(ctx) {
|
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
reject(new Error(`创建日程失败: ${event.
|
|
109
|
+
reject(new Error(`创建日程失败: ${JSON.stringify(event.outputs)}`));
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
};
|
|
@@ -120,6 +120,7 @@ export function createCalendarTool(ctx) {
|
|
|
120
120
|
taskId: currentTaskId,
|
|
121
121
|
messageId,
|
|
122
122
|
command,
|
|
123
|
+
toolCallId,
|
|
123
124
|
})
|
|
124
125
|
.then(() => {
|
|
125
126
|
})
|
|
@@ -22,7 +22,6 @@ import { createUploadFileTool } from "./upload-file-tool.js";
|
|
|
22
22
|
import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
|
|
23
23
|
import { createSendEmailTool } from "./send-email-tool.js";
|
|
24
24
|
import { createSearchEmailTool } from "./search-email-tool.js";
|
|
25
|
-
import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
|
|
26
25
|
import { sendStatusUpdate } from "../formatter.js";
|
|
27
26
|
import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
|
|
28
27
|
/**
|
|
@@ -55,7 +54,6 @@ export function createCallDeviceTool(ctx) {
|
|
|
55
54
|
const searchFileTool = createSearchFileTool(ctx);
|
|
56
55
|
const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
|
|
57
56
|
const searchEmailTool = createSearchEmailTool(ctx);
|
|
58
|
-
const findPcDevicesTool = createFindPcDevicesTool(ctx);
|
|
59
57
|
/**
|
|
60
58
|
* 端工具注册表 —— 按 name 索引所有可通过 call_device_tool 调度的工具。
|
|
61
59
|
*/
|
|
@@ -84,7 +82,6 @@ export function createCallDeviceTool(ctx) {
|
|
|
84
82
|
[saveFileToPhoneTool.name, saveFileToPhoneTool],
|
|
85
83
|
[sendEmailTool.name, sendEmailTool],
|
|
86
84
|
[searchEmailTool.name, searchEmailTool],
|
|
87
|
-
[findPcDevicesTool.name, findPcDevicesTool],
|
|
88
85
|
]);
|
|
89
86
|
return {
|
|
90
87
|
name: "call_device_tool",
|
|
@@ -98,7 +98,7 @@ export function createCallPhoneTool(ctx) {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
else {
|
|
101
|
-
reject(new Error(`拨打电话失败: ${event.
|
|
101
|
+
reject(new Error(`拨打电话失败: ${JSON.stringify(event.outputs)}`));
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
};
|
|
@@ -112,6 +112,7 @@ export function createCallPhoneTool(ctx) {
|
|
|
112
112
|
taskId: currentTaskId,
|
|
113
113
|
messageId,
|
|
114
114
|
command,
|
|
115
|
+
toolCallId,
|
|
115
116
|
})
|
|
116
117
|
.then(() => {
|
|
117
118
|
})
|
|
@@ -244,7 +244,7 @@ b. 使用该工具之前需获取当前真实时间
|
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
246
|
else {
|
|
247
|
-
reject(new Error(`创建闹钟失败: ${event.
|
|
247
|
+
reject(new Error(`创建闹钟失败: ${JSON.stringify(event.outputs)}`));
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
};
|
|
@@ -258,6 +258,7 @@ b. 使用该工具之前需获取当前真实时间
|
|
|
258
258
|
taskId: currentTaskId,
|
|
259
259
|
messageId,
|
|
260
260
|
command,
|
|
261
|
+
toolCallId,
|
|
261
262
|
})
|
|
262
263
|
.then(() => {
|
|
263
264
|
})
|
|
@@ -13,10 +13,12 @@ import { createGetPhotoToolSchemaTool } from "./get-photo-tool-schema.js";
|
|
|
13
13
|
import { createGetDeviceFileToolSchemaTool } from "./get-device-file-tool-schema.js";
|
|
14
14
|
import { createGetAlarmToolSchemaTool } from "./get-alarm-tool-schema.js";
|
|
15
15
|
import { createGetCollectionToolSchemaTool } from "./get-collection-tool-schema.js";
|
|
16
|
-
import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
|
|
16
|
+
// import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
|
|
17
17
|
import { createLoginTokenTool } from "./login-token-tool.js";
|
|
18
18
|
import { createAgentAsSkillTool } from "./agent-as-skill-tool.js";
|
|
19
|
-
import {
|
|
19
|
+
import { createDiscoverCrossDevicesTool } from "./discover-cross-devices-tool.js";
|
|
20
|
+
import { createSendCrossDeviceTaskTool } from "./send-cross-device-task-tool.js";
|
|
21
|
+
import { createDisplayA2UICardTool } from "./display-a2ui-card-tool.js";
|
|
20
22
|
import { logger } from "../utils/logger.js";
|
|
21
23
|
/**
|
|
22
24
|
* Create all XY channel tools for the given session context.
|
|
@@ -32,6 +34,9 @@ export function createAllTools(ctx) {
|
|
|
32
34
|
logger.log(`[CREATE-ALL-TOOLS] creating tools`);
|
|
33
35
|
return [
|
|
34
36
|
createLocationTool(ctx),
|
|
37
|
+
createDiscoverCrossDevicesTool(ctx),
|
|
38
|
+
createSendCrossDeviceTaskTool(ctx),
|
|
39
|
+
createDisplayA2UICardTool(ctx),
|
|
35
40
|
createCallDeviceTool(ctx),
|
|
36
41
|
createGetNoteToolSchemaTool(ctx),
|
|
37
42
|
createGetCalendarToolSchemaTool(ctx),
|
|
@@ -42,13 +47,12 @@ export function createAllTools(ctx) {
|
|
|
42
47
|
createGetAlarmToolSchemaTool(ctx),
|
|
43
48
|
createGetCollectionToolSchemaTool(ctx),
|
|
44
49
|
createSendFileToUserTool(ctx),
|
|
45
|
-
createGetEmailToolSchemaTool(ctx),
|
|
50
|
+
// createGetEmailToolSchemaTool(ctx),
|
|
46
51
|
viewPushResultTool,
|
|
47
52
|
createImageReadingTool(ctx),
|
|
48
53
|
timestampToUtc8Tool,
|
|
49
54
|
createSaveSelfEvolutionSkillTool(ctx),
|
|
50
55
|
createLoginTokenTool(ctx),
|
|
51
56
|
createAgentAsSkillTool(ctx),
|
|
52
|
-
createFindPcDevicesTool(ctx),
|
|
53
57
|
];
|
|
54
58
|
}
|
|
@@ -143,7 +143,7 @@ export function createDeleteAlarmTool(ctx) {
|
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
145
|
else {
|
|
146
|
-
reject(new Error(`删除闹钟失败: ${event.
|
|
146
|
+
reject(new Error(`删除闹钟失败: ${JSON.stringify(event.outputs)}`));
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
};
|
|
@@ -157,6 +157,7 @@ export function createDeleteAlarmTool(ctx) {
|
|
|
157
157
|
taskId: currentTaskId,
|
|
158
158
|
messageId,
|
|
159
159
|
command,
|
|
160
|
+
toolCallId,
|
|
160
161
|
})
|
|
161
162
|
.then(() => {
|
|
162
163
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Known device type enum. */
|
|
2
|
-
export declare const DEVICE_TYPES: readonly ["car", "2in1", "phone"];
|
|
2
|
+
export declare const DEVICE_TYPES: readonly ["car", "2in1", "phone", "web"];
|
|
3
3
|
export type DeviceType = (typeof DEVICE_TYPES)[number];
|
|
4
4
|
export declare function filterToolsByDevice(tools: any[], deviceType?: string): any[];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// - denylist: listed tools are blocked, everything else is available (used for permissive devices like pc)
|
|
5
5
|
// Tools NOT listed in any device entry → available to all devices (no restriction).
|
|
6
6
|
/** Known device type enum. */
|
|
7
|
-
export const DEVICE_TYPES = ["car", "2in1", "phone"];
|
|
7
|
+
export const DEVICE_TYPES = ["car", "2in1", "phone", "web"];
|
|
8
8
|
const DEVICE_TOOL_POLICY = {
|
|
9
9
|
"2in1": {
|
|
10
10
|
allowlist: false,
|
|
@@ -15,10 +15,17 @@ const DEVICE_TOOL_POLICY = {
|
|
|
15
15
|
"search_message",
|
|
16
16
|
"search_contact",
|
|
17
17
|
"get_contact_tool_schema",
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
"web": {
|
|
21
|
+
allowlist: true,
|
|
22
|
+
tools: [
|
|
23
|
+
"send_file_to_user",
|
|
24
|
+
"view_push_result",
|
|
25
|
+
"image_reading",
|
|
26
|
+
"convert_time_to_utc8_time",
|
|
27
|
+
"save_self_evolution_skill",
|
|
28
|
+
"displayA2UICard",
|
|
22
29
|
],
|
|
23
30
|
},
|
|
24
31
|
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { sendCommand, sendStatusUpdate } from "../formatter.js";
|
|
2
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
3
|
+
import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
const DISCOVER_DEVICES_INTENT = "SearchAllDeviceInfo";
|
|
6
|
+
const DISCOVER_DEVICES_BUNDLE = "com.huawei.hmos.vassistant";
|
|
7
|
+
const DISCOVER_DEVICES_TIMEOUT_MS = 30_000;
|
|
8
|
+
const LOG_TAG = "[GetPCDeviceList]";
|
|
9
|
+
const DISCOVER_DEVICES_STATUS_TEXT = "正在查询设备列表...";
|
|
10
|
+
const DEVICE_TYPE_LABELS = {
|
|
11
|
+
"14": "phone",
|
|
12
|
+
"17": "pad",
|
|
13
|
+
"131": "car",
|
|
14
|
+
"2607": "PC",
|
|
15
|
+
};
|
|
16
|
+
function buildResultText(result) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: JSON.stringify(result),
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function normalizeDevices(rawDevices) {
|
|
27
|
+
if (!Array.isArray(rawDevices)) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return rawDevices
|
|
31
|
+
.filter((item) => Boolean(item) && typeof item === "object")
|
|
32
|
+
.map((device) => {
|
|
33
|
+
const networkId = typeof device.networkId === "string"
|
|
34
|
+
? device.networkId
|
|
35
|
+
: typeof device.deviceId === "string"
|
|
36
|
+
? device.deviceId
|
|
37
|
+
: "";
|
|
38
|
+
const deviceTypeId = typeof device.deviceTypeId === "string"
|
|
39
|
+
? device.deviceTypeId
|
|
40
|
+
: typeof device.deviceType === "string"
|
|
41
|
+
? device.deviceType
|
|
42
|
+
: "";
|
|
43
|
+
return {
|
|
44
|
+
networkId,
|
|
45
|
+
deviceName: typeof device.deviceName === "string" ? device.deviceName : "",
|
|
46
|
+
deviceTypeId,
|
|
47
|
+
deviceTypeLabel: DEVICE_TYPE_LABELS[deviceTypeId] ?? "unknown",
|
|
48
|
+
nearby: device.nearby === true,
|
|
49
|
+
rawDevice: device,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function inferDesiredDeviceTypes(query) {
|
|
54
|
+
const normalized = query.toLowerCase();
|
|
55
|
+
if (/(pc|computer|desktop|laptop|notebook)/iu.test(normalized) || /电脑|台式机|笔记本/iu.test(query)) {
|
|
56
|
+
return ["2607"];
|
|
57
|
+
}
|
|
58
|
+
if (/(tablet|pad|ipad)/iu.test(normalized) || /平板/iu.test(query)) {
|
|
59
|
+
return ["17"];
|
|
60
|
+
}
|
|
61
|
+
if (/(phone|mobile)/iu.test(normalized) || /手机/iu.test(query)) {
|
|
62
|
+
return ["14"];
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
function sortByNearby(devices) {
|
|
67
|
+
return [...devices].sort((a, b) => Number(b.nearby) - Number(a.nearby));
|
|
68
|
+
}
|
|
69
|
+
function recommendDevices(query, devices) {
|
|
70
|
+
const desiredTypes = inferDesiredDeviceTypes(query);
|
|
71
|
+
if (desiredTypes.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
recommendedDevices: [],
|
|
74
|
+
recommendationReason: "No explicit target device type was detected in the query.",
|
|
75
|
+
needsUserSelection: devices.length > 1,
|
|
76
|
+
selectionPrompt: devices.length > 1
|
|
77
|
+
? "The query does not identify a unique device type. Ask the user to choose a target device by deviceName or networkId before sending a cross-device task."
|
|
78
|
+
: "",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const matches = devices.filter((device) => desiredTypes.includes(device.deviceTypeId));
|
|
82
|
+
if (matches.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
recommendedDevices: [],
|
|
85
|
+
recommendationReason: `No discovered device matches requested type(s): ${desiredTypes.join(", ")}.`,
|
|
86
|
+
needsUserSelection: false,
|
|
87
|
+
selectionPrompt: "",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const sortedMatches = sortByNearby(matches);
|
|
91
|
+
return {
|
|
92
|
+
recommendedDevices: sortedMatches,
|
|
93
|
+
recommendationReason: `Matched requested device type(s): ${desiredTypes.join(", ")}. Nearby devices are ranked first.`,
|
|
94
|
+
needsUserSelection: sortedMatches.length > 1,
|
|
95
|
+
selectionPrompt: sortedMatches.length > 1
|
|
96
|
+
? "Multiple candidate devices match the user request. Ask the user to choose one target device by deviceName or networkId before calling send_cross_device_task."
|
|
97
|
+
: "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function createDiscoverCrossDevicesTool(ctx) {
|
|
101
|
+
const { config, sessionId, taskId, messageId } = ctx;
|
|
102
|
+
return {
|
|
103
|
+
name: "discover_cross_devices",
|
|
104
|
+
label: "发现跨设备协作设备",
|
|
105
|
+
description: `跨设备协作的设备发现工具。
|
|
106
|
+
|
|
107
|
+
当用户明确表达要从另一台设备获取、查找、使用或操作内容时,必须优先调用本工具,例如从 PC、电脑、平板、手机等设备获取文件或查找内容。
|
|
108
|
+
|
|
109
|
+
本工具只做设备发现和目标设备推荐,不会读取副设备文件内容,不会上传文件,也不会真正下发跨端执行任务。下发跨端执行任务需要使用SendCrossDeviceTaskTool`,
|
|
110
|
+
parameters: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
query: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "The user's original cross-device request, used to recommend the target device type.",
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
required: ["query"],
|
|
119
|
+
},
|
|
120
|
+
async execute(_toolCallId, params) {
|
|
121
|
+
const query = typeof params.query === "string" ? params.query.trim() : "";
|
|
122
|
+
logger.log(`${LOG_TAG} tool invoked`);
|
|
123
|
+
if (!query) {
|
|
124
|
+
return buildResultText({
|
|
125
|
+
success: false,
|
|
126
|
+
rawOutputs: null,
|
|
127
|
+
devices: [],
|
|
128
|
+
recommendedDevices: [],
|
|
129
|
+
recommendationReason: "",
|
|
130
|
+
message: "Missing required parameter: query.",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
134
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
135
|
+
const wsManager = getXYWebSocketManager(config);
|
|
136
|
+
const command = {
|
|
137
|
+
header: {
|
|
138
|
+
namespace: "Common",
|
|
139
|
+
name: "Action",
|
|
140
|
+
},
|
|
141
|
+
payload: {
|
|
142
|
+
needUploadResult: true,
|
|
143
|
+
actionResponseConfig: {},
|
|
144
|
+
response: [],
|
|
145
|
+
executeParam: {
|
|
146
|
+
executeMode: "background",
|
|
147
|
+
intentName: DISCOVER_DEVICES_INTENT,
|
|
148
|
+
intentParam: {},
|
|
149
|
+
bundleName: DISCOVER_DEVICES_BUNDLE,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
let timeout;
|
|
155
|
+
let handler;
|
|
156
|
+
let settled = false;
|
|
157
|
+
const cleanup = () => {
|
|
158
|
+
clearTimeout(timeout);
|
|
159
|
+
wsManager.off("data-event", handler);
|
|
160
|
+
};
|
|
161
|
+
const finish = (result) => {
|
|
162
|
+
if (settled) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
settled = true;
|
|
166
|
+
cleanup();
|
|
167
|
+
resolve(buildResultText(result));
|
|
168
|
+
};
|
|
169
|
+
handler = (event) => {
|
|
170
|
+
if (event.intentName !== DISCOVER_DEVICES_INTENT) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const rawOutputs = event.outputs ?? {};
|
|
174
|
+
const code = rawOutputs.code;
|
|
175
|
+
const success = event.status === "success" && String(code) === "0";
|
|
176
|
+
const devices = normalizeDevices(rawOutputs.result?.devices);
|
|
177
|
+
const recommendation = recommendDevices(query, devices);
|
|
178
|
+
logger.log(`${LOG_TAG} completed, success=${success}, deviceCount=${devices.length}, recommendedCount=${recommendation.recommendedDevices.length}`);
|
|
179
|
+
finish({
|
|
180
|
+
success,
|
|
181
|
+
rawOutputs,
|
|
182
|
+
devices,
|
|
183
|
+
recommendedDevices: recommendation.recommendedDevices,
|
|
184
|
+
recommendationReason: recommendation.recommendationReason,
|
|
185
|
+
needsUserSelection: recommendation.needsUserSelection,
|
|
186
|
+
selectionPrompt: recommendation.selectionPrompt,
|
|
187
|
+
message: success
|
|
188
|
+
? recommendation.needsUserSelection
|
|
189
|
+
? `Discovered ${devices.length} device(s). Multiple candidates may match; ask the user to choose the target device before sending a cross-device task.`
|
|
190
|
+
: `Discovered ${devices.length} device(s). The model should choose the final target device based on the user request.`
|
|
191
|
+
: "Device discovery failed on the device side.",
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
timeout = setTimeout(() => {
|
|
195
|
+
logger.log(`${LOG_TAG} timeout waiting UploadExeResult after ${DISCOVER_DEVICES_TIMEOUT_MS}ms`);
|
|
196
|
+
finish({
|
|
197
|
+
success: false,
|
|
198
|
+
rawOutputs: null,
|
|
199
|
+
devices: [],
|
|
200
|
+
recommendedDevices: [],
|
|
201
|
+
recommendationReason: "",
|
|
202
|
+
message: `Device discovery timed out after ${DISCOVER_DEVICES_TIMEOUT_MS / 1000} seconds.`,
|
|
203
|
+
});
|
|
204
|
+
}, DISCOVER_DEVICES_TIMEOUT_MS);
|
|
205
|
+
wsManager.on("data-event", handler);
|
|
206
|
+
sendStatusUpdate({
|
|
207
|
+
config,
|
|
208
|
+
sessionId,
|
|
209
|
+
taskId: currentTaskId,
|
|
210
|
+
messageId: currentMessageId,
|
|
211
|
+
text: DISCOVER_DEVICES_STATUS_TEXT,
|
|
212
|
+
state: "working",
|
|
213
|
+
})
|
|
214
|
+
.then(() => sendCommand({
|
|
215
|
+
config,
|
|
216
|
+
sessionId,
|
|
217
|
+
taskId: currentTaskId,
|
|
218
|
+
messageId: currentMessageId,
|
|
219
|
+
command,
|
|
220
|
+
}))
|
|
221
|
+
.catch((error) => {
|
|
222
|
+
logger.error(`${LOG_TAG} failed to send device discovery command: ${error instanceof Error ? error.message : String(error)}`);
|
|
223
|
+
finish({
|
|
224
|
+
success: false,
|
|
225
|
+
rawOutputs: null,
|
|
226
|
+
devices: [],
|
|
227
|
+
recommendedDevices: [],
|
|
228
|
+
recommendationReason: "",
|
|
229
|
+
message: `Failed to send device discovery command: ${error instanceof Error ? error.message : String(error)}`,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { sendCommand } from "../formatter.js";
|
|
2
|
+
import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
class ToolInputError extends Error {
|
|
5
|
+
status = 400;
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ToolInputError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isJsonObjectOrArray(value) {
|
|
12
|
+
return !!value && typeof value === "object";
|
|
13
|
+
}
|
|
14
|
+
export function createDisplayA2UICardTool(ctx) {
|
|
15
|
+
const { config, sessionId, taskId, messageId } = ctx;
|
|
16
|
+
return {
|
|
17
|
+
name: "displayA2UICard",
|
|
18
|
+
label: "Display A2UI Card",
|
|
19
|
+
description: "当模型根据 MCP 工具返回结果判断需要向端侧下发 A2UI card 时调用。参数 cardId 和 cardData 由模型根据 MCP 工具返回结果传入,本工具只负责下发卡片展示指令。",
|
|
20
|
+
parameters: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
cardId: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "A2UI card 的唯一标识。",
|
|
26
|
+
},
|
|
27
|
+
cardData: {
|
|
28
|
+
oneOf: [
|
|
29
|
+
{ type: "object" },
|
|
30
|
+
{ type: "array" },
|
|
31
|
+
],
|
|
32
|
+
description: "A2UI card 渲染所需的 JSON 对象或 JSON 数组,由模型根据 MCP 工具返回结果填充。",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["cardId", "cardData"],
|
|
36
|
+
},
|
|
37
|
+
async execute(toolCallId, params) {
|
|
38
|
+
const cardId = typeof params?.cardId === "string" ? params.cardId.trim() : "";
|
|
39
|
+
const cardData = params?.cardData;
|
|
40
|
+
if (!cardId) {
|
|
41
|
+
throw new ToolInputError("缺少必填参数: cardId");
|
|
42
|
+
}
|
|
43
|
+
if (!isJsonObjectOrArray(cardData)) {
|
|
44
|
+
throw new ToolInputError("缺少必填参数: cardData,且必须是 JSON 对象或 JSON 数组");
|
|
45
|
+
}
|
|
46
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
47
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
48
|
+
const command = {
|
|
49
|
+
header: {
|
|
50
|
+
namespace: "Common",
|
|
51
|
+
name: "DisplayFACard",
|
|
52
|
+
},
|
|
53
|
+
payload: {
|
|
54
|
+
isA2ui: true,
|
|
55
|
+
a2uiParam: {
|
|
56
|
+
cardId,
|
|
57
|
+
cardData,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
logger.log(`[DISPLAY-A2UI-CARD] sending card, cardId=${cardId}`);
|
|
62
|
+
await sendCommand({
|
|
63
|
+
config,
|
|
64
|
+
sessionId,
|
|
65
|
+
taskId: currentTaskId,
|
|
66
|
+
messageId: currentMessageId,
|
|
67
|
+
command,
|
|
68
|
+
toolCallId,
|
|
69
|
+
});
|
|
70
|
+
logger.log(`[DISPLAY-A2UI-CARD] card sent successfully, cardId=${cardId}`);
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
success: true,
|
|
77
|
+
cardId,
|
|
78
|
+
message: "A2UI card display command sent successfully.",
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -6,7 +6,7 @@ export function createGetCollectionToolSchemaTool(ctx) {
|
|
|
6
6
|
return createSchemaTool({
|
|
7
7
|
name: "get_collection_tool_schema",
|
|
8
8
|
label: "Get Collection Tool Schema",
|
|
9
|
-
description: "
|
|
9
|
+
description: "获取可在用户设备上添加、检索、删除小艺收藏(也叫小艺帮记)中的公共知识数据的相关端工具列表。",
|
|
10
10
|
tools: [createXiaoyiAddCollectionTool(ctx), createXiaoyiCollectionTool(ctx), createXiaoyiDeleteCollectionTool(ctx)],
|
|
11
11
|
});
|
|
12
12
|
}
|
|
@@ -2,14 +2,13 @@ import { createSchemaTool } from "./schema-tool-factory.js";
|
|
|
2
2
|
import { createSearchFileTool } from "./search-file-tool.js";
|
|
3
3
|
import { createUploadFileTool } from "./upload-file-tool.js";
|
|
4
4
|
import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
|
|
5
|
-
import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
|
|
6
5
|
export function createGetDeviceFileToolSchemaTool(ctx) {
|
|
7
6
|
const searchFileTool = createSearchFileTool(ctx);
|
|
8
7
|
const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
|
|
9
8
|
return createSchemaTool({
|
|
10
9
|
name: "get_device_file_tool_schema",
|
|
11
10
|
label: "Get Device File Tool Schema",
|
|
12
|
-
description: "
|
|
13
|
-
tools: [searchFileTool, createUploadFileTool(ctx), saveFileToPhoneTool
|
|
11
|
+
description: "获取可在用户设备上搜索文件系统的文件、将用户设备本地文件上传到公网并获取链接、保存文件到文件管理器的相关端工具列表。",
|
|
12
|
+
tools: [searchFileTool, createUploadFileTool(ctx), saveFileToPhoneTool],
|
|
14
13
|
});
|
|
15
14
|
}
|
|
@@ -75,7 +75,7 @@ export function createLocationTool(ctx) {
|
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
|
-
reject(new Error(`获取位置失败: ${event.
|
|
78
|
+
reject(new Error(`获取位置失败: ${JSON.stringify(event.outputs)}`));
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
};
|
|
@@ -90,6 +90,7 @@ export function createLocationTool(ctx) {
|
|
|
90
90
|
taskId: currentTaskId,
|
|
91
91
|
messageId,
|
|
92
92
|
command,
|
|
93
|
+
toolCallId,
|
|
93
94
|
}).then(() => {
|
|
94
95
|
}).catch((error) => {
|
|
95
96
|
clearTimeout(timeout);
|