Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,8 @@ export class Tools {
isError: true,
};
}
// // @ts-expect-error
// const result = validateToolParams(tool.parameters, args);
// if (!result.success) {
// return {
// llmContent: `Invalid tool parameters: ${result.error}`,
// isError: true,
// };
// }

// Parse JSON arguments
let argsObj: any;
try {
argsObj = JSON.parse(args);
Expand All @@ -151,6 +145,32 @@ export class Tools {
isError: true,
};
}

// Validate parameters using Zod schema (skip for MCP tools)
const isMcpTool = toolName.startsWith('mcp__');
if (!isMcpTool) {
const { validateToolParams, sanitizeToolParams } = await import(
'./utils/validateToolParams'
);

// Try to sanitize parameters first (auto-fix common issues)
argsObj = sanitizeToolParams(tool.parameters, argsObj);

// Validate after sanitization
const validationResult = validateToolParams(tool.parameters, argsObj);
if (!validationResult.success) {
// TODO: Add retry limit to prevent infinite loops when LLM repeatedly fails
// Current behavior: LLM will retry up to maxTurns (default 50) times
// Future improvement: Track consecutive identical parameter errors and fail fast after 3 attempts
// This should be implemented in loop.ts by detecting repeated tool+params failures
// Example: if (consecutiveSameErrors >= 3) { return 'repeated_tool_error' }
return {
llmContent: validationResult.error,
isError: true,
};
}
}

return await tool.execute(argsObj);
}

Expand Down
11 changes: 11 additions & 0 deletions src/tools/askUserQuestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ const AskUserQuestionInputSchema = z
})
.refine(
(data) => {
// Defensive check: ensure questions is an array
if (!Array.isArray(data.questions)) {
return false;
}
const questionTexts = data.questions.map((q) => q.question);
return questionTexts.length === new Set(questionTexts).size;
},
Expand All @@ -68,7 +72,14 @@ const AskUserQuestionInputSchema = z
)
.refine(
(data) => {
// Defensive check: ensure questions is an array
if (!Array.isArray(data.questions)) {
return false;
}
for (const question of data.questions) {
if (!question.options || !Array.isArray(question.options)) {
return false;
}
const labels = question.options.map((o) => o.label);
if (labels.length !== new Set(labels).size) {
return false;
Expand Down
14 changes: 10 additions & 4 deletions src/tools/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ Usage:
if (!params.task_id || typeof params.task_id !== 'string') {
return 'Read background task output';
}
return `Read output from task: ${params.task_id}`;
return `Read output from task: ${String(params.task_id)}`;
},
execute: async ({ task_id }) => {
const task = backgroundTaskManager.getTask(task_id);
Expand Down Expand Up @@ -713,7 +713,7 @@ Usage:
if (!params.task_id || typeof params.task_id !== 'string') {
return 'Terminate background task';
}
return `Terminate task: ${params.task_id}`;
return `Terminate task: ${String(params.task_id)}`;
},
execute: async ({ task_id }) => {
const task = backgroundTaskManager.getTask(task_id);
Expand Down Expand Up @@ -830,8 +830,14 @@ cd /foo/bar && pytest tests
if (!params.command || typeof params.command !== 'string') {
return 'No command provided';
}
const command = params.command.trim();
return command.length > 100 ? `${command.substring(0, 97)}...` : command;
try {
const command = String(params.command).trim();
return command.length > 100
? `${command.substring(0, 97)}...`
: command;
} catch (error) {
return 'Invalid command';
}
},
execute: async ({
command,
Expand Down
11 changes: 10 additions & 1 deletion src/tools/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@ Usage:
if (!params.file_path || typeof params.file_path !== 'string') {
return 'No file path provided';
}
return path.relative(cwd, params.file_path);
// Ensure cwd is a string to prevent .trim() errors in path.relative()
if (typeof cwd !== 'string') {
return params.file_path;
}
try {
return path.relative(cwd, params.file_path);
} catch (error) {
// Fallback if path.relative fails
return params.file_path;
}
},
execute: async ({ file_path, old_string, new_string, replace_all }) => {
try {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Remembers:
if (!params.url || typeof params.url !== 'string') {
return 'No URL provided';
}
return params.url;
return String(params.url);
},
execute: async ({ url, prompt }) => {
try {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/glob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Glob
if (!params.pattern || typeof params.pattern !== 'string') {
return 'No pattern provided';
}
return params.pattern;
return String(params.pattern);
},
execute: async ({ pattern, path }) => {
try {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/grep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function createGrepTool(opts: { cwd: string }) {
if (!params.pattern || typeof params.pattern !== 'string') {
return 'No pattern provided';
}
return params.pattern;
return String(params.pattern);
},
execute: async ({ pattern, search_path, include, limit }) => {
try {
Expand Down
13 changes: 11 additions & 2 deletions src/tools/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ export function createLSTool(opts: { cwd: string }) {
parameters: z.object({
dir_path: z.string().describe('The path to the directory to list.'),
}),
getDescription: ({ params }) => {
getDescription: ({ params, cwd }) => {
if (!params.dir_path || typeof params.dir_path !== 'string') {
return '.';
}
return path.relative(opts.cwd, params.dir_path);
// Ensure cwd is a string to prevent .trim() errors in path.relative()
if (typeof cwd !== 'string') {
return params.dir_path;
}
try {
return path.relative(cwd, params.dir_path);
} catch (error) {
// Fallback if path.relative fails
return params.dir_path;
}
},
execute: async (params) => {
const { dir_path } = params;
Expand Down
11 changes: 10 additions & 1 deletion src/tools/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,16 @@ Usage:
if (!params.file_path || typeof params.file_path !== 'string') {
return 'No file path provided';
}
return path.relative(cwd, params.file_path);
// Ensure cwd is a string to prevent .trim() errors in path.relative()
if (typeof cwd !== 'string') {
return params.file_path;
}
try {
return path.relative(cwd, params.file_path);
} catch (error) {
// Fallback if path.relative fails
return params.file_path;
}
},
execute: async ({ file_path, offset, limit }) => {
try {
Expand Down
11 changes: 10 additions & 1 deletion src/tools/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ export function createWriteTool(opts: { cwd: string }) {
if (!params.file_path || typeof params.file_path !== 'string') {
return 'No file path provided';
}
return path.relative(cwd, params.file_path);
// Ensure cwd is a string to prevent .trim() errors in path.relative()
if (typeof cwd !== 'string') {
return params.file_path;
}
try {
return path.relative(cwd, params.file_path);
} catch (error) {
// Fallback if path.relative fails
return params.file_path;
}
},
execute: async ({ file_path, content }) => {
try {
Expand Down
Loading
Loading