@genkit-ai/anthropic
Version:
Genkit AI framework plugin for Anthropic APIs.
1 lines • 22 kB
Source Map (JSON)
{"version":3,"sources":["../../src/runner/base.ts"],"sourcesContent":["/**\n * Copyright 2025 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Anthropic } from '@anthropic-ai/sdk';\nimport type { DocumentBlockParam } from '@anthropic-ai/sdk/resources/messages';\nimport type {\n GenerateRequest,\n GenerateResponseChunkData,\n GenerateResponseData,\n MessageData,\n Part,\n Role,\n} from 'genkit';\nimport { Message as GenkitMessage } from 'genkit';\nimport type { ToolDefinition } from 'genkit/model';\n\nimport {\n AnthropicConfigSchema,\n Media,\n MediaSchema,\n MediaType,\n MediaTypeSchema,\n type ClaudeRunnerParams,\n type ThinkingConfig,\n} from '../types.mjs';\n\nimport {\n RunnerContentBlockParam,\n RunnerMessage,\n RunnerMessageParam,\n RunnerRequestBody,\n RunnerStream,\n RunnerStreamEvent,\n RunnerStreamingRequestBody,\n RunnerToolResponseContent,\n RunnerTypes,\n} from './types.mjs';\n\n/**\n * Shared runner logic for Anthropic SDK integrations.\n *\n * Concrete subclasses pass in their SDK-specific type bundle via `RunnerTypes`,\n * letting this base class handle message/tool translation once for both the\n * stable and beta APIs that share the same conceptual surface.\n */\nexport abstract class BaseRunner<ApiTypes extends RunnerTypes> {\n protected name: string;\n protected client: Anthropic;\n\n /**\n * Default maximum output tokens for Claude models when not specified in the request.\n */\n protected readonly DEFAULT_MAX_OUTPUT_TOKENS = 4096;\n\n constructor(params: ClaudeRunnerParams) {\n this.name = params.name;\n this.client = params.client;\n }\n\n /**\n * Converts a Genkit role to the corresponding Anthropic role.\n */\n protected toAnthropicRole(\n role: Role,\n toolMessageType?: 'tool_use' | 'tool_result'\n ): 'user' | 'assistant' {\n if (role === 'user') {\n return 'user';\n }\n if (role === 'model') {\n return 'assistant';\n }\n if (role === 'tool') {\n return toolMessageType === 'tool_use' ? 'assistant' : 'user';\n }\n throw new Error(`Unsupported genkit role: ${role}`);\n }\n\n protected isMediaType(value: string): value is MediaType {\n return MediaTypeSchema.safeParse(value).success;\n }\n\n protected isMediaObject(obj: unknown): obj is Media {\n return MediaSchema.safeParse(obj).success;\n }\n\n /**\n * Checks if a URL is a data URL (starts with 'data:').\n */\n protected isDataUrl(url: string): boolean {\n return url.startsWith('data:');\n }\n\n protected extractDataFromBase64Url(\n url: string\n ): { data: string; contentType: string } | null {\n const match = url.match(/^data:([^;]+);base64,(.+)$/);\n return (\n match && {\n contentType: match[1],\n data: match[2],\n }\n );\n }\n\n /**\n * Both the stable and beta Anthropic SDKs accept the same JSON shape for PDF\n * document sources (either `type: 'base64'` with a base64 payload or `type: 'url'`\n * with a public URL). Even though the return type references the stable SDK\n * union, TypeScript’s structural typing lets the beta runner reuse this helper.\n */\n protected toPdfDocumentSource(media: Media): DocumentBlockParam['source'] {\n if (media.contentType !== 'application/pdf') {\n throw new Error(\n `PDF contentType mismatch: expected application/pdf, got ${media.contentType}`\n );\n }\n const url = media.url;\n if (this.isDataUrl(url)) {\n const extracted = this.extractDataFromBase64Url(url);\n if (!extracted) {\n throw new Error(\n `Invalid PDF data URL format: ${url.substring(0, 50)}...`\n );\n }\n const { data, contentType } = extracted;\n if (contentType !== 'application/pdf') {\n throw new Error(\n `PDF contentType mismatch: expected application/pdf, got ${contentType}`\n );\n }\n return {\n type: 'base64',\n media_type: 'application/pdf',\n data,\n };\n }\n return {\n type: 'url',\n url,\n };\n }\n\n /**\n * Normalizes Genkit `Media` into either a base64 payload or a remote URL\n * accepted by the Anthropic SDK. Anthropic supports both `data:` URLs (which\n * we forward as base64) and remote `https` URLs without additional handling.\n */\n protected toImageSource(\n media: Media\n ):\n | { kind: 'base64'; data: string; mediaType: MediaType }\n | { kind: 'url'; url: string } {\n if (this.isDataUrl(media.url)) {\n const extracted = this.extractDataFromBase64Url(media.url);\n const { data, contentType } = extracted ?? {};\n if (!data || !contentType) {\n throw new Error(\n `Invalid genkit part media provided to toAnthropicMessageContent: ${JSON.stringify(\n media\n )}.`\n );\n }\n\n const resolvedMediaType = contentType;\n if (!resolvedMediaType) {\n throw new Error('Media type is required but was not provided');\n }\n if (!this.isMediaType(resolvedMediaType)) {\n // Provide helpful error message for text files\n if (resolvedMediaType === 'text/plain') {\n throw new Error(\n `Unsupported media type: ${resolvedMediaType}. Text files should be sent as text content in the message, not as media. For example, use { text: '...' } instead of { media: { url: '...', contentType: 'text/plain' } }`\n );\n }\n throw new Error(`Unsupported media type: ${resolvedMediaType}`);\n }\n return {\n kind: 'base64',\n data,\n mediaType: resolvedMediaType,\n };\n }\n\n if (!media.url) {\n throw new Error('Media url is required but was not provided');\n }\n\n // For non-data URLs, use the provided contentType or default to a generic type\n // Note: Anthropic will validate the actual content when fetching from URL\n if (media.contentType) {\n if (!this.isMediaType(media.contentType)) {\n // Provide helpful error message for text files\n if (media.contentType === 'text/plain') {\n throw new Error(\n `Unsupported media type: ${media.contentType}. Text files should be sent as text content in the message, not as media. For example, use { text: '...' } instead of { media: { url: '...', contentType: 'text/plain' } }`\n );\n }\n throw new Error(`Unsupported media type: ${media.contentType}`);\n }\n }\n\n return {\n kind: 'url',\n url: media.url,\n };\n }\n\n /**\n * Converts tool response output to the appropriate Anthropic content format.\n * Handles Media objects, data URLs, strings, and other outputs.\n */\n protected toAnthropicToolResponseContent(\n part: Part\n ): RunnerToolResponseContent<ApiTypes> {\n const output = part.toolResponse?.output ?? {};\n\n // Handle Media objects (images returned by tools)\n if (this.isMediaObject(output)) {\n const { data, contentType } =\n this.extractDataFromBase64Url(output.url) ?? {};\n if (data && contentType) {\n if (!this.isMediaType(contentType)) {\n // Provide helpful error message for text files\n if (contentType === 'text/plain') {\n throw new Error(\n `Unsupported media type: ${contentType}. Text files should be sent as text content, not as media.`\n );\n }\n throw new Error(`Unsupported media type: ${contentType}`);\n }\n return {\n type: 'image',\n source: {\n type: 'base64',\n data,\n media_type: contentType,\n },\n };\n }\n }\n\n // Handle string outputs - check if it's a data URL\n if (typeof output === 'string') {\n // Check if string is a data URL (e.g., \"data:image/gif;base64,...\")\n if (this.isDataUrl(output)) {\n const { data, contentType } =\n this.extractDataFromBase64Url(output) ?? {};\n if (data && contentType) {\n if (!this.isMediaType(contentType)) {\n // Provide helpful error message for text files\n if (contentType === 'text/plain') {\n throw new Error(\n `Unsupported media type: ${contentType}. Text files should be sent as text content, not as media.`\n );\n }\n throw new Error(`Unsupported media type: ${contentType}`);\n }\n return {\n type: 'image',\n source: {\n type: 'base64',\n data,\n media_type: contentType,\n },\n };\n }\n }\n // Regular string output\n return {\n type: 'text',\n text: output,\n };\n }\n\n // Handle other outputs by stringifying\n return {\n type: 'text',\n text: JSON.stringify(output),\n };\n }\n\n protected getThinkingSignature(part: Part): string | undefined {\n const metadata = part.metadata as Record<string, unknown> | undefined;\n return typeof metadata?.thoughtSignature === 'string'\n ? metadata.thoughtSignature\n : undefined;\n }\n\n protected getRedactedThinkingData(part: Part): string | undefined {\n const custom = part.custom as Record<string, unknown> | undefined;\n const redacted = custom?.redactedThinking;\n return typeof redacted === 'string' ? redacted : undefined;\n }\n\n protected toAnthropicThinkingConfig(\n config: ThinkingConfig | undefined\n ):\n | { type: 'enabled'; budget_tokens: number }\n | { type: 'disabled' }\n | { type: 'adaptive'; display?: 'summarized' | 'omitted' }\n | undefined {\n if (!config) return undefined;\n\n const { enabled, budgetTokens, adaptive, display } = config;\n\n if (adaptive === true) {\n return {\n type: 'adaptive',\n ...(display !== undefined && { display }),\n };\n }\n\n if (enabled === true) {\n if (budgetTokens === undefined) {\n throw new Error('budgetTokens is required when thinking is enabled');\n }\n return { type: 'enabled', budget_tokens: budgetTokens };\n }\n\n if (enabled === false) {\n return { type: 'disabled' };\n }\n\n if (budgetTokens !== undefined) {\n return { type: 'enabled', budget_tokens: budgetTokens };\n }\n\n return undefined;\n }\n\n /**\n * Converts a Genkit Part to the corresponding Anthropic content block.\n * Each runner implements this to return its specific API type.\n */\n protected abstract toAnthropicMessageContent(\n part: Part\n ): RunnerContentBlockParam<ApiTypes>;\n\n /**\n * Converts Genkit messages to Anthropic format.\n * Extracts system message and converts remaining messages using the runner's\n * toAnthropicMessageContent implementation.\n */\n protected toAnthropicMessages(messages: MessageData[]): {\n system?: RunnerContentBlockParam<ApiTypes>[];\n messages: RunnerMessageParam<ApiTypes>[];\n } {\n let system: RunnerContentBlockParam<ApiTypes>[] | undefined;\n\n if (messages[0]?.role === 'system') {\n const systemMessage = messages[0];\n messages = messages.slice(1);\n\n for (const part of systemMessage.content ?? []) {\n if (part.media || part.toolRequest || part.toolResponse) {\n throw new Error(\n 'System messages can only contain text content. Media, tool requests, and tool responses are not supported in system messages.'\n );\n }\n }\n\n system = systemMessage.content.map((part) =>\n this.toAnthropicMessageContent(part)\n );\n }\n\n const anthropicMsgs: RunnerMessageParam<ApiTypes>[] = [];\n\n for (const message of messages) {\n const msg = new GenkitMessage(message);\n\n // Detect tool message kind from Genkit Parts (no SDK typing needed)\n const hadToolUse = msg.content.some((p) => !!p.toolRequest);\n const hadToolResult = msg.content.some((p) => !!p.toolResponse);\n\n const toolMessageType = hadToolUse\n ? ('tool_use' as const)\n : hadToolResult\n ? ('tool_result' as const)\n : undefined;\n\n const role = this.toAnthropicRole(message.role, toolMessageType);\n\n const content = msg.content.map((part) =>\n this.toAnthropicMessageContent(part)\n );\n\n anthropicMsgs.push({ role, content });\n }\n\n return { system, messages: anthropicMsgs };\n }\n\n /**\n * Converts a Genkit ToolDefinition to an Anthropic Tool object.\n *\n * Anthropic requires `input_schema.type` to be present (usually `\"object\"`).\n * Genkit's `ToolDefinition` may have an empty schema (e.g. from `z.void()`)\n * which lacks the `type` field. We default to `{ type: \"object\" }` to\n * prevent 400 errors from the Anthropic API.\n */\n protected toAnthropicTool(tool: ToolDefinition): ApiTypes['Tool'] {\n const schema = tool.inputSchema || {};\n const inputSchema =\n 'type' in schema ? schema : { ...schema, type: 'object' as const };\n return {\n name: tool.name,\n description: tool.description,\n input_schema: inputSchema,\n } as ApiTypes['Tool'];\n }\n\n /**\n * Converts an Anthropic request to a non-streaming Anthropic API request body.\n * @param modelName The name of the Anthropic model to use.\n * @param request The Genkit GenerateRequest to convert.\n * @returns The converted Anthropic API non-streaming request body.\n * @throws An error if an unsupported output format is requested.\n */\n protected abstract toAnthropicRequestBody(\n modelName: string,\n request: GenerateRequest<typeof AnthropicConfigSchema>\n ): RunnerRequestBody<ApiTypes>;\n\n /**\n * Converts an Anthropic request to a streaming Anthropic API request body.\n * @param modelName The name of the Anthropic model to use.\n * @param request The Genkit GenerateRequest to convert.\n * @returns The converted Anthropic API streaming request body.\n * @throws An error if an unsupported output format is requested.\n */\n protected abstract toAnthropicStreamingRequestBody(\n modelName: string,\n request: GenerateRequest<typeof AnthropicConfigSchema>\n ): RunnerStreamingRequestBody<ApiTypes>;\n\n protected abstract createMessage(\n body: RunnerRequestBody<ApiTypes>,\n abortSignal: AbortSignal\n ): Promise<RunnerMessage<ApiTypes>>;\n\n protected abstract streamMessages(\n body: RunnerStreamingRequestBody<ApiTypes>,\n abortSignal: AbortSignal\n ): RunnerStream<ApiTypes>;\n\n protected abstract toGenkitResponse(\n message: RunnerMessage<ApiTypes>\n ): GenerateResponseData;\n\n protected abstract toGenkitPart(\n event: RunnerStreamEvent<ApiTypes>\n ): Part | undefined;\n\n public async run(\n request: GenerateRequest<typeof AnthropicConfigSchema>,\n options: {\n streamingRequested: boolean;\n sendChunk: (chunk: GenerateResponseChunkData) => void;\n abortSignal: AbortSignal;\n }\n ): Promise<GenerateResponseData> {\n const { streamingRequested, sendChunk, abortSignal } = options;\n\n if (streamingRequested) {\n const body = this.toAnthropicStreamingRequestBody(this.name, request);\n const stream = this.streamMessages(body, abortSignal);\n for await (const event of stream) {\n const part = this.toGenkitPart(event);\n if (part) {\n sendChunk({\n index: 0,\n content: [part],\n });\n }\n }\n const finalMessage = await stream.finalMessage();\n return this.toGenkitResponse(finalMessage);\n }\n\n const body = this.toAnthropicRequestBody(this.name, request);\n const response = await this.createMessage(body, abortSignal);\n return this.toGenkitResponse(response);\n }\n}\n"],"mappings":"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","names":["body"]}