@ynhcj/xiaoyi-channel 0.0.67-beta → 0.0.68-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.
@@ -28,6 +28,7 @@ import { sendCommandToCarTool } from "./tools/send-command-to-car-tool.js";
28
28
  import { xiaoyiCollectionTool } from "./tools/xiaoyi-collection-tool.js";
29
29
  import { xiaoyiAddCollectionTool } from "./tools/xiaoyi-add-collection-tool.js";
30
30
  import { xiaoyiDeleteCollectionTool } from "./tools/xiaoyi-delete-collection-tool.js";
31
+ import { saveMediaToGalleryTool } from "./tools/save-media-to-gallery-tool.js";
31
32
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
32
33
  import { getCurrentSessionContext } from "./tools/session-manager.js";
33
34
  import { logger } from "./utils/logger.js";
@@ -70,7 +71,7 @@ export const xyPlugin = {
70
71
  },
71
72
  outbound: xyOutbound,
72
73
  agentTools: () => {
73
- const allTools = [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, sendCommandToCarTool, xiaoyiCollectionTool, xiaoyiAddCollectionTool, xiaoyiDeleteCollectionTool];
74
+ const allTools = [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, sendCommandToCarTool, xiaoyiCollectionTool, xiaoyiAddCollectionTool, xiaoyiDeleteCollectionTool, saveMediaToGalleryTool];
74
75
  const ctx = getCurrentSessionContext();
75
76
  const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
76
77
  logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * XY save media to gallery tool - saves image or video files to user's device gallery.
3
+ * Supports local file paths (auto-uploaded to get public URL) and public URLs.
4
+ */
5
+ export declare const saveMediaToGalleryTool: any;
@@ -0,0 +1,170 @@
1
+ import { getXYWebSocketManager } from "../client.js";
2
+ import { sendCommand } from "../formatter.js";
3
+ import { getCurrentSessionContext } from "./session-manager.js";
4
+ import { XYFileUploadService } from "../file-upload.js";
5
+ /**
6
+ * Duck-typed ToolInputError: openclaw 按 .name 字段匹配,不用 instanceof。
7
+ * 抛出此错误会让 openclaw 返回 HTTP 400 而非 500,
8
+ * LLM 会将其识别为参数错误而非瞬时故障,不会触发重试。
9
+ */
10
+ class ToolInputError extends Error {
11
+ status = 400;
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "ToolInputError";
15
+ }
16
+ }
17
+ /**
18
+ * XY save media to gallery tool - saves image or video files to user's device gallery.
19
+ * Supports local file paths (auto-uploaded to get public URL) and public URLs.
20
+ */
21
+ export const saveMediaToGalleryTool = {
22
+ name: "SaveMediaToGallery",
23
+ label: "Save Media to Gallery",
24
+ description: `将图片文件或者视频文件保存到手机图库。
25
+ 工具参数说明:
26
+ a. mediaType:非必填,string类型,不传端侧默认为pic。支持传 pic(图片) 或 video(视频)。
27
+ b. fileName:非必填,string类型,文件名称,不传手机侧默认生成随机uuid。
28
+ c. url:必填,string类型,支持本地路径或者公网url路径。如果是本地路径,会先上传获取公网url再保存到图库。
29
+
30
+ 注意:
31
+ a. 操作超时时间为60秒,请勿重复调用此工具
32
+ b. 如果遇到各类调用失败场景,最多只能重试一次,不可以重复调用多次。
33
+ c. 调用工具前需认真检查调用参数是否满足工具要求
34
+
35
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
36
+ `,
37
+ parameters: {
38
+ type: "object",
39
+ properties: {
40
+ mediaType: {
41
+ type: "string",
42
+ description: "非必填,不传默认为pic。支持 pic(图片) 或 video(视频)。",
43
+ },
44
+ fileName: {
45
+ type: "string",
46
+ description: "非必填,文件名称,不传手机侧默认生成随机uuid。",
47
+ },
48
+ url: {
49
+ type: "string",
50
+ description: "必填,支持本地路径或者公网url路径。如果是本地路径会先上传获取公网url。",
51
+ },
52
+ },
53
+ required: ["url"],
54
+ },
55
+ async execute(toolCallId, params) {
56
+ // Validate parameters
57
+ const { mediaType, fileName, url } = params;
58
+ if (!url || typeof url !== "string") {
59
+ throw new ToolInputError("缺少必填参数: url");
60
+ }
61
+ if (mediaType && !["pic", "video"].includes(mediaType)) {
62
+ throw new ToolInputError(`mediaType只支持 pic 或 video,当前值: ${mediaType}`);
63
+ }
64
+ // Get session context
65
+ const sessionContext = getCurrentSessionContext();
66
+ if (!sessionContext) {
67
+ throw new Error("No active XY session found. SaveMediaToGallery tool can only be used during an active conversation.");
68
+ }
69
+ const { config, sessionId, taskId, messageId } = sessionContext;
70
+ // Get WebSocket manager
71
+ const wsManager = getXYWebSocketManager(config);
72
+ // Determine the URL: if it's a local path, upload first to get public URL
73
+ let publicUrl = url;
74
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
75
+ // Local file path - upload to get public URL
76
+ const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
77
+ publicUrl = await uploadService.uploadFileAndGetUrl(url);
78
+ if (!publicUrl) {
79
+ throw new Error("本地文件上传失败,无法获取公网URL");
80
+ }
81
+ }
82
+ // Build intentParam
83
+ const intentParam = {
84
+ url: publicUrl,
85
+ };
86
+ if (mediaType) {
87
+ intentParam.mediaType = mediaType;
88
+ }
89
+ if (fileName) {
90
+ intentParam.fileName = fileName;
91
+ }
92
+ // Build SaveMediaToGallery command
93
+ const command = {
94
+ header: {
95
+ namespace: "Common",
96
+ name: "Action",
97
+ },
98
+ payload: {
99
+ cardParam: {},
100
+ executeParam: {
101
+ executeMode: "background",
102
+ intentName: "SaveMediaToGallery",
103
+ bundleName: "com.huawei.hmos.vassistant",
104
+ dimension: "",
105
+ needUnlock: true,
106
+ actionResponse: true,
107
+ appType: "OHOS_APP",
108
+ timeOut: 5,
109
+ intentParam,
110
+ permissionId: ["ohos.permission.WRITE_IMAGEVIDEO"],
111
+ achieveType: "INTENT",
112
+ },
113
+ responses: [
114
+ {
115
+ resultCode: "",
116
+ displayText: "",
117
+ ttsText: "",
118
+ },
119
+ ],
120
+ needUploadResult: true,
121
+ noHalfPage: false,
122
+ pageControlRelated: false,
123
+ },
124
+ };
125
+ // Send command and wait for response (60 second timeout)
126
+ return new Promise((resolve, reject) => {
127
+ const timeout = setTimeout(() => {
128
+ wsManager.off("data-event", handler);
129
+ reject(new Error("保存媒体到图库超时(60秒)"));
130
+ }, 60000);
131
+ // Listen for data events from WebSocket
132
+ const handler = (event) => {
133
+ if (event.intentName === "SaveMediaToGallery") {
134
+ clearTimeout(timeout);
135
+ wsManager.off("data-event", handler);
136
+ if (event.status === "success" && event.outputs) {
137
+ resolve({
138
+ content: [
139
+ {
140
+ type: "text",
141
+ text: JSON.stringify(event.outputs),
142
+ }
143
+ ]
144
+ });
145
+ }
146
+ else {
147
+ reject(new Error(`保存媒体到图库失败: ${event.status}`));
148
+ }
149
+ }
150
+ };
151
+ // Register event handler
152
+ wsManager.on("data-event", handler);
153
+ // Send the command
154
+ sendCommand({
155
+ config,
156
+ sessionId,
157
+ taskId,
158
+ messageId,
159
+ command,
160
+ })
161
+ .then(() => {
162
+ })
163
+ .catch((error) => {
164
+ clearTimeout(timeout);
165
+ wsManager.off("data-event", handler);
166
+ reject(error);
167
+ });
168
+ });
169
+ },
170
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.67-beta",
3
+ "version": "0.0.68-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",