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.
- package/dist/agent.d.ts +105 -24
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +438 -53
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/managers/aiManager.d.ts +18 -7
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +254 -142
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +11 -9
- package/dist/managers/hookManager.d.ts +6 -6
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +81 -39
- package/dist/managers/liveConfigManager.d.ts +95 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +442 -0
- package/dist/managers/lspManager.d.ts +43 -0
- package/dist/managers/lspManager.d.ts.map +1 -0
- package/dist/managers/lspManager.js +326 -0
- package/dist/managers/messageManager.d.ts +41 -24
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +184 -73
- package/dist/managers/permissionManager.d.ts +66 -0
- package/dist/managers/permissionManager.d.ts.map +1 -0
- package/dist/managers/permissionManager.js +208 -0
- package/dist/managers/skillManager.d.ts +1 -0
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +2 -1
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +4 -2
- package/dist/managers/subagentManager.d.ts +42 -6
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +213 -62
- package/dist/managers/toolManager.d.ts +38 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +66 -2
- package/dist/services/aiService.d.ts +15 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +446 -77
- package/dist/services/configurationService.d.ts +116 -0
- package/dist/services/configurationService.d.ts.map +1 -0
- package/dist/services/configurationService.js +585 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +212 -0
- package/dist/services/hook.d.ts +5 -40
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +47 -109
- package/dist/services/jsonlHandler.d.ts +71 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +236 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +33 -11
- package/dist/services/session.d.ts +116 -52
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +415 -143
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +77 -17
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +27 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +33 -8
- package/dist/tools/lspTool.d.ts +6 -0
- package/dist/tools/lspTool.d.ts.map +1 -0
- package/dist/tools/lspTool.js +589 -0
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +30 -10
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +113 -3
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +23 -0
- package/dist/tools/types.d.ts +11 -8
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +30 -15
- package/dist/types/commands.d.ts +4 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +69 -0
- package/dist/types/configuration.d.ts.map +1 -0
- package/dist/types/configuration.js +8 -0
- package/dist/types/core.d.ts +45 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +83 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/fileSearch.d.ts +5 -0
- package/dist/types/fileSearch.d.ts.map +1 -0
- package/dist/types/fileSearch.js +1 -0
- package/dist/types/hooks.d.ts +18 -3
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -8
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/lsp.d.ts +90 -0
- package/dist/types/lsp.d.ts.map +1 -0
- package/dist/types/lsp.js +4 -0
- package/dist/types/messaging.d.ts +19 -12
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/permissions.d.ts +35 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +12 -0
- package/dist/types/session.d.ts +15 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/types/skills.d.ts +1 -0
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/types/tools.d.ts +35 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +4 -0
- package/dist/utils/abortUtils.d.ts +34 -0
- package/dist/utils/abortUtils.d.ts.map +1 -0
- package/dist/utils/abortUtils.js +92 -0
- package/dist/utils/bashHistory.d.ts +4 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +48 -30
- package/dist/utils/builtinSubagents.d.ts +7 -0
- package/dist/utils/builtinSubagents.d.ts.map +1 -0
- package/dist/utils/builtinSubagents.js +65 -0
- package/dist/utils/cacheControlUtils.d.ts +96 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +324 -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/constants.d.ts +1 -13
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +2 -14
- package/dist/utils/convertMessagesForAPI.d.ts +2 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +39 -18
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileSearch.d.ts +14 -0
- package/dist/utils/fileSearch.d.ts.map +1 -0
- package/dist/utils/fileSearch.js +88 -0
- package/dist/utils/fileUtils.d.ts +27 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +145 -0
- package/dist/utils/globalLogger.d.ts +88 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +120 -0
- package/dist/utils/largeOutputHandler.d.ts +15 -0
- package/dist/utils/largeOutputHandler.d.ts.map +1 -0
- package/dist/utils/largeOutputHandler.js +40 -0
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +1 -17
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +20 -18
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +30 -38
- package/dist/utils/pathEncoder.d.ts +108 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +279 -0
- package/dist/utils/subagentParser.d.ts +2 -2
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +12 -8
- 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/dist/utils/tokenEstimator.d.ts +39 -0
- package/dist/utils/tokenEstimator.d.ts.map +1 -0
- package/dist/utils/tokenEstimator.js +55 -0
- package/package.json +6 -6
- package/src/agent.ts +586 -78
- package/src/index.ts +4 -0
- package/src/managers/aiManager.ts +341 -192
- package/src/managers/backgroundBashManager.ts +11 -9
- package/src/managers/hookManager.ts +102 -54
- package/src/managers/liveConfigManager.ts +634 -0
- package/src/managers/lspManager.ts +434 -0
- package/src/managers/messageManager.ts +258 -121
- package/src/managers/permissionManager.ts +276 -0
- package/src/managers/skillManager.ts +3 -1
- package/src/managers/slashCommandManager.ts +5 -3
- package/src/managers/subagentManager.ts +295 -76
- package/src/managers/toolManager.ts +95 -3
- package/src/services/aiService.ts +656 -84
- package/src/services/configurationService.ts +762 -0
- package/src/services/fileWatcher.ts +300 -0
- package/src/services/hook.ts +54 -144
- package/src/services/jsonlHandler.ts +303 -0
- package/src/services/memory.ts +34 -11
- package/src/services/session.ts +522 -173
- package/src/tools/bashTool.ts +94 -20
- package/src/tools/deleteFileTool.ts +38 -1
- package/src/tools/editTool.ts +44 -9
- package/src/tools/lspTool.ts +760 -0
- package/src/tools/multiEditTool.ts +41 -11
- package/src/tools/readTool.ts +127 -3
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/todoWriteTool.ts +33 -1
- package/src/tools/types.ts +15 -9
- package/src/tools/writeTool.ts +43 -16
- package/src/types/commands.ts +6 -1
- package/src/types/config.ts +5 -0
- package/src/types/configuration.ts +73 -0
- package/src/types/core.ts +55 -0
- package/src/types/environment.ts +104 -0
- package/src/types/fileSearch.ts +4 -0
- package/src/types/hooks.ts +32 -16
- package/src/types/index.ts +7 -0
- package/src/types/lsp.ts +96 -0
- package/src/types/messaging.ts +21 -14
- package/src/types/permissions.ts +48 -0
- package/src/types/session.ts +20 -0
- package/src/types/skills.ts +1 -0
- package/src/types/tools.ts +38 -0
- package/src/utils/abortUtils.ts +118 -0
- package/src/utils/bashHistory.ts +55 -31
- package/src/utils/builtinSubagents.ts +71 -0
- package/src/utils/cacheControlUtils.ts +475 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/constants.ts +2 -17
- package/src/utils/convertMessagesForAPI.ts +44 -18
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileSearch.ts +107 -0
- package/src/utils/fileUtils.ts +160 -0
- package/src/utils/globalLogger.ts +128 -0
- package/src/utils/largeOutputHandler.ts +55 -0
- package/src/utils/markdownParser.ts +1 -19
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +47 -53
- package/src/utils/pathEncoder.ts +394 -0
- package/src/utils/subagentParser.ts +13 -9
- package/src/utils/tokenCalculation.ts +43 -0
- package/src/utils/tokenEstimator.ts +68 -0
- package/dist/utils/configResolver.d.ts +0 -38
- package/dist/utils/configResolver.d.ts.map +0 -1
- package/dist/utils/configResolver.js +0 -106
- package/src/utils/configResolver.ts +0 -142
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Reads the first line of a file efficiently using Node.js readline.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} filePath - The path to the file.
|
|
11
|
+
* @return {Promise<string>} - The first non-empty line of the file, or an empty string otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export async function readFirstLine(filePath: string): Promise<string> {
|
|
14
|
+
const { createInterface } = await import("node:readline");
|
|
15
|
+
|
|
16
|
+
const fileStream = createReadStream(filePath);
|
|
17
|
+
const rl = createInterface({
|
|
18
|
+
input: fileStream,
|
|
19
|
+
crlfDelay: Infinity, // Handle \r\n properly
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
for await (const line of rl) {
|
|
24
|
+
const trimmedLine = line.trim();
|
|
25
|
+
if (trimmedLine.length > 0) {
|
|
26
|
+
return trimmedLine;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return "";
|
|
30
|
+
} catch {
|
|
31
|
+
// If reading fails (e.g., file doesn't exist), return empty string
|
|
32
|
+
return "";
|
|
33
|
+
} finally {
|
|
34
|
+
rl.close();
|
|
35
|
+
fileStream.destroy();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reads a file from the end and returns the last non-empty line.
|
|
41
|
+
*
|
|
42
|
+
* This version supports files that end with:
|
|
43
|
+
* - "\n" (Unix-style, including modern macOS)
|
|
44
|
+
* - "\r\n" (Windows-style)
|
|
45
|
+
* - "\r" (older Mac-style, HL7, etc.)
|
|
46
|
+
*
|
|
47
|
+
* @param {string} filePath - The path to the file.
|
|
48
|
+
* @param {number} [minLength=1] - Minimum length for the returned line.
|
|
49
|
+
* @return {Promise<string>} - The last non-empty line of the file, or an empty string if no non-empty lines found.
|
|
50
|
+
*/
|
|
51
|
+
export async function getLastLine(
|
|
52
|
+
filePath: string,
|
|
53
|
+
minLength = 1,
|
|
54
|
+
): Promise<string> {
|
|
55
|
+
let fileHandle;
|
|
56
|
+
try {
|
|
57
|
+
const stats = await fs.stat(filePath);
|
|
58
|
+
const fileSize = stats.size;
|
|
59
|
+
|
|
60
|
+
if (fileSize === 0) return "";
|
|
61
|
+
|
|
62
|
+
fileHandle = await fs.open(filePath, "r");
|
|
63
|
+
const bufferSize = 8 * 1024; // 8KB buffer is usually enough for the last line
|
|
64
|
+
const buffer = Buffer.alloc(bufferSize);
|
|
65
|
+
|
|
66
|
+
let lineEnd: number | null = null;
|
|
67
|
+
let lineStart: number | null = null;
|
|
68
|
+
let currentPosition = fileSize;
|
|
69
|
+
|
|
70
|
+
while (currentPosition > 0 && lineStart === null) {
|
|
71
|
+
const readSize = Math.min(bufferSize, currentPosition);
|
|
72
|
+
currentPosition -= readSize;
|
|
73
|
+
|
|
74
|
+
const { bytesRead } = await fileHandle.read(
|
|
75
|
+
buffer,
|
|
76
|
+
0,
|
|
77
|
+
readSize,
|
|
78
|
+
currentPosition,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
for (let i = bytesRead - 1; i >= 0; i--) {
|
|
82
|
+
const charCode = buffer[i];
|
|
83
|
+
if (lineEnd === null) {
|
|
84
|
+
// Still looking for the end of the last non-empty line (skip trailing newlines and whitespace)
|
|
85
|
+
if (charCode > 32) {
|
|
86
|
+
lineEnd = currentPosition + i + 1;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Looking for the start of the line (the newline before it)
|
|
90
|
+
if (charCode === 10 || charCode === 13) {
|
|
91
|
+
lineStart = currentPosition + i + 1;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (lineEnd === null) return "";
|
|
99
|
+
if (lineStart === null) lineStart = 0;
|
|
100
|
+
|
|
101
|
+
const length = lineEnd - lineStart;
|
|
102
|
+
if (length < minLength) return "";
|
|
103
|
+
|
|
104
|
+
const resultBuffer = Buffer.alloc(length);
|
|
105
|
+
await fileHandle.read(resultBuffer, 0, length, lineStart);
|
|
106
|
+
const result = resultBuffer.toString("utf8").trim();
|
|
107
|
+
return result.length >= minLength ? result : "";
|
|
108
|
+
} catch {
|
|
109
|
+
// If reading fails (e.g., file doesn't exist), return empty string
|
|
110
|
+
return "";
|
|
111
|
+
} finally {
|
|
112
|
+
if (fileHandle) {
|
|
113
|
+
await fileHandle.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Ensures that a pattern is present in the global git ignore file.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} pattern - The pattern to add to global git ignore.
|
|
122
|
+
*/
|
|
123
|
+
export async function ensureGlobalGitIgnore(pattern: string): Promise<void> {
|
|
124
|
+
try {
|
|
125
|
+
let globalIgnorePath: string;
|
|
126
|
+
try {
|
|
127
|
+
globalIgnorePath = execSync("git config --get core.excludesfile", {
|
|
128
|
+
encoding: "utf8",
|
|
129
|
+
}).trim();
|
|
130
|
+
} catch {
|
|
131
|
+
// If not set, use default paths
|
|
132
|
+
const xdgConfigHome =
|
|
133
|
+
process.env.XDG_CONFIG_HOME || path.join(homedir(), ".config");
|
|
134
|
+
globalIgnorePath = path.join(xdgConfigHome, "git", "ignore");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!globalIgnorePath) return;
|
|
138
|
+
|
|
139
|
+
// Ensure directory exists
|
|
140
|
+
await fs.mkdir(path.dirname(globalIgnorePath), { recursive: true });
|
|
141
|
+
|
|
142
|
+
let content = "";
|
|
143
|
+
try {
|
|
144
|
+
content = await fs.readFile(globalIgnorePath, "utf8");
|
|
145
|
+
} catch {
|
|
146
|
+
// File doesn't exist
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const lines = content.split("\n").map((line) => line.trim());
|
|
150
|
+
if (!lines.includes(pattern)) {
|
|
151
|
+
const newContent =
|
|
152
|
+
content.endsWith("\n") || content === ""
|
|
153
|
+
? `${content}${pattern}\n`
|
|
154
|
+
: `${content}\n${pattern}\n`;
|
|
155
|
+
await fs.writeFile(globalIgnorePath, newContent, "utf8");
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// Ignore errors
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
* Reset global logger to unconfigured state
|
|
51
|
+
* Equivalent to setGlobalLogger(null)
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* clearGlobalLogger(); // All subsequent logging calls become no-ops
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function clearGlobalLogger(): void {
|
|
59
|
+
globalLogger = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if global logger is currently configured
|
|
64
|
+
*
|
|
65
|
+
* @returns true if logger configured, false otherwise
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* if (isLoggerConfigured()) {
|
|
70
|
+
* // Perform expensive logging operation
|
|
71
|
+
* const debugInfo = generateDebugInfo();
|
|
72
|
+
* logger.debug(debugInfo);
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function isLoggerConfigured(): boolean {
|
|
77
|
+
return globalLogger !== null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Zero-Overhead Logging API
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Zero-overhead logging interface
|
|
86
|
+
*
|
|
87
|
+
* Performance characteristics:
|
|
88
|
+
* - Unconfigured: Single null check + early return (near-zero cost)
|
|
89
|
+
* - Configured: Null check + function delegation
|
|
90
|
+
* - No object creation or intermediate allocations
|
|
91
|
+
*/
|
|
92
|
+
export const logger = {
|
|
93
|
+
/**
|
|
94
|
+
* Log debug-level message through global logger
|
|
95
|
+
* No-op when global logger is null (zero overhead)
|
|
96
|
+
*/
|
|
97
|
+
debug: (...args: unknown[]): void => {
|
|
98
|
+
if (globalLogger === null) return;
|
|
99
|
+
globalLogger.debug(...args);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Log info-level message through global logger
|
|
104
|
+
* No-op when global logger is null (zero overhead)
|
|
105
|
+
*/
|
|
106
|
+
info: (...args: unknown[]): void => {
|
|
107
|
+
if (globalLogger === null) return;
|
|
108
|
+
globalLogger.info(...args);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Log warning-level message through global logger
|
|
113
|
+
* No-op when global logger is null (zero overhead)
|
|
114
|
+
*/
|
|
115
|
+
warn: (...args: unknown[]): void => {
|
|
116
|
+
if (globalLogger === null) return;
|
|
117
|
+
globalLogger.warn(...args);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Log error-level message through global logger
|
|
122
|
+
* No-op when global logger is null (zero overhead)
|
|
123
|
+
*/
|
|
124
|
+
error: (...args: unknown[]): void => {
|
|
125
|
+
if (globalLogger === null) return;
|
|
126
|
+
globalLogger.error(...args);
|
|
127
|
+
},
|
|
128
|
+
} as const;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { writeFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { logger } from "./globalLogger.js";
|
|
5
|
+
import {
|
|
6
|
+
estimateTokenCount,
|
|
7
|
+
getTokenUsageDescription,
|
|
8
|
+
} from "./tokenEstimator.js";
|
|
9
|
+
|
|
10
|
+
// Token threshold for writing output to temp file (20k tokens)
|
|
11
|
+
export const LARGE_OUTPUT_TOKEN_THRESHOLD = 20000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle large command output by writing to temporary file when token threshold is exceeded
|
|
15
|
+
*
|
|
16
|
+
* Uses token-based threshold (20k tokens) to determine when output should be written to temp file.
|
|
17
|
+
* This provides accurate estimation of actual token cost for LLM processing.
|
|
18
|
+
*
|
|
19
|
+
* @param output - The command output string
|
|
20
|
+
* @returns Object containing processed content and optional file path
|
|
21
|
+
*/
|
|
22
|
+
export async function handleLargeOutput(output: string): Promise<{
|
|
23
|
+
content: string;
|
|
24
|
+
filePath?: string;
|
|
25
|
+
}> {
|
|
26
|
+
const estimatedTokens = estimateTokenCount(output);
|
|
27
|
+
|
|
28
|
+
// Check token threshold
|
|
29
|
+
if (estimatedTokens <= LARGE_OUTPUT_TOKEN_THRESHOLD) {
|
|
30
|
+
return { content: output };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Create temp file for large output
|
|
35
|
+
const tempFileName = `bash-output-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.txt`;
|
|
36
|
+
const tempFilePath = join(tmpdir(), tempFileName);
|
|
37
|
+
|
|
38
|
+
await writeFile(tempFilePath, output, "utf8");
|
|
39
|
+
|
|
40
|
+
const sizeKB = Math.round(output.length / 1024);
|
|
41
|
+
const tokenDescription = getTokenUsageDescription(
|
|
42
|
+
output,
|
|
43
|
+
LARGE_OUTPUT_TOKEN_THRESHOLD,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
content: `Large output (${sizeKB} KB, ${tokenDescription}) written to temporary file. Use the Read tool to access the full content.`,
|
|
48
|
+
filePath: tempFilePath,
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.warn(`Failed to write large output to temp file: ${error}`);
|
|
52
|
+
// Fallback to direct output if temp file creation fails
|
|
53
|
+
return { content: output };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -37,18 +37,7 @@ function parseFrontmatter(content: string): {
|
|
|
37
37
|
const key = trimmedLine.slice(0, colonIndex).trim();
|
|
38
38
|
const value = trimmedLine.slice(colonIndex + 1).trim();
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
if (key === "allowed-tools" && value) {
|
|
42
|
-
// Simple array parsing: "tool1, tool2, tool3" or "[tool1, tool2]"
|
|
43
|
-
let arrayValue = value;
|
|
44
|
-
if (arrayValue.startsWith("[") && arrayValue.endsWith("]")) {
|
|
45
|
-
arrayValue = arrayValue.slice(1, -1);
|
|
46
|
-
}
|
|
47
|
-
frontmatter[key] = arrayValue
|
|
48
|
-
.split(",")
|
|
49
|
-
.map((s) => s.trim())
|
|
50
|
-
.filter(Boolean);
|
|
51
|
-
} else if (value) {
|
|
40
|
+
if (value) {
|
|
52
41
|
frontmatter[key] = value;
|
|
53
42
|
}
|
|
54
43
|
}
|
|
@@ -73,13 +62,6 @@ export function parseMarkdownFile(filePath: string): ParsedMarkdownFile {
|
|
|
73
62
|
if (frontmatter) {
|
|
74
63
|
config = {};
|
|
75
64
|
|
|
76
|
-
if (
|
|
77
|
-
frontmatter["allowed-tools"] &&
|
|
78
|
-
Array.isArray(frontmatter["allowed-tools"])
|
|
79
|
-
) {
|
|
80
|
-
config.allowedTools = frontmatter["allowed-tools"] as string[];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
65
|
if (frontmatter.model && typeof frontmatter.model === "string") {
|
|
84
66
|
config.model = frontmatter.model;
|
|
85
67
|
}
|
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)
|
|
@@ -36,12 +47,6 @@ export type AgentToolBlockUpdateParams = Omit<
|
|
|
36
47
|
"messages"
|
|
37
48
|
>;
|
|
38
49
|
|
|
39
|
-
export interface AddDiffBlockParams {
|
|
40
|
-
messages: Message[];
|
|
41
|
-
path: string;
|
|
42
|
-
diffResult: Array<{ value: string; added?: boolean; removed?: boolean }>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
50
|
export interface AddErrorBlockParams {
|
|
46
51
|
messages: Message[];
|
|
47
52
|
error: string;
|
|
@@ -74,17 +79,18 @@ export interface CompleteCommandParams {
|
|
|
74
79
|
|
|
75
80
|
/**
|
|
76
81
|
* Extract text content from user messages in the messages array
|
|
82
|
+
* Excludes messages with source HOOK to prevent hook-generated content from entering user history
|
|
77
83
|
*/
|
|
78
84
|
export const extractUserInputHistory = (messages: Message[]): string[] => {
|
|
79
85
|
return messages
|
|
80
86
|
.filter((message) => message.role === "user")
|
|
81
87
|
.map((message) => {
|
|
82
|
-
// Extract
|
|
88
|
+
// Extract text block content, excluding HOOK-sourced blocks
|
|
83
89
|
const textBlocks = message.blocks.filter(
|
|
84
|
-
(block) => block.type === "text",
|
|
90
|
+
(block) => block.type === "text" && block.source !== MessageSource.HOOK,
|
|
85
91
|
);
|
|
86
92
|
return textBlocks
|
|
87
|
-
.map((block) => block.content)
|
|
93
|
+
.map((block) => (block as { content: string }).content)
|
|
88
94
|
.join(" ")
|
|
89
95
|
.trim();
|
|
90
96
|
})
|
|
@@ -126,8 +132,8 @@ export const convertImageToBase64 = (imagePath: string): string => {
|
|
|
126
132
|
|
|
127
133
|
const base64String = imageBuffer.toString("base64");
|
|
128
134
|
return `data:${mimeType};base64,${base64String}`;
|
|
129
|
-
} catch {
|
|
130
|
-
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.error(`Failed to convert image to base64: ${imagePath}`, error);
|
|
131
137
|
// Return an error placeholder or throw error
|
|
132
138
|
return `data:image/png;base64,`; // Empty base64, avoid program crash
|
|
133
139
|
}
|
|
@@ -174,6 +180,7 @@ export const addAssistantMessageToMessages = (
|
|
|
174
180
|
content?: string,
|
|
175
181
|
toolCalls?: ChatCompletionMessageFunctionToolCall[],
|
|
176
182
|
usage?: Usage,
|
|
183
|
+
additionalFields?: Record<string, unknown>,
|
|
177
184
|
): Message[] => {
|
|
178
185
|
const blocks: Message["blocks"] = [];
|
|
179
186
|
|
|
@@ -191,7 +198,7 @@ export const addAssistantMessageToMessages = (
|
|
|
191
198
|
result: "",
|
|
192
199
|
id: toolCall.id || "",
|
|
193
200
|
name: toolCall.function?.name || "",
|
|
194
|
-
|
|
201
|
+
stage: "start",
|
|
195
202
|
});
|
|
196
203
|
});
|
|
197
204
|
}
|
|
@@ -200,33 +207,12 @@ export const addAssistantMessageToMessages = (
|
|
|
200
207
|
role: "assistant",
|
|
201
208
|
blocks,
|
|
202
209
|
usage, // Include usage data if provided
|
|
210
|
+
...(additionalFields ? { additionalFields: { ...additionalFields } } : {}),
|
|
203
211
|
};
|
|
204
212
|
|
|
205
213
|
return [...messages, initialAssistantMessage];
|
|
206
214
|
};
|
|
207
215
|
|
|
208
|
-
// Update File Operation Block of the last assistant message
|
|
209
|
-
export const addDiffBlockToMessage = ({
|
|
210
|
-
messages,
|
|
211
|
-
path,
|
|
212
|
-
diffResult,
|
|
213
|
-
}: AddDiffBlockParams): Message[] => {
|
|
214
|
-
const newMessages = [...messages];
|
|
215
|
-
// Find the last assistant message
|
|
216
|
-
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
217
|
-
if (newMessages[i].role === "assistant") {
|
|
218
|
-
// Directly add diff block instead of replacing existing blocks
|
|
219
|
-
newMessages[i].blocks.push({
|
|
220
|
-
type: "diff",
|
|
221
|
-
path: path,
|
|
222
|
-
diffResult: diffResult,
|
|
223
|
-
});
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return newMessages;
|
|
228
|
-
};
|
|
229
|
-
|
|
230
216
|
// Update Tool Block of the last assistant message
|
|
231
217
|
export const updateToolBlockInMessage = ({
|
|
232
218
|
messages,
|
|
@@ -235,11 +221,12 @@ export const updateToolBlockInMessage = ({
|
|
|
235
221
|
result,
|
|
236
222
|
success,
|
|
237
223
|
error,
|
|
238
|
-
|
|
224
|
+
stage,
|
|
239
225
|
name,
|
|
240
226
|
shortResult,
|
|
241
227
|
images,
|
|
242
228
|
compactParams,
|
|
229
|
+
parametersChunk,
|
|
243
230
|
}: UpdateToolBlockParams): Message[] => {
|
|
244
231
|
const newMessages = [...messages];
|
|
245
232
|
// Find the last assistant message
|
|
@@ -258,24 +245,28 @@ export const updateToolBlockInMessage = ({
|
|
|
258
245
|
toolBlock.images = images; // Add image data update
|
|
259
246
|
if (success !== undefined) toolBlock.success = success;
|
|
260
247
|
if (error !== undefined) toolBlock.error = error;
|
|
261
|
-
if (
|
|
248
|
+
if (stage !== undefined) toolBlock.stage = stage;
|
|
262
249
|
if (compactParams !== undefined)
|
|
263
250
|
toolBlock.compactParams = compactParams;
|
|
251
|
+
if (parametersChunk !== undefined)
|
|
252
|
+
toolBlock.parametersChunk = parametersChunk;
|
|
264
253
|
}
|
|
265
|
-
} else
|
|
254
|
+
} else {
|
|
266
255
|
// If existing block not found, create new one
|
|
256
|
+
// This handles cases where we're streaming tool parameters before execution
|
|
267
257
|
newMessages[i].blocks.push({
|
|
268
258
|
type: "tool",
|
|
269
259
|
parameters: parameters,
|
|
270
|
-
result: result,
|
|
260
|
+
result: result || "",
|
|
271
261
|
shortResult: shortResult,
|
|
272
262
|
images: images, // Add image data
|
|
273
263
|
id: id,
|
|
274
264
|
name: name || "unknown",
|
|
275
265
|
success: success,
|
|
276
266
|
error: error,
|
|
277
|
-
|
|
267
|
+
stage: stage ?? "start",
|
|
278
268
|
compactParams: compactParams,
|
|
269
|
+
parametersChunk: parametersChunk,
|
|
279
270
|
});
|
|
280
271
|
}
|
|
281
272
|
break;
|
|
@@ -434,7 +425,7 @@ export const addCommandOutputMessage = ({
|
|
|
434
425
|
command,
|
|
435
426
|
}: AddCommandOutputParams): Message[] => {
|
|
436
427
|
const outputMessage: Message = {
|
|
437
|
-
role: "
|
|
428
|
+
role: "user",
|
|
438
429
|
blocks: [
|
|
439
430
|
{
|
|
440
431
|
type: "command_output",
|
|
@@ -456,10 +447,10 @@ export const updateCommandOutputInMessage = ({
|
|
|
456
447
|
output,
|
|
457
448
|
}: UpdateCommandOutputParams): Message[] => {
|
|
458
449
|
const newMessages = [...messages];
|
|
459
|
-
// Find the last
|
|
450
|
+
// Find the last user message with a command_output block for this command
|
|
460
451
|
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
461
452
|
const msg = newMessages[i];
|
|
462
|
-
if (msg.role === "
|
|
453
|
+
if (msg.role === "user") {
|
|
463
454
|
const commandBlock = msg.blocks.find(
|
|
464
455
|
(block) =>
|
|
465
456
|
block.type === "command_output" &&
|
|
@@ -482,10 +473,10 @@ export const completeCommandInMessage = ({
|
|
|
482
473
|
exitCode,
|
|
483
474
|
}: CompleteCommandParams): Message[] => {
|
|
484
475
|
const newMessages = [...messages];
|
|
485
|
-
// Find the last
|
|
476
|
+
// Find the last user message with a command_output block for this command
|
|
486
477
|
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
487
478
|
const msg = newMessages[i];
|
|
488
|
-
if (msg.role === "
|
|
479
|
+
if (msg.role === "user") {
|
|
489
480
|
const commandBlock = msg.blocks.find(
|
|
490
481
|
(block) =>
|
|
491
482
|
block.type === "command_output" &&
|
|
@@ -508,14 +499,15 @@ export interface AddSubagentBlockParams {
|
|
|
508
499
|
subagentId: string;
|
|
509
500
|
subagentName: string;
|
|
510
501
|
status: "active" | "completed" | "error" | "aborted";
|
|
511
|
-
|
|
502
|
+
sessionId: string;
|
|
503
|
+
configuration: SubagentConfiguration;
|
|
512
504
|
}
|
|
513
505
|
|
|
514
506
|
export interface UpdateSubagentBlockParams {
|
|
515
507
|
messages: Message[];
|
|
516
508
|
subagentId: string;
|
|
517
509
|
status: "active" | "completed" | "error" | "aborted";
|
|
518
|
-
|
|
510
|
+
sessionId?: string;
|
|
519
511
|
}
|
|
520
512
|
|
|
521
513
|
export const addSubagentBlockToMessage = ({
|
|
@@ -523,7 +515,8 @@ export const addSubagentBlockToMessage = ({
|
|
|
523
515
|
subagentId,
|
|
524
516
|
subagentName,
|
|
525
517
|
status,
|
|
526
|
-
|
|
518
|
+
sessionId,
|
|
519
|
+
configuration,
|
|
527
520
|
}: AddSubagentBlockParams): Message[] => {
|
|
528
521
|
const newMessages = [...messages];
|
|
529
522
|
|
|
@@ -545,7 +538,8 @@ export const addSubagentBlockToMessage = ({
|
|
|
545
538
|
subagentId,
|
|
546
539
|
subagentName,
|
|
547
540
|
status,
|
|
548
|
-
|
|
541
|
+
sessionId,
|
|
542
|
+
configuration,
|
|
549
543
|
});
|
|
550
544
|
|
|
551
545
|
return newMessages;
|
|
@@ -556,7 +550,7 @@ export const updateSubagentBlockInMessage = (
|
|
|
556
550
|
subagentId: string,
|
|
557
551
|
updates: Partial<{
|
|
558
552
|
status: "active" | "completed" | "error" | "aborted";
|
|
559
|
-
|
|
553
|
+
sessionId: string;
|
|
560
554
|
}>,
|
|
561
555
|
): Message[] => {
|
|
562
556
|
const newMessages = [...messages];
|
|
@@ -570,8 +564,8 @@ export const updateSubagentBlockInMessage = (
|
|
|
570
564
|
if (updates.status !== undefined) {
|
|
571
565
|
block.status = updates.status;
|
|
572
566
|
}
|
|
573
|
-
if (updates.
|
|
574
|
-
block.
|
|
567
|
+
if (updates.sessionId !== undefined) {
|
|
568
|
+
block.sessionId = updates.sessionId;
|
|
575
569
|
}
|
|
576
570
|
return newMessages;
|
|
577
571
|
}
|