diff --git a/js/plugins/anthropic/src/parts/server_tool_use_part.ts b/js/plugins/anthropic/src/parts/server_tool_use_part.ts index e2baeac61d..ce19571311 100644 --- a/js/plugins/anthropic/src/parts/server_tool_use_part.ts +++ b/js/plugins/anthropic/src/parts/server_tool_use_part.ts @@ -45,7 +45,6 @@ export const ServerToolUsePart: SupportedPart = { : baseName; return { - text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(contentBlock.input)}`, custom: { anthropicServerToolUse: { id: contentBlock.id, @@ -70,7 +69,6 @@ export const ServerToolUsePart: SupportedPart = { ); } return { - text: `[Anthropic server tool ${contentBlock.name}] input: ${JSON.stringify(contentBlock.input)}`, custom: { anthropicServerToolUse: { id: contentBlock.id, diff --git a/js/plugins/anthropic/src/parts/web_search_tool_result_part.ts b/js/plugins/anthropic/src/parts/web_search_tool_result_part.ts index a71dfd4dc2..186c730f72 100644 --- a/js/plugins/anthropic/src/parts/web_search_tool_result_part.ts +++ b/js/plugins/anthropic/src/parts/web_search_tool_result_part.ts @@ -77,7 +77,6 @@ function toWebSearchToolResultPart(params: { }): Part { const { toolUseId, content, type } = params; return { - text: `[Anthropic server tool result ${toolUseId}] ${JSON.stringify(content)}`, custom: { anthropicServerToolResult: { type, diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 35f226cb6c..a8d5773b46 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -340,7 +340,11 @@ export class BetaRunner extends BaseRunner { .metadata as BetaMessageCreateParams['metadata']; } if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); + const configTools = (request.config?.tools as BetaTool[]) ?? []; + body.tools = [ + ...configTools, + ...request.tools.map((tool) => this.toAnthropicTool(tool)), + ]; } const thinkingConfig = this.toAnthropicThinkingConfig( request.config?.thinking @@ -411,7 +415,11 @@ export class BetaRunner extends BaseRunner { .metadata as BetaMessageCreateParams['metadata']; } if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); + const configTools = (request.config?.tools as BetaTool[]) ?? []; + body.tools = [ + ...configTools, + ...request.tools.map((tool) => this.toAnthropicTool(tool)), + ]; } const thinkingConfig = this.toAnthropicThinkingConfig( request.config?.thinking diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index b5c6bef63f..b97aab4898 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -223,7 +223,11 @@ export class Runner extends BaseRunner { } if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); + const configTools = (request.config?.tools as Tool[]) ?? []; + body.tools = [ + ...configTools, + ...request.tools.map((tool) => this.toAnthropicTool(tool)), + ]; } if (request.config?.topK !== undefined) { body.top_k = request.config.topK; @@ -294,7 +298,11 @@ export class Runner extends BaseRunner { } if (request.tools) { - body.tools = request.tools.map((tool) => this.toAnthropicTool(tool)); + const configTools = (request.config?.tools as Tool[]) ?? []; + body.tools = [ + ...configTools, + ...request.tools.map((tool) => this.toAnthropicTool(tool)), + ]; } if (request.config?.topK !== undefined) { body.top_k = request.config.topK; diff --git a/js/plugins/anthropic/src/types.ts b/js/plugins/anthropic/src/types.ts index 9796d71c36..0e83f15f97 100644 --- a/js/plugins/anthropic/src/types.ts +++ b/js/plugins/anthropic/src/types.ts @@ -86,7 +86,7 @@ export const AnthropicBaseConfigSchema = GenerationCommonConfigSchema.extend({ .optional(), /** Optional shorthand to pick API surface for this request. */ apiVersion: z.enum(['stable', 'beta']).optional(), -}); +}).passthrough(); export type AnthropicBaseConfigSchemaType = typeof AnthropicBaseConfigSchema; diff --git a/js/testapps/anthropic/package.json b/js/testapps/anthropic/package.json index 08e1a0d2fd..6f2fd565a9 100644 --- a/js/testapps/anthropic/package.json +++ b/js/testapps/anthropic/package.json @@ -13,6 +13,7 @@ "dev:stable:text-plain": "genkit start -- npx tsx --watch src/stable/text-plain.ts", "dev:stable:webp": "genkit start -- npx tsx --watch src/stable/webp.ts", "dev:stable:pdf": "genkit start -- npx tsx --watch src/stable/pdf.ts", + "dev:stable:web-search-tool": "genkit start -- npx tsx --watch src/stable/web-search-tool.ts", "genkit:dev": "cross-env GENKIT_ENV=dev npm run dev:stable", "genkit:start": "cross-env GENKIT_ENV=dev genkit start -- tsx --watch src/stable/basic.ts", "dev": "export GENKIT_RUNTIME_ID=$(openssl rand -hex 8) && node lib/stable/basic.js 2>&1" diff --git a/js/testapps/anthropic/src/stable/web-search-tool.ts b/js/testapps/anthropic/src/stable/web-search-tool.ts new file mode 100644 index 0000000000..c056f4c935 --- /dev/null +++ b/js/testapps/anthropic/src/stable/web-search-tool.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { anthropic } from '@genkit-ai/anthropic'; +import { genkit } from 'genkit'; + +const ai = genkit({ + plugins: [ + // Configure the plugin with environment-driven API key + anthropic(), + ], +}); + +ai.defineFlow('anthropic-stable-web-search-tool', async () => { + const { text } = await ai.generate({ + model: anthropic.model('claude-sonnet-4-5'), + prompt: 'What is the weather in Tokyo?', + config: { + tools: [ + { + type: 'web_search_20250305', + name: 'web_search', + }, + ], + }, + }); + + return text; +}); + +ai.defineFlow( + 'anthropic-stable-web-search-tool-stream', + async (_, { sendChunk }) => { + const { stream } = ai.generateStream({ + model: anthropic.model('claude-sonnet-4-5'), + prompt: 'What is the weather in Tokyo?', + config: { + tools: [ + { + type: 'web_search_20250305', + name: 'web_search', + }, + ], + }, + }); + + let response = ''; + for await (const chunk of stream) { + if (chunk.text) { + response += chunk.text; + sendChunk(chunk.text); + } + } + + return response; + } +);