| import { makeImageProcessor, type ImageProcessorOptions } from "../images"; |
| import { makeDocumentProcessor, type FileProcessorOptions } from "../document"; |
| import type { EndpointMessage } from "../endpoints"; |
| import type { MessageFile } from "$lib/types/Message"; |
| import type { |
| BetaImageBlockParam, |
| BetaMessageParam, |
| BetaBase64PDFBlock, |
| } from "@anthropic-ai/sdk/resources/beta/messages/messages.mjs"; |
| import type { ToolResult } from "$lib/types/Tool"; |
| import { downloadFile } from "$lib/server/files/downloadFile"; |
| import type { ObjectId } from "mongodb"; |
|
|
| export async function fileToImageBlock( |
| file: MessageFile, |
| opts: ImageProcessorOptions<"image/png" | "image/jpeg" | "image/webp"> |
| ): Promise<BetaImageBlockParam> { |
| const processor = makeImageProcessor(opts); |
|
|
| const { image, mime } = await processor(file); |
|
|
| return { |
| type: "image", |
| source: { |
| type: "base64", |
| media_type: mime, |
| data: image.toString("base64"), |
| }, |
| }; |
| } |
|
|
| export async function fileToDocumentBlock( |
| file: MessageFile, |
| opts: FileProcessorOptions<"application/pdf"> |
| ): Promise<BetaBase64PDFBlock> { |
| const processor = makeDocumentProcessor(opts); |
| const { file: document, mime } = await processor(file); |
|
|
| return { |
| type: "document", |
| source: { |
| type: "base64", |
| media_type: mime, |
| data: document.toString("base64"), |
| }, |
| }; |
| } |
|
|
| type NonSystemMessage = EndpointMessage & { from: "user" | "assistant" }; |
| export async function endpointMessagesToAnthropicMessages( |
| messages: EndpointMessage[], |
| multimodal: { |
| image: ImageProcessorOptions<"image/png" | "image/jpeg" | "image/webp">; |
| document?: FileProcessorOptions<"application/pdf">; |
| }, |
| conversationId?: ObjectId | undefined |
| ): Promise<BetaMessageParam[]> { |
| return await Promise.all( |
| messages |
| .filter((message): message is NonSystemMessage => message.from !== "system") |
| .map<Promise<BetaMessageParam>>(async (message) => { |
| return { |
| role: message.from, |
| content: [ |
| ...(message.from === "user" |
| ? await Promise.all( |
| (message.files ?? []).map(async (file) => { |
| if (file.type === "hash" && conversationId) { |
| file = await downloadFile(file.value, conversationId); |
| } |
|
|
| if (file.mime.startsWith("image/")) { |
| return fileToImageBlock(file, multimodal.image); |
| } else if (file.mime === "application/pdf" && multimodal.document) { |
| return fileToDocumentBlock(file, multimodal.document); |
| } else { |
| throw new Error(`Unsupported file type: ${file.mime}`); |
| } |
| }) |
| ) |
| : []), |
| { type: "text", text: message.content }, |
| ], |
| }; |
| }) |
| ); |
| } |
|
|
| export function addToolResults( |
| messages: BetaMessageParam[], |
| toolResults: ToolResult[] |
| ): BetaMessageParam[] { |
| const id = crypto.randomUUID(); |
| if (toolResults.length === 0) { |
| return messages; |
| } |
| return [ |
| ...messages, |
| { |
| role: "assistant", |
| content: toolResults.map((result, index) => ({ |
| type: "tool_use", |
| id: `tool_${index}_${id}`, |
| name: result.call.name, |
| input: result.call.parameters, |
| })), |
| }, |
| { |
| role: "user", |
| content: toolResults.map((result, index) => ({ |
| type: "tool_result", |
| tool_use_id: `tool_${index}_${id}`, |
| is_error: result.status === "error", |
| content: JSON.stringify( |
| result.status === "error" ? result.message : "outputs" in result ? result.outputs : "" |
| ), |
| })), |
| }, |
| ]; |
| } |
|
|