wave-agent-sdk 0.11.6 → 0.11.7
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/builtin/skills/init/SKILL.md +2 -0
- package/builtin/skills/settings/SKILLS.md +3 -2
- package/builtin/skills/settings/SUBAGENTS.md +1 -3
- package/dist/agent.d.ts +6 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +18 -1
- package/dist/constants/tools.d.ts +1 -1
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +1 -1
- package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
- package/dist/managers/MemoryRuleManager.js +1 -9
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +22 -3
- package/dist/managers/messageManager.d.ts +13 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +62 -34
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +4 -2
- package/dist/managers/slashCommandManager.d.ts +2 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +98 -4
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +8 -2
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -0
- package/dist/services/GitService.d.ts +1 -0
- package/dist/services/GitService.d.ts.map +1 -1
- package/dist/services/GitService.js +16 -0
- package/dist/services/MarketplaceService.d.ts +7 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +321 -252
- package/dist/services/aiService.d.ts +34 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +124 -1
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +18 -0
- package/dist/tools/agentTool.js +3 -3
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +4 -4
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +2 -0
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +15 -3
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +38 -12
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +61 -0
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/types.d.ts +16 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts +3 -0
- package/dist/tools/webFetchTool.d.ts.map +1 -0
- package/dist/tools/webFetchTool.js +171 -0
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +2 -0
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/bashParser.d.ts +14 -0
- package/dist/utils/bashParser.d.ts.map +1 -1
- package/dist/utils/bashParser.js +243 -142
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -0
- package/dist/utils/fileUtils.d.ts +8 -0
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +52 -0
- package/dist/utils/messageOperations.d.ts +12 -3
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +77 -9
- package/package.json +4 -2
- package/src/agent.ts +19 -1
- package/src/constants/tools.ts +1 -1
- package/src/managers/MemoryRuleManager.ts +1 -10
- package/src/managers/aiManager.ts +23 -3
- package/src/managers/messageManager.ts +76 -38
- package/src/managers/pluginManager.ts +4 -2
- package/src/managers/slashCommandManager.ts +130 -4
- package/src/managers/toolManager.ts +11 -2
- package/src/prompts/index.ts +6 -0
- package/src/services/GitService.ts +20 -0
- package/src/services/MarketplaceService.ts +397 -324
- package/src/services/aiService.ts +197 -1
- package/src/services/initializationService.ts +38 -0
- package/src/tools/agentTool.ts +3 -3
- package/src/tools/bashTool.ts +3 -4
- package/src/tools/editTool.ts +3 -0
- package/src/tools/globTool.ts +16 -3
- package/src/tools/grepTool.ts +41 -13
- package/src/tools/readTool.ts +69 -0
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/types.ts +13 -0
- package/src/tools/webFetchTool.ts +194 -0
- package/src/tools/writeTool.ts +3 -0
- package/src/types/commands.ts +1 -1
- package/src/types/messaging.ts +1 -0
- package/src/utils/bashParser.ts +268 -157
- package/src/utils/convertMessagesForAPI.ts +8 -0
- package/src/utils/fileUtils.ts +69 -0
- package/src/utils/messageOperations.ts +84 -9
- package/dist/tools/taskOutputTool.d.ts +0 -3
- package/dist/tools/taskOutputTool.d.ts.map +0 -1
- package/dist/tools/taskOutputTool.js +0 -198
- package/src/tools/taskOutputTool.ts +0 -222
|
@@ -22,7 +22,11 @@ import * as os from "os";
|
|
|
22
22
|
import * as fs from "fs";
|
|
23
23
|
import * as path from "path";
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
COMPRESS_MESSAGES_SYSTEM_PROMPT,
|
|
27
|
+
WEB_CONTENT_SYSTEM_PROMPT,
|
|
28
|
+
BTW_SYSTEM_PROMPT,
|
|
29
|
+
} from "../prompts/index.js";
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Interface for debug data saved during 400 errors
|
|
@@ -829,3 +833,195 @@ export async function compressMessages(
|
|
|
829
833
|
throw error;
|
|
830
834
|
}
|
|
831
835
|
}
|
|
836
|
+
|
|
837
|
+
export interface ProcessWebContentOptions {
|
|
838
|
+
// Resolved configuration
|
|
839
|
+
gatewayConfig: GatewayConfig;
|
|
840
|
+
modelConfig: ModelConfig;
|
|
841
|
+
|
|
842
|
+
// Parameters
|
|
843
|
+
content: string;
|
|
844
|
+
prompt: string;
|
|
845
|
+
abortSignal?: AbortSignal;
|
|
846
|
+
model?: string;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export interface ProcessWebContentResult {
|
|
850
|
+
content: string;
|
|
851
|
+
usage?: {
|
|
852
|
+
prompt_tokens: number;
|
|
853
|
+
completion_tokens: number;
|
|
854
|
+
total_tokens: number;
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export async function processWebContent(
|
|
859
|
+
options: ProcessWebContentOptions,
|
|
860
|
+
): Promise<ProcessWebContentResult> {
|
|
861
|
+
const { gatewayConfig, modelConfig, content, prompt, abortSignal } = options;
|
|
862
|
+
|
|
863
|
+
// Apply global 1 QPS rate limit
|
|
864
|
+
if (
|
|
865
|
+
process.env.NODE_ENV !== "test" ||
|
|
866
|
+
modelConfig.model === "rate-limit-test"
|
|
867
|
+
) {
|
|
868
|
+
await acquireSlot(abortSignal);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Create OpenAI client with injected configuration
|
|
872
|
+
const openai = new OpenAIClient({
|
|
873
|
+
apiKey: gatewayConfig.apiKey,
|
|
874
|
+
baseURL: gatewayConfig.baseURL,
|
|
875
|
+
defaultHeaders: gatewayConfig.defaultHeaders,
|
|
876
|
+
fetchOptions: gatewayConfig.fetchOptions,
|
|
877
|
+
fetch: gatewayConfig.fetch,
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// Get model configuration - use injected agent model
|
|
881
|
+
const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
|
|
882
|
+
temperature: 0.1,
|
|
883
|
+
max_tokens: 4096,
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
try {
|
|
887
|
+
const response = await openai.chat.completions.create(
|
|
888
|
+
{
|
|
889
|
+
...openaiModelConfig,
|
|
890
|
+
messages: [
|
|
891
|
+
{
|
|
892
|
+
role: "system",
|
|
893
|
+
content: WEB_CONTENT_SYSTEM_PROMPT,
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
role: "user",
|
|
897
|
+
content: `Web Content:\n\n${content}\n\nUser Prompt: ${prompt}`,
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
signal: abortSignal,
|
|
903
|
+
},
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
const result = response.choices[0]?.message?.content?.trim();
|
|
907
|
+
if (!result) {
|
|
908
|
+
throw new Error("Failed to process web content: Empty response from AI");
|
|
909
|
+
}
|
|
910
|
+
const usage = response.usage
|
|
911
|
+
? {
|
|
912
|
+
prompt_tokens: response.usage.prompt_tokens,
|
|
913
|
+
completion_tokens: response.usage.completion_tokens,
|
|
914
|
+
total_tokens: response.usage.total_tokens,
|
|
915
|
+
}
|
|
916
|
+
: undefined;
|
|
917
|
+
|
|
918
|
+
return {
|
|
919
|
+
content: result,
|
|
920
|
+
usage,
|
|
921
|
+
};
|
|
922
|
+
} catch (error) {
|
|
923
|
+
if ((error as Error).name === "AbortError") {
|
|
924
|
+
logger.info("Web content processing request was aborted");
|
|
925
|
+
throw new Error("Web content processing request was aborted");
|
|
926
|
+
}
|
|
927
|
+
logger.error("Failed to process web content:", error);
|
|
928
|
+
throw error;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export interface BtwOptions {
|
|
933
|
+
// Resolved configuration
|
|
934
|
+
gatewayConfig: GatewayConfig;
|
|
935
|
+
modelConfig: ModelConfig;
|
|
936
|
+
|
|
937
|
+
// Parameters
|
|
938
|
+
messages: ChatCompletionMessageParam[];
|
|
939
|
+
question: string;
|
|
940
|
+
abortSignal?: AbortSignal;
|
|
941
|
+
model?: string;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
export interface BtwResult {
|
|
945
|
+
content: string;
|
|
946
|
+
usage?: {
|
|
947
|
+
prompt_tokens: number;
|
|
948
|
+
completion_tokens: number;
|
|
949
|
+
total_tokens: number;
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
export async function btw(options: BtwOptions): Promise<BtwResult> {
|
|
954
|
+
const { gatewayConfig, modelConfig, messages, question, abortSignal } =
|
|
955
|
+
options;
|
|
956
|
+
|
|
957
|
+
// Apply global 1 QPS rate limit
|
|
958
|
+
if (
|
|
959
|
+
process.env.NODE_ENV !== "test" ||
|
|
960
|
+
modelConfig.model === "rate-limit-test"
|
|
961
|
+
) {
|
|
962
|
+
await acquireSlot(abortSignal);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Create OpenAI client with injected configuration
|
|
966
|
+
const openai = new OpenAIClient({
|
|
967
|
+
apiKey: gatewayConfig.apiKey,
|
|
968
|
+
baseURL: gatewayConfig.baseURL,
|
|
969
|
+
defaultHeaders: gatewayConfig.defaultHeaders,
|
|
970
|
+
fetchOptions: gatewayConfig.fetchOptions,
|
|
971
|
+
fetch: gatewayConfig.fetch,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
// Get model configuration - use injected agent model
|
|
975
|
+
const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
|
|
976
|
+
temperature: 0.1,
|
|
977
|
+
max_tokens: 4096,
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
try {
|
|
981
|
+
const response = await openai.chat.completions.create(
|
|
982
|
+
{
|
|
983
|
+
...openaiModelConfig,
|
|
984
|
+
messages: [
|
|
985
|
+
{
|
|
986
|
+
role: "system",
|
|
987
|
+
content: BTW_SYSTEM_PROMPT,
|
|
988
|
+
},
|
|
989
|
+
...messages,
|
|
990
|
+
{
|
|
991
|
+
role: "user",
|
|
992
|
+
content: question,
|
|
993
|
+
},
|
|
994
|
+
],
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
signal: abortSignal,
|
|
998
|
+
},
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
const result = response.choices[0]?.message?.content?.trim();
|
|
1002
|
+
if (!result) {
|
|
1003
|
+
throw new Error(
|
|
1004
|
+
"Failed to process side question: Empty response from AI",
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
const usage = response.usage
|
|
1008
|
+
? {
|
|
1009
|
+
prompt_tokens: response.usage.prompt_tokens,
|
|
1010
|
+
completion_tokens: response.usage.completion_tokens,
|
|
1011
|
+
total_tokens: response.usage.total_tokens,
|
|
1012
|
+
}
|
|
1013
|
+
: undefined;
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
content: result,
|
|
1017
|
+
usage,
|
|
1018
|
+
};
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
if ((error as Error).name === "AbortError") {
|
|
1021
|
+
logger.info("Side question request was aborted");
|
|
1022
|
+
throw new Error("Side question request was aborted");
|
|
1023
|
+
}
|
|
1024
|
+
logger.error("Failed to process side question:", error);
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
@@ -78,8 +78,11 @@ export class InitializationService {
|
|
|
78
78
|
resolveAndValidateConfig,
|
|
79
79
|
} = context;
|
|
80
80
|
|
|
81
|
+
const startTime = performance.now();
|
|
82
|
+
|
|
81
83
|
// Initialize managers first
|
|
82
84
|
try {
|
|
85
|
+
const phaseStart = performance.now();
|
|
83
86
|
// Initialize SkillManager
|
|
84
87
|
await skillManager.initialize();
|
|
85
88
|
|
|
@@ -100,6 +103,9 @@ export class InitializationService {
|
|
|
100
103
|
slashCommandManager.registerSkillCommands(
|
|
101
104
|
skillManager.getAvailableSkills(),
|
|
102
105
|
);
|
|
106
|
+
logger?.debug(
|
|
107
|
+
`Initialization Phase [Managers and Tools] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
108
|
+
);
|
|
103
109
|
} catch (error) {
|
|
104
110
|
logger?.error("Failed to initialize managers and tools:", error);
|
|
105
111
|
// Don't throw error to prevent app startup failure
|
|
@@ -107,10 +113,14 @@ export class InitializationService {
|
|
|
107
113
|
|
|
108
114
|
// Initialize MCP servers with auto-connect
|
|
109
115
|
try {
|
|
116
|
+
const phaseStart = performance.now();
|
|
110
117
|
await mcpManager.initialize(workdir, true);
|
|
111
118
|
if (lspManager instanceof LspManager) {
|
|
112
119
|
await lspManager.initialize(workdir);
|
|
113
120
|
}
|
|
121
|
+
logger?.debug(
|
|
122
|
+
`Initialization Phase [MCP and LSP] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
123
|
+
);
|
|
114
124
|
} catch (error) {
|
|
115
125
|
logger?.error("Failed to initialize MCP servers:", error);
|
|
116
126
|
// Don't throw error to prevent app startup failure
|
|
@@ -118,6 +128,7 @@ export class InitializationService {
|
|
|
118
128
|
|
|
119
129
|
// Initialize hooks configuration
|
|
120
130
|
try {
|
|
131
|
+
const phaseStart = performance.now();
|
|
121
132
|
// Load hooks configuration using ConfigurationService
|
|
122
133
|
const configResult =
|
|
123
134
|
await configurationService.loadMergedConfiguration(workdir);
|
|
@@ -158,6 +169,9 @@ export class InitializationService {
|
|
|
158
169
|
}
|
|
159
170
|
}
|
|
160
171
|
}
|
|
172
|
+
logger?.debug(
|
|
173
|
+
`Initialization Phase [Hooks Configuration] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
174
|
+
);
|
|
161
175
|
} catch (error) {
|
|
162
176
|
logger?.error("Failed to initialize hooks system:", error);
|
|
163
177
|
// Don't throw error to prevent app startup failure
|
|
@@ -196,6 +210,7 @@ export class InitializationService {
|
|
|
196
210
|
|
|
197
211
|
// Initialize auto-memory directory
|
|
198
212
|
try {
|
|
213
|
+
const phaseStart = performance.now();
|
|
199
214
|
if (configurationService.resolveAutoMemoryEnabled()) {
|
|
200
215
|
const memoryService =
|
|
201
216
|
container.get<import("./memory.js").MemoryService>("MemoryService");
|
|
@@ -209,6 +224,9 @@ export class InitializationService {
|
|
|
209
224
|
}
|
|
210
225
|
}
|
|
211
226
|
}
|
|
227
|
+
logger?.debug(
|
|
228
|
+
`Initialization Phase [Auto-memory Initialization] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
229
|
+
);
|
|
212
230
|
} catch (error) {
|
|
213
231
|
logger?.error("Failed to initialize auto-memory directory:", error);
|
|
214
232
|
}
|
|
@@ -218,14 +236,22 @@ export class InitializationService {
|
|
|
218
236
|
|
|
219
237
|
// Discover modular memory rules
|
|
220
238
|
try {
|
|
239
|
+
const phaseStart = performance.now();
|
|
221
240
|
await memoryRuleManager.discoverRules();
|
|
241
|
+
logger?.debug(
|
|
242
|
+
`Initialization Phase [Memory Rules Discovery] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
243
|
+
);
|
|
222
244
|
} catch (error) {
|
|
223
245
|
logger?.error("Failed to discover memory rules:", error);
|
|
224
246
|
}
|
|
225
247
|
|
|
226
248
|
// Initialize live configuration reload
|
|
227
249
|
try {
|
|
250
|
+
const phaseStart = performance.now();
|
|
228
251
|
await liveConfigManager.initialize();
|
|
252
|
+
logger?.debug(
|
|
253
|
+
`Initialization Phase [Live Config Initialization] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
254
|
+
);
|
|
229
255
|
} catch (error) {
|
|
230
256
|
logger?.error("Failed to initialize live configuration reload:", error);
|
|
231
257
|
// Don't throw error to prevent app startup failure - continue without live reload
|
|
@@ -233,6 +259,7 @@ export class InitializationService {
|
|
|
233
259
|
|
|
234
260
|
// Load memory files during initialization
|
|
235
261
|
try {
|
|
262
|
+
const phaseStart = performance.now();
|
|
236
263
|
const memoryService = container.get<MemoryService>("MemoryService");
|
|
237
264
|
if (!memoryService) {
|
|
238
265
|
throw new Error("MemoryService not found in container");
|
|
@@ -256,6 +283,9 @@ export class InitializationService {
|
|
|
256
283
|
logger?.warn("Failed to load user memory file:", error);
|
|
257
284
|
setUserMemory("");
|
|
258
285
|
}
|
|
286
|
+
logger?.debug(
|
|
287
|
+
`Initialization Phase [Memory Files Loading] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
288
|
+
);
|
|
259
289
|
} catch (error) {
|
|
260
290
|
// Ensure memory is always initialized even if loading fails
|
|
261
291
|
setProjectMemory("");
|
|
@@ -265,6 +295,7 @@ export class InitializationService {
|
|
|
265
295
|
}
|
|
266
296
|
|
|
267
297
|
// Handle session restoration or set provided messages
|
|
298
|
+
const sessionPhaseStart = performance.now();
|
|
268
299
|
if (options?.messages) {
|
|
269
300
|
// If messages are provided, use them directly (useful for testing)
|
|
270
301
|
messageManager.setMessages(options.messages);
|
|
@@ -293,5 +324,12 @@ export class InitializationService {
|
|
|
293
324
|
agentOptions.callbacks?.onTasksChange?.(tasks);
|
|
294
325
|
}
|
|
295
326
|
}
|
|
327
|
+
logger?.debug(
|
|
328
|
+
`Initialization Phase [Session Restoration] took ${(performance.now() - sessionPhaseStart).toFixed(2)}ms`,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
logger?.debug(
|
|
332
|
+
`Total Initialization took ${(performance.now() - startTime).toFixed(2)}ms`,
|
|
333
|
+
);
|
|
296
334
|
}
|
|
297
335
|
}
|
package/src/tools/agentTool.ts
CHANGED
|
@@ -36,7 +36,7 @@ export const agentTool: ToolPlugin = {
|
|
|
36
36
|
run_in_background: {
|
|
37
37
|
type: "boolean",
|
|
38
38
|
description:
|
|
39
|
-
"Set to true to run this command in the background. Use
|
|
39
|
+
"Set to true to run this command in the background. Use Read to read the output later.",
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
required: ["description", "prompt", "subagent_type"],
|
|
@@ -154,7 +154,7 @@ When using the Agent tool, you must specify a subagent_type parameter to select
|
|
|
154
154
|
if (run_in_background || isBackgrounded) return;
|
|
155
155
|
|
|
156
156
|
const messages = instance.messageManager.getMessages();
|
|
157
|
-
const tokens = instance.messageManager.
|
|
157
|
+
const tokens = instance.messageManager.getLatestTotalTokens();
|
|
158
158
|
const lastTools = instance.lastTools;
|
|
159
159
|
|
|
160
160
|
const toolCount = countToolBlocks(messages);
|
|
@@ -224,7 +224,7 @@ When using the Agent tool, you must specify a subagent_type parameter to select
|
|
|
224
224
|
subagentManager.cleanupInstance(instance.subagentId);
|
|
225
225
|
|
|
226
226
|
const messages = instance.messageManager.getMessages();
|
|
227
|
-
const tokens = instance.messageManager.
|
|
227
|
+
const tokens = instance.messageManager.getLatestTotalTokens();
|
|
228
228
|
const toolCount = countToolBlocks(messages);
|
|
229
229
|
const summary = formatToolTokenSummary(toolCount, tokens);
|
|
230
230
|
|
package/src/tools/bashTool.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { stripAnsiColors } from "../utils/stringUtils.js";
|
|
|
7
7
|
import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
|
|
8
8
|
import {
|
|
9
9
|
BASH_TOOL_NAME,
|
|
10
|
-
TASK_OUTPUT_TOOL_NAME,
|
|
11
10
|
GLOB_TOOL_NAME,
|
|
12
11
|
GREP_TOOL_NAME,
|
|
13
12
|
READ_TOOL_NAME,
|
|
@@ -87,7 +86,7 @@ export const bashTool: ToolPlugin = {
|
|
|
87
86
|
},
|
|
88
87
|
run_in_background: {
|
|
89
88
|
type: "boolean",
|
|
90
|
-
description: `Set to true to run this command in the background. Use ${
|
|
89
|
+
description: `Set to true to run this command in the background. Use ${READ_TOOL_NAME} to read the output later.`,
|
|
91
90
|
},
|
|
92
91
|
},
|
|
93
92
|
required: ["command"],
|
|
@@ -120,7 +119,7 @@ Usage notes:
|
|
|
120
119
|
- You can specify an optional timeout in milliseconds (up to ${BASH_DEFAULT_TIMEOUT_MS}ms / ${BASH_DEFAULT_TIMEOUT_MS / 60000} minutes). If not specified, commands will timeout after ${BASH_DEFAULT_TIMEOUT_MS}ms (${BASH_DEFAULT_TIMEOUT_MS / 60000} minutes).
|
|
121
120
|
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
|
|
122
121
|
- If the output exceeds ${MAX_OUTPUT_LENGTH} characters, output will be truncated and the full output will be persisted to a temporary file.
|
|
123
|
-
- You can use the \`run_in_background\` parameter to run the command in the background, which allows you to continue working while the command runs. You can monitor the output using the ${
|
|
122
|
+
- You can use the \`run_in_background\` parameter to run the command in the background, which allows you to continue working while the command runs. You can monitor the output using the ${READ_TOOL_NAME} tool as it becomes available. You do not need to use '&' at the end of the command when using this parameter.
|
|
124
123
|
- Avoid using ${BASH_TOOL_NAME} with the \`find\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or when these commands are truly necessary for the task. Instead, always prefer using the dedicated tools for these commands:
|
|
125
124
|
- File search: Use ${GLOB_TOOL_NAME} (NOT find or ls)
|
|
126
125
|
- Content search: Use ${GREP_TOOL_NAME}
|
|
@@ -226,7 +225,7 @@ Usage notes:
|
|
|
226
225
|
const outputPath = task?.outputPath;
|
|
227
226
|
return {
|
|
228
227
|
success: true,
|
|
229
|
-
content: `Command started in background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ` Use
|
|
228
|
+
content: `Command started in background with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ` Use ${READ_TOOL_NAME} tool with task_id="${taskId}" to monitor output.`}`,
|
|
230
229
|
shortResult: `Background process ${taskId} started`,
|
|
231
230
|
};
|
|
232
231
|
}
|
package/src/tools/editTool.ts
CHANGED
package/src/tools/globTool.ts
CHANGED
|
@@ -32,6 +32,10 @@ export const globTool: ToolPlugin = {
|
|
|
32
32
|
description:
|
|
33
33
|
'The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.',
|
|
34
34
|
},
|
|
35
|
+
limit: {
|
|
36
|
+
type: "number",
|
|
37
|
+
description: "Maximum number of files to return. Defaults to 100.",
|
|
38
|
+
},
|
|
35
39
|
},
|
|
36
40
|
required: ["pattern"],
|
|
37
41
|
},
|
|
@@ -50,6 +54,8 @@ export const globTool: ToolPlugin = {
|
|
|
50
54
|
): Promise<ToolResult> => {
|
|
51
55
|
const pattern = args.pattern as string;
|
|
52
56
|
const searchPath = args.path as string;
|
|
57
|
+
const limit = (args.limit as number) || MAX_GLOB_RESULTS;
|
|
58
|
+
const startTime = Date.now();
|
|
53
59
|
|
|
54
60
|
if (!pattern || typeof pattern !== "string") {
|
|
55
61
|
return {
|
|
@@ -113,22 +119,29 @@ export const globTool: ToolPlugin = {
|
|
|
113
119
|
.map((item) => item.path);
|
|
114
120
|
|
|
115
121
|
const totalCount = sortedFiles.length;
|
|
116
|
-
const finalFiles = sortedFiles.slice(0,
|
|
122
|
+
const finalFiles = sortedFiles.slice(0, limit);
|
|
117
123
|
|
|
118
124
|
// Format output
|
|
119
125
|
const output = finalFiles
|
|
120
126
|
.map((file, index) => `${index + 1}. ${file}`)
|
|
121
127
|
.join("\n");
|
|
122
128
|
|
|
123
|
-
const isTruncated = totalCount >
|
|
129
|
+
const isTruncated = totalCount > limit;
|
|
124
130
|
const shortResult = isTruncated
|
|
125
|
-
? `Found ${totalCount} files (showing first ${
|
|
131
|
+
? `Found ${totalCount} files (showing first ${limit})`
|
|
126
132
|
: `Found ${totalCount} file${totalCount === 1 ? "" : "s"}`;
|
|
127
133
|
|
|
134
|
+
const durationMs = Date.now() - startTime;
|
|
135
|
+
|
|
128
136
|
return {
|
|
129
137
|
success: true,
|
|
130
138
|
content: output,
|
|
131
139
|
shortResult,
|
|
140
|
+
metadata: {
|
|
141
|
+
durationMs,
|
|
142
|
+
numFiles: totalCount,
|
|
143
|
+
truncated: isTruncated,
|
|
144
|
+
},
|
|
132
145
|
};
|
|
133
146
|
} catch (error) {
|
|
134
147
|
return {
|
package/src/tools/grepTool.ts
CHANGED
|
@@ -81,6 +81,15 @@ export const grepTool: ToolPlugin = {
|
|
|
81
81
|
description:
|
|
82
82
|
"Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false.",
|
|
83
83
|
},
|
|
84
|
+
offset: {
|
|
85
|
+
type: "number",
|
|
86
|
+
description: "The number of matches to skip.",
|
|
87
|
+
},
|
|
88
|
+
context: {
|
|
89
|
+
type: "number",
|
|
90
|
+
description:
|
|
91
|
+
'Alias for -C. Number of lines to show before and after each match. Requires output_mode: "content", ignored otherwise.',
|
|
92
|
+
},
|
|
84
93
|
},
|
|
85
94
|
required: ["pattern"],
|
|
86
95
|
},
|
|
@@ -113,6 +122,8 @@ export const grepTool: ToolPlugin = {
|
|
|
113
122
|
const fileType = args.type as string;
|
|
114
123
|
const headLimit = args.head_limit as number;
|
|
115
124
|
const multiline = args.multiline as boolean;
|
|
125
|
+
const offset = args.offset as number;
|
|
126
|
+
const contextArg = args.context as number;
|
|
116
127
|
|
|
117
128
|
if (!pattern || typeof pattern !== "string") {
|
|
118
129
|
return {
|
|
@@ -165,8 +176,9 @@ export const grepTool: ToolPlugin = {
|
|
|
165
176
|
|
|
166
177
|
// Context lines (only effective in content mode)
|
|
167
178
|
if (outputMode === "content") {
|
|
168
|
-
|
|
169
|
-
|
|
179
|
+
const effectiveContext = contextArg ?? contextAround;
|
|
180
|
+
if (effectiveContext) {
|
|
181
|
+
rgArgs.push("-C", effectiveContext.toString());
|
|
170
182
|
} else {
|
|
171
183
|
if (contextBefore) {
|
|
172
184
|
rgArgs.push("-B", contextBefore.toString());
|
|
@@ -215,41 +227,57 @@ export const grepTool: ToolPlugin = {
|
|
|
215
227
|
content:
|
|
216
228
|
"No matches found. Suggestion: specify the 'path' field to search in ignored or other directories (e.g., 'node_modules'), as the default search path is the current working directory and respects .gitignore.",
|
|
217
229
|
shortResult: "No matches found",
|
|
230
|
+
metadata: {
|
|
231
|
+
numMatches: 0,
|
|
232
|
+
},
|
|
218
233
|
};
|
|
219
234
|
}
|
|
220
235
|
|
|
221
|
-
// Apply head_limit with default fallback
|
|
222
|
-
let finalOutput = output;
|
|
223
236
|
let lines = output.split("\n");
|
|
237
|
+
const totalMatches = lines.length;
|
|
224
238
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
239
|
+
// Apply offset
|
|
240
|
+
const effectiveOffset = offset || 0;
|
|
241
|
+
if (effectiveOffset > 0) {
|
|
242
|
+
lines = lines.slice(effectiveOffset);
|
|
243
|
+
}
|
|
227
244
|
|
|
245
|
+
// Apply head_limit
|
|
246
|
+
const effectiveHeadLimit = headLimit || 0;
|
|
247
|
+
let truncated = false;
|
|
228
248
|
if (effectiveHeadLimit > 0 && lines.length > effectiveHeadLimit) {
|
|
229
249
|
lines = lines.slice(0, effectiveHeadLimit);
|
|
230
|
-
|
|
250
|
+
truncated = true;
|
|
231
251
|
}
|
|
232
252
|
|
|
253
|
+
const finalOutput = lines.join("\n");
|
|
254
|
+
|
|
233
255
|
// Generate short result
|
|
234
256
|
let shortResult: string;
|
|
235
|
-
const
|
|
257
|
+
const numMatches = lines.length;
|
|
236
258
|
|
|
237
259
|
if (outputMode === "files_with_matches") {
|
|
238
|
-
shortResult = `Found ${
|
|
260
|
+
shortResult = `Found ${numMatches} file${numMatches === 1 ? "" : "s"}`;
|
|
239
261
|
} else if (outputMode === "count") {
|
|
240
|
-
shortResult = `Match counts for ${
|
|
262
|
+
shortResult = `Match counts for ${numMatches} file${numMatches === 1 ? "" : "s"}`;
|
|
241
263
|
} else {
|
|
242
|
-
shortResult = `Found ${
|
|
264
|
+
shortResult = `Found ${numMatches} matching line${numMatches === 1 ? "" : "s"}`;
|
|
243
265
|
}
|
|
244
266
|
|
|
245
|
-
if (
|
|
246
|
-
shortResult += ` (showing
|
|
267
|
+
if (effectiveOffset > 0 || truncated) {
|
|
268
|
+
shortResult += ` (showing ${numMatches} of ${totalMatches})`;
|
|
247
269
|
}
|
|
248
270
|
|
|
249
271
|
return {
|
|
250
272
|
success: true,
|
|
251
273
|
content: finalOutput,
|
|
252
274
|
shortResult,
|
|
275
|
+
metadata: {
|
|
276
|
+
numMatches: totalMatches,
|
|
277
|
+
truncated,
|
|
278
|
+
appliedLimit: effectiveHeadLimit,
|
|
279
|
+
appliedOffset: effectiveOffset,
|
|
280
|
+
},
|
|
253
281
|
};
|
|
254
282
|
} catch (error) {
|
|
255
283
|
return {
|
package/src/tools/readTool.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, stat } from "fs/promises";
|
|
2
2
|
import { extname } from "path";
|
|
3
|
+
import { createHash } from "crypto";
|
|
3
4
|
import { logger } from "../utils/globalLogger.js";
|
|
4
5
|
import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
|
|
5
6
|
import { resolvePath, getDisplayPath } from "../utils/path.js";
|
|
@@ -117,6 +118,10 @@ async function processImageFile(
|
|
|
117
118
|
mediaType: mimeType,
|
|
118
119
|
},
|
|
119
120
|
],
|
|
121
|
+
metadata: {
|
|
122
|
+
type: "image",
|
|
123
|
+
mimeType,
|
|
124
|
+
},
|
|
120
125
|
};
|
|
121
126
|
} catch (error) {
|
|
122
127
|
return {
|
|
@@ -190,12 +195,19 @@ Usage:
|
|
|
190
195
|
};
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
// Touch file to track it in context
|
|
199
|
+
context.messageManager?.touchFile(filePath);
|
|
200
|
+
|
|
193
201
|
// Check for binary document formats
|
|
194
202
|
if (isBinaryDocument(filePath)) {
|
|
203
|
+
const isPdf = filePath.toLowerCase().endsWith(".pdf");
|
|
195
204
|
return {
|
|
196
205
|
success: false,
|
|
197
206
|
content: "",
|
|
198
207
|
error: getBinaryDocumentError(filePath),
|
|
208
|
+
metadata: {
|
|
209
|
+
type: isPdf ? "pdf" : "binary",
|
|
210
|
+
},
|
|
199
211
|
};
|
|
200
212
|
}
|
|
201
213
|
|
|
@@ -230,8 +242,54 @@ Usage:
|
|
|
230
242
|
? filePath
|
|
231
243
|
: resolvePath(filePath, context.workdir);
|
|
232
244
|
|
|
245
|
+
const stats = await stat(actualFilePath);
|
|
246
|
+
|
|
247
|
+
// Deduplication
|
|
248
|
+
if (context.readFileState) {
|
|
249
|
+
const state = context.readFileState.get(actualFilePath);
|
|
250
|
+
if (state && state.mtime === stats.mtime.getTime()) {
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
content: `File ${filePath} has not changed since last read.`,
|
|
254
|
+
shortResult: "File unchanged",
|
|
255
|
+
metadata: {
|
|
256
|
+
type: "file_unchanged",
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Resource Limits
|
|
263
|
+
const maxSizeBytes =
|
|
264
|
+
context.fileReadingLimits?.maxSizeBytes ?? 1024 * 1024; // Default 1MB
|
|
265
|
+
if (
|
|
266
|
+
stats.size > maxSizeBytes &&
|
|
267
|
+
typeof offset !== "number" &&
|
|
268
|
+
typeof limit !== "number"
|
|
269
|
+
) {
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
content: "",
|
|
273
|
+
error: `File size (${(stats.size / 1024).toFixed(2)}KB) exceeds limit (${(maxSizeBytes / 1024).toFixed(2)}KB). Please use offset and limit to read a portion of the file.`,
|
|
274
|
+
metadata: {
|
|
275
|
+
type: "error_limit_exceeded",
|
|
276
|
+
size: stats.size,
|
|
277
|
+
limit: maxSizeBytes,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
233
282
|
const fileContent = await readFile(actualFilePath, "utf-8");
|
|
234
283
|
|
|
284
|
+
// Update readFileState
|
|
285
|
+
if (context.readFileState) {
|
|
286
|
+
const hash = createHash("sha256").update(fileContent).digest("hex");
|
|
287
|
+
context.readFileState.set(actualFilePath, {
|
|
288
|
+
mtime: stats.mtime.getTime(),
|
|
289
|
+
hash,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
235
293
|
// Check if file is empty
|
|
236
294
|
if (fileContent.length === 0) {
|
|
237
295
|
logger.warn(`File ${filePath} exists but has empty contents`);
|
|
@@ -240,6 +298,10 @@ Usage:
|
|
|
240
298
|
content:
|
|
241
299
|
"⚠️ System reminder: This file exists but has empty contents.",
|
|
242
300
|
shortResult: "Empty file",
|
|
301
|
+
metadata: {
|
|
302
|
+
type: "text",
|
|
303
|
+
isEmpty: true,
|
|
304
|
+
},
|
|
243
305
|
};
|
|
244
306
|
}
|
|
245
307
|
|
|
@@ -306,6 +368,13 @@ Usage:
|
|
|
306
368
|
success: true,
|
|
307
369
|
content,
|
|
308
370
|
shortResult: `Read ${selectedLines.length} lines${totalLines > 2000 ? " (truncated)" : ""}`,
|
|
371
|
+
metadata: {
|
|
372
|
+
type: "text",
|
|
373
|
+
totalLines,
|
|
374
|
+
startLine,
|
|
375
|
+
endLine,
|
|
376
|
+
truncated: endLine < totalLines,
|
|
377
|
+
},
|
|
309
378
|
};
|
|
310
379
|
} catch (error) {
|
|
311
380
|
return {
|