wave-agent-sdk 0.0.6 → 0.0.8

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 (180) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +209 -24
  4. package/dist/constants/events.d.ts +28 -0
  5. package/dist/constants/events.d.ts.map +1 -0
  6. package/dist/constants/events.js +27 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/managers/aiManager.d.ts +34 -1
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +248 -132
  13. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundBashManager.js +7 -6
  15. package/dist/managers/hookManager.d.ts +13 -16
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +81 -44
  18. package/dist/managers/liveConfigManager.d.ts +58 -0
  19. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  20. package/dist/managers/liveConfigManager.js +160 -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 +168 -49
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +9 -3
  26. package/dist/managers/subagentManager.d.ts +51 -0
  27. package/dist/managers/subagentManager.d.ts.map +1 -1
  28. package/dist/managers/subagentManager.js +190 -19
  29. package/dist/services/aiService.d.ts +13 -5
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +350 -74
  32. package/dist/services/configurationWatcher.d.ts +120 -0
  33. package/dist/services/configurationWatcher.d.ts.map +1 -0
  34. package/dist/services/configurationWatcher.js +439 -0
  35. package/dist/services/fileWatcher.d.ts +69 -0
  36. package/dist/services/fileWatcher.d.ts.map +1 -0
  37. package/dist/services/fileWatcher.js +213 -0
  38. package/dist/services/hook.d.ts +91 -9
  39. package/dist/services/hook.d.ts.map +1 -1
  40. package/dist/services/hook.js +393 -43
  41. package/dist/services/jsonlHandler.d.ts +62 -0
  42. package/dist/services/jsonlHandler.d.ts.map +1 -0
  43. package/dist/services/jsonlHandler.js +257 -0
  44. package/dist/services/memory.d.ts +9 -0
  45. package/dist/services/memory.d.ts.map +1 -1
  46. package/dist/services/memory.js +81 -12
  47. package/dist/services/memoryStore.d.ts +81 -0
  48. package/dist/services/memoryStore.d.ts.map +1 -0
  49. package/dist/services/memoryStore.js +200 -0
  50. package/dist/services/session.d.ts +64 -49
  51. package/dist/services/session.d.ts.map +1 -1
  52. package/dist/services/session.js +310 -132
  53. package/dist/tools/bashTool.d.ts.map +1 -1
  54. package/dist/tools/bashTool.js +5 -4
  55. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  56. package/dist/tools/deleteFileTool.js +2 -1
  57. package/dist/tools/editTool.d.ts.map +1 -1
  58. package/dist/tools/editTool.js +3 -2
  59. package/dist/tools/multiEditTool.d.ts.map +1 -1
  60. package/dist/tools/multiEditTool.js +4 -3
  61. package/dist/tools/readTool.d.ts.map +1 -1
  62. package/dist/tools/readTool.js +2 -1
  63. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  64. package/dist/tools/todoWriteTool.js +3 -10
  65. package/dist/tools/writeTool.d.ts.map +1 -1
  66. package/dist/tools/writeTool.js +5 -6
  67. package/dist/types/commands.d.ts +4 -0
  68. package/dist/types/commands.d.ts.map +1 -1
  69. package/dist/types/core.d.ts +35 -0
  70. package/dist/types/core.d.ts.map +1 -1
  71. package/dist/types/environment.d.ts +42 -0
  72. package/dist/types/environment.d.ts.map +1 -0
  73. package/dist/types/environment.js +21 -0
  74. package/dist/types/hooks.d.ts +8 -2
  75. package/dist/types/hooks.d.ts.map +1 -1
  76. package/dist/types/hooks.js +8 -2
  77. package/dist/types/index.d.ts +2 -0
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js +2 -0
  80. package/dist/types/memoryStore.d.ts +82 -0
  81. package/dist/types/memoryStore.d.ts.map +1 -0
  82. package/dist/types/memoryStore.js +7 -0
  83. package/dist/types/messaging.d.ts +21 -9
  84. package/dist/types/messaging.d.ts.map +1 -1
  85. package/dist/types/messaging.js +5 -1
  86. package/dist/types/session.d.ts +20 -0
  87. package/dist/types/session.d.ts.map +1 -0
  88. package/dist/types/session.js +7 -0
  89. package/dist/utils/bashHistory.d.ts.map +1 -1
  90. package/dist/utils/bashHistory.js +27 -26
  91. package/dist/utils/cacheControlUtils.d.ts +121 -0
  92. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  93. package/dist/utils/cacheControlUtils.js +367 -0
  94. package/dist/utils/commandPathResolver.d.ts +52 -0
  95. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  96. package/dist/utils/commandPathResolver.js +145 -0
  97. package/dist/utils/configPaths.d.ts +85 -0
  98. package/dist/utils/configPaths.d.ts.map +1 -0
  99. package/dist/utils/configPaths.js +121 -0
  100. package/dist/utils/configResolver.d.ts +37 -10
  101. package/dist/utils/configResolver.d.ts.map +1 -1
  102. package/dist/utils/configResolver.js +127 -23
  103. package/dist/utils/constants.d.ts +1 -1
  104. package/dist/utils/constants.js +1 -1
  105. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  106. package/dist/utils/convertMessagesForAPI.js +8 -13
  107. package/dist/utils/customCommands.d.ts.map +1 -1
  108. package/dist/utils/customCommands.js +66 -21
  109. package/dist/utils/fileUtils.d.ts +15 -0
  110. package/dist/utils/fileUtils.d.ts.map +1 -0
  111. package/dist/utils/fileUtils.js +61 -0
  112. package/dist/utils/globalLogger.d.ts +102 -0
  113. package/dist/utils/globalLogger.d.ts.map +1 -0
  114. package/dist/utils/globalLogger.js +136 -0
  115. package/dist/utils/hookMatcher.d.ts +1 -6
  116. package/dist/utils/hookMatcher.d.ts.map +1 -1
  117. package/dist/utils/mcpUtils.d.ts.map +1 -1
  118. package/dist/utils/mcpUtils.js +25 -3
  119. package/dist/utils/messageOperations.d.ts +27 -27
  120. package/dist/utils/messageOperations.d.ts.map +1 -1
  121. package/dist/utils/messageOperations.js +46 -36
  122. package/dist/utils/pathEncoder.d.ts +104 -0
  123. package/dist/utils/pathEncoder.d.ts.map +1 -0
  124. package/dist/utils/pathEncoder.js +272 -0
  125. package/dist/utils/subagentParser.d.ts.map +1 -1
  126. package/dist/utils/subagentParser.js +2 -1
  127. package/dist/utils/tokenCalculation.d.ts +26 -0
  128. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  129. package/dist/utils/tokenCalculation.js +36 -0
  130. package/package.json +6 -3
  131. package/src/agent.ts +301 -37
  132. package/src/constants/events.ts +38 -0
  133. package/src/index.ts +2 -0
  134. package/src/managers/aiManager.ts +325 -173
  135. package/src/managers/backgroundBashManager.ts +7 -6
  136. package/src/managers/hookManager.ts +106 -84
  137. package/src/managers/liveConfigManager.ts +248 -0
  138. package/src/managers/messageManager.ts +237 -100
  139. package/src/managers/slashCommandManager.ts +9 -7
  140. package/src/managers/subagentManager.ts +284 -22
  141. package/src/services/aiService.ts +474 -83
  142. package/src/services/configurationWatcher.ts +622 -0
  143. package/src/services/fileWatcher.ts +301 -0
  144. package/src/services/hook.ts +538 -47
  145. package/src/services/jsonlHandler.ts +319 -0
  146. package/src/services/memory.ts +92 -12
  147. package/src/services/memoryStore.ts +279 -0
  148. package/src/services/session.ts +381 -157
  149. package/src/tools/bashTool.ts +5 -4
  150. package/src/tools/deleteFileTool.ts +2 -1
  151. package/src/tools/editTool.ts +3 -2
  152. package/src/tools/multiEditTool.ts +4 -3
  153. package/src/tools/readTool.ts +2 -1
  154. package/src/tools/todoWriteTool.ts +3 -11
  155. package/src/tools/writeTool.ts +7 -6
  156. package/src/types/commands.ts +6 -0
  157. package/src/types/core.ts +44 -0
  158. package/src/types/environment.ts +60 -0
  159. package/src/types/hooks.ts +21 -8
  160. package/src/types/index.ts +2 -0
  161. package/src/types/memoryStore.ts +94 -0
  162. package/src/types/messaging.ts +21 -10
  163. package/src/types/session.ts +25 -0
  164. package/src/utils/bashHistory.ts +27 -27
  165. package/src/utils/cacheControlUtils.ts +540 -0
  166. package/src/utils/commandPathResolver.ts +189 -0
  167. package/src/utils/configPaths.ts +163 -0
  168. package/src/utils/configResolver.ts +182 -22
  169. package/src/utils/constants.ts +1 -1
  170. package/src/utils/convertMessagesForAPI.ts +8 -14
  171. package/src/utils/customCommands.ts +90 -22
  172. package/src/utils/fileUtils.ts +65 -0
  173. package/src/utils/globalLogger.ts +145 -0
  174. package/src/utils/hookMatcher.ts +1 -12
  175. package/src/utils/mcpUtils.ts +34 -3
  176. package/src/utils/messageOperations.ts +77 -60
  177. package/src/utils/pathEncoder.ts +379 -0
  178. package/src/utils/subagentParser.ts +2 -1
  179. package/src/utils/tokenCalculation.ts +43 -0
  180. package/src/types/index.ts.backup +0 -357
@@ -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,8 +22,8 @@ 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
  }
@@ -75,8 +76,8 @@ export function convertMessagesForAPI(
75
76
 
76
77
  if (toolBlocks.length > 0) {
77
78
  toolBlocks.forEach((toolBlock) => {
78
- // Only add completed tool blocks (i.e., not running)
79
- if (toolBlock.id && !toolBlock.isRunning) {
79
+ // Only add completed tool blocks (i.e., stage is 'end')
80
+ if (toolBlock.id && toolBlock.stage === "end") {
80
81
  completedToolIds.add(toolBlock.id);
81
82
 
82
83
  // Check for image data
@@ -163,6 +164,7 @@ export function convertMessagesForAPI(
163
164
  role: "assistant",
164
165
  content,
165
166
  tool_calls,
167
+ ...(message.metadata ? { ...message.metadata } : {}),
166
168
  };
167
169
 
168
170
  recentMessages.unshift(assistantMessage);
@@ -176,15 +178,7 @@ export function convertMessagesForAPI(
176
178
  if (block.type === "text" && block.content) {
177
179
  contentParts.push({
178
180
  type: "text",
179
- text: block.content,
180
- });
181
- }
182
-
183
- // Handle custom command blocks - pass full content as text to AI
184
- if (block.type === "custom_command" && block.content) {
185
- contentParts.push({
186
- type: "text",
187
- text: block.content,
181
+ text: block.customCommandContent || block.content,
188
182
  });
189
183
  }
190
184
 
@@ -202,7 +196,7 @@ export function convertMessagesForAPI(
202
196
  try {
203
197
  finalImageUrl = convertImageToBase64(imageUrl);
204
198
  } catch (error) {
205
- console.error(
199
+ logger.error(
206
200
  "Failed to convert image path to base64:",
207
201
  imageUrl,
208
202
  error,
@@ -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,65 @@
1
+ import fs from "node:fs";
2
+
3
+ /**
4
+ * Reads the first line of a file efficiently using Node.js readline.
5
+ *
6
+ * @param {string} filePath - The path to the file.
7
+ * @return {Promise<string>} - The first non-empty line of the file, or an empty string otherwise.
8
+ */
9
+ export async function readFirstLine(filePath: string): Promise<string> {
10
+ const { createReadStream } = fs;
11
+ const { createInterface } = await import("node:readline");
12
+
13
+ const fileStream = createReadStream(filePath);
14
+ const rl = createInterface({
15
+ input: fileStream,
16
+ crlfDelay: Infinity, // Handle \r\n properly
17
+ });
18
+
19
+ try {
20
+ for await (const line of rl) {
21
+ const trimmedLine = line.trim();
22
+ if (trimmedLine.length > 0) {
23
+ return trimmedLine;
24
+ }
25
+ }
26
+ return "";
27
+ } catch {
28
+ // If reading fails (e.g., file doesn't exist), return empty string
29
+ return "";
30
+ } finally {
31
+ rl.close();
32
+ fileStream.destroy();
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Reads a file from the end and returns the first non-empty line.
38
+ *
39
+ * @param {string} filePath - The path to the file.
40
+ * @return {Promise<string>} - The last non-empty line of the file, or an empty string if no non-empty lines found.
41
+ */
42
+ export async function getLastLine(filePath: string): Promise<string> {
43
+ const { exec } = await import("child_process");
44
+ const { promisify } = await import("util");
45
+ const execAsync = promisify(exec);
46
+
47
+ try {
48
+ // Use tail with multiple lines to handle cases where the last line might be empty
49
+ const { stdout } = await execAsync(`tail -n 3 "${filePath}"`);
50
+ const lines = stdout.split(/\r?\n/);
51
+
52
+ // Find the first non-empty line working backwards
53
+ for (let i = lines.length - 1; i >= 0; i--) {
54
+ const line = lines[i].trim();
55
+ if (line.length > 0) {
56
+ return line;
57
+ }
58
+ }
59
+
60
+ return "";
61
+ } catch {
62
+ // If tail fails (e.g., file doesn't exist), return empty string
63
+ return "";
64
+ }
65
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Global Logger Registry for Agent SDK
3
+ *
4
+ * Provides zero-overhead logging access for utility functions and services
5
+ * without requiring parameter passing. Maintains singleton logger instance
6
+ * accessible across all SDK modules.
7
+ *
8
+ * Features:
9
+ * - Zero overhead when no logger configured (single null check)
10
+ * - Thread-safe in Node.js single-threaded environment
11
+ * - Maintains backward compatibility with existing Logger interface
12
+ * - Direct function delegation when configured
13
+ */
14
+
15
+ import type { Logger } from "../types/core.js";
16
+
17
+ /**
18
+ * Module-level storage for the global logger instance
19
+ * null = unconfigured (logging calls are no-ops)
20
+ * Logger = configured (calls are forwarded)
21
+ */
22
+ let globalLogger: Logger | null = null;
23
+
24
+ // =============================================================================
25
+ // Registry Management API
26
+ // =============================================================================
27
+
28
+ /**
29
+ * Configure the global logger instance used by utility functions and services
30
+ *
31
+ * @param logger - Logger instance implementing the Logger interface, or null to disable logging
32
+ * @returns void
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { setGlobalLogger } from './utils/globalLogger.js';
37
+ *
38
+ * // Configure logger
39
+ * setGlobalLogger(myLogger);
40
+ *
41
+ * // Disable logging
42
+ * setGlobalLogger(null);
43
+ * ```
44
+ */
45
+ export function setGlobalLogger(logger: Logger | null): void {
46
+ globalLogger = logger;
47
+ }
48
+
49
+ /**
50
+ * Retrieve the current global logger instance
51
+ *
52
+ * @returns Current logger instance or null if unconfigured
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const currentLogger = getGlobalLogger();
57
+ * if (currentLogger) {
58
+ * currentLogger.info('Direct logger access');
59
+ * }
60
+ * ```
61
+ */
62
+ export function getGlobalLogger(): Logger | null {
63
+ return globalLogger;
64
+ }
65
+
66
+ /**
67
+ * Reset global logger to unconfigured state
68
+ * Equivalent to setGlobalLogger(null)
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * clearGlobalLogger(); // All subsequent logging calls become no-ops
73
+ * ```
74
+ */
75
+ export function clearGlobalLogger(): void {
76
+ globalLogger = null;
77
+ }
78
+
79
+ /**
80
+ * Check if global logger is currently configured
81
+ *
82
+ * @returns true if logger configured, false otherwise
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * if (isLoggerConfigured()) {
87
+ * // Perform expensive logging operation
88
+ * const debugInfo = generateDebugInfo();
89
+ * logger.debug(debugInfo);
90
+ * }
91
+ * ```
92
+ */
93
+ export function isLoggerConfigured(): boolean {
94
+ return globalLogger !== null;
95
+ }
96
+
97
+ // =============================================================================
98
+ // Zero-Overhead Logging API
99
+ // =============================================================================
100
+
101
+ /**
102
+ * Zero-overhead logging interface
103
+ *
104
+ * Performance characteristics:
105
+ * - Unconfigured: Single null check + early return (near-zero cost)
106
+ * - Configured: Null check + function delegation
107
+ * - No object creation or intermediate allocations
108
+ */
109
+ export const logger = {
110
+ /**
111
+ * Log debug-level message through global logger
112
+ * No-op when global logger is null (zero overhead)
113
+ */
114
+ debug: (...args: unknown[]): void => {
115
+ if (globalLogger === null) return;
116
+ globalLogger.debug(...args);
117
+ },
118
+
119
+ /**
120
+ * Log info-level message through global logger
121
+ * No-op when global logger is null (zero overhead)
122
+ */
123
+ info: (...args: unknown[]): void => {
124
+ if (globalLogger === null) return;
125
+ globalLogger.info(...args);
126
+ },
127
+
128
+ /**
129
+ * Log warning-level message through global logger
130
+ * No-op when global logger is null (zero overhead)
131
+ */
132
+ warn: (...args: unknown[]): void => {
133
+ if (globalLogger === null) return;
134
+ globalLogger.warn(...args);
135
+ },
136
+
137
+ /**
138
+ * Log error-level message through global logger
139
+ * No-op when global logger is null (zero overhead)
140
+ */
141
+ error: (...args: unknown[]): void => {
142
+ if (globalLogger === null) return;
143
+ globalLogger.error(...args);
144
+ },
145
+ } as const;
@@ -7,18 +7,7 @@
7
7
 
8
8
  import { minimatch } from "minimatch";
9
9
 
10
- export interface IHookMatcher {
11
- // Test if pattern matches tool name
12
- matches(pattern: string, toolName: string): boolean;
13
-
14
- // Validate pattern syntax
15
- isValidPattern(pattern: string): boolean;
16
-
17
- // Get pattern type for optimization
18
- getPatternType(pattern: string): "exact" | "glob" | "regex" | "alternatives";
19
- }
20
-
21
- export class HookMatcher implements IHookMatcher {
10
+ export class HookMatcher {
22
11
  /**
23
12
  * Test if pattern matches tool name
24
13
  * Supports multiple matching strategies:
@@ -2,6 +2,36 @@ import { ChatCompletionFunctionTool } from "openai/resources.js";
2
2
  import type { ToolPlugin, ToolResult, ToolContext } from "../tools/types.js";
3
3
  import type { McpTool, McpServerStatus } from "../types/index.js";
4
4
 
5
+ /**
6
+ * Recursively clean schema to remove unsupported fields
7
+ */
8
+ function cleanSchema(schema: unknown): unknown {
9
+ if (typeof schema !== "object" || schema === null) {
10
+ return schema;
11
+ }
12
+
13
+ if (Array.isArray(schema)) {
14
+ return schema.map(cleanSchema);
15
+ }
16
+
17
+ const newSchema: Record<string, unknown> = {};
18
+ const obj = schema as Record<string, unknown>;
19
+
20
+ for (const key in obj) {
21
+ // Remove $schema as OpenAI API doesn't accept it
22
+ // Remove exclusiveMinimum/exclusiveMaximum as some models (e.g. Gemini) don't support them
23
+ if (
24
+ key === "$schema" ||
25
+ key === "exclusiveMinimum" ||
26
+ key === "exclusiveMaximum"
27
+ ) {
28
+ continue;
29
+ }
30
+ newSchema[key] = cleanSchema(obj[key]);
31
+ }
32
+ return newSchema;
33
+ }
34
+
5
35
  /**
6
36
  * Convert MCP tool to OpenAI function tool format
7
37
  */
@@ -9,9 +39,10 @@ export function mcpToolToOpenAITool(
9
39
  mcpTool: McpTool,
10
40
  serverName: string,
11
41
  ): ChatCompletionFunctionTool {
12
- // Remove $schema field if it exists, as OpenAI API doesn't accept it
13
- const cleanInputSchema = { ...mcpTool.inputSchema };
14
- delete cleanInputSchema.$schema;
42
+ const cleanInputSchema = cleanSchema(mcpTool.inputSchema) as Record<
43
+ string,
44
+ unknown
45
+ >;
15
46
 
16
47
  return {
17
48
  type: "function",