From 691eb09598738446c85b535324ba308028aad74f Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 6 Jan 2026 09:34:54 +0000 Subject: [PATCH 1/9] refactor(js/plugins/anthropic): extract out shared utils --- js/plugins/anthropic/src/runner/base.ts | 38 +------ js/plugins/anthropic/src/runner/beta.ts | 88 +++++++++------- js/plugins/anthropic/src/runner/stable.ts | 73 ++++--------- js/plugins/anthropic/src/runner/utils.ts | 122 ++++++++++++++++++++++ 4 files changed, 192 insertions(+), 129 deletions(-) create mode 100644 js/plugins/anthropic/src/runner/utils.ts diff --git a/js/plugins/anthropic/src/runner/base.ts b/js/plugins/anthropic/src/runner/base.ts index e6b7132e28..6b3fd9c087 100644 --- a/js/plugins/anthropic/src/runner/base.ts +++ b/js/plugins/anthropic/src/runner/base.ts @@ -49,8 +49,7 @@ import { RunnerToolResponseContent, RunnerTypes, } from './types.js'; - -const ANTHROPIC_THINKING_CUSTOM_KEY = 'anthropicThinking'; +import { ANTHROPIC_THINKING_CUSTOM_KEY } from './utils.js'; /** * Shared runner logic for Anthropic SDK integrations. @@ -298,23 +297,6 @@ export abstract class BaseRunner { }; } - protected createThinkingPart(thinking: string, signature?: string): Part { - const custom = - signature !== undefined - ? { - [ANTHROPIC_THINKING_CUSTOM_KEY]: { signature }, - } - : undefined; - return custom - ? { - reasoning: thinking, - custom, - } - : { - reasoning: thinking, - }; - } - protected getThinkingSignature(part: Part): string | undefined { const custom = part.custom as Record | undefined; const thinkingValue = custom?.[ANTHROPIC_THINKING_CUSTOM_KEY]; @@ -363,24 +345,6 @@ export abstract class BaseRunner { return undefined; } - protected toWebSearchToolResultPart(params: { - toolUseId: string; - content: unknown; - type: string; - }): Part { - const { toolUseId, content, type } = params; - return { - text: `[Anthropic server tool result ${toolUseId}] ${JSON.stringify(content)}`, - custom: { - anthropicServerToolResult: { - type, - toolUseId, - content, - }, - }, - }; - } - /** * Converts a Genkit Part to the corresponding Anthropic content block. * Each runner implements this to return its specific API type. diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 099a589909..5dd73ac1c6 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -47,6 +47,13 @@ import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; import { RunnerTypes } from './types.js'; +import { + redactedThinkingBlockToPart, + textBlockToPart, + thinkingBlockToPart, + toolUseBlockToPart, + webSearchToolResultBlockToPart, +} from './utils.js'; /** * Server-managed tool blocks emitted by the beta API that Genkit cannot yet @@ -67,6 +74,10 @@ const BETA_UNSUPPORTED_SERVER_TOOL_BLOCK_TYPES = new Set([ 'container_upload', ]); +function unsupportedServerToolError(blockType: string): string { + return `Anthropic beta runner does not yet support server-managed tool block '${blockType}'. Please retry against the stable API or wait for dedicated support.`; +} + const BETA_APIS = [ // 'message-batches-2024-09-24', // 'prompt-caching-2024-07-31', @@ -118,8 +129,31 @@ function toAnthropicSchema( return out; } -const unsupportedServerToolError = (blockType: string): string => - `Anthropic beta runner does not yet support server-managed tool block '${blockType}'. Please retry against the stable API or wait for dedicated support.`; +/** + * Converts a beta server_tool_use block to a Genkit Part. + * Beta API has an optional server_name prefix that stable doesn't have. + */ +function betaServerToolUseBlockToPart(block: { + id: string; + name?: string; + input: unknown; + server_name?: string; +}): Part { + const baseName = block.name ?? 'unknown_tool'; + const serverToolName = block.server_name + ? `${block.server_name}/${baseName}` + : baseName; + return { + text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(block.input)}`, + custom: { + anthropicServerToolUse: { + id: block.id, + name: serverToolName, + input: block.input, + }, + }, + }; +} interface BetaRunnerTypes extends RunnerTypes { Message: BetaMessage; @@ -486,55 +520,31 @@ export class BetaRunner extends BaseRunner { private fromBetaContentBlock(contentBlock: BetaContentBlock): Part { switch (contentBlock.type) { - case 'tool_use': { - return { - toolRequest: { - ref: contentBlock.id, - name: contentBlock.name ?? 'unknown_tool', - input: contentBlock.input, - }, - }; - } + case 'tool_use': + // Beta API may have undefined name, fallback to 'unknown_tool' + return toolUseBlockToPart({ + id: contentBlock.id, + name: contentBlock.name ?? 'unknown_tool', + input: contentBlock.input, + }); case 'mcp_tool_use': throw new Error(unsupportedServerToolError(contentBlock.type)); - case 'server_tool_use': { - const baseName = contentBlock.name ?? 'unknown_tool'; - const serverToolName = - 'server_name' in contentBlock && contentBlock.server_name - ? `${contentBlock.server_name}/${baseName}` - : baseName; - return { - text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(contentBlock.input)}`, - custom: { - anthropicServerToolUse: { - id: contentBlock.id, - name: serverToolName, - input: contentBlock.input, - }, - }, - }; - } + case 'server_tool_use': + return betaServerToolUseBlockToPart(contentBlock); case 'web_search_tool_result': - return this.toWebSearchToolResultPart({ - type: contentBlock.type, - toolUseId: contentBlock.tool_use_id, - content: contentBlock.content, - }); + return webSearchToolResultBlockToPart(contentBlock); case 'text': - return { text: contentBlock.text }; + return textBlockToPart(contentBlock); case 'thinking': - return this.createThinkingPart( - contentBlock.thinking, - contentBlock.signature - ); + return thinkingBlockToPart(contentBlock); case 'redacted_thinking': - return { custom: { redactedThinking: contentBlock.data } }; + return redactedThinkingBlockToPart(contentBlock); default: { if (BETA_UNSUPPORTED_SERVER_TOOL_BLOCK_TYPES.has(contentBlock.type)) { diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 1496029ebd..06aa9426e8 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -45,6 +45,14 @@ import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; import { RunnerTypes as BaseRunnerTypes } from './types.js'; +import { + redactedThinkingBlockToPart, + serverToolUseBlockToPart, + textBlockToPart, + thinkingBlockToPart, + toolUseBlockToPart, + webSearchToolResultBlockToPart, +} from './utils.js'; interface RunnerTypes extends BaseRunnerTypes { Message: Message; @@ -360,41 +368,22 @@ export class Runner extends BaseRunner { switch (block.type) { case 'server_tool_use': - return { - text: `[Anthropic server tool ${block.name}] input: ${JSON.stringify(block.input)}`, - custom: { - anthropicServerToolUse: { - id: block.id, - name: block.name, - input: block.input, - }, - }, - }; + return serverToolUseBlockToPart(block); case 'web_search_tool_result': - return this.toWebSearchToolResultPart({ - type: block.type, - toolUseId: block.tool_use_id, - content: block.content, - }); + return webSearchToolResultBlockToPart(block); case 'text': - return { text: block.text }; + return textBlockToPart(block); case 'thinking': - return this.createThinkingPart(block.thinking, block.signature); + return thinkingBlockToPart(block); case 'redacted_thinking': - return { custom: { redactedThinking: block.data } }; + return redactedThinkingBlockToPart(block); case 'tool_use': - return { - toolRequest: { - ref: block.id, - name: block.name, - input: block.input, - }, - }; + return toolUseBlockToPart(block); default: { const unknownType = (block as { type: string }).type; @@ -413,44 +402,22 @@ export class Runner extends BaseRunner { protected fromAnthropicContentBlock(contentBlock: ContentBlock): Part { switch (contentBlock.type) { case 'server_tool_use': - return { - text: `[Anthropic server tool ${contentBlock.name}] input: ${JSON.stringify(contentBlock.input)}`, - custom: { - anthropicServerToolUse: { - id: contentBlock.id, - name: contentBlock.name, - input: contentBlock.input, - }, - }, - }; + return serverToolUseBlockToPart(contentBlock); case 'web_search_tool_result': - return this.toWebSearchToolResultPart({ - type: contentBlock.type, - toolUseId: contentBlock.tool_use_id, - content: contentBlock.content, - }); + return webSearchToolResultBlockToPart(contentBlock); case 'tool_use': - return { - toolRequest: { - ref: contentBlock.id, - name: contentBlock.name, - input: contentBlock.input, - }, - }; + return toolUseBlockToPart(contentBlock); case 'text': - return { text: contentBlock.text }; + return textBlockToPart(contentBlock); case 'thinking': - return this.createThinkingPart( - contentBlock.thinking, - contentBlock.signature - ); + return thinkingBlockToPart(contentBlock); case 'redacted_thinking': - return { custom: { redactedThinking: contentBlock.data } }; + return redactedThinkingBlockToPart(contentBlock); default: { const unknownType = (contentBlock as { type: string }).type; diff --git a/js/plugins/anthropic/src/runner/utils.ts b/js/plugins/anthropic/src/runner/utils.ts new file mode 100644 index 0000000000..1cf3e1cd61 --- /dev/null +++ b/js/plugins/anthropic/src/runner/utils.ts @@ -0,0 +1,122 @@ +/** + * 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. + */ + +/** + * Pure utility functions for converting Anthropic content blocks to Genkit Parts. + * + * Uses structural typing so both stable and beta API types work with these functions. + */ + +import type { Part } from 'genkit'; + +/** + * Key used to store Anthropic-specific thinking metadata in Genkit Part custom field. + */ +export const ANTHROPIC_THINKING_CUSTOM_KEY = 'anthropicThinking'; + +/** + * Converts a text block to a Genkit Part. + */ +export function textBlockToPart(block: { text: string }): Part { + return { text: block.text }; +} + +/** + * Converts a thinking block to a Genkit Part, optionally including signature metadata. + */ +export function thinkingBlockToPart( + block: { thinking: string; signature?: string }, + signature?: string +): Part { + const sig = signature ?? block.signature; + const custom = + sig !== undefined + ? { + [ANTHROPIC_THINKING_CUSTOM_KEY]: { signature: sig }, + } + : undefined; + return custom + ? { + reasoning: block.thinking, + custom, + } + : { + reasoning: block.thinking, + }; +} + +/** + * Converts a redacted thinking block to a Genkit Part. + */ +export function redactedThinkingBlockToPart(block: { data: string }): Part { + return { custom: { redactedThinking: block.data } }; +} + +/** + * Converts a tool_use block to a Genkit Part. + */ +export function toolUseBlockToPart(block: { + id: string; + name: string; + input: unknown; +}): Part { + return { + toolRequest: { + ref: block.id, + name: block.name, + input: block.input, + }, + }; +} + +/** + * Converts a server_tool_use block to a Genkit Part. + */ +export function serverToolUseBlockToPart(block: { + id: string; + name: string; + input: unknown; +}): Part { + return { + text: `[Anthropic server tool ${block.name}] input: ${JSON.stringify(block.input)}`, + custom: { + anthropicServerToolUse: { + id: block.id, + name: block.name, + input: block.input, + }, + }, + }; +} + +/** + * Converts a web_search_tool_result block to a Genkit Part. + */ +export function webSearchToolResultBlockToPart(block: { + tool_use_id: string; + content: unknown; +}): Part { + return { + text: `[Anthropic server tool result ${block.tool_use_id}] ${JSON.stringify(block.content)}`, + custom: { + anthropicServerToolResult: { + type: 'web_search_tool_result', + toolUseId: block.tool_use_id, + content: block.content, + }, + }, + }; +} From 6791b79b2827c4a5fe59f446783302b367c247ad Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 6 Jan 2026 13:38:52 +0000 Subject: [PATCH 2/9] refactor(js/plugins/anthropic): better extraction of converters --- js/plugins/anthropic/src/runner/base.ts | 2 +- js/plugins/anthropic/src/runner/beta.ts | 116 ++++++------------ .../anthropic/src/runner/converters/beta.ts | 54 ++++++++ .../runner/{utils.ts => converters/shared.ts} | 92 +++++++------- .../anthropic/src/runner/converters/stable.ts | 42 +++++++ js/plugins/anthropic/src/runner/stable.ts | 65 ++++++---- .../anthropic/tests/beta_runner_test.ts | 9 ++ 7 files changed, 228 insertions(+), 152 deletions(-) create mode 100644 js/plugins/anthropic/src/runner/converters/beta.ts rename js/plugins/anthropic/src/runner/{utils.ts => converters/shared.ts} (64%) create mode 100644 js/plugins/anthropic/src/runner/converters/stable.ts diff --git a/js/plugins/anthropic/src/runner/base.ts b/js/plugins/anthropic/src/runner/base.ts index 6b3fd9c087..e2466f0adc 100644 --- a/js/plugins/anthropic/src/runner/base.ts +++ b/js/plugins/anthropic/src/runner/base.ts @@ -37,6 +37,7 @@ import { type ThinkingConfig, } from '../types.js'; +import { ANTHROPIC_THINKING_CUSTOM_KEY } from './converters/shared.js'; import { RunnerContentBlockParam, RunnerMessage, @@ -49,7 +50,6 @@ import { RunnerToolResponseContent, RunnerTypes, } from './types.js'; -import { ANTHROPIC_THINKING_CUSTOM_KEY } from './utils.js'; /** * Shared runner logic for Anthropic SDK integrations. diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 5dd73ac1c6..2fea57c59c 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -46,37 +46,21 @@ import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js'; import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; -import { RunnerTypes } from './types.js'; import { + betaServerToolUseBlockToPart, + unsupportedServerToolError, +} from './converters/beta.js'; +import { + inputJsonDeltaError, redactedThinkingBlockToPart, textBlockToPart, + textDeltaToPart, thinkingBlockToPart, + thinkingDeltaToPart, toolUseBlockToPart, webSearchToolResultBlockToPart, -} from './utils.js'; - -/** - * Server-managed tool blocks emitted by the beta API that Genkit cannot yet - * interpret. We fail fast on these so callers do not accidentally treat them as - * locally executable tool invocations. - */ -/** - * Server tool types that exist in beta but are not yet supported. - * Note: server_tool_use and web_search_tool_result ARE supported (same as stable API). - */ -const BETA_UNSUPPORTED_SERVER_TOOL_BLOCK_TYPES = new Set([ - 'web_fetch_tool_result', - 'code_execution_tool_result', - 'bash_code_execution_tool_result', - 'text_editor_code_execution_tool_result', - 'mcp_tool_result', - 'mcp_tool_use', - 'container_upload', -]); - -function unsupportedServerToolError(blockType: string): string { - return `Anthropic beta runner does not yet support server-managed tool block '${blockType}'. Please retry against the stable API or wait for dedicated support.`; -} +} from './converters/shared.js'; +import { RunnerTypes } from './types.js'; const BETA_APIS = [ // 'message-batches-2024-09-24', @@ -129,32 +113,6 @@ function toAnthropicSchema( return out; } -/** - * Converts a beta server_tool_use block to a Genkit Part. - * Beta API has an optional server_name prefix that stable doesn't have. - */ -function betaServerToolUseBlockToPart(block: { - id: string; - name?: string; - input: unknown; - server_name?: string; -}): Part { - const baseName = block.name ?? 'unknown_tool'; - const serverToolName = block.server_name - ? `${block.server_name}/${baseName}` - : baseName; - return { - text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(block.input)}`, - custom: { - anthropicServerToolUse: { - id: block.id, - name: serverToolName, - input: block.input, - }, - }, - }; -} - interface BetaRunnerTypes extends RunnerTypes { Message: BetaMessage; Stream: BetaMessageStream; @@ -496,23 +454,19 @@ export class BetaRunner extends BaseRunner { protected toGenkitPart(event: BetaRawMessageStreamEvent): Part | undefined { if (event.type === 'content_block_start') { - const blockType = (event.content_block as { type?: string }).type; - if ( - blockType && - BETA_UNSUPPORTED_SERVER_TOOL_BLOCK_TYPES.has(blockType) - ) { - throw new Error(unsupportedServerToolError(blockType)); - } return this.fromBetaContentBlock(event.content_block); } if (event.type === 'content_block_delta') { if (event.delta.type === 'text_delta') { - return { text: event.delta.text }; + return textDeltaToPart(event.delta); } if (event.delta.type === 'thinking_delta') { - return { reasoning: event.delta.thinking }; + return thinkingDeltaToPart(event.delta); } - // server/client tool input_json_delta not supported yet + if (event.delta.type === 'input_json_delta') { + throw inputJsonDeltaError(); + } + // signature_delta - ignore return undefined; } return undefined; @@ -520,6 +474,9 @@ export class BetaRunner extends BaseRunner { private fromBetaContentBlock(contentBlock: BetaContentBlock): Part { switch (contentBlock.type) { + case 'text': + return textBlockToPart(contentBlock); + case 'tool_use': // Beta API may have undefined name, fallback to 'unknown_tool' return toolUseBlockToPart({ @@ -528,8 +485,11 @@ export class BetaRunner extends BaseRunner { input: contentBlock.input, }); - case 'mcp_tool_use': - throw new Error(unsupportedServerToolError(contentBlock.type)); + case 'thinking': + return thinkingBlockToPart(contentBlock); + + case 'redacted_thinking': + return redactedThinkingBlockToPart(contentBlock); case 'server_tool_use': return betaServerToolUseBlockToPart(contentBlock); @@ -537,24 +497,26 @@ export class BetaRunner extends BaseRunner { case 'web_search_tool_result': return webSearchToolResultBlockToPart(contentBlock); - case 'text': - return textBlockToPart(contentBlock); - - case 'thinking': - return thinkingBlockToPart(contentBlock); - - case 'redacted_thinking': - return redactedThinkingBlockToPart(contentBlock); + // Unsupported beta server tool types + case 'mcp_tool_use': + case 'mcp_tool_result': + case 'web_fetch_tool_result': + case 'code_execution_tool_result': + case 'bash_code_execution_tool_result': + case 'text_editor_code_execution_tool_result': + case 'container_upload': + case 'tool_search_tool_result': + throw new Error(unsupportedServerToolError(contentBlock.type)); default: { - if (BETA_UNSUPPORTED_SERVER_TOOL_BLOCK_TYPES.has(contentBlock.type)) { - throw new Error(unsupportedServerToolError(contentBlock.type)); - } + // Exhaustive check (uncomment when all types are handled): + // const _exhaustive: never = contentBlock; + // throw new Error( + // `Unhandled block type: ${(_exhaustive as { type: string }).type}` + // ); const unknownType = (contentBlock as { type: string }).type; logger.warn( - `Unexpected Anthropic beta content block type: ${unknownType}. Returning empty text. Content block: ${JSON.stringify( - contentBlock - )}` + `Unexpected Anthropic beta content block type: ${unknownType}. Returning empty text. Content block: ${JSON.stringify(contentBlock)}` ); return { text: '' }; } diff --git a/js/plugins/anthropic/src/runner/converters/beta.ts b/js/plugins/anthropic/src/runner/converters/beta.ts new file mode 100644 index 0000000000..dabc90911b --- /dev/null +++ b/js/plugins/anthropic/src/runner/converters/beta.ts @@ -0,0 +1,54 @@ +/** + * 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. + */ + +/** + * Converters for beta API content blocks. + */ + +import type { Part } from 'genkit'; + +/** + * Converts a server_tool_use block to a Genkit Part. + * In the beta API, name may be undefined and server_name prefix is supported. + */ +export function betaServerToolUseBlockToPart(block: { + id: string; + name?: string; + input: unknown; + server_name?: string; +}): Part { + const baseName = block.name ?? 'unknown_tool'; + const serverToolName = block.server_name + ? `${block.server_name}/${baseName}` + : baseName; + return { + text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(block.input)}`, + custom: { + anthropicServerToolUse: { + id: block.id, + name: serverToolName, + input: block.input, + }, + }, + }; +} + +/** + * Error message for unsupported server tool block types in the beta API. + */ +export function unsupportedServerToolError(blockType: string): string { + return `Anthropic beta runner does not yet support server-managed tool block '${blockType}'. Please retry against the stable API or wait for dedicated support.`; +} diff --git a/js/plugins/anthropic/src/runner/utils.ts b/js/plugins/anthropic/src/runner/converters/shared.ts similarity index 64% rename from js/plugins/anthropic/src/runner/utils.ts rename to js/plugins/anthropic/src/runner/converters/shared.ts index 1cf3e1cd61..a8df7a5b5e 100644 --- a/js/plugins/anthropic/src/runner/utils.ts +++ b/js/plugins/anthropic/src/runner/converters/shared.ts @@ -15,8 +15,7 @@ */ /** - * Pure utility functions for converting Anthropic content blocks to Genkit Parts. - * + * Shared utilities for converting Anthropic content blocks to Genkit Parts. * Uses structural typing so both stable and beta API types work with these functions. */ @@ -34,37 +33,6 @@ export function textBlockToPart(block: { text: string }): Part { return { text: block.text }; } -/** - * Converts a thinking block to a Genkit Part, optionally including signature metadata. - */ -export function thinkingBlockToPart( - block: { thinking: string; signature?: string }, - signature?: string -): Part { - const sig = signature ?? block.signature; - const custom = - sig !== undefined - ? { - [ANTHROPIC_THINKING_CUSTOM_KEY]: { signature: sig }, - } - : undefined; - return custom - ? { - reasoning: block.thinking, - custom, - } - : { - reasoning: block.thinking, - }; -} - -/** - * Converts a redacted thinking block to a Genkit Part. - */ -export function redactedThinkingBlockToPart(block: { data: string }): Part { - return { custom: { redactedThinking: block.data } }; -} - /** * Converts a tool_use block to a Genkit Part. */ @@ -83,23 +51,28 @@ export function toolUseBlockToPart(block: { } /** - * Converts a server_tool_use block to a Genkit Part. + * Converts a thinking block to a Genkit Part, including signature metadata if present. */ -export function serverToolUseBlockToPart(block: { - id: string; - name: string; - input: unknown; +export function thinkingBlockToPart(block: { + thinking: string; + signature?: string; }): Part { - return { - text: `[Anthropic server tool ${block.name}] input: ${JSON.stringify(block.input)}`, - custom: { - anthropicServerToolUse: { - id: block.id, - name: block.name, - input: block.input, + if (block.signature !== undefined) { + return { + reasoning: block.thinking, + custom: { + [ANTHROPIC_THINKING_CUSTOM_KEY]: { signature: block.signature }, }, - }, - }; + }; + } + return { reasoning: block.thinking }; +} + +/** + * Converts a redacted thinking block to a Genkit Part. + */ +export function redactedThinkingBlockToPart(block: { data: string }): Part { + return { custom: { redactedThinking: block.data } }; } /** @@ -120,3 +93,28 @@ export function webSearchToolResultBlockToPart(block: { }, }; } + +// --- Delta converters for streaming --- + +/** + * Converts a text_delta to a Genkit Part. + */ +export function textDeltaToPart(delta: { text: string }): Part { + return { text: delta.text }; +} + +/** + * Converts a thinking_delta to a Genkit Part. + */ +export function thinkingDeltaToPart(delta: { thinking: string }): Part { + return { reasoning: delta.thinking }; +} + +/** + * Error for unsupported input_json_delta in streaming. + */ +export function inputJsonDeltaError(): Error { + return new Error( + 'Anthropic streaming tool input (input_json_delta) is not yet supported. Please disable streaming or upgrade this plugin.' + ); +} diff --git a/js/plugins/anthropic/src/runner/converters/stable.ts b/js/plugins/anthropic/src/runner/converters/stable.ts new file mode 100644 index 0000000000..addaa6c3c4 --- /dev/null +++ b/js/plugins/anthropic/src/runner/converters/stable.ts @@ -0,0 +1,42 @@ +/** + * 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. + */ + +/** + * Converters for stable API content blocks. + */ + +import type { Part } from 'genkit'; + +/** + * Converts a server_tool_use block to a Genkit Part. + * In the stable API, name is always present. + */ +export function serverToolUseBlockToPart(block: { + id: string; + name: string; + input: unknown; +}): Part { + return { + text: `[Anthropic server tool ${block.name}] input: ${JSON.stringify(block.input)}`, + custom: { + anthropicServerToolUse: { + id: block.id, + name: block.name, + input: block.input, + }, + }, + }; +} diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 06aa9426e8..139afc506c 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -44,15 +44,18 @@ import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js'; import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; -import { RunnerTypes as BaseRunnerTypes } from './types.js'; import { + inputJsonDeltaError, redactedThinkingBlockToPart, - serverToolUseBlockToPart, textBlockToPart, + textDeltaToPart, thinkingBlockToPart, + thinkingDeltaToPart, toolUseBlockToPart, webSearchToolResultBlockToPart, -} from './utils.js'; +} from './converters/shared.js'; +import { serverToolUseBlockToPart } from './converters/stable.js'; +import { RunnerTypes as BaseRunnerTypes } from './types.js'; interface RunnerTypes extends BaseRunnerTypes { Message: Message; @@ -344,18 +347,16 @@ export class Runner extends BaseRunner { if (event.type === 'content_block_delta') { const delta = event.delta; - if (delta.type === 'input_json_delta') { - throw new Error( - 'Anthropic streaming tool input (input_json_delta) is not yet supported. Please disable streaming or upgrade this plugin.' - ); - } - if (delta.type === 'text_delta') { - return { text: delta.text }; + return textDeltaToPart(delta); } if (delta.type === 'thinking_delta') { - return { reasoning: delta.thinking }; + return thinkingDeltaToPart(delta); + } + + if (delta.type === 'input_json_delta') { + throw inputJsonDeltaError(); } // signature_delta - ignore @@ -367,25 +368,30 @@ export class Runner extends BaseRunner { const block = event.content_block; switch (block.type) { - case 'server_tool_use': - return serverToolUseBlockToPart(block); - - case 'web_search_tool_result': - return webSearchToolResultBlockToPart(block); - case 'text': return textBlockToPart(block); + case 'tool_use': + return toolUseBlockToPart(block); + case 'thinking': return thinkingBlockToPart(block); case 'redacted_thinking': return redactedThinkingBlockToPart(block); - case 'tool_use': - return toolUseBlockToPart(block); + case 'server_tool_use': + return serverToolUseBlockToPart(block); + + case 'web_search_tool_result': + return webSearchToolResultBlockToPart(block); default: { + // Exhaustive check (uncomment when all types are handled): + // const _exhaustive: never = block; + // throw new Error( + // `Unhandled block type: ${(_exhaustive as { type: string }).type}` + // ); const unknownType = (block as { type: string }).type; logger.warn( `Unexpected Anthropic content block type in stream: ${unknownType}. Returning undefined. Content block: ${JSON.stringify(block)}` @@ -401,25 +407,30 @@ export class Runner extends BaseRunner { protected fromAnthropicContentBlock(contentBlock: ContentBlock): Part { switch (contentBlock.type) { - case 'server_tool_use': - return serverToolUseBlockToPart(contentBlock); - - case 'web_search_tool_result': - return webSearchToolResultBlockToPart(contentBlock); + case 'text': + return textBlockToPart(contentBlock); case 'tool_use': return toolUseBlockToPart(contentBlock); - case 'text': - return textBlockToPart(contentBlock); - case 'thinking': return thinkingBlockToPart(contentBlock); case 'redacted_thinking': return redactedThinkingBlockToPart(contentBlock); + case 'server_tool_use': + return serverToolUseBlockToPart(contentBlock); + + case 'web_search_tool_result': + return webSearchToolResultBlockToPart(contentBlock); + default: { + // Exhaustive check (uncomment when all types are handled): + // const _exhaustive: never = contentBlock; + // throw new Error( + // `Unhandled block type: ${(_exhaustive as { type: string }).type}` + // ); const unknownType = (contentBlock as { type: string }).type; logger.warn( `Unexpected Anthropic content block type: ${unknownType}. Returning empty text. Content block: ${JSON.stringify(contentBlock)}` diff --git a/js/plugins/anthropic/tests/beta_runner_test.ts b/js/plugins/anthropic/tests/beta_runner_test.ts index 0d549b938c..e68f8c59c8 100644 --- a/js/plugins/anthropic/tests/beta_runner_test.ts +++ b/js/plugins/anthropic/tests/beta_runner_test.ts @@ -775,6 +775,15 @@ describe('BetaRunner', () => { }, }); + // Unknown block types log a warning and return empty text + // (Exhaustive check is commented out for forward compatibility) + // assert.throws( + // () => + // (runner as any).fromBetaContentBlock({ + // type: 'mystery', + // }), + // /Unhandled block type: mystery/ + // ); const warnMock = mock.method(console, 'warn', () => {}); const fallbackPart = (runner as any).fromBetaContentBlock({ type: 'mystery', From ca7ab3b336debb99867a58bc897151f4d37fdc1d Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 6 Jan 2026 15:41:11 +0000 Subject: [PATCH 3/9] refactor(js/plugins/anthropic): remove commented code --- js/plugins/anthropic/src/runner/beta.ts | 5 ----- js/plugins/anthropic/src/runner/stable.ts | 5 ----- js/plugins/anthropic/tests/beta_runner_test.ts | 9 --------- 3 files changed, 19 deletions(-) diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 2fea57c59c..cc55a97d97 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -509,11 +509,6 @@ export class BetaRunner extends BaseRunner { throw new Error(unsupportedServerToolError(contentBlock.type)); default: { - // Exhaustive check (uncomment when all types are handled): - // const _exhaustive: never = contentBlock; - // throw new Error( - // `Unhandled block type: ${(_exhaustive as { type: string }).type}` - // ); const unknownType = (contentBlock as { type: string }).type; logger.warn( `Unexpected Anthropic beta content block type: ${unknownType}. Returning empty text. Content block: ${JSON.stringify(contentBlock)}` diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 139afc506c..d24463a154 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -387,11 +387,6 @@ export class Runner extends BaseRunner { return webSearchToolResultBlockToPart(block); default: { - // Exhaustive check (uncomment when all types are handled): - // const _exhaustive: never = block; - // throw new Error( - // `Unhandled block type: ${(_exhaustive as { type: string }).type}` - // ); const unknownType = (block as { type: string }).type; logger.warn( `Unexpected Anthropic content block type in stream: ${unknownType}. Returning undefined. Content block: ${JSON.stringify(block)}` diff --git a/js/plugins/anthropic/tests/beta_runner_test.ts b/js/plugins/anthropic/tests/beta_runner_test.ts index e68f8c59c8..0d549b938c 100644 --- a/js/plugins/anthropic/tests/beta_runner_test.ts +++ b/js/plugins/anthropic/tests/beta_runner_test.ts @@ -775,15 +775,6 @@ describe('BetaRunner', () => { }, }); - // Unknown block types log a warning and return empty text - // (Exhaustive check is commented out for forward compatibility) - // assert.throws( - // () => - // (runner as any).fromBetaContentBlock({ - // type: 'mystery', - // }), - // /Unhandled block type: mystery/ - // ); const warnMock = mock.method(console, 'warn', () => {}); const fallbackPart = (runner as any).fromBetaContentBlock({ type: 'mystery', From 3672a0a8c931de52865f3f8372e33aae2939270a Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 6 Jan 2026 17:02:20 +0000 Subject: [PATCH 4/9] fix(js/plugins/anthropic): use metadata correctly on parts --- js/plugins/anthropic/src/runner/base.ts | 16 ++++------------ .../anthropic/src/runner/converters/beta.ts | 2 +- .../anthropic/src/runner/converters/shared.ts | 11 ++--------- .../anthropic/src/runner/converters/stable.ts | 2 +- js/plugins/anthropic/tests/beta_runner_test.ts | 6 +++--- js/plugins/anthropic/tests/integration_test.ts | 2 +- js/plugins/anthropic/tests/stable_runner_test.ts | 2 +- 7 files changed, 13 insertions(+), 28 deletions(-) diff --git a/js/plugins/anthropic/src/runner/base.ts b/js/plugins/anthropic/src/runner/base.ts index e2466f0adc..36c5a8a175 100644 --- a/js/plugins/anthropic/src/runner/base.ts +++ b/js/plugins/anthropic/src/runner/base.ts @@ -37,7 +37,6 @@ import { type ThinkingConfig, } from '../types.js'; -import { ANTHROPIC_THINKING_CUSTOM_KEY } from './converters/shared.js'; import { RunnerContentBlockParam, RunnerMessage, @@ -298,17 +297,10 @@ export abstract class BaseRunner { } protected getThinkingSignature(part: Part): string | undefined { - const custom = part.custom as Record | undefined; - const thinkingValue = custom?.[ANTHROPIC_THINKING_CUSTOM_KEY]; - if ( - typeof thinkingValue === 'object' && - thinkingValue !== null && - 'signature' in thinkingValue && - typeof (thinkingValue as { signature: unknown }).signature === 'string' - ) { - return (thinkingValue as { signature: string }).signature; - } - return undefined; + const metadata = part.metadata as Record | undefined; + return typeof metadata?.thoughtSignature === 'string' + ? metadata.thoughtSignature + : undefined; } protected getRedactedThinkingData(part: Part): string | undefined { diff --git a/js/plugins/anthropic/src/runner/converters/beta.ts b/js/plugins/anthropic/src/runner/converters/beta.ts index dabc90911b..c597dbb848 100644 --- a/js/plugins/anthropic/src/runner/converters/beta.ts +++ b/js/plugins/anthropic/src/runner/converters/beta.ts @@ -36,7 +36,7 @@ export function betaServerToolUseBlockToPart(block: { : baseName; return { text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(block.input)}`, - custom: { + metadata: { anthropicServerToolUse: { id: block.id, name: serverToolName, diff --git a/js/plugins/anthropic/src/runner/converters/shared.ts b/js/plugins/anthropic/src/runner/converters/shared.ts index a8df7a5b5e..6d6faf6091 100644 --- a/js/plugins/anthropic/src/runner/converters/shared.ts +++ b/js/plugins/anthropic/src/runner/converters/shared.ts @@ -21,11 +21,6 @@ import type { Part } from 'genkit'; -/** - * Key used to store Anthropic-specific thinking metadata in Genkit Part custom field. - */ -export const ANTHROPIC_THINKING_CUSTOM_KEY = 'anthropicThinking'; - /** * Converts a text block to a Genkit Part. */ @@ -60,9 +55,7 @@ export function thinkingBlockToPart(block: { if (block.signature !== undefined) { return { reasoning: block.thinking, - custom: { - [ANTHROPIC_THINKING_CUSTOM_KEY]: { signature: block.signature }, - }, + metadata: { thoughtSignature: block.signature }, }; } return { reasoning: block.thinking }; @@ -84,7 +77,7 @@ export function webSearchToolResultBlockToPart(block: { }): Part { return { text: `[Anthropic server tool result ${block.tool_use_id}] ${JSON.stringify(block.content)}`, - custom: { + metadata: { anthropicServerToolResult: { type: 'web_search_tool_result', toolUseId: block.tool_use_id, diff --git a/js/plugins/anthropic/src/runner/converters/stable.ts b/js/plugins/anthropic/src/runner/converters/stable.ts index addaa6c3c4..d6b3508d42 100644 --- a/js/plugins/anthropic/src/runner/converters/stable.ts +++ b/js/plugins/anthropic/src/runner/converters/stable.ts @@ -31,7 +31,7 @@ export function serverToolUseBlockToPart(block: { }): Part { return { text: `[Anthropic server tool ${block.name}] input: ${JSON.stringify(block.input)}`, - custom: { + metadata: { anthropicServerToolUse: { id: block.id, name: block.name, diff --git a/js/plugins/anthropic/tests/beta_runner_test.ts b/js/plugins/anthropic/tests/beta_runner_test.ts index 0d549b938c..98e4226e86 100644 --- a/js/plugins/anthropic/tests/beta_runner_test.ts +++ b/js/plugins/anthropic/tests/beta_runner_test.ts @@ -321,7 +321,7 @@ describe('BetaRunner', () => { const toolPart = exposed.toGenkitPart(serverToolEvent); assert.deepStrictEqual(toolPart, { text: '[Anthropic server tool srv/myTool] input: {"foo":"bar"}', - custom: { + metadata: { anthropicServerToolUse: { id: 'toolu_test', name: 'srv/myTool', @@ -732,7 +732,7 @@ describe('BetaRunner', () => { }); assert.deepStrictEqual(thinkingPart, { reasoning: 'pondering', - custom: { anthropicThinking: { signature: 'sig_456' } }, + metadata: { thoughtSignature: 'sig_456' }, }); const redactedPart = (runner as any).fromBetaContentBlock({ @@ -766,7 +766,7 @@ describe('BetaRunner', () => { }); assert.deepStrictEqual(serverToolPart, { text: '[Anthropic server tool srv/serverTool] input: {"arg":"value"}', - custom: { + metadata: { anthropicServerToolUse: { id: 'srv_tool_1', name: 'srv/serverTool', diff --git a/js/plugins/anthropic/tests/integration_test.ts b/js/plugins/anthropic/tests/integration_test.ts index 209a455870..13d88b373f 100644 --- a/js/plugins/anthropic/tests/integration_test.ts +++ b/js/plugins/anthropic/tests/integration_test.ts @@ -345,7 +345,7 @@ describe('Anthropic Integration', () => { ); assert.ok(reasoningPart, 'Expected reasoning part in assistant message'); assert.strictEqual( - reasoningPart?.custom?.anthropicThinking?.signature, + (reasoningPart?.metadata as Record)?.thoughtSignature, 'sig_reasoning_123' ); }); diff --git a/js/plugins/anthropic/tests/stable_runner_test.ts b/js/plugins/anthropic/tests/stable_runner_test.ts index 72797251b9..28e1834e2a 100644 --- a/js/plugins/anthropic/tests/stable_runner_test.ts +++ b/js/plugins/anthropic/tests/stable_runner_test.ts @@ -684,7 +684,7 @@ describe('fromAnthropicContentBlockChunk', () => { }, expectedOutput: { reasoning: 'Let me reason through this.', - custom: { anthropicThinking: { signature: 'sig_123' } }, + metadata: { thoughtSignature: 'sig_123' }, }, }, { From e34b676c736d7e4e7a4f1b996636a158375c06d5 Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Tue, 6 Jan 2026 17:08:23 +0000 Subject: [PATCH 5/9] fix(js/plugins/anthropic): update error message paths for metadata --- js/plugins/anthropic/src/runner/beta.ts | 2 +- js/plugins/anthropic/src/runner/stable.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index cc55a97d97..4efcd1fd13 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -159,7 +159,7 @@ export class BetaRunner extends BaseRunner { const signature = this.getThinkingSignature(part); if (!signature) { throw new Error( - 'Anthropic thinking parts require a signature when sending back to the API. Preserve the `custom.anthropicThinking.signature` value from the original response.' + 'Anthropic thinking parts require a signature when sending back to the API. Preserve the `metadata.thoughtSignature` value from the original response.' ); } return { diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index d24463a154..61c921b97c 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -95,7 +95,7 @@ export class Runner extends BaseRunner { const signature = this.getThinkingSignature(part); if (!signature) { throw new Error( - 'Anthropic thinking parts require a signature when sending back to the API. Preserve the `custom.anthropicThinking.signature` value from the original response.' + 'Anthropic thinking parts require a signature when sending back to the API. Preserve the `metadata.thoughtSignature` value from the original response.' ); } return { From f84104b5e5a924cb1ffa76c3348ff8592e2886af Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Wed, 7 Jan 2026 16:19:48 +0000 Subject: [PATCH 6/9] feat(anthropic): add `input_json_delta` --- js/plugins/anthropic/src/runner/beta.ts | 3 +-- .../anthropic/src/runner/converters/shared.ts | 9 ------- js/plugins/anthropic/src/runner/stable.ts | 3 +-- .../anthropic/tests/stable_runner_test.ts | 25 +++++++++---------- js/testapps/anthropic/src/stable/tools.ts | 21 ++++++++++++++++ 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/js/plugins/anthropic/src/runner/beta.ts b/js/plugins/anthropic/src/runner/beta.ts index 4efcd1fd13..24fcac60d8 100644 --- a/js/plugins/anthropic/src/runner/beta.ts +++ b/js/plugins/anthropic/src/runner/beta.ts @@ -51,7 +51,6 @@ import { unsupportedServerToolError, } from './converters/beta.js'; import { - inputJsonDeltaError, redactedThinkingBlockToPart, textBlockToPart, textDeltaToPart, @@ -464,7 +463,7 @@ export class BetaRunner extends BaseRunner { return thinkingDeltaToPart(event.delta); } if (event.delta.type === 'input_json_delta') { - throw inputJsonDeltaError(); + return { data: event.delta.partial_json }; } // signature_delta - ignore return undefined; diff --git a/js/plugins/anthropic/src/runner/converters/shared.ts b/js/plugins/anthropic/src/runner/converters/shared.ts index 6d6faf6091..ddb2fc0772 100644 --- a/js/plugins/anthropic/src/runner/converters/shared.ts +++ b/js/plugins/anthropic/src/runner/converters/shared.ts @@ -102,12 +102,3 @@ export function textDeltaToPart(delta: { text: string }): Part { export function thinkingDeltaToPart(delta: { thinking: string }): Part { return { reasoning: delta.thinking }; } - -/** - * Error for unsupported input_json_delta in streaming. - */ -export function inputJsonDeltaError(): Error { - return new Error( - 'Anthropic streaming tool input (input_json_delta) is not yet supported. Please disable streaming or upgrade this plugin.' - ); -} diff --git a/js/plugins/anthropic/src/runner/stable.ts b/js/plugins/anthropic/src/runner/stable.ts index 61c921b97c..e08031e85e 100644 --- a/js/plugins/anthropic/src/runner/stable.ts +++ b/js/plugins/anthropic/src/runner/stable.ts @@ -45,7 +45,6 @@ import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js'; import { removeUndefinedProperties } from '../utils.js'; import { BaseRunner } from './base.js'; import { - inputJsonDeltaError, redactedThinkingBlockToPart, textBlockToPart, textDeltaToPart, @@ -356,7 +355,7 @@ export class Runner extends BaseRunner { } if (delta.type === 'input_json_delta') { - throw inputJsonDeltaError(); + return { data: delta.partial_json }; } // signature_delta - ignore diff --git a/js/plugins/anthropic/tests/stable_runner_test.ts b/js/plugins/anthropic/tests/stable_runner_test.ts index 28e1834e2a..cf6e5ae502 100644 --- a/js/plugins/anthropic/tests/stable_runner_test.ts +++ b/js/plugins/anthropic/tests/stable_runner_test.ts @@ -762,19 +762,18 @@ describe('fromAnthropicContentBlockChunk', () => { }); } - it('should throw for unsupported tool input streaming deltas', () => { - assert.throws( - () => - testRunner.fromAnthropicContentBlockChunk({ - index: 0, - type: 'content_block_delta', - delta: { - type: 'input_json_delta', - partial_json: '{"foo":', - }, - } as MessageStreamEvent), - /Anthropic streaming tool input \(input_json_delta\) is not yet supported/ - ); + it('should handle tool input streaming deltas', () => { + const actualOutput = testRunner.fromAnthropicContentBlockChunk({ + index: 0, + type: 'content_block_delta', + delta: { + type: 'input_json_delta', + partial_json: '{"foo":', + }, + } as MessageStreamEvent); + assert.deepStrictEqual(actualOutput, { + inputJson: '{"foo":', + }); }); }); diff --git a/js/testapps/anthropic/src/stable/tools.ts b/js/testapps/anthropic/src/stable/tools.ts index 95c62c5870..6947971600 100644 --- a/js/testapps/anthropic/src/stable/tools.ts +++ b/js/testapps/anthropic/src/stable/tools.ts @@ -54,3 +54,24 @@ ai.defineFlow( return text; } ); + +ai.defineFlow( + 'anthropic-stable-tools-stream', + async ({ place }: { place: string }, { sendChunk }) => { + const { stream } = ai.generateStream({ + model: anthropic.model('claude-sonnet-4-5'), + tools: [getWeather], + prompt: `What is the weather in ${place}?`, + }); + + let response = ''; + for await (const chunk of stream) { + response += chunk.text ?? ''; + if (chunk.text) { + sendChunk(chunk.text); + } + } + + return response; + } +); From 2734dfd24ae0bcad4caa6e26496da503fdefe43f Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Mon, 12 Jan 2026 09:28:29 +0000 Subject: [PATCH 7/9] fix(anthropic): tests --- js/plugins/anthropic/tests/stable_runner_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/plugins/anthropic/tests/stable_runner_test.ts b/js/plugins/anthropic/tests/stable_runner_test.ts index cf6e5ae502..478a9c1015 100644 --- a/js/plugins/anthropic/tests/stable_runner_test.ts +++ b/js/plugins/anthropic/tests/stable_runner_test.ts @@ -772,7 +772,7 @@ describe('fromAnthropicContentBlockChunk', () => { }, } as MessageStreamEvent); assert.deepStrictEqual(actualOutput, { - inputJson: '{"foo":', + data: '{"foo":', }); }); }); From d84936fcc558f329ea8d8e9a0b30b14f13e98f2f Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Mon, 12 Jan 2026 15:04:55 +0000 Subject: [PATCH 8/9] chore(testapps/anthropic): update readme --- js/testapps/anthropic/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/testapps/anthropic/README.md b/js/testapps/anthropic/README.md index b209958e68..82f9182b69 100644 --- a/js/testapps/anthropic/README.md +++ b/js/testapps/anthropic/README.md @@ -72,4 +72,8 @@ Each source file defines flows that can be invoked from the Dev UI or the Genkit - `stable-vision-base64` – Analyze an image from a local file (base64 encoded) - `stable-vision-conversation` – Multi-turn conversation about an image +### Tools +- `anthropic-stable-tools` – Get the weather in a given location using a Genkit tool +- `anthropic-stable-tools-stream` – Streaming response with Genkit tools + Example: `genkit flow:run anthropic-stable-hello` From fb88dc712a3bc2d4fb6605a413b408f977f569e3 Mon Sep 17 00:00:00 2001 From: Corie Watson Date: Mon, 19 Jan 2026 10:36:29 +0000 Subject: [PATCH 9/9] chore: format --- js/plugins/anthropic/src/runner/converters/shared.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/plugins/anthropic/src/runner/converters/shared.ts b/js/plugins/anthropic/src/runner/converters/shared.ts index 75f6c28ba1..ddb2fc0772 100644 --- a/js/plugins/anthropic/src/runner/converters/shared.ts +++ b/js/plugins/anthropic/src/runner/converters/shared.ts @@ -101,4 +101,4 @@ export function textDeltaToPart(delta: { text: string }): Part { */ export function thinkingDeltaToPart(delta: { thinking: string }): Part { return { reasoning: delta.thinking }; -} \ No newline at end of file +}