Skip to content

Commit 35cfaa4

Browse files
authored
Merge pull request #82 from drivecore/throw-exceptions-in-tools
Get rid of exception catching in tools, they should rely on toolExecu…
2 parents c4ecf58 + 1da2e60 commit 35cfaa4

File tree

19 files changed

+279
-441
lines changed

19 files changed

+279
-441
lines changed

packages/agent/src/core/executeToolCall.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,25 @@ export const executeToolCall = async (
3939
}
4040

4141
// TODO: validate JSON schema for input
42-
const output = await tool.execute(toolCall.input, toolContext);
42+
let output;
43+
try {
44+
output = await tool.execute(toolCall.input, toolContext);
45+
} catch (err) {
46+
if (err instanceof Error) {
47+
logger.error(err.message);
48+
return JSON.stringify({
49+
error: true,
50+
message: err.message,
51+
stack: err.stack,
52+
});
53+
} else {
54+
logger.error(err);
55+
return JSON.stringify({
56+
error: true,
57+
message: err,
58+
});
59+
}
60+
}
4361

4462
// for each result log it and its name
4563
if (tool.logReturns) {

packages/agent/src/core/toolAgent.test.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const toolContext: ToolContext = {
1414
userSession: false,
1515
pageFilter: 'simple',
1616
tokenTracker: new TokenTracker(),
17+
githubMode: false,
1718
};
1819

1920
// Mock tool for testing
@@ -98,16 +99,21 @@ describe('toolAgent', () => {
9899
});
99100

100101
it('should handle tool execution errors', async () => {
101-
await expect(
102-
executeToolCall(
103-
{
104-
id: '1',
105-
name: 'errorTool',
106-
input: {},
107-
},
108-
[errorTool],
109-
toolContext,
110-
),
111-
).rejects.toThrow('Deliberate failure');
102+
const result = await executeToolCall(
103+
{
104+
id: '1',
105+
name: 'errorTool',
106+
input: {},
107+
},
108+
[errorTool],
109+
toolContext,
110+
);
111+
112+
// Parse the result as JSON
113+
const parsedResult = JSON.parse(result);
114+
115+
// Check that it contains the expected error properties
116+
expect(parsedResult.error).toBe(true);
117+
expect(parsedResult.message).toContain('Deliberate failure');
112118
});
113119
});

packages/agent/src/core/toolAgent/config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export function getModel(provider: ModelProvider, modelName: string) {
3030
}
3131
}
3232

33+
import { ToolContext } from '../types';
34+
3335
/**
3436
* Default configuration for the tool agent
3537
*/
@@ -44,9 +46,7 @@ export const DEFAULT_CONFIG = {
4446
/**
4547
* Gets the default system prompt with contextual information about the environment
4648
*/
47-
export function getDefaultSystemPrompt(options?: {
48-
githubMode?: boolean;
49-
}): string {
49+
export function getDefaultSystemPrompt(toolContext: ToolContext): string {
5050
// Gather context with error handling
5151
const getCommandOutput = (command: string, label: string): string => {
5252
try {
@@ -61,7 +61,7 @@ export function getDefaultSystemPrompt(options?: {
6161
files: getCommandOutput('ls -la', 'file listing'),
6262
system: getCommandOutput('uname -a', 'system information'),
6363
datetime: new Date().toString(),
64-
githubMode: options?.githubMode || false,
64+
githubMode: toolContext.githubMode,
6565
};
6666

6767
const githubModeInstructions = context.githubMode
@@ -110,7 +110,7 @@ export function getDefaultSystemPrompt(options?: {
110110
'3. Update documentation as needed',
111111
'4. Consider adding documentation if you encountered setup/understanding challenges',
112112
'',
113-
'Feel free to use Google and Bing via the browser tools to search for information or for ideas when you get stuck.',
113+
'Feel free to use AI friendly search engines via the browser tools to search for information or for ideas when you get stuck.',
114114
'',
115115
'When you run into issues or unexpected results, take a step back and read the project documentation and configuration files and look at other source files in the project for examples of what works.',
116116
'',

packages/agent/src/core/toolAgent/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const toolAgent = async (
4343
logger.debug('User message:', initialPrompt);
4444

4545
// Get the system prompt once at the start
46-
const systemPrompt = config.getSystemPrompt();
46+
const systemPrompt = config.getSystemPrompt(context);
4747

4848
for (let i = 0; i < config.maxIterations; i++) {
4949
logger.verbose(

packages/agent/src/core/toolAgent/toolExecutor.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,18 @@ export async function executeTools(
4949
...context,
5050
tokenTracker: new TokenTracker(call.name, context.tokenTracker),
5151
});
52-
} catch (error: any) {
53-
toolResult = JSON.stringify({
54-
errorMessage: error.message,
55-
errorType: error.constructor.name,
56-
});
52+
} catch (errorStr: any) {
53+
if (errorStr instanceof Error) {
54+
if (errorStr.stack) {
55+
context.logger.error(`Tool error stack trace: ${errorStr.stack}`);
56+
}
57+
toolResult = JSON.stringify(errorStr);
58+
} else {
59+
toolResult = JSON.stringify({
60+
errorMessage: errorStr.message,
61+
errorType: errorStr.name,
62+
});
63+
}
5764
}
5865

5966
return {

packages/agent/src/core/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type ToolContext = {
1616
userSession: boolean;
1717
pageFilter: pageFilter;
1818
tokenTracker: TokenTracker;
19+
githubMode: boolean;
1920
};
2021

2122
export type Tool<TParams = Record<string, any>, TReturn = any> = {

packages/agent/src/tools/interaction/subAgent.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { z } from 'zod';
22
import { zodToJsonSchema } from 'zod-to-json-schema';
33

4+
import { getDefaultSystemPrompt } from '../../core/toolAgent/index.js';
45
import { getModel } from '../../core/toolAgent/config.js';
56
import { toolAgent } from '../../core/toolAgent.js';
6-
import { Tool } from '../../core/types.js';
7+
import { Tool, ToolContext } from '../../core/types.js';
78
import { getTools } from '../getTools.js';
89

910
const parameterSchema = z.object({
@@ -53,8 +54,9 @@ const subAgentConfig = {
5354
model: getModel('anthropic', 'claude-3-7-sonnet-20250219'),
5455
maxTokens: 4096,
5556
temperature: 0.7,
56-
getSystemPrompt: () => {
57+
getSystemPrompt: (context: ToolContext) => {
5758
return [
59+
getDefaultSystemPrompt(context),
5860
'You are a focused AI sub-agent handling a specific task.',
5961
'You have access to the same tools as the main agent but should focus only on your assigned task.',
6062
'When complete, call the sequenceComplete tool with your results.',

packages/agent/src/tools/io/textEditor.test.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const toolContext: ToolContext = {
1919
userSession: false,
2020
pageFilter: 'simple',
2121
tokenTracker: new TokenTracker(),
22+
githubMode: false,
2223
};
2324

2425
describe('textEditor', () => {
@@ -259,18 +260,16 @@ describe('textEditor', () => {
259260
const testPath = join(testDir, `${randomUUID()}.txt`);
260261

261262
// Try to view a non-existent file
262-
const result = await textEditorTool.execute(
263-
{
264-
command: 'view',
265-
path: testPath,
266-
description: 'test',
267-
},
268-
toolContext,
269-
);
270-
271-
// Verify return value
272-
expect(result.success).toBe(false);
273-
expect(result.message).toContain('not found');
263+
await expect(async () => {
264+
await textEditorTool.execute(
265+
{
266+
command: 'view',
267+
path: testPath,
268+
description: 'test',
269+
},
270+
toolContext,
271+
);
272+
}).rejects.toThrow(/not found/);
274273
});
275274

276275
it('should handle errors for duplicate string replacements', async () => {
@@ -291,19 +290,17 @@ describe('textEditor', () => {
291290
);
292291

293292
// Try to replace text with multiple occurrences
294-
const result = await textEditorTool.execute(
295-
{
296-
command: 'str_replace',
297-
path: testPath,
298-
old_str: oldStr,
299-
new_str: newStr,
300-
description: 'test',
301-
},
302-
toolContext,
303-
);
304-
305-
// Verify return value
306-
expect(result.success).toBe(false);
307-
expect(result.message).toContain('Found 2 occurrences');
293+
await expect(async () => {
294+
await textEditorTool.execute(
295+
{
296+
command: 'str_replace',
297+
path: testPath,
298+
old_str: oldStr,
299+
new_str: newStr,
300+
description: 'test',
301+
},
302+
toolContext,
303+
);
304+
}).rejects.toThrow(/Found 2 occurrences/);
308305
});
309306
});

0 commit comments

Comments
 (0)