SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
12.9 KB · 147 lines typescript
Raw Blame History
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