diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts deleted file mode 100644 index f99fccb..0000000 --- a/packages/agent/src/core/toolAgent.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @fileoverview - * This file has been refactored into a modular structure. - * Please import from the toolAgent directory instead. - */ - -import { toolAgent, ToolAgentResult } from './toolAgent/index.js'; -import { Tool, ToolContext } from './toolAgent/types.js'; - -// Re-export the main toolAgent function and types for backward compatibility -export { toolAgent, ToolAgentResult, Tool, ToolContext }; - -// Provide a deprecation warning when this file is imported directly -const deprecationWarning = () => { - console.warn( - 'Warning: Importing directly from toolAgent.ts is deprecated. ' + - 'Please import from the toolAgent directory instead.', - ); -}; - -// Call the deprecation warning when this module is loaded -deprecationWarning(); diff --git a/packages/agent/src/core/toolAgent/README.md b/packages/agent/src/core/toolAgent/README.md index dbcc9a9..56147b4 100644 --- a/packages/agent/src/core/toolAgent/README.md +++ b/packages/agent/src/core/toolAgent/README.md @@ -1,10 +1,11 @@ # Tool Agent Module -This directory contains the refactored Tool Agent implementation, split into smaller, focused modules for improved maintainability and testability. +This directory contains the Tool Agent implementation, split into smaller, focused modules for improved maintainability and testability. ## Module Structure -- **index.ts**: Main entry point and orchestration of the tool agent functionality +- **index.ts**: Re-exports from toolAgentCore.ts and other modules +- **toolAgentCore.ts**: Main implementation of the tool agent functionality - **config.ts**: Configuration-related code and default settings - **messageUtils.ts**: Utilities for handling and formatting messages - **toolExecutor.ts**: Logic for executing tool calls @@ -14,10 +15,10 @@ This directory contains the refactored Tool Agent implementation, split into sma ## Usage ```typescript -import { toolAgent } from './toolAgent/index.js'; -import { Tool, ToolContext } from './toolAgent/types.js'; +import { toolAgent } from '../../core/toolAgent/index.js'; +import { Tool, ToolContext } from '../../core/types.js'; -// Use the toolAgent function as before +// Use the toolAgent function const result = await toolAgent(prompt, tools, config, context); ``` @@ -28,7 +29,3 @@ const result = await toolAgent(prompt, tools, config, context); - **Clearer responsibilities**: Each module has a single purpose - **Easier onboarding**: New developers can understand the system more quickly - **Simpler future extensions**: Modular design makes it easier to extend functionality - -## Migration - -The original `toolAgent.ts` file now re-exports from this directory for backward compatibility, but it will display a deprecation warning. New code should import directly from the toolAgent directory. diff --git a/packages/agent/src/core/toolAgent/index.ts b/packages/agent/src/core/toolAgent/index.ts index 1d5b3dd..6292320 100644 --- a/packages/agent/src/core/toolAgent/index.ts +++ b/packages/agent/src/core/toolAgent/index.ts @@ -1,153 +1,10 @@ -import { CoreMessage, ToolSet, generateText, tool as makeTool } from 'ai'; - -import { getAnthropicApiKeyError } from '../../utils/errors.js'; - -import { DEFAULT_CONFIG } from './config.js'; -import { - addCacheControlToMessages, - createCacheControlMessageFromSystemPrompt, - createToolCallParts, - formatToolCalls, -} from './messageUtils.js'; -import { logTokenUsage } from './tokenTracking.js'; -import { executeTools } from './toolExecutor.js'; -import { Tool, ToolAgentResult, ToolContext } from './types.js'; - /** - * Main tool agent function that orchestrates the conversation with the AI - * and handles tool execution + * Main entry point for the toolAgent module + * Re-exports all functionality from the modular structure */ -export const toolAgent = async ( - initialPrompt: string, - tools: Tool[], - config = DEFAULT_CONFIG, - context: ToolContext, -): Promise => { - const { logger, tokenTracker } = context; - - logger.verbose('Starting agent execution'); - logger.verbose('Initial prompt:', initialPrompt); - - let interactions = 0; - - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) throw new Error(getAnthropicApiKeyError()); - - const messages: CoreMessage[] = [ - { - role: 'user', - content: [{ type: 'text', text: initialPrompt }], - }, - ]; - - logger.debug('User message:', initialPrompt); - - // Get the system prompt once at the start - const systemPrompt = config.getSystemPrompt(context); - - for (let i = 0; i < config.maxIterations; i++) { - logger.verbose( - `Requesting completion ${i + 1} with ${messages.length} messages with ${ - JSON.stringify(messages).length - } bytes`, - ); - - interactions++; - - const toolSet: ToolSet = {}; - tools.forEach((tool) => { - toolSet[tool.name] = makeTool({ - description: tool.description, - parameters: tool.parameters, - }); - }); - - // Apply cache control to messages for token caching - const messagesWithCacheControl = [ - createCacheControlMessageFromSystemPrompt(systemPrompt), - ...addCacheControlToMessages(messages), - ]; - - const generateTextProps = { - model: config.model, - temperature: config.temperature, - messages: messagesWithCacheControl, - tools: toolSet, - }; - const { text, toolCalls } = await generateText(generateTextProps); - const localToolCalls = formatToolCalls(toolCalls); - - if (!text.length) { - // Instead of treating empty response as completion, remind the agent - logger.verbose('Received empty response from agent, sending reminder'); - messages.push({ - role: 'user', - content: [ - { - type: 'text', - text: 'I notice you sent an empty response. If you are done with your tasks, please call the sequenceComplete tool with your results. If you are waiting for other tools to complete, you can use the sleep tool to wait before checking again.', - }, - ], - }); - continue; - } - - messages.push({ - role: 'assistant', - content: [{ type: 'text', text: text }], - }); - - if (text) { - logger.info(text); - } - - if (toolCalls.length > 0) { - const toolCallParts = createToolCallParts(toolCalls); - - messages.push({ - role: 'assistant', - content: toolCallParts, - }); - } - - const { sequenceCompleted, completionResult, respawn } = await executeTools( - localToolCalls, - tools, - messages, - context, - ); - - if (respawn) { - logger.info('Respawning agent with new context'); - // Reset messages to just the new context - messages.length = 0; - messages.push({ - role: 'user', - content: [{ type: 'text', text: respawn.context }], - }); - continue; - } - - if (sequenceCompleted) { - const result: ToolAgentResult = { - result: completionResult ?? 'Sequence explicitly completed', - interactions, - }; - logTokenUsage(tokenTracker); - return result; - } - } - - logger.warn('Maximum iterations reached'); - const result = { - result: 'Maximum sub-agent iterations reach without successful completion', - interactions, - }; - - logTokenUsage(tokenTracker); - return result; -}; +// Export the main toolAgent function +export { toolAgent } from './toolAgentCore.js'; // Re-export everything from the module export * from './config.js'; @@ -155,3 +12,15 @@ export * from './messageUtils.js'; export * from './toolExecutor.js'; export * from './tokenTracking.js'; export * from './types.js'; + +// Export default system prompt for convenience +export const getDefaultSystemPrompt = (context: Record) => { + return `You are an AI agent that can use tools to accomplish tasks. + +Current Context: +Directory: ${context.workingDirectory} +Files: +${context.directoryListing ?? 'No directory listing available'} +System: ${context.systemInfo ?? 'No system info available'} +DateTime: ${new Date().toString()}`; +}; diff --git a/packages/agent/src/core/toolAgent/toolAgentCore.ts b/packages/agent/src/core/toolAgent/toolAgentCore.ts new file mode 100644 index 0000000..a472f07 --- /dev/null +++ b/packages/agent/src/core/toolAgent/toolAgentCore.ts @@ -0,0 +1,150 @@ +import { CoreMessage, ToolSet, generateText, tool as makeTool } from 'ai'; + +import { getAnthropicApiKeyError } from '../../utils/errors.js'; + +import { DEFAULT_CONFIG } from './config.js'; +import { + addCacheControlToMessages, + createCacheControlMessageFromSystemPrompt, + createToolCallParts, + formatToolCalls, +} from './messageUtils.js'; +import { logTokenUsage } from './tokenTracking.js'; +import { executeTools } from './toolExecutor.js'; +import { Tool, ToolAgentResult, ToolContext } from './types.js'; + +/** + * Main tool agent function that orchestrates the conversation with the AI + * and handles tool execution + */ +export const toolAgent = async ( + initialPrompt: string, + tools: Tool[], + config = DEFAULT_CONFIG, + context: ToolContext, +): Promise => { + const { logger, tokenTracker } = context; + + logger.verbose('Starting agent execution'); + logger.verbose('Initial prompt:', initialPrompt); + + let interactions = 0; + + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) throw new Error(getAnthropicApiKeyError()); + + const messages: CoreMessage[] = [ + { + role: 'user', + content: [{ type: 'text', text: initialPrompt }], + }, + ]; + + logger.debug('User message:', initialPrompt); + + // Get the system prompt once at the start + const systemPrompt = config.getSystemPrompt(context); + + for (let i = 0; i < config.maxIterations; i++) { + logger.verbose( + `Requesting completion ${i + 1} with ${messages.length} messages with ${ + JSON.stringify(messages).length + } bytes`, + ); + + interactions++; + + const toolSet: ToolSet = {}; + tools.forEach((tool) => { + toolSet[tool.name] = makeTool({ + description: tool.description, + parameters: tool.parameters, + }); + }); + + // Apply cache control to messages for token caching + const messagesWithCacheControl = [ + createCacheControlMessageFromSystemPrompt(systemPrompt), + ...addCacheControlToMessages(messages), + ]; + + const generateTextProps = { + model: config.model, + temperature: config.temperature, + messages: messagesWithCacheControl, + tools: toolSet, + }; + const { text, toolCalls } = await generateText(generateTextProps); + + const localToolCalls = formatToolCalls(toolCalls); + + if (!text.length) { + // Instead of treating empty response as completion, remind the agent + logger.verbose('Received empty response from agent, sending reminder'); + messages.push({ + role: 'user', + content: [ + { + type: 'text', + text: 'I notice you sent an empty response. If you are done with your tasks, please call the sequenceComplete tool with your results. If you are waiting for other tools to complete, you can use the sleep tool to wait before checking again.', + }, + ], + }); + continue; + } + + messages.push({ + role: 'assistant', + content: [{ type: 'text', text: text }], + }); + + if (text) { + logger.info(text); + } + + if (toolCalls.length > 0) { + const toolCallParts = createToolCallParts(toolCalls); + + messages.push({ + role: 'assistant', + content: toolCallParts, + }); + } + + const { sequenceCompleted, completionResult, respawn } = await executeTools( + localToolCalls, + tools, + messages, + context, + ); + + if (respawn) { + logger.info('Respawning agent with new context'); + // Reset messages to just the new context + messages.length = 0; + messages.push({ + role: 'user', + content: [{ type: 'text', text: respawn.context }], + }); + continue; + } + + if (sequenceCompleted) { + const result: ToolAgentResult = { + result: completionResult ?? 'Sequence explicitly completed', + interactions, + }; + logTokenUsage(tokenTracker); + return result; + } + } + + logger.warn('Maximum iterations reached'); + const result = { + result: 'Maximum sub-agent iterations reach without successful completion', + interactions, + }; + + logTokenUsage(tokenTracker); + return result; +}; diff --git a/packages/agent/src/core/toolAgent/toolExecutor.ts b/packages/agent/src/core/toolAgent/toolExecutor.ts index 52c0f64..3f05e1a 100644 --- a/packages/agent/src/core/toolAgent/toolExecutor.ts +++ b/packages/agent/src/core/toolAgent/toolExecutor.ts @@ -2,8 +2,18 @@ import { CoreMessage, CoreToolMessage, ToolResultPart } from 'ai'; import { executeToolCall } from '../executeToolCall.js'; import { TokenTracker } from '../tokens.js'; +import { ToolUseContent } from '../types.js'; -import { Tool, ToolCallResult, ToolContext, ToolUseContent } from './types.js'; +import { Tool, ToolCallResult, ToolContext } from './types.js'; + +const safeParse = (value: string) => { + try { + return JSON.parse(value); + } catch (error) { + console.error('Error parsing JSON:', error, 'original value:', value); + return { error: value }; + } +}; /** * Executes a list of tool calls and returns the results @@ -67,7 +77,7 @@ export async function executeTools( type: 'tool-result', toolCallId: call.id, toolName: call.name, - result: JSON.parse(toolResult), + result: safeParse(toolResult), } satisfies ToolResultPart; }), ); diff --git a/packages/agent/src/core/toolAgent/types.ts b/packages/agent/src/core/toolAgent/types.ts index a7c57b0..52c6b12 100644 --- a/packages/agent/src/core/toolAgent/types.ts +++ b/packages/agent/src/core/toolAgent/types.ts @@ -1,7 +1,10 @@ -// Re-export all types from the original types.ts file -export * from '../types.js'; +// Import types from the core types file +import { Tool, ToolContext } from '../types.js'; -// Only define new types specific to toolAgent here +// Export the imported types explicitly +export { Tool, ToolContext }; + +// Define types specific to toolAgent here export interface ToolAgentResult { result: string; interactions: number; @@ -10,7 +13,7 @@ export interface ToolAgentResult { export interface ToolCallResult { sequenceCompleted: boolean; completionResult?: string; - toolResults: any[]; + toolResults: unknown[]; respawn?: { context: string }; } diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index 28ee7e0..e9bf527 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -25,8 +25,7 @@ export * from './tools/interaction/userPrompt.js'; // Core export * from './core/executeToolCall.js'; export * from './core/types.js'; -export * from './core/toolAgent.js'; -export * from './core/toolAgent/config.js'; +export * from './core/toolAgent/index.js'; // Utils export * from './tools/getTools.js'; diff --git a/packages/agent/src/tools/interaction/subAgent.ts b/packages/agent/src/tools/interaction/subAgent.ts index d175bdf..41784a3 100644 --- a/packages/agent/src/tools/interaction/subAgent.ts +++ b/packages/agent/src/tools/interaction/subAgent.ts @@ -1,9 +1,11 @@ import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; -import { getModel } from '../../core/toolAgent/config.js'; -import { getDefaultSystemPrompt } from '../../core/toolAgent/index.js'; -import { toolAgent } from '../../core/toolAgent.js'; +import { + getDefaultSystemPrompt, + getModel, + toolAgent, +} from '../../core/toolAgent/index.js'; import { Tool, ToolContext } from '../../core/types.js'; import { getTools } from '../getTools.js';