import type { AssistantMessage } from "@mariozechner/pi-ai"; import type { ClawdbotConfig } from "../../config/config.js"; import { extractAssistantText } from "../pi-embedded-utils.js"; export type ImageModelConfig = { primary?: string; fallbacks?: string[] }; export function decodeDataUrl(dataUrl: string): { buffer: Buffer; mimeType: string; kind: "image"; } { const trimmed = dataUrl.trim(); const match = /^data:([^;,]+);base64,([a-z0-9+/=\r\n]+)$/i.exec(trimmed); if (!match) throw new Error("Invalid data URL (expected base64 data: URL)."); const mimeType = (match[1] ?? "").trim().toLowerCase(); if (!mimeType.startsWith("image/")) { throw new Error(`Unsupported data URL type: ${mimeType || "unknown"}`); } const b64 = (match[2] ?? "").trim(); const buffer = Buffer.from(b64, "base64"); if (buffer.length === 0) { throw new Error("Invalid data URL: empty payload."); } return { buffer, mimeType, kind: "image" }; } export function coerceImageAssistantText(params: { message: AssistantMessage; provider: string; model: string; }): string { const stop = params.message.stopReason; const errorMessage = params.message.errorMessage?.trim(); if (stop === "error" || stop === "aborted") { throw new Error( errorMessage ? `Image model failed (${params.provider}/${params.model}): ${errorMessage}` : `Image model failed (${params.provider}/${params.model})`, ); } if (errorMessage) { throw new Error(`Image model failed (${params.provider}/${params.model}): ${errorMessage}`); } const text = extractAssistantText(params.message); if (text.trim()) return text.trim(); throw new Error(`Image model returned no text (${params.provider}/${params.model}).`); } export function coerceImageModelConfig(cfg?: ClawdbotConfig): ImageModelConfig { const imageModel = cfg?.agents?.defaults?.imageModel as | { primary?: string; fallbacks?: string[] } | string | undefined; const primary = typeof imageModel === "string" ? imageModel.trim() : imageModel?.primary; const fallbacks = typeof imageModel === "object" ? (imageModel?.fallbacks ?? []) : []; return { ...(primary?.trim() ? { primary: primary.trim() } : {}), ...(fallbacks.length > 0 ? { fallbacks } : {}), }; } export function resolveProviderVisionModelFromConfig(params: { cfg?: ClawdbotConfig; provider: string; }): string | null { const providerCfg = params.cfg?.models?.providers?.[params.provider] as unknown as | { models?: Array<{ id?: string; input?: string[] }> } | undefined; const models = providerCfg?.models ?? []; const preferMinimaxVl = params.provider === "minimax" ? models.find( (m) => (m?.id ?? "").trim() === "MiniMax-VL-01" && Array.isArray(m?.input) && m.input.includes("image"), ) : null; const picked = preferMinimaxVl ?? models.find((m) => Boolean((m?.id ?? "").trim()) && m.input?.includes("image")); const id = (picked?.id ?? "").trim(); return id ? `${params.provider}/${id}` : null; }