/** * Model availability test script. * Tests all chat_capable models against their provider APIs. * Uses a 1-token prompt to minimize cost. */ const OPENAI_KEY = process.env.OPENAI_KEY const ANTHROPIC_KEY = process.env.ANTHROPIC_KEY const GEMINI_KEY = process.env.GEMINI_KEY if (!OPENAI_KEY || !ANTHROPIC_KEY || !GEMINI_KEY) { console.error('Missing keys. Set OPENAI_KEY, ANTHROPIC_KEY, GEMINI_KEY env vars.') process.exit(1) } // ── No-temperature models (same logic as playground-runner.ts) ──────────────── const NO_TEMP = new Set(['chat-latest', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5.5']) function skipTemp(model) { return /^o\d/.test(model) || NO_TEMP.has(model) } // ── Models to test ──────────────────────────────────────────────────────────── const OPENAI_MODELS = [ 'gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-5', 'gpt-5.1', 'gpt-5.2', 'gpt-5-mini', 'gpt-5-nano', 'chat-latest', 'o4-mini', 'o3', 'o3-mini', 'o1', 'gpt-4o', 'gpt-4o-mini', 'gpt-4o-2024-05-13', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4-turbo', 'gpt-4-turbo-2024-04-09', 'gpt-4', 'gpt-4-0613', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0125', 'gpt-3.5-turbo-1106', ] const ANTHROPIC_MODELS = [ 'claude-opus-4-7', 'claude-opus-4-6', 'claude-opus-4-5', 'claude-opus-4-5-20251101', 'claude-opus-4-1', 'claude-opus-4-1-20250805', 'claude-opus-4', 'claude-opus-4-0', 'claude-opus-4-20250514', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-sonnet-4-5-20250929', 'claude-sonnet-4', 'claude-sonnet-4-0', 'claude-sonnet-4-20250514', 'claude-haiku-4-5', 'claude-haiku-4-5-20251001', 'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229', 'claude-3-haiku-20240307', ] const GEMINI_MODELS = [ 'gemini-3.5-flash', 'gemini-3.1-pro-preview', 'gemini-3.1-pro-preview-customtools', 'gemini-3.1-flash-lite', 'gemini-3.1-flash-lite-preview', 'gemini-3-flash-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-flash-lite-preview-09-2025', 'gemini-2.5-computer-use-preview-10-2025', 'gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-pro', 'gemini-1.5-flash', ] // ── Test functions ──────────────────────────────────────────────────────────── async function testOpenAI(model) { const body = { model, messages: [{ role: 'user', content: 'Hi' }], max_completion_tokens: 5, ...(skipTemp(model) ? {} : { temperature: 0.7 }), } const res = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${OPENAI_KEY}` }, body: JSON.stringify(body), }) if (!res.ok) { const text = await res.text().catch(() => '') let msg = text try { msg = JSON.parse(text).error?.message ?? text } catch {} return { ok: false, error: msg } } return { ok: true } } async function testAnthropic(model) { const res = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': ANTHROPIC_KEY, 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model, max_tokens: 5, messages: [{ role: 'user', content: 'Hi' }], }), }) if (!res.ok) { const text = await res.text().catch(() => '') let msg = text try { msg = JSON.parse(text).error?.message ?? text } catch {} return { ok: false, error: msg } } return { ok: true } } async function testGemini(model) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_KEY}` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ role: 'user', parts: [{ text: 'Hi' }] }], generationConfig: { maxOutputTokens: 5 }, }), }) if (!res.ok) { const text = await res.text().catch(() => '') let msg = text try { msg = JSON.parse(text).error?.message ?? text } catch {} return { ok: false, error: msg } } return { ok: true } } // ── Runner ──────────────────────────────────────────────────────────────────── async function runBatch(label, models, testFn, concurrency = 3) { console.log(`\n${'─'.repeat(60)}`) console.log(`${label} (${models.length} models)`) console.log('─'.repeat(60)) const ok = [], fail = [] for (let i = 0; i < models.length; i += concurrency) { const batch = models.slice(i, i + concurrency) const results = await Promise.all(batch.map(m => testFn(m).then(r => ({ model: m, ...r })))) for (const r of results) { const icon = r.ok ? '✓' : '✗' const line = r.ok ? ` ${icon} ${r.model}` : ` ${icon} ${r.model}\n → ${r.error}` console.log(line) if (r.ok) ok.push(r.model); else fail.push({ model: r.model, error: r.error }) } // small delay between batches to avoid rate limits if (i + concurrency < models.length) await new Promise(r => setTimeout(r, 500)) } console.log(`\n PASS ${ok.length} / FAIL ${fail.length}`) return fail } // ── Main ────────────────────────────────────────────────────────────────────── const allFailed = [] const oaiFail = await runBatch('OpenAI', OPENAI_MODELS, testOpenAI, 3) const antFail = await runBatch('Anthropic', ANTHROPIC_MODELS, testAnthropic, 3) const gemFail = await runBatch('Gemini', GEMINI_MODELS, testGemini, 3) allFailed.push(...oaiFail.map(f => ({ provider: 'openai', ...f }))) allFailed.push(...antFail.map(f => ({ provider: 'anthropic', ...f }))) allFailed.push(...gemFail.map(f => ({ provider: 'gemini', ...f }))) if (allFailed.length > 0) { console.log('\n\n══ FAILED MODELS (need chat_capable = FALSE) ══') for (const f of allFailed) { console.log(` ${f.provider} / ${f.model}`) console.log(` ${f.error}`) } } else { console.log('\n\n✓ All models passed!') }