mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 22:48:27 +00:00
fix(image): route MiniMax vision to VLM
This commit is contained in:
115
src/agents/minimax-vlm.ts
Normal file
115
src/agents/minimax-vlm.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user