diff --git a/packages/agent/package.json b/packages/agent/package.json index 6126113..5b643f5 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -48,6 +48,7 @@ "@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", diff --git a/packages/agent/src/core/llm/anthropic.ts b/packages/agent/src/core/llm/anthropic.ts new file mode 100644 index 0000000..804eee5 --- /dev/null +++ b/packages/agent/src/core/llm/anthropic.ts @@ -0,0 +1,161 @@ +import Anthropic from '@anthropic-ai/sdk'; +import { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages/messages.js'; + +import { getAnthropicApiKeyError } from '../../utils/errors.js'; +import { Message, Tool, ToolContext, ToolUseContent } from '../types.js'; +import { TokenUsage } from '../tokens.js'; + +import { LLMProvider, LLMProviderResponse } from './types.js'; + +function processResponse(content: any[]): { + content: any[]; + toolCalls: ToolUseContent[]; +} { + const processedContent: any[] = []; + const toolCalls: ToolUseContent[] = []; + + for (const message of content) { + if (message.type === 'text') { + processedContent.push({ type: 'text', text: message.text }); + } else if (message.type === 'tool_use') { + const toolUse: ToolUseContent = { + type: 'tool_use', + name: message.name, + id: message.id, + input: message.input, + }; + processedContent.push(toolUse); + toolCalls.push(toolUse); + } + } + + return { content: processedContent, toolCalls }; +} + +// Helper function to add cache control to content blocks +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' } }; + } + } + return c; + }); +} + +// Helper function to add cache control to messages +function addCacheControlToMessages(messages: any[]): any[] { + 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, + }; + }); +} + +// Helper function to add cache control to tools +function addCacheControlToTools(tools: T[]): T[] { + return tools.map((t, i) => ({ + ...t, + ...(i === tools.length - 1 ? { cache_control: { type: 'ephemeral' } } : {}), + })); +} + +export class AnthropicProvider implements LLMProvider { + private model: string; + private maxTokens: number; + private temperature: number; + + constructor({ + model = 'claude-3-7-sonnet-latest', + maxTokens = 4096, + temperature = 0.7, + } = {}) { + this.model = model; + this.maxTokens = maxTokens; + this.temperature = temperature; + } + + async sendRequest({ + systemPrompt, + messages, + tools, + context, + }: { + systemPrompt: string; + messages: Message[]; + tools: Tool[]; + context: ToolContext; + }): Promise { + const { logger, tokenTracker } = context; + + const apiKey = process.env.ANTHROPIC_API_KEY; + if (!apiKey) throw new Error(getAnthropicApiKeyError()); + + // Create Anthropic client + const client = new Anthropic({ apiKey }); + + logger.verbose( + `Requesting completion with ${messages.length} messages with ${JSON.stringify(messages).length} bytes`, + ); + + // Create request parameters + const response = await client.messages.create({ + model: this.model, + max_tokens: this.maxTokens, + temperature: this.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' }, + }); + + if (!response.content.length) { + return { content: [], toolCalls: [] }; + } + + // Track token usage + const tokenUsagePerMessage = TokenUsage.fromMessage(response); + tokenTracker.tokenUsage.add(tokenUsagePerMessage); + + return processResponse(response.content); + } +} diff --git a/packages/agent/src/core/llm/index.ts b/packages/agent/src/core/llm/index.ts new file mode 100644 index 0000000..c2f4098 --- /dev/null +++ b/packages/agent/src/core/llm/index.ts @@ -0,0 +1,2 @@ +export * from './types.js'; +export * from './anthropic.js'; diff --git a/packages/agent/src/core/llm/types.ts b/packages/agent/src/core/llm/types.ts new file mode 100644 index 0000000..8b8a8a4 --- /dev/null +++ b/packages/agent/src/core/llm/types.ts @@ -0,0 +1,23 @@ +import { Tool, Message, ToolContext } from '../types.js'; + +export interface LLMProviderResponse { + content: any[]; + toolCalls: any[]; +} + +export interface LLMProvider { + /** + * Sends a request to the LLM provider and returns the response + */ + sendRequest({ + systemPrompt, + messages, + tools, + context, + }: { + systemPrompt: string; + messages: Message[]; + tools: Tool[]; + context: ToolContext; + }): Promise; +} diff --git a/packages/agent/src/core/toolAgent.respawn.test.ts b/packages/agent/src/core/toolAgent.respawn.test.ts index be5c49a..75b9a13 100644 --- a/packages/agent/src/core/toolAgent.respawn.test.ts +++ b/packages/agent/src/core/toolAgent.respawn.test.ts @@ -15,32 +15,37 @@ 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 }, - }), - }, - })), - }; -}); + +// Mock the AnthropicProvider +vi.mock('./llm/anthropic.js', () => ({ + AnthropicProvider: class { + constructor() {} + sendRequest = vi + .fn() + .mockResolvedValueOnce({ + content: [ + { + type: 'tool_use', + name: 'respawn', + id: 'test-id', + input: { respawnContext: 'new context' }, + }, + ], + toolCalls: [ + { + type: 'tool_use', + name: 'respawn', + id: 'test-id', + input: { respawnContext: 'new context' }, + }, + ], + }) + .mockResolvedValueOnce({ + content: [], + toolCalls: [] + }) + }, +})); describe('toolAgent respawn functionality', () => { const tools = getTools(); diff --git a/packages/agent/src/core/toolAgent.test.ts b/packages/agent/src/core/toolAgent.test.ts index 43bf116..64076de 100644 --- a/packages/agent/src/core/toolAgent.test.ts +++ b/packages/agent/src/core/toolAgent.test.ts @@ -7,6 +7,31 @@ import { TokenTracker } from './tokens.js'; import { toolAgent } from './toolAgent.js'; import { Tool, ToolContext } from './types.js'; +// Mock the AnthropicProvider +vi.mock('./llm/anthropic.js', () => ({ + AnthropicProvider: class { + constructor() {} + sendRequest = vi.fn().mockImplementation(() => ({ + content: [ + { + type: 'tool_use', + name: 'sequenceComplete', + id: '1', + input: { result: 'Test complete' }, + }, + ], + toolCalls: [ + { + type: 'tool_use', + name: 'sequenceComplete', + id: '1', + input: { result: 'Test complete' }, + }, + ], + })) + }, +})); + const toolContext: ToolContext = { logger: new MockLogger(), headless: true, @@ -25,32 +50,6 @@ const testConfig = { getSystemPrompt: () => 'Test system prompt', }; -// Mock Anthropic client response -const mockResponse = { - content: [ - { - type: 'tool_use', - name: 'sequenceComplete', - id: '1', - input: { result: 'Test complete' }, - }, - ], - usage: { input_tokens: 10, output_tokens: 10 }, - model: 'claude-3-7-sonnet-latest', - role: 'assistant', - id: 'msg_123', -}; - -// Mock Anthropic SDK -const mockCreate = vi.fn().mockImplementation(() => mockResponse); -vi.mock('@anthropic-ai/sdk', () => ({ - default: class { - messages = { - create: mockCreate, - }; - }, -})); - describe('toolAgent', () => { beforeEach(() => { process.env.ANTHROPIC_API_KEY = 'test-key'; @@ -160,17 +159,8 @@ describe('toolAgent', () => { ).rejects.toThrow('Deliberate failure'); }); - // Test empty response handling - it('should handle empty responses by sending a reminder', async () => { - // Reset the mock and set up the sequence of responses - mockCreate.mockReset(); - mockCreate - .mockResolvedValueOnce({ - content: [], - usage: { input_tokens: 5, output_tokens: 5 }, - }) - .mockResolvedValueOnce(mockResponse); - + // Test the toolAgent with the mocked AnthropicProvider + it('should complete a sequence', async () => { const result = await toolAgent( 'Test prompt', [sequenceCompleteTool], @@ -178,17 +168,11 @@ describe('toolAgent', () => { toolContext, ); - // Verify that create was called twice (once for empty response, once for completion) - expect(mockCreate).toHaveBeenCalledTimes(2); expect(result.result).toBe('Test complete'); }); // New tests for async system prompt it('should handle async system prompt', async () => { - // Reset mock and set expected response - mockCreate.mockReset(); - mockCreate.mockResolvedValue(mockResponse); - const result = await toolAgent( 'Test prompt', [sequenceCompleteTool], diff --git a/packages/agent/src/core/toolAgent.ts b/packages/agent/src/core/toolAgent.ts index 4a7c9ae..c221ebe 100644 --- a/packages/agent/src/core/toolAgent.ts +++ b/packages/agent/src/core/toolAgent.ts @@ -1,13 +1,11 @@ import { execSync } from 'child_process'; -import Anthropic from '@anthropic-ai/sdk'; -import { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages/messages.js'; import chalk from 'chalk'; -import { getAnthropicApiKeyError } from '../utils/errors.js'; - +import { AnthropicProvider } from './llm/anthropic.js'; +import { LLMProvider } from './llm/types.js'; import { executeToolCall } from './executeToolCall.js'; -import { TokenTracker, TokenUsage } from './tokens.js'; +import { TokenTracker } from './tokens.js'; import { Tool, TextContent, @@ -79,7 +77,7 @@ const CONFIG = { 'When you run into issues or unexpected results, take a step back and read the project documentation and configuration files and look at other source files in the project for examples of what works.', '', 'Use sub-agents for parallel tasks, providing them with specific context they need rather than having them rediscover it.', - ].join('\\n'); + ].join('\n'); }, }; @@ -89,28 +87,6 @@ interface ToolCallResult { toolResults: ToolResultContent[]; } -function processResponse(response: Anthropic.Message) { - const content: (TextContent | ToolUseContent)[] = []; - const toolCalls: ToolUseContent[] = []; - - for (const message of response.content) { - if (message.type === 'text') { - content.push({ type: 'text', text: message.text }); - } else if (message.type === 'tool_use') { - const toolUse: ToolUseContent = { - type: 'tool_use', - name: message.name, - id: message.id, - input: message.input, - }; - content.push(toolUse); - toolCalls.push(toolUse); - } - } - - return { content, toolCalls }; -} - async function executeTools( toolCalls: ToolUseContent[], tools: Tool[], @@ -184,62 +160,6 @@ async function executeTools( return { sequenceCompleted, 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) => ({ - ...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' } }; - } - } - return c; - }); -} -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, tools: Tool[], @@ -253,10 +173,13 @@ export const toolAgent = async ( let interactions = 0; - const apiKey = process.env.ANTHROPIC_API_KEY; - if (!apiKey) throw new Error(getAnthropicApiKeyError()); + // Create LLM provider + const llmProvider: LLMProvider = new AnthropicProvider({ + model: config.model, + maxTokens: config.maxTokens, + temperature: config.temperature, + }); - const client = new Anthropic({ apiKey }); const messages: Message[] = [ { role: 'user', @@ -278,32 +201,15 @@ 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 response = await client.messages.create(requestParams); + // Request completion from LLM provider + const { content, toolCalls } = await llmProvider.sendRequest({ + systemPrompt, + messages, + tools, + context, + }); - if (!response.content.length) { + if (!content.length) { // Instead of treating empty response as completion, remind the agent logger.verbose('Received empty response from agent, sending reminder'); messages.push({ @@ -318,11 +224,6 @@ export const toolAgent = async ( continue; } - // Track both regular and cached token usage - const tokenUsagePerMessage = TokenUsage.fromMessage(response); - tokenTracker.tokenUsage.add(tokenUsagePerMessage); - - const { content, toolCalls } = processResponse(response); messages.push({ role: 'assistant', content, @@ -332,14 +233,14 @@ export const toolAgent = async ( const assistantMessage = content .filter((c) => c.type === 'text') .map((c) => c.text) - .join('\\n'); + .join('\n'); if (assistantMessage) { logger.info(assistantMessage); } logger.log( tokenTracker.logLevel, - chalk.blue(`[Token Usage/Message] ${tokenUsagePerMessage.toString()}`), + chalk.blue(`[Token Usage/Message] ${tokenTracker.tokenUsage.toString()}`), ); const { sequenceCompleted, completionResult, respawn } = await executeTools( diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 338de52..fd189d0 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -9,11 +9,10 @@ import { hideBin } from 'yargs/helpers'; import { fileCommands } from 'yargs-file-commands'; // 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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2a1fd5..df3bd5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,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 @@ -167,6 +170,40 @@ importers: packages: + '@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 + '@anthropic-ai/sdk@0.37.0': resolution: {integrity: sha512-tHjX2YbkUBwEgg0JZU3EFSSAQPoK4qQR/NFYa8Vtzd5UAyXzZksCw2In69Rml4R/TyHPBfRYaLK35XiOe33pjw==} @@ -1080,6 +1117,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==} @@ -1252,6 +1292,18 @@ packages: 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 +1553,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'} @@ -1726,6 +1781,10 @@ packages: 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==} engines: {node: '>=12.0.0'} @@ -2135,6 +2194,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 +2204,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==} @@ -2487,6 +2554,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 +2643,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 +2786,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 +2811,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==} @@ -2869,6 +2952,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 @@ -3082,6 +3170,37 @@ packages: snapshots: + '@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 + '@anthropic-ai/sdk@0.37.0': dependencies: '@types/node': 18.19.76 @@ -4013,6 +4132,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': {} @@ -4228,6 +4349,18 @@ snapshots: dependencies: humanize-ms: 1.2.1 + ai@4.1.50(react@19.0.0)(zod@3.24.2): + dependencies: + '@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: fast-deep-equal: 3.1.3 @@ -4478,6 +4611,8 @@ snapshots: detect-indent@6.1.0: {} + diff-match-patch@1.0.5: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -4819,6 +4954,8 @@ snapshots: event-target-shim@5.0.1: {} + eventsource-parser@3.0.0: {} + expect-type@1.1.0: {} extendable-error@0.1.7: {} @@ -5273,12 +5410,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 @@ -5582,6 +5727,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 +5849,8 @@ snapshots: dependencies: xmlchars: 2.2.0 + secure-json-parse@2.7.0: {} + semver@6.3.1: {} semver@7.7.1: {} @@ -5855,6 +6004,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 +6029,8 @@ snapshots: source-map-support: 0.5.21 optional: true + throttleit@2.1.0: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -6020,6 +6177,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):