From 6746351dfd989b43a7c8eb0d8deaf7aa189225da Mon Sep 17 00:00:00 2001 From: Ben Houston Date: Mon, 3 Mar 2025 15:16:06 -0500 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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}`);