// Probe 00: GET /models (full dump), /key, /credits, and a summary of the listing. import { raw, save, short } from "./lib.ts"; // Default call (no params) — to see whether the response is paginated / how many models come back const first = await raw("/models"); const fb: any = first.body; console.log("GET /models (no params)", first.status, `data=${fb?.data?.length} total_count=${fb?.total_count} links=${JSON.stringify(fb?.links)} keys=${Object.keys(fb ?? {})}`); save("models-default-page.meta.json", { status: first.status, headers: first.headers, count: fb?.data?.length, total_count: fb?.total_count, links: fb?.links, keys: Object.keys(fb ?? {}) }); // Full dump: follow pagination with limit=1000 const all: any[] = []; let next: string | null = "/models?limit=1000"; let pages = 0; let lastPage: any = null; while (next) { const r = await raw(next.replace(/^https:\/\/openrouter\.ai\/api\/v1/, "")); const b: any = r.body; pages++; lastPage = b; all.push(...(b?.data ?? [])); console.log(`page ${pages}: status=${r.status} data=${b?.data?.length} total_count=${b?.total_count} next=${b?.links?.next}`); next = b?.links?.next ?? null; if (pages > 10) break; } const models = { status: first.status, body: { ...lastPage, data: all } }; save("models.json", models.body); const key = await raw("/key"); console.log("GET /key", key.status, short(key.body, 1200)); save("key.json", { status: key.status, headers: key.headers, body: key.body }); const credits = await raw("/credits"); console.log("GET /credits", credits.status, short(credits.body, 600)); save("credits.json", { status: credits.status, headers: credits.headers, body: credits.body }); // Extra listing endpoints worth knowing about for (const p of ["/models/user", "/models?supported_parameters=tools", "/models/count", "/auth/key"]) { const r = await raw(p); const b: any = r.body; console.log("GET", p, r.status, Array.isArray(b?.data) ? `data.length=${b.data.length}` : short(b, 200)); } const data: any[] = (models.body as any)?.data ?? []; const count = data.length; const outMod: Record = {}; const inMod: Record = {}; const params = new Map(); const topKeys = new Set(); const pricingKeys = new Set(); const archKeys = new Set(); const tpKeys = new Set(); let free = 0; let freeSuffix = 0; let withExpiration = 0; let withReasoning = 0; let withDefaults = 0; const reasoningSamples: any[] = []; const defaultSamples: any[] = []; const expSamples: any[] = []; const prlSamples: any[] = []; const variants: Record = {}; const nullCtx: string[] = []; const nullMaxOut = { count: 0 }; const tokenizers: Record = {}; const instructTypes: Record = {}; const modalityCombos: Record = {}; const nonTextOut: string[] = []; let perRequestLimitsNonNull = 0; let hfIds = 0; for (const m of data) { for (const k of Object.keys(m)) topKeys.add(k); for (const k of Object.keys(m.pricing ?? {})) pricingKeys.add(k); for (const k of Object.keys(m.architecture ?? {})) archKeys.add(k); for (const k of Object.keys(m.top_provider ?? {})) tpKeys.add(k); const om = (m.architecture?.output_modalities ?? []).slice().sort().join("+") || "(none)"; outMod[om] = (outMod[om] ?? 0) + 1; const im = (m.architecture?.input_modalities ?? []).slice().sort().join("+") || "(none)"; inMod[im] = (inMod[im] ?? 0) + 1; const mod = m.architecture?.modality ?? "(none)"; modalityCombos[mod] = (modalityCombos[mod] ?? 0) + 1; if (m.architecture?.tokenizer) tokenizers[m.architecture.tokenizer] = (tokenizers[m.architecture.tokenizer] ?? 0) + 1; const it = m.architecture?.instruct_type ?? "(null)"; instructTypes[it] = (instructTypes[it] ?? 0) + 1; for (const p of m.supported_parameters ?? []) params.set(p, (params.get(p) ?? 0) + 1); if (m.pricing?.prompt === "0") free++; if (/:free$/.test(m.id)) freeSuffix++; const v = m.id.includes(":") ? m.id.slice(m.id.indexOf(":")) : "(none)"; variants[v] = (variants[v] ?? 0) + 1; if (m.expiration_date) { withExpiration++; if (expSamples.length < 8) expSamples.push({ id: m.id, expiration_date: m.expiration_date }); } if (m.reasoning != null) { withReasoning++; if (reasoningSamples.length < 12) reasoningSamples.push({ id: m.id, reasoning: m.reasoning }); } if (m.default_parameters != null && Object.keys(m.default_parameters).length) { withDefaults++; if (defaultSamples.length < 8) defaultSamples.push({ id: m.id, default_parameters: m.default_parameters }); } if (m.per_request_limits != null) { perRequestLimitsNonNull++; if (prlSamples.length < 3) prlSamples.push({ id: m.id, per_request_limits: m.per_request_limits }); } if (m.hugging_face_id) hfIds++; if (m.context_length == null) nullCtx.push(m.id); if (m.top_provider?.max_completion_tokens == null) nullMaxOut.count++; const outs: string[] = m.architecture?.output_modalities ?? []; if (!outs.includes("text")) nonTextOut.push(`${m.id} → ${outs.join(",")}`); } const reasoningShapes: Record = {}; for (const m of data) { const r = m.reasoning; const shape = r == null ? "null/absent" : JSON.stringify(Object.fromEntries(Object.entries(r).map(([k, v]) => [k, Array.isArray(v) ? "array" : typeof v]))); reasoningShapes[shape] = (reasoningShapes[shape] ?? 0) + 1; } const summary = { count, topLevelKeys: [...topKeys], architectureKeys: [...archKeys], pricingKeys: [...pricingKeys], topProviderKeys: [...tpKeys], outputModalities: outMod, inputModalities: inMod, modalityStrings: modalityCombos, tokenizers, instructTypes, supportedParameters: Object.fromEntries([...params.entries()].sort((a, b) => b[1] - a[1])), freeByPromptZero: free, freeBySuffix: freeSuffix, variantSuffixes: variants, withExpirationDate: withExpiration, expirationSamples: expSamples, withReasoningField: withReasoning, reasoningFieldShapes: reasoningShapes, reasoningSamples, withDefaultParameters: withDefaults, defaultParameterSamples: defaultSamples, perRequestLimitsNonNull, perRequestLimitsSamples: prlSamples, huggingFaceIds: hfIds, nullContextLength: nullCtx, nullMaxCompletionTokens: nullMaxOut.count, nonTextOutput: nonTextOut, sample: data.find((m) => m.id === "anthropic/claude-haiku-4-5") ?? data[0], }; save("models-summary.json", summary); console.log(JSON.stringify({ ...summary, sample: undefined, nonTextOutput: summary.nonTextOutput.length, nullContextLength: nullCtx.length }, null, 1).slice(0, 6000)); // Candidate cheap models for the probes const want = [ "openai/gpt-5.4-nano", "anthropic/claude-haiku-4-5", "google/gemini-3.5-flash-lite", "deepseek/deepseek-v4-flash", "x-ai/grok-4.20-0309-non-reasoning", ]; for (const w of want) { const m = data.find((x) => x.id === w); console.log(w, m ? `OK ctx=${m.context_length} in=${m.pricing.prompt} out=${m.pricing.completion} params=${(m.supported_parameters ?? []).join(",")}` : "MISSING"); } const cheap = data .filter((m) => m.pricing?.prompt !== "0" && Number(m.pricing?.prompt) < 0.0000005 && (m.architecture?.output_modalities ?? []).includes("text")) .filter((m) => /^(openai|anthropic|google|deepseek|x-ai|mistralai|meta-llama|qwen|moonshotai|z-ai)\//.test(m.id)) .map((m) => `${m.id} in=${m.pricing.prompt} out=${m.pricing.completion} ctx=${m.context_length} img=${(m.architecture.input_modalities ?? []).includes("image")}`); console.log("cheap candidates:\n" + cheap.join("\n"));