TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1// Generates docs/provider-research/openrouter.models.json (representative subset) from out/models.json,2// applying the /api/v1/models → PolyModel mapping documented in docs/provider-research/openrouter.md.3import { readFileSync, writeFileSync } from "node:fs";45const OUT = new URL("./out/", import.meta.url).pathname;6const listing: any[] = JSON.parse(readFileSync(`${OUT}models.json`, "utf8")).data;7const byId = new Map(listing.map((m) => [m.id, m]));89const PICK: Record<string, string> = {10 "openai/gpt-6-astra": "OpenAI flagship (Sep 2026). Reasoning mandatory (effort max…low, default medium). No temperature/top_p in supported_parameters → do not send sampling params. 1.05M context; pricing.overrides doubles the price above 272k prompt tokens.",11 "openai/gpt-5.5": "Reasoning optional (default_enabled true); efforts xhigh…none. No sampling params exposed. Long-context override above 272k tokens.",12 "openai/gpt-5.4": "Reasoning optional, default OFF (default_enabled false) → behaves as a non-reasoning model unless `reasoning` is sent. Cheaper than 5.5.",13 "openai/gpt-5.4-mini": "Target of the `~openai/gpt-mini-latest` alias (probed: alias call answered with model = openai/gpt-5.4-mini).",14 "openai/gpt-5.4-nano": "PROBED (all probes). Reasoning default OFF; `reasoning.effort` low/none accepted; sampling params (temperature, top_p, top_k, penalties, stop, logprobs) are SILENTLY DROPPED by default and return 404 'No endpoints found that can handle the requested parameters' with provider.require_parameters=true. Tools, json_schema strict, vision (32×32 PNG → 21 prompt tokens), streaming all OK. `:online` variant on this model consumed 8.5k prompt tokens of search results and 200 reasoning tokens before any content (finish length) → give web-search calls a large max_tokens.",15 "openai/gpt-oss-120b": "Open-weights OpenAI model served by many providers; reasoning mandatory with effort high/medium/low; response.provider varies per call.",16 "anthropic/claude-fable-5.1": "Anthropic flagship; reasoning mandatory, efforts max…low (default high); 1M context / 128k output. Target of `~anthropic/claude-fable-latest`. Prompt caching needs `cache_control` breakpoints (write 1.25×, read 0.1×).",17 "anthropic/claude-opus-5": "Reasoning optional, default_enabled true.",18 "anthropic/claude-sonnet-5": "Reasoning optional, default_enabled true; supports top_k.",19 "anthropic/claude-haiku-4.5": "PROBED (all probes). reasoning = {mandatory:false} only (no supported_efforts): `reasoning.effort` does NOT enable thinking (0 reasoning tokens) — only `reasoning.max_tokens` does (probed 43–65 reasoning tokens; reasoning_details type reasoning.text with format anthropic-claude-v1 + signature, streamed as fragments). seed/repetition_penalty/logprobs not supported (404 with require_parameters). Served via Amazon Bedrock / Azure / Anthropic; provider chosen per request. cache_control ephemeral probed: 5208 cache_write_tokens then 5208 cached_tokens, cost 0.0065 → 0.00055.",20 "google/gemini-3.8-flash": "Target of `~google/gemini-flash-latest`. Reasoning mandatory (high/medium/low). Inputs text+image+video+file+audio.",21 "google/gemini-3.1-pro-preview": "Target of `~google/gemini-pro-latest`. Reasoning mandatory. Long-context override above 200k prompt tokens (2× input). Separate `internal_reasoning` price.",22 "google/gemini-3.5-flash-lite": "PROBED (all probes). Reasoning mandatory (default_effort minimal): `reasoning.effort: none` / `enabled: false` → 400 'Reasoning is mandatory for this endpoint and cannot be disabled.' Reasoning text is NOT returned (reasoning: null) — only reasoning_details [{type:'reasoning.text', signature, format:'google-gemini-v1'}] (encrypted thought signature) or reasoning.encrypted attached to tool calls (id = tool call id). Vision OK (269 prompt tokens for a 32×32 PNG). Accepts audio/video/file input.",23 "google/gemini-3.1-flash-image": "Image OUTPUT model (output_modalities text+image): request `modalities: ['image','text']`, images come back in message.images[]; pricing.image_output per image. Hide from the plain chat picker or flag as image generation.",24 "x-ai/grok-4.6": "xAI flagship via OpenRouter (target of `~x-ai/grok-latest`). Reasoning mandatory, efforts xhigh…low. supported_parameters include stop and top_k here (xAI direct rejects stop). Override above 200k prompt tokens (2×).",25 "x-ai/grok-4.3": "Reasoning optional (efforts high/medium/low/none).",26 "x-ai/grok-4.20": "PROBED (all probes). reasoning = {mandatory:false, default_enabled:false} → non-reasoning by default; `reasoning.effort: low` turns thinking on (~90–165 reasoning tokens) and returns reasoning_details [reasoning.summary (format xai-responses-v1) + reasoning.encrypted]. Emits many ': OPENROUTER PROCESSING' keep-alive comments while thinking. top_k/penalties/stop silently dropped (404 with require_parameters). Hidden xAI system prompt ≈ 196 prompt tokens, 128 cached on every call. 2M context.",27 "deepseek/deepseek-v4-flash": "PROBED (all probes). Text-only (vision → 404 'No endpoints found that support image input'). Served by many providers (Novita, GMICloud, Parasail, StreamLake, AtlasCloud, Alibaba, Venice, Azure, DigitalOcean… — response.provider changes per call, output quality/format varies). Reasoning on by default; `effort: none` / `enabled: false` disable it. IMPORTANT: reasoning tokens count against max_tokens — with max_tokens 40 + exclude:true the answer came back null (finish length); strict json_schema with require_parameters returned null content on one provider. Give ≥ 1–2k max_tokens. Widest supported_parameters list (top_k, min_p, top_a, repetition_penalty, logit_bias, logprobs).",28 "deepseek/deepseek-v4-pro": "Reasoning optional (xhigh/high).",29 "moonshotai/kimi-k3": "Target of `~moonshotai/kimi-latest`; 1M context, text+image+video input; reasoning optional with efforts.",30 "qwen/qwen3.8-max-0902": "Reasoning mandatory (default xhigh). text+image+video.",31 "z-ai/glm-5.3-flash": "Very cheap; reasoning mandatory; expiration_date '2098-12-31' (placeholder — treat dates ≥ 2090 as 'none'). Target of `~z-ai/glm-flash-latest`. default_parameters {temperature:1, top_p:0.95}.",32 "mistralai/mistral-medium-3-5": "Reasoning optional with efforts ['high','none'] — a model whose only effort levels are on/off.",33 "meta-llama/llama-4-maverick": "No `reasoning` field at all → non-reasoning. Many third-party providers; instruct_type null.",34 "perplexity/sonar-pro": "Built-in native web search (answers include citations); max_completion_tokens 8000; no reasoning field.",35 "minimax/minimax-m3": "Cheap 1M-context reasoning model (mandatory false, no efforts). text+image+video.",36 "nvidia/nemotron-3-super-120b-a12b:free": "FREE variant (pricing.prompt '0'): 20 req/min and 50 req/day (<$10 purchased) or 1000/day (≥$10) — docs/limits. Providers may train on free-tier prompts unless data_collection: 'deny'. Show as free but expect 429s.",37 "openai/gpt-audio-mini": "Audio in/out model (output_modalities text+audio; pricing.audio / audio_output per token). Needs `modalities: ['text','audio']` + audio config — out of scope for the first adapter; hide or flag.",38 "openrouter/auto": "Meta-model: pricing '-1' (unknown up front; billed at the routed model's rate, no fee). response.model tells the chosen model (probed: routed a 'say hi' prompt to deepseek/deepseek-v4-flash-0731). context_length 2M and output_modalities text+image are the union of candidates. supported_parameters is the union too — keep sampling params off unless the user opts in. `:online` is defined as openrouter/auto + web plugin.",39};4041const toPerMillion = (s: unknown) => (s == null || s === "-1" ? null : Math.round(Number(s) * 1e6 * 1e6) / 1e6);42const num = (s: unknown) => (s == null ? null : Number(s));4344function map(m: any) {45 const sp: string[] = m.supported_parameters ?? [];46 const has = (p: string) => sp.includes(p);47 const inMods: string[] = m.architecture?.input_modalities ?? [];48 const outMods: string[] = m.architecture?.output_modalities ?? [];49 const r = m.reasoning ?? null;50 const reasoning = !!r; // field present → model can think51 const efforts: string[] | null = r?.supported_efforts ?? null;52 const author = m.id.split("/")[0];53 const base = m.id.split(":")[0];54 const variant = m.id.includes(":") ? m.id.slice(m.id.indexOf(":") + 1) : null;55 const expired = m.expiration_date && m.expiration_date < "2090-01-01" && m.expiration_date <= "2026-09-08";56 const override = m.pricing?.overrides?.[0];57 return {58 id: m.id,59 displayName: m.name,60 family: author,61 canonicalSlug: m.canonical_slug,62 variant,63 baseId: base,64 contextTokens: m.context_length ?? m.top_provider?.context_length ?? null,65 maxOutputTokens: m.top_provider?.max_completion_tokens ?? null,66 capabilities: {67 text: outMods.includes("text"),68 vision: inMods.includes("image"),69 audioInput: inMods.includes("audio"),70 audioOutput: outMods.includes("audio"),71 imageGeneration: outMods.includes("image"),72 video: inMods.includes("video"),73 reasoning,74 tools: has("tools"),75 structuredOutput: has("structured_outputs") || has("response_format"),76 strictJsonSchema: has("structured_outputs"),77 streaming: true,78 files: inMods.includes("file"),79 webSearch: "via-plugin-or-:online",80 moderated: m.top_provider?.is_moderated ?? false,81 },82 parameters: {83 temperature: has("temperature"),84 topP: has("top_p"),85 topK: has("top_k"),86 minP: has("min_p"),87 topA: has("top_a"),88 maxTokens: has("max_tokens") || has("max_completion_tokens"),89 reasoningEffort: reasoning ? (efforts ?? (r.mandatory ? false : "toggle-only (reasoning.enabled / reasoning.max_tokens)")) : false,90 reasoningMandatory: r?.mandatory ?? false,91 reasoningDefaultEnabled: r?.default_enabled ?? (r ? r.mandatory : false),92 reasoningDefaultEffort: r?.default_effort ?? null,93 thinkingBudget: r?.supports_max_tokens ?? (author === "anthropic" || author === "google" ? "reasoning.max_tokens (docs)" : false),94 stop: has("stop"),95 seed: has("seed"),96 frequencyPenalty: has("frequency_penalty"),97 presencePenalty: has("presence_penalty"),98 repetitionPenalty: has("repetition_penalty"),99 logprobs: has("logprobs"),100 logitBias: has("logit_bias"),101 responseFormat: has("response_format"),102 toolChoice: has("tool_choice"),103 parallelToolCalls: has("parallel_tool_calls"),104 verbosity: has("verbosity"),105 webSearchOptions: has("web_search_options"),106 defaults: m.default_parameters && Object.keys(m.default_parameters).length ? m.default_parameters : null,107 },108 pricing: {109 inputPerMillion: toPerMillion(m.pricing?.prompt),110 cachedInputPerMillion: toPerMillion(m.pricing?.input_cache_read),111 cacheWritePerMillion: toPerMillion(m.pricing?.input_cache_write),112 cacheWrite1hPerMillion: toPerMillion(m.pricing?.input_cache_write_1h),113 outputPerMillion: toPerMillion(m.pricing?.completion),114 internalReasoningPerMillion: toPerMillion(m.pricing?.internal_reasoning),115 imageInputPerImage: num(m.pricing?.image),116 imageOutputPerImage: num(m.pricing?.image_output),117 audioInputPerMillion: toPerMillion(m.pricing?.audio),118 audioOutputPerMillion: toPerMillion(m.pricing?.audio_output),119 perRequest: num(m.pricing?.request),120 webSearchPerRequest: num(m.pricing?.web_search),121 longContextThresholdTokens: override?.min_prompt_tokens ?? null,122 longContextInputPerMillion: toPerMillion(override?.prompt),123 longContextOutputPerMillion: toPerMillion(override?.completion),124 longContextCachedInputPerMillion: toPerMillion(override?.input_cache_read),125 free: m.pricing?.prompt === "0" && m.pricing?.completion === "0",126 unknown: m.pricing?.prompt === "-1",127 },128 status: expired ? "expired" : variant === "free" ? "free" : m.id.startsWith("openrouter/") ? "meta" : "active",129 expirationDate: m.expiration_date && m.expiration_date < "2090-01-01" ? m.expiration_date : null,130 knowledgeCutoff: m.knowledge_cutoff ?? null,131 aliases: listing.filter((x) => x.alias_target?.slug === m.id).map((x) => x.id),132 huggingFaceId: m.hugging_face_id ?? null,133 created: m.created,134 notes: PICK[m.id],135 };136}137138const rows = Object.keys(PICK).map((id) => {139 const m = byId.get(id);140 if (!m) throw new Error(`missing in listing: ${id}`);141 return map(m);142});143const target = new URL("../../docs/provider-research/openrouter.models.json", import.meta.url).pathname;144writeFileSync(target, JSON.stringify(rows, null, 2) + "\n");145console.log(`wrote ${target} (${rows.length} models)`);146for (const r of rows) console.log(`${r.id.padEnd(44)} ctx=${String(r.contextTokens).padStart(8)} out=${String(r.maxOutputTokens).padStart(7)} $${r.pricing.inputPerMillion}/${r.pricing.outputPerMillion} eff=${JSON.stringify(r.parameters.reasoningEffort)} vis=${r.capabilities.vision} tools=${r.capabilities.tools} so=${r.capabilities.strictJsonSchema} temp=${r.parameters.temperature}`);147