wave-agent-sdk 0.0.7 → 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 (172) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +202 -20
  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 +243 -128
  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 +9 -4
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +62 -30
  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 +38 -13
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +163 -30
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +4 -1
  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 +189 -18
  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/writeTool.d.ts.map +1 -1
  64. package/dist/tools/writeTool.js +5 -6
  65. package/dist/types/commands.d.ts +4 -0
  66. package/dist/types/commands.d.ts.map +1 -1
  67. package/dist/types/core.d.ts +35 -0
  68. package/dist/types/core.d.ts.map +1 -1
  69. package/dist/types/environment.d.ts +42 -0
  70. package/dist/types/environment.d.ts.map +1 -0
  71. package/dist/types/environment.js +21 -0
  72. package/dist/types/hooks.d.ts +8 -2
  73. package/dist/types/hooks.d.ts.map +1 -1
  74. package/dist/types/hooks.js +8 -2
  75. package/dist/types/index.d.ts +2 -0
  76. package/dist/types/index.d.ts.map +1 -1
  77. package/dist/types/index.js +2 -0
  78. package/dist/types/memoryStore.d.ts +82 -0
  79. package/dist/types/memoryStore.d.ts.map +1 -0
  80. package/dist/types/memoryStore.js +7 -0
  81. package/dist/types/messaging.d.ts +14 -2
  82. package/dist/types/messaging.d.ts.map +1 -1
  83. package/dist/types/session.d.ts +20 -0
  84. package/dist/types/session.d.ts.map +1 -0
  85. package/dist/types/session.js +7 -0
  86. package/dist/utils/bashHistory.d.ts.map +1 -1
  87. package/dist/utils/bashHistory.js +27 -26
  88. package/dist/utils/cacheControlUtils.d.ts +121 -0
  89. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  90. package/dist/utils/cacheControlUtils.js +367 -0
  91. package/dist/utils/commandPathResolver.d.ts +52 -0
  92. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  93. package/dist/utils/commandPathResolver.js +145 -0
  94. package/dist/utils/configPaths.d.ts +85 -0
  95. package/dist/utils/configPaths.d.ts.map +1 -0
  96. package/dist/utils/configPaths.js +121 -0
  97. package/dist/utils/configResolver.d.ts +37 -10
  98. package/dist/utils/configResolver.d.ts.map +1 -1
  99. package/dist/utils/configResolver.js +127 -23
  100. package/dist/utils/constants.d.ts +1 -1
  101. package/dist/utils/constants.js +1 -1
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  103. package/dist/utils/convertMessagesForAPI.js +7 -5
  104. package/dist/utils/customCommands.d.ts.map +1 -1
  105. package/dist/utils/customCommands.js +66 -21
  106. package/dist/utils/fileUtils.d.ts +15 -0
  107. package/dist/utils/fileUtils.d.ts.map +1 -0
  108. package/dist/utils/fileUtils.js +61 -0
  109. package/dist/utils/globalLogger.d.ts +102 -0
  110. package/dist/utils/globalLogger.d.ts.map +1 -0
  111. package/dist/utils/globalLogger.js +136 -0
  112. package/dist/utils/mcpUtils.d.ts.map +1 -1
  113. package/dist/utils/mcpUtils.js +25 -3
  114. package/dist/utils/messageOperations.d.ts +20 -8
  115. package/dist/utils/messageOperations.d.ts.map +1 -1
  116. package/dist/utils/messageOperations.js +25 -16
  117. package/dist/utils/pathEncoder.d.ts +104 -0
  118. package/dist/utils/pathEncoder.d.ts.map +1 -0
  119. package/dist/utils/pathEncoder.js +272 -0
  120. package/dist/utils/subagentParser.d.ts.map +1 -1
  121. package/dist/utils/subagentParser.js +2 -1
  122. package/dist/utils/tokenCalculation.d.ts +26 -0
  123. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  124. package/dist/utils/tokenCalculation.js +36 -0
  125. package/package.json +6 -3
  126. package/src/agent.ts +298 -34
  127. package/src/constants/events.ts +38 -0
  128. package/src/index.ts +2 -0
  129. package/src/managers/aiManager.ts +323 -170
  130. package/src/managers/backgroundBashManager.ts +7 -6
  131. package/src/managers/hookManager.ts +83 -40
  132. package/src/managers/liveConfigManager.ts +248 -0
  133. package/src/managers/messageManager.ts +230 -63
  134. package/src/managers/slashCommandManager.ts +4 -1
  135. package/src/managers/subagentManager.ts +283 -21
  136. package/src/services/aiService.ts +474 -83
  137. package/src/services/configurationWatcher.ts +622 -0
  138. package/src/services/fileWatcher.ts +301 -0
  139. package/src/services/hook.ts +538 -47
  140. package/src/services/jsonlHandler.ts +319 -0
  141. package/src/services/memory.ts +92 -12
  142. package/src/services/memoryStore.ts +279 -0
  143. package/src/services/session.ts +381 -157
  144. package/src/tools/bashTool.ts +5 -4
  145. package/src/tools/deleteFileTool.ts +2 -1
  146. package/src/tools/editTool.ts +3 -2
  147. package/src/tools/multiEditTool.ts +4 -3
  148. package/src/tools/readTool.ts +2 -1
  149. package/src/tools/writeTool.ts +7 -6
  150. package/src/types/commands.ts +6 -0
  151. package/src/types/core.ts +44 -0
  152. package/src/types/environment.ts +60 -0
  153. package/src/types/hooks.ts +21 -8
  154. package/src/types/index.ts +2 -0
  155. package/src/types/memoryStore.ts +94 -0
  156. package/src/types/messaging.ts +14 -2
  157. package/src/types/session.ts +25 -0
  158. package/src/utils/bashHistory.ts +27 -27
  159. package/src/utils/cacheControlUtils.ts +540 -0
  160. package/src/utils/commandPathResolver.ts +189 -0
  161. package/src/utils/configPaths.ts +163 -0
  162. package/src/utils/configResolver.ts +182 -22
  163. package/src/utils/constants.ts +1 -1
  164. package/src/utils/convertMessagesForAPI.ts +7 -5
  165. package/src/utils/customCommands.ts +90 -22
  166. package/src/utils/fileUtils.ts +65 -0
  167. package/src/utils/globalLogger.ts +145 -0
  168. package/src/utils/mcpUtils.ts +34 -3
  169. package/src/utils/messageOperations.ts +42 -20
  170. package/src/utils/pathEncoder.ts +379 -0
  171. package/src/utils/subagentParser.ts +2 -1
  172. package/src/utils/tokenCalculation.ts +43 -0
@@ -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);
@@ -194,7 +196,7 @@ export function convertMessagesForAPI(
194
196
  try {
195
197
  finalImageUrl = convertImageToBase64(imageUrl);
196
198
  } catch (error) {
197
- console.error(
199
+ logger.error(
198
200
  "Failed to convert image path to base64:",
199
201
  imageUrl,
200
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;
@@ -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",
@@ -1,7 +1,10 @@
1
- import type { Message, Usage, MessageSource } from "../types/index.js";
1
+ import type { Message, Usage } from "../types/index.js";
2
+ import { MessageSource } from "../types/index.js";
3
+ import type { SubagentConfiguration } from "./subagentParser.js";
2
4
  import { readFileSync } from "fs";
3
5
  import { extname } from "path";
4
6
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
7
+ import { logger } from "./globalLogger.js";
5
8
 
6
9
  // Base user message parameters interface
7
10
  export interface UserMessageParams {
@@ -23,11 +26,19 @@ export interface UpdateToolBlockParams {
23
26
  result?: string;
24
27
  success?: boolean;
25
28
  error?: string;
26
- isRunning?: boolean;
29
+ /**
30
+ * Tool execution stage:
31
+ * - 'start': Tool call initiated during AI streaming
32
+ * - 'streaming': Tool parameters being received incrementally
33
+ * - 'running': Tool execution in progress
34
+ * - 'end': Tool execution completed with final result
35
+ */
36
+ stage: "start" | "streaming" | "running" | "end";
27
37
  name?: string;
28
38
  shortResult?: string;
29
39
  images?: Array<{ data: string; mediaType?: string }>;
30
40
  compactParams?: string;
41
+ parametersChunk?: string; // Incremental parameter updates for streaming
31
42
  }
32
43
 
33
44
  // Agent specific interfaces (without messages parameter)
@@ -74,17 +85,18 @@ export interface CompleteCommandParams {
74
85
 
75
86
  /**
76
87
  * Extract text content from user messages in the messages array
88
+ * Excludes messages with source HOOK to prevent hook-generated content from entering user history
77
89
  */
78
90
  export const extractUserInputHistory = (messages: Message[]): string[] => {
79
91
  return messages
80
92
  .filter((message) => message.role === "user")
81
93
  .map((message) => {
82
- // Extract all text block content and merge
94
+ // Extract text block content, excluding HOOK-sourced blocks
83
95
  const textBlocks = message.blocks.filter(
84
- (block) => block.type === "text",
96
+ (block) => block.type === "text" && block.source !== MessageSource.HOOK,
85
97
  );
86
98
  return textBlocks
87
- .map((block) => block.content)
99
+ .map((block) => (block as { content: string }).content)
88
100
  .join(" ")
89
101
  .trim();
90
102
  })
@@ -126,8 +138,8 @@ export const convertImageToBase64 = (imagePath: string): string => {
126
138
 
127
139
  const base64String = imageBuffer.toString("base64");
128
140
  return `data:${mimeType};base64,${base64String}`;
129
- } catch {
130
- // logger.error(`Failed to convert image to base64: ${imagePath}`, error);
141
+ } catch (error) {
142
+ logger.error(`Failed to convert image to base64: ${imagePath}`, error);
131
143
  // Return an error placeholder or throw error
132
144
  return `data:image/png;base64,`; // Empty base64, avoid program crash
133
145
  }
@@ -174,6 +186,7 @@ export const addAssistantMessageToMessages = (
174
186
  content?: string,
175
187
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
176
188
  usage?: Usage,
189
+ metadata?: Record<string, unknown>,
177
190
  ): Message[] => {
178
191
  const blocks: Message["blocks"] = [];
179
192
 
@@ -191,7 +204,7 @@ export const addAssistantMessageToMessages = (
191
204
  result: "",
192
205
  id: toolCall.id || "",
193
206
  name: toolCall.function?.name || "",
194
- isRunning: false,
207
+ stage: "start",
195
208
  });
196
209
  });
197
210
  }
@@ -200,6 +213,7 @@ export const addAssistantMessageToMessages = (
200
213
  role: "assistant",
201
214
  blocks,
202
215
  usage, // Include usage data if provided
216
+ ...(metadata ? { metadata: { ...metadata } } : {}),
203
217
  };
204
218
 
205
219
  return [...messages, initialAssistantMessage];
@@ -235,11 +249,12 @@ export const updateToolBlockInMessage = ({
235
249
  result,
236
250
  success,
237
251
  error,
238
- isRunning,
252
+ stage,
239
253
  name,
240
254
  shortResult,
241
255
  images,
242
256
  compactParams,
257
+ parametersChunk,
243
258
  }: UpdateToolBlockParams): Message[] => {
244
259
  const newMessages = [...messages];
245
260
  // Find the last assistant message
@@ -258,24 +273,28 @@ export const updateToolBlockInMessage = ({
258
273
  toolBlock.images = images; // Add image data update
259
274
  if (success !== undefined) toolBlock.success = success;
260
275
  if (error !== undefined) toolBlock.error = error;
261
- if (isRunning !== undefined) toolBlock.isRunning = isRunning;
276
+ if (stage !== undefined) toolBlock.stage = stage;
262
277
  if (compactParams !== undefined)
263
278
  toolBlock.compactParams = compactParams;
279
+ if (parametersChunk !== undefined)
280
+ toolBlock.parametersChunk = parametersChunk;
264
281
  }
265
- } else if (result !== undefined) {
282
+ } else {
266
283
  // If existing block not found, create new one
284
+ // This handles cases where we're streaming tool parameters before execution
267
285
  newMessages[i].blocks.push({
268
286
  type: "tool",
269
287
  parameters: parameters,
270
- result: result,
288
+ result: result || "",
271
289
  shortResult: shortResult,
272
290
  images: images, // Add image data
273
291
  id: id,
274
292
  name: name || "unknown",
275
293
  success: success,
276
294
  error: error,
277
- isRunning: isRunning ?? false,
295
+ stage: stage ?? "start",
278
296
  compactParams: compactParams,
297
+ parametersChunk: parametersChunk,
279
298
  });
280
299
  }
281
300
  break;
@@ -508,14 +527,15 @@ export interface AddSubagentBlockParams {
508
527
  subagentId: string;
509
528
  subagentName: string;
510
529
  status: "active" | "completed" | "error" | "aborted";
511
- subagentMessages?: Message[];
530
+ sessionId: string;
531
+ configuration: SubagentConfiguration;
512
532
  }
513
533
 
514
534
  export interface UpdateSubagentBlockParams {
515
535
  messages: Message[];
516
536
  subagentId: string;
517
537
  status: "active" | "completed" | "error" | "aborted";
518
- subagentMessages: Message[];
538
+ sessionId?: string;
519
539
  }
520
540
 
521
541
  export const addSubagentBlockToMessage = ({
@@ -523,7 +543,8 @@ export const addSubagentBlockToMessage = ({
523
543
  subagentId,
524
544
  subagentName,
525
545
  status,
526
- subagentMessages = [],
546
+ sessionId,
547
+ configuration,
527
548
  }: AddSubagentBlockParams): Message[] => {
528
549
  const newMessages = [...messages];
529
550
 
@@ -545,7 +566,8 @@ export const addSubagentBlockToMessage = ({
545
566
  subagentId,
546
567
  subagentName,
547
568
  status,
548
- messages: subagentMessages,
569
+ sessionId,
570
+ configuration,
549
571
  });
550
572
 
551
573
  return newMessages;
@@ -556,7 +578,7 @@ export const updateSubagentBlockInMessage = (
556
578
  subagentId: string,
557
579
  updates: Partial<{
558
580
  status: "active" | "completed" | "error" | "aborted";
559
- messages: Message[];
581
+ sessionId: string;
560
582
  }>,
561
583
  ): Message[] => {
562
584
  const newMessages = [...messages];
@@ -570,8 +592,8 @@ export const updateSubagentBlockInMessage = (
570
592
  if (updates.status !== undefined) {
571
593
  block.status = updates.status;
572
594
  }
573
- if (updates.messages !== undefined) {
574
- block.messages = updates.messages;
595
+ if (updates.sessionId !== undefined) {
596
+ block.sessionId = updates.sessionId;
575
597
  }
576
598
  return newMessages;
577
599
  }