Roo-Code/src/api/transform/bedrock-converse-format.ts
2025-05-30 14:54:48 -07:00

194 lines
5.2 KiB
TypeScript

import { Anthropic } from "@anthropic-ai/sdk"
import { ConversationRole, Message, ContentBlock } from "@aws-sdk/client-bedrock-runtime"
interface BedrockMessageContent {
type: "text" | "image" | "video" | "tool_use" | "tool_result"
text?: string
source?: {
type: "base64"
data: string | Uint8Array // string for Anthropic, Uint8Array for Bedrock
media_type: "image/jpeg" | "image/png" | "image/gif" | "image/webp"
}
// Video specific fields
format?: string
s3Location?: {
uri: string
bucketOwner?: string
}
// Tool use and result fields
toolUseId?: string
name?: string
input?: any
output?: any // Used for tool_result type
}
/**
* Convert Anthropic messages to Bedrock Converse format
*/
export function convertToBedrockConverseMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
return anthropicMessages.map((anthropicMessage) => {
// Map Anthropic roles to Bedrock roles
const role: ConversationRole = anthropicMessage.role === "assistant" ? "assistant" : "user"
if (typeof anthropicMessage.content === "string") {
return {
role,
content: [
{
text: anthropicMessage.content,
},
] as ContentBlock[],
}
}
// Process complex content types
const content = anthropicMessage.content.map((block) => {
const messageBlock = block as BedrockMessageContent & {
id?: string
tool_use_id?: string
content?: Array<{ type: string; text: string }>
output?: string | Array<{ type: string; text: string }>
}
if (messageBlock.type === "text") {
return {
text: messageBlock.text || "",
} as ContentBlock
}
if (messageBlock.type === "image" && messageBlock.source) {
// Convert base64 string to byte array if needed
let byteArray: Uint8Array
if (typeof messageBlock.source.data === "string") {
const binaryString = atob(messageBlock.source.data)
byteArray = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
byteArray[i] = binaryString.charCodeAt(i)
}
} else {
byteArray = messageBlock.source.data
}
// Extract format from media_type (e.g., "image/jpeg" -> "jpeg")
const format = messageBlock.source.media_type.split("/")[1]
if (!["png", "jpeg", "gif", "webp"].includes(format)) {
throw new Error(`Unsupported image format: ${format}`)
}
return {
image: {
format: format as "png" | "jpeg" | "gif" | "webp",
source: {
bytes: byteArray,
},
},
} as ContentBlock
}
if (messageBlock.type === "tool_use") {
// Convert tool use to XML format
const toolParams = Object.entries(messageBlock.input || {})
.map(([key, value]) => `<${key}>\n${value}\n</${key}>`)
.join("\n")
return {
toolUse: {
toolUseId: messageBlock.id || "",
name: messageBlock.name || "",
input: `<${messageBlock.name}>\n${toolParams}\n</${messageBlock.name}>`,
},
} as ContentBlock
}
if (messageBlock.type === "tool_result") {
// First try to use content if available
if (messageBlock.content && Array.isArray(messageBlock.content)) {
return {
toolResult: {
toolUseId: messageBlock.tool_use_id || "",
content: messageBlock.content.map((item) => ({
text: item.text,
})),
status: "success",
},
} as ContentBlock
}
// Fall back to output handling if content is not available
if (messageBlock.output && typeof messageBlock.output === "string") {
return {
toolResult: {
toolUseId: messageBlock.tool_use_id || "",
content: [
{
text: messageBlock.output,
},
],
status: "success",
},
} as ContentBlock
}
// Handle array of content blocks if output is an array
if (Array.isArray(messageBlock.output)) {
return {
toolResult: {
toolUseId: messageBlock.tool_use_id || "",
content: messageBlock.output.map((part) => {
if (typeof part === "object" && "text" in part) {
return { text: part.text }
}
// Skip images in tool results as they're handled separately
if (typeof part === "object" && "type" in part && part.type === "image") {
return { text: "(see following message for image)" }
}
return { text: String(part) }
}),
status: "success",
},
} as ContentBlock
}
// Default case
return {
toolResult: {
toolUseId: messageBlock.tool_use_id || "",
content: [
{
text: String(messageBlock.output || ""),
},
],
status: "success",
},
} as ContentBlock
}
if (messageBlock.type === "video") {
const videoContent = messageBlock.s3Location
? {
s3Location: {
uri: messageBlock.s3Location.uri,
bucketOwner: messageBlock.s3Location.bucketOwner,
},
}
: messageBlock.source
return {
video: {
format: "mp4", // Default to mp4, adjust based on actual format if needed
source: videoContent,
},
} as ContentBlock
}
// Default case for unknown block types
return {
text: "[Unknown Block Type]",
} as ContentBlock
})
return {
role,
content,
}
})
}