@genkit-ai/anthropic
Version:
Genkit AI framework plugin for Anthropic APIs.
329 lines • 11.2 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var base_exports = {};
__export(base_exports, {
BaseRunner: () => BaseRunner
});
module.exports = __toCommonJS(base_exports);
var import_genkit = require("genkit");
var import_types = require("../types.js");
class BaseRunner {
name;
client;
/**
* Default maximum output tokens for Claude models when not specified in the request.
*/
DEFAULT_MAX_OUTPUT_TOKENS = 4096;
constructor(params) {
this.name = params.name;
this.client = params.client;
}
/**
* Converts a Genkit role to the corresponding Anthropic role.
*/
toAnthropicRole(role, toolMessageType) {
if (role === "user") {
return "user";
}
if (role === "model") {
return "assistant";
}
if (role === "tool") {
return toolMessageType === "tool_use" ? "assistant" : "user";
}
throw new Error(`Unsupported genkit role: ${role}`);
}
isMediaType(value) {
return import_types.MediaTypeSchema.safeParse(value).success;
}
isMediaObject(obj) {
return import_types.MediaSchema.safeParse(obj).success;
}
/**
* Checks if a URL is a data URL (starts with 'data:').
*/
isDataUrl(url) {
return url.startsWith("data:");
}
extractDataFromBase64Url(url) {
const match = url.match(/^data:([^;]+);base64,(.+)$/);
return match && {
contentType: match[1],
data: match[2]
};
}
/**
* Both the stable and beta Anthropic SDKs accept the same JSON shape for PDF
* document sources (either `type: 'base64'` with a base64 payload or `type: 'url'`
* with a public URL). Even though the return type references the stable SDK
* union, TypeScript’s structural typing lets the beta runner reuse this helper.
*/
toPdfDocumentSource(media) {
if (media.contentType !== "application/pdf") {
throw new Error(
`PDF contentType mismatch: expected application/pdf, got ${media.contentType}`
);
}
const url = media.url;
if (this.isDataUrl(url)) {
const extracted = this.extractDataFromBase64Url(url);
if (!extracted) {
throw new Error(
`Invalid PDF data URL format: ${url.substring(0, 50)}...`
);
}
const { data, contentType } = extracted;
if (contentType !== "application/pdf") {
throw new Error(
`PDF contentType mismatch: expected application/pdf, got ${contentType}`
);
}
return {
type: "base64",
media_type: "application/pdf",
data
};
}
return {
type: "url",
url
};
}
/**
* Normalizes Genkit `Media` into either a base64 payload or a remote URL
* accepted by the Anthropic SDK. Anthropic supports both `data:` URLs (which
* we forward as base64) and remote `https` URLs without additional handling.
*/
toImageSource(media) {
if (this.isDataUrl(media.url)) {
const extracted = this.extractDataFromBase64Url(media.url);
const { data, contentType } = extracted ?? {};
if (!data || !contentType) {
throw new Error(
`Invalid genkit part media provided to toAnthropicMessageContent: ${JSON.stringify(
media
)}.`
);
}
const resolvedMediaType = contentType;
if (!resolvedMediaType) {
throw new Error("Media type is required but was not provided");
}
if (!this.isMediaType(resolvedMediaType)) {
if (resolvedMediaType === "text/plain") {
throw new Error(
`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' } }`
);
}
throw new Error(`Unsupported media type: ${resolvedMediaType}`);
}
return {
kind: "base64",
data,
mediaType: resolvedMediaType
};
}
if (!media.url) {
throw new Error("Media url is required but was not provided");
}
if (media.contentType) {
if (!this.isMediaType(media.contentType)) {
if (media.contentType === "text/plain") {
throw new Error(
`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' } }`
);
}
throw new Error(`Unsupported media type: ${media.contentType}`);
}
}
return {
kind: "url",
url: media.url
};
}
/**
* Converts tool response output to the appropriate Anthropic content format.
* Handles Media objects, data URLs, strings, and other outputs.
*/
toAnthropicToolResponseContent(part) {
const output = part.toolResponse?.output ?? {};
if (this.isMediaObject(output)) {
const { data, contentType } = this.extractDataFromBase64Url(output.url) ?? {};
if (data && contentType) {
if (!this.isMediaType(contentType)) {
if (contentType === "text/plain") {
throw new Error(
`Unsupported media type: ${contentType}. Text files should be sent as text content, not as media.`
);
}
throw new Error(`Unsupported media type: ${contentType}`);
}
return {
type: "image",
source: {
type: "base64",
data,
media_type: contentType
}
};
}
}
if (typeof output === "string") {
if (this.isDataUrl(output)) {
const { data, contentType } = this.extractDataFromBase64Url(output) ?? {};
if (data && contentType) {
if (!this.isMediaType(contentType)) {
if (contentType === "text/plain") {
throw new Error(
`Unsupported media type: ${contentType}. Text files should be sent as text content, not as media.`
);
}
throw new Error(`Unsupported media type: ${contentType}`);
}
return {
type: "image",
source: {
type: "base64",
data,
media_type: contentType
}
};
}
}
return {
type: "text",
text: output
};
}
return {
type: "text",
text: JSON.stringify(output)
};
}
getThinkingSignature(part) {
const metadata = part.metadata;
return typeof metadata?.thoughtSignature === "string" ? metadata.thoughtSignature : void 0;
}
getRedactedThinkingData(part) {
const custom = part.custom;
const redacted = custom?.redactedThinking;
return typeof redacted === "string" ? redacted : void 0;
}
toAnthropicThinkingConfig(config) {
if (!config) return void 0;
const { enabled, budgetTokens, adaptive, display } = config;
if (adaptive === true) {
return {
type: "adaptive",
...display !== void 0 && { display }
};
}
if (enabled === true) {
if (budgetTokens === void 0) {
throw new Error("budgetTokens is required when thinking is enabled");
}
return { type: "enabled", budget_tokens: budgetTokens };
}
if (enabled === false) {
return { type: "disabled" };
}
if (budgetTokens !== void 0) {
return { type: "enabled", budget_tokens: budgetTokens };
}
return void 0;
}
/**
* Converts Genkit messages to Anthropic format.
* Extracts system message and converts remaining messages using the runner's
* toAnthropicMessageContent implementation.
*/
toAnthropicMessages(messages) {
let system;
if (messages[0]?.role === "system") {
const systemMessage = messages[0];
messages = messages.slice(1);
for (const part of systemMessage.content ?? []) {
if (part.media || part.toolRequest || part.toolResponse) {
throw new Error(
"System messages can only contain text content. Media, tool requests, and tool responses are not supported in system messages."
);
}
}
system = systemMessage.content.map(
(part) => this.toAnthropicMessageContent(part)
);
}
const anthropicMsgs = [];
for (const message of messages) {
const msg = new import_genkit.Message(message);
const hadToolUse = msg.content.some((p) => !!p.toolRequest);
const hadToolResult = msg.content.some((p) => !!p.toolResponse);
const toolMessageType = hadToolUse ? "tool_use" : hadToolResult ? "tool_result" : void 0;
const role = this.toAnthropicRole(message.role, toolMessageType);
const content = msg.content.map(
(part) => this.toAnthropicMessageContent(part)
);
anthropicMsgs.push({ role, content });
}
return { system, messages: anthropicMsgs };
}
/**
* Converts a Genkit ToolDefinition to an Anthropic Tool object.
*
* Anthropic requires `input_schema.type` to be present (usually `"object"`).
* Genkit's `ToolDefinition` may have an empty schema (e.g. from `z.void()`)
* which lacks the `type` field. We default to `{ type: "object" }` to
* prevent 400 errors from the Anthropic API.
*/
toAnthropicTool(tool) {
const schema = tool.inputSchema || {};
const inputSchema = "type" in schema ? schema : { ...schema, type: "object" };
return {
name: tool.name,
description: tool.description,
input_schema: inputSchema
};
}
async run(request, options) {
const { streamingRequested, sendChunk, abortSignal } = options;
if (streamingRequested) {
const body2 = this.toAnthropicStreamingRequestBody(this.name, request);
const stream = this.streamMessages(body2, abortSignal);
for await (const event of stream) {
const part = this.toGenkitPart(event);
if (part) {
sendChunk({
index: 0,
content: [part]
});
}
}
const finalMessage = await stream.finalMessage();
return this.toGenkitResponse(finalMessage);
}
const body = this.toAnthropicRequestBody(this.name, request);
const response = await this.createMessage(body, abortSignal);
return this.toGenkitResponse(response);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BaseRunner
});
//# sourceMappingURL=base.js.map