Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 4 additions & 48 deletions js/plugins/anthropic/src/runner/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ import {
RunnerTypes,
} from './types.js';

const ANTHROPIC_THINKING_CUSTOM_KEY = 'anthropicThinking';

/**
* Shared runner logic for Anthropic SDK integrations.
*
Expand Down Expand Up @@ -298,35 +296,11 @@ export abstract class BaseRunner<ApiTypes extends RunnerTypes> {
};
}

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<string, unknown> | 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<string, unknown> | undefined;
return typeof metadata?.thoughtSignature === 'string'
? metadata.thoughtSignature
: undefined;
}

protected getRedactedThinkingData(part: Part): string | undefined {
Expand Down Expand Up @@ -363,24 +337,6 @@ export abstract class BaseRunner<ApiTypes extends RunnerTypes> {
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.
Expand Down
131 changes: 49 additions & 82 deletions js/plugins/anthropic/src/runner/beta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,22 @@ 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 {
betaServerToolUseBlockToPart,
unsupportedServerToolError,
} from './converters/beta.js';
import {
inputJsonDeltaError,
redactedThinkingBlockToPart,
textBlockToPart,
textDeltaToPart,
thinkingBlockToPart,
thinkingDeltaToPart,
toolUseBlockToPart,
webSearchToolResultBlockToPart,
} from './converters/shared.js';
import { RunnerTypes } from './types.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<string>([
'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',
]);

const BETA_APIS = [
// 'message-batches-2024-09-24',
// 'prompt-caching-2024-07-31',
Expand Down Expand Up @@ -118,9 +113,6 @@ 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.`;

interface BetaRunnerTypes extends RunnerTypes {
Message: BetaMessage;
Stream: BetaMessageStream;
Expand Down Expand Up @@ -167,7 +159,7 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
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 {
Expand Down Expand Up @@ -462,89 +454,64 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {

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;
}

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 '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 'text':
return textBlockToPart(contentBlock);

case 'web_search_tool_result':
return this.toWebSearchToolResultPart({
type: contentBlock.type,
toolUseId: contentBlock.tool_use_id,
content: contentBlock.content,
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 'text':
return { text: contentBlock.text };

case 'thinking':
return this.createThinkingPart(
contentBlock.thinking,
contentBlock.signature
);
return thinkingBlockToPart(contentBlock);

case 'redacted_thinking':
return { custom: { redactedThinking: contentBlock.data } };
return redactedThinkingBlockToPart(contentBlock);

case 'server_tool_use':
return betaServerToolUseBlockToPart(contentBlock);

case 'web_search_tool_result':
return webSearchToolResultBlockToPart(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));
}
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: '' };
}
Expand Down
54 changes: 54 additions & 0 deletions js/plugins/anthropic/src/runner/converters/beta.ts
Original file line number Diff line number Diff line change
@@ -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)}`,
metadata: {
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.`;
}
Loading