wave-agent-sdk 0.0.7 → 0.0.10

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 (240) hide show
  1. package/dist/agent.d.ts +105 -24
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +438 -53
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -0
  7. package/dist/managers/aiManager.d.ts +18 -7
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +254 -142
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +11 -9
  12. package/dist/managers/hookManager.d.ts +6 -6
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +81 -39
  15. package/dist/managers/liveConfigManager.d.ts +95 -0
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  17. package/dist/managers/liveConfigManager.js +442 -0
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +184 -73
  24. package/dist/managers/permissionManager.d.ts +66 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +208 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +4 -2
  32. package/dist/managers/subagentManager.d.ts +42 -6
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +213 -62
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +15 -5
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +446 -77
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts +69 -0
  45. package/dist/services/fileWatcher.d.ts.map +1 -0
  46. package/dist/services/fileWatcher.js +212 -0
  47. package/dist/services/hook.d.ts +5 -40
  48. package/dist/services/hook.d.ts.map +1 -1
  49. package/dist/services/hook.js +47 -109
  50. package/dist/services/jsonlHandler.d.ts +71 -0
  51. package/dist/services/jsonlHandler.d.ts.map +1 -0
  52. package/dist/services/jsonlHandler.js +236 -0
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +33 -11
  55. package/dist/services/session.d.ts +116 -52
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +415 -143
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +77 -17
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +27 -1
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +33 -8
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +30 -10
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +113 -3
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +30 -15
  78. package/dist/types/commands.d.ts +4 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +45 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +83 -0
  88. package/dist/types/environment.d.ts.map +1 -0
  89. package/dist/types/environment.js +21 -0
  90. package/dist/types/fileSearch.d.ts +5 -0
  91. package/dist/types/fileSearch.d.ts.map +1 -0
  92. package/dist/types/fileSearch.js +1 -0
  93. package/dist/types/hooks.d.ts +18 -3
  94. package/dist/types/hooks.d.ts.map +1 -1
  95. package/dist/types/hooks.js +8 -8
  96. package/dist/types/index.d.ts +7 -0
  97. package/dist/types/index.d.ts.map +1 -1
  98. package/dist/types/index.js +7 -0
  99. package/dist/types/lsp.d.ts +90 -0
  100. package/dist/types/lsp.d.ts.map +1 -0
  101. package/dist/types/lsp.js +4 -0
  102. package/dist/types/messaging.d.ts +19 -12
  103. package/dist/types/messaging.d.ts.map +1 -1
  104. package/dist/types/permissions.d.ts +35 -0
  105. package/dist/types/permissions.d.ts.map +1 -0
  106. package/dist/types/permissions.js +12 -0
  107. package/dist/types/session.d.ts +15 -0
  108. package/dist/types/session.d.ts.map +1 -0
  109. package/dist/types/session.js +7 -0
  110. package/dist/types/skills.d.ts +1 -0
  111. package/dist/types/skills.d.ts.map +1 -1
  112. package/dist/types/tools.d.ts +35 -0
  113. package/dist/types/tools.d.ts.map +1 -0
  114. package/dist/types/tools.js +4 -0
  115. package/dist/utils/abortUtils.d.ts +34 -0
  116. package/dist/utils/abortUtils.d.ts.map +1 -0
  117. package/dist/utils/abortUtils.js +92 -0
  118. package/dist/utils/bashHistory.d.ts +4 -0
  119. package/dist/utils/bashHistory.d.ts.map +1 -1
  120. package/dist/utils/bashHistory.js +48 -30
  121. package/dist/utils/builtinSubagents.d.ts +7 -0
  122. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  123. package/dist/utils/builtinSubagents.js +65 -0
  124. package/dist/utils/cacheControlUtils.d.ts +96 -0
  125. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  126. package/dist/utils/cacheControlUtils.js +324 -0
  127. package/dist/utils/commandPathResolver.d.ts +52 -0
  128. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  129. package/dist/utils/commandPathResolver.js +145 -0
  130. package/dist/utils/configPaths.d.ts +85 -0
  131. package/dist/utils/configPaths.d.ts.map +1 -0
  132. package/dist/utils/configPaths.js +121 -0
  133. package/dist/utils/constants.d.ts +1 -13
  134. package/dist/utils/constants.d.ts.map +1 -1
  135. package/dist/utils/constants.js +2 -14
  136. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  137. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  138. package/dist/utils/convertMessagesForAPI.js +39 -18
  139. package/dist/utils/customCommands.d.ts.map +1 -1
  140. package/dist/utils/customCommands.js +66 -21
  141. package/dist/utils/fileSearch.d.ts +14 -0
  142. package/dist/utils/fileSearch.d.ts.map +1 -0
  143. package/dist/utils/fileSearch.js +88 -0
  144. package/dist/utils/fileUtils.d.ts +27 -0
  145. package/dist/utils/fileUtils.d.ts.map +1 -0
  146. package/dist/utils/fileUtils.js +145 -0
  147. package/dist/utils/globalLogger.d.ts +88 -0
  148. package/dist/utils/globalLogger.d.ts.map +1 -0
  149. package/dist/utils/globalLogger.js +120 -0
  150. package/dist/utils/largeOutputHandler.d.ts +15 -0
  151. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  152. package/dist/utils/largeOutputHandler.js +40 -0
  153. package/dist/utils/markdownParser.d.ts.map +1 -1
  154. package/dist/utils/markdownParser.js +1 -17
  155. package/dist/utils/mcpUtils.d.ts.map +1 -1
  156. package/dist/utils/mcpUtils.js +25 -3
  157. package/dist/utils/messageOperations.d.ts +20 -18
  158. package/dist/utils/messageOperations.d.ts.map +1 -1
  159. package/dist/utils/messageOperations.js +30 -38
  160. package/dist/utils/pathEncoder.d.ts +108 -0
  161. package/dist/utils/pathEncoder.d.ts.map +1 -0
  162. package/dist/utils/pathEncoder.js +279 -0
  163. package/dist/utils/subagentParser.d.ts +2 -2
  164. package/dist/utils/subagentParser.d.ts.map +1 -1
  165. package/dist/utils/subagentParser.js +12 -8
  166. package/dist/utils/tokenCalculation.d.ts +26 -0
  167. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  168. package/dist/utils/tokenCalculation.js +36 -0
  169. package/dist/utils/tokenEstimator.d.ts +39 -0
  170. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  171. package/dist/utils/tokenEstimator.js +55 -0
  172. package/package.json +6 -6
  173. package/src/agent.ts +586 -78
  174. package/src/index.ts +4 -0
  175. package/src/managers/aiManager.ts +341 -192
  176. package/src/managers/backgroundBashManager.ts +11 -9
  177. package/src/managers/hookManager.ts +102 -54
  178. package/src/managers/liveConfigManager.ts +634 -0
  179. package/src/managers/lspManager.ts +434 -0
  180. package/src/managers/messageManager.ts +258 -121
  181. package/src/managers/permissionManager.ts +276 -0
  182. package/src/managers/skillManager.ts +3 -1
  183. package/src/managers/slashCommandManager.ts +5 -3
  184. package/src/managers/subagentManager.ts +295 -76
  185. package/src/managers/toolManager.ts +95 -3
  186. package/src/services/aiService.ts +656 -84
  187. package/src/services/configurationService.ts +762 -0
  188. package/src/services/fileWatcher.ts +300 -0
  189. package/src/services/hook.ts +54 -144
  190. package/src/services/jsonlHandler.ts +303 -0
  191. package/src/services/memory.ts +34 -11
  192. package/src/services/session.ts +522 -173
  193. package/src/tools/bashTool.ts +94 -20
  194. package/src/tools/deleteFileTool.ts +38 -1
  195. package/src/tools/editTool.ts +44 -9
  196. package/src/tools/lspTool.ts +760 -0
  197. package/src/tools/multiEditTool.ts +41 -11
  198. package/src/tools/readTool.ts +127 -3
  199. package/src/tools/skillTool.ts +2 -2
  200. package/src/tools/todoWriteTool.ts +33 -1
  201. package/src/tools/types.ts +15 -9
  202. package/src/tools/writeTool.ts +43 -16
  203. package/src/types/commands.ts +6 -1
  204. package/src/types/config.ts +5 -0
  205. package/src/types/configuration.ts +73 -0
  206. package/src/types/core.ts +55 -0
  207. package/src/types/environment.ts +104 -0
  208. package/src/types/fileSearch.ts +4 -0
  209. package/src/types/hooks.ts +32 -16
  210. package/src/types/index.ts +7 -0
  211. package/src/types/lsp.ts +96 -0
  212. package/src/types/messaging.ts +21 -14
  213. package/src/types/permissions.ts +48 -0
  214. package/src/types/session.ts +20 -0
  215. package/src/types/skills.ts +1 -0
  216. package/src/types/tools.ts +38 -0
  217. package/src/utils/abortUtils.ts +118 -0
  218. package/src/utils/bashHistory.ts +55 -31
  219. package/src/utils/builtinSubagents.ts +71 -0
  220. package/src/utils/cacheControlUtils.ts +475 -0
  221. package/src/utils/commandPathResolver.ts +189 -0
  222. package/src/utils/configPaths.ts +163 -0
  223. package/src/utils/constants.ts +2 -17
  224. package/src/utils/convertMessagesForAPI.ts +44 -18
  225. package/src/utils/customCommands.ts +90 -22
  226. package/src/utils/fileSearch.ts +107 -0
  227. package/src/utils/fileUtils.ts +160 -0
  228. package/src/utils/globalLogger.ts +128 -0
  229. package/src/utils/largeOutputHandler.ts +55 -0
  230. package/src/utils/markdownParser.ts +1 -19
  231. package/src/utils/mcpUtils.ts +34 -3
  232. package/src/utils/messageOperations.ts +47 -53
  233. package/src/utils/pathEncoder.ts +394 -0
  234. package/src/utils/subagentParser.ts +13 -9
  235. package/src/utils/tokenCalculation.ts +43 -0
  236. package/src/utils/tokenEstimator.ts +68 -0
  237. package/dist/utils/configResolver.d.ts +0 -38
  238. package/dist/utils/configResolver.d.ts.map +0 -1
  239. package/dist/utils/configResolver.js +0 -106
  240. package/src/utils/configResolver.ts +0 -142
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Configuration Path Utilities
3
+ *
4
+ * Centralized utilities for resolving Wave configuration file paths.
5
+ * Supports both regular settings.json and settings.local.json with proper priority.
6
+ *
7
+ * Priority system:
8
+ * - User configs: ~/.wave/settings.local.json > ~/.wave/settings.json
9
+ * - Project configs: {workdir}/.wave/settings.local.json > {workdir}/.wave/settings.json
10
+ * - Project configs override user configs (existing behavior)
11
+ */
12
+
13
+ import { join } from "path";
14
+ import { homedir } from "os";
15
+ import { existsSync } from "fs";
16
+
17
+ /**
18
+ * Get the user-specific configuration file path (legacy function)
19
+ * @deprecated Use getUserConfigPaths() for better priority support
20
+ */
21
+ export function getUserConfigPath(): string {
22
+ return join(homedir(), ".wave", "settings.json");
23
+ }
24
+
25
+ /**
26
+ * Get the project-specific configuration file path (legacy function)
27
+ * @deprecated Use getProjectConfigPaths() for better priority support
28
+ */
29
+ export function getProjectConfigPath(workdir: string): string {
30
+ return join(workdir, ".wave", "settings.json");
31
+ }
32
+
33
+ /**
34
+ * Get the user-specific configuration file paths in priority order
35
+ * Returns array with .local.json first, then .json
36
+ */
37
+ export function getUserConfigPaths(): string[] {
38
+ const baseDir = join(homedir(), ".wave");
39
+ return [join(baseDir, "settings.local.json"), join(baseDir, "settings.json")];
40
+ }
41
+
42
+ /**
43
+ * Get the project-specific configuration file paths in priority order
44
+ * Returns array with .local.json first, then .json
45
+ */
46
+ export function getProjectConfigPaths(workdir: string): string[] {
47
+ const baseDir = join(workdir, ".wave");
48
+ return [join(baseDir, "settings.local.json"), join(baseDir, "settings.json")];
49
+ }
50
+
51
+ /**
52
+ * Get all configuration file paths (user and project) in priority order
53
+ * Useful for comprehensive configuration detection
54
+ */
55
+ export function getAllConfigPaths(workdir: string): {
56
+ userPaths: string[];
57
+ projectPaths: string[];
58
+ allPaths: string[];
59
+ } {
60
+ const userPaths = getUserConfigPaths();
61
+ const projectPaths = getProjectConfigPaths(workdir);
62
+
63
+ return {
64
+ userPaths,
65
+ projectPaths,
66
+ allPaths: [...userPaths, ...projectPaths],
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Get existing configuration file paths
72
+ * Returns only the paths that actually exist on the filesystem
73
+ */
74
+ export function getExistingConfigPaths(workdir: string): {
75
+ userPaths: string[];
76
+ projectPaths: string[];
77
+ existingPaths: string[];
78
+ } {
79
+ const allPaths = getAllConfigPaths(workdir);
80
+
81
+ const existingUserPaths = allPaths.userPaths.filter(existsSync);
82
+ const existingProjectPaths = allPaths.projectPaths.filter(existsSync);
83
+ const allExistingPaths = allPaths.allPaths.filter(existsSync);
84
+
85
+ return {
86
+ userPaths: existingUserPaths,
87
+ projectPaths: existingProjectPaths,
88
+ existingPaths: allExistingPaths,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Get the first existing configuration file path with the specified priority
94
+ * @param paths Array of paths in priority order
95
+ * @returns The first path that exists, or undefined if none exist
96
+ */
97
+ export function getFirstExistingPath(paths: string[]): string | undefined {
98
+ return paths.find((path) => existsSync(path));
99
+ }
100
+
101
+ /**
102
+ * Get effective configuration paths (the ones that would actually be used)
103
+ * Returns the highest priority existing path for each category
104
+ */
105
+ export function getEffectiveConfigPaths(workdir: string): {
106
+ userPath?: string;
107
+ projectPath?: string;
108
+ effectivePath?: string; // The path that takes final precedence
109
+ } {
110
+ const userPaths = getUserConfigPaths();
111
+ const projectPaths = getProjectConfigPaths(workdir);
112
+
113
+ const userPath = getFirstExistingPath(userPaths);
114
+ const projectPath = getFirstExistingPath(projectPaths);
115
+
116
+ // Project path takes precedence over user path if both exist
117
+ const effectivePath = projectPath || userPath;
118
+
119
+ return {
120
+ userPath,
121
+ projectPath,
122
+ effectivePath,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Check if any configuration files exist
128
+ */
129
+ export function hasAnyConfig(workdir: string): boolean {
130
+ const { existingPaths } = getExistingConfigPaths(workdir);
131
+ return existingPaths.length > 0;
132
+ }
133
+
134
+ /**
135
+ * Get configuration information for debugging and monitoring
136
+ */
137
+ export function getConfigurationInfo(workdir: string): {
138
+ hasUser: boolean;
139
+ hasProject: boolean;
140
+ paths: string[];
141
+ userPaths: string[];
142
+ projectPaths: string[];
143
+ existingPaths: string[];
144
+ effectivePaths: {
145
+ userPath?: string;
146
+ projectPath?: string;
147
+ effectivePath?: string;
148
+ };
149
+ } {
150
+ const allPaths = getAllConfigPaths(workdir);
151
+ const existingPaths = getExistingConfigPaths(workdir);
152
+ const effectivePaths = getEffectiveConfigPaths(workdir);
153
+
154
+ return {
155
+ hasUser: existingPaths.userPaths.length > 0,
156
+ hasProject: existingPaths.projectPaths.length > 0,
157
+ paths: allPaths.allPaths,
158
+ userPaths: allPaths.userPaths,
159
+ projectPaths: allPaths.projectPaths,
160
+ existingPaths: existingPaths.existingPaths,
161
+ effectivePaths,
162
+ };
163
+ }
@@ -24,24 +24,9 @@ export const ERROR_LOG_DIRECTORY = path.join(DATA_DIRECTORY, "error-logs");
24
24
  /**
25
25
  * User-level memory file path
26
26
  */
27
- export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "user-memory.md");
27
+ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
28
28
 
29
29
  /**
30
30
  * AI related constants
31
31
  */
32
- export const DEFAULT_TOKEN_LIMIT = 64000; // Default token limit
33
-
34
- /**
35
- * @deprecated These constants are now legacy. Use ModelConfig through Agent constructor instead.
36
- * They are maintained for backward compatibility with existing code that might still reference them,
37
- * but the actual AI services now use configuration injection.
38
- */
39
- export const FAST_MODEL_ID = process.env.AIGW_FAST_MODEL || "gemini-2.5-flash";
40
-
41
- /**
42
- * @deprecated These constants are now legacy. Use ModelConfig through Agent constructor instead.
43
- * They are maintained for backward compatibility with existing code that might still reference them,
44
- * but the actual AI services now use configuration injection.
45
- */
46
- export const AGENT_MODEL_ID =
47
- process.env.AIGW_MODEL || "claude-sonnet-4-20250514";
32
+ export const DEFAULT_TOKEN_LIMIT = 96000; // Default token limit
@@ -6,6 +6,7 @@ import {
6
6
  ChatCompletionContentPart,
7
7
  ChatCompletionMessageParam,
8
8
  } from "openai/resources.js";
9
+ import { logger } from "./globalLogger.js";
9
10
 
10
11
  /**
11
12
  * Safely handle tool call parameters, ensuring a legal JSON string is returned
@@ -21,15 +22,16 @@ function safeToolArguments(args: string): string {
21
22
  // Try to parse as JSON to validate format
22
23
  JSON.parse(args);
23
24
  return args;
24
- } catch {
25
- // logger.error(`Invalid tool arguments: ${args}`);
25
+ } catch (error) {
26
+ logger.error(`Invalid tool arguments: ${args}`, error);
26
27
  // If not valid JSON, return a fallback empty object
27
28
  return "{}";
28
29
  }
29
30
  }
30
31
 
31
32
  /**
32
- * Convert message format to API call format, stopping when a compressed message is encountered
33
+ * Convert message format to API call format, stopping when a compressed message is encountered.
34
+ * Messages with no meaningful content or tool calls are filtered out.
33
35
  * @param messages Message list
34
36
  * @returns Converted API message format list
35
37
  */
@@ -60,7 +62,7 @@ export function convertMessagesForAPI(
60
62
  break;
61
63
  }
62
64
 
63
- // Skip empty assistant messages
65
+ // Skip empty assistant messages (no blocks or all blocks are empty)
64
66
  if (message.role === "assistant" && message.blocks.length === 0) {
65
67
  continue;
66
68
  }
@@ -75,8 +77,8 @@ export function convertMessagesForAPI(
75
77
 
76
78
  if (toolBlocks.length > 0) {
77
79
  toolBlocks.forEach((toolBlock) => {
78
- // Only add completed tool blocks (i.e., not running)
79
- if (toolBlock.id && !toolBlock.isRunning) {
80
+ // Only add completed tool blocks (i.e., stage is 'end')
81
+ if (toolBlock.id && toolBlock.stage === "end") {
80
82
  completedToolIds.add(toolBlock.id);
81
83
 
82
84
  // Check for image data
@@ -127,12 +129,17 @@ export function convertMessagesForAPI(
127
129
  let content = "";
128
130
  let tool_calls: ChatCompletionMessageToolCall[] | undefined = undefined;
129
131
 
130
- // Construct content from text blocks
132
+ // Construct content from text blocks - filter out empty content
131
133
  const textBlocks = message.blocks.filter(
132
- (block) => block.type === "text",
134
+ (block) =>
135
+ block.type === "text" &&
136
+ block.content &&
137
+ block.content.trim().length > 0,
133
138
  );
134
139
  if (textBlocks.length > 0) {
135
- content = textBlocks.map((block) => block.content || "").join("\n");
140
+ content = textBlocks
141
+ .map((block) => (block.type === "text" ? block.content : ""))
142
+ .join("\n");
136
143
  }
137
144
 
138
145
  // Construct tool calls from tool blocks
@@ -157,12 +164,16 @@ export function convertMessagesForAPI(
157
164
  }
158
165
  }
159
166
 
160
- // Construct assistant message - only add if there is content or tool calls
161
- if (content || tool_calls) {
167
+ // Construct assistant message - only add if there is meaningful content or tool calls
168
+ const hasContent = content && content.trim().length > 0;
169
+ const hasToolCalls = tool_calls && tool_calls.length > 0;
170
+
171
+ if (hasContent || hasToolCalls) {
162
172
  const assistantMessage: ChatCompletionMessageParam = {
163
173
  role: "assistant",
164
- content,
174
+ content: hasContent ? content : undefined,
165
175
  tool_calls,
176
+ ...(message.additionalFields ? { ...message.additionalFields } : {}),
166
177
  };
167
178
 
168
179
  recentMessages.unshift(assistantMessage);
@@ -172,8 +183,12 @@ export function convertMessagesForAPI(
172
183
  const contentParts: ChatCompletionContentPart[] = [];
173
184
 
174
185
  message.blocks.forEach((block) => {
175
- // Add text content
176
- if (block.type === "text" && block.content) {
186
+ // Add text content - only if it has meaningful content
187
+ if (
188
+ block.type === "text" &&
189
+ block.content &&
190
+ block.content.trim().length > 0
191
+ ) {
177
192
  contentParts.push({
178
193
  type: "text",
179
194
  text: block.customCommandContent || block.content,
@@ -194,7 +209,7 @@ export function convertMessagesForAPI(
194
209
  try {
195
210
  finalImageUrl = convertImageToBase64(imageUrl);
196
211
  } catch (error) {
197
- console.error(
212
+ logger.error(
198
213
  "Failed to convert image path to base64:",
199
214
  imageUrl,
200
215
  error,
@@ -215,11 +230,22 @@ export function convertMessagesForAPI(
215
230
  }
216
231
  });
217
232
 
233
+ // Only add user message if there is meaningful content
218
234
  if (contentParts.length > 0) {
219
- recentMessages.unshift({
220
- role: "user",
221
- content: contentParts,
235
+ // Filter out empty text parts
236
+ const meaningfulParts = contentParts.filter((part) => {
237
+ if (part.type === "text") {
238
+ return part.text && part.text.trim().length > 0;
239
+ }
240
+ return true; // Keep image parts
222
241
  });
242
+
243
+ if (meaningfulParts.length > 0) {
244
+ recentMessages.unshift({
245
+ role: "user",
246
+ content: meaningfulParts,
247
+ });
248
+ }
223
249
  }
224
250
  }
225
251
  }
@@ -1,8 +1,15 @@
1
- import { existsSync, readdirSync } from "fs";
1
+ import { existsSync, readdirSync, statSync } from "fs";
2
2
  import { join, extname, basename } from "path";
3
3
  import { homedir } from "os";
4
4
  import type { CustomSlashCommand } from "../types/index.js";
5
5
  import { parseMarkdownFile } from "./markdownParser.js";
6
+ import {
7
+ generateCommandId,
8
+ getCommandSegments,
9
+ getNamespace,
10
+ getDepth,
11
+ } from "./commandPathResolver.js";
12
+ import { logger } from "./globalLogger.js";
6
13
 
7
14
  /**
8
15
  * Get the project-specific commands directory
@@ -19,43 +26,104 @@ export function getUserCommandsDir(): string {
19
26
  }
20
27
 
21
28
  /**
22
- * Scan a directory for markdown command files
29
+ * Scan a directory for markdown command files with nested directory support
30
+ * @param dirPath - Root commands directory path
31
+ * @param maxDepth - Maximum nesting depth to scan (default: 1)
23
32
  */
24
- function scanCommandsDirectory(dirPath: string): CustomSlashCommand[] {
33
+ function scanCommandsDirectory(
34
+ dirPath: string,
35
+ maxDepth: number = 1,
36
+ ): CustomSlashCommand[] {
25
37
  if (!existsSync(dirPath)) {
26
38
  return [];
27
39
  }
28
40
 
41
+ return scanCommandsDirectoryRecursive(dirPath, dirPath, 0, maxDepth);
42
+ }
43
+
44
+ /**
45
+ * Recursively scan directory for commands with depth control
46
+ * @param currentPath - Current directory being scanned
47
+ * @param rootPath - Root commands directory (for relative path calculation)
48
+ * @param currentDepth - Current nesting depth
49
+ * @param maxDepth - Maximum allowed depth
50
+ */
51
+ function scanCommandsDirectoryRecursive(
52
+ currentPath: string,
53
+ rootPath: string,
54
+ currentDepth: number,
55
+ maxDepth: number,
56
+ ): CustomSlashCommand[] {
29
57
  const commands: CustomSlashCommand[] = [];
30
58
 
31
59
  try {
32
- const files = readdirSync(dirPath);
60
+ const entries = readdirSync(currentPath);
33
61
 
34
- for (const file of files) {
35
- if (extname(file) !== ".md") {
36
- continue;
37
- }
62
+ for (const entryName of entries) {
63
+ const fullPath = join(currentPath, entryName);
38
64
 
39
- const filePath = join(dirPath, file);
40
- const commandName = basename(file, ".md");
65
+ let isDirectory = false;
66
+ let isFile = false;
41
67
 
42
68
  try {
43
- const { content, config } = parseMarkdownFile(filePath);
44
-
45
- commands.push({
46
- id: commandName,
47
- name: commandName,
48
- description: config?.description, // Use description from frontmatter
49
- filePath,
50
- content,
51
- config,
52
- });
69
+ const stats = statSync(fullPath);
70
+ isDirectory = stats.isDirectory();
71
+ isFile = stats.isFile();
53
72
  } catch (error) {
54
- console.warn(`Failed to load custom command from ${filePath}:`, error);
73
+ // Skip entries that cannot be stat'd
74
+ logger.warn(`Cannot access ${fullPath}:`, error);
75
+ continue;
76
+ }
77
+
78
+ if (isDirectory) {
79
+ // Skip subdirectories if we're at max depth
80
+ if (currentDepth >= maxDepth) {
81
+ logger.warn(
82
+ `Skipping directory ${fullPath}: exceeds maximum nesting depth of ${maxDepth}`,
83
+ );
84
+ continue;
85
+ }
86
+
87
+ // Recursively scan subdirectory
88
+ const nestedCommands = scanCommandsDirectoryRecursive(
89
+ fullPath,
90
+ rootPath,
91
+ currentDepth + 1,
92
+ maxDepth,
93
+ );
94
+ commands.push(...nestedCommands);
95
+ } else if (isFile && extname(entryName) === ".md") {
96
+ // Process markdown file
97
+ try {
98
+ const commandId = generateCommandId(fullPath, rootPath);
99
+ const segments = getCommandSegments(fullPath, rootPath);
100
+ const namespace = getNamespace(segments);
101
+ const depth = getDepth(segments);
102
+
103
+ const { content, config } = parseMarkdownFile(fullPath);
104
+
105
+ commands.push({
106
+ id: commandId,
107
+ name: basename(entryName, ".md"),
108
+ description: config?.description,
109
+ filePath: fullPath,
110
+ content,
111
+ config,
112
+
113
+ // Nested command metadata
114
+ namespace,
115
+ isNested: depth > 0,
116
+ depth,
117
+ segments,
118
+ });
119
+ } catch (error) {
120
+ logger.warn(`Failed to load custom command from ${fullPath}:`, error);
121
+ }
55
122
  }
123
+ // Skip non-markdown files silently
56
124
  }
57
125
  } catch (error) {
58
- console.warn(`Failed to scan commands directory ${dirPath}:`, error);
126
+ logger.warn(`Failed to scan commands directory ${currentPath}:`, error);
59
127
  }
60
128
 
61
129
  return commands;
@@ -0,0 +1,107 @@
1
+ import { globIterate, type Path } from "glob";
2
+ import { getGlobIgnorePatterns } from "./fileFilter.js";
3
+ import type { FileItem } from "../types/fileSearch.js";
4
+
5
+ /**
6
+ * Convert Path objects to FileItem objects
7
+ */
8
+ export const convertPathsToFileItems = (paths: Path[]): FileItem[] => {
9
+ return paths.map((pathObj) => ({
10
+ path: pathObj.relative(),
11
+ type: pathObj.isDirectory() ? "directory" : "file",
12
+ }));
13
+ };
14
+
15
+ /**
16
+ * Search files and directories using glob patterns
17
+ */
18
+ export const searchFiles = async (
19
+ query: string,
20
+ options?: {
21
+ maxResults?: number;
22
+ workingDirectory?: string;
23
+ },
24
+ ): Promise<FileItem[]> => {
25
+ const { maxResults = 10, workingDirectory = process.cwd() } = options || {};
26
+
27
+ try {
28
+ const globOptions: import("glob").GlobOptionsWithFileTypesTrue = {
29
+ ignore: getGlobIgnorePatterns(workingDirectory),
30
+ maxDepth: 10,
31
+ nocase: true, // Case insensitive
32
+ dot: true, // Include hidden files and directories
33
+ cwd: workingDirectory, // Specify search root directory
34
+ withFileTypes: true, // Get Path objects instead of strings
35
+ };
36
+
37
+ // Build glob patterns based on query
38
+ let patterns: string[] = [];
39
+
40
+ if (!query.trim()) {
41
+ // When query is empty, show some common file types and directories
42
+ patterns = [
43
+ "**/*.{ts,tsx,js,jsx,json,py,java}", // Combine common file extensions
44
+ "*/", // First level directories
45
+ ];
46
+ } else {
47
+ // Build multiple glob patterns to support more flexible search
48
+ patterns = [
49
+ // Match files with filenames containing query
50
+ `**/*${query}*`,
51
+ // Match files with query in path (match directory names)
52
+ `**/${query}*/**/*`,
53
+ // Match directory names containing query
54
+ `**/*${query}*/`,
55
+ // Match directories containing query in path
56
+ `**/${query}*/`,
57
+ ];
58
+ }
59
+
60
+ // Collect results until we reach maxResults
61
+ const collectedPaths: Path[] = [];
62
+ const seenPaths = new Set<string>();
63
+
64
+ // Process each pattern sequentially
65
+ for (const pattern of patterns) {
66
+ if (collectedPaths.length >= maxResults) {
67
+ break;
68
+ }
69
+
70
+ // Use globIterate to get results one by one
71
+ const iterator = globIterate(pattern, globOptions) as AsyncGenerator<
72
+ Path,
73
+ void,
74
+ void
75
+ >;
76
+
77
+ for await (const pathObj of iterator) {
78
+ if (collectedPaths.length >= maxResults) {
79
+ // Stop the iterator when we have enough results
80
+ break;
81
+ }
82
+
83
+ const relativePath = pathObj.relative();
84
+ if (!seenPaths.has(relativePath)) {
85
+ seenPaths.add(relativePath);
86
+ collectedPaths.push(pathObj);
87
+ }
88
+ }
89
+ }
90
+
91
+ // Sort collected paths: directories first, then files
92
+ collectedPaths.sort((a, b) => {
93
+ const aIsDir = a.isDirectory();
94
+ const bIsDir = b.isDirectory();
95
+ if (aIsDir && !bIsDir) return -1;
96
+ if (!aIsDir && bIsDir) return 1;
97
+ return a.relative().localeCompare(b.relative());
98
+ });
99
+
100
+ // Convert to FileItems
101
+ const fileItems = convertPathsToFileItems(collectedPaths);
102
+ return fileItems;
103
+ } catch (error) {
104
+ console.error("Glob search error:", error);
105
+ return [];
106
+ }
107
+ };