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.
- package/dist/agent.d.ts +32 -20
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +202 -20
- package/dist/constants/events.d.ts +28 -0
- package/dist/constants/events.d.ts.map +1 -0
- package/dist/constants/events.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/managers/aiManager.d.ts +34 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +243 -128
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +7 -6
- package/dist/managers/hookManager.d.ts +9 -4
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +62 -30
- package/dist/managers/liveConfigManager.d.ts +58 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +160 -0
- package/dist/managers/messageManager.d.ts +38 -13
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +163 -30
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +4 -1
- package/dist/managers/subagentManager.d.ts +51 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +189 -18
- package/dist/services/aiService.d.ts +13 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +350 -74
- package/dist/services/configurationWatcher.d.ts +120 -0
- package/dist/services/configurationWatcher.d.ts.map +1 -0
- package/dist/services/configurationWatcher.js +439 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +213 -0
- package/dist/services/hook.d.ts +91 -9
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +393 -43
- package/dist/services/jsonlHandler.d.ts +62 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +257 -0
- package/dist/services/memory.d.ts +9 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +81 -12
- package/dist/services/memoryStore.d.ts +81 -0
- package/dist/services/memoryStore.d.ts.map +1 -0
- package/dist/services/memoryStore.js +200 -0
- package/dist/services/session.d.ts +64 -49
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +310 -132
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +5 -4
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +2 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +3 -2
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +4 -3
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +2 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +5 -6
- package/dist/types/commands.d.ts +4 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/core.d.ts +35 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +42 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/hooks.d.ts +8 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/memoryStore.d.ts +82 -0
- package/dist/types/memoryStore.d.ts.map +1 -0
- package/dist/types/memoryStore.js +7 -0
- package/dist/types/messaging.d.ts +14 -2
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/session.d.ts +20 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +27 -26
- package/dist/utils/cacheControlUtils.d.ts +121 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +367 -0
- package/dist/utils/commandPathResolver.d.ts +52 -0
- package/dist/utils/commandPathResolver.d.ts.map +1 -0
- package/dist/utils/commandPathResolver.js +145 -0
- package/dist/utils/configPaths.d.ts +85 -0
- package/dist/utils/configPaths.d.ts.map +1 -0
- package/dist/utils/configPaths.js +121 -0
- package/dist/utils/configResolver.d.ts +37 -10
- package/dist/utils/configResolver.d.ts.map +1 -1
- package/dist/utils/configResolver.js +127 -23
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -5
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileUtils.d.ts +15 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +61 -0
- package/dist/utils/globalLogger.d.ts +102 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +136 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +20 -8
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +25 -16
- package/dist/utils/pathEncoder.d.ts +104 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +272 -0
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +2 -1
- package/dist/utils/tokenCalculation.d.ts +26 -0
- package/dist/utils/tokenCalculation.d.ts.map +1 -0
- package/dist/utils/tokenCalculation.js +36 -0
- package/package.json +6 -3
- package/src/agent.ts +298 -34
- package/src/constants/events.ts +38 -0
- package/src/index.ts +2 -0
- package/src/managers/aiManager.ts +323 -170
- package/src/managers/backgroundBashManager.ts +7 -6
- package/src/managers/hookManager.ts +83 -40
- package/src/managers/liveConfigManager.ts +248 -0
- package/src/managers/messageManager.ts +230 -63
- package/src/managers/slashCommandManager.ts +4 -1
- package/src/managers/subagentManager.ts +283 -21
- package/src/services/aiService.ts +474 -83
- package/src/services/configurationWatcher.ts +622 -0
- package/src/services/fileWatcher.ts +301 -0
- package/src/services/hook.ts +538 -47
- package/src/services/jsonlHandler.ts +319 -0
- package/src/services/memory.ts +92 -12
- package/src/services/memoryStore.ts +279 -0
- package/src/services/session.ts +381 -157
- package/src/tools/bashTool.ts +5 -4
- package/src/tools/deleteFileTool.ts +2 -1
- package/src/tools/editTool.ts +3 -2
- package/src/tools/multiEditTool.ts +4 -3
- package/src/tools/readTool.ts +2 -1
- package/src/tools/writeTool.ts +7 -6
- package/src/types/commands.ts +6 -0
- package/src/types/core.ts +44 -0
- package/src/types/environment.ts +60 -0
- package/src/types/hooks.ts +21 -8
- package/src/types/index.ts +2 -0
- package/src/types/memoryStore.ts +94 -0
- package/src/types/messaging.ts +14 -2
- package/src/types/session.ts +25 -0
- package/src/utils/bashHistory.ts +27 -27
- package/src/utils/cacheControlUtils.ts +540 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/configResolver.ts +182 -22
- package/src/utils/constants.ts +1 -1
- package/src/utils/convertMessagesForAPI.ts +7 -5
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileUtils.ts +65 -0
- package/src/utils/globalLogger.ts +145 -0
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +42 -20
- package/src/utils/pathEncoder.ts +379 -0
- package/src/utils/subagentParser.ts +2 -1
- 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
|
-
|
|
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.,
|
|
79
|
-
if (toolBlock.id &&
|
|
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
|
-
|
|
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(
|
|
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
|
|
60
|
+
const entries = readdirSync(currentPath);
|
|
33
61
|
|
|
34
|
-
for (const
|
|
35
|
-
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
62
|
+
for (const entryName of entries) {
|
|
63
|
+
const fullPath = join(currentPath, entryName);
|
|
38
64
|
|
|
39
|
-
|
|
40
|
-
|
|
65
|
+
let isDirectory = false;
|
|
66
|
+
let isFile = false;
|
|
41
67
|
|
|
42
68
|
try {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/src/utils/mcpUtils.ts
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
574
|
-
block.
|
|
595
|
+
if (updates.sessionId !== undefined) {
|
|
596
|
+
block.sessionId = updates.sessionId;
|
|
575
597
|
}
|
|
576
598
|
return newMessages;
|
|
577
599
|
}
|