wave-agent-sdk 0.0.1

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 (170) hide show
  1. package/README.md +32 -0
  2. package/dist/agent.d.ts +96 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +286 -0
  5. package/dist/hooks/executor.d.ts +56 -0
  6. package/dist/hooks/executor.d.ts.map +1 -0
  7. package/dist/hooks/executor.js +312 -0
  8. package/dist/hooks/index.d.ts +17 -0
  9. package/dist/hooks/index.d.ts.map +1 -0
  10. package/dist/hooks/index.js +14 -0
  11. package/dist/hooks/manager.d.ts +90 -0
  12. package/dist/hooks/manager.d.ts.map +1 -0
  13. package/dist/hooks/manager.js +395 -0
  14. package/dist/hooks/matcher.d.ts +49 -0
  15. package/dist/hooks/matcher.d.ts.map +1 -0
  16. package/dist/hooks/matcher.js +147 -0
  17. package/dist/hooks/settings.d.ts +46 -0
  18. package/dist/hooks/settings.d.ts.map +1 -0
  19. package/dist/hooks/settings.js +100 -0
  20. package/dist/hooks/types.d.ts +80 -0
  21. package/dist/hooks/types.d.ts.map +1 -0
  22. package/dist/hooks/types.js +59 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/managers/aiManager.d.ts +61 -0
  27. package/dist/managers/aiManager.d.ts.map +1 -0
  28. package/dist/managers/aiManager.js +415 -0
  29. package/dist/managers/backgroundBashManager.d.ts +27 -0
  30. package/dist/managers/backgroundBashManager.d.ts.map +1 -0
  31. package/dist/managers/backgroundBashManager.js +166 -0
  32. package/dist/managers/bashManager.d.ts +20 -0
  33. package/dist/managers/bashManager.d.ts.map +1 -0
  34. package/dist/managers/bashManager.js +66 -0
  35. package/dist/managers/mcpManager.d.ts +63 -0
  36. package/dist/managers/mcpManager.d.ts.map +1 -0
  37. package/dist/managers/mcpManager.js +378 -0
  38. package/dist/managers/messageManager.d.ts +85 -0
  39. package/dist/managers/messageManager.d.ts.map +1 -0
  40. package/dist/managers/messageManager.js +265 -0
  41. package/dist/managers/skillManager.d.ts +59 -0
  42. package/dist/managers/skillManager.d.ts.map +1 -0
  43. package/dist/managers/skillManager.js +317 -0
  44. package/dist/managers/slashCommandManager.d.ts +77 -0
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -0
  46. package/dist/managers/slashCommandManager.js +208 -0
  47. package/dist/managers/toolManager.d.ts +23 -0
  48. package/dist/managers/toolManager.d.ts.map +1 -0
  49. package/dist/managers/toolManager.js +79 -0
  50. package/dist/services/aiService.d.ts +28 -0
  51. package/dist/services/aiService.d.ts.map +1 -0
  52. package/dist/services/aiService.js +180 -0
  53. package/dist/services/memory.d.ts +8 -0
  54. package/dist/services/memory.d.ts.map +1 -0
  55. package/dist/services/memory.js +128 -0
  56. package/dist/services/session.d.ts +54 -0
  57. package/dist/services/session.d.ts.map +1 -0
  58. package/dist/services/session.js +196 -0
  59. package/dist/tools/bashTool.d.ts +14 -0
  60. package/dist/tools/bashTool.d.ts.map +1 -0
  61. package/dist/tools/bashTool.js +351 -0
  62. package/dist/tools/deleteFileTool.d.ts +6 -0
  63. package/dist/tools/deleteFileTool.d.ts.map +1 -0
  64. package/dist/tools/deleteFileTool.js +67 -0
  65. package/dist/tools/editTool.d.ts +6 -0
  66. package/dist/tools/editTool.d.ts.map +1 -0
  67. package/dist/tools/editTool.js +168 -0
  68. package/dist/tools/globTool.d.ts +6 -0
  69. package/dist/tools/globTool.d.ts.map +1 -0
  70. package/dist/tools/globTool.js +113 -0
  71. package/dist/tools/grepTool.d.ts +6 -0
  72. package/dist/tools/grepTool.d.ts.map +1 -0
  73. package/dist/tools/grepTool.js +268 -0
  74. package/dist/tools/lsTool.d.ts +6 -0
  75. package/dist/tools/lsTool.d.ts.map +1 -0
  76. package/dist/tools/lsTool.js +160 -0
  77. package/dist/tools/multiEditTool.d.ts +6 -0
  78. package/dist/tools/multiEditTool.d.ts.map +1 -0
  79. package/dist/tools/multiEditTool.js +222 -0
  80. package/dist/tools/readTool.d.ts +6 -0
  81. package/dist/tools/readTool.d.ts.map +1 -0
  82. package/dist/tools/readTool.js +136 -0
  83. package/dist/tools/types.d.ts +35 -0
  84. package/dist/tools/types.d.ts.map +1 -0
  85. package/dist/tools/types.js +4 -0
  86. package/dist/tools/writeTool.d.ts +6 -0
  87. package/dist/tools/writeTool.d.ts.map +1 -0
  88. package/dist/tools/writeTool.js +138 -0
  89. package/dist/types.d.ts +212 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/dist/utils/bashHistory.d.ts +46 -0
  93. package/dist/utils/bashHistory.d.ts.map +1 -0
  94. package/dist/utils/bashHistory.js +236 -0
  95. package/dist/utils/commandArgumentParser.d.ts +34 -0
  96. package/dist/utils/commandArgumentParser.d.ts.map +1 -0
  97. package/dist/utils/commandArgumentParser.js +123 -0
  98. package/dist/utils/constants.d.ts +27 -0
  99. package/dist/utils/constants.d.ts.map +1 -0
  100. package/dist/utils/constants.js +28 -0
  101. package/dist/utils/convertMessagesForAPI.d.ts +9 -0
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
  103. package/dist/utils/convertMessagesForAPI.js +189 -0
  104. package/dist/utils/customCommands.d.ts +14 -0
  105. package/dist/utils/customCommands.d.ts.map +1 -0
  106. package/dist/utils/customCommands.js +71 -0
  107. package/dist/utils/fileFilter.d.ts +26 -0
  108. package/dist/utils/fileFilter.d.ts.map +1 -0
  109. package/dist/utils/fileFilter.js +177 -0
  110. package/dist/utils/markdownParser.d.ts +27 -0
  111. package/dist/utils/markdownParser.d.ts.map +1 -0
  112. package/dist/utils/markdownParser.js +109 -0
  113. package/dist/utils/mcpUtils.d.ts +24 -0
  114. package/dist/utils/mcpUtils.d.ts.map +1 -0
  115. package/dist/utils/mcpUtils.js +51 -0
  116. package/dist/utils/messageOperations.d.ts +118 -0
  117. package/dist/utils/messageOperations.d.ts.map +1 -0
  118. package/dist/utils/messageOperations.js +334 -0
  119. package/dist/utils/path.d.ts +25 -0
  120. package/dist/utils/path.d.ts.map +1 -0
  121. package/dist/utils/path.js +109 -0
  122. package/dist/utils/skillParser.d.ts +18 -0
  123. package/dist/utils/skillParser.d.ts.map +1 -0
  124. package/dist/utils/skillParser.js +147 -0
  125. package/dist/utils/stringUtils.d.ts +13 -0
  126. package/dist/utils/stringUtils.d.ts.map +1 -0
  127. package/dist/utils/stringUtils.js +44 -0
  128. package/package.json +51 -0
  129. package/src/agent.ts +405 -0
  130. package/src/hooks/executor.ts +440 -0
  131. package/src/hooks/index.ts +52 -0
  132. package/src/hooks/manager.ts +618 -0
  133. package/src/hooks/matcher.ts +187 -0
  134. package/src/hooks/settings.ts +129 -0
  135. package/src/hooks/types.ts +169 -0
  136. package/src/index.ts +24 -0
  137. package/src/managers/aiManager.ts +573 -0
  138. package/src/managers/backgroundBashManager.ts +203 -0
  139. package/src/managers/bashManager.ts +97 -0
  140. package/src/managers/mcpManager.ts +493 -0
  141. package/src/managers/messageManager.ts +415 -0
  142. package/src/managers/skillManager.ts +404 -0
  143. package/src/managers/slashCommandManager.ts +293 -0
  144. package/src/managers/toolManager.ts +106 -0
  145. package/src/services/aiService.ts +252 -0
  146. package/src/services/memory.ts +149 -0
  147. package/src/services/session.ts +265 -0
  148. package/src/tools/bashTool.ts +402 -0
  149. package/src/tools/deleteFileTool.ts +81 -0
  150. package/src/tools/editTool.ts +192 -0
  151. package/src/tools/globTool.ts +135 -0
  152. package/src/tools/grepTool.ts +326 -0
  153. package/src/tools/lsTool.ts +187 -0
  154. package/src/tools/multiEditTool.ts +268 -0
  155. package/src/tools/readTool.ts +165 -0
  156. package/src/tools/types.ts +47 -0
  157. package/src/tools/writeTool.ts +163 -0
  158. package/src/types.ts +260 -0
  159. package/src/utils/bashHistory.ts +303 -0
  160. package/src/utils/commandArgumentParser.ts +153 -0
  161. package/src/utils/constants.ts +37 -0
  162. package/src/utils/convertMessagesForAPI.ts +236 -0
  163. package/src/utils/customCommands.ts +85 -0
  164. package/src/utils/fileFilter.ts +202 -0
  165. package/src/utils/markdownParser.ts +156 -0
  166. package/src/utils/mcpUtils.ts +81 -0
  167. package/src/utils/messageOperations.ts +506 -0
  168. package/src/utils/path.ts +118 -0
  169. package/src/utils/skillParser.ts +188 -0
  170. package/src/utils/stringUtils.ts +50 -0
@@ -0,0 +1,506 @@
1
+ import type { Message } from "../types.js";
2
+ import { readFileSync } from "fs";
3
+ import { extname } from "path";
4
+ import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
5
+
6
+ // Parameter interfaces for message operations
7
+ export interface AddUserMessageParams {
8
+ messages: Message[];
9
+ content: string;
10
+ images?: Array<{ path: string; mimeType: string }>;
11
+ customCommandBlock?: {
12
+ type: "custom_command";
13
+ commandName: string;
14
+ content: string;
15
+ originalInput?: string;
16
+ };
17
+ }
18
+
19
+ export interface UpdateToolBlockParams {
20
+ messages: Message[];
21
+ id: string;
22
+ parameters: string;
23
+ result?: string;
24
+ success?: boolean;
25
+ error?: string;
26
+ isRunning?: boolean;
27
+ name?: string;
28
+ shortResult?: string;
29
+ images?: Array<{ data: string; mediaType?: string }>;
30
+ compactParams?: string;
31
+ }
32
+
33
+ // Agent specific interfaces (without messages parameter)
34
+ export interface AgentToolBlockUpdateParams {
35
+ toolId: string;
36
+ args?: string;
37
+ result?: string;
38
+ success?: boolean;
39
+ error?: string;
40
+ isRunning?: boolean;
41
+ name?: string;
42
+ shortResult?: string;
43
+ compactParams?: string;
44
+ }
45
+
46
+ export interface AddDiffBlockParams {
47
+ messages: Message[];
48
+ path: string;
49
+ diffResult: Array<{ value: string; added?: boolean; removed?: boolean }>;
50
+ }
51
+
52
+ export interface AddErrorBlockParams {
53
+ messages: Message[];
54
+ error: string;
55
+ }
56
+
57
+ export interface AddMemoryBlockParams {
58
+ messages: Message[];
59
+ content: string;
60
+ isSuccess: boolean;
61
+ memoryType?: "project" | "user";
62
+ storagePath?: string;
63
+ }
64
+
65
+ export interface AddCommandOutputParams {
66
+ messages: Message[];
67
+ command: string;
68
+ }
69
+
70
+ export interface UpdateCommandOutputParams {
71
+ messages: Message[];
72
+ command: string;
73
+ output: string;
74
+ }
75
+
76
+ export interface CompleteCommandParams {
77
+ messages: Message[];
78
+ command: string;
79
+ exitCode: number;
80
+ }
81
+
82
+ /**
83
+ * Extract text content from user messages in the messages array
84
+ */
85
+ export const extractUserInputHistory = (messages: Message[]): string[] => {
86
+ return messages
87
+ .filter((message) => message.role === "user")
88
+ .map((message) => {
89
+ // Extract all text block content and merge
90
+ const textBlocks = message.blocks.filter(
91
+ (block) => block.type === "text",
92
+ );
93
+ return textBlocks
94
+ .map((block) => block.content)
95
+ .join(" ")
96
+ .trim();
97
+ })
98
+ .filter((text) => text.length > 0); // Filter out empty text
99
+ };
100
+
101
+ /**
102
+ * Convert image file path to base64 format
103
+ * @param imagePath Image file path
104
+ * @returns base64 format image data URL
105
+ */
106
+ export const convertImageToBase64 = (imagePath: string): string => {
107
+ try {
108
+ const imageBuffer = readFileSync(imagePath);
109
+ const ext = extname(imagePath).toLowerCase().substring(1);
110
+
111
+ // Determine MIME type based on file extension
112
+ let mimeType = "image/png"; // Default
113
+ switch (ext) {
114
+ case "jpg":
115
+ case "jpeg":
116
+ mimeType = "image/jpeg";
117
+ break;
118
+ case "png":
119
+ mimeType = "image/png";
120
+ break;
121
+ case "gif":
122
+ mimeType = "image/gif";
123
+ break;
124
+ case "webp":
125
+ mimeType = "image/webp";
126
+ break;
127
+ case "bmp":
128
+ mimeType = "image/bmp";
129
+ break;
130
+ default:
131
+ mimeType = "image/png";
132
+ }
133
+
134
+ const base64String = imageBuffer.toString("base64");
135
+ return `data:${mimeType};base64,${base64String}`;
136
+ } catch {
137
+ // logger.error(`Failed to convert image to base64: ${imagePath}`, error);
138
+ // Return an error placeholder or throw error
139
+ return `data:image/png;base64,`; // Empty base64, avoid program crash
140
+ }
141
+ };
142
+
143
+ // Add user message
144
+ export const addUserMessageToMessages = ({
145
+ messages,
146
+ content,
147
+ images,
148
+ customCommandBlock,
149
+ }: AddUserMessageParams): Message[] => {
150
+ const blocks: Message["blocks"] = [];
151
+
152
+ // If there's a custom command block, use it instead of text content
153
+ if (customCommandBlock) {
154
+ blocks.push(customCommandBlock);
155
+ } else {
156
+ blocks.push({ type: "text", content });
157
+ }
158
+
159
+ // If there are images, add image block
160
+ if (images && images.length > 0) {
161
+ const imageUrls = images.map((img) => img.path);
162
+ blocks.push({
163
+ type: "image",
164
+ imageUrls,
165
+ });
166
+ }
167
+
168
+ const userMessage: Message = {
169
+ role: "user",
170
+ blocks,
171
+ };
172
+ return [...messages, userMessage];
173
+ };
174
+
175
+ // Add assistant message (support one-time addition of answer and tool calls)
176
+ export const addAssistantMessageToMessages = (
177
+ messages: Message[],
178
+ content?: string,
179
+ toolCalls?: ChatCompletionMessageFunctionToolCall[],
180
+ ): Message[] => {
181
+ const blocks: Message["blocks"] = [];
182
+
183
+ // If there's answer content, add text block
184
+ if (content) {
185
+ blocks.push({ type: "text", content: content });
186
+ }
187
+
188
+ // If there are tool calls, add tool blocks
189
+ if (toolCalls && toolCalls.length > 0) {
190
+ toolCalls.forEach((toolCall) => {
191
+ blocks.push({
192
+ type: "tool",
193
+ parameters: toolCall.function.arguments || "",
194
+ result: "",
195
+ id: toolCall.id || "",
196
+ name: toolCall.function?.name || "",
197
+ isRunning: false,
198
+ });
199
+ });
200
+ }
201
+
202
+ const initialAssistantMessage: Message = {
203
+ role: "assistant",
204
+ blocks,
205
+ };
206
+
207
+ return [...messages, initialAssistantMessage];
208
+ };
209
+
210
+ // Update File Operation Block of the last assistant message
211
+ export const addDiffBlockToMessage = ({
212
+ messages,
213
+ path,
214
+ diffResult,
215
+ }: AddDiffBlockParams): Message[] => {
216
+ const newMessages = [...messages];
217
+ // Find the last assistant message
218
+ for (let i = newMessages.length - 1; i >= 0; i--) {
219
+ if (newMessages[i].role === "assistant") {
220
+ // Directly add diff block instead of replacing existing blocks
221
+ newMessages[i].blocks.push({
222
+ type: "diff",
223
+ path: path,
224
+ diffResult: diffResult,
225
+ });
226
+ break;
227
+ }
228
+ }
229
+ return newMessages;
230
+ };
231
+
232
+ // Update Tool Block of the last assistant message
233
+ export const updateToolBlockInMessage = ({
234
+ messages,
235
+ id,
236
+ parameters,
237
+ result,
238
+ success,
239
+ error,
240
+ isRunning,
241
+ name,
242
+ shortResult,
243
+ images,
244
+ compactParams,
245
+ }: UpdateToolBlockParams): Message[] => {
246
+ const newMessages = [...messages];
247
+ // Find the last assistant message
248
+ for (let i = newMessages.length - 1; i >= 0; i--) {
249
+ if (newMessages[i].role === "assistant") {
250
+ const toolBlockIndex = newMessages[i].blocks.findIndex(
251
+ (block) => block.type === "tool" && block.id === id,
252
+ );
253
+
254
+ if (toolBlockIndex !== -1) {
255
+ const toolBlock = newMessages[i].blocks[toolBlockIndex];
256
+ if (toolBlock.type === "tool") {
257
+ toolBlock.parameters = parameters;
258
+ if (result !== undefined) toolBlock.result = result;
259
+ if (shortResult !== undefined) toolBlock.shortResult = shortResult;
260
+ toolBlock.images = images; // Add image data update
261
+ if (success !== undefined) toolBlock.success = success;
262
+ if (error !== undefined) toolBlock.error = error;
263
+ if (isRunning !== undefined) toolBlock.isRunning = isRunning;
264
+ if (compactParams !== undefined)
265
+ toolBlock.compactParams = compactParams;
266
+ }
267
+ } else if (result !== undefined) {
268
+ // If existing block not found, create new one
269
+ newMessages[i].blocks.push({
270
+ type: "tool",
271
+ parameters: parameters,
272
+ result: result,
273
+ shortResult: shortResult,
274
+ images: images, // Add image data
275
+ id: id,
276
+ name: name || "unknown",
277
+ success: success,
278
+ error: error,
279
+ isRunning: isRunning ?? false,
280
+ compactParams: compactParams,
281
+ });
282
+ }
283
+ break;
284
+ }
285
+ }
286
+ return newMessages;
287
+ };
288
+
289
+ // Add Error Block to the last assistant message
290
+ export const addErrorBlockToMessage = ({
291
+ messages,
292
+ error,
293
+ }: AddErrorBlockParams): Message[] => {
294
+ const newMessages = [...messages];
295
+ // Find the last assistant message
296
+ let assistantMessageFound = false;
297
+ for (let i = newMessages.length - 1; i >= 0; i--) {
298
+ if (newMessages[i].role === "assistant") {
299
+ newMessages[i].blocks = [
300
+ ...newMessages[i].blocks,
301
+ {
302
+ type: "error",
303
+ content: error,
304
+ },
305
+ ];
306
+ assistantMessageFound = true;
307
+ break;
308
+ }
309
+ }
310
+
311
+ // If no assistant message found, create a new assistant message with only error block
312
+ if (!assistantMessageFound) {
313
+ newMessages.push({
314
+ role: "assistant",
315
+ blocks: [
316
+ {
317
+ type: "error",
318
+ content: error,
319
+ },
320
+ ],
321
+ });
322
+ }
323
+
324
+ return newMessages;
325
+ };
326
+
327
+ // Add Memory Block as new assistant message
328
+ export const addMemoryBlockToMessage = ({
329
+ messages,
330
+ content,
331
+ isSuccess,
332
+ memoryType,
333
+ storagePath,
334
+ }: AddMemoryBlockParams): Message[] => {
335
+ const newMessages = [...messages];
336
+
337
+ // Create new assistant message containing MemoryBlock
338
+ const memoryMessage: Message = {
339
+ role: "assistant",
340
+ blocks: [
341
+ {
342
+ type: "memory",
343
+ content,
344
+ isSuccess,
345
+ memoryType,
346
+ storagePath,
347
+ },
348
+ ],
349
+ };
350
+
351
+ // Add to end of message list
352
+ newMessages.push(memoryMessage);
353
+ return newMessages;
354
+ };
355
+
356
+ /**
357
+ * Count valid blocks from the end
358
+ * Only text, image, and tool type blocks are counted
359
+ * @param messages Message array
360
+ * @param targetCount Number of valid blocks to count
361
+ * @returns { messageIndex: number, blockCount: number } Message index and actual counted block count
362
+ */
363
+ export const countValidBlocksFromEnd = (
364
+ messages: Message[],
365
+ targetCount: number,
366
+ ): { messageIndex: number; blockCount: number } => {
367
+ let validBlockCount = 0;
368
+
369
+ // Iterate messages from end to beginning
370
+ for (let i = messages.length - 1; i >= 0; i--) {
371
+ const message = messages[i];
372
+
373
+ // Iterate through all blocks of current message
374
+ for (const block of message.blocks) {
375
+ // Only count valid block types
376
+ if (
377
+ block.type === "text" ||
378
+ block.type === "image" ||
379
+ block.type === "tool"
380
+ ) {
381
+ validBlockCount++;
382
+
383
+ // If target count reached, return current message index
384
+ if (validBlockCount >= targetCount) {
385
+ return { messageIndex: i, blockCount: validBlockCount };
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ // If target count not reached, return index 0
392
+ return { messageIndex: 0, blockCount: validBlockCount };
393
+ };
394
+
395
+ /**
396
+ * Get messages to be compressed and insertion position
397
+ * @param messages Message array
398
+ * @param keepLastCount Keep the last few valid blocks uncompressed
399
+ * @returns { messagesToCompress: Message[], insertIndex: number }
400
+ */
401
+ export const getMessagesToCompress = (
402
+ messages: Message[],
403
+ keepLastCount: number = 7,
404
+ ): { messagesToCompress: Message[]; insertIndex: number } => {
405
+ // Calculate message position to keep from end to beginning
406
+ const { messageIndex } = countValidBlocksFromEnd(messages, keepLastCount);
407
+
408
+ // Find the last message containing compression block
409
+ let lastCompressIndex = -1;
410
+ for (let i = messages.length - 1; i >= 0; i--) {
411
+ const hasCompressBlock = messages[i].blocks.some(
412
+ (block) => block.type === "compress",
413
+ );
414
+ if (hasCompressBlock) {
415
+ lastCompressIndex = i;
416
+ break;
417
+ }
418
+ }
419
+
420
+ // Determine compression start position
421
+ // If compression block exists, start from compression block position (include compression block)
422
+ // If no compression block, start from beginning
423
+ const startIndex = lastCompressIndex >= 0 ? lastCompressIndex : 0;
424
+
425
+ // Messages to compress are all messages from start position to before calculated position
426
+ const messagesToCompress = messages.slice(startIndex, messageIndex);
427
+
428
+ // Change insertion position to negative number, indicating position from end
429
+ const insertIndex = messageIndex - messages.length;
430
+
431
+ return { messagesToCompress, insertIndex };
432
+ };
433
+
434
+ // Add command output block to message list
435
+ export const addCommandOutputMessage = ({
436
+ messages,
437
+ command,
438
+ }: AddCommandOutputParams): Message[] => {
439
+ const outputMessage: Message = {
440
+ role: "assistant",
441
+ blocks: [
442
+ {
443
+ type: "command_output",
444
+ command,
445
+ output: "",
446
+ isRunning: true,
447
+ exitCode: null,
448
+ },
449
+ ],
450
+ };
451
+
452
+ return [...messages, outputMessage];
453
+ };
454
+
455
+ // Update output content of command output block
456
+ export const updateCommandOutputInMessage = ({
457
+ messages,
458
+ command,
459
+ output,
460
+ }: UpdateCommandOutputParams): Message[] => {
461
+ const newMessages = [...messages];
462
+ // Find the last assistant message with a command_output block for this command
463
+ for (let i = newMessages.length - 1; i >= 0; i--) {
464
+ const msg = newMessages[i];
465
+ if (msg.role === "assistant") {
466
+ const commandBlock = msg.blocks.find(
467
+ (block) =>
468
+ block.type === "command_output" &&
469
+ block.command === command &&
470
+ block.isRunning,
471
+ );
472
+ if (commandBlock && commandBlock.type === "command_output") {
473
+ commandBlock.output = output.trim();
474
+ break;
475
+ }
476
+ }
477
+ }
478
+ return newMessages;
479
+ };
480
+
481
+ // Complete command execution, update exit status
482
+ export const completeCommandInMessage = ({
483
+ messages,
484
+ command,
485
+ exitCode,
486
+ }: CompleteCommandParams): Message[] => {
487
+ const newMessages = [...messages];
488
+ // Find the last assistant message with a command_output block for this command
489
+ for (let i = newMessages.length - 1; i >= 0; i--) {
490
+ const msg = newMessages[i];
491
+ if (msg.role === "assistant") {
492
+ const commandBlock = msg.blocks.find(
493
+ (block) =>
494
+ block.type === "command_output" &&
495
+ block.command === command &&
496
+ block.isRunning,
497
+ );
498
+ if (commandBlock && commandBlock.type === "command_output") {
499
+ commandBlock.isRunning = false;
500
+ commandBlock.exitCode = exitCode;
501
+ break;
502
+ }
503
+ }
504
+ }
505
+ return newMessages;
506
+ };
@@ -0,0 +1,118 @@
1
+ import { resolve } from "path";
2
+ import { homedir } from "os";
3
+ import { relative } from "path";
4
+
5
+ /**
6
+ * Handle paths, supporting ~ expansion
7
+ * @param filePath File path
8
+ * @param workdir Working directory
9
+ * @returns Resolved absolute path
10
+ */
11
+ export function resolvePath(filePath: string, workdir: string): string {
12
+ // If the path starts with ~, replace it with the user's home directory
13
+ if (filePath.startsWith("~/")) {
14
+ return resolve(homedir(), filePath.slice(2));
15
+ }
16
+
17
+ // If the path starts with ~ but has no slash, it means it's the home directory
18
+ if (filePath === "~") {
19
+ return homedir();
20
+ }
21
+
22
+ // For other paths, resolve using the specified working directory
23
+ return resolve(workdir, filePath);
24
+ }
25
+
26
+ /**
27
+ * Binary file extension list
28
+ */
29
+ export const binaryExtensions = [
30
+ // Image files
31
+ "png",
32
+ "jpg",
33
+ "jpeg",
34
+ "gif",
35
+ "bmp",
36
+ "ico",
37
+ "webp",
38
+ "svg",
39
+ "sketch",
40
+ // Audio files
41
+ "mp3",
42
+ "wav",
43
+ "ogg",
44
+ "aac",
45
+ // Video files
46
+ "mp4",
47
+ "webm",
48
+ "avi",
49
+ "mov",
50
+ // Document files
51
+ "pdf",
52
+ "doc",
53
+ "docx",
54
+ "xls",
55
+ "xlsx",
56
+ "ppt",
57
+ "pptx",
58
+ // Compressed files
59
+ "zip",
60
+ "rar",
61
+ "7z",
62
+ "tar",
63
+ "gz",
64
+ // Font files
65
+ "ttf",
66
+ "otf",
67
+ "woff",
68
+ "woff2",
69
+ "eot",
70
+ // Other binary files
71
+ "exe",
72
+ "dll",
73
+ "so",
74
+ "dylib",
75
+ "bin",
76
+ ] as const;
77
+ /**
78
+ * Check if a file is a binary file
79
+ * @param filename File name
80
+ * @returns Whether it is a binary file
81
+ */
82
+ export const isBinary = (filename: string): boolean => {
83
+ const parts = filename.split(".");
84
+ const ext = parts.length > 1 ? parts.pop()?.toLowerCase() || "" : "";
85
+ return binaryExtensions.includes(ext as (typeof binaryExtensions)[number]);
86
+ };
87
+
88
+ /**
89
+ * Get relative path for display, use relative path if shorter and not in parent directory
90
+ * @param filePath Absolute path
91
+ * @param workdir Working directory
92
+ * @returns Path for display (relative or absolute)
93
+ */
94
+ export function getDisplayPath(filePath: string, workdir: string): string {
95
+ if (!filePath) {
96
+ return filePath;
97
+ }
98
+
99
+ try {
100
+ const relativePath = relative(workdir, filePath);
101
+
102
+ // If the relative path is empty (i.e., the paths are the same), return "."
103
+ if (relativePath === "") {
104
+ return ".";
105
+ }
106
+
107
+ // If the relative path is shorter than the absolute path and does not start with .. (not in parent directory), use the relative path
108
+ if (
109
+ relativePath.length < filePath.length &&
110
+ !relativePath.startsWith("..")
111
+ ) {
112
+ return relativePath;
113
+ }
114
+ } catch {
115
+ // If calculating the relative path fails, keep the original path
116
+ }
117
+ return filePath;
118
+ }