diff --git a/src/proxy/anthropic.ts b/src/proxy/anthropic.ts index 8d8965e..ba1874a 100644 --- a/src/proxy/anthropic.ts +++ b/src/proxy/anthropic.ts @@ -178,6 +178,62 @@ function setAnthropicBetaHeader(req: Request) { } } +/** + * Adds web search tool for Claude-3.5 and Claude-3.7 models when enable_web_search is true + * + * Supports all optional parameters documented in the Claude API: + * - max_uses: Limit the number of searches per request + * - allowed_domains: Only include results from these domains + * - blocked_domains: Never include results from these domains + * - user_location: Localize search results + */ +function addWebSearchTool(req: Request) { + // Check if this is a Claude model that supports web search and if web search is enabled + const supportsWebSearch = /^claude-3-(5|7)/.test(req.body.model); + const useWebSearch = supportsWebSearch && Boolean(req.body.enable_web_search); + + if (useWebSearch) { + // Create the base web search tool + const webSearchTool: any = { + 'type': 'web_search_20250305', + 'name': 'web_search', + }; + + // Add optional parameters if provided by the client + + // max_uses: Limit the number of searches per request + if (typeof req.body.web_search_max_uses === 'number') { + webSearchTool.max_uses = req.body.web_search_max_uses; + delete req.body.web_search_max_uses; + } + + // allowed_domains: Only include results from these domains + if (Array.isArray(req.body.web_search_allowed_domains)) { + webSearchTool.allowed_domains = req.body.web_search_allowed_domains; + delete req.body.web_search_allowed_domains; + } + + // blocked_domains: Never include results from these domains + if (Array.isArray(req.body.web_search_blocked_domains)) { + webSearchTool.blocked_domains = req.body.web_search_blocked_domains; + delete req.body.web_search_blocked_domains; + } + + // user_location: Localize search results + if (req.body.web_search_user_location) { + webSearchTool.user_location = req.body.web_search_user_location; + delete req.body.web_search_user_location; + } + + // Add the web search tool to the tools array + req.body.tools = [...(req.body.tools || []), webSearchTool]; + } + + // Delete custom parameters as they're not standard Claude API parameters + delete req.body.enable_web_search; + delete req.body.reasoning_effort; +} + function selectUpstreamPath(manager: ProxyReqManager) { const req = manager.request; const pathname = req.url.split("?")[0]; @@ -206,20 +262,26 @@ const anthropicProxy = createQueuedProxyMiddleware({ const nativeAnthropicChatPreprocessor = createPreprocessorMiddleware( { inApi: "anthropic-chat", outApi: "anthropic-chat", service: "anthropic" }, - { afterTransform: [setAnthropicBetaHeader] } + { afterTransform: [setAnthropicBetaHeader, addWebSearchTool] } ); -const nativeTextPreprocessor = createPreprocessorMiddleware({ - inApi: "anthropic-text", - outApi: "anthropic-text", - service: "anthropic", -}); +const nativeTextPreprocessor = createPreprocessorMiddleware( + { + inApi: "anthropic-text", + outApi: "anthropic-text", + service: "anthropic", + }, + { afterTransform: [addWebSearchTool] } +); -const textToChatPreprocessor = createPreprocessorMiddleware({ - inApi: "anthropic-text", - outApi: "anthropic-chat", - service: "anthropic", -}); +const textToChatPreprocessor = createPreprocessorMiddleware( + { + inApi: "anthropic-text", + outApi: "anthropic-chat", + service: "anthropic", + }, + { afterTransform: [addWebSearchTool] } +); /** * Routes text completion prompts to anthropic-chat if they need translation @@ -239,11 +301,14 @@ const oaiToTextPreprocessor = createPreprocessorMiddleware({ service: "anthropic", }); -const oaiToChatPreprocessor = createPreprocessorMiddleware({ - inApi: "openai", - outApi: "anthropic-chat", - service: "anthropic", -}); +const oaiToChatPreprocessor = createPreprocessorMiddleware( + { + inApi: "openai", + outApi: "anthropic-chat", + service: "anthropic", + }, + { afterTransform: [addWebSearchTool] } +); /** * Routes an OpenAI prompt to either the legacy Claude text completion endpoint