work-agent 0.1.0

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.
Files changed (245) hide show
  1. package/README.md +234 -0
  2. package/app/(admin)/approvals/page.tsx +16 -0
  3. package/app/(admin)/audit/page.tsx +18 -0
  4. package/app/(admin)/layout.tsx +47 -0
  5. package/app/(admin)/scheduled-tasks/page.tsx +17 -0
  6. package/app/(admin)/settings/page.tsx +46 -0
  7. package/app/(admin)/skills/[name]/page.tsx +378 -0
  8. package/app/(admin)/skills/page.tsx +406 -0
  9. package/app/(admin)/statistics/page.tsx +416 -0
  10. package/app/(admin)/tickets/[id]/page.tsx +348 -0
  11. package/app/(admin)/tickets/new/page.tsx +309 -0
  12. package/app/(admin)/tickets/page.tsx +27 -0
  13. package/app/api/audit/route.ts +30 -0
  14. package/app/api/auth/feishu/callback/route.ts +72 -0
  15. package/app/api/auth/feishu/login/route.ts +17 -0
  16. package/app/api/auth/feishu/sso/route.ts +78 -0
  17. package/app/api/auth/login/route.ts +85 -0
  18. package/app/api/auth/oauth/route.ts +168 -0
  19. package/app/api/config/providers/route.ts +105 -0
  20. package/app/api/config/route.ts +115 -0
  21. package/app/api/config/status/route.ts +56 -0
  22. package/app/api/config/test/route.ts +212 -0
  23. package/app/api/documents/[id]/route.ts +88 -0
  24. package/app/api/documents/route.ts +53 -0
  25. package/app/api/health/route.ts +32 -0
  26. package/app/api/knowledge/[id]/route.ts +152 -0
  27. package/app/api/knowledge/from-session/route.ts +27 -0
  28. package/app/api/knowledge/route.ts +100 -0
  29. package/app/api/market/knowledge/[id]/route.ts +92 -0
  30. package/app/api/market/knowledge/route.ts +130 -0
  31. package/app/api/marketplace/skills/[id]/approve/route.ts +68 -0
  32. package/app/api/marketplace/skills/[id]/certify/route.ts +54 -0
  33. package/app/api/marketplace/skills/[id]/install/route.ts +180 -0
  34. package/app/api/marketplace/skills/[id]/promote-to-system/route.ts +219 -0
  35. package/app/api/marketplace/skills/[id]/rate/route.ts +90 -0
  36. package/app/api/marketplace/skills/[id]/ratings/route.ts +55 -0
  37. package/app/api/marketplace/skills/[id]/reject/route.ts +68 -0
  38. package/app/api/marketplace/skills/[id]/route.ts +177 -0
  39. package/app/api/marketplace/skills/route.ts +235 -0
  40. package/app/api/memory/route.ts +40 -0
  41. package/app/api/my/files/[id]/route.ts +52 -0
  42. package/app/api/my/files/route.ts +230 -0
  43. package/app/api/my/knowledge/route.ts +36 -0
  44. package/app/api/pi-chat/route.ts +443 -0
  45. package/app/api/recommend/route.ts +38 -0
  46. package/app/api/scheduled-tasks/[id]/execute/route.ts +132 -0
  47. package/app/api/scheduled-tasks/[id]/route.ts +165 -0
  48. package/app/api/scheduled-tasks/[id]/toggle/route.ts +53 -0
  49. package/app/api/scheduled-tasks/route.ts +101 -0
  50. package/app/api/sessions/[id]/messages/route.ts +212 -0
  51. package/app/api/sessions/route.ts +101 -0
  52. package/app/api/share/file/[id]/route.ts +37 -0
  53. package/app/api/skills/[name]/execute/route.ts +121 -0
  54. package/app/api/skills/[name]/route.ts +167 -0
  55. package/app/api/skills/create/route.ts +65 -0
  56. package/app/api/skills/generate/route.ts +405 -0
  57. package/app/api/skills/installed/route.ts +151 -0
  58. package/app/api/skills/route.ts +174 -0
  59. package/app/api/skills/translate/route.ts +40 -0
  60. package/app/api/skills/user/[name]/route.ts +159 -0
  61. package/app/api/skills/user/route.ts +90 -0
  62. package/app/api/statistics/route.ts +94 -0
  63. package/app/api/task-executions/[id]/route.ts +34 -0
  64. package/app/api/task-executions/route.ts +29 -0
  65. package/app/api/tickets/[id]/approve/route.ts +129 -0
  66. package/app/api/tickets/[id]/execute/route.ts +201 -0
  67. package/app/api/tickets/[id]/route.ts +127 -0
  68. package/app/api/tickets/route.ts +103 -0
  69. package/app/api/user/skills/route.ts +175 -0
  70. package/app/api/users/route.ts +80 -0
  71. package/app/chat/page.tsx +5 -0
  72. package/app/globals.css +84 -0
  73. package/app/h5/layout.tsx +5 -0
  74. package/app/h5/mobile-approvals-page.tsx +167 -0
  75. package/app/h5/mobile-chat-page.tsx +951 -0
  76. package/app/h5/mobile-profile-page.tsx +147 -0
  77. package/app/h5/mobile-tickets-page.tsx +121 -0
  78. package/app/h5/page.tsx +23 -0
  79. package/app/h5/ticket-action-buttons.tsx +80 -0
  80. package/app/layout.tsx +26 -0
  81. package/app/login/page.tsx +318 -0
  82. package/app/market/knowledge/[id]/page.tsx +77 -0
  83. package/app/market/knowledge/page.tsx +358 -0
  84. package/app/market/layout.tsx +29 -0
  85. package/app/market/page.tsx +18 -0
  86. package/app/market/skills/page.tsx +397 -0
  87. package/app/my/files/page.tsx +511 -0
  88. package/app/my/knowledge/[id]/page.tsx +271 -0
  89. package/app/my/knowledge/new/page.tsx +234 -0
  90. package/app/my/knowledge/page.tsx +248 -0
  91. package/app/my/layout.tsx +32 -0
  92. package/app/my/memory/page.tsx +164 -0
  93. package/app/my/page.tsx +18 -0
  94. package/app/my/scheduled-tasks/[id]/edit/page.tsx +290 -0
  95. package/app/my/scheduled-tasks/[id]/executions/page.tsx +275 -0
  96. package/app/my/scheduled-tasks/[id]/page.tsx +284 -0
  97. package/app/my/scheduled-tasks/new/page.tsx +230 -0
  98. package/app/my/scheduled-tasks/page.tsx +27 -0
  99. package/app/my/skills/[name]/page.tsx +320 -0
  100. package/app/my/skills/new/page.tsx +394 -0
  101. package/app/my/skills/page.tsx +303 -0
  102. package/app/page.tsx +2288 -0
  103. package/app/share/[sessionId]/page.tsx +226 -0
  104. package/app/share/file/[id]/page.tsx +140 -0
  105. package/bin/README.md +63 -0
  106. package/bin/generate-api-system +300 -0
  107. package/bin/postinstall.js +95 -0
  108. package/bin/work-agent.js +173 -0
  109. package/components/ai-elements/agent.tsx +142 -0
  110. package/components/ai-elements/artifact.tsx +149 -0
  111. package/components/ai-elements/attachments.tsx +427 -0
  112. package/components/ai-elements/audio-player.tsx +232 -0
  113. package/components/ai-elements/canvas.tsx +26 -0
  114. package/components/ai-elements/chain-of-thought.tsx +223 -0
  115. package/components/ai-elements/checkpoint.tsx +72 -0
  116. package/components/ai-elements/code-block.tsx +555 -0
  117. package/components/ai-elements/commit.tsx +449 -0
  118. package/components/ai-elements/confirmation.tsx +173 -0
  119. package/components/ai-elements/connection.tsx +28 -0
  120. package/components/ai-elements/context.tsx +410 -0
  121. package/components/ai-elements/controls.tsx +19 -0
  122. package/components/ai-elements/conversation.tsx +167 -0
  123. package/components/ai-elements/edge.tsx +144 -0
  124. package/components/ai-elements/environment-variables.tsx +325 -0
  125. package/components/ai-elements/file-tree.tsx +298 -0
  126. package/components/ai-elements/image.tsx +25 -0
  127. package/components/ai-elements/inline-citation.tsx +294 -0
  128. package/components/ai-elements/jsx-preview.tsx +250 -0
  129. package/components/ai-elements/message.tsx +367 -0
  130. package/components/ai-elements/mic-selector.tsx +372 -0
  131. package/components/ai-elements/model-selector.tsx +214 -0
  132. package/components/ai-elements/node.tsx +72 -0
  133. package/components/ai-elements/open-in-chat.tsx +367 -0
  134. package/components/ai-elements/package-info.tsx +235 -0
  135. package/components/ai-elements/panel.tsx +16 -0
  136. package/components/ai-elements/persona.tsx +280 -0
  137. package/components/ai-elements/plan.tsx +144 -0
  138. package/components/ai-elements/prompt-input.tsx +1341 -0
  139. package/components/ai-elements/queue.tsx +275 -0
  140. package/components/ai-elements/reasoning.tsx +355 -0
  141. package/components/ai-elements/sandbox.tsx +133 -0
  142. package/components/ai-elements/schema-display.tsx +473 -0
  143. package/components/ai-elements/shimmer.tsx +78 -0
  144. package/components/ai-elements/snippet.tsx +141 -0
  145. package/components/ai-elements/sources.tsx +78 -0
  146. package/components/ai-elements/speech-input.tsx +324 -0
  147. package/components/ai-elements/stack-trace.tsx +531 -0
  148. package/components/ai-elements/suggestion.tsx +58 -0
  149. package/components/ai-elements/task.tsx +88 -0
  150. package/components/ai-elements/terminal.tsx +277 -0
  151. package/components/ai-elements/test-results.tsx +497 -0
  152. package/components/ai-elements/tool.tsx +174 -0
  153. package/components/ai-elements/toolbar.tsx +17 -0
  154. package/components/ai-elements/transcription.tsx +126 -0
  155. package/components/ai-elements/voice-selector.tsx +525 -0
  156. package/components/ai-elements/web-preview.tsx +282 -0
  157. package/components/audit-log-list.tsx +114 -0
  158. package/components/chat/EmptyPreviewState.tsx +12 -0
  159. package/components/chat/KnowledgePickerDialog.tsx +464 -0
  160. package/components/chat/KnowledgePreview.tsx +70 -0
  161. package/components/chat/KnowledgePreviewPanel.tsx +86 -0
  162. package/components/chat/MentionInput.tsx +309 -0
  163. package/components/chat/OrganizeDialog.tsx +258 -0
  164. package/components/chat/RecommendationBanner.tsx +94 -0
  165. package/components/chat/SaveToKnowledgeDialog.tsx +193 -0
  166. package/components/chat/SkillSelector.tsx +305 -0
  167. package/components/chat/SkillSwitcher.tsx +163 -0
  168. package/components/client-layout.tsx +15 -0
  169. package/components/knowledge/KnowledgeMetadataPanel.tsx +293 -0
  170. package/components/layout-wrapper.tsx +18 -0
  171. package/components/mobile-layout.tsx +62 -0
  172. package/components/scheduled-task-list.tsx +356 -0
  173. package/components/setup-guide.tsx +484 -0
  174. package/components/sub-nav.tsx +54 -0
  175. package/components/ticket-detail-content.tsx +383 -0
  176. package/components/ticket-list.tsx +366 -0
  177. package/components/top-nav.tsx +132 -0
  178. package/components/ui/accordion.tsx +58 -0
  179. package/components/ui/alert.tsx +59 -0
  180. package/components/ui/avatar.tsx +50 -0
  181. package/components/ui/badge.tsx +36 -0
  182. package/components/ui/button-group.tsx +83 -0
  183. package/components/ui/button.tsx +57 -0
  184. package/components/ui/card.tsx +91 -0
  185. package/components/ui/carousel.tsx +262 -0
  186. package/components/ui/collapsible.tsx +11 -0
  187. package/components/ui/command.tsx +153 -0
  188. package/components/ui/dialog.tsx +122 -0
  189. package/components/ui/dropdown-menu.tsx +200 -0
  190. package/components/ui/hover-card.tsx +29 -0
  191. package/components/ui/input-group.tsx +170 -0
  192. package/components/ui/input.tsx +22 -0
  193. package/components/ui/label.tsx +26 -0
  194. package/components/ui/popover.tsx +31 -0
  195. package/components/ui/progress.tsx +28 -0
  196. package/components/ui/scroll-area.tsx +48 -0
  197. package/components/ui/select.tsx +174 -0
  198. package/components/ui/separator.tsx +31 -0
  199. package/components/ui/spinner.tsx +16 -0
  200. package/components/ui/switch.tsx +29 -0
  201. package/components/ui/table.tsx +120 -0
  202. package/components/ui/tabs.tsx +55 -0
  203. package/components/ui/textarea.tsx +22 -0
  204. package/components/ui/tooltip.tsx +30 -0
  205. package/components/welcome-guide.tsx +182 -0
  206. package/components.json +24 -0
  207. package/lib/command-parser.ts +331 -0
  208. package/lib/dangerous-commands.ts +672 -0
  209. package/lib/db.ts +2250 -0
  210. package/lib/feishu-auth.ts +135 -0
  211. package/lib/file-storage.ts +306 -0
  212. package/lib/file-tool.ts +583 -0
  213. package/lib/knowledge-tool.ts +152 -0
  214. package/lib/knowledge-types.ts +66 -0
  215. package/lib/market-client.ts +313 -0
  216. package/lib/market-db.ts +736 -0
  217. package/lib/market-types.ts +51 -0
  218. package/lib/memory-tool.ts +211 -0
  219. package/lib/memory.ts +197 -0
  220. package/lib/pi-config.ts +436 -0
  221. package/lib/pi-session.ts +799 -0
  222. package/lib/pinyin.ts +13 -0
  223. package/lib/recommendation.ts +227 -0
  224. package/lib/risk-estimator.ts +350 -0
  225. package/lib/scheduled-task-tool.ts +184 -0
  226. package/lib/scheduler-init.ts +43 -0
  227. package/lib/scheduler.ts +416 -0
  228. package/lib/secure-bash-tool.ts +413 -0
  229. package/lib/skill-engine.ts +396 -0
  230. package/lib/skill-generator.ts +269 -0
  231. package/lib/skill-loader.ts +234 -0
  232. package/lib/skill-tool.ts +188 -0
  233. package/lib/skill-types.ts +82 -0
  234. package/lib/skills-init.ts +58 -0
  235. package/lib/ticket-tool.ts +246 -0
  236. package/lib/user-skill-types.ts +30 -0
  237. package/lib/user-skills.ts +362 -0
  238. package/lib/utils.ts +6 -0
  239. package/lib/workflow.ts +154 -0
  240. package/lib/zip-tool.ts +191 -0
  241. package/next.config.js +8 -0
  242. package/package.json +106 -0
  243. package/public/.gitkeep +1 -0
  244. package/public/icon.svg +1 -0
  245. package/tsconfig.json +42 -0
@@ -0,0 +1,583 @@
1
+ /**
2
+ * 文件管理工具
3
+ *
4
+ * 提供文件上传、下载、列表、删除等操作。
5
+ */
6
+
7
+ import { Type } from '@sinclair/typebox';
8
+ import type { ToolDefinition, AgentToolResult, ExtensionContext } from '@mariozechner/pi-coding-agent';
9
+ import { getDocuments, createDocument, deleteDocument, updateDocument, getDocument } from './db';
10
+ import { getStorageProvider, MAX_FILE_SIZE, isAllowedMimeType } from './file-storage';
11
+ import type { FileMetadata } from './db';
12
+
13
+ function formatFileSizeLocal(bytes: number): string {
14
+ if (bytes === 0) return '0 B';
15
+ const k = 1024;
16
+ const sizes = ['B', 'KB', 'MB', 'GB'];
17
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
18
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
19
+ }
20
+
21
+ const listFilesSchema = Type.Object({
22
+ search: Type.Optional(Type.String({ description: '搜索关键词(可选)' })),
23
+ limit: Type.Optional(Type.Number({ description: '返回数量限制,默认20' })),
24
+ userId: Type.String({ description: '用户ID' }),
25
+ });
26
+
27
+ interface ListFilesDetails {
28
+ success: boolean;
29
+ count?: number;
30
+ files?: Array<{
31
+ id: string;
32
+ title: string;
33
+ size: string;
34
+ mimeType: string;
35
+ isPublic: boolean;
36
+ createdAt: string;
37
+ }>;
38
+ error?: string;
39
+ }
40
+
41
+ export const listFilesTool: ToolDefinition<typeof listFilesSchema, ListFilesDetails> = {
42
+ name: 'list_user_files',
43
+ label: 'List User Files',
44
+ description: '列出用户上传的所有文件。可以搜索特定文件名。',
45
+ parameters: listFilesSchema,
46
+
47
+ async execute(
48
+ toolCallId: string,
49
+ params: any,
50
+ signal: AbortSignal | undefined,
51
+ onUpdate: ((result: AgentToolResult<ListFilesDetails>) => void) | undefined,
52
+ ctx: ExtensionContext
53
+ ): Promise<AgentToolResult<ListFilesDetails>> {
54
+ try {
55
+ const { search, limit = 20, userId } = params;
56
+
57
+ const documents = getDocuments({
58
+ author: userId,
59
+ type: 'file',
60
+ limit,
61
+ search,
62
+ }) as any[];
63
+
64
+ const files = documents.map(doc => ({
65
+ id: doc.id,
66
+ title: doc.title,
67
+ size: formatFileSizeLocal(doc.fileMetadata?.size || 0),
68
+ mimeType: doc.fileMetadata?.mimeType || 'unknown',
69
+ isPublic: doc.isPublic,
70
+ createdAt: doc.createdAt.toISOString(),
71
+ }));
72
+
73
+ const result: AgentToolResult<ListFilesDetails> = {
74
+ content: [{ type: 'text', text: `找到 ${files.length} 个文件:\n${files.map(f => `- ${f.title} (${f.size})`).join('\n')}` }],
75
+ details: { success: true, count: files.length, files },
76
+ };
77
+
78
+ onUpdate?.(result);
79
+ return result;
80
+ } catch (error: any) {
81
+ const errorResult: AgentToolResult<ListFilesDetails> = {
82
+ content: [{ type: 'text', text: `❌ 列出文件失败: ${error.message}` }],
83
+ details: { success: false, error: error.message },
84
+ };
85
+ onUpdate?.(errorResult);
86
+ return errorResult;
87
+ }
88
+ },
89
+ };
90
+
91
+ const getFileInfoSchema = Type.Object({
92
+ fileId: Type.String({ description: '文件ID' }),
93
+ userId: Type.String({ description: '用户ID' }),
94
+ });
95
+
96
+ interface GetFileInfoDetails {
97
+ success: boolean;
98
+ file?: {
99
+ id: string;
100
+ title: string;
101
+ description?: string;
102
+ size: string;
103
+ sizeBytes: number;
104
+ mimeType?: string;
105
+ md5?: string;
106
+ isPublic?: boolean;
107
+ createdAt: string;
108
+ updatedAt: string;
109
+ };
110
+ error?: string;
111
+ }
112
+
113
+ export const getFileInfoTool: ToolDefinition<typeof getFileInfoSchema, GetFileInfoDetails> = {
114
+ name: 'get_file_info',
115
+ label: 'Get File Info',
116
+ description: '获取文件的详细信息,包括元数据。',
117
+ parameters: getFileInfoSchema,
118
+
119
+ async execute(
120
+ toolCallId: string,
121
+ params: any,
122
+ signal: AbortSignal | undefined,
123
+ onUpdate: ((result: AgentToolResult<GetFileInfoDetails>) => void) | undefined,
124
+ ctx: ExtensionContext
125
+ ): Promise<AgentToolResult<GetFileInfoDetails>> {
126
+ try {
127
+ const { fileId, userId } = params;
128
+ const doc = getDocument(fileId);
129
+
130
+ if (!doc) {
131
+ const errorResult: AgentToolResult<GetFileInfoDetails> = {
132
+ content: [{ type: 'text', text: '❌ 文件未找到' }],
133
+ details: { success: false, error: 'File not found' },
134
+ };
135
+ onUpdate?.(errorResult);
136
+ return errorResult;
137
+ }
138
+
139
+ if (doc.author !== userId) {
140
+ const errorResult: AgentToolResult<GetFileInfoDetails> = {
141
+ content: [{ type: 'text', text: '❌ 无权访问此文件' }],
142
+ details: { success: false, error: 'Permission denied' },
143
+ };
144
+ onUpdate?.(errorResult);
145
+ return errorResult;
146
+ }
147
+
148
+ const result: AgentToolResult<GetFileInfoDetails> = {
149
+ content: [{ type: 'text', text: `文件信息:\n- 名称: ${doc.title}\n- 大小: ${formatFileSizeLocal(doc.fileMetadata?.size || 0)}\n- 类型: ${doc.fileMetadata?.mimeType || 'unknown'}` }],
150
+ details: {
151
+ success: true,
152
+ file: {
153
+ id: doc.id,
154
+ title: doc.title,
155
+ description: doc.content,
156
+ size: formatFileSizeLocal(doc.fileMetadata?.size || 0),
157
+ sizeBytes: doc.fileMetadata?.size || 0,
158
+ mimeType: doc.fileMetadata?.mimeType,
159
+ md5: doc.fileMetadata?.md5,
160
+ isPublic: doc.isPublic,
161
+ createdAt: doc.createdAt.toISOString(),
162
+ updatedAt: doc.updatedAt.toISOString(),
163
+ },
164
+ },
165
+ };
166
+
167
+ onUpdate?.(result);
168
+ return result;
169
+ } catch (error: any) {
170
+ const errorResult: AgentToolResult<GetFileInfoDetails> = {
171
+ content: [{ type: 'text', text: `❌ 获取文件信息失败: ${error.message}` }],
172
+ details: { success: false, error: error.message },
173
+ };
174
+ onUpdate?.(errorResult);
175
+ return errorResult;
176
+ }
177
+ },
178
+ };
179
+
180
+ const downloadFileSchema = Type.Object({
181
+ fileId: Type.String({ description: '文件ID' }),
182
+ userId: Type.String({ description: '用户ID' }),
183
+ maxSize: Type.Optional(Type.Number({ description: '最大允许下载的文件大小(字节),默认1MB' })),
184
+ });
185
+
186
+ interface DownloadFileDetails {
187
+ success: boolean;
188
+ file?: {
189
+ id: string;
190
+ title: string;
191
+ mimeType: string;
192
+ size: number;
193
+ base64Content: string;
194
+ };
195
+ error?: string;
196
+ }
197
+
198
+ export const downloadFileTool: ToolDefinition<typeof downloadFileSchema, DownloadFileDetails> = {
199
+ name: 'download_file',
200
+ label: 'Download File',
201
+ description: '下载文件内容。返回文件的Base64编码内容和元数据。适用于小文件(<1MB)。',
202
+ parameters: downloadFileSchema,
203
+
204
+ async execute(
205
+ toolCallId: string,
206
+ params: any,
207
+ signal: AbortSignal | undefined,
208
+ onUpdate: ((result: AgentToolResult<DownloadFileDetails>) => void) | undefined,
209
+ ctx: ExtensionContext
210
+ ): Promise<AgentToolResult<DownloadFileDetails>> {
211
+ try {
212
+ const { fileId, userId, maxSize = 1024 * 1024 } = params;
213
+ const doc = getDocument(fileId);
214
+
215
+ if (!doc) {
216
+ const errorResult: AgentToolResult<DownloadFileDetails> = {
217
+ content: [{ type: 'text', text: '❌ 文件未找到' }],
218
+ details: { success: false, error: 'File not found' },
219
+ };
220
+ onUpdate?.(errorResult);
221
+ return errorResult;
222
+ }
223
+
224
+ if (doc.author !== userId && !doc.isPublic) {
225
+ const errorResult: AgentToolResult<DownloadFileDetails> = {
226
+ content: [{ type: 'text', text: '❌ 无权访问此文件' }],
227
+ details: { success: false, error: 'Permission denied' },
228
+ };
229
+ onUpdate?.(errorResult);
230
+ return errorResult;
231
+ }
232
+
233
+ if (!doc.fileMetadata?.storageKey) {
234
+ const errorResult: AgentToolResult<DownloadFileDetails> = {
235
+ content: [{ type: 'text', text: '❌ 文件存储信息缺失' }],
236
+ details: { success: false, error: 'File storage key not found' },
237
+ };
238
+ onUpdate?.(errorResult);
239
+ return errorResult;
240
+ }
241
+
242
+ if (doc.fileMetadata.size > maxSize) {
243
+ const errorResult: AgentToolResult<DownloadFileDetails> = {
244
+ content: [{ type: 'text', text: `❌ 文件太大 (${formatFileSizeLocal(doc.fileMetadata.size)}),最大允许 ${formatFileSizeLocal(maxSize)}` }],
245
+ details: { success: false, error: 'File too large' },
246
+ };
247
+ onUpdate?.(errorResult);
248
+ return errorResult;
249
+ }
250
+
251
+ const storage = getStorageProvider();
252
+ const buffer = await storage.download(doc.fileMetadata.storageKey);
253
+ const base64 = buffer.toString('base64');
254
+
255
+ const result: AgentToolResult<DownloadFileDetails> = {
256
+ content: [{ type: 'text', text: `✅ 已下载文件 "${doc.title}" (${formatFileSizeLocal(buffer.length)})` }],
257
+ details: {
258
+ success: true,
259
+ file: {
260
+ id: doc.id,
261
+ title: doc.title,
262
+ mimeType: doc.fileMetadata.mimeType,
263
+ size: buffer.length,
264
+ base64Content: base64,
265
+ },
266
+ },
267
+ };
268
+
269
+ onUpdate?.(result);
270
+ return result;
271
+ } catch (error: any) {
272
+ const errorResult: AgentToolResult<DownloadFileDetails> = {
273
+ content: [{ type: 'text', text: `❌ 下载文件失败: ${error.message}` }],
274
+ details: { success: false, error: error.message },
275
+ };
276
+ onUpdate?.(errorResult);
277
+ return errorResult;
278
+ }
279
+ },
280
+ };
281
+
282
+ const uploadFileSchema = Type.Object({
283
+ filename: Type.String({ description: '文件名' }),
284
+ userId: Type.String({ description: '用户ID' }),
285
+ content: Type.Optional(Type.String({ description: '文件内容(Base64编码)' })),
286
+ url: Type.Optional(Type.String({ description: '文件URL(可选,优先使用)' })),
287
+ mimeType: Type.Optional(Type.String({ description: '文件MIME类型(可选)' })),
288
+ description: Type.Optional(Type.String({ description: '文件描述(可选)' })),
289
+ isPublic: Type.Optional(Type.Boolean({ description: '是否公开分享(默认false)' })),
290
+ });
291
+
292
+ interface UploadFileDetails {
293
+ success: boolean;
294
+ file?: {
295
+ id: string;
296
+ title: string;
297
+ size: string;
298
+ mimeType: string;
299
+ isPublic?: boolean;
300
+ };
301
+ error?: string;
302
+ }
303
+
304
+ export const uploadFileTool: ToolDefinition<typeof uploadFileSchema, UploadFileDetails> = {
305
+ name: 'upload_file',
306
+ label: 'Upload File',
307
+ description: '上传文件。可以从URL下载文件,或从Base64内容创建文件。',
308
+ parameters: uploadFileSchema,
309
+
310
+ async execute(
311
+ toolCallId: string,
312
+ params: any,
313
+ signal: AbortSignal | undefined,
314
+ onUpdate: ((result: AgentToolResult<UploadFileDetails>) => void) | undefined,
315
+ ctx: ExtensionContext
316
+ ): Promise<AgentToolResult<UploadFileDetails>> {
317
+ try {
318
+ const { filename, userId, content, url, mimeType, description, isPublic } = params;
319
+ let buffer: Buffer;
320
+ let detectedMimeType = mimeType || 'application/octet-stream';
321
+
322
+ if (url) {
323
+ const response = await fetch(url);
324
+ if (!response.ok) {
325
+ const errorResult: AgentToolResult<UploadFileDetails> = {
326
+ content: [{ type: 'text', text: `❌ 下载URL失败: ${response.status}` }],
327
+ details: { success: false, error: `Failed to fetch URL: ${response.status}` },
328
+ };
329
+ onUpdate?.(errorResult);
330
+ return errorResult;
331
+ }
332
+ const arrayBuffer = await response.arrayBuffer();
333
+ buffer = Buffer.from(arrayBuffer);
334
+ detectedMimeType = response.headers.get('content-type') || detectedMimeType;
335
+ } else if (content) {
336
+ buffer = Buffer.from(content, 'base64');
337
+ } else {
338
+ const errorResult: AgentToolResult<UploadFileDetails> = {
339
+ content: [{ type: 'text', text: '❌ 需要提供 url 或 content 参数' }],
340
+ details: { success: false, error: 'Either url or content is required' },
341
+ };
342
+ onUpdate?.(errorResult);
343
+ return errorResult;
344
+ }
345
+
346
+ if (buffer.length > MAX_FILE_SIZE) {
347
+ const errorResult: AgentToolResult<UploadFileDetails> = {
348
+ content: [{ type: 'text', text: `❌ 文件太大 (${formatFileSizeLocal(buffer.length)}),最大允许 ${formatFileSizeLocal(MAX_FILE_SIZE)}` }],
349
+ details: { success: false, error: 'File too large' },
350
+ };
351
+ onUpdate?.(errorResult);
352
+ return errorResult;
353
+ }
354
+
355
+ if (!isAllowedMimeType(detectedMimeType)) {
356
+ const errorResult: AgentToolResult<UploadFileDetails> = {
357
+ content: [{ type: 'text', text: `❌ 不支持的文件类型: ${detectedMimeType}` }],
358
+ details: { success: false, error: `File type "${detectedMimeType}" is not allowed` },
359
+ };
360
+ onUpdate?.(errorResult);
361
+ return errorResult;
362
+ }
363
+
364
+ const storage = getStorageProvider();
365
+ const { storageKey, md5 } = await storage.upload({
366
+ userId,
367
+ filename,
368
+ mimeType: detectedMimeType,
369
+ buffer,
370
+ });
371
+
372
+ const fileMetadata: FileMetadata = {
373
+ size: buffer.length,
374
+ mimeType: detectedMimeType,
375
+ md5,
376
+ storageKey,
377
+ storageType: 'local',
378
+ originalName: filename,
379
+ };
380
+
381
+ const document = createDocument({
382
+ title: filename,
383
+ content: description || '',
384
+ author: userId,
385
+ type: 'file',
386
+ fileMetadata,
387
+ isPublic: isPublic || false,
388
+ isActive: true,
389
+ });
390
+
391
+ const result: AgentToolResult<UploadFileDetails> = {
392
+ content: [{ type: 'text', text: `✅ 文件上传成功: ${filename} (${formatFileSizeLocal(buffer.length)})` }],
393
+ details: {
394
+ success: true,
395
+ file: {
396
+ id: document.id,
397
+ title: document.title,
398
+ size: formatFileSizeLocal(buffer.length),
399
+ mimeType: detectedMimeType,
400
+ isPublic: document.isPublic,
401
+ },
402
+ },
403
+ };
404
+
405
+ onUpdate?.(result);
406
+ return result;
407
+ } catch (error: any) {
408
+ const errorResult: AgentToolResult<UploadFileDetails> = {
409
+ content: [{ type: 'text', text: `❌ 上传文件失败: ${error.message}` }],
410
+ details: { success: false, error: error.message },
411
+ };
412
+ onUpdate?.(errorResult);
413
+ return errorResult;
414
+ }
415
+ },
416
+ };
417
+
418
+ const deleteFileSchema = Type.Object({
419
+ fileId: Type.String({ description: '文件ID' }),
420
+ userId: Type.String({ description: '用户ID' }),
421
+ });
422
+
423
+ interface DeleteFileDetails {
424
+ success: boolean;
425
+ message?: string;
426
+ error?: string;
427
+ }
428
+
429
+ export const deleteFileTool: ToolDefinition<typeof deleteFileSchema, DeleteFileDetails> = {
430
+ name: 'delete_file',
431
+ label: 'Delete File',
432
+ description: '删除文件。',
433
+ parameters: deleteFileSchema,
434
+
435
+ async execute(
436
+ toolCallId: string,
437
+ params: any,
438
+ signal: AbortSignal | undefined,
439
+ onUpdate: ((result: AgentToolResult<DeleteFileDetails>) => void) | undefined,
440
+ ctx: ExtensionContext
441
+ ): Promise<AgentToolResult<DeleteFileDetails>> {
442
+ try {
443
+ const { fileId, userId } = params;
444
+ const doc = getDocument(fileId);
445
+
446
+ if (!doc) {
447
+ const errorResult: AgentToolResult<DeleteFileDetails> = {
448
+ content: [{ type: 'text', text: '❌ 文件未找到' }],
449
+ details: { success: false, error: 'File not found' },
450
+ };
451
+ onUpdate?.(errorResult);
452
+ return errorResult;
453
+ }
454
+
455
+ if (doc.author !== userId) {
456
+ const errorResult: AgentToolResult<DeleteFileDetails> = {
457
+ content: [{ type: 'text', text: '❌ 无权删除此文件' }],
458
+ details: { success: false, error: 'Permission denied' },
459
+ };
460
+ onUpdate?.(errorResult);
461
+ return errorResult;
462
+ }
463
+
464
+ if (doc.fileMetadata?.storageKey) {
465
+ const storage = getStorageProvider();
466
+ await storage.delete(doc.fileMetadata.storageKey);
467
+ }
468
+
469
+ deleteDocument(fileId);
470
+
471
+ const result: AgentToolResult<DeleteFileDetails> = {
472
+ content: [{ type: 'text', text: `✅ 文件 "${doc.title}" 已删除` }],
473
+ details: { success: true, message: `File "${doc.title}" deleted` },
474
+ };
475
+
476
+ onUpdate?.(result);
477
+ return result;
478
+ } catch (error: any) {
479
+ const errorResult: AgentToolResult<DeleteFileDetails> = {
480
+ content: [{ type: 'text', text: `❌ 删除文件失败: ${error.message}` }],
481
+ details: { success: false, error: error.message },
482
+ };
483
+ onUpdate?.(errorResult);
484
+ return errorResult;
485
+ }
486
+ },
487
+ };
488
+
489
+ const updateFileInfoSchema = Type.Object({
490
+ fileId: Type.String({ description: '文件ID' }),
491
+ userId: Type.String({ description: '用户ID' }),
492
+ title: Type.Optional(Type.String({ description: '新标题(可选)' })),
493
+ description: Type.Optional(Type.String({ description: '新描述(可选)' })),
494
+ isPublic: Type.Optional(Type.Boolean({ description: '是否公开分享(可选)' })),
495
+ });
496
+
497
+ interface UpdateFileInfoDetails {
498
+ success: boolean;
499
+ file?: {
500
+ id?: string;
501
+ title?: string;
502
+ description?: string;
503
+ isPublic?: boolean;
504
+ };
505
+ error?: string;
506
+ }
507
+
508
+ export const updateFileInfoTool: ToolDefinition<typeof updateFileInfoSchema, UpdateFileInfoDetails> = {
509
+ name: 'update_file_info',
510
+ label: 'Update File Info',
511
+ description: '更新文件信息(标题、描述、公开状态)。',
512
+ parameters: updateFileInfoSchema,
513
+
514
+ async execute(
515
+ toolCallId: string,
516
+ params: any,
517
+ signal: AbortSignal | undefined,
518
+ onUpdate: ((result: AgentToolResult<UpdateFileInfoDetails>) => void) | undefined,
519
+ ctx: ExtensionContext
520
+ ): Promise<AgentToolResult<UpdateFileInfoDetails>> {
521
+ try {
522
+ const { fileId, userId, title, description, isPublic } = params;
523
+ const doc = getDocument(fileId);
524
+
525
+ if (!doc) {
526
+ const errorResult: AgentToolResult<UpdateFileInfoDetails> = {
527
+ content: [{ type: 'text', text: '❌ 文件未找到' }],
528
+ details: { success: false, error: 'File not found' },
529
+ };
530
+ onUpdate?.(errorResult);
531
+ return errorResult;
532
+ }
533
+
534
+ if (doc.author !== userId) {
535
+ const errorResult: AgentToolResult<UpdateFileInfoDetails> = {
536
+ content: [{ type: 'text', text: '❌ 无权修改此文件' }],
537
+ details: { success: false, error: 'Permission denied' },
538
+ };
539
+ onUpdate?.(errorResult);
540
+ return errorResult;
541
+ }
542
+
543
+ const updates: any = {};
544
+ if (title !== undefined) updates.title = title;
545
+ if (description !== undefined) updates.content = description;
546
+ if (isPublic !== undefined) updates.isPublic = isPublic;
547
+
548
+ const updated = updateDocument(fileId, updates);
549
+
550
+ const result: AgentToolResult<UpdateFileInfoDetails> = {
551
+ content: [{ type: 'text', text: `✅ 文件信息已更新` }],
552
+ details: {
553
+ success: true,
554
+ file: {
555
+ id: updated?.id,
556
+ title: updated?.title,
557
+ description: updated?.content,
558
+ isPublic: updated?.isPublic,
559
+ },
560
+ },
561
+ };
562
+
563
+ onUpdate?.(result);
564
+ return result;
565
+ } catch (error: any) {
566
+ const errorResult: AgentToolResult<UpdateFileInfoDetails> = {
567
+ content: [{ type: 'text', text: `❌ 更新文件信息失败: ${error.message}` }],
568
+ details: { success: false, error: error.message },
569
+ };
570
+ onUpdate?.(errorResult);
571
+ return errorResult;
572
+ }
573
+ },
574
+ };
575
+
576
+ export const fileTools = [
577
+ listFilesTool,
578
+ getFileInfoTool,
579
+ downloadFileTool,
580
+ uploadFileTool,
581
+ deleteFileTool,
582
+ updateFileInfoTool,
583
+ ];