// 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" && //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();