Files

144 lines
5.3 KiB
JavaScript

// Probe every OpenCode Zen model for: (a) thinking/reasoning support, (b) vision (multimodal) support.
// Run with Node 20: "C:\Program Files\nodejs\node.exe" probe-zen.mjs
import { readFileSync, writeFileSync } from "node:fs";
const envText = readFileSync("C:/Users/12914/.mimo2codex/.env", "utf8");
const KEY = envText.match(/^MIMO_API_KEY=(.+)$/m)?.[1]?.trim();
const BASE = "https://opencode.ai/zen/go/v1";
if (!KEY) { console.error("no key"); process.exit(1); }
const TINY_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
async function call(body, timeoutMs = 90000) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), timeoutMs);
const started = Date.now();
try {
const res = await fetch(`${BASE}/chat/completions`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: ctrl.signal,
});
const ms = Date.now() - started;
let json = null, text = null;
const raw = await res.text();
try { json = JSON.parse(raw); } catch { text = raw.slice(0, 300); }
return { status: res.status, ms, json, text };
} catch (e) {
return { status: 0, ms: Date.now() - started, error: String(e?.message || e) };
} finally { clearTimeout(t); }
}
function extractReasoning(json) {
try {
const msg = json?.choices?.[0]?.message;
if (!msg) return null;
if (typeof msg.reasoning_content === "string" && msg.reasoning_content.trim()) return "reasoning_content";
if (typeof msg.reasoning === "string" && msg.reasoning.trim()) return "reasoning";
if (Array.isArray(msg.reasoning_content) && msg.reasoning_content.length) return "reasoning_content(array)";
if (typeof msg.content === "string" && /<think>/i.test(msg.content)) return "think_tags";
return null;
} catch { return null; }
}
async function probeBaseline(model) {
const r = await call({
model,
messages: [{ role: "user", content: "只回答一个数字:1+1=?" }],
max_tokens: 512,
stream: false,
});
return r;
}
// Try several common "enable thinking" conventions; any success that yields
// reasoning output means the model supports thinking.
async function probeThinking(model) {
const variants = [
{ name: "thinking+effort", body: { thinking: { type: "enabled" }, reasoning_effort: "high" } },
{ name: "effort-only", body: { reasoning_effort: "high" } },
{ name: "thinking-only", body: { thinking: { type: "enabled" } } },
];
const detail = [];
for (const v of variants) {
const r = await call({
model,
messages: [{ role: "user", content: "只回答一个数字:2+2=?" }],
max_tokens: 900,
stream: false,
...v.body,
});
const reason = r.json ? extractReasoning(r.json) : null;
detail.push({
variant: v.name,
status: r.status,
ms: r.ms,
reasoning: reason,
err: r.json?.error?.message?.slice(0, 200) ?? r.error ?? r.text ?? null,
contentPreview: r.json?.choices?.[0]?.message?.content?.slice(0, 80) ?? null,
});
if (reason) return { supported: true, via: v.name, reason, detail };
// If this variant 400'd on unknown field, try the next variant.
}
return { supported: false, via: null, reason: null, detail };
}
async function probeVision(model) {
const r = await call({
model,
messages: [{
role: "user",
content: [
{ type: "text", text: "这是什么颜色的图片?只回答颜色词。" },
{ type: "image_url", image_url: { url: `data:image/png;base64,${TINY_PNG}` } },
],
}],
max_tokens: 300,
stream: false,
});
const ok = r.status === 200 && r.json?.choices?.[0]?.message;
return {
supported: !!ok,
status: r.status,
ms: r.ms,
answer: ok ? String(r.json.choices[0].message.content).slice(0, 80) : null,
err: r.json?.error?.message?.slice(0, 200) ?? r.error ?? r.text ?? null,
};
}
async function main() {
// 1. list models
const lr = await fetch(`${BASE}/models`, { headers: { Authorization: `Bearer ${KEY}` } });
const lj = await lr.json();
const models = (lj?.data ?? []).map((m) => m.id).sort();
console.log(`models: ${models.length}`);
const results = {};
const CONC = 4;
const queue = [...models];
async function worker(id) {
while (queue.length) {
const m = queue.shift();
if (!m) break;
process.stdout.write(`[${m}] probing...\n`);
const base = await probeBaseline(m);
const think = await probeThinking(m);
const vision = await probeVision(m);
results[m] = {
baseline: { status: base.status, ms: base.ms, content: base.json?.choices?.[0]?.message?.content?.slice(0, 60) ?? null, err: base.json?.error?.message?.slice(0, 150) ?? base.error ?? null },
thinking: think,
vision,
};
process.stdout.write(`[${m}] think=${think.supported ? think.reason : false} vision=${vision.supported}\n`);
writeFileSync("C:/Users/12914/Desktop/vscode/.mimo2codex-audit/probe-results.json", JSON.stringify(results, null, 2));
}
}
await Promise.all(Array.from({ length: CONC }, (_, i) => worker(i)));
writeFileSync("C:/Users/12914/Desktop/vscode/.mimo2codex-audit/probe-results.json", JSON.stringify(results, null, 2));
console.log("DONE");
}
main();