diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd5ce40055..625b7d63b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -141,6 +141,7 @@ jobs: run: yarn workspace @forestadmin/ai-proxy test --testPathPattern='llm.integration' env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} send-coverage: name: Send Coverage diff --git a/packages/_example/src/forest/agent.ts b/packages/_example/src/forest/agent.ts index e3130780ee..5cb307c2de 100644 --- a/packages/_example/src/forest/agent.ts +++ b/packages/_example/src/forest/agent.ts @@ -94,5 +94,11 @@ export default function makeAgent() { .customizeCollection('post', customizePost) .customizeCollection('comment', customizeComment) .customizeCollection('review', customizeReview) - .customizeCollection('sales', customizeSales); + .customizeCollection('sales', customizeSales) + .addAi({ + model: 'gpt-4o', + provider: 'openai', + name: 'test', + apiKey: process.env.OPENAI_API_KEY, + }); } diff --git a/packages/ai-proxy/jest.config.ts b/packages/ai-proxy/jest.config.ts index a5116844e1..cd7c83fbf7 100644 --- a/packages/ai-proxy/jest.config.ts +++ b/packages/ai-proxy/jest.config.ts @@ -6,4 +6,8 @@ export default { collectCoverageFrom: ['/src/**/*.ts'], testMatch: ['/test/**/*.test.ts'], setupFiles: ['/test/setup-env.ts'], + // Fix module resolution for @anthropic-ai/sdk submodules (peer dep of @langchain/anthropic) + moduleNameMapper: { + '^@anthropic-ai/sdk/(.*)$': '/../../node_modules/@anthropic-ai/sdk/$1', + }, }; diff --git a/packages/ai-proxy/package.json b/packages/ai-proxy/package.json index a6dc96053a..90bfa0d70b 100644 --- a/packages/ai-proxy/package.json +++ b/packages/ai-proxy/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@forestadmin/datasource-toolkit": "1.50.1", + "@langchain/anthropic": "1.3.14", "@langchain/community": "1.1.4", "@langchain/core": "1.1.15", "@langchain/langgraph": "^1.1.0", diff --git a/packages/ai-proxy/src/errors.ts b/packages/ai-proxy/src/errors.ts index fc1e5e2507..7e667a71a9 100644 --- a/packages/ai-proxy/src/errors.ts +++ b/packages/ai-proxy/src/errors.ts @@ -69,6 +69,13 @@ export class OpenAIUnprocessableError extends AIUnprocessableError { } } +export class AnthropicUnprocessableError extends AIUnprocessableError { + constructor(message: string) { + super(message); + this.name = 'AnthropicError'; + } +} + export class AIToolUnprocessableError extends AIUnprocessableError { constructor(message: string) { super(message); diff --git a/packages/ai-proxy/src/provider-dispatcher.ts b/packages/ai-proxy/src/provider-dispatcher.ts index ac324d01fc..ac49a14f97 100644 --- a/packages/ai-proxy/src/provider-dispatcher.ts +++ b/packages/ai-proxy/src/provider-dispatcher.ts @@ -1,17 +1,31 @@ -import type { AiConfiguration, ChatCompletionResponse, ChatCompletionTool } from './provider'; +import type { + AiConfiguration, + ChatCompletionResponse, + ChatCompletionTool, + ChatCompletionToolChoice, +} from './provider'; import type { RemoteTools } from './remote-tools'; import type { DispatchBody } from './schemas/route'; -import type { BaseMessageLike } from '@langchain/core/messages'; +import type { BaseMessage, BaseMessageLike } from '@langchain/core/messages'; +import { ChatAnthropic } from '@langchain/anthropic'; +import { AIMessage, HumanMessage, SystemMessage, ToolMessage } from '@langchain/core/messages'; import { convertToOpenAIFunction } from '@langchain/core/utils/function_calling'; import { ChatOpenAI } from '@langchain/openai'; -import { AINotConfiguredError, OpenAIUnprocessableError } from './errors'; +import { + AIBadRequestError, + AINotConfiguredError, + AnthropicUnprocessableError, + OpenAIUnprocessableError, +} from './errors'; // Re-export types for consumers export type { AiConfiguration, AiProvider, + AnthropicConfiguration, + AnthropicModel, BaseAiConfiguration, ChatCompletionMessage, ChatCompletionResponse, @@ -21,8 +35,24 @@ export type { } from './provider'; export type { DispatchBody } from './schemas/route'; +interface OpenAIMessage { + role: 'system' | 'user' | 'assistant' | 'tool'; + content: string | null; + tool_calls?: Array<{ + id: string; + function: { + name: string; + arguments: string; + }; + }>; + tool_call_id?: string; +} export class ProviderDispatcher { - private readonly chatModel: ChatOpenAI | null = null; + private readonly openaiModel: ChatOpenAI | null = null; + + private readonly anthropicModel: ChatAnthropic | null = null; + + private readonly modelName: string | null = null; private readonly remoteTools: RemoteTools; @@ -31,19 +61,35 @@ export class ProviderDispatcher { if (configuration?.provider === 'openai') { const { provider, name, ...chatOpenAIOptions } = configuration; - this.chatModel = new ChatOpenAI({ + this.openaiModel = new ChatOpenAI({ maxRetries: 0, // No retries by default - this lib is a passthrough ...chatOpenAIOptions, __includeRawResponse: true, }); + } else if (configuration?.provider === 'anthropic') { + const { provider, name, model, ...clientOptions } = configuration; + this.anthropicModel = new ChatAnthropic({ + maxRetries: 0, // No retries by default - this lib is a passthrough + ...clientOptions, + model, + }); + this.modelName = model; } } async dispatch(body: DispatchBody): Promise { - if (!this.chatModel) { - throw new AINotConfiguredError(); + if (this.openaiModel) { + return this.dispatchOpenAI(body); } + if (this.anthropicModel) { + return this.dispatchAnthropic(body); + } + + throw new AINotConfiguredError(); + } + + private async dispatchOpenAI(body: DispatchBody): Promise { const { tools, messages, @@ -53,11 +99,11 @@ export class ProviderDispatcher { const enrichedTools = this.enrichToolDefinitions(tools); const model = enrichedTools?.length - ? this.chatModel.bindTools(enrichedTools, { + ? this.openaiModel!.bindTools(enrichedTools, { tool_choice: toolChoice, parallel_tool_calls: parallelToolCalls, }) - : this.chatModel; + : this.openaiModel!; try { const response = await model.invoke(messages as BaseMessageLike[]); @@ -89,6 +135,165 @@ export class ProviderDispatcher { } } + private async dispatchAnthropic(body: DispatchBody): Promise { + const { tools, messages, tool_choice: toolChoice } = body; + + // Convert messages outside try-catch so input validation errors propagate directly + const langChainMessages = this.convertMessagesToLangChain(messages as OpenAIMessage[]); + const enhancedTools = tools ? this.enrichToolDefinitions(tools) : undefined; + + try { + let response: AIMessage; + + if (enhancedTools?.length) { + const langChainTools = this.convertToolsToLangChain(enhancedTools); + const clientWithTools = this.anthropicModel!.bindTools(langChainTools, { + tool_choice: this.convertToolChoiceToLangChain(toolChoice), + }); + response = await clientWithTools.invoke(langChainMessages); + } else { + response = await this.anthropicModel!.invoke(langChainMessages); + } + + return this.convertLangChainResponseToOpenAI(response); + } catch (error) { + if (error instanceof AnthropicUnprocessableError) throw error; + + const err = error as Error & { status?: number }; + + if (err.status === 429) { + throw new AnthropicUnprocessableError(`Rate limit exceeded: ${err.message}`); + } + + if (err.status === 401) { + throw new AnthropicUnprocessableError(`Authentication failed: ${err.message}`); + } + + throw new AnthropicUnprocessableError(`Error while calling Anthropic: ${err.message}`); + } + } + + private convertMessagesToLangChain(messages: OpenAIMessage[]): BaseMessage[] { + return messages.map(msg => { + switch (msg.role) { + case 'system': + return new SystemMessage(msg.content || ''); + case 'user': + return new HumanMessage(msg.content || ''); + case 'assistant': + if (msg.tool_calls) { + return new AIMessage({ + content: msg.content || '', + tool_calls: msg.tool_calls.map(tc => ({ + id: tc.id, + name: tc.function.name, + args: ProviderDispatcher.parseToolArguments( + tc.function.name, + tc.function.arguments, + ), + })), + }); + } + + return new AIMessage(msg.content || ''); + case 'tool': + if (!msg.tool_call_id) { + throw new AIBadRequestError('Tool message is missing required "tool_call_id" field.'); + } + + return new ToolMessage({ + content: msg.content || '', + tool_call_id: msg.tool_call_id, + }); + default: + throw new AIBadRequestError( + `Unsupported message role '${msg.role}'. Expected: system, user, assistant, or tool.`, + ); + } + }); + } + + private static parseToolArguments(toolName: string, args: string): Record { + try { + return JSON.parse(args); + } catch { + throw new AIBadRequestError( + `Invalid JSON in tool_calls arguments for tool '${toolName}': ${args}`, + ); + } + } + + private convertToolsToLangChain(tools: ChatCompletionTool[]): Array<{ + type: 'function'; + function: { name: string; description?: string; parameters?: Record }; + }> { + return tools + .filter((tool): tool is ChatCompletionTool & { type: 'function' } => tool.type === 'function') + .map(tool => ({ + type: 'function' as const, + function: { + name: tool.function.name, + description: tool.function.description, + parameters: tool.function.parameters as Record | undefined, + }, + })); + } + + private convertToolChoiceToLangChain( + toolChoice: ChatCompletionToolChoice | undefined, + ): 'auto' | 'any' | 'none' | { type: 'tool'; name: string } | undefined { + if (!toolChoice) return undefined; + if (toolChoice === 'auto') return 'auto'; + if (toolChoice === 'none') return 'none'; + if (toolChoice === 'required') return 'any'; + + if (typeof toolChoice === 'object' && toolChoice.type === 'function') { + return { type: 'tool', name: toolChoice.function.name }; + } + + return undefined; + } + + private convertLangChainResponseToOpenAI(response: AIMessage): ChatCompletionResponse { + const toolCalls = response.tool_calls?.map(tc => ({ + id: tc.id || `call_${Date.now()}`, + type: 'function' as const, + function: { + name: tc.name, + arguments: JSON.stringify(tc.args), + }, + })); + + const usageMetadata = response.usage_metadata as + | { input_tokens?: number; output_tokens?: number; total_tokens?: number } + | undefined; + + return { + id: response.id || `msg_${Date.now()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: this.modelName!, + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: typeof response.content === 'string' ? response.content : null, + refusal: null, + tool_calls: toolCalls?.length ? toolCalls : undefined, + }, + finish_reason: toolCalls?.length ? 'tool_calls' : 'stop', + logprobs: null, + }, + ], + usage: { + prompt_tokens: usageMetadata?.input_tokens ?? 0, + completion_tokens: usageMetadata?.output_tokens ?? 0, + total_tokens: usageMetadata?.total_tokens ?? 0, + }, + }; + } + private enrichToolDefinitions(tools?: ChatCompletionTool[]) { if (!tools || !Array.isArray(tools)) return tools; diff --git a/packages/ai-proxy/src/provider.ts b/packages/ai-proxy/src/provider.ts index ba1730c5f6..a3d6a351e8 100644 --- a/packages/ai-proxy/src/provider.ts +++ b/packages/ai-proxy/src/provider.ts @@ -1,3 +1,4 @@ +import type { AnthropicInput, AnthropicMessagesModelId } from '@langchain/anthropic'; import type { ChatOpenAIFields, OpenAIChatModelId } from '@langchain/openai'; import type OpenAI from 'openai'; @@ -7,8 +8,12 @@ export type ChatCompletionMessage = OpenAI.Chat.Completions.ChatCompletionMessag export type ChatCompletionTool = OpenAI.Chat.Completions.ChatCompletionTool; export type ChatCompletionToolChoice = OpenAI.Chat.Completions.ChatCompletionToolChoiceOption; +// Anthropic model type from langchain (auto-updated when SDK updates) +// Includes known models for autocomplete + allows custom strings +export type AnthropicModel = AnthropicMessagesModelId; + // AI Provider types -export type AiProvider = 'openai'; +export type AiProvider = 'openai' | 'anthropic'; /** * Base configuration common to all AI providers. @@ -24,7 +29,7 @@ export type BaseAiConfiguration = { * OpenAI-specific configuration. * Extends base with all ChatOpenAI options (temperature, maxTokens, configuration, etc.) */ -export type OpenAiConfiguration = BaseAiConfiguration & +export type OpenAiConfiguration = Omit & Omit & { provider: 'openai'; // OpenAIChatModelId provides autocomplete for known models (gpt-4o, gpt-4-turbo, etc.) @@ -32,4 +37,15 @@ export type OpenAiConfiguration = BaseAiConfiguration & model: OpenAIChatModelId | (string & NonNullable); }; -export type AiConfiguration = OpenAiConfiguration; +/** + * Anthropic-specific configuration. + * Extends base with all ChatAnthropic options (temperature, maxTokens, etc.) + * Supports both `apiKey` (unified) and `anthropicApiKey` (native) for flexibility. + */ +export type AnthropicConfiguration = BaseAiConfiguration & + Omit & { + provider: 'anthropic'; + model: AnthropicModel; + }; + +export type AiConfiguration = OpenAiConfiguration | AnthropicConfiguration; diff --git a/packages/ai-proxy/src/router.ts b/packages/ai-proxy/src/router.ts index 5812df10b8..d8ea971edc 100644 --- a/packages/ai-proxy/src/router.ts +++ b/packages/ai-proxy/src/router.ts @@ -45,7 +45,7 @@ export class Router { private validateConfigurations(): void { for (const config of this.aiConfigurations) { - if (!isModelSupportingTools(config.model)) { + if (config.provider === 'openai' && !isModelSupportingTools(config.model)) { throw new AIModelNotSupportedError(config.model); } } diff --git a/packages/ai-proxy/test/.env-test.example b/packages/ai-proxy/test/.env-test.example index d11920933a..3e5aab7d73 100644 --- a/packages/ai-proxy/test/.env-test.example +++ b/packages/ai-proxy/test/.env-test.example @@ -2,3 +2,4 @@ # This file is used for integration tests OPENAI_API_KEY=sk-your-openai-api-key-here +ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here diff --git a/packages/ai-proxy/test/llm.integration.test.ts b/packages/ai-proxy/test/llm.integration.test.ts index a3f65d55bb..442c1742f4 100644 --- a/packages/ai-proxy/test/llm.integration.test.ts +++ b/packages/ai-proxy/test/llm.integration.test.ts @@ -1,14 +1,18 @@ /** - * End-to-end integration tests with real OpenAI API and MCP server. + * End-to-end integration tests with real OpenAI and Anthropic APIs and MCP server. * - * These tests require a valid OPENAI_API_KEY environment variable. - * They are skipped if the key is not present. + * These tests require valid API key environment variables: + * - OPENAI_API_KEY for OpenAI tests + * - ANTHROPIC_API_KEY for Anthropic tests * - * Run with: yarn workspace @forestadmin/ai-proxy test openai.integration + * Tests are skipped if the corresponding key is not present. + * + * Run with: yarn workspace @forestadmin/ai-proxy test llm.integration */ import type { ChatCompletionResponse } from '../src'; import type { Server } from 'http'; +import Anthropic from '@anthropic-ai/sdk'; // eslint-disable-next-line import/extensions import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import OpenAI from 'openai'; @@ -18,8 +22,9 @@ import { Router } from '../src'; import runMcpServer from '../src/examples/simple-mcp-server'; import isModelSupportingTools from '../src/supported-models'; -const { OPENAI_API_KEY } = process.env; +const { OPENAI_API_KEY, ANTHROPIC_API_KEY } = process.env; const describeWithOpenAI = OPENAI_API_KEY ? describe : describe.skip; +const describeWithAnthropic = ANTHROPIC_API_KEY ? describe : describe.skip; /** * Fetches available models from OpenAI API. @@ -47,6 +52,29 @@ async function fetchChatModelsFromOpenAI(): Promise { .sort(); } +/** + * Fetches available models from Anthropic API. + * Returns all model IDs sorted alphabetically. + * + * All Anthropic chat models support tools, so no filtering is needed. + */ +async function fetchChatModelsFromAnthropic(): Promise { + const anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY }); + + let models; + try { + models = await anthropic.models.list({ limit: 1000 }); + } catch (error) { + throw new Error( + `Failed to fetch models from Anthropic API. ` + + `Ensure ANTHROPIC_API_KEY is valid and network is available. ` + + `Original error: ${error}`, + ); + } + + return models.data.map(m => m.id).sort(); +} + describeWithOpenAI('OpenAI Integration (real API)', () => { const router = new Router({ aiConfigurations: [ @@ -802,3 +830,270 @@ describeWithOpenAI('OpenAI Integration (real API)', () => { }, 300000); // 5 minutes for all models }); }); + +describeWithAnthropic('Anthropic Integration (real API)', () => { + const router = new Router({ + aiConfigurations: [ + { + name: 'test-claude', + provider: 'anthropic', + model: 'claude-3-5-haiku-latest', // Cheapest model with tool support + apiKey: ANTHROPIC_API_KEY, + }, + ], + }); + + describe('route: ai-query', () => { + it('should complete a simple chat request', async () => { + const response = (await router.route({ + route: 'ai-query', + body: { + messages: [ + { role: 'system', content: 'You are a helpful assistant. Be very concise.' }, + { role: 'user', content: 'What is 2+2? Reply with just the number.' }, + ], + }, + })) as ChatCompletionResponse; + + // Anthropic responses are converted to OpenAI-compatible format + expect(response).toMatchObject({ + object: 'chat.completion', + model: 'claude-3-5-haiku-latest', + choices: expect.arrayContaining([ + expect.objectContaining({ + index: 0, + message: expect.objectContaining({ + role: 'assistant', + content: expect.stringContaining('4'), + }), + finish_reason: 'stop', + }), + ]), + usage: expect.objectContaining({ + prompt_tokens: expect.any(Number), + completion_tokens: expect.any(Number), + total_tokens: expect.any(Number), + }), + }); + }, 10000); + + it('should handle tool calls', async () => { + const response = (await router.route({ + route: 'ai-query', + body: { + messages: [{ role: 'user', content: 'What is the weather in Paris?' }], + tools: [ + { + type: 'function', + function: { + name: 'get_weather', + description: 'Get the current weather in a given location', + parameters: { + type: 'object', + properties: { + location: { type: 'string', description: 'The city name' }, + }, + required: ['location'], + }, + }, + }, + ], + tool_choice: 'auto', + }, + })) as ChatCompletionResponse; + + expect(response.choices[0].finish_reason).toBe('tool_calls'); + expect(response.choices[0].message.tool_calls).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'function', + function: expect.objectContaining({ + name: 'get_weather', + arguments: expect.stringContaining('Paris'), + }), + }), + ]), + ); + }, 10000); + + it('should handle tool_choice: required', async () => { + const response = (await router.route({ + route: 'ai-query', + body: { + messages: [{ role: 'user', content: 'Hello!' }], + tools: [ + { + type: 'function', + function: { + name: 'greet', + description: 'Greet the user', + parameters: { type: 'object', properties: {} }, + }, + }, + ], + tool_choice: 'required', + }, + })) as ChatCompletionResponse; + + expect(response.choices[0].finish_reason).toBe('tool_calls'); + const toolCall = response.choices[0].message.tool_calls?.[0] as { + function: { name: string }; + }; + expect(toolCall.function.name).toBe('greet'); + }, 10000); + + it('should complete multi-turn conversation with tool results', async () => { + const addTool = { + type: 'function' as const, + function: { + name: 'calculate', + description: 'Calculate a math expression', + parameters: { + type: 'object', + properties: { expression: { type: 'string' } }, + required: ['expression'], + }, + }, + }; + + // First turn: get tool call + const response1 = (await router.route({ + route: 'ai-query', + body: { + messages: [{ role: 'user', content: 'What is 5 + 3?' }], + tools: [addTool], + tool_choice: 'required', + }, + })) as ChatCompletionResponse; + + expect(response1.choices[0].finish_reason).toBe('tool_calls'); + const toolCall = response1.choices[0].message.tool_calls?.[0]; + expect(toolCall).toBeDefined(); + + // Second turn: provide tool result and get final answer + const response2 = (await router.route({ + route: 'ai-query', + body: { + messages: [ + { role: 'user', content: 'What is 5 + 3?' }, + response1.choices[0].message, + { + role: 'tool', + tool_call_id: toolCall!.id, + content: '8', + }, + ], + }, + })) as ChatCompletionResponse; + + expect(response2.choices[0].finish_reason).toBe('stop'); + expect(response2.choices[0].message.content).toContain('8'); + }, 15000); + }); + + describe('error handling', () => { + it('should throw authentication error with invalid API key', async () => { + const invalidRouter = new Router({ + aiConfigurations: [ + { + name: 'invalid', + provider: 'anthropic', + model: 'claude-3-5-haiku-latest', + apiKey: 'sk-ant-invalid-key', + }, + ], + }); + + await expect( + invalidRouter.route({ + route: 'ai-query', + body: { + messages: [{ role: 'user', content: 'test' }], + }, + }), + ).rejects.toThrow(/Authentication failed|invalid x-api-key/i); + }, 10000); + }); + + describe('Model tool support verification', () => { + let modelsToTest: string[]; + + beforeAll(async () => { + modelsToTest = await fetchChatModelsFromAnthropic(); + }); + + it('should have found models from Anthropic API', () => { + expect(modelsToTest.length).toBeGreaterThan(0); + // eslint-disable-next-line no-console + console.log(`Testing ${modelsToTest.length} Anthropic models:`, modelsToTest); + }); + + it('all models should support tool calls', async () => { + const results: { model: string; success: boolean; error?: string }[] = []; + + for (const model of modelsToTest) { + const modelRouter = new Router({ + aiConfigurations: [ + { name: 'test', provider: 'anthropic', model, apiKey: ANTHROPIC_API_KEY }, + ], + }); + + try { + const response = (await modelRouter.route({ + route: 'ai-query', + body: { + messages: [{ role: 'user', content: 'What is 2+2?' }], + tools: [ + { + type: 'function', + function: { + name: 'calculate', + description: 'Calculate a math expression', + parameters: { type: 'object', properties: { result: { type: 'number' } } }, + }, + }, + ], + tool_choice: 'required', + }, + })) as ChatCompletionResponse; + + const success = + response.choices[0].finish_reason === 'tool_calls' && + response.choices[0].message.tool_calls !== undefined; + + results.push({ model, success }); + } catch (error) { + const errorMessage = String(error); + + // Infrastructure errors should fail the test immediately + const isInfrastructureError = + errorMessage.includes('rate limit') || + errorMessage.includes('429') || + errorMessage.includes('401') || + errorMessage.includes('Authentication') || + errorMessage.includes('ECONNREFUSED') || + errorMessage.includes('ETIMEDOUT') || + errorMessage.includes('getaddrinfo'); + + if (isInfrastructureError) { + throw new Error(`Infrastructure error testing model ${model}: ${errorMessage}`); + } + + results.push({ model, success: false, error: errorMessage }); + } + } + + const failures = results.filter(r => !r.success); + if (failures.length > 0) { + const failedModelNames = failures.map(f => f.model).join(', '); + // eslint-disable-next-line no-console + console.error( + `\n❌ ${failures.length} Anthropic model(s) failed tool support: ${failedModelNames}\n`, + failures, + ); + } + + expect(failures).toEqual([]); + }, 300000); // 5 minutes for all models + }); +}); diff --git a/packages/ai-proxy/test/provider-dispatcher.test.ts b/packages/ai-proxy/test/provider-dispatcher.test.ts index b1b8afb97f..e4bd39f2d2 100644 --- a/packages/ai-proxy/test/provider-dispatcher.test.ts +++ b/packages/ai-proxy/test/provider-dispatcher.test.ts @@ -1,8 +1,14 @@ import type { DispatchBody } from '../src'; +import { AIMessage } from '@langchain/core/messages'; import { convertToOpenAIFunction } from '@langchain/core/utils/function_calling'; -import { AINotConfiguredError, ProviderDispatcher, RemoteTools } from '../src'; +import { + AINotConfiguredError, + AnthropicUnprocessableError, + ProviderDispatcher, + RemoteTools, +} from '../src'; // Mock raw OpenAI response (returned via __includeRawResponse: true) const mockOpenAIResponse = { @@ -45,6 +51,20 @@ jest.mock('@langchain/openai', () => ({ })), })); +const anthropicInvokeMock = jest.fn(); +const anthropicBindToolsMock = jest.fn().mockReturnValue({ invoke: anthropicInvokeMock }); + +jest.mock('@langchain/anthropic', () => { + return { + ChatAnthropic: jest.fn().mockImplementation(() => { + return { + invoke: anthropicInvokeMock, + bindTools: anthropicBindToolsMock, + }; + }), + }; +}); + describe('ProviderDispatcher', () => { const apiKeys = { AI_REMOTE_TOOL_BRAVE_SEARCH_API_KEY: 'api-key' }; @@ -296,4 +316,344 @@ describe('ProviderDispatcher', () => { }); }); }); + + describe('anthropic', () => { + describe('when anthropic is configured', () => { + it('should return the response from anthropic in OpenAI format', async () => { + const mockResponse = new AIMessage({ + content: 'Hello from Claude', + id: 'msg_123', + }); + Object.assign(mockResponse, { + usage_metadata: { input_tokens: 10, output_tokens: 20, total_tokens: 30 }, + }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + const response = await dispatcher.dispatch({ + tools: [], + messages: [{ role: 'user', content: 'Hello' }], + } as unknown as DispatchBody); + + expect(response).toEqual( + expect.objectContaining({ + object: 'chat.completion', + model: 'claude-3-5-sonnet-latest', + choices: [ + expect.objectContaining({ + index: 0, + message: expect.objectContaining({ + role: 'assistant', + content: 'Hello from Claude', + }), + finish_reason: 'stop', + }), + ], + usage: { + prompt_tokens: 10, + completion_tokens: 20, + total_tokens: 30, + }, + }), + ); + }); + + it('should convert OpenAI messages to LangChain format', async () => { + const mockResponse = new AIMessage({ content: 'Response' }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await dispatcher.dispatch({ + tools: [], + messages: [ + { role: 'system', content: 'You are helpful' }, + { role: 'user', content: 'Hello' }, + { role: 'assistant', content: 'Hi there' }, + ], + } as unknown as DispatchBody); + + expect(anthropicInvokeMock).toHaveBeenCalledWith([ + expect.objectContaining({ content: 'You are helpful' }), + expect.objectContaining({ content: 'Hello' }), + expect.objectContaining({ content: 'Hi there' }), + ]); + }); + + it('should convert assistant messages with tool_calls correctly', async () => { + const mockResponse = new AIMessage({ content: 'Done' }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await dispatcher.dispatch({ + tools: [], + messages: [ + { + role: 'assistant', + content: '', + tool_calls: [ + { + id: 'call_123', + function: { name: 'get_weather', arguments: '{"city":"Paris"}' }, + }, + ], + }, + { role: 'tool', content: 'Sunny', tool_call_id: 'call_123' }, + ], + } as unknown as DispatchBody); + + expect(anthropicInvokeMock).toHaveBeenCalledWith([ + expect.objectContaining({ + content: '', + tool_calls: [{ id: 'call_123', name: 'get_weather', args: { city: 'Paris' } }], + }), + expect.objectContaining({ content: 'Sunny', tool_call_id: 'call_123' }), + ]); + }); + + it('should return tool_calls in OpenAI format when Claude calls tools', async () => { + const mockResponse = new AIMessage({ + content: '', + tool_calls: [{ id: 'call_456', name: 'search', args: { query: 'test' } }], + }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + const response = (await dispatcher.dispatch({ + tools: [], + messages: [{ role: 'user', content: 'Search for test' }], + } as unknown as DispatchBody)) as { + choices: Array<{ message: { tool_calls: unknown[] }; finish_reason: string }>; + }; + + expect(response.choices[0].message.tool_calls).toEqual([ + { + id: 'call_456', + type: 'function', + function: { name: 'search', arguments: '{"query":"test"}' }, + }, + ]); + expect(response.choices[0].finish_reason).toBe('tool_calls'); + }); + }); + + describe('when tools are provided', () => { + it('should bind tools to the client', async () => { + const mockResponse = new AIMessage({ content: 'Response' }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await dispatcher.dispatch({ + tools: [ + { + type: 'function', + function: { + name: 'get_weather', + description: 'Get weather for a city', + parameters: { type: 'object', properties: { city: { type: 'string' } } }, + }, + }, + ], + messages: [{ role: 'user', content: 'What is the weather in Paris?' }], + tool_choice: 'auto', + } as unknown as DispatchBody); + + expect(anthropicBindToolsMock).toHaveBeenCalledWith( + [ + { + type: 'function', + function: { + name: 'get_weather', + description: 'Get weather for a city', + parameters: { type: 'object', properties: { city: { type: 'string' } } }, + }, + }, + ], + { tool_choice: 'auto' }, + ); + }); + + it('should convert tool_choice "required" to "any"', async () => { + const mockResponse = new AIMessage({ content: 'Response' }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await dispatcher.dispatch({ + tools: [{ type: 'function', function: { name: 'tool1' } }], + messages: [], + tool_choice: 'required', + } as unknown as DispatchBody); + + expect(anthropicBindToolsMock).toHaveBeenCalledWith(expect.anything(), { + tool_choice: 'any', + }); + }); + + it('should convert specific function tool_choice to Anthropic format', async () => { + const mockResponse = new AIMessage({ content: 'Response' }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await dispatcher.dispatch({ + tools: [{ type: 'function', function: { name: 'specific_tool' } }], + messages: [], + tool_choice: { type: 'function', function: { name: 'specific_tool' } }, + } as unknown as DispatchBody); + + expect(anthropicBindToolsMock).toHaveBeenCalledWith(expect.anything(), { + tool_choice: { type: 'tool', name: 'specific_tool' }, + }); + }); + }); + + describe('when the anthropic client throws an error', () => { + it('should throw an AnthropicUnprocessableError', async () => { + anthropicInvokeMock.mockRejectedValueOnce(new Error('Anthropic API error')); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await expect( + dispatcher.dispatch({ + tools: [], + messages: [{ role: 'user', content: 'Hello' }], + } as unknown as DispatchBody), + ).rejects.toThrow(AnthropicUnprocessableError); + }); + + it('should include the error message from Anthropic', async () => { + anthropicInvokeMock.mockRejectedValueOnce(new Error('Anthropic API error')); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + new RemoteTools(apiKeys), + ); + + await expect( + dispatcher.dispatch({ + tools: [], + messages: [{ role: 'user', content: 'Hello' }], + } as unknown as DispatchBody), + ).rejects.toThrow('Error while calling Anthropic: Anthropic API error'); + }); + }); + + describe('when there is a remote tool', () => { + it('should enhance the remote tools definition', async () => { + const mockResponse = new AIMessage({ content: 'Response' }); + anthropicInvokeMock.mockResolvedValueOnce(mockResponse); + + const remoteTools = new RemoteTools(apiKeys); + + const dispatcher = new ProviderDispatcher( + { + name: 'claude', + provider: 'anthropic', + apiKey: 'test-api-key', + model: 'claude-3-5-sonnet-latest', + }, + remoteTools, + ); + + await dispatcher.dispatch({ + tools: [ + { + type: 'function', + function: { name: remoteTools.tools[0].base.name, parameters: {} }, + }, + ], + messages: [], + } as unknown as DispatchBody); + + const expectedEnhancedFunction = convertToOpenAIFunction(remoteTools.tools[0].base); + expect(anthropicBindToolsMock).toHaveBeenCalledWith( + [ + { + type: 'function', + function: { + name: expectedEnhancedFunction.name, + description: expectedEnhancedFunction.description, + parameters: expectedEnhancedFunction.parameters, + }, + }, + ], + expect.anything(), + ); + }); + }); + }); }); diff --git a/packages/ai-proxy/test/router.test.ts b/packages/ai-proxy/test/router.test.ts index 82abb1dbf1..ce2b33d6b1 100644 --- a/packages/ai-proxy/test/router.test.ts +++ b/packages/ai-proxy/test/router.test.ts @@ -431,5 +431,59 @@ describe('route', () => { }), ).toThrow("Model 'gpt-4' does not support tools"); }); + + describe('should accept supported models', () => { + const supportedModels = [ + 'gpt-4o', + 'gpt-4o-mini', + 'gpt-4o-2024-08-06', + 'gpt-4-turbo', + 'gpt-4-turbo-2024-04-09', + 'gpt-4.1', + 'gpt-4.1-mini', + 'gpt-3.5-turbo', + 'gpt-3.5-turbo-0125', + 'gpt-3.5', + 'gpt-5', + 'unknown-model', + 'future-gpt-model', + ]; + + it.each(supportedModels)('%s', model => { + expect( + () => + new Router({ + aiConfigurations: [ + { name: 'test', provider: 'openai', apiKey: 'dev', model }, + ], + }), + ).not.toThrow(); + }); + }); + + describe('should reject known unsupported models', () => { + const unsupportedModels = [ + 'gpt-4', + 'gpt-4-0613', + 'o1', + 'o3-mini', + 'text-davinci-003', + 'davinci', + 'curie', + 'babbage', + 'ada', + ]; + + it.each(unsupportedModels)('%s', model => { + expect( + () => + new Router({ + aiConfigurations: [ + { name: 'test', provider: 'openai', apiKey: 'dev', model }, + ], + }), + ).toThrow(AIModelNotSupportedError); + }); + }); }); }); diff --git a/yarn.lock b/yarn.lock index 434e55cc6e..20ecb7cf91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,12 +8,12 @@ integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== "@actions/core@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@actions/core/-/core-2.0.2.tgz#81c59e1f3437660d2148a064c1ba8e99931f2cf7" - integrity sha512-Ast1V7yHbGAhplAsuVlnb/5J8Mtr/Zl6byPPL+Qjq3lmfIgWF1ak1iYfF/079cRERiuTALTXkSuEUdZeDCfGtA== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@actions/core/-/core-2.0.3.tgz#b05e8cf407ab393e5d10282357a74e1ee2315eee" + integrity sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA== dependencies: "@actions/exec" "^2.0.0" - "@actions/http-client" "^3.0.1" + "@actions/http-client" "^3.0.2" "@actions/exec@^2.0.0": version "2.0.0" @@ -22,13 +22,13 @@ dependencies: "@actions/io" "^2.0.0" -"@actions/http-client@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-3.0.1.tgz#0ac91c3abf179a401e23d40abf0d7caa92324268" - integrity sha512-SbGS8c/vySbNO3kjFgSW77n83C4MQx/Yoe+b1hAdpuvfHxnkHzDq2pWljUpAA56Si1Gae/7zjeZsV0CYjmLo/w== +"@actions/http-client@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-3.0.2.tgz#3db9c83af9d29d51ac8c30b45bc17f7014beb1b2" + integrity sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA== dependencies: tunnel "^0.0.6" - undici "^5.28.5" + undici "^6.23.0" "@actions/io@^2.0.0": version "2.0.0" @@ -43,6 +43,20 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" +"@anthropic-ai/sdk@^0.71.0": + version "0.71.2" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.71.2.tgz#1e3e08a7b2c3129828480a3d0ca4487472fdde3d" + integrity sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ== + dependencies: + json-schema-to-ts "^3.1.1" + +"@anthropic-ai/sdk@^0.72.1": + version "0.72.1" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.72.1.tgz#822c46649a1af64df72ec9fa2b8cd852c9795285" + integrity sha512-MiUnue7qN7DvLIoYHgkedN2z05mRf2CutBzjXXY2krzOhG2r/rIfISS2uVkNLikgToB5hYIzw+xp2jdOtRkqYQ== + dependencies: + json-schema-to-ts "^3.1.1" + "@aws-crypto/crc32@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa" @@ -1046,9 +1060,9 @@ chalk "^2.4.2" "@babel/code-frame@^7.26.2", "@babel/code-frame@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" - integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== dependencies: "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" @@ -1302,11 +1316,11 @@ "@babel/types" "^7.28.4" "@babel/parser@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" - integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== dependencies: - "@babel/types" "^7.28.6" + "@babel/types" "^7.29.0" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -1406,6 +1420,11 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/runtime@^7.18.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b" + integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA== + "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1479,10 +1498,10 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" -"@babel/types@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" - integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== dependencies: "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" @@ -1764,11 +1783,6 @@ ajv-formats "^2.1.1" fast-uri "^2.0.0" -"@fastify/busboy@^2.0.0": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" - integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== - "@fastify/cors@9.0.1": version "9.0.1" resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-9.0.1.tgz#9ddb61b4a61e02749c5c54ca29f1c646794145be" @@ -2070,10 +2084,10 @@ resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== +"@isaacs/brace-expansion@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz#0ef5a92d91f2fff2a37646ce54da9e5f599f6eff" + integrity sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ== dependencies: "@isaacs/balanced-match" "^4.0.1" @@ -2407,6 +2421,14 @@ koa-compose "^4.1.0" path-to-regexp "^6.3.0" +"@langchain/anthropic@1.3.14": + version "1.3.14" + resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-1.3.14.tgz#ca3f91702986f9ab4dbb04c19122ab2b24bb01de" + integrity sha512-mexm4UyThn11cwDGsR7+D56bjmwaoJi+WWjWzCGi59zove6PTe9hxHXaOwiv9Z3PjFKyjldQOqoJT7JhzWKGVA== + dependencies: + "@anthropic-ai/sdk" "^0.71.0" + zod "^3.25.76 || ^4" + "@langchain/classic@1.0.9": version "1.0.9" resolved "https://registry.yarnpkg.com/@langchain/classic/-/classic-1.0.9.tgz#bdb19539db47469370727f32e1bf63c52777426b" @@ -2461,22 +2483,22 @@ dependencies: uuid "^10.0.0" -"@langchain/langgraph-sdk@~1.5.4": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.5.4.tgz#40caf09ebc9a5bcd192610a127e8cad659f1cce6" - integrity sha512-eSYqG875c2qvcPwdvBwQH0niTZxt6roMGc2dAWBqCbWCUiUL0X4ftYHg2OqOelsrNE3SO6faLr/m0LIPc9hDwg== +"@langchain/langgraph-sdk@~1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.5.5.tgz#a84fe0f27e2ed6452a83106c3759d7673789a1f0" + integrity sha512-SyiAs6TVXPWlt/8cI9pj/43nbIvclY3ytKqUFbL5MplCUnItetEyqvH87EncxyVF5D7iJKRZRfSVYBMmOZbjbQ== dependencies: p-queue "^9.0.1" p-retry "^7.1.1" uuid "^13.0.0" "@langchain/langgraph@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.1.0.tgz#ef873a69db4a43c25c90fb745392d880d9d2dcbb" - integrity sha512-3n1GL0ZTtr57ZwbYvbi4Th26fwiGogmpFn8OA8UXEpBM2HcpGwcv1+c8YSBJF4XRjlcCzIlXtY+DyrNsvinc6g== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.1.1.tgz#7acacca2e9b38c701578d3f43d549432bf3d325b" + integrity sha512-FfFiaUwc2P5cy0AyALTA72S9OuutBLy+TRQECQSkwI5H40UNF+h7HM0U28xGTfmQ6MrqzbnnpRfXRTQX4jqsUw== dependencies: "@langchain/langgraph-checkpoint" "^1.0.0" - "@langchain/langgraph-sdk" "~1.5.4" + "@langchain/langgraph-sdk" "~1.5.5" uuid "^10.0.0" "@langchain/mcp-adapters@1.1.1": @@ -2663,9 +2685,9 @@ sparse-bitfield "^3.0.3" "@mongodb-js/saslprep@^1.3.0": - version "1.4.4" - resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz#34a946ff6ae142e8f2259b87f2935f8284ba874d" - integrity sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g== + version "1.4.5" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.4.5.tgz#0f53a6c5a350fbe4bfa12cc80b69e8d358f1bbc0" + integrity sha512-k64Lbyb7ycCSXHSLzxVdb2xsKGPMvYZfCICXvDsI8Z65CeWQzTEKS4YmGbnqw+U9RBvLPTsB6UCmwkgsDTGWIw== dependencies: sparse-bitfield "^3.0.3" @@ -2812,10 +2834,10 @@ treeverse "^3.0.0" walk-up-path "^3.0.1" -"@npmcli/arborist@^9.1.9": - version "9.1.9" - resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-9.1.9.tgz#1458850184fa97967263c67c6f34a052ac632b46" - integrity sha512-O/rLeBo64mkUn1zU+1tFDWXvbAA9UXe9eUldwTwRLxOLFx9obqjNoozW65LmYqgWb0DG40i9lNZSv78VX2GKhw== +"@npmcli/arborist@^9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-9.2.0.tgz#0ac8d0ed2dea1f1a3c459c4a203b19ccf9334a31" + integrity sha512-FRvpUoL5RUaMZ+CNJAZAegHymbamcGptjktD72T8Td94OllbJcBlOZQ69cB/DiNTYDvGBU0uvmcBWivlip1h+Q== dependencies: "@isaacs/string-locale-compare" "^1.1.0" "@npmcli/fs" "^5.0.0" @@ -2830,7 +2852,7 @@ "@npmcli/run-script" "^10.0.0" bin-links "^6.0.0" cacache "^20.0.1" - common-ancestor-path "^1.0.1" + common-ancestor-path "^2.0.0" hosted-git-info "^9.0.0" json-stringify-nice "^1.1.4" lru-cache "^11.2.1" @@ -2851,10 +2873,10 @@ treeverse "^3.0.0" walk-up-path "^4.0.0" -"@npmcli/config@^10.4.5": - version "10.4.5" - resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-10.4.5.tgz#6b5bfe6326d8ffe0c53998ea59b3b338a972a058" - integrity sha512-i3d+ysO0ix+2YGXLxKu44cEe9z47dtUPKbiPLFklDZvp/rJAsLmeWG2Bf6YKuqR8jEhMl/pHw1pGOquJBxvKIA== +"@npmcli/config@^10.6.0": + version "10.6.0" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-10.6.0.tgz#6f6098d95480f18227845c375b85e2996433a1de" + integrity sha512-bYtwUdRBFNHlifqOJNHSS1n5MnlLmP85RGD9Cp20KiqaJEX8Xa0vAbvKpPGnaqKKq+1ZRWBNgbBqR3xV92KeQg== dependencies: "@npmcli/map-workspaces" "^5.0.0" "@npmcli/package-json" "^7.0.0" @@ -3591,9 +3613,9 @@ p-reduce "^2.0.0" "@semantic-release/github@^12.0.0": - version "12.0.2" - resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-12.0.2.tgz#bc1f76e9cd386c5b01a20c3f0606e8eec6b1b93a" - integrity sha512-qyqLS+aSGH1SfXIooBKjs7mvrv0deg8v+jemegfJg1kq6ji+GJV8CO08VJDEsvjp3O8XJmTTIAjjZbMzagzsdw== + version "12.0.3" + resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-12.0.3.tgz#855e6998f0f95316fb0ec4a516a96845fef9678b" + integrity sha512-pod3AVGVVVk2rUczMBL4+gfY7hP7A9YYOwjpxVFSusF+pDbFOYBzFRQcHjv1H3IntQyB/Noxzx8LUZ/iwAQQeQ== dependencies: "@octokit/core" "^7.0.0" "@octokit/plugin-paginate-rest" "^14.0.0" @@ -3785,7 +3807,7 @@ "@sigstore/protobuf-specs" "^0.3.2" tuf-js "^2.2.1" -"@sigstore/tuf@^4.0.0", "@sigstore/tuf@^4.0.1": +"@sigstore/tuf@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@sigstore/tuf/-/tuf-4.0.1.tgz#9b080390936d79ea3b6a893b64baf3123e92d6d3" integrity sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw== @@ -5484,9 +5506,9 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: type-fest "^0.21.3" ansi-escapes@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.2.0.tgz#31b25afa3edd3efc09d98c2fee831d460ff06b49" - integrity sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw== + version "7.3.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz#5395bb74b2150a4a1d6e3c2565f4aeca78d28627" + integrity sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg== dependencies: environment "^1.0.0" @@ -6620,12 +6642,12 @@ ci-info@^4.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.2.0.tgz#cbd21386152ebfe1d56f280a3b5feccbd96764c7" integrity sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg== -ci-info@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa" - integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA== +ci-info@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.4.0.tgz#7d54eff9f54b45b62401c26032696eb59c8bd18c" + integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== -cidr-regex@5.0.1: +cidr-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-5.0.1.tgz#4b3972457b06445832929f6f268b477fe0372c1f" integrity sha512-2Apfc6qH9uwF3QHmlYBA8ExB9VHq+1/Doj9sEMY55TVBcpQ3y/+gmMpcNIBBtfb5k54Vphmta+1IxjMqPlWWAA== @@ -6929,6 +6951,11 @@ common-ancestor-path@^1.0.1: resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== +common-ancestor-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz#f1d361aea9236aad5b92a0ff5b9df1422dd360ff" + integrity sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng== + common-tags@^1.4.0: version "1.8.2" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" @@ -9550,11 +9577,11 @@ glob@^10.3.10: path-scurry "^1.11.1" glob@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.0.tgz#9d9233a4a274fc28ef7adce5508b7ef6237a1be3" - integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== + version "13.0.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.1.tgz#c59a2500c9a5f1ab9cdd370217ced63c2aa81e60" + integrity sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w== dependencies: - minimatch "^10.1.1" + minimatch "^10.1.2" minipass "^7.1.2" path-scurry "^2.0.0" @@ -10456,11 +10483,11 @@ is-ci@3.0.1: ci-info "^3.2.0" is-cidr@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-6.0.1.tgz#125e9dead938b6fa996aa500662a5e9f88f338f4" - integrity sha512-JIJlvXodfsoWFAvvjB7Elqu8qQcys2SZjkIJCLdk4XherUqZ6+zH7WIpXkp4B3ZxMH0Fz7zIsZwyvs6JfM0csw== + version "6.0.2" + resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-6.0.2.tgz#890cd6ae7a165323df97cc6660dc4908d7a6e7f4" + integrity sha512-a4SY0KixgRXn67zSCooPNO4YRHZd1z6BIxrEBQUVNPteeajzrJLhZKnn81GVQO51uiCljTtGY+J3o6O3/DZpzg== dependencies: - cidr-regex "5.0.1" + cidr-regex "^5.0.1" is-core-module@^2.13.0, is-core-module@^2.5.0: version "2.13.1" @@ -11495,6 +11522,14 @@ json-schema-ref-resolver@^1.0.1: dependencies: fast-deep-equal "^3.1.3" +json-schema-to-ts@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz#81f3acaf5a34736492f6f5f51870ef9ece1ca853" + integrity sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g== + dependencies: + "@babel/runtime" "^7.18.3" + ts-algebra "^2.0.0" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -11800,9 +11835,9 @@ koa@^3.0.1: vary "^1.1.2" "langsmith@>=0.4.0 <1.0.0": - version "0.4.7" - resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.4.7.tgz#054232706d6b55518b20cff654fc3f91acb07e5f" - integrity sha512-Esv5g/J8wwRwbGQr10PB9+bLsNk0mWbrXc7nnEreQDhh0azbU57I7epSnT7GC4sS4EOWavhbxk+6p8PTXtreHw== + version "0.4.8" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.4.8.tgz#846626dd2e8eba5a1ef0cf17f0c1e38de2fdd13e" + integrity sha512-zyhQ4zp/TJqITfoQvtk8ehUmdIkxj24dTA76nEnMw63lb04/JKZgs29r/epH1pmEwbt0nUlQWKlE8n2g6BabUA== dependencies: "@types/uuid" "^10.0.0" chalk "^4.1.2" @@ -11926,12 +11961,12 @@ libnpmaccess@^10.0.3: npm-package-arg "^13.0.0" npm-registry-fetch "^19.0.0" -libnpmdiff@^8.0.12: - version "8.0.12" - resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-8.0.12.tgz#c55c80e0cb196588174989f36c285750fe7de048" - integrity sha512-M33yWsbxCUv4fwquYNxdRl//mX8CcmY+pHhZZ+f8ihKh+yfcQw2jROv0sJQ3eX5FzRVJKdCdH7nM0cNlHy83DQ== +libnpmdiff@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-8.1.0.tgz#d8b4dea24d2c9b40f9b42a652c0a980abd35df8c" + integrity sha512-7OjwzhB75HXAJDBPVUNSlf7UBsEdUCC3HeiQlpVoibiImgrMw/tzcaL9esOWCwDbn3OeHXny2mZLgznZyUIacQ== dependencies: - "@npmcli/arborist" "^9.1.9" + "@npmcli/arborist" "^9.2.0" "@npmcli/installed-package-contents" "^4.0.0" binary-extensions "^3.0.0" diff "^8.0.2" @@ -11940,12 +11975,12 @@ libnpmdiff@^8.0.12: pacote "^21.0.2" tar "^7.5.1" -libnpmexec@^10.1.11: - version "10.1.11" - resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-10.1.11.tgz#6ccc19f2d81c0eeb4f72f2fe09e8fc1637f5ec7f" - integrity sha512-228ZmYSfElpfywVFO3FMieLkFUDNknExXLLJoFcKJbyrucHc8KgDW4i9F4uJGNrbPvDqDtm7hcSEvrneN0Anqg== +libnpmexec@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-10.2.0.tgz#94b0fed1d3c4bc24c72ed0587f34f68f1505f360" + integrity sha512-9niBq6RIPpRcIMmT2tL8PnYnyv0GMmVRzYXVnv5qUZ4/z+JMYNSgWZMIetF8H2HAH7qsoAgkYN/CGvc90yUnhw== dependencies: - "@npmcli/arborist" "^9.1.9" + "@npmcli/arborist" "^9.2.0" "@npmcli/package-json" "^7.0.0" "@npmcli/run-script" "^10.0.0" ci-info "^4.0.0" @@ -11958,12 +11993,12 @@ libnpmexec@^10.1.11: signal-exit "^4.1.0" walk-up-path "^4.0.0" -libnpmfund@^7.0.12: - version "7.0.12" - resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-7.0.12.tgz#0a8afd552c0e9d56b8e5904599406d62f2a640be" - integrity sha512-Jg4zvboAkI35JFoywEleJa9eU0ZIkMOZH3gt16VoexaYV3yVTjjIr4ZVnPx+MfsLo28y6DHQ8RgN4PFuKt1bhg== +libnpmfund@^7.0.14: + version "7.0.14" + resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-7.0.14.tgz#43f3f5cec46edd882c5e559f15dcef2a49fb01ee" + integrity sha512-ZQ11zzPzOQRyOnbxzmLQkNUifOgZbizdNcy7paUek8xnQZ1l7MQTeh7s3JJNwm8TA+VGfUOgnULPKXbvYp1IHQ== dependencies: - "@npmcli/arborist" "^9.1.9" + "@npmcli/arborist" "^9.2.0" libnpmorg@^8.0.1: version "8.0.1" @@ -11973,12 +12008,12 @@ libnpmorg@^8.0.1: aproba "^2.0.0" npm-registry-fetch "^19.0.0" -libnpmpack@^9.0.12: - version "9.0.12" - resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-9.0.12.tgz#1514e3caa44f47896089bfa7f474beb8a10de21a" - integrity sha512-32j+CIrJhVngbqGUbhnpNFnPi6rkx6NP1lRO1OHf4aoZ57ad+mTkS788FfeAoXoiJDmfmAqgZejXRmEfy7s6Sg== +libnpmpack@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-9.1.0.tgz#8dc736438faec4efb1827e635dbad470d0186371" + integrity sha512-gRjsz+nTuAplimIdmA8awClqiDmyS4aIie+WNeP409cnFcXbhYoIQhOfN7BojPX9JQr/nX4y4yTh9g8VmVtzAg== dependencies: - "@npmcli/arborist" "^9.1.9" + "@npmcli/arborist" "^9.2.0" "@npmcli/run-script" "^10.0.0" npm-package-arg "^13.0.0" pacote "^21.0.2" @@ -12357,9 +12392,9 @@ lru-cache@^10.2.0, lru-cache@^10.2.2: integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^11.0.0, lru-cache@^11.1.0, lru-cache@^11.2.1: - version "11.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.4.tgz#ecb523ebb0e6f4d837c807ad1abaea8e0619770d" - integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg== + version "11.2.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.5.tgz#6811ae01652ae5d749948cdd80bcc22218c6744f" + integrity sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw== lru-cache@^5.1.1: version "5.1.1" @@ -12919,12 +12954,12 @@ minimatch@9.0.3, minimatch@^9.0.0, minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^10.0.3, minimatch@^10.1.1: - version "10.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== +minimatch@^10.0.3, minimatch@^10.1.1, minimatch@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.2.tgz#6c3f289f9de66d628fa3feb1842804396a43d81c" + integrity sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw== dependencies: - "@isaacs/brace-expansion" "^5.0.0" + "@isaacs/brace-expansion" "^5.0.1" minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" @@ -13005,12 +13040,12 @@ minipass-fetch@^3.0.0: encoding "^0.1.13" minipass-fetch@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-5.0.0.tgz#644ed3fa172d43b3163bb32f736540fc138c4afb" - integrity sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A== + version "5.0.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-5.0.1.tgz#c8d62dc0393f9d4bc156421f7fb10321cd54a697" + integrity sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw== dependencies: minipass "^7.0.3" - minipass-sized "^1.0.3" + minipass-sized "^2.0.0" minizlib "^3.0.1" optionalDependencies: encoding "^0.1.13" @@ -13036,6 +13071,13 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" +minipass-sized@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-2.0.0.tgz#2228ee97e3f74f6b22ba6d1319addb7621534306" + integrity sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA== + dependencies: + minipass "^7.1.2" + minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" @@ -13455,10 +13497,10 @@ node-gyp@^10.0.0: tar "^6.2.1" which "^4.0.0" -node-gyp@^12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-12.1.0.tgz#302fc2d3fec36975cfb8bfee7a6bf6b7f0be9553" - integrity sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g== +node-gyp@^12.1.0, node-gyp@^12.2.0: + version "12.2.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-12.2.0.tgz#ff73f6f509e33d8b7e768f889ffc9738ad117b07" + integrity sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ== dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" @@ -13467,7 +13509,7 @@ node-gyp@^12.1.0: nopt "^9.0.0" proc-log "^6.0.0" semver "^7.3.5" - tar "^7.5.2" + tar "^7.5.4" tinyglobby "^0.2.12" which "^6.0.0" @@ -13790,13 +13832,13 @@ npm-user-validate@^4.0.0: integrity sha512-TP+Ziq/qPi/JRdhaEhnaiMkqfMGjhDLoh/oRfW+t5aCuIfJxIUxvwk6Sg/6ZJ069N/Be6gs00r+aZeJTfS9uHQ== npm@^11.6.2: - version "11.7.0" - resolved "https://registry.yarnpkg.com/npm/-/npm-11.7.0.tgz#897fa4af764b64fa384b50e071636e7497d4f6de" - integrity sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw== + version "11.9.0" + resolved "https://registry.yarnpkg.com/npm/-/npm-11.9.0.tgz#8cc4bc499c7ab52f1113985acc725572f99885b3" + integrity sha512-BBZoU926FCypj4b7V7ElinxsWcy4Kss88UG3ejFYmKyq7Uc5XnT34Me2nEhgCOaL5qY4HvGu5aI92C4OYd7NaA== dependencies: "@isaacs/string-locale-compare" "^1.1.0" - "@npmcli/arborist" "^9.1.9" - "@npmcli/config" "^10.4.5" + "@npmcli/arborist" "^9.2.0" + "@npmcli/config" "^10.6.0" "@npmcli/fs" "^5.0.0" "@npmcli/map-workspaces" "^5.0.3" "@npmcli/metavuln-calculator" "^9.0.3" @@ -13804,12 +13846,12 @@ npm@^11.6.2: "@npmcli/promise-spawn" "^9.0.1" "@npmcli/redact" "^4.0.0" "@npmcli/run-script" "^10.0.3" - "@sigstore/tuf" "^4.0.0" + "@sigstore/tuf" "^4.0.1" abbrev "^4.0.0" archy "~1.0.0" cacache "^20.0.3" chalk "^5.6.2" - ci-info "^4.3.1" + ci-info "^4.4.0" cli-columns "^4.0.0" fastest-levenshtein "^1.0.16" fs-minipass "^3.0.3" @@ -13821,11 +13863,11 @@ npm@^11.6.2: is-cidr "^6.0.1" json-parse-even-better-errors "^5.0.0" libnpmaccess "^10.0.3" - libnpmdiff "^8.0.12" - libnpmexec "^10.1.11" - libnpmfund "^7.0.12" + libnpmdiff "^8.1.0" + libnpmexec "^10.2.0" + libnpmfund "^7.0.14" libnpmorg "^8.0.1" - libnpmpack "^9.0.12" + libnpmpack "^9.1.0" libnpmpublish "^11.1.3" libnpmsearch "^9.0.1" libnpmteam "^8.0.2" @@ -13835,7 +13877,7 @@ npm@^11.6.2: minipass "^7.1.1" minipass-pipeline "^1.2.4" ms "^2.1.2" - node-gyp "^12.1.0" + node-gyp "^12.2.0" nopt "^9.0.0" npm-audit-report "^7.0.0" npm-install-checks "^8.0.0" @@ -13845,7 +13887,7 @@ npm@^11.6.2: npm-registry-fetch "^19.1.1" npm-user-validate "^4.0.0" p-map "^7.0.4" - pacote "^21.0.4" + pacote "^21.1.0" parse-conflict-json "^5.0.1" proc-log "^6.1.0" qrcode-terminal "^0.12.0" @@ -13854,11 +13896,11 @@ npm@^11.6.2: spdx-expression-parse "^4.0.0" ssri "^13.0.0" supports-color "^10.2.2" - tar "^7.5.2" + tar "^7.5.7" text-table "~0.2.0" tiny-relative-date "^2.0.2" treeverse "^3.0.0" - validate-npm-package-name "^7.0.0" + validate-npm-package-name "^7.0.2" which "^6.0.0" npmlog@^5.0.1: @@ -14465,10 +14507,10 @@ pacote@^18.0.0, pacote@^18.0.6: ssri "^10.0.0" tar "^6.1.11" -pacote@^21.0.0, pacote@^21.0.2, pacote@^21.0.4: - version "21.0.4" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-21.0.4.tgz#59cd2a2b5a4c8c1b625f33991a96b136d1c05d95" - integrity sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA== +pacote@^21.0.0, pacote@^21.0.2, pacote@^21.1.0: + version "21.1.0" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-21.1.0.tgz#4f319aae0192dc96f707ac6b33c22ecf523b6253" + integrity sha512-WF/PwrImIIVaLmtuCeO5L7n6DA0ZGCqmDPO/XbNjZgNUX+2O5z4f4Wdmu6erBWNICkl3ftKJvit2eIVcpegRRw== dependencies: "@npmcli/git" "^7.0.0" "@npmcli/installed-package-contents" "^4.0.0" @@ -15893,9 +15935,9 @@ semantic-release-slack-bot@^4.0.2: slackify-markdown "^4.3.0" semantic-release@^21.0.5, semantic-release@^25.0.0: - version "25.0.2" - resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-25.0.2.tgz#efd4fa16ce3518a747e737baf3f69fd82979d98e" - integrity sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g== + version "25.0.3" + resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-25.0.3.tgz#77c2a7bfdcc63125fa2dea062d2cee28662ce224" + integrity sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA== dependencies: "@semantic-release/commit-analyzer" "^13.0.1" "@semantic-release/error" "^4.0.0" @@ -15923,17 +15965,9 @@ semantic-release@^21.0.5, semantic-release@^25.0.0: read-package-up "^12.0.0" resolve-from "^5.0.0" semver "^7.3.2" - semver-diff "^5.0.0" signale "^1.2.1" yargs "^18.0.0" -semver-diff@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-5.0.0.tgz#62a8396f44c11386c83d1e57caedc806c6c7755c" - integrity sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg== - dependencies: - semver "^7.3.5" - semver-regex@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-4.0.5.tgz#fbfa36c7ba70461311f5debcb3928821eb4f9180" @@ -17025,7 +17059,7 @@ tar-stream@^2.1.4, tar-stream@~2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@6.2.1, tar@^6.0.2, tar@^6.1.11, tar@^6.1.2, tar@^6.2.1, tar@^7.4.3, tar@^7.5.1, tar@^7.5.2: +tar@6.2.1, tar@^6.0.2, tar@^6.1.11, tar@^6.1.2, tar@^6.2.1, tar@^7.4.3, tar@^7.5.1, tar@^7.5.4, tar@^7.5.7: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== @@ -17301,6 +17335,11 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +ts-algebra@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-2.0.0.tgz#4e3e0953878f26518fce7f6bb115064a65388b7a" + integrity sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw== + ts-invariant@^0.4.0: version "0.4.4" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86" @@ -17506,9 +17545,9 @@ type-fest@^4.39.1, type-fest@^4.6.0: integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== type-fest@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.4.1.tgz#aa9eaadcdc0acb0b5bd52e54f966ee3e38e125d2" - integrity sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ== + version "5.4.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-5.4.3.tgz#b4c7e028da129098911ee2162a0c30df8a1be904" + integrity sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA== dependencies: tagged-tag "^1.0.0" @@ -17765,17 +17804,15 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== -undici@^5.28.5: - version "5.29.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3" - integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg== - dependencies: - "@fastify/busboy" "^2.0.0" +undici@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.23.0.tgz#7953087744d9095a96f115de3140ca3828aff3a4" + integrity sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g== undici@^7.0.0: - version "7.18.2" - resolved "https://registry.yarnpkg.com/undici/-/undici-7.18.2.tgz#6cf724ef799a67d94fd55adf66b1e184176efcdf" - integrity sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw== + version "7.20.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.20.0.tgz#62af069a2eae7cfccbe850ff11f44e04be7768e7" + integrity sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ== unicode-emoji-modifier-base@^1.0.0: version "1.0.0" @@ -18023,7 +18060,7 @@ validate-npm-package-name@^5.0.0: dependencies: builtins "^5.0.0" -validate-npm-package-name@^7.0.0: +validate-npm-package-name@^7.0.0, validate-npm-package-name@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz#e57c3d721a4c8bbff454a246e7f7da811559ea0d" integrity sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A== @@ -18519,9 +18556,9 @@ zod@^4.2.1: integrity sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw== zod@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" - integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g== + version "4.3.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a" + integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== zwitch@^1.0.0: version "1.0.5"