import type { UIMessage } from "ai"; import { MODEL_INPUTS, type ModelConfiguration } from "./model-providers.ts"; export const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024; export const MAX_IMAGE_BYTES = 3.5 * 1024 * 1024; export const MAX_ATTACHMENTS = 4; export const ATTACHMENT_ACCEPT = "image/png,image/jpeg,image/webp,application/pdf"; export const attachmentId = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; export interface Attachment { id: string; name: string; mediaType: "image/png" | "image/jpeg" | "image/webp" | "application/pdf"; size: number; } export function attachmentUrl(conversation: string, id: string) { return `/api/conversations/${conversation}/attachments/${id}`; } export function messageAttachments(message: UIMessage): Attachment[] { const value = (message.metadata as { attachments?: unknown } | undefined) ?.attachments; if (!Array.isArray(value)) return []; return value .slice(0, MAX_ATTACHMENTS) .filter( (file): file is Attachment => file && attachmentId.test(file.id) && typeof file.name === "string" && file.name.length <= 180 && ATTACHMENT_ACCEPT.split(",").includes(file.mediaType) && Number.isSafeInteger(file.size) && file.size > 0 && file.size <= MAX_ATTACHMENT_BYTES, ); } export function attachmentCapabilities(configuration: ModelConfiguration) { const models: Record = MODEL_INPUTS[configuration.provider]; const inputs = models[configuration.model] ?? []; return { images: inputs.includes("image"), pdf: inputs.includes("pdf"), }; } export function requestedAttachmentReader( messages: readonly UIMessage[], continuation = false, ) { if (continuation) return false; const latest = messages.findLast((m) => m.role === "user"); if (!latest) return false; if (messageAttachments(latest).length) return true; if (!messages.some((m) => m.role === "user" && messageAttachments(m).length)) return false; const text = latest.parts .filter((p) => p.type === "text") .map((p) => p.text) .join(" "); return ( /\b(?:the|this|that|attached|uploaded)\s+(?:(?:attached|uploaded)\s+)?(?:image|picture|photo|pdf|document|thumbnail|attachment)s?\b/i.test( text, ) || messages.some((m) => messageAttachments(m).some((file) => text.includes(file.name)), ) ); }