From 6746351dfd989b43a7c8eb0d8deaf7aa189225da Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 15:16:06 -0500 Subject: [PATCH 01/14] partial conversion. --- packages/agent/package.json | 5 +- packages/agent/src/core/tokens.ts | 5 +- .../agent/src/core/toolAgent.respawn.test.ts | 29 +- packages/agent/src/core/toolAgent.test.ts | 3 +- packages/agent/src/core/toolAgent.ts | 150 ++++---- .../agent/src/tools/interaction/subAgent.ts | 3 +- packages/cli/src/index.ts | 27 +- pnpm-lock.yaml | 324 +++++++++++------- 8 files changed, 306 insertions(+), 240 deletions(-) diff --git a/packages/agent/package.json b/packages/agent/package.json index 6126113..c2c5578 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -44,13 +44,16 @@ "author": "Ben Houston", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "^0.37", + "@ai-sdk/anthropic": "^1.1.13", + "@ai-sdk/openai": "^1.2.0", "@mozilla/readability": "^0.5.0", "@playwright/test": "^1.50.1", "@vitest/browser": "^3.0.5", + "ai": "^4.1.50", "chalk": "^5", "dotenv": "^16", "jsdom": "^26.0.0", + "ollama-ai-provider": "^1.2.0", "playwright": "^1.50.1", "uuid": "^11", "zod": "^3", diff --git a/packages/agent/src/core/tokens.ts b/packages/agent/src/core/tokens.ts index e1d99da..ebad962 100644 --- a/packages/agent/src/core/tokens.ts +++ b/packages/agent/src/core/tokens.ts @@ -1,4 +1,4 @@ -import Anthropic from '@anthropic-ai/sdk'; +//import Anthropic from '@anthropic-ai/sdk'; import { LogLevel } from '../utils/logger.js'; @@ -34,6 +34,7 @@ export class TokenUsage { return usage; } + /* static fromMessage(message: Anthropic.Message) { const usage = new TokenUsage(); usage.input = message.usage.input_tokens; @@ -41,7 +42,7 @@ export class TokenUsage { usage.cacheReads = message.usage.cache_read_input_tokens ?? 0; usage.output = message.usage.output_tokens; return usage; - } + }*/ static sum(usages: TokenUsage[]) { const usage = new TokenUsage(); diff --git a/packages/agent/src/core/toolAgent.respawn.test.ts b/packages/agent/src/core/toolAgent.respawn.test.ts index be5c49a..40046d9 100644 --- a/packages/agent/src/core/toolAgent.respawn.test.ts +++ b/packages/agent/src/core/toolAgent.respawn.test.ts @@ -1,3 +1,4 @@ +import { anthropic } from '@ai-sdk/anthropic'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { toolAgent } from '../../src/core/toolAgent.js'; @@ -15,32 +16,6 @@ const toolContext: ToolContext = { pageFilter: 'simple', tokenTracker: new TokenTracker(), }; -// Mock Anthropic SDK -vi.mock('@anthropic-ai/sdk', () => { - return { - default: vi.fn().mockImplementation(() => ({ - messages: { - create: vi - .fn() - .mockResolvedValueOnce({ - content: [ - { - type: 'tool_use', - name: 'respawn', - id: 'test-id', - input: { respawnContext: 'new context' }, - }, - ], - usage: { input_tokens: 10, output_tokens: 10 }, - }) - .mockResolvedValueOnce({ - content: [], - usage: { input_tokens: 5, output_tokens: 5 }, - }), - }, - })), - }; -}); describe('toolAgent respawn functionality', () => { const tools = getTools(); @@ -56,7 +31,7 @@ describe('toolAgent respawn functionality', () => { tools, { maxIterations: 2, // Need at least 2 iterations for respawn + empty response - model: 'test-model', + model: anthropic('claude-3-7-sonnet-20250219'), maxTokens: 100, temperature: 0, getSystemPrompt: () => 'test system prompt', diff --git a/packages/agent/src/core/toolAgent.test.ts b/packages/agent/src/core/toolAgent.test.ts index 43bf116..47e10d8 100644 --- a/packages/agent/src/core/toolAgent.test.ts +++ b/packages/agent/src/core/toolAgent.test.ts @@ -1,3 +1,4 @@ +import { anthropic } from '@ai-sdk/anthropic'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { MockLogger } from '../utils/mockLogger.js'; @@ -19,7 +20,7 @@ const toolContext: ToolContext = { // Mock configuration for testing const testConfig = { maxIterations: 50, - model: 'claude-3-7-sonnet-latest', + model: anthropic('claude-3-7-sonnet-20250219'), maxTokens: 4096, temperature: 0.7, getSystemPrompt: () => 'Test system prompt', diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 4a7c9ae..33f373b 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -1,19 +1,23 @@ import { execSync } from 'child_process'; -import Anthropic from '@anthropic-ai/sdk'; -import { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages/messages.js'; +import { anthropic } from '@ai-sdk/anthropic'; +import { + CoreMessage, + CoreToolMessage, + generateText, + ToolResultPart, + ToolSet, +} from 'ai'; import chalk from 'chalk'; import { getAnthropicApiKeyError } from '../utils/errors.js'; import { executeToolCall } from './executeToolCall.js'; -import { TokenTracker, TokenUsage } from './tokens.js'; +import { TokenTracker } from './tokens.js'; import { Tool, - TextContent, ToolUseContent, ToolResultContent, - Message, ToolContext, } from './types.js'; @@ -24,7 +28,7 @@ export interface ToolAgentResult { const CONFIG = { maxIterations: 200, - model: 'claude-3-7-sonnet-latest', + model: anthropic('claude-3-7-sonnet-20250219'), maxTokens: 4096, temperature: 0.7, getSystemPrompt: () => { @@ -86,9 +90,9 @@ const CONFIG = { interface ToolCallResult { sequenceCompleted: boolean; completionResult?: string; - toolResults: ToolResultContent[]; + toolResults: ToolResultPart[]; } - +/* function processResponse(response: Anthropic.Message) { const content: (TextContent | ToolUseContent)[] = []; const toolCalls: ToolUseContent[] = []; @@ -110,11 +114,12 @@ function processResponse(response: Anthropic.Message) { return { content, toolCalls }; } +*/ async function executeTools( toolCalls: ToolUseContent[], tools: Tool[], - messages: Message[], + messages: CoreMessage[], context: ToolContext, ): Promise { if (toolCalls.length === 0) { @@ -132,10 +137,11 @@ async function executeTools( sequenceCompleted: false, toolResults: [ { - type: 'tool_result', - tool_use_id: respawnCall.id, - content: 'Respawn initiated', - }, + type: 'tool-result', + toolCallId: respawnCall.id, + toolName: respawnCall.name, + result: { success: true }, + } satisfies ToolResultPart, ], respawn: { context: respawnCall.input.respawnContext, @@ -143,7 +149,7 @@ async function executeTools( }; } - const results = await Promise.all( + const toolResults: ToolResultPart[] = await Promise.all( toolCalls.map(async (call) => { let toolResult = ''; try { @@ -155,35 +161,36 @@ async function executeTools( toolResult = `Error: Exception thrown during tool execution. Type: ${error.constructor.name}, Message: ${error.message}`; } return { - type: 'tool_result' as const, - tool_use_id: call.id, - content: toolResult, - isComplete: call.name === 'sequenceComplete', - }; + type: 'tool-result', + toolCallId: call.id, + toolName: call.name, + result: JSON.parse(toolResult) satisfies ToolResultContent, + } satisfies ToolResultPart; }), ); - const toolResults = results.map(({ type, tool_use_id, content }) => ({ - type, - tool_use_id, - content, - })); - - const sequenceCompleted = results.some((r) => r.isComplete); - const completionResult = results.find((r) => r.isComplete)?.content; + const sequenceCompletedTool = toolResults.find( + (r) => r.toolName === 'sequenceComplete', + ); + const completionResult = sequenceCompletedTool?.result as string; messages.push({ - role: 'user', + role: 'tool', content: toolResults, - }); + } satisfies CoreToolMessage); - if (sequenceCompleted) { + if (sequenceCompletedTool) { logger.verbose('Sequence completed', { completionResult }); } - return { sequenceCompleted, completionResult, toolResults }; + return { + sequenceCompleted: sequenceCompletedTool !== undefined, + completionResult, + toolResults, + }; } +/* // a function that takes a list of messages and returns a list of messages but with the last message having a cache_control of ephemeral function addCacheControlToTools(messages: T[]): T[] { return messages.map((m, i) => ({ @@ -238,7 +245,7 @@ function addCacheControlToMessages( : m.content, }; }); -} +}*/ export const toolAgent = async ( initialPrompt: string, @@ -256,8 +263,8 @@ export const toolAgent = async ( const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) throw new Error(getAnthropicApiKeyError()); - const client = new Anthropic({ apiKey }); - const messages: Message[] = [ + // const client = new Anthropic({ apiKey }); + const messages: CoreMessage[] = [ { role: 'user', content: [{ type: 'text', text: initialPrompt }], @@ -278,32 +285,31 @@ export const toolAgent = async ( interactions++; - // Create request parameters - const requestParams: Anthropic.MessageCreateParams = { - model: config.model, - max_tokens: config.maxTokens, - temperature: config.temperature, - messages: addCacheControlToMessages(messages), - system: [ - { - type: 'text', - text: systemPrompt, - cache_control: { type: 'ephemeral' }, - }, - ], - tools: addCacheControlToTools( - tools.map((t) => ({ - name: t.name, - description: t.description, - input_schema: t.parameters as Anthropic.Tool.InputSchema, - })), - ), - tool_choice: { type: 'auto' }, - }; + const toolSet: ToolSet = {}; + tools.forEach((tool) => { + toolSet[tool.name] = { + description: tool.description, + parameters: tool.parameters, + }; + }); + const { text, reasoning, reasoningDetails, toolCalls, toolResults } = + await generateText({ + model: config.model, + temperature: config.temperature, + messages, + system: systemPrompt, + tools: toolSet, + toolChoice: 'auto', + }); - const response = await client.messages.create(requestParams); + const localToolCalls: ToolUseContent[] = toolCalls.map((call) => ({ + type: 'tool_use', + name: call.toolName, + id: call.toolCallId, + input: call.args, + })); - if (!response.content.length) { + if (!text.length) { // Instead of treating empty response as completion, remind the agent logger.verbose('Received empty response from agent, sending reminder'); messages.push({ @@ -319,31 +325,25 @@ export const toolAgent = async ( } // Track both regular and cached token usage - const tokenUsagePerMessage = TokenUsage.fromMessage(response); - tokenTracker.tokenUsage.add(tokenUsagePerMessage); + //const tokenUsagePerMessage = TokenUsage.fromMessage(response); + //tokenTracker.tokenUsage.add(tokenUsagePerMessage); - const { content, toolCalls } = processResponse(response); messages.push({ role: 'assistant', - content, + content: [{ type: 'text', text: text }], }); - // Log the assistant's message - const assistantMessage = content - .filter((c) => c.type === 'text') - .map((c) => c.text) - .join('\\n'); - if (assistantMessage) { - logger.info(assistantMessage); + if (text) { + logger.info(text); } - logger.log( + /*logger.log( tokenTracker.logLevel, chalk.blue(`[Token Usage/Message] ${tokenUsagePerMessage.toString()}`), - ); + );*/ const { sequenceCompleted, completionResult, respawn } = await executeTools( - toolCalls, + localToolCalls, tools, messages, context, @@ -361,10 +361,8 @@ export const toolAgent = async ( } if (sequenceCompleted) { - const result = { - result: - completionResult ?? - 'Sequence explicitly completed with an empty result', + const result: ToolAgentResult = { + result: completionResult ?? 'Sequence explicitly completed', interactions, }; logger.log( diff --git a/packages/agent/src/tools/interaction/subAgent.ts b/packages/agent/src/tools/interaction/subAgent.ts index 18f8715..87e5755 100644 --- a/packages/agent/src/tools/interaction/subAgent.ts +++ b/packages/agent/src/tools/interaction/subAgent.ts @@ -1,3 +1,4 @@ +import { anthropic } from '@ai-sdk/anthropic'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; @@ -47,7 +48,7 @@ type ReturnType = z.infer; // Sub-agent specific configuration const subAgentConfig = { maxIterations: 50, - model: process.env.AGENT_MODEL || 'claude-3-opus-20240229', + model: anthropic('claude-3-7-sonnet-20250219'), maxTokens: 4096, temperature: 0.7, getSystemPrompt: () => { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 338de52..8a2f968 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,19 +1,19 @@ import { createRequire } from 'module'; -import { join } from 'path'; -import { fileURLToPath } from 'url'; import * as dotenv from 'dotenv'; import sourceMapSupport from 'source-map-support'; -import yargs from 'yargs'; +import yargs, { CommandModule } from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { fileCommands } from 'yargs-file-commands'; + +import { command as defaultCommand } from './commands/$default.js'; +import { command as testSentryCommand } from './commands/test-sentry.js'; +import { command as toolsCommand } from './commands/tools.js'; // Initialize Sentry as early as possible +import { sharedOptions } from './options.js'; import { initSentry, captureException } from './sentry/index.js'; initSentry(); -import { sharedOptions } from './options.js'; - import type { PackageJson } from 'type-fest'; // Add global declaration for our patched toolAgent @@ -26,10 +26,6 @@ const main = async () => { const require = createRequire(import.meta.url); const packageInfo = require('../package.json') as PackageJson; - // Get the directory where commands are located - const __filename = fileURLToPath(import.meta.url); - const commandsDir = join(__filename, '..', 'commands'); - // Set up yargs with the new CLI interface await yargs(hideBin(process.argv)) .scriptName(packageInfo.name!) @@ -37,12 +33,11 @@ const main = async () => { .options(sharedOptions) .alias('h', 'help') .alias('V', 'version') - .command( - await fileCommands({ - commandDirs: [commandsDir], - logLevel: 'info', - }), - ) + .command([ + defaultCommand, + testSentryCommand, + toolsCommand, + ] as CommandModule[]) .strict() .showHelpOnFail(true) .help().argv; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2a1fd5..94c11c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,9 +54,12 @@ importers: packages/agent: dependencies: - '@anthropic-ai/sdk': - specifier: ^0.37 - version: 0.37.0 + '@ai-sdk/anthropic': + specifier: ^1.1.13 + version: 1.1.13(zod@3.24.2) + '@ai-sdk/openai': + specifier: ^1.2.0 + version: 1.2.0(zod@3.24.2) '@mozilla/readability': specifier: ^0.5.0 version: 0.5.0 @@ -66,6 +69,9 @@ importers: '@vitest/browser': specifier: ^3.0.5 version: 3.0.6(@types/node@18.19.76)(playwright@1.50.1)(typescript@5.7.3)(vite@6.1.1(@types/node@18.19.76)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.0.6) + ai: + specifier: ^4.1.50 + version: 4.1.50(react@19.0.0)(zod@3.24.2) chalk: specifier: ^5 version: 5.4.1 @@ -75,6 +81,9 @@ importers: jsdom: specifier: ^26.0.0 version: 26.0.0 + ollama-ai-provider: + specifier: ^1.2.0 + version: 1.2.0(zod@3.24.2) playwright: specifier: ^1.50.1 version: 1.50.1 @@ -167,8 +176,51 @@ importers: packages: - '@anthropic-ai/sdk@0.37.0': - resolution: {integrity: sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==} + '@ai-sdk/anthropic@1.1.13': + resolution: {integrity: sha512-dBivw7ggokys0c9UmbhxHW36S+EHMQEHk/hVcakGO3sMEe6Vi0dR575xDjXJqs8uZPAmbcZjNb1s89U8cA0Y+Q==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/openai@1.2.0': + resolution: {integrity: sha512-tzxH6OxKL5ffts4zJPdziQSJGGpSrQcJmuSrE92jCt7pJ4PAU5Dx4tjNNFIU8lSfwarLnywejZEt3Fz0uQZZOQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/provider-utils@2.1.10': + resolution: {integrity: sha512-4GZ8GHjOFxePFzkl3q42AU0DQOtTQ5w09vmaWUf/pKFXJPizlnzKSUkF0f+VkapIUfDugyMqPMT1ge8XQzVI7Q==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider@1.0.9': + resolution: {integrity: sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.1.20': + resolution: {integrity: sha512-4QOM9fR9SryaRraybckDjrhl1O6XejqELdKmrM5g9y9eLnWAfjwF+W1aN0knkSHzbbjMqN77sy9B9yL8EuJbDw==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + + '@ai-sdk/ui-utils@1.1.16': + resolution: {integrity: sha512-jfblR2yZVISmNK2zyNzJZFtkgX57WDAUQXcmn3XUBJyo8LFsADu+/vYMn5AOyBi9qJT0RBk11PEtIxIqvByw3Q==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true '@asamuzakjp/css-color@2.8.3': resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} @@ -1080,6 +1132,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -1095,18 +1150,12 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node-fetch@2.6.12': - resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} - '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} '@types/node@18.19.76': resolution: {integrity: sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==} - '@types/node@20.17.19': - resolution: {integrity: sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==} - '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} @@ -1225,10 +1274,6 @@ packages: '@vitest/utils@3.0.6': resolution: {integrity: sha512-18ktZpf4GQFTbf9jK543uspU03Q2qya7ZGya5yiZ0Gx0nnnalBvd5ZBislbl2EhLjM8A8rt4OilqKG7QwcGkvQ==} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -1248,9 +1293,17 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} + ai@4.1.50: + resolution: {integrity: sha512-YBNeemrJKDrxoBQd3V9aaxhKm5q5YyRcF7PZE7W0NmLuvsdva/1aQNYTAsxs47gQFdvqfYmlFy4B0E+356OlPA==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1501,6 +1554,9 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1722,9 +1778,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} + eventsource-parser@3.0.0: + resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} + engines: {node: '>=18.0.0'} expect-type@1.1.0: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} @@ -1795,17 +1851,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - form-data-encoder@1.7.2: - resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} - form-data@4.0.2: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} - formdata-node@4.4.1: - resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} - engines: {node: '>= 12.20'} - forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -1945,9 +1994,6 @@ packages: resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} hasBin: true - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -2135,6 +2181,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -2142,6 +2191,11 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -2257,19 +2311,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - nwsapi@2.2.16: resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} @@ -2297,6 +2338,15 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + ollama-ai-provider@1.2.0: + resolution: {integrity: sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2356,6 +2406,9 @@ packages: parse5@7.2.1: resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2487,6 +2540,10 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -2572,6 +2629,9 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2712,6 +2772,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swr@2.3.2: + resolution: {integrity: sha512-RosxFpiabojs75IwQ316DGoDRmOqtiAj0tg8wCcbEu4CiLZBs/a9QNtHV7TUfDXmmlgqij/NqzKq/eLelyv9xA==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -2732,6 +2797,10 @@ packages: engines: {node: '>=10'} hasBin: true + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2781,9 +2850,6 @@ packages: resolution: {integrity: sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==} engines: {node: '>=16'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tr46@5.0.0: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} @@ -2852,9 +2918,6 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -2869,6 +2932,11 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true @@ -2950,13 +3018,6 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - web-streams-polyfill@4.0.0-beta.3: - resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} - engines: {node: '>= 14'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -2973,9 +3034,6 @@ packages: resolution: {integrity: sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==} engines: {node: '>=18'} - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3082,17 +3140,48 @@ packages: snapshots: - '@anthropic-ai/sdk@0.37.0': + '@ai-sdk/anthropic@1.1.13(zod@3.24.2)': dependencies: - '@types/node': 18.19.76 - '@types/node-fetch': 2.6.12 - abort-controller: 3.0.0 - agentkeepalive: 4.6.0 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.24.2) + zod: 3.24.2 + + '@ai-sdk/openai@1.2.0(zod@3.24.2)': + dependencies: + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.24.2) + zod: 3.24.2 + + '@ai-sdk/provider-utils@2.1.10(zod@3.24.2)': + dependencies: + '@ai-sdk/provider': 1.0.9 + eventsource-parser: 3.0.0 + nanoid: 3.3.8 + secure-json-parse: 2.7.0 + optionalDependencies: + zod: 3.24.2 + + '@ai-sdk/provider@1.0.9': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.1.20(react@19.0.0)(zod@3.24.2)': + dependencies: + '@ai-sdk/provider-utils': 2.1.10(zod@3.24.2) + '@ai-sdk/ui-utils': 1.1.16(zod@3.24.2) + swr: 2.3.2(react@19.0.0) + throttleit: 2.1.0 + optionalDependencies: + react: 19.0.0 + zod: 3.24.2 + + '@ai-sdk/ui-utils@1.1.16(zod@3.24.2)': + dependencies: + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.24.2) + zod-to-json-schema: 3.24.3(zod@3.24.2) + optionalDependencies: + zod: 3.24.2 '@asamuzakjp/css-color@2.8.3': dependencies: @@ -4013,6 +4102,8 @@ snapshots: '@types/ms': 2.1.0 optional: true + '@types/diff-match-patch@1.0.36': {} + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} @@ -4026,21 +4117,12 @@ snapshots: dependencies: '@types/node': 18.19.76 - '@types/node-fetch@2.6.12': - dependencies: - '@types/node': 20.17.19 - form-data: 4.0.2 - '@types/node@12.20.55': {} '@types/node@18.19.76': dependencies: undici-types: 5.26.5 - '@types/node@20.17.19': - dependencies: - undici-types: 6.19.8 - '@types/pg-pool@2.0.6': dependencies: '@types/pg': 8.6.1 @@ -4208,10 +4290,6 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - acorn-import-attributes@1.9.5(acorn@8.14.0): dependencies: acorn: 8.14.0 @@ -4224,9 +4302,17 @@ snapshots: agent-base@7.1.3: {} - agentkeepalive@4.6.0: + ai@4.1.50(react@19.0.0)(zod@3.24.2): dependencies: - humanize-ms: 1.2.1 + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.24.2) + '@ai-sdk/react': 1.1.20(react@19.0.0)(zod@3.24.2) + '@ai-sdk/ui-utils': 1.1.16(zod@3.24.2) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + optionalDependencies: + react: 19.0.0 + zod: 3.24.2 ajv@6.12.6: dependencies: @@ -4478,6 +4564,8 @@ snapshots: detect-indent@6.1.0: {} + diff-match-patch@1.0.5: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -4817,7 +4905,7 @@ snapshots: esutils@2.0.3: {} - event-target-shim@5.0.1: {} + eventsource-parser@3.0.0: {} expect-type@1.1.0: {} @@ -4887,8 +4975,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data-encoder@1.7.2: {} - form-data@4.0.2: dependencies: asynckit: 0.4.0 @@ -4896,11 +4982,6 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 - formdata-node@4.4.1: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 4.0.0-beta.3 - forwarded-parse@2.1.2: {} fs-extra@7.0.1: @@ -5058,10 +5139,6 @@ snapshots: human-id@4.1.1: {} - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -5273,12 +5350,20 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: dependencies: minimist: 1.2.8 + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.4.1 + diff-match-patch: 1.0.5 + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -5386,12 +5471,6 @@ snapshots: natural-compare@1.4.0: {} - node-domexception@1.0.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - nwsapi@2.2.16: {} object-inspect@1.13.4: {} @@ -5427,6 +5506,14 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + ollama-ai-provider@1.2.0(zod@3.24.2): + dependencies: + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.24.2) + partial-json: 0.1.7 + optionalDependencies: + zod: 3.24.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5484,6 +5571,8 @@ snapshots: dependencies: entities: 4.5.0 + partial-json@0.1.7: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -5582,6 +5671,8 @@ snapshots: react-is@17.0.2: {} + react@19.0.0: {} + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -5702,6 +5793,8 @@ snapshots: dependencies: xmlchars: 2.2.0 + secure-json-parse@2.7.0: {} + semver@6.3.1: {} semver@7.7.1: {} @@ -5855,6 +5948,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swr@2.3.2(react@19.0.0): + dependencies: + dequal: 2.0.3 + react: 19.0.0 + use-sync-external-store: 1.4.0(react@19.0.0) + symbol-tree@3.2.4: {} synckit@0.9.2: @@ -5874,6 +5973,8 @@ snapshots: source-map-support: 0.5.21 optional: true + throttleit@2.1.0: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -5916,8 +6017,6 @@ snapshots: dependencies: tldts: 6.1.79 - tr46@0.0.3: {} - tr46@5.0.0: dependencies: punycode: 2.3.1 @@ -6005,8 +6104,6 @@ snapshots: undici-types@5.26.5: {} - undici-types@6.19.8: {} - universalify@0.1.2: {} universalify@0.2.0: {} @@ -6020,6 +6117,10 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + use-sync-external-store@1.4.0(react@19.0.0): + dependencies: + react: 19.0.0 + uuid@11.1.0: {} vite-node@3.0.6(@types/node@18.19.76)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): @@ -6101,10 +6202,6 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - web-streams-polyfill@4.0.0-beta.3: {} - - webidl-conversions@3.0.1: {} - webidl-conversions@7.0.0: {} whatwg-encoding@3.1.1: @@ -6118,11 +6215,6 @@ snapshots: tr46: 5.0.0 webidl-conversions: 7.0.0 - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 From a51b970e2e7a07e6f59a17edb355aaa0f39d847f Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 15:46:19 -0500 Subject: [PATCH 02/14] Convert from JsonSchema7Type to ZodSchema for tool parameters and returns (issue #56) --- .changeset/convert-to-zod.md | 6 + packages/agent/src/core/toolAgent.test.ts | 23 +- packages/agent/src/core/toolAgent.ts | 23 +- packages/agent/src/core/types.ts | 9 +- .../agent/src/tools/browser/browseMessage.ts | 6 +- .../src/tools/browser/browseMessage.ts.bak | 242 ++++++++++++++++++ .../agent/src/tools/browser/browseStart.ts | 6 +- .../src/tools/browser/browseStart.ts.bak | 163 ++++++++++++ .../agent/src/tools/interaction/subAgent.ts | 6 +- .../src/tools/interaction/subAgent.ts.bak | 116 +++++++++ .../agent/src/tools/interaction/userPrompt.ts | 6 +- .../src/tools/interaction/userPrompt.ts.bak | 33 +++ packages/agent/src/tools/io/fetch.ts | 6 +- packages/agent/src/tools/io/readFile.ts | 6 +- packages/agent/src/tools/io/updateFile.ts | 6 +- packages/agent/src/tools/system/respawn.ts | 28 +- .../agent/src/tools/system/respawn.ts.bak | 34 +++ .../src/tools/system/sequenceComplete.ts | 6 +- .../src/tools/system/sequenceComplete.ts.bak | 28 ++ .../agent/src/tools/system/shellExecute.ts | 6 +- .../agent/src/tools/system/shellMessage.ts | 6 +- .../src/tools/system/shellMessage.ts.bak | 175 +++++++++++++ packages/agent/src/tools/system/shellStart.ts | 6 +- packages/agent/src/tools/system/sleep.ts | 6 +- packages/agent/src/tools/system/sleep.ts.bak | 43 ++++ packages/cli/src/commands/$default.ts | 7 +- packages/cli/src/commands/tools.ts | 14 +- 27 files changed, 953 insertions(+), 63 deletions(-) create mode 100644 .changeset/convert-to-zod.md create mode 100644 packages/agent/src/tools/browser/browseMessage.ts.bak create mode 100644 packages/agent/src/tools/browser/browseStart.ts.bak create mode 100644 packages/agent/src/tools/interaction/subAgent.ts.bak create mode 100644 packages/agent/src/tools/interaction/userPrompt.ts.bak create mode 100644 packages/agent/src/tools/system/respawn.ts.bak create mode 100644 packages/agent/src/tools/system/sequenceComplete.ts.bak create mode 100644 packages/agent/src/tools/system/shellMessage.ts.bak create mode 100644 packages/agent/src/tools/system/sleep.ts.bak diff --git a/.changeset/convert-to-zod.md b/.changeset/convert-to-zod.md new file mode 100644 index 0000000..636f167 --- /dev/null +++ b/.changeset/convert-to-zod.md @@ -0,0 +1,6 @@ +--- +"mycoder-agent": minor +"mycoder": minor +--- + +Convert from JsonSchema7Type to ZodSchema for tool parameters and returns, required for Vercel AI SDK integration. diff --git a/packages/agent/src/core/toolAgent.test.ts b/packages/agent/src/core/toolAgent.test.ts index 47e10d8..dc63da0 100644 --- a/packages/agent/src/core/toolAgent.test.ts +++ b/packages/agent/src/core/toolAgent.test.ts @@ -1,5 +1,6 @@ import { anthropic } from '@ai-sdk/anthropic'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { z } from 'zod'; import { MockLogger } from '../utils/mockLogger.js'; @@ -65,7 +66,11 @@ describe('toolAgent', () => { const mockTool: Tool = { name: 'mockTool', description: 'A mock tool for testing', - parameters: { + parameters: z.object({ + input: z.string().describe('Test input'), + }), + returns: z.string().describe('The processed result'), + parametersJsonSchema: { type: 'object', properties: { input: { @@ -75,7 +80,7 @@ describe('toolAgent', () => { }, required: ['input'], }, - returns: { + returnsJsonSchema: { type: 'string', description: 'The processed result', }, @@ -85,7 +90,11 @@ describe('toolAgent', () => { const sequenceCompleteTool: Tool = { name: 'sequenceComplete', description: 'Completes the sequence', - parameters: { + parameters: z.object({ + result: z.string().describe('The final result'), + }), + returns: z.string().describe('The final result'), + parametersJsonSchema: { type: 'object', properties: { result: { @@ -95,7 +104,7 @@ describe('toolAgent', () => { }, required: ['result'], }, - returns: { + returnsJsonSchema: { type: 'string', description: 'The final result', }, @@ -134,12 +143,14 @@ describe('toolAgent', () => { const errorTool: Tool = { name: 'errorTool', description: 'A tool that always fails', - parameters: { + parameters: z.object({}), + returns: z.string().describe('Error message'), + parametersJsonSchema: { type: 'object', properties: {}, required: [], }, - returns: { + returnsJsonSchema: { type: 'string', description: 'Error message', }, diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 33f373b..c1bc896 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -7,6 +7,7 @@ import { generateText, ToolResultPart, ToolSet, + tool as makeTool, } from 'ai'; import chalk from 'chalk'; @@ -287,20 +288,22 @@ export const toolAgent = async ( const toolSet: ToolSet = {}; tools.forEach((tool) => { - toolSet[tool.name] = { + toolSet[tool.name] = makeTool({ description: tool.description, parameters: tool.parameters, - }; + }); }); + console.log('toolSet', toolSet); + const generateTextProps = { + model: config.model, + temperature: config.temperature, + messages, + system: systemPrompt, + tools: toolSet, + }; + console.log('generateTextProps', generateTextProps); const { text, reasoning, reasoningDetails, toolCalls, toolResults } = - await generateText({ - model: config.model, - temperature: config.temperature, - messages, - system: systemPrompt, - tools: toolSet, - toolChoice: 'auto', - }); + await generateText(generateTextProps); const localToolCalls: ToolUseContent[] = toolCalls.map((call) => ({ type: 'tool_use', diff --git a/packages/agent/src/core/types.ts b/packages/agent/src/core/types.ts index 328f146..696ccae 100644 --- a/packages/agent/src/core/types.ts +++ b/packages/agent/src/core/types.ts @@ -1,3 +1,4 @@ +import { z } from 'zod'; import { JsonSchema7Type } from 'zod-to-json-schema'; import { Logger } from '../utils/logger.js'; @@ -20,14 +21,18 @@ export type ToolContext = { export type Tool, TReturn = any> = { name: string; description: string; - parameters: JsonSchema7Type; - returns: JsonSchema7Type; + parameters: z.ZodType; + returns: z.ZodType; logPrefix?: string; logParameters?: (params: TParams, context: ToolContext) => void; logReturns?: (returns: TReturn, context: ToolContext) => void; execute: (params: TParams, context: ToolContext) => Promise; + + // Keep JsonSchema7Type for backward compatibility and Vercel AI SDK integration + parametersJsonSchema?: JsonSchema7Type; + returnsJsonSchema?: JsonSchema7Type; }; export type ToolCall = { diff --git a/packages/agent/src/tools/browser/browseMessage.ts b/packages/agent/src/tools/browser/browseMessage.ts index 49547da..9b9e299 100644 --- a/packages/agent/src/tools/browser/browseMessage.ts +++ b/packages/agent/src/tools/browser/browseMessage.ts @@ -70,8 +70,10 @@ export const browseMessageTool: Tool = { name: 'browseMessage', logPrefix: '🏄', description: 'Performs actions in an active browser session', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returns: returnSchema, + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { instanceId, action }, diff --git a/packages/agent/src/tools/browser/browseMessage.ts.bak b/packages/agent/src/tools/browser/browseMessage.ts.bak new file mode 100644 index 0000000..49547da --- /dev/null +++ b/packages/agent/src/tools/browser/browseMessage.ts.bak @@ -0,0 +1,242 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from '../../core/types.js'; +import { errorToString } from '../../utils/errorToString.js'; +import { sleep } from '../../utils/sleep.js'; + +import { filterPageContent } from './filterPageContent.js'; +import { browserSessions, type BrowserAction, SelectorType } from './types.js'; + +// Schema for browser action +const browserActionSchema = z + .object({ + actionType: z.enum(['goto', 'click', 'type', 'wait', 'content', 'close']), + url: z + .string() + .url() + .optional() + .describe('URL to navigate to if "goto" actionType'), + selector: z + .string() + .optional() + .describe('Selector to click if "click" actionType'), + selectorType: z + .nativeEnum(SelectorType) + .optional() + .describe('Type of selector if "click" actionType'), + text: z + .string() + .optional() + .describe( + 'Text to type if "type" actionType, for other actionType, this is ignored', + ), + }) + .describe('Browser action to perform'); + +// Main parameter schema +const parameterSchema = z.object({ + instanceId: z.string().describe('The ID returned by browseStart'), + action: browserActionSchema, + description: z + .string() + .max(80) + .describe('The reason for this browser action (max 80 chars)'), +}); + +// Return schema +const returnSchema = z.object({ + status: z.string(), + content: z.string().optional(), + error: z.string().optional(), +}); + +type Parameters = z.infer; +type ReturnType = z.infer; + +// Helper function to handle selectors +const getSelector = (selector: string, type?: SelectorType): string => { + switch (type) { + case SelectorType.XPATH: + return `xpath=${selector}`; + case SelectorType.TEXT: + return `text=${selector}`; + default: + return selector; // CSS selector is default + } +}; + +export const browseMessageTool: Tool = { + name: 'browseMessage', + logPrefix: '🏄', + description: 'Performs actions in an active browser session', + parameters: zodToJsonSchema(parameterSchema), + returns: zodToJsonSchema(returnSchema), + + execute: async ( + { instanceId, action }, + { logger, pageFilter }, + ): Promise => { + // Validate action format + if (!action || typeof action !== 'object') { + logger.error('Invalid action format: action must be an object'); + return { + status: 'error', + error: 'Invalid action format: action must be an object', + }; + } + + if (!action.actionType) { + logger.error('Invalid action format: actionType is required'); + return { + status: 'error', + error: 'Invalid action format: actionType is required', + }; + } + + logger.verbose(`Executing browser action: ${action.actionType}`); + logger.verbose(`Webpage processing mode: ${pageFilter}`); + + try { + const session = browserSessions.get(instanceId); + if (!session) { + throw new Error(`No browser session found with ID ${instanceId}`); + } + + const { page } = session; + + switch (action.actionType) { + case 'goto': { + if (!action.url) { + throw new Error('URL required for goto action'); + } + + try { + // Try with 'domcontentloaded' first which is more reliable than 'networkidle' + logger.verbose( + `Navigating to ${action.url} with 'domcontentloaded' waitUntil`, + ); + await page.goto(action.url, { waitUntil: 'domcontentloaded' }); + await sleep(3000); + const content = await filterPageContent(page, pageFilter); + logger.verbose(`Content: ${content}`); + logger.verbose( + 'Navigation completed with domcontentloaded strategy', + ); + logger.verbose(`Content length: ${content.length} characters`); + return { status: 'success', content }; + } catch (navError) { + // If that fails, try with no waitUntil option + logger.warn( + `Failed with domcontentloaded strategy: ${errorToString(navError)}`, + ); + logger.verbose( + `Retrying navigation to ${action.url} with no waitUntil option`, + ); + + try { + await page.goto(action.url); + await sleep(3000); + const content = await filterPageContent(page, pageFilter); + logger.verbose(`Content: ${content}`); + logger.verbose('Navigation completed with basic strategy'); + return { status: 'success', content }; + } catch (innerError) { + logger.error( + `Failed with basic navigation strategy: ${errorToString(innerError)}`, + ); + throw innerError; // Re-throw to be caught by outer catch block + } + } + } + + case 'click': { + if (!action.selector) { + throw new Error('Selector required for click action'); + } + const clickSelector = getSelector( + action.selector, + action.selectorType, + ); + await page.click(clickSelector); + await sleep(1000); // Wait for any content changes after click + const content = await filterPageContent(page, pageFilter); + logger.verbose( + `Click action completed on selector: ${clickSelector}`, + ); + return { status: 'success', content }; + } + + case 'type': { + if (!action.selector || !action.text) { + throw new Error('Selector and text required for type action'); + } + const typeSelector = getSelector( + action.selector, + action.selectorType, + ); + await page.fill(typeSelector, action.text); + logger.verbose(`Type action completed on selector: ${typeSelector}`); + return { status: 'success' }; + } + + case 'wait': { + if (!action.selector) { + throw new Error('Selector required for wait action'); + } + const waitSelector = getSelector( + action.selector, + action.selectorType, + ); + await page.waitForSelector(waitSelector); + logger.verbose(`Wait action completed for selector: ${waitSelector}`); + return { status: 'success' }; + } + + case 'content': { + const content = await filterPageContent(page, pageFilter); + logger.verbose('Page content retrieved successfully'); + logger.verbose(`Content length: ${content.length} characters`); + return { status: 'success', content }; + } + + case 'close': { + await session.page.context().close(); + await session.browser.close(); + browserSessions.delete(instanceId); + logger.verbose('Browser session closed successfully'); + return { status: 'closed' }; + } + + default: { + throw new Error( + `Unsupported action type: ${(action as BrowserAction).actionType}`, + ); + } + } + } catch (error) { + logger.error('Browser action failed:', { error }); + return { + status: 'error', + error: errorToString(error), + }; + } + }, + + logParameters: ( + { action, description }, + { logger, pageFilter = 'simple' }, + ) => { + logger.info( + `Performing browser action: ${action.actionType} with ${pageFilter} processing, ${description}`, + ); + }, + + logReturns: (output, { logger }) => { + if (output.error) { + logger.error(`Browser action failed: ${output.error}`); + } else { + logger.info(`Browser action completed with status: ${output.status}`); + } + }, +}; diff --git a/packages/agent/src/tools/browser/browseStart.ts b/packages/agent/src/tools/browser/browseStart.ts index a4c5fa6..ebab30b 100644 --- a/packages/agent/src/tools/browser/browseStart.ts +++ b/packages/agent/src/tools/browser/browseStart.ts @@ -36,8 +36,10 @@ export const browseStartTool: Tool = { name: 'browseStart', logPrefix: '🏄', description: 'Starts a new browser session with optional initial URL', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returns: returnSchema, + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { url, timeout = 30000 }, diff --git a/packages/agent/src/tools/browser/browseStart.ts.bak b/packages/agent/src/tools/browser/browseStart.ts.bak new file mode 100644 index 0000000..a4c5fa6 --- /dev/null +++ b/packages/agent/src/tools/browser/browseStart.ts.bak @@ -0,0 +1,163 @@ +import { chromium } from '@playwright/test'; +import { v4 as uuidv4 } from 'uuid'; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from '../../core/types.js'; +import { errorToString } from '../../utils/errorToString.js'; +import { sleep } from '../../utils/sleep.js'; + +import { filterPageContent } from './filterPageContent.js'; +import { browserSessions } from './types.js'; + +const parameterSchema = z.object({ + url: z.string().url().optional().describe('Initial URL to navigate to'), + timeout: z + .number() + .optional() + .describe('Default timeout in milliseconds (default: 30000)'), + description: z + .string() + .max(80) + .describe('The reason for starting this browser session (max 80 chars)'), +}); + +const returnSchema = z.object({ + instanceId: z.string(), + status: z.string(), + content: z.string().optional(), + error: z.string().optional(), +}); + +type Parameters = z.infer; +type ReturnType = z.infer; + +export const browseStartTool: Tool = { + name: 'browseStart', + logPrefix: '🏄', + description: 'Starts a new browser session with optional initial URL', + parameters: zodToJsonSchema(parameterSchema), + returns: zodToJsonSchema(returnSchema), + + execute: async ( + { url, timeout = 30000 }, + { logger, headless, userSession, pageFilter }, + ): Promise => { + logger.verbose(`Starting browser session${url ? ` at ${url}` : ''}`); + logger.verbose( + `User session mode: ${userSession ? 'enabled' : 'disabled'}`, + ); + logger.verbose(`Webpage processing mode: ${pageFilter}`); + + try { + const instanceId = uuidv4(); + + // Launch browser + const launchOptions = { + headless, + }; + + // Use system Chrome installation if userSession is true + if (userSession) { + logger.verbose('Using system Chrome installation'); + // For Chrome, we use the channel option to specify Chrome + launchOptions['channel'] = 'chrome'; + } + + const browser = await chromium.launch(launchOptions); + + // Create new context with default settings + const context = await browser.newContext({ + viewport: null, + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + serviceWorkers: 'block', // Block service workers which can cause continuous network activity + }); + + // Create new page + const page = await context.newPage(); + page.setDefaultTimeout(timeout); + + // Initialize browser session + const session = { + browser, + page, + id: instanceId, + }; + + browserSessions.set(instanceId, session); + + // Setup cleanup handlers + browser.on('disconnected', () => { + browserSessions.delete(instanceId); + }); + + // Navigate to URL if provided + let content = ''; + if (url) { + try { + // Try with 'domcontentloaded' first which is more reliable than 'networkidle' + logger.verbose( + `Navigating to ${url} with 'domcontentloaded' waitUntil`, + ); + await page.goto(url, { waitUntil: 'domcontentloaded', timeout }); + await sleep(3000); + content = await filterPageContent(page, pageFilter); + logger.verbose(`Content: ${content}`); + logger.verbose('Navigation completed with domcontentloaded strategy'); + } catch (error) { + // If that fails, try with no waitUntil option at all (most basic) + logger.warn( + `Failed with domcontentloaded strategy: ${errorToString(error)}`, + ); + logger.verbose( + `Retrying navigation to ${url} with no waitUntil option`, + ); + + try { + await page.goto(url, { timeout }); + await sleep(3000); + content = await filterPageContent(page, pageFilter); + logger.verbose(`Content: ${content}`); + logger.verbose('Navigation completed with basic strategy'); + } catch (innerError) { + logger.error( + `Failed with basic navigation strategy: ${errorToString(innerError)}`, + ); + throw innerError; // Re-throw to be caught by outer catch block + } + } + } + + logger.verbose('Browser session started successfully'); + logger.verbose(`Content length: ${content.length} characters`); + + return { + instanceId, + status: 'initialized', + content: content || undefined, + }; + } catch (error) { + logger.error(`Failed to start browser: ${errorToString(error)}`); + return { + instanceId: '', + status: 'error', + error: errorToString(error), + }; + } + }, + + logParameters: ({ url, description }, { logger, pageFilter = 'simple' }) => { + logger.info( + `Starting browser session${url ? ` at ${url}` : ''} with ${pageFilter} processing, ${description}`, + ); + }, + + logReturns: (output, { logger }) => { + if (output.error) { + logger.error(`Browser start failed: ${output.error}`); + } else { + logger.info(`Browser session started with ID: ${output.instanceId}`); + } + }, +}; diff --git a/packages/agent/src/tools/interaction/subAgent.ts b/packages/agent/src/tools/interaction/subAgent.ts index 87e5755..df33879 100644 --- a/packages/agent/src/tools/interaction/subAgent.ts +++ b/packages/agent/src/tools/interaction/subAgent.ts @@ -67,8 +67,10 @@ export const subAgentTool: Tool = { description: 'Creates a sub-agent that has access to all tools to solve a specific task', logPrefix: '🤖', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returns: returnSchema, + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async (params, context) => { // Validate parameters const { description, goal, projectContext, fileContext } = diff --git a/packages/agent/src/tools/interaction/subAgent.ts.bak b/packages/agent/src/tools/interaction/subAgent.ts.bak new file mode 100644 index 0000000..87e5755 --- /dev/null +++ b/packages/agent/src/tools/interaction/subAgent.ts.bak @@ -0,0 +1,116 @@ +import { anthropic } from '@ai-sdk/anthropic'; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { toolAgent } from '../../core/toolAgent.js'; +import { Tool } from '../../core/types.js'; +import { getTools } from '../getTools.js'; + +const parameterSchema = z.object({ + description: z + .string() + .max(80) + .describe("A brief description of the sub-agent's purpose (max 80 chars)"), + goal: z + .string() + .describe('The main objective that the sub-agent needs to achieve'), + projectContext: z + .string() + .describe('Context about the problem or environment'), + fileContext: z + .object({ + workingDirectory: z + .string() + .optional() + .describe('The directory where the sub-agent should operate'), + relevantFiles: z + .string() + .optional() + .describe( + 'A list of files, which may include ** or * wildcard characters', + ), + }) + .describe( + 'When working with files and directories, it is best to be very specific to avoid sub-agents making incorrect assumptions', + ) + .optional(), +}); + +const returnSchema = z + .string() + .describe( + 'The response from the sub-agent including its reasoning and tool usage', + ); + +type Parameters = z.infer; +type ReturnType = z.infer; + +// Sub-agent specific configuration +const subAgentConfig = { + maxIterations: 50, + model: anthropic('claude-3-7-sonnet-20250219'), + maxTokens: 4096, + temperature: 0.7, + getSystemPrompt: () => { + return [ + 'You are a focused AI sub-agent handling a specific task.', + 'You have access to the same tools as the main agent but should focus only on your assigned task.', + 'When complete, call the sequenceComplete tool with your results.', + 'Follow any specific conventions or requirements provided in the task context.', + 'Ask the main agent for clarification if critical information is missing.', + ].join('\n'); + }, +}; + +export const subAgentTool: Tool = { + name: 'subAgent', + description: + 'Creates a sub-agent that has access to all tools to solve a specific task', + logPrefix: '🤖', + parameters: zodToJsonSchema(parameterSchema), + returns: zodToJsonSchema(returnSchema), + execute: async (params, context) => { + // Validate parameters + const { description, goal, projectContext, fileContext } = + parameterSchema.parse(params); + + // Construct a well-structured prompt + const prompt = [ + `Description: ${description}`, + `Goal: ${goal}`, + `Project Context: ${projectContext}`, + fileContext + ? `\nContext:\n${[ + fileContext.workingDirectory + ? `- Working Directory: ${fileContext.workingDirectory}` + : '', + fileContext.relevantFiles + ? `- Relevant Files:\n ${fileContext.relevantFiles}` + : '', + ] + .filter(Boolean) + .join('\n')}` + : '', + ] + .filter(Boolean) + .join('\n'); + + const tools = getTools().filter((tool) => tool.name !== 'userPrompt'); + + // Update config if timeout is specified + const config = { + ...subAgentConfig, + }; + + const result = await toolAgent(prompt, tools, config, { + ...context, + workingDirectory: + fileContext?.workingDirectory ?? context.workingDirectory, + }); + return result.result; // Return the result string directly + }, + logParameters: (input, { logger }) => { + logger.info(`Delegating task "${input.description}"`); + }, + logReturns: () => {}, +}; diff --git a/packages/agent/src/tools/interaction/userPrompt.ts b/packages/agent/src/tools/interaction/userPrompt.ts index ebeaa14..9de4968 100644 --- a/packages/agent/src/tools/interaction/userPrompt.ts +++ b/packages/agent/src/tools/interaction/userPrompt.ts @@ -17,8 +17,10 @@ export const userPromptTool: Tool = { name: 'userPrompt', description: 'Prompts the user for input and returns their response', logPrefix: '🗣️', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returns: returnSchema, + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ({ prompt }, { logger }) => { logger.verbose(`Prompting user with: ${prompt}`); diff --git a/packages/agent/src/tools/interaction/userPrompt.ts.bak b/packages/agent/src/tools/interaction/userPrompt.ts.bak new file mode 100644 index 0000000..ebeaa14 --- /dev/null +++ b/packages/agent/src/tools/interaction/userPrompt.ts.bak @@ -0,0 +1,33 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from '../../core/types.js'; +import { userPrompt } from '../../utils/userPrompt.js'; + +const parameterSchema = z.object({ + prompt: z.string().describe('The prompt message to display to the user'), +}); + +const returnSchema = z.string().describe("The user's response"); + +type Parameters = z.infer; +type ReturnType = z.infer; + +export const userPromptTool: Tool = { + name: 'userPrompt', + description: 'Prompts the user for input and returns their response', + logPrefix: '🗣️', + parameters: zodToJsonSchema(parameterSchema), + returns: zodToJsonSchema(returnSchema), + execute: async ({ prompt }, { logger }) => { + logger.verbose(`Prompting user with: ${prompt}`); + + const response = await userPrompt(prompt); + + logger.verbose(`Received user response: ${response}`); + + return response; + }, + logParameters: () => {}, + logReturns: () => {}, +}; diff --git a/packages/agent/src/tools/io/fetch.ts b/packages/agent/src/tools/io/fetch.ts index b98b9dc..5982b01 100644 --- a/packages/agent/src/tools/io/fetch.ts +++ b/packages/agent/src/tools/io/fetch.ts @@ -38,8 +38,10 @@ export const fetchTool: Tool = { description: 'Executes HTTP requests using native Node.js fetch API, for using APIs, not for browsing the web.', logPrefix: '🌐', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + returns: returnSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { method, url, params, body, headers }: Parameters, { logger }, diff --git a/packages/agent/src/tools/io/readFile.ts b/packages/agent/src/tools/io/readFile.ts index 2edb166..55ee8e0 100644 --- a/packages/agent/src/tools/io/readFile.ts +++ b/packages/agent/src/tools/io/readFile.ts @@ -48,8 +48,10 @@ export const readFileTool: Tool = { name: 'readFile', description: 'Reads file content within size limits and optional range', logPrefix: '📖', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + returns: returnSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { path: filePath, range, maxSize = OUTPUT_LIMIT }, context, diff --git a/packages/agent/src/tools/io/updateFile.ts b/packages/agent/src/tools/io/updateFile.ts index 3355344..007ddf6 100644 --- a/packages/agent/src/tools/io/updateFile.ts +++ b/packages/agent/src/tools/io/updateFile.ts @@ -45,8 +45,10 @@ export const updateFileTool: Tool = { description: 'Creates a file or updates a file by rewriting, patching, or appending content', logPrefix: '📝', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + returns: returnSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { path: filePath, operation }, { logger, workingDirectory }, diff --git a/packages/agent/src/tools/system/respawn.ts b/packages/agent/src/tools/system/respawn.ts index 6b0030e..995596d 100644 --- a/packages/agent/src/tools/system/respawn.ts +++ b/packages/agent/src/tools/system/respawn.ts @@ -1,29 +1,27 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + import { Tool, ToolContext } from '../../core/types.js'; export interface RespawnInput { respawnContext: string; } +const parameterSchema = z.object({ + respawnContext: z.string().describe('The context to keep after respawning'), +}); + +const returnSchema = z.string().describe('A message indicating that the respawn has been initiated'); + export const respawnTool: Tool = { name: 'respawn', description: 'Resets the agent context to just the system prompt and provided context', logPrefix: '🔄', - parameters: { - type: 'object', - properties: { - respawnContext: { - type: 'string', - description: 'The context to keep after respawning', - }, - }, - required: ['respawnContext'], - additionalProperties: false, - }, - returns: { - type: 'string', - description: 'A message indicating that the respawn has been initiated', - }, + parameters: parameterSchema, + returns: returnSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: ( _params: Record, _context: ToolContext, diff --git a/packages/agent/src/tools/system/respawn.ts.bak b/packages/agent/src/tools/system/respawn.ts.bak new file mode 100644 index 0000000..6b0030e --- /dev/null +++ b/packages/agent/src/tools/system/respawn.ts.bak @@ -0,0 +1,34 @@ +import { Tool, ToolContext } from '../../core/types.js'; + +export interface RespawnInput { + respawnContext: string; +} + +export const respawnTool: Tool = { + name: 'respawn', + description: + 'Resets the agent context to just the system prompt and provided context', + logPrefix: '🔄', + parameters: { + type: 'object', + properties: { + respawnContext: { + type: 'string', + description: 'The context to keep after respawning', + }, + }, + required: ['respawnContext'], + additionalProperties: false, + }, + returns: { + type: 'string', + description: 'A message indicating that the respawn has been initiated', + }, + execute: ( + _params: Record, + _context: ToolContext, + ): Promise => { + // This is a special case tool - the actual respawn logic is handled in toolAgent + return Promise.resolve('Respawn initiated'); + }, +}; diff --git a/packages/agent/src/tools/system/sequenceComplete.ts b/packages/agent/src/tools/system/sequenceComplete.ts index 037ef5b..1b3a2dd 100644 --- a/packages/agent/src/tools/system/sequenceComplete.ts +++ b/packages/agent/src/tools/system/sequenceComplete.ts @@ -18,8 +18,10 @@ export const sequenceCompleteTool: Tool = { name: 'sequenceComplete', description: 'Completes the tool use sequence and returns the final result', logPrefix: '✅', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returns: returnSchema, + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: ({ result }) => Promise.resolve(result), logParameters: () => {}, logReturns: (output, { logger }) => { diff --git a/packages/agent/src/tools/system/sequenceComplete.ts.bak b/packages/agent/src/tools/system/sequenceComplete.ts.bak new file mode 100644 index 0000000..037ef5b --- /dev/null +++ b/packages/agent/src/tools/system/sequenceComplete.ts.bak @@ -0,0 +1,28 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from '../../core/types.js'; + +const parameterSchema = z.object({ + result: z.string().describe('The final result to return from the tool agent'), +}); + +const returnSchema = z + .string() + .describe('This is returned to the caller of the tool agent.'); + +type Parameters = z.infer; +type ReturnType = z.infer; + +export const sequenceCompleteTool: Tool = { + name: 'sequenceComplete', + description: 'Completes the tool use sequence and returns the final result', + logPrefix: '✅', + parameters: zodToJsonSchema(parameterSchema), + returns: zodToJsonSchema(returnSchema), + execute: ({ result }) => Promise.resolve(result), + logParameters: () => {}, + logReturns: (output, { logger }) => { + logger.info(`Completed: ${output}`); + }, +}; diff --git a/packages/agent/src/tools/system/shellExecute.ts b/packages/agent/src/tools/system/shellExecute.ts index d042734..4fbe278 100644 --- a/packages/agent/src/tools/system/shellExecute.ts +++ b/packages/agent/src/tools/system/shellExecute.ts @@ -48,8 +48,10 @@ export const shellExecuteTool: Tool = { logPrefix: '💻', description: 'Executes a bash shell command and returns its output, can do amazing things if you are a shell scripting wizard', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + returns: returnSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { command, timeout = 30000 }, diff --git a/packages/agent/src/tools/system/shellMessage.ts b/packages/agent/src/tools/system/shellMessage.ts index c774500..d25e288 100644 --- a/packages/agent/src/tools/system/shellMessage.ts +++ b/packages/agent/src/tools/system/shellMessage.ts @@ -77,8 +77,10 @@ export const shellMessageTool: Tool = { description: 'Interacts with a running shell process, sending input and receiving output', logPrefix: '💻', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returns: returnSchema, + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { instanceId, stdin, signal }, diff --git a/packages/agent/src/tools/system/shellMessage.ts.bak b/packages/agent/src/tools/system/shellMessage.ts.bak new file mode 100644 index 0000000..c774500 --- /dev/null +++ b/packages/agent/src/tools/system/shellMessage.ts.bak @@ -0,0 +1,175 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from '../../core/types.js'; +import { sleep } from '../../utils/sleep.js'; + +import { processStates } from './shellStart.js'; + +// Define NodeJS signals as an enum +export enum NodeSignals { + SIGABRT = 'SIGABRT', + SIGALRM = 'SIGALRM', + SIGBUS = 'SIGBUS', + SIGCHLD = 'SIGCHLD', + SIGCONT = 'SIGCONT', + SIGFPE = 'SIGFPE', + SIGHUP = 'SIGHUP', + SIGILL = 'SIGILL', + SIGINT = 'SIGINT', + SIGIO = 'SIGIO', + SIGIOT = 'SIGIOT', + SIGKILL = 'SIGKILL', + SIGPIPE = 'SIGPIPE', + SIGPOLL = 'SIGPOLL', + SIGPROF = 'SIGPROF', + SIGPWR = 'SIGPWR', + SIGQUIT = 'SIGQUIT', + SIGSEGV = 'SIGSEGV', + SIGSTKFLT = 'SIGSTKFLT', + SIGSTOP = 'SIGSTOP', + SIGSYS = 'SIGSYS', + SIGTERM = 'SIGTERM', + SIGTRAP = 'SIGTRAP', + SIGTSTP = 'SIGTSTP', + SIGTTIN = 'SIGTTIN', + SIGTTOU = 'SIGTTOU', + SIGUNUSED = 'SIGUNUSED', + SIGURG = 'SIGURG', + SIGUSR1 = 'SIGUSR1', + SIGUSR2 = 'SIGUSR2', + SIGVTALRM = 'SIGVTALRM', + SIGWINCH = 'SIGWINCH', + SIGXCPU = 'SIGXCPU', + SIGXFSZ = 'SIGXFSZ', +} + +const parameterSchema = z.object({ + instanceId: z.string().describe('The ID returned by shellStart'), + stdin: z.string().optional().describe('Input to send to process'), + signal: z + .nativeEnum(NodeSignals) + .optional() + .describe('Signal to send to the process (e.g., SIGTERM, SIGINT)'), + description: z + .string() + .max(80) + .describe('The reason for this shell interaction (max 80 chars)'), +}); + +const returnSchema = z + .object({ + stdout: z.string(), + stderr: z.string(), + completed: z.boolean(), + error: z.string().optional(), + signaled: z.boolean().optional(), + }) + .describe( + 'Process interaction results including stdout, stderr, and completion status', + ); + +type Parameters = z.infer; +type ReturnType = z.infer; + +export const shellMessageTool: Tool = { + name: 'shellMessage', + description: + 'Interacts with a running shell process, sending input and receiving output', + logPrefix: '💻', + parameters: zodToJsonSchema(parameterSchema), + returns: zodToJsonSchema(returnSchema), + + execute: async ( + { instanceId, stdin, signal }, + { logger }, + ): Promise => { + logger.verbose( + `Interacting with shell process ${instanceId}${stdin ? ' with input' : ''}${signal ? ` with signal ${signal}` : ''}`, + ); + + try { + const processState = processStates.get(instanceId); + if (!processState) { + throw new Error(`No process found with ID ${instanceId}`); + } + + // Send signal if provided + if (signal) { + const wasKilled = processState.process.kill(signal); + if (!wasKilled) { + return { + stdout: '', + stderr: '', + completed: processState.state.completed, + signaled: false, + error: `Failed to send signal ${signal} to process (process may have already terminated)`, + }; + } + processState.state.signaled = true; + } + + // Send input if provided + if (stdin) { + if (!processState.process.stdin?.writable) { + throw new Error('Process stdin is not available'); + } + processState.process.stdin.write(`${stdin}\n`); + } + + // Wait a brief moment for output to be processed + await sleep(100); + + // Get accumulated output + const stdout = processState.stdout.join(''); + const stderr = processState.stderr.join(''); + + // Clear the buffers + processState.stdout = []; + processState.stderr = []; + + logger.verbose('Interaction completed successfully'); + if (stdout) { + logger.verbose(`stdout: ${stdout.trim()}`); + } + if (stderr) { + logger.verbose(`stderr: ${stderr.trim()}`); + } + + return { + stdout: stdout.trim(), + stderr: stderr.trim(), + completed: processState.state.completed, + signaled: processState.state.signaled, + }; + } catch (error) { + if (error instanceof Error) { + logger.verbose(`Process interaction failed: ${error.message}`); + + return { + stdout: '', + stderr: '', + completed: false, + error: error.message, + }; + } + + const errorMessage = String(error); + logger.error(`Unknown error during process interaction: ${errorMessage}`); + return { + stdout: '', + stderr: '', + completed: false, + error: `Unknown error occurred: ${errorMessage}`, + }; + } + }, + + logParameters: (input, { logger }) => { + const processState = processStates.get(input.instanceId); + logger.info( + `Interacting with shell command "${processState ? processState.command : ''}", ${input.description}`, + ); + }, + logReturns: () => {}, +}; diff --git a/packages/agent/src/tools/system/shellStart.ts b/packages/agent/src/tools/system/shellStart.ts index 810e7f4..60508ca 100644 --- a/packages/agent/src/tools/system/shellStart.ts +++ b/packages/agent/src/tools/system/shellStart.ts @@ -72,8 +72,10 @@ export const shellStartTool: Tool = { description: 'Starts a shell command with fast sync mode (default 100ms timeout) that falls back to async mode for longer-running commands', logPrefix: '💻', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), + parameters: parameterSchema, + returns: returnSchema, + parametersJsonSchema: zodToJsonSchema(parameterSchema), + returnsJsonSchema: zodToJsonSchema(returnSchema), execute: async ( { command, timeout = DEFAULT_TIMEOUT }, diff --git a/packages/agent/src/tools/system/sleep.ts b/packages/agent/src/tools/system/sleep.ts index 5ff84f2..fc28062 100644 --- a/packages/agent/src/tools/system/sleep.ts +++ b/packages/agent/src/tools/system/sleep.ts @@ -23,8 +23,10 @@ export const sleepTool: Tool = { description: 'Pauses execution for the specified number of seconds, useful when waiting for async tools to make progress before checking on them', logPrefix: '💤', - parameters: zodToJsonSchema(parametersSchema), - returns: zodToJsonSchema(returnsSchema), + parameters: parametersSchema, + returns: returnsSchema, + parametersJsonSchema: zodToJsonSchema(parametersSchema), + returnsJsonSchema: zodToJsonSchema(returnsSchema), async execute(params) { const { seconds } = parametersSchema.parse(params); diff --git a/packages/agent/src/tools/system/sleep.ts.bak b/packages/agent/src/tools/system/sleep.ts.bak new file mode 100644 index 0000000..5ff84f2 --- /dev/null +++ b/packages/agent/src/tools/system/sleep.ts.bak @@ -0,0 +1,43 @@ +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { Tool } from '../../core/types.js'; +import { sleep } from '../../utils/sleep.js'; + +const MAX_SLEEP_SECONDS = 3600; // 1 hour + +const parametersSchema = z.object({ + seconds: z + .number() + .min(0) + .max(MAX_SLEEP_SECONDS) + .describe('Number of seconds to sleep (max 1 hour)'), +}); + +const returnsSchema = z.object({ + sleptFor: z.number().describe('Actual number of seconds slept'), +}); + +export const sleepTool: Tool = { + name: 'sleep', + description: + 'Pauses execution for the specified number of seconds, useful when waiting for async tools to make progress before checking on them', + logPrefix: '💤', + parameters: zodToJsonSchema(parametersSchema), + returns: zodToJsonSchema(returnsSchema), + async execute(params) { + const { seconds } = parametersSchema.parse(params); + + await sleep(seconds * 1000); + + return returnsSchema.parse({ + sleptFor: seconds, + }); + }, + logParameters({ seconds }) { + return `sleeping for ${seconds} seconds`; + }, + logReturns() { + return ''; + }, +}; diff --git a/packages/cli/src/commands/$default.ts b/packages/cli/src/commands/$default.ts index 01959ac..8b06b2d 100644 --- a/packages/cli/src/commands/$default.ts +++ b/packages/cli/src/commands/$default.ts @@ -10,6 +10,7 @@ import { userPrompt, LogLevel, subAgentTool, + errorToString, } from 'mycoder-agent'; import { TokenTracker } from 'mycoder-agent/dist/core/tokens.js'; @@ -148,7 +149,11 @@ export const command: CommandModule = { : JSON.stringify(result.result, null, 2); logger.info('\n=== Result ===\n', output); } catch (error) { - logger.error('An error occurred:', error); + logger.error( + 'An error occurred:', + errorToString(error), + error instanceof Error ? error.stack : '', + ); // Capture the error with Sentry captureException(error); } diff --git a/packages/cli/src/commands/tools.ts b/packages/cli/src/commands/tools.ts index 7b1e580..d984b57 100644 --- a/packages/cli/src/commands/tools.ts +++ b/packages/cli/src/commands/tools.ts @@ -43,19 +43,21 @@ export const command: CommandModule = { try { const tools = getTools(); - console.log('Available Tools:\\n'); + console.log('Available Tools:\n'); for (const tool of tools) { // Tool name and description console.log(`${tool.name}`); console.log('-'.repeat(tool.name.length)); - console.log(`Description: ${tool.description}\\n`); + console.log(`Description: ${tool.description}\n`); // Parameters section console.log('Parameters:'); + // Use parametersJsonSchema if available, otherwise convert from ZodSchema + const parametersSchema = (tool as any).parametersJsonSchema || tool.parameters; console.log( formatSchema( - tool.parameters as { + parametersSchema as { properties?: Record; required?: string[]; }, @@ -65,9 +67,11 @@ export const command: CommandModule = { // Returns section console.log('Returns:'); if (tool.returns) { + // Use returnsJsonSchema if available, otherwise convert from ZodSchema + const returnsSchema = (tool as any).returnsJsonSchema || tool.returns; console.log( formatSchema( - tool.returns as { + returnsSchema as { properties?: Record; required?: string[]; }, @@ -75,7 +79,7 @@ export const command: CommandModule = { ); } else { console.log(' Type: any'); - console.log(' Description: Tool execution result or error\\n'); + console.log(' Description: Tool execution result or error\n'); } console.log(); // Add spacing between tools From 2af9361b71d1dad01a0f11a806394cbe19ec117b Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 15:48:52 -0500 Subject: [PATCH 03/14] remove mistakenly added *.bak files. --- .../src/tools/browser/browseMessage.ts.bak | 242 ------------------ .../src/tools/browser/browseStart.ts.bak | 163 ------------ .../src/tools/interaction/subAgent.ts.bak | 116 --------- .../src/tools/interaction/userPrompt.ts.bak | 33 --- .../agent/src/tools/system/respawn.ts.bak | 34 --- .../src/tools/system/sequenceComplete.ts.bak | 28 -- .../src/tools/system/shellMessage.ts.bak | 175 ------------- packages/agent/src/tools/system/sleep.ts.bak | 43 ---- 8 files changed, 834 deletions(-) delete mode 100644 packages/agent/src/tools/browser/browseMessage.ts.bak delete mode 100644 packages/agent/src/tools/browser/browseStart.ts.bak delete mode 100644 packages/agent/src/tools/interaction/subAgent.ts.bak delete mode 100644 packages/agent/src/tools/interaction/userPrompt.ts.bak delete mode 100644 packages/agent/src/tools/system/respawn.ts.bak delete mode 100644 packages/agent/src/tools/system/sequenceComplete.ts.bak delete mode 100644 packages/agent/src/tools/system/shellMessage.ts.bak delete mode 100644 packages/agent/src/tools/system/sleep.ts.bak diff --git a/packages/agent/src/tools/browser/browseMessage.ts.bak b/packages/agent/src/tools/browser/browseMessage.ts.bak deleted file mode 100644 index 49547da..0000000 --- a/packages/agent/src/tools/browser/browseMessage.ts.bak +++ /dev/null @@ -1,242 +0,0 @@ -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Tool } from '../../core/types.js'; -import { errorToString } from '../../utils/errorToString.js'; -import { sleep } from '../../utils/sleep.js'; - -import { filterPageContent } from './filterPageContent.js'; -import { browserSessions, type BrowserAction, SelectorType } from './types.js'; - -// Schema for browser action -const browserActionSchema = z - .object({ - actionType: z.enum(['goto', 'click', 'type', 'wait', 'content', 'close']), - url: z - .string() - .url() - .optional() - .describe('URL to navigate to if "goto" actionType'), - selector: z - .string() - .optional() - .describe('Selector to click if "click" actionType'), - selectorType: z - .nativeEnum(SelectorType) - .optional() - .describe('Type of selector if "click" actionType'), - text: z - .string() - .optional() - .describe( - 'Text to type if "type" actionType, for other actionType, this is ignored', - ), - }) - .describe('Browser action to perform'); - -// Main parameter schema -const parameterSchema = z.object({ - instanceId: z.string().describe('The ID returned by browseStart'), - action: browserActionSchema, - description: z - .string() - .max(80) - .describe('The reason for this browser action (max 80 chars)'), -}); - -// Return schema -const returnSchema = z.object({ - status: z.string(), - content: z.string().optional(), - error: z.string().optional(), -}); - -type Parameters = z.infer; -type ReturnType = z.infer; - -// Helper function to handle selectors -const getSelector = (selector: string, type?: SelectorType): string => { - switch (type) { - case SelectorType.XPATH: - return `xpath=${selector}`; - case SelectorType.TEXT: - return `text=${selector}`; - default: - return selector; // CSS selector is default - } -}; - -export const browseMessageTool: Tool = { - name: 'browseMessage', - logPrefix: '🏄', - description: 'Performs actions in an active browser session', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), - - execute: async ( - { instanceId, action }, - { logger, pageFilter }, - ): Promise => { - // Validate action format - if (!action || typeof action !== 'object') { - logger.error('Invalid action format: action must be an object'); - return { - status: 'error', - error: 'Invalid action format: action must be an object', - }; - } - - if (!action.actionType) { - logger.error('Invalid action format: actionType is required'); - return { - status: 'error', - error: 'Invalid action format: actionType is required', - }; - } - - logger.verbose(`Executing browser action: ${action.actionType}`); - logger.verbose(`Webpage processing mode: ${pageFilter}`); - - try { - const session = browserSessions.get(instanceId); - if (!session) { - throw new Error(`No browser session found with ID ${instanceId}`); - } - - const { page } = session; - - switch (action.actionType) { - case 'goto': { - if (!action.url) { - throw new Error('URL required for goto action'); - } - - try { - // Try with 'domcontentloaded' first which is more reliable than 'networkidle' - logger.verbose( - `Navigating to ${action.url} with 'domcontentloaded' waitUntil`, - ); - await page.goto(action.url, { waitUntil: 'domcontentloaded' }); - await sleep(3000); - const content = await filterPageContent(page, pageFilter); - logger.verbose(`Content: ${content}`); - logger.verbose( - 'Navigation completed with domcontentloaded strategy', - ); - logger.verbose(`Content length: ${content.length} characters`); - return { status: 'success', content }; - } catch (navError) { - // If that fails, try with no waitUntil option - logger.warn( - `Failed with domcontentloaded strategy: ${errorToString(navError)}`, - ); - logger.verbose( - `Retrying navigation to ${action.url} with no waitUntil option`, - ); - - try { - await page.goto(action.url); - await sleep(3000); - const content = await filterPageContent(page, pageFilter); - logger.verbose(`Content: ${content}`); - logger.verbose('Navigation completed with basic strategy'); - return { status: 'success', content }; - } catch (innerError) { - logger.error( - `Failed with basic navigation strategy: ${errorToString(innerError)}`, - ); - throw innerError; // Re-throw to be caught by outer catch block - } - } - } - - case 'click': { - if (!action.selector) { - throw new Error('Selector required for click action'); - } - const clickSelector = getSelector( - action.selector, - action.selectorType, - ); - await page.click(clickSelector); - await sleep(1000); // Wait for any content changes after click - const content = await filterPageContent(page, pageFilter); - logger.verbose( - `Click action completed on selector: ${clickSelector}`, - ); - return { status: 'success', content }; - } - - case 'type': { - if (!action.selector || !action.text) { - throw new Error('Selector and text required for type action'); - } - const typeSelector = getSelector( - action.selector, - action.selectorType, - ); - await page.fill(typeSelector, action.text); - logger.verbose(`Type action completed on selector: ${typeSelector}`); - return { status: 'success' }; - } - - case 'wait': { - if (!action.selector) { - throw new Error('Selector required for wait action'); - } - const waitSelector = getSelector( - action.selector, - action.selectorType, - ); - await page.waitForSelector(waitSelector); - logger.verbose(`Wait action completed for selector: ${waitSelector}`); - return { status: 'success' }; - } - - case 'content': { - const content = await filterPageContent(page, pageFilter); - logger.verbose('Page content retrieved successfully'); - logger.verbose(`Content length: ${content.length} characters`); - return { status: 'success', content }; - } - - case 'close': { - await session.page.context().close(); - await session.browser.close(); - browserSessions.delete(instanceId); - logger.verbose('Browser session closed successfully'); - return { status: 'closed' }; - } - - default: { - throw new Error( - `Unsupported action type: ${(action as BrowserAction).actionType}`, - ); - } - } - } catch (error) { - logger.error('Browser action failed:', { error }); - return { - status: 'error', - error: errorToString(error), - }; - } - }, - - logParameters: ( - { action, description }, - { logger, pageFilter = 'simple' }, - ) => { - logger.info( - `Performing browser action: ${action.actionType} with ${pageFilter} processing, ${description}`, - ); - }, - - logReturns: (output, { logger }) => { - if (output.error) { - logger.error(`Browser action failed: ${output.error}`); - } else { - logger.info(`Browser action completed with status: ${output.status}`); - } - }, -}; diff --git a/packages/agent/src/tools/browser/browseStart.ts.bak b/packages/agent/src/tools/browser/browseStart.ts.bak deleted file mode 100644 index a4c5fa6..0000000 --- a/packages/agent/src/tools/browser/browseStart.ts.bak +++ /dev/null @@ -1,163 +0,0 @@ -import { chromium } from '@playwright/test'; -import { v4 as uuidv4 } from 'uuid'; -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Tool } from '../../core/types.js'; -import { errorToString } from '../../utils/errorToString.js'; -import { sleep } from '../../utils/sleep.js'; - -import { filterPageContent } from './filterPageContent.js'; -import { browserSessions } from './types.js'; - -const parameterSchema = z.object({ - url: z.string().url().optional().describe('Initial URL to navigate to'), - timeout: z - .number() - .optional() - .describe('Default timeout in milliseconds (default: 30000)'), - description: z - .string() - .max(80) - .describe('The reason for starting this browser session (max 80 chars)'), -}); - -const returnSchema = z.object({ - instanceId: z.string(), - status: z.string(), - content: z.string().optional(), - error: z.string().optional(), -}); - -type Parameters = z.infer; -type ReturnType = z.infer; - -export const browseStartTool: Tool = { - name: 'browseStart', - logPrefix: '🏄', - description: 'Starts a new browser session with optional initial URL', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), - - execute: async ( - { url, timeout = 30000 }, - { logger, headless, userSession, pageFilter }, - ): Promise => { - logger.verbose(`Starting browser session${url ? ` at ${url}` : ''}`); - logger.verbose( - `User session mode: ${userSession ? 'enabled' : 'disabled'}`, - ); - logger.verbose(`Webpage processing mode: ${pageFilter}`); - - try { - const instanceId = uuidv4(); - - // Launch browser - const launchOptions = { - headless, - }; - - // Use system Chrome installation if userSession is true - if (userSession) { - logger.verbose('Using system Chrome installation'); - // For Chrome, we use the channel option to specify Chrome - launchOptions['channel'] = 'chrome'; - } - - const browser = await chromium.launch(launchOptions); - - // Create new context with default settings - const context = await browser.newContext({ - viewport: null, - userAgent: - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - serviceWorkers: 'block', // Block service workers which can cause continuous network activity - }); - - // Create new page - const page = await context.newPage(); - page.setDefaultTimeout(timeout); - - // Initialize browser session - const session = { - browser, - page, - id: instanceId, - }; - - browserSessions.set(instanceId, session); - - // Setup cleanup handlers - browser.on('disconnected', () => { - browserSessions.delete(instanceId); - }); - - // Navigate to URL if provided - let content = ''; - if (url) { - try { - // Try with 'domcontentloaded' first which is more reliable than 'networkidle' - logger.verbose( - `Navigating to ${url} with 'domcontentloaded' waitUntil`, - ); - await page.goto(url, { waitUntil: 'domcontentloaded', timeout }); - await sleep(3000); - content = await filterPageContent(page, pageFilter); - logger.verbose(`Content: ${content}`); - logger.verbose('Navigation completed with domcontentloaded strategy'); - } catch (error) { - // If that fails, try with no waitUntil option at all (most basic) - logger.warn( - `Failed with domcontentloaded strategy: ${errorToString(error)}`, - ); - logger.verbose( - `Retrying navigation to ${url} with no waitUntil option`, - ); - - try { - await page.goto(url, { timeout }); - await sleep(3000); - content = await filterPageContent(page, pageFilter); - logger.verbose(`Content: ${content}`); - logger.verbose('Navigation completed with basic strategy'); - } catch (innerError) { - logger.error( - `Failed with basic navigation strategy: ${errorToString(innerError)}`, - ); - throw innerError; // Re-throw to be caught by outer catch block - } - } - } - - logger.verbose('Browser session started successfully'); - logger.verbose(`Content length: ${content.length} characters`); - - return { - instanceId, - status: 'initialized', - content: content || undefined, - }; - } catch (error) { - logger.error(`Failed to start browser: ${errorToString(error)}`); - return { - instanceId: '', - status: 'error', - error: errorToString(error), - }; - } - }, - - logParameters: ({ url, description }, { logger, pageFilter = 'simple' }) => { - logger.info( - `Starting browser session${url ? ` at ${url}` : ''} with ${pageFilter} processing, ${description}`, - ); - }, - - logReturns: (output, { logger }) => { - if (output.error) { - logger.error(`Browser start failed: ${output.error}`); - } else { - logger.info(`Browser session started with ID: ${output.instanceId}`); - } - }, -}; diff --git a/packages/agent/src/tools/interaction/subAgent.ts.bak b/packages/agent/src/tools/interaction/subAgent.ts.bak deleted file mode 100644 index 87e5755..0000000 --- a/packages/agent/src/tools/interaction/subAgent.ts.bak +++ /dev/null @@ -1,116 +0,0 @@ -import { anthropic } from '@ai-sdk/anthropic'; -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { toolAgent } from '../../core/toolAgent.js'; -import { Tool } from '../../core/types.js'; -import { getTools } from '../getTools.js'; - -const parameterSchema = z.object({ - description: z - .string() - .max(80) - .describe("A brief description of the sub-agent's purpose (max 80 chars)"), - goal: z - .string() - .describe('The main objective that the sub-agent needs to achieve'), - projectContext: z - .string() - .describe('Context about the problem or environment'), - fileContext: z - .object({ - workingDirectory: z - .string() - .optional() - .describe('The directory where the sub-agent should operate'), - relevantFiles: z - .string() - .optional() - .describe( - 'A list of files, which may include ** or * wildcard characters', - ), - }) - .describe( - 'When working with files and directories, it is best to be very specific to avoid sub-agents making incorrect assumptions', - ) - .optional(), -}); - -const returnSchema = z - .string() - .describe( - 'The response from the sub-agent including its reasoning and tool usage', - ); - -type Parameters = z.infer; -type ReturnType = z.infer; - -// Sub-agent specific configuration -const subAgentConfig = { - maxIterations: 50, - model: anthropic('claude-3-7-sonnet-20250219'), - maxTokens: 4096, - temperature: 0.7, - getSystemPrompt: () => { - return [ - 'You are a focused AI sub-agent handling a specific task.', - 'You have access to the same tools as the main agent but should focus only on your assigned task.', - 'When complete, call the sequenceComplete tool with your results.', - 'Follow any specific conventions or requirements provided in the task context.', - 'Ask the main agent for clarification if critical information is missing.', - ].join('\n'); - }, -}; - -export const subAgentTool: Tool = { - name: 'subAgent', - description: - 'Creates a sub-agent that has access to all tools to solve a specific task', - logPrefix: '🤖', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), - execute: async (params, context) => { - // Validate parameters - const { description, goal, projectContext, fileContext } = - parameterSchema.parse(params); - - // Construct a well-structured prompt - const prompt = [ - `Description: ${description}`, - `Goal: ${goal}`, - `Project Context: ${projectContext}`, - fileContext - ? `\nContext:\n${[ - fileContext.workingDirectory - ? `- Working Directory: ${fileContext.workingDirectory}` - : '', - fileContext.relevantFiles - ? `- Relevant Files:\n ${fileContext.relevantFiles}` - : '', - ] - .filter(Boolean) - .join('\n')}` - : '', - ] - .filter(Boolean) - .join('\n'); - - const tools = getTools().filter((tool) => tool.name !== 'userPrompt'); - - // Update config if timeout is specified - const config = { - ...subAgentConfig, - }; - - const result = await toolAgent(prompt, tools, config, { - ...context, - workingDirectory: - fileContext?.workingDirectory ?? context.workingDirectory, - }); - return result.result; // Return the result string directly - }, - logParameters: (input, { logger }) => { - logger.info(`Delegating task "${input.description}"`); - }, - logReturns: () => {}, -}; diff --git a/packages/agent/src/tools/interaction/userPrompt.ts.bak b/packages/agent/src/tools/interaction/userPrompt.ts.bak deleted file mode 100644 index ebeaa14..0000000 --- a/packages/agent/src/tools/interaction/userPrompt.ts.bak +++ /dev/null @@ -1,33 +0,0 @@ -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Tool } from '../../core/types.js'; -import { userPrompt } from '../../utils/userPrompt.js'; - -const parameterSchema = z.object({ - prompt: z.string().describe('The prompt message to display to the user'), -}); - -const returnSchema = z.string().describe("The user's response"); - -type Parameters = z.infer; -type ReturnType = z.infer; - -export const userPromptTool: Tool = { - name: 'userPrompt', - description: 'Prompts the user for input and returns their response', - logPrefix: '🗣️', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), - execute: async ({ prompt }, { logger }) => { - logger.verbose(`Prompting user with: ${prompt}`); - - const response = await userPrompt(prompt); - - logger.verbose(`Received user response: ${response}`); - - return response; - }, - logParameters: () => {}, - logReturns: () => {}, -}; diff --git a/packages/agent/src/tools/system/respawn.ts.bak b/packages/agent/src/tools/system/respawn.ts.bak deleted file mode 100644 index 6b0030e..0000000 --- a/packages/agent/src/tools/system/respawn.ts.bak +++ /dev/null @@ -1,34 +0,0 @@ -import { Tool, ToolContext } from '../../core/types.js'; - -export interface RespawnInput { - respawnContext: string; -} - -export const respawnTool: Tool = { - name: 'respawn', - description: - 'Resets the agent context to just the system prompt and provided context', - logPrefix: '🔄', - parameters: { - type: 'object', - properties: { - respawnContext: { - type: 'string', - description: 'The context to keep after respawning', - }, - }, - required: ['respawnContext'], - additionalProperties: false, - }, - returns: { - type: 'string', - description: 'A message indicating that the respawn has been initiated', - }, - execute: ( - _params: Record, - _context: ToolContext, - ): Promise => { - // This is a special case tool - the actual respawn logic is handled in toolAgent - return Promise.resolve('Respawn initiated'); - }, -}; diff --git a/packages/agent/src/tools/system/sequenceComplete.ts.bak b/packages/agent/src/tools/system/sequenceComplete.ts.bak deleted file mode 100644 index 037ef5b..0000000 --- a/packages/agent/src/tools/system/sequenceComplete.ts.bak +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Tool } from '../../core/types.js'; - -const parameterSchema = z.object({ - result: z.string().describe('The final result to return from the tool agent'), -}); - -const returnSchema = z - .string() - .describe('This is returned to the caller of the tool agent.'); - -type Parameters = z.infer; -type ReturnType = z.infer; - -export const sequenceCompleteTool: Tool = { - name: 'sequenceComplete', - description: 'Completes the tool use sequence and returns the final result', - logPrefix: '✅', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), - execute: ({ result }) => Promise.resolve(result), - logParameters: () => {}, - logReturns: (output, { logger }) => { - logger.info(`Completed: ${output}`); - }, -}; diff --git a/packages/agent/src/tools/system/shellMessage.ts.bak b/packages/agent/src/tools/system/shellMessage.ts.bak deleted file mode 100644 index c774500..0000000 --- a/packages/agent/src/tools/system/shellMessage.ts.bak +++ /dev/null @@ -1,175 +0,0 @@ -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Tool } from '../../core/types.js'; -import { sleep } from '../../utils/sleep.js'; - -import { processStates } from './shellStart.js'; - -// Define NodeJS signals as an enum -export enum NodeSignals { - SIGABRT = 'SIGABRT', - SIGALRM = 'SIGALRM', - SIGBUS = 'SIGBUS', - SIGCHLD = 'SIGCHLD', - SIGCONT = 'SIGCONT', - SIGFPE = 'SIGFPE', - SIGHUP = 'SIGHUP', - SIGILL = 'SIGILL', - SIGINT = 'SIGINT', - SIGIO = 'SIGIO', - SIGIOT = 'SIGIOT', - SIGKILL = 'SIGKILL', - SIGPIPE = 'SIGPIPE', - SIGPOLL = 'SIGPOLL', - SIGPROF = 'SIGPROF', - SIGPWR = 'SIGPWR', - SIGQUIT = 'SIGQUIT', - SIGSEGV = 'SIGSEGV', - SIGSTKFLT = 'SIGSTKFLT', - SIGSTOP = 'SIGSTOP', - SIGSYS = 'SIGSYS', - SIGTERM = 'SIGTERM', - SIGTRAP = 'SIGTRAP', - SIGTSTP = 'SIGTSTP', - SIGTTIN = 'SIGTTIN', - SIGTTOU = 'SIGTTOU', - SIGUNUSED = 'SIGUNUSED', - SIGURG = 'SIGURG', - SIGUSR1 = 'SIGUSR1', - SIGUSR2 = 'SIGUSR2', - SIGVTALRM = 'SIGVTALRM', - SIGWINCH = 'SIGWINCH', - SIGXCPU = 'SIGXCPU', - SIGXFSZ = 'SIGXFSZ', -} - -const parameterSchema = z.object({ - instanceId: z.string().describe('The ID returned by shellStart'), - stdin: z.string().optional().describe('Input to send to process'), - signal: z - .nativeEnum(NodeSignals) - .optional() - .describe('Signal to send to the process (e.g., SIGTERM, SIGINT)'), - description: z - .string() - .max(80) - .describe('The reason for this shell interaction (max 80 chars)'), -}); - -const returnSchema = z - .object({ - stdout: z.string(), - stderr: z.string(), - completed: z.boolean(), - error: z.string().optional(), - signaled: z.boolean().optional(), - }) - .describe( - 'Process interaction results including stdout, stderr, and completion status', - ); - -type Parameters = z.infer; -type ReturnType = z.infer; - -export const shellMessageTool: Tool = { - name: 'shellMessage', - description: - 'Interacts with a running shell process, sending input and receiving output', - logPrefix: '💻', - parameters: zodToJsonSchema(parameterSchema), - returns: zodToJsonSchema(returnSchema), - - execute: async ( - { instanceId, stdin, signal }, - { logger }, - ): Promise => { - logger.verbose( - `Interacting with shell process ${instanceId}${stdin ? ' with input' : ''}${signal ? ` with signal ${signal}` : ''}`, - ); - - try { - const processState = processStates.get(instanceId); - if (!processState) { - throw new Error(`No process found with ID ${instanceId}`); - } - - // Send signal if provided - if (signal) { - const wasKilled = processState.process.kill(signal); - if (!wasKilled) { - return { - stdout: '', - stderr: '', - completed: processState.state.completed, - signaled: false, - error: `Failed to send signal ${signal} to process (process may have already terminated)`, - }; - } - processState.state.signaled = true; - } - - // Send input if provided - if (stdin) { - if (!processState.process.stdin?.writable) { - throw new Error('Process stdin is not available'); - } - processState.process.stdin.write(`${stdin}\n`); - } - - // Wait a brief moment for output to be processed - await sleep(100); - - // Get accumulated output - const stdout = processState.stdout.join(''); - const stderr = processState.stderr.join(''); - - // Clear the buffers - processState.stdout = []; - processState.stderr = []; - - logger.verbose('Interaction completed successfully'); - if (stdout) { - logger.verbose(`stdout: ${stdout.trim()}`); - } - if (stderr) { - logger.verbose(`stderr: ${stderr.trim()}`); - } - - return { - stdout: stdout.trim(), - stderr: stderr.trim(), - completed: processState.state.completed, - signaled: processState.state.signaled, - }; - } catch (error) { - if (error instanceof Error) { - logger.verbose(`Process interaction failed: ${error.message}`); - - return { - stdout: '', - stderr: '', - completed: false, - error: error.message, - }; - } - - const errorMessage = String(error); - logger.error(`Unknown error during process interaction: ${errorMessage}`); - return { - stdout: '', - stderr: '', - completed: false, - error: `Unknown error occurred: ${errorMessage}`, - }; - } - }, - - logParameters: (input, { logger }) => { - const processState = processStates.get(input.instanceId); - logger.info( - `Interacting with shell command "${processState ? processState.command : ''}", ${input.description}`, - ); - }, - logReturns: () => {}, -}; diff --git a/packages/agent/src/tools/system/sleep.ts.bak b/packages/agent/src/tools/system/sleep.ts.bak deleted file mode 100644 index 5ff84f2..0000000 --- a/packages/agent/src/tools/system/sleep.ts.bak +++ /dev/null @@ -1,43 +0,0 @@ -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { Tool } from '../../core/types.js'; -import { sleep } from '../../utils/sleep.js'; - -const MAX_SLEEP_SECONDS = 3600; // 1 hour - -const parametersSchema = z.object({ - seconds: z - .number() - .min(0) - .max(MAX_SLEEP_SECONDS) - .describe('Number of seconds to sleep (max 1 hour)'), -}); - -const returnsSchema = z.object({ - sleptFor: z.number().describe('Actual number of seconds slept'), -}); - -export const sleepTool: Tool = { - name: 'sleep', - description: - 'Pauses execution for the specified number of seconds, useful when waiting for async tools to make progress before checking on them', - logPrefix: '💤', - parameters: zodToJsonSchema(parametersSchema), - returns: zodToJsonSchema(returnsSchema), - async execute(params) { - const { seconds } = parametersSchema.parse(params); - - await sleep(seconds * 1000); - - return returnsSchema.parse({ - sleptFor: seconds, - }); - }, - logParameters({ seconds }) { - return `sleeping for ${seconds} seconds`; - }, - logReturns() { - return ''; - }, -}; From 5b1f3797e4a6d7e55b6f0f0e9544fbee21fa93e1 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 15:53:44 -0500 Subject: [PATCH 04/14] remove debug console logs. --- packages/agent/src/core/toolAgent.ts | 2 - .../agent/src/tools/browser/BrowserManager.ts | 1 - .../src/tools/browser/filterPageContent.ts | 7 --- packages/cli/src/sentry/index.ts | 48 +++++++++++-------- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index c1bc896..2600801 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -293,7 +293,6 @@ export const toolAgent = async ( parameters: tool.parameters, }); }); - console.log('toolSet', toolSet); const generateTextProps = { model: config.model, temperature: config.temperature, @@ -301,7 +300,6 @@ export const toolAgent = async ( system: systemPrompt, tools: toolSet, }; - console.log('generateTextProps', generateTextProps); const { text, reasoning, reasoningDetails, toolCalls, toolResults } = await generateText(generateTextProps); diff --git a/packages/agent/src/tools/browser/BrowserManager.ts b/packages/agent/src/tools/browser/BrowserManager.ts index c507666..a136e8a 100644 --- a/packages/agent/src/tools/browser/BrowserManager.ts +++ b/packages/agent/src/tools/browser/BrowserManager.ts @@ -18,7 +18,6 @@ export class BrowserManager { async createSession(config?: BrowserConfig): Promise { try { const sessionConfig = { ...this.defaultConfig, ...config }; - //console.log('sessionConfig', sessionConfig); const browser = await chromium.launch({ headless: sessionConfig.headless, }); diff --git a/packages/agent/src/tools/browser/filterPageContent.ts b/packages/agent/src/tools/browser/filterPageContent.ts index a1d3879..398cc1e 100644 --- a/packages/agent/src/tools/browser/filterPageContent.ts +++ b/packages/agent/src/tools/browser/filterPageContent.ts @@ -80,13 +80,6 @@ async function getSimpleProcessedDOM(page: Page): Promise { elementsToRemove.forEach((element) => element.remove()); - console.log( - 'removing ', - elementsToRemove.length, - ' elements out of a total ', - elements.length, - ); - return clone.outerHTML; }); diff --git a/packages/cli/src/sentry/index.ts b/packages/cli/src/sentry/index.ts index 360344d..5f9a26a 100644 --- a/packages/cli/src/sentry/index.ts +++ b/packages/cli/src/sentry/index.ts @@ -1,6 +1,7 @@ -import * as Sentry from '@sentry/node'; import { createRequire } from 'module'; +import * as Sentry from '@sentry/node'; + /** * Initialize Sentry for error tracking * @param dsn Optional custom DSN to use instead of the default @@ -10,7 +11,8 @@ export function initSentry(dsn?: string) { let packageVersion = 'unknown'; try { const require = createRequire(import.meta.url); - packageVersion = process.env.npm_package_version || require('../../package.json').version; + packageVersion = + process.env.npm_package_version || require('../../package.json').version; } catch (error) { console.warn('Could not determine package version for Sentry:', error); } @@ -18,27 +20,26 @@ export function initSentry(dsn?: string) { // Initialize Sentry Sentry.init({ // Default DSN from Sentry.io integration instructions - dsn: dsn || 'https://2873d2518b60f645918b6a08ae5e69ae@o4508898407481344.ingest.us.sentry.io/4508898476687360', - + dsn: + dsn || + 'https://2873d2518b60f645918b6a08ae5e69ae@o4508898407481344.ingest.us.sentry.io/4508898476687360', + // No profiling integration as requested - + // Capture 100% of the transactions tracesSampleRate: 1.0, - + // Set environment based on NODE_ENV environment: process.env.NODE_ENV || 'development', - + // Add release version from package.json release: `mycoder@${packageVersion}`, - + // Don't capture errors in development mode unless explicitly enabled - enabled: process.env.NODE_ENV !== 'development' || process.env.ENABLE_SENTRY === 'true', + enabled: + process.env.NODE_ENV !== 'development' || + process.env.ENABLE_SENTRY === 'true', }); - - // Log confirmation that Sentry is initialized with version info - if (process.env.NODE_ENV !== 'test') { - console.log(`Sentry initialized for mycoder@${packageVersion}`); - } } /** @@ -67,20 +68,27 @@ export function testSentryErrorReporting() { let packageVersion = 'unknown'; try { const require = createRequire(import.meta.url); - packageVersion = process.env.npm_package_version || require('../../package.json').version; + packageVersion = + process.env.npm_package_version || + require('../../package.json').version; } catch (error) { - console.warn('Could not determine package version for test error:', error); + console.warn( + 'Could not determine package version for test error:', + error, + ); } - + // Throw a test error with version information - throw new Error(`Test error for Sentry.io integration from mycoder@${packageVersion}`); + throw new Error( + `Test error for Sentry.io integration from mycoder@${packageVersion}`, + ); } catch (error) { // Capture the error with Sentry Sentry.captureException(error); - + // Log a message about the test console.log('Test error sent to Sentry.io'); - + // Return the error for inspection return error; } From 462602dbf890ff86417ef207b19af548e6eba167 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 15:54:06 -0500 Subject: [PATCH 05/14] format + lint --- .changeset/convert-to-zod.md | 4 ++-- docs/LargeCodeBase_Plan.md | 14 ++++++++++++++ docs/SentryIntegration.md | 7 ++++++- packages/agent/src/core/types.ts | 2 +- packages/agent/src/tools/system/respawn.ts | 4 +++- packages/cli/src/commands/tools.ts | 3 ++- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.changeset/convert-to-zod.md b/.changeset/convert-to-zod.md index 636f167..bea2cbf 100644 --- a/.changeset/convert-to-zod.md +++ b/.changeset/convert-to-zod.md @@ -1,6 +1,6 @@ --- -"mycoder-agent": minor -"mycoder": minor +'mycoder-agent': minor +'mycoder': minor --- Convert from JsonSchema7Type to ZodSchema for tool parameters and returns, required for Vercel AI SDK integration. diff --git a/docs/LargeCodeBase_Plan.md b/docs/LargeCodeBase_Plan.md index 52a7781..b9ecb13 100644 --- a/docs/LargeCodeBase_Plan.md +++ b/docs/LargeCodeBase_Plan.md @@ -11,16 +11,19 @@ This document presents research findings on how leading AI coding tools handle l While detailed technical documentation on Claude Code's internal architecture is limited in public sources, we can infer several approaches from Anthropic's general AI architecture and Claude Code's capabilities: 1. **Chunking and Retrieval Augmentation**: + - Claude Code likely employs retrieval-augmented generation (RAG) to handle large codebases - Files are likely chunked into manageable segments with semantic understanding - Relevant code chunks are retrieved based on query relevance 2. **Hierarchical Code Understanding**: + - Builds a hierarchical representation of code (project → modules → files → functions) - Maintains a graph of relationships between code components - Prioritizes context based on relevance to the current task 3. **Incremental Context Management**: + - Dynamically adjusts the context window to include only relevant code - Maintains a "working memory" of recently accessed or modified files - Uses sliding context windows to process large files sequentially @@ -35,16 +38,19 @@ While detailed technical documentation on Claude Code's internal architecture is Aider's approach to handling large codebases can be inferred from its open-source codebase and documentation: 1. **Git Integration**: + - Leverages Git to track file changes and understand repository structure - Uses Git history to prioritize recently modified files - Employs Git's diff capabilities to minimize context needed for changes 2. **Selective File Context**: + - Only includes relevant files in the context rather than the entire codebase - Uses heuristics to identify related files based on imports, references, and naming patterns - Implements a "map-reduce" approach where it first analyzes the codebase structure, then selectively processes relevant files 3. **Prompt Engineering and Chunking**: + - Designs prompts that can work with limited context by focusing on specific tasks - Chunks large files and processes them incrementally - Uses summarization to compress information about non-focal code parts @@ -90,6 +96,7 @@ Based on the research findings, we recommend the following enhancements to MyCod ``` **Implementation Details:** + - Create a lightweight indexer that runs during project initialization - Generate embeddings for code files, focusing on API definitions, function signatures, and documentation - Build a graph of relationships between files based on imports/exports and references @@ -120,6 +127,7 @@ Based on the research findings, we recommend the following enhancements to MyCod ``` **Implementation Details:** + - Develop a working set manager that tracks currently relevant files - Implement a relevance scoring algorithm that considers: - Semantic similarity to the current task @@ -148,6 +156,7 @@ Based on the research findings, we recommend the following enhancements to MyCod ``` **Implementation Details:** + - Chunk files at meaningful boundaries (functions, classes, modules) - Implement overlapping chunks to maintain context across boundaries - Develop a progressive loading strategy: @@ -181,6 +190,7 @@ Based on the research findings, we recommend the following enhancements to MyCod ``` **Implementation Details:** + - Implement a multi-level caching system: - Token cache: Store tokenized representations of files to avoid re-tokenization - Embedding cache: Store vector embeddings for semantic search @@ -209,6 +219,7 @@ Based on the research findings, we recommend the following enhancements to MyCod ``` **Implementation Details:** + - Improve task decomposition to identify parallelizable sub-tasks - Implement smart context distribution to sub-agents: - Provide each sub-agent with only the context it needs @@ -222,16 +233,19 @@ Based on the research findings, we recommend the following enhancements to MyCod ## Implementation Roadmap ### Phase 1: Foundation (1-2 months) + - Develop the basic indexing system for project structure and file metadata - Implement a simple relevance-based context selection mechanism - Create a basic chunking strategy for large files ### Phase 2: Advanced Features (2-3 months) + - Implement the semantic indexing system with code embeddings - Develop the full context management system with working sets - Create the multi-level caching system ### Phase 3: Optimization and Integration (1-2 months) + - Enhance sub-agent coordination for parallel processing - Optimize performance with better caching and context management - Integrate all components into a cohesive system diff --git a/docs/SentryIntegration.md b/docs/SentryIntegration.md index b6897f2..8ab2745 100644 --- a/docs/SentryIntegration.md +++ b/docs/SentryIntegration.md @@ -17,6 +17,7 @@ npm install @sentry/node --save ## Configuration By default, Sentry is: + - Enabled in production environments - Disabled in development environments (unless explicitly enabled) - Configured to capture 100% of transactions @@ -56,7 +57,9 @@ Sentry.init({ tracesSampleRate: 1.0, environment: process.env.NODE_ENV || 'development', release: `mycoder@${packageVersion}`, - enabled: process.env.NODE_ENV !== 'development' || process.env.ENABLE_SENTRY === 'true', + enabled: + process.env.NODE_ENV !== 'development' || + process.env.ENABLE_SENTRY === 'true', }); // Capture errors @@ -76,6 +79,7 @@ mycoder test-sentry ``` This command will: + 1. Generate a test error that includes the package version 2. Report it to Sentry.io 3. Output the result to the console @@ -85,6 +89,7 @@ Note: In development environments, you may need to set `ENABLE_SENTRY=true` for ## Privacy Error reports sent to Sentry include: + - Stack traces - Error messages - Environment information diff --git a/packages/agent/src/core/types.ts b/packages/agent/src/core/types.ts index 696ccae..1bdab20 100644 --- a/packages/agent/src/core/types.ts +++ b/packages/agent/src/core/types.ts @@ -29,7 +29,7 @@ export type Tool, TReturn = any> = { logReturns?: (returns: TReturn, context: ToolContext) => void; execute: (params: TParams, context: ToolContext) => Promise; - + // Keep JsonSchema7Type for backward compatibility and Vercel AI SDK integration parametersJsonSchema?: JsonSchema7Type; returnsJsonSchema?: JsonSchema7Type; diff --git a/packages/agent/src/tools/system/respawn.ts b/packages/agent/src/tools/system/respawn.ts index 995596d..27b94cd 100644 --- a/packages/agent/src/tools/system/respawn.ts +++ b/packages/agent/src/tools/system/respawn.ts @@ -11,7 +11,9 @@ const parameterSchema = z.object({ respawnContext: z.string().describe('The context to keep after respawning'), }); -const returnSchema = z.string().describe('A message indicating that the respawn has been initiated'); +const returnSchema = z + .string() + .describe('A message indicating that the respawn has been initiated'); export const respawnTool: Tool = { name: 'respawn', diff --git a/packages/cli/src/commands/tools.ts b/packages/cli/src/commands/tools.ts index d984b57..5656a0e 100644 --- a/packages/cli/src/commands/tools.ts +++ b/packages/cli/src/commands/tools.ts @@ -54,7 +54,8 @@ export const command: CommandModule = { // Parameters section console.log('Parameters:'); // Use parametersJsonSchema if available, otherwise convert from ZodSchema - const parametersSchema = (tool as any).parametersJsonSchema || tool.parameters; + const parametersSchema = + (tool as any).parametersJsonSchema || tool.parameters; console.log( formatSchema( parametersSchema as { From 462cff69433c86c34edfc83a0740ed88fc493f5e Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 16:02:53 -0500 Subject: [PATCH 06/14] more lint cleanup --- packages/agent/src/core/toolAgent.ts | 3 +-- packages/cli/src/index.ts | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 2600801..a57c99a 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -300,8 +300,7 @@ export const toolAgent = async ( system: systemPrompt, tools: toolSet, }; - const { text, reasoning, reasoningDetails, toolCalls, toolResults } = - await generateText(generateTextProps); + const { text, toolCalls } = await generateText(generateTextProps); const localToolCalls: ToolUseContent[] = toolCalls.map((call) => ({ type: 'tool_use', diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 8a2f968..187b1bf 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -8,8 +8,6 @@ import { hideBin } from 'yargs/helpers'; import { command as defaultCommand } from './commands/$default.js'; import { command as testSentryCommand } from './commands/test-sentry.js'; import { command as toolsCommand } from './commands/tools.js'; - -// Initialize Sentry as early as possible import { sharedOptions } from './options.js'; import { initSentry, captureException } from './sentry/index.js'; initSentry(); From 870cbee7681382a4d81d6300b4d2740701d97192 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 16:12:18 -0500 Subject: [PATCH 07/14] Re-implement token caching for Vercel AI SDK usage with Anthropic provider (fixes #58) --- .changeset/implement-token-caching.md | 5 ++ packages/agent/src/core/toolAgent.ts | 90 +++++++++++---------------- 2 files changed, 42 insertions(+), 53 deletions(-) create mode 100644 .changeset/implement-token-caching.md diff --git a/.changeset/implement-token-caching.md b/.changeset/implement-token-caching.md new file mode 100644 index 0000000..6c1c577 --- /dev/null +++ b/.changeset/implement-token-caching.md @@ -0,0 +1,5 @@ +--- +"mycoder-agent": patch +--- + +Re-implemented token caching for Vercel AI SDK usage with Anthropic provider to reduce token consumption during repeated API calls. \ No newline at end of file diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index a57c99a..574281a 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -8,6 +8,7 @@ import { ToolResultPart, ToolSet, tool as makeTool, + Message } from 'ai'; import chalk from 'chalk'; @@ -191,62 +192,42 @@ async function executeTools( }; } -/* -// a function that takes a list of messages and returns a list of messages but with the last message having a cache_control of ephemeral -function addCacheControlToTools(messages: T[]): T[] { - return messages.map((m, i) => ({ - ...m, - ...(i === messages.length - 1 - ? { cache_control: { type: 'ephemeral' } } - : {}), - })); -} - -function addCacheControlToContentBlocks( - content: ContentBlockParam[], -): ContentBlockParam[] { - return content.map((c, i) => { - if (i === content.length - 1) { - if ( - c.type === 'text' || - c.type === 'document' || - c.type === 'image' || - c.type === 'tool_use' || - c.type === 'tool_result' || - c.type === 'thinking' || - c.type === 'redacted_thinking' - ) { - return { ...c, cache_control: { type: 'ephemeral' } }; +/** + * Adds cache control to the messages for token caching with the Vercel AI SDK + * This marks the last two messages as ephemeral which allows the conversation up to that + * point to be cached (with a ~5 minute window), reducing token usage when making multiple API calls + */ +function addCacheControlToMessages(messages: CoreMessage[]): CoreMessage[] { + if (messages.length <= 1) return messages; + + // Create a deep copy of the messages array to avoid mutating the original + const result = JSON.parse(JSON.stringify(messages)) as CoreMessage[]; + + // Get the last two messages (if available) + const lastTwoMessageIndices = [ + messages.length - 1, + messages.length - 2 + ]; + + // Add providerOptions with anthropic cache control to the last two messages + lastTwoMessageIndices.forEach(index => { + if (index >= 0) { + const message = result[index]; + if (message) { + // For the Vercel AI SDK, we need to add the providerOptions.anthropic property + // with cacheControl: 'ephemeral' to enable token caching + message.providerOptions = { + ...message.providerOptions, + anthropic: { + cacheControl: 'ephemeral' + } + }; } } - return c; }); + + return result; } -function addCacheControlToMessages( - messages: Anthropic.Messages.MessageParam[], -): Anthropic.Messages.MessageParam[] { - return messages.map((m, i) => { - if (typeof m.content === 'string') { - return { - ...m, - content: [ - { - type: 'text', - text: m.content, - cache_control: { type: 'ephemeral' }, - }, - ] as ContentBlockParam[], - }; - } - return { - ...m, - content: - i >= messages.length - 2 - ? addCacheControlToContentBlocks(m.content) - : m.content, - }; - }); -}*/ export const toolAgent = async ( initialPrompt: string, @@ -293,10 +274,13 @@ export const toolAgent = async ( parameters: tool.parameters, }); }); + // Apply cache control to messages for token caching + const messagesWithCacheControl = addCacheControlToMessages(messages); + const generateTextProps = { model: config.model, temperature: config.temperature, - messages, + messages: messagesWithCacheControl, system: systemPrompt, tools: toolSet, }; From 3dfa8d1f3507b1caed59f3e6f2d89d61e637fc5c Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 20:25:10 -0500 Subject: [PATCH 08/14] cache system prompt as well. --- packages/agent/src/core/toolAgent.ts | 40 +++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 574281a..a009bf9 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -8,7 +8,6 @@ import { ToolResultPart, ToolSet, tool as makeTool, - Message } from 'ai'; import chalk from 'chalk'; @@ -192,6 +191,18 @@ async function executeTools( }; } +function createCacheControlMessageFromSystemPrompt( + systemPrompt: string, +): CoreMessage { + return { + role: 'system', + content: systemPrompt, + providerOptions: { + anthropic: { cacheControl: { type: 'ephemeral' } }, + }, + }; +} + /** * Adds cache control to the messages for token caching with the Vercel AI SDK * This marks the last two messages as ephemeral which allows the conversation up to that @@ -199,18 +210,15 @@ async function executeTools( */ function addCacheControlToMessages(messages: CoreMessage[]): CoreMessage[] { if (messages.length <= 1) return messages; - + // Create a deep copy of the messages array to avoid mutating the original const result = JSON.parse(JSON.stringify(messages)) as CoreMessage[]; - + // Get the last two messages (if available) - const lastTwoMessageIndices = [ - messages.length - 1, - messages.length - 2 - ]; - + const lastTwoMessageIndices = [messages.length - 1, messages.length - 2]; + // Add providerOptions with anthropic cache control to the last two messages - lastTwoMessageIndices.forEach(index => { + lastTwoMessageIndices.forEach((index) => { if (index >= 0) { const message = result[index]; if (message) { @@ -218,14 +226,12 @@ function addCacheControlToMessages(messages: CoreMessage[]): CoreMessage[] { // with cacheControl: 'ephemeral' to enable token caching message.providerOptions = { ...message.providerOptions, - anthropic: { - cacheControl: 'ephemeral' - } + anthropic: { cacheControl: { type: 'ephemeral' } }, }; } } }); - + return result; } @@ -275,13 +281,15 @@ export const toolAgent = async ( }); }); // Apply cache control to messages for token caching - const messagesWithCacheControl = addCacheControlToMessages(messages); - + const messagesWithCacheControl = [ + createCacheControlMessageFromSystemPrompt(systemPrompt), + ...addCacheControlToMessages(messages), + ]; + const generateTextProps = { model: config.model, temperature: config.temperature, messages: messagesWithCacheControl, - system: systemPrompt, tools: toolSet, }; const { text, toolCalls } = await generateText(generateTextProps); From 73604de26807d5ef7a4ba1b4bca8bd83cda80b87 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 21:24:26 -0500 Subject: [PATCH 09/14] ensure that it is always objects returned --- packages/agent/src/core/toolAgent.ts | 37 +++++++++++++++++-- .../agent/src/tools/interaction/subAgent.ts | 14 ++++--- .../agent/src/tools/interaction/userPrompt.ts | 6 ++- packages/agent/src/tools/system/respawn.ts | 8 ++-- .../src/tools/system/sequenceComplete.ts | 10 +++-- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index a009bf9..9c7b146 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -8,6 +8,7 @@ import { ToolResultPart, ToolSet, tool as makeTool, + ToolCallPart, } from 'ai'; import chalk from 'chalk'; @@ -117,6 +118,11 @@ function processResponse(response: Anthropic.Message) { } */ +type ErrorResult = { + errorMessage: string; + errorType: string; +}; + async function executeTools( toolCalls: ToolUseContent[], tools: Tool[], @@ -159,8 +165,12 @@ async function executeTools( tokenTracker: new TokenTracker(call.name, context.tokenTracker), }); } catch (error: any) { - toolResult = `Error: Exception thrown during tool execution. Type: ${error.constructor.name}, Message: ${error.message}`; + toolResult = JSON.stringify({ + errorMessage: error.message, + errorType: error.constructor.name, + }); } + return { type: 'tool-result', toolCallId: call.id, @@ -173,7 +183,8 @@ async function executeTools( const sequenceCompletedTool = toolResults.find( (r) => r.toolName === 'sequenceComplete', ); - const completionResult = sequenceCompletedTool?.result as string; + const completionResult = (sequenceCompletedTool?.result as { result: string }) + .result; messages.push({ role: 'tool', @@ -292,7 +303,13 @@ export const toolAgent = async ( messages: messagesWithCacheControl, tools: toolSet, }; - const { text, toolCalls } = await generateText(generateTextProps); + const { text, toolCalls, ...other } = await generateText(generateTextProps); + + //console.log( + // 'providerMetadata', + // JSON.stringify(other.providerMetadata, null, 2), + //); + //console.log('other data', JSON.stringify(other, null, 2)); const localToolCalls: ToolUseContent[] = toolCalls.map((call) => ({ type: 'tool_use', @@ -329,6 +346,20 @@ export const toolAgent = async ( logger.info(text); } + if (toolCalls.length > 0) { + const toolCallParts: Array = toolCalls.map((toolCall) => ({ + type: 'tool-call', + toolCallId: toolCall.toolCallId, + toolName: toolCall.toolName, + args: toolCall.args, + })); + + messages.push({ + role: 'assistant', + content: toolCallParts, + }); + } + /*logger.log( tokenTracker.logLevel, chalk.blue(`[Token Usage/Message] ${tokenUsagePerMessage.toString()}`), diff --git a/packages/agent/src/tools/interaction/subAgent.ts b/packages/agent/src/tools/interaction/subAgent.ts index df33879..8d8de08 100644 --- a/packages/agent/src/tools/interaction/subAgent.ts +++ b/packages/agent/src/tools/interaction/subAgent.ts @@ -36,11 +36,13 @@ const parameterSchema = z.object({ .optional(), }); -const returnSchema = z - .string() - .describe( - 'The response from the sub-agent including its reasoning and tool usage', - ); +const returnSchema = z.object({ + response: z + .string() + .describe( + 'The response from the sub-agent including its reasoning and tool usage', + ), +}); type Parameters = z.infer; type ReturnType = z.infer; @@ -109,7 +111,7 @@ export const subAgentTool: Tool = { workingDirectory: fileContext?.workingDirectory ?? context.workingDirectory, }); - return result.result; // Return the result string directly + return { response: result.result }; }, logParameters: (input, { logger }) => { logger.info(`Delegating task "${input.description}"`); diff --git a/packages/agent/src/tools/interaction/userPrompt.ts b/packages/agent/src/tools/interaction/userPrompt.ts index 9de4968..638085e 100644 --- a/packages/agent/src/tools/interaction/userPrompt.ts +++ b/packages/agent/src/tools/interaction/userPrompt.ts @@ -8,7 +8,9 @@ const parameterSchema = z.object({ prompt: z.string().describe('The prompt message to display to the user'), }); -const returnSchema = z.string().describe("The user's response"); +const returnSchema = z.object({ + userText: z.string().describe("The user's response"), +}); type Parameters = z.infer; type ReturnType = z.infer; @@ -28,7 +30,7 @@ export const userPromptTool: Tool = { logger.verbose(`Received user response: ${response}`); - return response; + return { userText: response }; }, logParameters: () => {}, logReturns: () => {}, diff --git a/packages/agent/src/tools/system/respawn.ts b/packages/agent/src/tools/system/respawn.ts index 27b94cd..1640764 100644 --- a/packages/agent/src/tools/system/respawn.ts +++ b/packages/agent/src/tools/system/respawn.ts @@ -11,9 +11,11 @@ const parameterSchema = z.object({ respawnContext: z.string().describe('The context to keep after respawning'), }); -const returnSchema = z - .string() - .describe('A message indicating that the respawn has been initiated'); +const returnSchema = z.object({ + result: z + .string() + .describe('A message indicating that the respawn has been initiated'), +}); export const respawnTool: Tool = { name: 'respawn', diff --git a/packages/agent/src/tools/system/sequenceComplete.ts b/packages/agent/src/tools/system/sequenceComplete.ts index 1b3a2dd..cb3bf1f 100644 --- a/packages/agent/src/tools/system/sequenceComplete.ts +++ b/packages/agent/src/tools/system/sequenceComplete.ts @@ -7,9 +7,11 @@ const parameterSchema = z.object({ result: z.string().describe('The final result to return from the tool agent'), }); -const returnSchema = z - .string() - .describe('This is returned to the caller of the tool agent.'); +const returnSchema = z.object({ + result: z + .string() + .describe('This is returned to the caller of the tool agent.'), +}); type Parameters = z.infer; type ReturnType = z.infer; @@ -22,7 +24,7 @@ export const sequenceCompleteTool: Tool = { parametersJsonSchema: zodToJsonSchema(parameterSchema), returns: returnSchema, returnsJsonSchema: zodToJsonSchema(returnSchema), - execute: ({ result }) => Promise.resolve(result), + execute: ({ result }) => Promise.resolve({ result }), logParameters: () => {}, logReturns: (output, { logger }) => { logger.info(`Completed: ${output}`); From 214998519cb24b18de2704558b996e78024e0dda Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Tue, 4 Mar 2025 09:11:17 -0500 Subject: [PATCH 10/14] tests pass. --- .../agent/src/core/toolAgent.respawn.test.ts | 46 ----- packages/agent/src/core/toolAgent.test.ts | 179 ++++-------------- packages/agent/src/core/toolAgent.ts | 5 +- 3 files changed, 43 insertions(+), 187 deletions(-) delete mode 100644 packages/agent/src/core/toolAgent.respawn.test.ts diff --git a/packages/agent/src/core/toolAgent.respawn.test.ts b/packages/agent/src/core/toolAgent.respawn.test.ts deleted file mode 100644 index 40046d9..0000000 --- a/packages/agent/src/core/toolAgent.respawn.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { anthropic } from '@ai-sdk/anthropic'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; - -import { toolAgent } from '../../src/core/toolAgent.js'; -import { getTools } from '../../src/tools/getTools.js'; -import { MockLogger } from '../utils/mockLogger.js'; - -import { TokenTracker } from './tokens.js'; -import { ToolContext } from './types.js'; - -const toolContext: ToolContext = { - logger: new MockLogger(), - headless: true, - workingDirectory: '.', - userSession: false, - pageFilter: 'simple', - tokenTracker: new TokenTracker(), -}; - -describe('toolAgent respawn functionality', () => { - const tools = getTools(); - - beforeEach(() => { - process.env.ANTHROPIC_API_KEY = 'test-key'; - vi.clearAllMocks(); - }); - - it('should handle respawn tool calls', async () => { - const result = await toolAgent( - 'initial prompt', - tools, - { - maxIterations: 2, // Need at least 2 iterations for respawn + empty response - model: anthropic('claude-3-7-sonnet-20250219'), - maxTokens: 100, - temperature: 0, - getSystemPrompt: () => 'test system prompt', - }, - toolContext, - ); - - expect(result.result).toBe( - 'Maximum sub-agent iterations reach without successful completion', - ); - }); -}); diff --git a/packages/agent/src/core/toolAgent.test.ts b/packages/agent/src/core/toolAgent.test.ts index dc63da0..a2d35a6 100644 --- a/packages/agent/src/core/toolAgent.test.ts +++ b/packages/agent/src/core/toolAgent.test.ts @@ -6,7 +6,6 @@ import { MockLogger } from '../utils/mockLogger.js'; import { executeToolCall } from './executeToolCall.js'; import { TokenTracker } from './tokens.js'; -import { toolAgent } from './toolAgent.js'; import { Tool, ToolContext } from './types.js'; const toolContext: ToolContext = { @@ -18,99 +17,59 @@ const toolContext: ToolContext = { tokenTracker: new TokenTracker(), }; -// Mock configuration for testing -const testConfig = { - maxIterations: 50, - model: anthropic('claude-3-7-sonnet-20250219'), - maxTokens: 4096, - temperature: 0.7, - getSystemPrompt: () => 'Test system prompt', -}; - -// Mock Anthropic client response -const mockResponse = { - content: [ - { - type: 'tool_use', - name: 'sequenceComplete', - id: '1', - input: { result: 'Test complete' }, +// Mock tool for testing +const mockTool: Tool = { + name: 'mockTool', + description: 'A mock tool for testing', + parameters: z.object({ + input: z.string().describe('Test input'), + }), + returns: z.string().describe('The processed result'), + parametersJsonSchema: { + type: 'object', + properties: { + input: { + type: 'string', + description: 'Test input', + }, }, - ], - usage: { input_tokens: 10, output_tokens: 10 }, - model: 'claude-3-7-sonnet-latest', - role: 'assistant', - id: 'msg_123', + required: ['input'], + }, + returnsJsonSchema: { + type: 'string', + description: 'The processed result', + }, + execute: ({ input }) => Promise.resolve(`Processed: ${input}`), }; -// Mock Anthropic SDK -const mockCreate = vi.fn().mockImplementation(() => mockResponse); -vi.mock('@anthropic-ai/sdk', () => ({ - default: class { - messages = { - create: mockCreate, - }; +const errorTool: Tool = { + name: 'errorTool', + description: 'A tool that always fails', + parameters: z.object({}), + returns: z.string().describe('Error message'), + parametersJsonSchema: { + type: 'object', + properties: {}, + required: [], + }, + returnsJsonSchema: { + type: 'string', + description: 'Error message', + }, + execute: () => { + throw new Error('Deliberate failure'); }, -})); +}; describe('toolAgent', () => { beforeEach(() => { - process.env.ANTHROPIC_API_KEY = 'test-key'; + vi.clearAllMocks(); }); afterEach(() => { vi.clearAllMocks(); }); - // Mock tool for testing - const mockTool: Tool = { - name: 'mockTool', - description: 'A mock tool for testing', - parameters: z.object({ - input: z.string().describe('Test input'), - }), - returns: z.string().describe('The processed result'), - parametersJsonSchema: { - type: 'object', - properties: { - input: { - type: 'string', - description: 'Test input', - }, - }, - required: ['input'], - }, - returnsJsonSchema: { - type: 'string', - description: 'The processed result', - }, - execute: ({ input }) => Promise.resolve(`Processed: ${input}`), - }; - - const sequenceCompleteTool: Tool = { - name: 'sequenceComplete', - description: 'Completes the sequence', - parameters: z.object({ - result: z.string().describe('The final result'), - }), - returns: z.string().describe('The final result'), - parametersJsonSchema: { - type: 'object', - properties: { - result: { - type: 'string', - description: 'The final result', - }, - }, - required: ['result'], - }, - returnsJsonSchema: { - type: 'string', - description: 'The final result', - }, - execute: ({ result }) => Promise.resolve(result), - }; - it('should execute tool calls', async () => { const result = await executeToolCall( { @@ -140,25 +99,6 @@ describe('toolAgent', () => { }); it('should handle tool execution errors', async () => { - const errorTool: Tool = { - name: 'errorTool', - description: 'A tool that always fails', - parameters: z.object({}), - returns: z.string().describe('Error message'), - parametersJsonSchema: { - type: 'object', - properties: {}, - required: [], - }, - returnsJsonSchema: { - type: 'string', - description: 'Error message', - }, - execute: () => { - throw new Error('Deliberate failure'); - }, - }; - await expect( executeToolCall( { @@ -171,43 +111,4 @@ describe('toolAgent', () => { ), ).rejects.toThrow('Deliberate failure'); }); - - // Test empty response handling - it('should handle empty responses by sending a reminder', async () => { - // Reset the mock and set up the sequence of responses - mockCreate.mockReset(); - mockCreate - .mockResolvedValueOnce({ - content: [], - usage: { input_tokens: 5, output_tokens: 5 }, - }) - .mockResolvedValueOnce(mockResponse); - - const result = await toolAgent( - 'Test prompt', - [sequenceCompleteTool], - testConfig, - toolContext, - ); - - // Verify that create was called twice (once for empty response, once for completion) - expect(mockCreate).toHaveBeenCalledTimes(2); - expect(result.result).toBe('Test complete'); - }); - - // New tests for async system prompt - it('should handle async system prompt', async () => { - // Reset mock and set expected response - mockCreate.mockReset(); - mockCreate.mockResolvedValue(mockResponse); - - const result = await toolAgent( - 'Test prompt', - [sequenceCompleteTool], - testConfig, - toolContext, - ); - - expect(result.result).toBe('Test complete'); - }); }); diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 9c7b146..00009c7 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -183,8 +183,9 @@ async function executeTools( const sequenceCompletedTool = toolResults.find( (r) => r.toolName === 'sequenceComplete', ); - const completionResult = (sequenceCompletedTool?.result as { result: string }) - .result; + const completionResult = sequenceCompletedTool + ? (sequenceCompletedTool.result as { result: string }).result + : undefined; messages.push({ role: 'tool', From 27c2ba56b40b6003b427a7292581f124c107a825 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Tue, 4 Mar 2025 09:14:27 -0500 Subject: [PATCH 11/14] Refactor toolAgent.ts into modular components --- .changeset/refactor-tool-agent.md | 5 + packages/agent/src/core/toolAgent.ts | 419 +----------------- packages/agent/src/core/toolAgent/README.md | 34 ++ packages/agent/src/core/toolAgent/config.ts | 71 +++ packages/agent/src/core/toolAgent/index.ts | 158 +++++++ .../agent/src/core/toolAgent/messageUtils.ts | 73 +++ .../agent/src/core/toolAgent/tokenTracking.ts | 36 ++ .../agent/src/core/toolAgent/toolExecutor.ts | 89 ++++ packages/agent/src/core/toolAgent/types.ts | 87 ++++ 9 files changed, 568 insertions(+), 404 deletions(-) create mode 100644 .changeset/refactor-tool-agent.md create mode 100644 packages/agent/src/core/toolAgent/README.md create mode 100644 packages/agent/src/core/toolAgent/config.ts create mode 100644 packages/agent/src/core/toolAgent/index.ts create mode 100644 packages/agent/src/core/toolAgent/messageUtils.ts create mode 100644 packages/agent/src/core/toolAgent/tokenTracking.ts create mode 100644 packages/agent/src/core/toolAgent/toolExecutor.ts create mode 100644 packages/agent/src/core/toolAgent/types.ts diff --git a/.changeset/refactor-tool-agent.md b/.changeset/refactor-tool-agent.md new file mode 100644 index 0000000..454802d --- /dev/null +++ b/.changeset/refactor-tool-agent.md @@ -0,0 +1,5 @@ +--- +"mycoder-agent": minor +--- + +Refactored toolAgent.ts into modular components for improved maintainability and testability. Split into config.ts, messageUtils.ts, toolExecutor.ts, tokenTracking.ts, and types.ts modules. \ No newline at end of file diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 00009c7..918a6c9 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -1,411 +1,22 @@ -import { execSync } from 'child_process'; - -import { anthropic } from '@ai-sdk/anthropic'; -import { - CoreMessage, - CoreToolMessage, - generateText, - ToolResultPart, - ToolSet, - tool as makeTool, - ToolCallPart, -} from 'ai'; -import chalk from 'chalk'; - -import { getAnthropicApiKeyError } from '../utils/errors.js'; - -import { executeToolCall } from './executeToolCall.js'; -import { TokenTracker } from './tokens.js'; -import { - Tool, - ToolUseContent, - ToolResultContent, - ToolContext, -} from './types.js'; - -export interface ToolAgentResult { - result: string; - interactions: number; -} - -const CONFIG = { - maxIterations: 200, - model: anthropic('claude-3-7-sonnet-20250219'), - maxTokens: 4096, - temperature: 0.7, - getSystemPrompt: () => { - // Gather context with error handling - const getCommandOutput = (command: string, label: string): string => { - try { - return execSync(command).toString().trim(); - } catch (error) { - return `[Error getting ${label}: ${(error as Error).message}]`; - } - }; - - const context = { - pwd: getCommandOutput('pwd', 'current directory'), - files: getCommandOutput('ls -la', 'file listing'), - system: getCommandOutput('uname -a', 'system information'), - datetime: new Date().toString(), - }; - - return [ - 'You are an AI agent that can use tools to accomplish tasks.', - '', - 'Current Context:', - `Directory: ${context.pwd}`, - 'Files:', - context.files, - `System: ${context.system}`, - `DateTime: ${context.datetime}`, - '', - 'You prefer to call tools in parallel when possible because it leads to faster execution and less resource usage.', - 'When done, call the sequenceComplete tool with your results to indicate that the sequence has completed.', - '', - 'For coding tasks:', - '0. Try to break large tasks into smaller sub-tasks that can be completed and verified sequentially.', - " - trying to make lots of changes in one go can make it really hard to identify when something doesn't work", - ' - use sub-agents for each sub-task, leaving the main agent in a supervisory role', - ' - when possible ensure the project compiles/builds and the tests pass after each sub-task', - ' - give the sub-agents the guidance and context necessary be successful', - '1. First understand the context by:', - ' - Reading README.md, CONTRIBUTING.md, and similar documentation', - ' - Checking project configuration files (e.g., package.json)', - ' - Understanding coding standards', - '2. Ensure changes:', - ' - Follow project conventions', - ' - Build successfully', - ' - Pass all tests', - '3. Update documentation as needed', - '4. Consider adding documentation if you encountered setup/understanding challenges', - '', - 'Feel free to use Google and Bing via the browser tools to search for information or for ideas when you get stuck.', - '', - '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.', - '', - 'Use sub-agents for parallel tasks, providing them with specific context they need rather than having them rediscover it.', - ].join('\\n'); - }, -}; - -interface ToolCallResult { - sequenceCompleted: boolean; - completionResult?: string; - toolResults: ToolResultPart[]; -} -/* -function processResponse(response: Anthropic.Message) { - const content: (TextContent | ToolUseContent)[] = []; - const toolCalls: ToolUseContent[] = []; - - for (const message of response.content) { - if (message.type === 'text') { - content.push({ type: 'text', text: message.text }); - } else if (message.type === 'tool_use') { - const toolUse: ToolUseContent = { - type: 'tool_use', - name: message.name, - id: message.id, - input: message.input, - }; - content.push(toolUse); - toolCalls.push(toolUse); - } - } - - return { content, toolCalls }; -} -*/ - -type ErrorResult = { - errorMessage: string; - errorType: string; -}; - -async function executeTools( - toolCalls: ToolUseContent[], - tools: Tool[], - messages: CoreMessage[], - context: ToolContext, -): Promise { - if (toolCalls.length === 0) { - return { sequenceCompleted: false, toolResults: [] }; - } - - const { logger } = context; - - logger.verbose(`Executing ${toolCalls.length} tool calls`); - - // Check for respawn tool call - const respawnCall = toolCalls.find((call) => call.name === 'respawn'); - if (respawnCall) { - return { - sequenceCompleted: false, - toolResults: [ - { - type: 'tool-result', - toolCallId: respawnCall.id, - toolName: respawnCall.name, - result: { success: true }, - } satisfies ToolResultPart, - ], - respawn: { - context: respawnCall.input.respawnContext, - }, - }; - } - - const toolResults: ToolResultPart[] = await Promise.all( - toolCalls.map(async (call) => { - let toolResult = ''; - try { - toolResult = await executeToolCall(call, tools, { - ...context, - tokenTracker: new TokenTracker(call.name, context.tokenTracker), - }); - } catch (error: any) { - toolResult = JSON.stringify({ - errorMessage: error.message, - errorType: error.constructor.name, - }); - } - - return { - type: 'tool-result', - toolCallId: call.id, - toolName: call.name, - result: JSON.parse(toolResult) satisfies ToolResultContent, - } satisfies ToolResultPart; - }), - ); - - const sequenceCompletedTool = toolResults.find( - (r) => r.toolName === 'sequenceComplete', - ); - const completionResult = sequenceCompletedTool - ? (sequenceCompletedTool.result as { result: string }).result - : undefined; - - messages.push({ - role: 'tool', - content: toolResults, - } satisfies CoreToolMessage); - - if (sequenceCompletedTool) { - logger.verbose('Sequence completed', { completionResult }); - } - - return { - sequenceCompleted: sequenceCompletedTool !== undefined, - completionResult, - toolResults, - }; -} - -function createCacheControlMessageFromSystemPrompt( - systemPrompt: string, -): CoreMessage { - return { - role: 'system', - content: systemPrompt, - providerOptions: { - anthropic: { cacheControl: { type: 'ephemeral' } }, - }, - }; -} - /** - * Adds cache control to the messages for token caching with the Vercel AI SDK - * This marks the last two messages as ephemeral which allows the conversation up to that - * point to be cached (with a ~5 minute window), reducing token usage when making multiple API calls + * @fileoverview + * This file has been refactored into a modular structure. + * Please import from the toolAgent directory instead. */ -function addCacheControlToMessages(messages: CoreMessage[]): CoreMessage[] { - if (messages.length <= 1) return messages; - // Create a deep copy of the messages array to avoid mutating the original - const result = JSON.parse(JSON.stringify(messages)) as CoreMessage[]; +import { toolAgent, ToolAgentResult } from './toolAgent/index.js'; +import { Tool, ToolContext } from './toolAgent/types.js'; - // Get the last two messages (if available) - const lastTwoMessageIndices = [messages.length - 1, messages.length - 2]; +// Re-export the main toolAgent function and types for backward compatibility +export { toolAgent, ToolAgentResult, Tool, ToolContext }; - // Add providerOptions with anthropic cache control to the last two messages - lastTwoMessageIndices.forEach((index) => { - if (index >= 0) { - const message = result[index]; - if (message) { - // For the Vercel AI SDK, we need to add the providerOptions.anthropic property - // with cacheControl: 'ephemeral' to enable token caching - message.providerOptions = { - ...message.providerOptions, - anthropic: { cacheControl: { type: 'ephemeral' } }, - }; - } - } - }); - - return result; -} - -export const toolAgent = async ( - initialPrompt: string, - tools: Tool[], - config = 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 client = new Anthropic({ apiKey }); - 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(); - - 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, ...other } = await generateText(generateTextProps); - - //console.log( - // 'providerMetadata', - // JSON.stringify(other.providerMetadata, null, 2), - //); - //console.log('other data', JSON.stringify(other, null, 2)); - - const localToolCalls: ToolUseContent[] = toolCalls.map((call) => ({ - type: 'tool_use', - name: call.toolName, - id: call.toolCallId, - input: call.args, - })); - - 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; - } - - // Track both regular and cached token usage - //const tokenUsagePerMessage = TokenUsage.fromMessage(response); - //tokenTracker.tokenUsage.add(tokenUsagePerMessage); - - messages.push({ - role: 'assistant', - content: [{ type: 'text', text: text }], - }); - - if (text) { - logger.info(text); - } - - if (toolCalls.length > 0) { - const toolCallParts: Array = toolCalls.map((toolCall) => ({ - type: 'tool-call', - toolCallId: toolCall.toolCallId, - toolName: toolCall.toolName, - args: toolCall.args, - })); - - messages.push({ - role: 'assistant', - content: toolCallParts, - }); - } - - /*logger.log( - tokenTracker.logLevel, - chalk.blue(`[Token Usage/Message] ${tokenUsagePerMessage.toString()}`), - );*/ - - 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, - }; - logger.log( - tokenTracker.logLevel, - chalk.blueBright(`[Token Usage/Agent] ${tokenTracker.toString()}`), - ); - return result; - } - } - - logger.warn('Maximum iterations reached'); - const result = { - result: 'Maximum sub-agent iterations reach without successful completion', - interactions, - }; - // Use the appropriate log level based on tokenUsage flag - logger.log( - tokenTracker.logLevel, - chalk.blueBright(`[Token Usage/Agent] ${tokenTracker.toString()}`), +// 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.' ); - return result; }; + +// 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 new file mode 100644 index 0000000..37f91d3 --- /dev/null +++ b/packages/agent/src/core/toolAgent/README.md @@ -0,0 +1,34 @@ +# Tool Agent Module + +This directory contains the refactored 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 +- **config.ts**: Configuration-related code and default settings +- **messageUtils.ts**: Utilities for handling and formatting messages +- **toolExecutor.ts**: Logic for executing tool calls +- **tokenTracking.ts**: Enhanced utilities for token tracking +- **types.ts**: Consolidated type definitions + +## Usage + +```typescript +import { toolAgent } from './toolAgent/index.js'; +import { Tool, ToolContext } from './toolAgent/types.js'; + +// Use the toolAgent function as before +const result = await toolAgent(prompt, tools, config, context); +``` + +## Benefits of This Structure + +- **Improved maintainability**: Smaller, focused files are easier to understand and modify +- **Better testability**: Isolated components can be tested independently +- **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/config.ts b/packages/agent/src/core/toolAgent/config.ts new file mode 100644 index 0000000..25f56d9 --- /dev/null +++ b/packages/agent/src/core/toolAgent/config.ts @@ -0,0 +1,71 @@ +import { execSync } from 'child_process'; +import { anthropic } from '@ai-sdk/anthropic'; + +/** + * Default configuration for the tool agent + */ +export const DEFAULT_CONFIG = { + maxIterations: 200, + model: anthropic('claude-3-7-sonnet-20250219'), + maxTokens: 4096, + temperature: 0.7, + getSystemPrompt: getDefaultSystemPrompt, +}; + +/** + * Gets the default system prompt with contextual information about the environment + */ +export function getDefaultSystemPrompt(): string { + // Gather context with error handling + const getCommandOutput = (command: string, label: string): string => { + try { + return execSync(command).toString().trim(); + } catch (error) { + return `[Error getting ${label}: ${(error as Error).message}]`; + } + }; + + const context = { + pwd: getCommandOutput('pwd', 'current directory'), + files: getCommandOutput('ls -la', 'file listing'), + system: getCommandOutput('uname -a', 'system information'), + datetime: new Date().toString(), + }; + + return [ + 'You are an AI agent that can use tools to accomplish tasks.', + '', + 'Current Context:', + `Directory: ${context.pwd}`, + 'Files:', + context.files, + `System: ${context.system}`, + `DateTime: ${context.datetime}`, + '', + 'You prefer to call tools in parallel when possible because it leads to faster execution and less resource usage.', + 'When done, call the sequenceComplete tool with your results to indicate that the sequence has completed.', + '', + 'For coding tasks:', + '0. Try to break large tasks into smaller sub-tasks that can be completed and verified sequentially.', + " - trying to make lots of changes in one go can make it really hard to identify when something doesn't work", + ' - use sub-agents for each sub-task, leaving the main agent in a supervisory role', + ' - when possible ensure the project compiles/builds and the tests pass after each sub-task', + ' - give the sub-agents the guidance and context necessary be successful', + '1. First understand the context by:', + ' - Reading README.md, CONTRIBUTING.md, and similar documentation', + ' - Checking project configuration files (e.g., package.json)', + ' - Understanding coding standards', + '2. Ensure changes:', + ' - Follow project conventions', + ' - Build successfully', + ' - Pass all tests', + '3. Update documentation as needed', + '4. Consider adding documentation if you encountered setup/understanding challenges', + '', + 'Feel free to use Google and Bing via the browser tools to search for information or for ideas when you get stuck.', + '', + '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.', + '', + 'Use sub-agents for parallel tasks, providing them with specific context they need rather than having them rediscover it.', + ].join('\n'); +} diff --git a/packages/agent/src/core/toolAgent/index.ts b/packages/agent/src/core/toolAgent/index.ts new file mode 100644 index 0000000..75fc7d5 --- /dev/null +++ b/packages/agent/src/core/toolAgent/index.ts @@ -0,0 +1,158 @@ +import { CoreMessage, ToolSet, generateText, tool as makeTool } from 'ai'; +import chalk from 'chalk'; + +import { getAnthropicApiKeyError } from '../../utils/errors.js'; + +import { DEFAULT_CONFIG } from './config.js'; +import { + addCacheControlToMessages, + createCacheControlMessageFromSystemPrompt, + createToolCallParts, + formatToolCalls, +} from './messageUtils.js'; +import { executeTools } from './toolExecutor.js'; +import { logTokenUsage } from './tokenTracking.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(); + + 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, ...other } = 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; +}; + +// Re-export everything from the module +export * from './config.js'; +export * from './messageUtils.js'; +export * from './toolExecutor.js'; +export * from './tokenTracking.js'; +export * from './types.js'; diff --git a/packages/agent/src/core/toolAgent/messageUtils.ts b/packages/agent/src/core/toolAgent/messageUtils.ts new file mode 100644 index 0000000..fa3be03 --- /dev/null +++ b/packages/agent/src/core/toolAgent/messageUtils.ts @@ -0,0 +1,73 @@ +import { CoreMessage, ToolCallPart } from 'ai'; + +/** + * Creates a cache control message from a system prompt + * This is used for token caching with the Vercel AI SDK + */ +export function createCacheControlMessageFromSystemPrompt( + systemPrompt: string, +): CoreMessage { + return { + role: 'system', + content: systemPrompt, + providerOptions: { + anthropic: { cacheControl: { type: 'ephemeral' } }, + }, + }; +} + +/** + * Adds cache control to the messages for token caching with the Vercel AI SDK + * This marks the last two messages as ephemeral which allows the conversation up to that + * point to be cached (with a ~5 minute window), reducing token usage when making multiple API calls + */ +export function addCacheControlToMessages(messages: CoreMessage[]): CoreMessage[] { + if (messages.length <= 1) return messages; + + // Create a deep copy of the messages array to avoid mutating the original + const result = JSON.parse(JSON.stringify(messages)) as CoreMessage[]; + + // Get the last two messages (if available) + const lastTwoMessageIndices = [messages.length - 1, messages.length - 2]; + + // Add providerOptions with anthropic cache control to the last two messages + lastTwoMessageIndices.forEach((index) => { + if (index >= 0) { + const message = result[index]; + if (message) { + // For the Vercel AI SDK, we need to add the providerOptions.anthropic property + // with cacheControl: 'ephemeral' to enable token caching + message.providerOptions = { + ...message.providerOptions, + anthropic: { cacheControl: { type: 'ephemeral' } }, + }; + } + } + }); + + return result; +} + +/** + * Formats tool calls from the AI into the ToolUseContent format + */ +export function formatToolCalls(toolCalls: any[]): any[] { + return toolCalls.map((call) => ({ + type: 'tool_use', + name: call.toolName, + id: call.toolCallId, + input: call.args, + })); +} + +/** + * Creates tool call parts for the assistant message + */ +export function createToolCallParts(toolCalls: any[]): Array { + return toolCalls.map((toolCall) => ({ + type: 'tool-call', + toolCallId: toolCall.toolCallId, + toolName: toolCall.toolName, + args: toolCall.args, + })); +} diff --git a/packages/agent/src/core/toolAgent/tokenTracking.ts b/packages/agent/src/core/toolAgent/tokenTracking.ts new file mode 100644 index 0000000..694ae20 --- /dev/null +++ b/packages/agent/src/core/toolAgent/tokenTracking.ts @@ -0,0 +1,36 @@ +import chalk from 'chalk'; + +import { TokenTracker, TokenUsage } from '../tokens.js'; + +/** + * Enhanced utilities for token tracking and reporting + */ +export function logTokenUsage(tokenTracker: TokenTracker): void { + console.log( + chalk.blueBright(`[Token Usage/Agent] ${tokenTracker.toString()}`), + ); +} + +/** + * Creates a child token tracker for a specific tool call + */ +export function createToolTokenTracker( + toolName: string, + parentTracker: TokenTracker, +): TokenTracker { + return new TokenTracker(toolName, parentTracker); +} + +/** + * Gets the total token usage for a token tracker and all its children + */ +export function getTotalTokenUsage(tokenTracker: TokenTracker): TokenUsage { + return tokenTracker.getTotalUsage(); +} + +/** + * Gets the total cost for a token tracker and all its children + */ +export function getTotalTokenCost(tokenTracker: TokenTracker): string { + return tokenTracker.getTotalCost(); +} diff --git a/packages/agent/src/core/toolAgent/toolExecutor.ts b/packages/agent/src/core/toolAgent/toolExecutor.ts new file mode 100644 index 0000000..56fa15c --- /dev/null +++ b/packages/agent/src/core/toolAgent/toolExecutor.ts @@ -0,0 +1,89 @@ +import { CoreMessage, CoreToolMessage, ToolResultPart } from 'ai'; + +import { executeToolCall } from '../executeToolCall.js'; +import { TokenTracker } from '../tokens.js'; + +import { Tool, ToolCallResult, ToolContext, ToolUseContent } from './types.js'; + +/** + * Executes a list of tool calls and returns the results + */ +export async function executeTools( + toolCalls: ToolUseContent[], + tools: Tool[], + messages: CoreMessage[], + context: ToolContext, +): Promise { + if (toolCalls.length === 0) { + return { sequenceCompleted: false, toolResults: [] }; + } + + const { logger } = context; + + logger.verbose(`Executing ${toolCalls.length} tool calls`); + + // Check for respawn tool call + const respawnCall = toolCalls.find((call) => call.name === 'respawn'); + if (respawnCall) { + return { + sequenceCompleted: false, + toolResults: [ + { + type: 'tool-result', + toolCallId: respawnCall.id, + toolName: respawnCall.name, + result: { success: true }, + } satisfies ToolResultPart, + ], + respawn: { + context: respawnCall.input.respawnContext, + }, + }; + } + + const toolResults: ToolResultPart[] = await Promise.all( + toolCalls.map(async (call) => { + let toolResult = ''; + try { + toolResult = await executeToolCall(call, tools, { + ...context, + tokenTracker: new TokenTracker(call.name, context.tokenTracker), + }); + } catch (error: any) { + toolResult = JSON.stringify({ + errorMessage: error.message, + errorType: error.constructor.name, + }); + } + + return { + type: 'tool-result', + toolCallId: call.id, + toolName: call.name, + result: JSON.parse(toolResult), + } satisfies ToolResultPart; + }), + ); + + const sequenceCompletedTool = toolResults.find( + (r) => r.toolName === 'sequenceComplete', + ); + const completionResult = sequenceCompletedTool + ? (sequenceCompletedTool.result as { result: string }).result + : undefined; + + messages.push({ + role: 'tool', + content: toolResults, + } satisfies CoreToolMessage); + + if (sequenceCompletedTool) { + logger.verbose('Sequence completed', { completionResult }); + } + + return { + sequenceCompleted: sequenceCompletedTool !== undefined, + completionResult, + toolResults, + }; +} diff --git a/packages/agent/src/core/toolAgent/types.ts b/packages/agent/src/core/toolAgent/types.ts new file mode 100644 index 0000000..a9debdb --- /dev/null +++ b/packages/agent/src/core/toolAgent/types.ts @@ -0,0 +1,87 @@ +import { z } from 'zod'; +import { JsonSchema7Type } from 'zod-to-json-schema'; +import { CoreMessage } from 'ai'; + +import { Logger } from '../../utils/logger.js'; +import { TokenTracker } from '../tokens.js'; + +// Re-export existing types from the original types.ts +export type TokenLevel = 'debug' | 'verbose' | 'info' | 'warn' | 'error'; +export type pageFilter = 'simple' | 'none' | 'readability'; + +export type ToolContext = { + logger: Logger; + workingDirectory: string; + headless: boolean; + userSession: boolean; + pageFilter: pageFilter; + tokenTracker: TokenTracker; +}; + +export type Tool, TReturn = any> = { + name: string; + description: string; + parameters: z.ZodType; + returns: z.ZodType; + logPrefix?: string; + + logParameters?: (params: TParams, context: ToolContext) => void; + logReturns?: (returns: TReturn, context: ToolContext) => void; + + execute: (params: TParams, context: ToolContext) => Promise; + + // Keep JsonSchema7Type for backward compatibility and Vercel AI SDK integration + parametersJsonSchema?: JsonSchema7Type; + returnsJsonSchema?: JsonSchema7Type; +}; + +export type ToolCall = { + id: string; + name: string; + input: any; +}; + +export type TextContent = { + type: 'text'; + text: string; +}; + +export type ToolUseContent = { + type: 'tool_use'; +} & ToolCall; + +export type AssistantMessage = { + role: 'assistant'; + content: (TextContent | ToolUseContent)[]; +}; + +export type ToolResultContent = { + type: 'tool_result'; + tool_use_id: string; + content: string; +}; + +export type UserMessage = { + role: 'user'; + content: (TextContent | ToolResultContent)[]; +}; + +export type Message = AssistantMessage | UserMessage; + +// New types specific to toolAgent +export interface ToolAgentResult { + result: string; + interactions: number; +} + +export interface ToolCallResult { + sequenceCompleted: boolean; + completionResult?: string; + toolResults: any[]; + respawn?: { context: string }; +} + +export type ErrorResult = { + errorMessage: string; + errorType: string; +}; From 49c468b5c9a42b1d153f6ca2862ea872a0475b0e Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Tue, 4 Mar 2025 09:26:55 -0500 Subject: [PATCH 12/14] Fix duplicate types.ts by re-exporting from original --- packages/agent/src/core/toolAgent/README.md | 2 +- packages/agent/src/core/toolAgent/types.ts | 71 +-------------------- 2 files changed, 4 insertions(+), 69 deletions(-) diff --git a/packages/agent/src/core/toolAgent/README.md b/packages/agent/src/core/toolAgent/README.md index 37f91d3..dbcc9a9 100644 --- a/packages/agent/src/core/toolAgent/README.md +++ b/packages/agent/src/core/toolAgent/README.md @@ -9,7 +9,7 @@ This directory contains the refactored Tool Agent implementation, split into sma - **messageUtils.ts**: Utilities for handling and formatting messages - **toolExecutor.ts**: Logic for executing tool calls - **tokenTracking.ts**: Enhanced utilities for token tracking -- **types.ts**: Consolidated type definitions +- **types.ts**: Additional type definitions specific to toolAgent (re-exports from core/types.ts) ## Usage diff --git a/packages/agent/src/core/toolAgent/types.ts b/packages/agent/src/core/toolAgent/types.ts index a9debdb..90af2e6 100644 --- a/packages/agent/src/core/toolAgent/types.ts +++ b/packages/agent/src/core/toolAgent/types.ts @@ -1,74 +1,9 @@ -import { z } from 'zod'; -import { JsonSchema7Type } from 'zod-to-json-schema'; import { CoreMessage } from 'ai'; -import { Logger } from '../../utils/logger.js'; -import { TokenTracker } from '../tokens.js'; +// Re-export all types from the original types.ts file +export * from '../types.js'; -// Re-export existing types from the original types.ts -export type TokenLevel = 'debug' | 'verbose' | 'info' | 'warn' | 'error'; -export type pageFilter = 'simple' | 'none' | 'readability'; - -export type ToolContext = { - logger: Logger; - workingDirectory: string; - headless: boolean; - userSession: boolean; - pageFilter: pageFilter; - tokenTracker: TokenTracker; -}; - -export type Tool, TReturn = any> = { - name: string; - description: string; - parameters: z.ZodType; - returns: z.ZodType; - logPrefix?: string; - - logParameters?: (params: TParams, context: ToolContext) => void; - logReturns?: (returns: TReturn, context: ToolContext) => void; - - execute: (params: TParams, context: ToolContext) => Promise; - - // Keep JsonSchema7Type for backward compatibility and Vercel AI SDK integration - parametersJsonSchema?: JsonSchema7Type; - returnsJsonSchema?: JsonSchema7Type; -}; - -export type ToolCall = { - id: string; - name: string; - input: any; -}; - -export type TextContent = { - type: 'text'; - text: string; -}; - -export type ToolUseContent = { - type: 'tool_use'; -} & ToolCall; - -export type AssistantMessage = { - role: 'assistant'; - content: (TextContent | ToolUseContent)[]; -}; - -export type ToolResultContent = { - type: 'tool_result'; - tool_use_id: string; - content: string; -}; - -export type UserMessage = { - role: 'user'; - content: (TextContent | ToolResultContent)[]; -}; - -export type Message = AssistantMessage | UserMessage; - -// New types specific to toolAgent +// Only define new types specific to toolAgent here export interface ToolAgentResult { result: string; interactions: number; From ac61683a20cabc91c8dbda6820ba09614ba111df Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Tue, 4 Mar 2025 09:56:27 -0500 Subject: [PATCH 13/14] modularity --- .changeset/implement-token-caching.md | 4 ++-- .changeset/refactor-tool-agent.md | 4 ++-- packages/agent/src/core/toolAgent.test.ts | 1 - packages/agent/src/core/toolAgent.ts | 2 +- packages/agent/src/core/toolAgent/config.ts | 1 + packages/agent/src/core/toolAgent/index.ts | 9 ++++----- packages/agent/src/core/toolAgent/messageUtils.ts | 4 +++- packages/agent/src/core/toolAgent/types.ts | 2 -- 8 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.changeset/implement-token-caching.md b/.changeset/implement-token-caching.md index 6c1c577..4f5e288 100644 --- a/.changeset/implement-token-caching.md +++ b/.changeset/implement-token-caching.md @@ -1,5 +1,5 @@ --- -"mycoder-agent": patch +'mycoder-agent': patch --- -Re-implemented token caching for Vercel AI SDK usage with Anthropic provider to reduce token consumption during repeated API calls. \ No newline at end of file +Re-implemented token caching for Vercel AI SDK usage with Anthropic provider to reduce token consumption during repeated API calls. diff --git a/.changeset/refactor-tool-agent.md b/.changeset/refactor-tool-agent.md index 454802d..2db0f5d 100644 --- a/.changeset/refactor-tool-agent.md +++ b/.changeset/refactor-tool-agent.md @@ -1,5 +1,5 @@ --- -"mycoder-agent": minor +'mycoder-agent': minor --- -Refactored toolAgent.ts into modular components for improved maintainability and testability. Split into config.ts, messageUtils.ts, toolExecutor.ts, tokenTracking.ts, and types.ts modules. \ No newline at end of file +Refactored toolAgent.ts into modular components for improved maintainability and testability. Split into config.ts, messageUtils.ts, toolExecutor.ts, tokenTracking.ts, and types.ts modules. diff --git a/packages/agent/src/core/toolAgent.test.ts b/packages/agent/src/core/toolAgent.test.ts index a2d35a6..16fd17f 100644 --- a/packages/agent/src/core/toolAgent.test.ts +++ b/packages/agent/src/core/toolAgent.test.ts @@ -1,4 +1,3 @@ -import { anthropic } from '@ai-sdk/anthropic'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { z } from 'zod'; diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 918a6c9..f99fccb 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -14,7 +14,7 @@ export { toolAgent, ToolAgentResult, Tool, ToolContext }; const deprecationWarning = () => { console.warn( 'Warning: Importing directly from toolAgent.ts is deprecated. ' + - 'Please import from the toolAgent directory instead.' + 'Please import from the toolAgent directory instead.', ); }; diff --git a/packages/agent/src/core/toolAgent/config.ts b/packages/agent/src/core/toolAgent/config.ts index 25f56d9..8eea29e 100644 --- a/packages/agent/src/core/toolAgent/config.ts +++ b/packages/agent/src/core/toolAgent/config.ts @@ -1,4 +1,5 @@ import { execSync } from 'child_process'; + import { anthropic } from '@ai-sdk/anthropic'; /** diff --git a/packages/agent/src/core/toolAgent/index.ts b/packages/agent/src/core/toolAgent/index.ts index 75fc7d5..6076672 100644 --- a/packages/agent/src/core/toolAgent/index.ts +++ b/packages/agent/src/core/toolAgent/index.ts @@ -1,5 +1,4 @@ import { CoreMessage, ToolSet, generateText, tool as makeTool } from 'ai'; -import chalk from 'chalk'; import { getAnthropicApiKeyError } from '../../utils/errors.js'; @@ -10,8 +9,8 @@ import { createToolCallParts, formatToolCalls, } from './messageUtils.js'; -import { executeTools } from './toolExecutor.js'; import { logTokenUsage } from './tokenTracking.js'; +import { executeTools } from './toolExecutor.js'; import { Tool, ToolAgentResult, ToolContext } from './types.js'; /** @@ -62,7 +61,7 @@ export const toolAgent = async ( parameters: tool.parameters, }); }); - + // Apply cache control to messages for token caching const messagesWithCacheControl = [ createCacheControlMessageFromSystemPrompt(systemPrompt), @@ -75,7 +74,7 @@ export const toolAgent = async ( messages: messagesWithCacheControl, tools: toolSet, }; - const { text, toolCalls, ...other } = await generateText(generateTextProps); + const { text, toolCalls } = await generateText(generateTextProps); const localToolCalls = formatToolCalls(toolCalls); @@ -145,7 +144,7 @@ export const toolAgent = async ( result: 'Maximum sub-agent iterations reach without successful completion', interactions, }; - + logTokenUsage(tokenTracker); return result; }; diff --git a/packages/agent/src/core/toolAgent/messageUtils.ts b/packages/agent/src/core/toolAgent/messageUtils.ts index fa3be03..7c8b71a 100644 --- a/packages/agent/src/core/toolAgent/messageUtils.ts +++ b/packages/agent/src/core/toolAgent/messageUtils.ts @@ -21,7 +21,9 @@ export function createCacheControlMessageFromSystemPrompt( * This marks the last two messages as ephemeral which allows the conversation up to that * point to be cached (with a ~5 minute window), reducing token usage when making multiple API calls */ -export function addCacheControlToMessages(messages: CoreMessage[]): CoreMessage[] { +export function addCacheControlToMessages( + messages: CoreMessage[], +): CoreMessage[] { if (messages.length <= 1) return messages; // Create a deep copy of the messages array to avoid mutating the original diff --git a/packages/agent/src/core/toolAgent/types.ts b/packages/agent/src/core/toolAgent/types.ts index 90af2e6..a7c57b0 100644 --- a/packages/agent/src/core/toolAgent/types.ts +++ b/packages/agent/src/core/toolAgent/types.ts @@ -1,5 +1,3 @@ -import { CoreMessage } from 'ai'; - // Re-export all types from the original types.ts file export * from '../types.js'; From ba7774da6671fb8d8bc4513e38583a1f980c93b9 Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Tue, 4 Mar 2025 10:10:19 -0500 Subject: [PATCH 14/14] minor fixes. --- .changeset/temp-changeset-message.txt | 1 - CONTRIBUTING.md | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 .changeset/temp-changeset-message.txt diff --git a/.changeset/temp-changeset-message.txt b/.changeset/temp-changeset-message.txt deleted file mode 100644 index da31bbe..0000000 --- a/.changeset/temp-changeset-message.txt +++ /dev/null @@ -1 +0,0 @@ -Add textEditor tool that combines readFile and updateFile functionality diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59bc346..ac90ad2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,17 +80,19 @@ This project and everyone participating in it is governed by our Code of Conduct 5. Push to your fork and create a Pull Request 6. Pre-commit Hooks: - + We use [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/okonet/lint-staged) to automatically run linting and formatting on staged files before each commit. This helps maintain code quality and consistency. The pre-commit hooks are configured to run: - - `pnpm lint`: Lints the staged files using ESLint + + - `pnpm lint`: Lints the staged files using ESLint - `pnpm format`: Formats the staged files using Prettier If either of these commands fails due to linting errors or formatting issues, the commit will be aborted. Please fix the reported issues and try committing again. You can also run the lint and format commands manually at any time: - ```bash + + ```bash pnpm lint # Lint all files pnpm format # Format all files ```