109 lines
3.1 KiB
TypeScript
109 lines
3.1 KiB
TypeScript
type MinimaxBaseResp = {
|
|
status_code?: number;
|
|
status_msg?: string;
|
|
};
|
|
|
|
function coerceApiHost(params: {
|
|
apiHost?: string;
|
|
modelBaseUrl?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): string {
|
|
const env = params.env ?? process.env;
|
|
const raw =
|
|
params.apiHost?.trim() ||
|
|
env.MINIMAX_API_HOST?.trim() ||
|
|
params.modelBaseUrl?.trim() ||
|
|
"https://api.minimax.io";
|
|
|
|
try {
|
|
const url = new URL(raw);
|
|
return url.origin;
|
|
} catch {}
|
|
|
|
try {
|
|
const url = new URL(`https://${raw}`);
|
|
return url.origin;
|
|
} catch {
|
|
return "https://api.minimax.io";
|
|
}
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
}
|
|
|
|
function pickString(rec: Record<string, unknown>, key: string): string {
|
|
const v = rec[key];
|
|
return typeof v === "string" ? v : "";
|
|
}
|
|
|
|
export async function minimaxUnderstandImage(params: {
|
|
apiKey: string;
|
|
prompt: string;
|
|
imageDataUrl: string;
|
|
apiHost?: string;
|
|
modelBaseUrl?: string;
|
|
}): Promise<string> {
|
|
const apiKey = params.apiKey.trim();
|
|
if (!apiKey) throw new Error("MiniMax VLM: apiKey required");
|
|
const prompt = params.prompt.trim();
|
|
if (!prompt) throw new Error("MiniMax VLM: prompt required");
|
|
const imageDataUrl = params.imageDataUrl.trim();
|
|
if (!imageDataUrl) throw new Error("MiniMax VLM: imageDataUrl required");
|
|
if (!/^data:image\/(png|jpeg|webp);base64,/i.test(imageDataUrl)) {
|
|
throw new Error("MiniMax VLM: imageDataUrl must be a base64 data:image/(png|jpeg|webp) URL");
|
|
}
|
|
|
|
const host = coerceApiHost({
|
|
apiHost: params.apiHost,
|
|
modelBaseUrl: params.modelBaseUrl,
|
|
});
|
|
const url = new URL("/v1/coding_plan/vlm", host).toString();
|
|
|
|
const res = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
"MM-API-Source": "Clawdbot",
|
|
},
|
|
body: JSON.stringify({
|
|
prompt,
|
|
image_url: imageDataUrl,
|
|
}),
|
|
});
|
|
|
|
const traceId = res.headers.get("Trace-Id") ?? "";
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => "");
|
|
const trace = traceId ? ` Trace-Id: ${traceId}` : "";
|
|
throw new Error(
|
|
`MiniMax VLM request failed (${res.status} ${res.statusText}).${trace}${
|
|
body ? ` Body: ${body.slice(0, 400)}` : ""
|
|
}`,
|
|
);
|
|
}
|
|
|
|
const json = (await res.json().catch(() => null)) as unknown;
|
|
if (!isRecord(json)) {
|
|
const trace = traceId ? ` Trace-Id: ${traceId}` : "";
|
|
throw new Error(`MiniMax VLM response was not JSON.${trace}`);
|
|
}
|
|
|
|
const baseResp = isRecord(json.base_resp) ? (json.base_resp as MinimaxBaseResp) : {};
|
|
const code = typeof baseResp.status_code === "number" ? baseResp.status_code : -1;
|
|
if (code !== 0) {
|
|
const msg = (baseResp.status_msg ?? "").trim();
|
|
const trace = traceId ? ` Trace-Id: ${traceId}` : "";
|
|
throw new Error(`MiniMax VLM API error (${code})${msg ? `: ${msg}` : ""}.${trace}`);
|
|
}
|
|
|
|
const content = pickString(json, "content").trim();
|
|
if (!content) {
|
|
const trace = traceId ? ` Trace-Id: ${traceId}` : "";
|
|
throw new Error(`MiniMax VLM returned no content.${trace}`);
|
|
}
|
|
|
|
return content;
|
|
}
|