diff --git a/packages/anthropic/src/advanced-tool-use.ts b/packages/anthropic/src/advanced-tool-use.ts new file mode 100644 index 000000000000..0fe14cb524c0 --- /dev/null +++ b/packages/anthropic/src/advanced-tool-use.ts @@ -0,0 +1,91 @@ +import { SharedV3ProviderMetadata, SharedV3Warning } from '@ai-sdk/provider'; +import { validateTypes } from '@ai-sdk/provider-utils'; +import { + AnthropicAdvancedToolUse, + anthropicInputExamplesSchema, + anthropicProgrammaticToolCallingSchema, + AnthropicTool, + anthropicToolSearchSchema, +} from './anthropic-messages-api'; + +/** + * Extracts and validates Anthropic advanced tool use configuration from provider metadata. + * + * This function processes provider metadata to extract tool use configuration options including + * deferred loading, allowed callers, and input examples. It supports both camelCase and snake_case + * property naming conventions for backwards compatibility. + * + * @param providerMetadata - Optional shared provider metadata containing Anthropic-specific configuration + * @returns A promise that resolves to an object containing validated advanced tool use settings: + * - `deferLoading`: Validated tool search/defer loading configuration + * - `allowedCallers`: Validated programmatic tool calling configuration + * - `inputExamples`: Validated input examples configuration + * @throws Will throw an error if any of the extracted configurations fail validation + */ + +export async function getAnthropicAdvancedToolUseFeaturesSupport( + providerMetadata: SharedV3ProviderMetadata | undefined, +): Promise { + const anthropic = providerMetadata?.anthropic; + const deferLoading = anthropic?.defer_loading ?? anthropic?.deferLoading; + const inputExamples = anthropic?.input_examples ?? anthropic?.inputExamples; + const allowed_callers = + anthropic?.allowed_callers ?? anthropic?.allowedCallers; + + const [parseDeferLoading, parseAllowedCallers, parseInputExamples] = + await Promise.all([ + validateTypes({ + value: deferLoading, + schema: anthropicToolSearchSchema, + }), + validateTypes({ + value: allowed_callers, + schema: anthropicProgrammaticToolCallingSchema, + }), + validateTypes({ + value: inputExamples, + schema: anthropicInputExamplesSchema, + }), + ]); + + let result: AnthropicAdvancedToolUse = {}; + if (parseDeferLoading !== undefined) { + result.defer_loading = parseDeferLoading; + } + + if (parseAllowedCallers !== undefined) { + result.allowed_callers = parseAllowedCallers; + } + + if (parseInputExamples !== undefined) { + result.input_examples = parseInputExamples; + } + + return result; +} + +export const handleAnthropicAdvancedToolUseFeaturesWarnings = ( + anthropicTools: AnthropicTool[], +) => { + const toolWarnings: SharedV3Warning[] = []; + + // Check if any tool uses defer_loading + const anyToolUsesDeferLoading = anthropicTools.some( + t => 'defer_loading' in t && t.defer_loading === true, + ); + + const searchTool = anthropicTools.find( + t => + t.name === 'tool_search_tool_bm25' || t.name === 'tool_search_tool_regex', + ); + + if (anyToolUsesDeferLoading && !searchTool) { + toolWarnings.push({ + type: 'unsupported', + feature: `tool`, + details: `At least one tool has defer_loading set to true, but no tool search tool (tool_search_tool_bm25 or tool_search_tool_regex) is provided. A tool search tool is required when using deferred loading.`, + }); + } + + return toolWarnings; +}; diff --git a/packages/anthropic/src/anthropic-messages-api.ts b/packages/anthropic/src/anthropic-messages-api.ts index 0c6d90947824..1fec83980559 100644 --- a/packages/anthropic/src/anthropic-messages-api.ts +++ b/packages/anthropic/src/anthropic-messages-api.ts @@ -273,13 +273,12 @@ export interface AnthropicMcpToolResultContent { } export type AnthropicTool = - | { + | ({ name: string; description: string | undefined; input_schema: JSONSchema7; cache_control: AnthropicCacheControl | undefined; - strict?: boolean; - } + } & AnthropicAdvancedToolUse) | { type: 'code_execution_20250522'; name: string; @@ -344,6 +343,14 @@ export type AnthropicTool = timezone?: string; }; cache_control: AnthropicCacheControl | undefined; + } + | { + type: 'tool_search_tool_regex_20251119'; + name: string; + } + | { + type: 'tool_search_tool_bm25_20251119'; + name: string; }; export type AnthropicToolChoice = @@ -886,3 +893,21 @@ export type Citation = NonNullable< type: 'text'; })['citations'] >[number]; + +export const anthropicToolSearchSchema = lazySchema(() => + zodSchema(z.boolean().optional()), +); + +export const anthropicProgrammaticToolCallingSchema = lazySchema(() => + zodSchema(z.array(z.enum(['code_execution_20250825', 'direct'])).optional()), +); + +export const anthropicInputExamplesSchema = lazySchema(() => + zodSchema(z.array(z.record(z.string(), z.unknown())).optional()), +); + +export type AnthropicAdvancedToolUse = { + defer_loading?: boolean; + allowed_callers?: Array<'code_execution_20250825' | 'direct'>; + input_examples?: Array>; +}; diff --git a/packages/anthropic/src/anthropic-prepare-tools.test.ts b/packages/anthropic/src/anthropic-prepare-tools.test.ts index 11dca9df3aea..345fb1069211 100644 --- a/packages/anthropic/src/anthropic-prepare-tools.test.ts +++ b/packages/anthropic/src/anthropic-prepare-tools.test.ts @@ -627,4 +627,46 @@ describe('prepareTools', () => { ] `); }); + + describe('anthropic advanced tools', () => { + it('should provide search tool when deferLoading is true', async () => { + const result = await prepareTools({ + tools: [ + { + type: 'function', + name: 'testFunction', + description: 'A test function', + inputSchema: {}, + providerOptions: { + anthropic: { deferLoading: true }, + }, + }, + ], + toolChoice: undefined, + }); + + expect(result).toMatchInlineSnapshot(` + { + "betas": Set {}, + "toolChoice": undefined, + "toolWarnings": [ + { + "details": "At least one tool has defer_loading set to true, but no tool search tool (tool_search_tool_bm25 or tool_search_tool_regex) is provided. A tool search tool is required when using deferred loading.", + "feature": "tool", + "type": "unsupported", + }, + ], + "tools": [ + { + "cache_control": undefined, + "defer_loading": true, + "description": "A test function", + "input_schema": {}, + "name": "testFunction", + }, + ], + } + `); + }); + }); }); diff --git a/packages/anthropic/src/anthropic-prepare-tools.ts b/packages/anthropic/src/anthropic-prepare-tools.ts index d4b9a6ae8e55..ddf852bef4b5 100644 --- a/packages/anthropic/src/anthropic-prepare-tools.ts +++ b/packages/anthropic/src/anthropic-prepare-tools.ts @@ -9,6 +9,10 @@ import { textEditor_20250728ArgsSchema } from './tool/text-editor_20250728'; import { webSearch_20250305ArgsSchema } from './tool/web-search_20250305'; import { webFetch_20250910ArgsSchema } from './tool/web-fetch-20250910'; import { validateTypes } from '@ai-sdk/provider-utils'; +import { + getAnthropicAdvancedToolUseFeaturesSupport, + handleAnthropicAdvancedToolUseFeaturesWarnings, +} from './advanced-tool-use'; export async function prepareTools({ tools, @@ -53,14 +57,17 @@ export async function prepareTools({ canCache: true, }); + const advancedToolFeatures = + await getAnthropicAdvancedToolUseFeaturesSupport( + tool.providerOptions, + ); + anthropicTools.push({ name: tool.name, description: tool.description, input_schema: tool.inputSchema, cache_control: cacheControl, - ...(supportsStructuredOutput === true && tool.strict != null - ? { strict: tool.strict } - : {}), + ...advancedToolFeatures, }); break; } @@ -212,6 +219,22 @@ export async function prepareTools({ break; } + case 'anthropic.tool_search_tool_regex_20251119': { + anthropicTools.push({ + type: 'tool_search_tool_regex_20251119', + name: 'tool_search_tool_regex', + }); + break; + } + + case 'anthropic.tool_search_tool_bm25_20251119': { + anthropicTools.push({ + type: 'tool_search_tool_bm25_20251119', + name: 'tool_search_tool_bm25', + }); + break; + } + default: { toolWarnings.push({ type: 'unsupported', @@ -233,6 +256,13 @@ export async function prepareTools({ } } + const advancedToolFeaturesWarnings = + handleAnthropicAdvancedToolUseFeaturesWarnings(anthropicTools); + + if (advancedToolFeaturesWarnings.length > 0) { + toolWarnings.push(...advancedToolFeaturesWarnings); + } + if (toolChoice == null) { return { tools: anthropicTools, diff --git a/packages/anthropic/src/anthropic-tools.ts b/packages/anthropic/src/anthropic-tools.ts index 5d4b8ea24095..4d3cda92f646 100644 --- a/packages/anthropic/src/anthropic-tools.ts +++ b/packages/anthropic/src/anthropic-tools.ts @@ -9,6 +9,8 @@ import { textEditor_20241022 } from './tool/text-editor_20241022'; import { textEditor_20250124 } from './tool/text-editor_20250124'; import { textEditor_20250429 } from './tool/text-editor_20250429'; import { textEditor_20250728 } from './tool/text-editor_20250728'; +import { toolSearchToolBm25_20251119 } from './tool/tool_search_tool_bm25_20251119'; +import { toolSearchToolRegex_20251119 } from './tool/tool_search_tool_regex_20251119'; import { webFetch_20250910 } from './tool/web-fetch-20250910'; import { webSearch_20250305 } from './tool/web-search_20250305'; @@ -173,4 +175,16 @@ export const anthropicTools = { * @param userLocation - Optional user location information to provide geographically relevant search results. */ webSearch_20250305, + /** + * Claude constructs regex patterns to search for tools + * + * Tool name must be `tool_search_tool_regex`. + */ + toolSearchToolRegex_20251119, + /** + * Claude uses natural language queries to search for tools + * + * Tool name must be `tool_search_tool_bm25`. + */ + toolSearchToolBm25_20251119, }; diff --git a/packages/anthropic/src/tool/tool_search_tool_bm25_20251119.ts b/packages/anthropic/src/tool/tool_search_tool_bm25_20251119.ts new file mode 100644 index 000000000000..2755c0440cec --- /dev/null +++ b/packages/anthropic/src/tool/tool_search_tool_bm25_20251119.ts @@ -0,0 +1,15 @@ +import { + createProviderToolFactory, + lazySchema, + zodSchema, +} from '@ai-sdk/provider-utils'; +import { z } from 'zod/v4'; + +const toolSearchToolBm25_20251119ArgsSchema = lazySchema(() => + zodSchema(z.object({})), +); + +export const toolSearchToolBm25_20251119 = createProviderToolFactory<{}, {}>({ + id: 'anthropic.tool_search_tool_bm25_20251119', + inputSchema: toolSearchToolBm25_20251119ArgsSchema, +}); diff --git a/packages/anthropic/src/tool/tool_search_tool_regex_20251119.ts b/packages/anthropic/src/tool/tool_search_tool_regex_20251119.ts new file mode 100644 index 000000000000..58a7f33690b1 --- /dev/null +++ b/packages/anthropic/src/tool/tool_search_tool_regex_20251119.ts @@ -0,0 +1,15 @@ +import { + createProviderToolFactory, + lazySchema, + zodSchema, +} from '@ai-sdk/provider-utils'; +import { z } from 'zod/v4'; + +const toolSearchToolRegex_20251119ArgsSchema = lazySchema(() => + zodSchema(z.object({})), +); + +export const toolSearchToolRegex_20251119 = createProviderToolFactory<{}, {}>({ + id: 'anthropic.tool_search_tool_regex_20251119', + inputSchema: toolSearchToolRegex_20251119ArgsSchema, +});