Compare commits

...

676 Commits

Author SHA1 Message Date
Aiden Cline 6a74991397 chore(sync): isolate cloudflare credentials 2026-05-20 20:19:47 -05:00
Aiden Cline 5fdcea6b36 Merge pull request #1815 from anomalyco/cloudflare-ai-gateway
chore(sync): add cloudflare workers ai sync
2026-05-20 18:02:28 -05:00
Aiden Cline 31e56480b4 chore(sync): add cloudflare workers ai sync 2026-05-20 16:55:29 -05:00
Aiden Cline 92a621594e Merge pull request #1813 from anomalyco/sync-xai
chore(sync): add xai model sync
2026-05-20 16:02:38 -05:00
Aiden Cline d2db353ceb chore: ignore sync reports 2026-05-20 16:01:51 -05:00
Aiden Cline 900ae509d2 Merge pull request #1808 from ajussak/scaleway
Added Mistral Medium 3.5 128B from Scaleway
2026-05-20 15:58:00 -05:00
Adrien Jussak cd3e99025f Update Mistral Medium 3.5 128B model configuration to extend from mistral-medium-2604 and adjust context window size. 2026-05-20 20:43:45 +02:00
Aiden Cline 1098981eb6 chore(sync): add xai model sync 2026-05-20 13:23:02 -05:00
Aiden Cline 27a151cf53 Merge pull request #1811 from anomalyco/xai-grok-build-model
Add xAI Grok Build model
2026-05-20 13:05:06 -05:00
Aiden Cline 41ff42ab7f Merge pull request #1810 from fhennerkes/dev
poe: add Gemini-3.5-Flash model
2026-05-20 13:04:47 -05:00
Aiden Cline adf1cbdecd add xai grok build model 2026-05-20 13:04:13 -05:00
Frank 10ddc78ce0 update zen models 2026-05-20 14:02:01 -04:00
fhennerkes 7ae897e440 poe: add Gemini-3.5-Flash model
Add new Google model from Poe API (released 2026-05-19).
Uses extends format inheriting from google/gemini-3.5-flash with
Poe-specific overrides (name format, no temperature, markup pricing,
limited input modalities).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 10:52:15 -07:00
Aiden Cline 02e452c2e8 Merge pull request #1805 from anomalyco/sync-google
sync google models
2026-05-20 10:53:57 -05:00
Aiden Cline f05b63fff5 Merge pull request #1807 from anomalyco/automation/sync-models-aggregators
chore(sync): update aggregator model catalogs
2026-05-20 10:34:30 -05:00
Adrien Jussak e277d60236 Add Mistral Medium 3.5 128B to Scaleway 2026-05-20 16:14:37 +02:00
github-actions[bot] b01277a737 chore(sync): update aggregator model catalogs 2026-05-20 09:25:37 +00:00
Frank a5da5aa429 update zen models 2026-05-20 04:14:28 -04:00
Aiden Cline 5ceac8a58b updates 2026-05-19 23:43:01 -05:00
Aiden Cline 11e1d5623a Merge pull request #1806 from Cahl-Dee/grid-model-updates-2026-05
Grid model updates 2026-05
2026-05-19 23:42:26 -05:00
Aiden Cline f107afc57c sync 2026-05-19 22:56:50 -05:00
Carl DiClementi e789d7c1f2 Merge branch 'anomalyco:dev' into grid-model-updates-2026-05 2026-05-19 16:06:23 -05:00
Cahl-Dee 899668ad49 added new code and agent models and updated existing text models 2026-05-19 16:04:58 -05:00
Aiden Cline 8c677f0134 sync google models 2026-05-19 15:58:06 -05:00
Aiden Cline 462c7877d9 add gemini 3.5 flash 2026-05-19 15:43:38 -05:00
Aiden Cline 55871f9dca Merge pull request #1804 from Adanlink/dev
feat: add deepseek-v4-flash to the fireworks-ai provider
2026-05-19 15:29:33 -05:00
Aiden Cline 4f7194a3c8 test 2026-05-19 15:09:11 -05:00
Aiden Cline f65f0148da add sync guide 2026-05-19 15:08:44 -05:00
Adán 14952f8855 Rename deepseek-v4-flash to deepseek-v4-flash.toml 2026-05-19 18:56:43 +02:00
Adán d7c6d3ad12 Add deepseek-v4-flash model configuration 2026-05-19 18:53:48 +02:00
Aiden Cline 356bc79d08 Merge pull request #1637 from elvexai/fix/amazon-bedrock-kimi-token-limits
fix: Token limits for Amazon Bedrock Kimi K2 models
2026-05-19 09:42:30 -05:00
Aiden Cline a89b1ed726 Merge pull request #1801 from bas3line/sync-routing-run-models
Sync routing.run model catalog
2026-05-19 09:41:38 -05:00
bas3line a998576773 fix(routing-run): match live model metadata 2026-05-19 10:38:58 +05:30
bas3line fbe842bbea fix(routing-run): expose reasoning metadata 2026-05-19 08:39:51 +05:30
bas3line 6c0c3d1b10 fix(routing-run): sync model catalog 2026-05-19 07:37:53 +05:30
Aiden Cline db0a7cf611 Merge pull request #1798 from anomalyco/rework-sync-logic
sync: centralize aggregator model updates
2026-05-18 20:12:50 -05:00
Aiden Cline d775e37e3b Merge pull request #1800 from jerome-benoit/feat/sap-ai-core-gpt-5.4
feat(sap-ai-core): add GPT-5.4
2026-05-18 20:12:21 -05:00
Jérôme Benoit 36753063d9 feat(sap-ai-core): add GPT-5.4
Add gpt-5.4 with availability date from official SAP source.

Drop [[cost.tiers]] from gemini-2.5-pro pending SAP-side tiered
pricing confirmation; sap-ai-core now declares no per-model tiers
(SAP Note 3437766 is login-gated and authoritative for capacity
unit conversion rates).
2026-05-19 02:58:29 +02:00
Aiden Cline 5ee955297a sync: drop vercel catalog updates 2026-05-18 19:07:14 -05:00
Aiden Cline 1b77511903 Merge pull request #1799 from vglafirov/add-gitlab-gpt-5-5
feat(gitlab): add Agentic Chat (GPT-5.5) model
2026-05-18 15:24:18 -05:00
Aiden Cline 8896ead7bf sync: fix vercel pricing tiers 2026-05-18 14:52:40 -05:00
Vladimir Glafirov eb96594d47 feat(gitlab): add Agentic Chat (GPT-5.5) model
Adds duo-chat-gpt-5-5 to the GitLab provider. The GitLab AI Gateway
proxies this model to OpenAI's gpt-5.5-2026-04-23 backend with a
1.05M token context window (922k input + 128k output).

Source: gitlab-org/modelops/applied-ml/code-suggestions/ai-assist
models.yml (gpt_5_5 entry with proxy_provider: openai).

The gitlab-ai-provider npm package exposes this model id starting in
v6.7.0.
2026-05-18 20:44:34 +02:00
Aiden Cline 327332efe3 Merge pull request #1794 from bas3line/add-routing-run-provider
Add routing.run provider
2026-05-18 12:32:34 -05:00
Aiden Cline 5020951745 sync: refresh openrouter after dev merge 2026-05-18 12:30:29 -05:00
Aiden Cline cb6f97774e Merge remote-tracking branch 'origin/dev' into rework-sync-logic 2026-05-18 12:29:28 -05:00
Aiden Cline 7f8b493b0c Merge pull request #1795 from delafthi/delafthi/lxxqxzktnozv
fix(providers/novita-ai): use lowercase model names
2026-05-18 12:28:32 -05:00
Aiden Cline d65a862533 sync: centralize aggregator model updates 2026-05-18 12:12:15 -05:00
Victor Navarro 8e710e19ea bring back old bick-pickle
Added interleaved section with reasoning_content field and removed provider section.
2026-05-18 11:44:14 +02:00
Frank 36c6896e97 update zen models 2026-05-17 22:58:06 -04:00
Aiden Cline a8be548a5d Merge pull request #1416 from Luew2/add-lilac-provider
Add Lilac provider
2026-05-17 19:23:07 -05:00
Luew2 8c2fae4ab0 Keep exact Lilac Gemma model name 2026-05-17 17:19:53 -07:00
Luew2 5e7fad350d Align Lilac Gemma display name 2026-05-17 17:19:04 -07:00
Luew2 feb85ef2c9 Align Lilac provider with registry conventions 2026-05-17 17:15:40 -07:00
Luew2 d4161ebf24 Follow models.dev conventions for Lilac provider 2026-05-17 17:11:03 -07:00
Luew2 b91ab02e2b Add Lilac MiniMax M2.7 model 2026-05-17 17:07:44 -07:00
Luew2 dd09d07f75 Update Lilac Kimi model to K2.6 2026-05-17 17:07:44 -07:00
Luew2 4515f85d47 Add Lilac cache pricing 2026-05-17 17:07:44 -07:00
Luew2 97572240e1 Add Gemma 4 31B IT model
Adds google/gemma-4-31b-it to the Lilac provider ($0.11/M input,
$0.35/M output, 262K context, native multimodal with image/video).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:07:44 -07:00
Luew2 80cc04e9e5 Fix Kimi K2.5 output limit to 262,144 tokens 2026-05-17 17:07:44 -07:00
Luew2 d532ebb89b Use purple gradient for Lilac logo (brand colors #6451dc → #b6a6f9) 2026-05-17 17:07:44 -07:00
Luew2 9bae3e887f Replace placeholder logo with Lilac icon mark (currentColor) 2026-05-17 17:07:44 -07:00
Luew2 0be69bf872 Add Lilac provider
Add Lilac as an OpenAI-compatible provider serving:
- z-ai/glm-5.1: Z.ai's flagship agentic model (754B MoE, 202.8K context)
- moonshotai/kimi-k2.5: Moonshot AI's multimodal reasoning model (1T MoE, 262K context)

API: https://api.getlilac.com/v1
Docs: https://docs.getlilac.com
2026-05-17 17:07:44 -07:00
Thierry Delafontaine 0ed38cbecf fix(providers/novita-ai): use lowercase model names
Mixed case naming causes conflicts on case-insensitive filesystems like macOS.
2026-05-17 21:17:46 +02:00
bas3line e65703382a feat: add routing.run provider 2026-05-17 20:27:56 +05:30
Aiden Cline 748754c99b Merge pull request #1792 from monotykamary/fix/neuralwatt-context-limits
fix(neuralwatt): sync context window and output limits with upstream API
2026-05-16 13:14:05 -05:00
Tom X Nguyen ec9c12d0fc fix(neuralwatt): sync context window and output limits with upstream API
Updates all 14 neuralwatt model TOML files with corrected context window
and max output token values as reported by the Neuralwatt API:

- Devstral-Small-2-24B-Instruct-2512: 262,144 -> 262,128
- GLM-5/GLM-5.1 variants: 200,000 -> 202,736
- GPT-OSS-20B: 16,384 -> 16,368
- Kimi-K2.5/K2.6 variants: 262,144 -> 262,128
- MiniMax-M2.5: 196,608 -> 196,592
- Qwen3.5-397B variants: 262,144 -> 262,128
- Qwen3.6-35B variants: 131,072 -> 131,056

Also fixes the README: moves kimi-k2.6-fast from 'Reasoning Models' to
'Fast Variants' and removes incorrect claim that fast variants support
reasoning.
2026-05-16 23:16:34 +07:00
Aiden Cline ac81822c89 Merge pull request #1777 from berget-ai/feat/berget-kimi-k2.6
feat: add Kimi K2.6 to berget.ai
2026-05-16 06:09:00 -05:00
Aiden Cline d32ed764bd Merge pull request #1791 from anomalyco/automation/sync-openrouter-models
Sync OpenRouter models
2026-05-16 06:08:09 -05:00
Christian Landgren 45fb951c42 feat: add Kimi K2.6 to berget.ai
Add Moonshot AI Kimi K2.6 model to berget.ai provider catalog.

- 262K context window
- 16K output tokens
- Text input/output
- Supports: reasoning, structured output, tool calling
- Pricing: /bin/zsh.83/M input, .85/M output (EUR-based)
- Open weights
2026-05-16 12:46:35 +02:00
github-actions[bot] 260d79b2d5 Sync OpenRouter models 2026-05-16 08:54:41 +00:00
Aiden Cline 746b9caf79 Merge pull request #1785 from jerome-benoit/feat/sap-ai-core-opus-4-7
feat(sap-ai-core): add Claude Opus 4.7 and sync model specs
2026-05-15 23:24:47 -05:00
Aiden Cline 8362b55503 Merge pull request #1786 from Ardakilic/chore/kilo-sync-20260516
providers(kilo): sync upstream
2026-05-15 23:24:34 -05:00
Aiden Cline dde3953a9f Merge pull request #1787 from Suat-B/codex/xpersona-www-api-url
Fix Xpersona API base URL
2026-05-15 23:24:06 -05:00
Aiden Cline 0a1695212c Merge pull request #1788 from Jaaneek/xai-may-15-2026-retirement
xai: drop models retired May 15, 2026 + add Grok Imagine models
2026-05-15 23:23:52 -05:00
Jaaneek 89fbb6bb69 xai: drop models retired May 15, 2026 + add Grok Imagine models 2026-05-16 01:57:15 +01:00
SuatB 005fe0fb5a Fix Xpersona provider API URL 2026-05-15 18:37:41 -05:00
Frank e283875ce7 update zen models 2026-05-15 17:24:53 -04:00
Arda Kilicdagi 3598019251 providers(kilo): sync upstream 2026-05-16 01:11:25 +04:00
Jérôme Benoit e9ad8b0a3f feat(sap-ai-core): add Claude Opus 4.7 and sync model specs 2026-05-15 22:16:07 +02:00
Aiden Cline 0ee78eeda5 sync: openrouter models 2026-05-15 10:20:36 -05:00
Aiden Cline 0a5b33e518 Merge pull request #1778 from zhenjunchen-png/add-orcarouter
feat: add OrcaRouter as a new provider
2026-05-15 10:16:29 -05:00
Aiden Cline 22416dda64 Merge pull request #1783 from anomalyco/sync-openrouter
add sync script for openrouter, sync openrouter models
2026-05-15 10:12:02 -05:00
Aiden Cline eba2702e3a fix: families 2026-05-15 10:03:39 -05:00
Aiden Cline c2c5cc8f21 add sync script for openrouter, sync openrouter models 2026-05-15 10:00:32 -05:00
zhenjun.chen 022b1b9946 feat(orcarouter): expand to 80 chat models and add brand logo
Adds 55 additional upstream-mirrored models alongside the existing 25,
covering the full OrcaRouter chat catalog as exposed by
https://www.orcarouter.ai/api/pricing (text-only chat — TTS, embeddings,
video, and image generation are filtered out).

Per-namespace upstream mappings used by [extends]:

  OrcaRouter ns  -> models.dev provider
  ---------------- + ---------------
  openai         -> openai
  anthropic      -> anthropic   (dot version -> dash, e.g. opus-4.7 -> opus-4-7)
  google         -> google
  deepseek       -> deepseek
  qwen           -> alibaba
  grok           -> xai
  kimi           -> moonshotai
  minimax        -> minimax     (minimax-m2.7 -> MiniMax-M2.7)
  z-ai           -> zai

OrcaRouter-specific aliases (dated snapshots like gpt-5-2025-08-07,
search-preview variants, qwen3-vl-* visual variants) are excluded from
v1 because their upstream canonical files do not yet exist in models.dev.

Also adds providers/orcarouter/logo.svg.
2026-05-15 14:48:31 +08:00
Aiden Cline 8269e04222 Merge pull request #1782 from Suat-B/codex/xpersona-limits-logo
Update Xpersona limits, cutoff, and logo
2026-05-15 00:29:32 -05:00
Aiden Cline a75cf2ed1c Merge pull request #1552 from aredridel/as/add-umans
feat(models): add umans.ai coding plan
2026-05-14 22:40:39 -05:00
Aria Stewart 7b00aafa79 feat(models): umans.ai definitions 2026-05-14 23:39:30 -04:00
Suat-B e54e0fc8b5 Update Xpersona limits, cutoff, and logo 2026-05-15 03:22:16 +00:00
Aiden Cline 38611e75fa Merge pull request #1781 from dpuyosa/feat/add-venice-claude-opus-4-7-fast-model
Venice: Add Claude Opus 4.7 Fast model
2026-05-14 22:08:14 -05:00
dpuyosa e62c1e973e [venice] Add Claude Opus 4.7 Fast model
- New pricing with 36/180 input/output per million tokens
- 1M context window with 128K output limit
- Text+image input, text-only output
2026-05-15 01:32:24 +02:00
Aiden Cline c2b3c601e4 Merge pull request #1724 from isaachuangGMICLOUD/feat/add-gmicloud-provider
providers(gmicloud): add GMI Cloud provider
2026-05-14 17:26:50 -05:00
Aiden Cline 9ff1d36a21 Merge pull request #1476 from Vect0rM/feat/add-atomic-chat-provider
feat: add Atomic Chat provider
2026-05-14 17:18:21 -05:00
Aiden Cline e0f4042ad1 Merge pull request #1776 from kapelame/docs/minimax-token-plan-rename
providers(minimax): rename Coding Plan → Token Plan in display labels
2026-05-14 17:16:28 -05:00
Frank 14736ba4b6 update zen models 2026-05-14 17:15:53 -04:00
Aiden Cline 9351d68731 Merge pull request #1772 from nearai/add-nearai
Add NEAR AI Cloud provider
2026-05-14 10:30:28 -05:00
Aiden Cline 7a5a1d2aff Merge pull request #1779 from NameIsHiki/siliconflow-deepseek-v4
feat(siliconflow): add DeepSeek v4 models
2026-05-14 10:29:52 -05:00
zhenjun.chen 699284ce91 chore(orcarouter): drop oversize logo, fall back to models.dev default
The previously committed logo is ~100KB; existing wrapper-provider logos
(openrouter, llmgateway, kilo, aihubmix, ambient) are all 0.3-6KB and use
`currentColor`. Falling back to the default logo per README:

  > If we don't have a provider's logo, a default logo is served instead.

A properly-sized currentColor logo will follow in a separate PR.
2026-05-14 21:37:48 +08:00
Hiki 21ce5c3ac8 Create deepseek-v4-flash.toml 2026-05-14 15:26:37 +02:00
Hiki 3485cf52d0 Create deepseek-v4-pro.toml 2026-05-14 15:22:53 +02:00
Frank 99e8f25c78 update zen models 2026-05-14 08:59:42 -04:00
zhenjun.chen 7102978cb4 feat: add OrcaRouter provider
OrcaRouter is an OpenAI-compatible meta-router aggregating 150+ LLMs
(OpenAI, Anthropic, Google, xAI, DeepSeek, Qwen, Kimi, MiniMax, ...)
behind a single API key, with a virtual orcarouter/auto smart-routing
entry that picks an upstream per request.

This initial scope covers 26 models (1 AUTO router + 25 upstream mirrors
using [extends]). Pricing computed from https://www.orcarouter.ai/api/pricing
on 2026-05-14: input = model_ratio * $2, output = model_ratio *
completion_ratio * $2 (USD per 1M tokens).

Disclosure: I'm an engineer on the OrcaRouter team.
2026-05-14 20:55:47 +08:00
kapelame 530f60c69c providers(minimax): rename Coding Plan → Token Plan in display labels
The product was renamed from "Coding Plan" to "Token Plan" when its
scope expanded beyond coding to cover all MiniMax modalities (text,
speech, video, music, image). Per
https://platform.minimax.io/docs/token-plan/intro:
"Token Plan extends upon our former Coding Plan."

Updates display name and doc URL for the two affected provider
catalog entries. Provider IDs (minimax-coding-plan,
minimax-cn-coding-plan) are intentionally unchanged for backward
compatibility — anyone with these IDs in opencode.json or
elsewhere keeps working. Old /coding-plan/* URLs still 307-redirect
to the new /token-plan/* paths upstream.

Region disambiguation stays as the URL in parens (matching the
existing minimax / minimax-cn naming convention) — no "China" word
added, since the URL already conveys the region cleanly in the
provider picker.
2026-05-14 19:18:03 +08:00
Misha Skvortsov 2415c5be21 fix(atomic-chat): update logo.svg with new design
Replaces the existing logo.svg file with an updated design for the Atomic Chat provider. This change enhances the visual branding of the application.
2026-05-14 10:58:36 +03:00
Aiden Cline 85aba468cd Merge pull request #1767 from Suat-B/codex/xpersona-provider-20260513
Add Xpersona provider
2026-05-13 23:20:20 -05:00
Aiden Cline d1ec1ba777 Merge pull request #1773 from ambient-gregory/dev
feat: add Ambient provider with GLM-5.1 and Kimi K2.6
2026-05-13 19:20:20 -05:00
Gregory 0f94bf16ec fix(ambient): shrink logo display size to match other providers 2026-05-13 19:33:26 -04:00
Aiden Cline 506e8f48a9 Merge pull request #1770 from EriDeLee/dev
chore(aihubmix): sync model catalog
2026-05-13 17:43:30 -05:00
Aiden Cline 3480bc5992 Merge pull request #1775 from michaelnchin/fix/amazon-bedrock-gpt-oss-tokens
fix: Output tokens for Bedrock GPT-OSS models
2026-05-13 17:36:42 -05:00
Michael Chin fde97814ef fix: Output tokens for Bedrock GPT-OSS models 2026-05-13 14:45:17 -07:00
Gregory ff7eddcb70 feat: add Ambient provider with GLM-5.1 and Kimi K2.6
Adds the Ambient inference provider (api.ambient.xyz) with an initial
catalog of GLM-5.1 and Kimi K2.6, plus a generator script that pulls
from /v1/models so pricing and limits stay in sync with the upstream API.

Run `bun run ambient:generate` to refresh model TOMLs.
2026-05-13 11:30:45 -04:00
Evrard-Nil Daillet 5cbab85b8d Add nearai logo.svg from cloud.near.ai favicon 2026-05-13 16:32:08 +02:00
Evrard-Nil Daillet 6f9820de9f Add NEAR AI Cloud provider
Adds nearai as an OpenAI-compatible provider at https://cloud-api.near.ai/v1
serving 33 models. First-party mirrors (anthropic/openai/google) use `extends`;
NEAR-hosted open-weight models (Qwen, GLM-5.1-FP8, gpt-oss, whisper, FLUX) have
full definitions.

Pricing and context limits sourced from cloud-api.near.ai/v1/models.
2026-05-13 16:32:08 +02:00
EriDeLee cdfb429098 chore(aihubmix): sync model catalog 2026-05-13 21:55:32 +08:00
Victor Navarro 1c2546af8a perf: virtualize models table and other improvements 2026-05-13 12:29:27 +02:00
vimtor 2288a1626b trim search index to essential fields and remove dead code 2026-05-13 12:21:52 +02:00
vimtor b3ecfc3d70 improve row scanning 2026-05-13 11:20:42 +02:00
Suat-B 30b3e677fd Add Xpersona provider 2026-05-13 00:39:09 -05:00
Suat-B 3b37eee86e Add Xpersona provider 2026-05-13 00:39:08 -05:00
Suat-B 71f069670e Add Xpersona provider 2026-05-13 00:39:07 -05:00
Aiden Cline f401672689 Merge pull request #1766 from michaelnchin/fix/amazon-bedrock-structured-output-05122026-2
fix: add structured_output=True for more supported Bedrock models
2026-05-12 23:24:39 -05:00
Michael Chin 2a0d86a034 update structured_output for more Bedrock models 2026-05-12 20:52:36 -07:00
Aiden Cline d9439cdf2f Merge pull request #1762 from zxyaction/feat/add-auriko-provider
feat: add Auriko provider with 15 models
2026-05-12 22:16:02 -05:00
Aiden Cline 3d443d568d Merge pull request #1765 from michaelnchin/fix/amazon-bedrock-structured-output-05122026
fix: update structured_output for Bedrock Claude 4.x models
2026-05-12 22:15:51 -05:00
Michael Chin a76c8fe9dd fix: update structured_output for Bedrock Claude 4.x models 2026-05-12 20:07:12 -07:00
Aiden Cline d08e8d6cc1 Merge pull request #1763 from Tavernari/feat/add-claudinio-provider
feat: add Claudinio provider
2026-05-12 19:13:54 -05:00
Victor Carvalho Tavernari 4c06e44047 fix: use currentColor in logo SVG per contributing guidelines 2026-05-12 23:59:03 +01:00
Aiden Cline 5e344ded49 Merge pull request #1755 from anomalyco/correct-context-tracking
feat: add new context pricing tiers
2026-05-12 17:40:48 -05:00
Aiden Cline 458b7f4d1a use Venice context tier thresholds 2026-05-12 17:39:40 -05:00
Aiden Cline baf4432140 Merge pull request #1759 from NameIsHiki/deepinfra-xiaomi-mimo-models
feat(deepinfra): add Xiaomi MiMo v2.5 and v2.5 Pro
2026-05-12 17:10:59 -05:00
Aiden Cline bbf72ea4e0 Merge pull request #1764 from anomalyco/add-anthropic-opus-4-7-fast-mode
Add fast mode for Anthropic Opus 4.7
2026-05-12 17:10:49 -05:00
Aiden Cline 8f9adc7567 fix generated tier change detection 2026-05-12 17:01:35 -05:00
Aiden Cline addaf1c036 add fast mode for anthropic opus 4.7 2026-05-12 17:01:05 -05:00
Victor Carvalho Tavernari 55d16a58b6 feat: add claudinio provider (OpenAI-compatible, 256K ctx, $0.50/$2.00 per MTok) 2026-05-12 22:33:45 +01:00
Hiki 72a4deab66 Update mimo-v2.5.toml 2026-05-12 23:23:53 +02:00
Hiki 8dd829a187 Update mimo-v2.5-pro.toml 2026-05-12 23:23:01 +02:00
Aiden Cline a671cc05d5 align cost tiers with model schema 2026-05-12 16:01:49 -05:00
Aiden Cline 656c6f08a7 Merge pull request #1761 from Sewer56/deprecate-wafer-models
providers/wafer.ai: Remove DeepSeek-V4-Pro and MiniMax-M2.7 models
2026-05-12 15:51:10 -05:00
Aiden Cline 8979741a32 Merge pull request #1760 from Ardakilic/fix/kilo/kimik26
Fix: Kimi k2.6 definition on Kilo Gateway
2026-05-12 15:50:53 -05:00
Frank 82851b9a3d update zen models 2026-05-12 16:44:41 -04:00
zxy_action ae511892d7 feat: add Auriko provider with 15 models
All models use [extends] to inherit from canonical definitions,
overriding only Auriko-specific pricing. Omits remove cost tiers
and features Auriko doesn't carry.

Models: claude-opus-4-{6,7}, claude-sonnet-4-6, deepseek-v4-{pro,flash},
gemini-{2.5-pro,2.5-flash,3.1-pro-preview}, grok-4.3, kimi-k2.{5,6},
minimax-m2-7{,-highspeed}, glm-5.1, qwen-3.6-plus
2026-05-12 13:34:34 -07:00
Sewer56 9312418242 Changed: Remove DSv4 Pro & MiniMax M2.7 from models.dev 2026-05-12 20:16:08 +01:00
vimtor 9828a0177d improve empty row 2026-05-12 19:49:03 +02:00
Arda Kılıçdağı 122627a852 fix: Kimi k2.6 definition on Kilo Gateway 2026-05-12 20:35:58 +03:00
vimtor 4dee8d0d34 minor improvements 2026-05-12 19:10:21 +02:00
Hiki 6883e793ce Create mimo-v2.5-pro.toml 2026-05-12 18:22:41 +02:00
Hiki e69064709b Update mimo-v2.5.toml 2026-05-12 18:19:52 +02:00
Hiki bd8e582b96 Create mimo-v2.5.toml 2026-05-12 18:03:09 +02:00
Shoubhit Dash 21945db90f Merge pull request #1662 from anomalyco/nxl/add-sarvam-provider
provider(sarvam): add chat models
2026-05-12 13:31:59 +05:30
Aiden Cline 1771e02be8 Merge pull request #1660 from Alex-wuhu/feat/novita-ai-sync-models
provider(novita-ai): sync latest models
2026-05-11 23:15:44 -05:00
Aiden Cline bb08fc26e9 Merge pull request #1706 from rohita5l/rohit/addDatabricks
Add Databricks as a provider
2026-05-11 19:28:34 -05:00
Aiden Cline 2e015de42d preserve generated tier thresholds 2026-05-11 17:07:19 -05:00
Rohit Agrawal 914a3d9d18 fix: restore bun.lock to use default registry instead of Databricks npm proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:54:26 -04:00
Rohit Agrawal bab01dd9ab refactor: move databricks generate to packages/core/script following repo conventions
Addresses review feedback by removing AI SDK dependencies from package.json
and aligning with the Vercel/Helicone/Wandb pattern.

- Move generate-databricks.ts to packages/core/script/
- Add databricks:generate to root scripts
- Remove smoke test and runtime filtering (catalog should reflect what the
  upstream API exposes; AI SDK compatibility is a downstream concern)
- Add --dry-run and --new-only flags
- Merge with existing TOMLs instead of nuking them; warn about orphans
- Restore databricks-gemini-3-pro and databricks-gemini-3-1-pro
- Drop @ai-sdk/openai-compatible, ai, zod from root dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:54:04 -04:00
Rohit Agrawal bdaae956af feat: add AI SDK compatibility test to generate script, remove incompatible models
Generate script now smoke-tests each model with streamText after writing TOMLs
and removes any that return empty responses (incompatible with @ai-sdk/openai-compatible).
Removes databricks-gemini-3-pro and databricks-gemini-3-1-pro which return content
as array with thoughtSignature that the AI SDK cannot parse.

Also adds test-databricks.ts for standalone smoke testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:53:44 -04:00
Rohit Agrawal 6f118145c0 fix: inline gpt-oss model metadata instead of invalid extends path
openrouter models in subdirectories can't use extends (schema requires
provider/model format); resolve() now inlines the source TOML content directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 17:53:44 -04:00
Rohit Agrawal 386eaed119 add databricks 2026-05-11 17:53:44 -04:00
Aiden Cline 151e9c9071 fix tiered cost generation 2026-05-11 16:43:32 -05:00
Isaac ba99f1edce Add GMI Cloud GLM models 2026-05-11 14:40:03 -07:00
Aiden Cline b96b074a3b fix long-context cost tier omissions 2026-05-11 16:31:07 -05:00
Aiden Cline 2593e131a1 wip 2026-05-11 16:11:19 -05:00
Aiden Cline 4aebbe5ca3 Merge pull request #1754 from BruceMacD/brucemacd/fix-ollama-kimi-k2-6-model-id
fix ollama cloud kimi k2.6 model id
2026-05-11 14:57:03 -05:00
Bruce MacDonald b98495e9c8 fix ollama cloud kimi k2.6 model id 2026-05-11 12:40:17 -07:00
Frank bc95b42ccd update zen models 2026-05-11 11:26:08 -04:00
Aiden Cline 6139fb8c69 Merge pull request #1598 from mugnimaestra/feat/chutes-generate-script
feat(chutes): add API-driven model generator script
2026-05-11 09:30:38 -05:00
Aiden Cline 3070758007 Merge pull request #1678 from 5kahoisaac/chore/nvidia-models
Sync NVIDIA endpoint model catalog
2026-05-11 09:29:54 -05:00
Frank 5525e83de4 update zen models 2026-05-11 10:00:09 -04:00
Frank 359fd879b8 update zen models 2026-05-10 03:54:03 -04:00
Frank 01b5a1a656 update zen models 2026-05-10 02:52:44 -04:00
Frank b1958be099 update zen models 2026-05-10 02:42:44 -04:00
Aiden Cline 08aa068523 Temporarily remove kiro provider and models 2026-05-10 01:19:47 -05:00
Aiden Cline f31ad0b02f Merge pull request #1738 from mattiacerutti/chore/remove-gh-copilot-deprecated
chore(copilot): mark deprecated models
2026-05-09 15:42:19 -05:00
Aiden Cline 585aa7fa1b Merge pull request #1741 from EriDeLee/dev
Update aihubmix models
2026-05-09 15:42:03 -05:00
Aiden Cline c42a327b3e Merge pull request #1745 from mads-digitial-solutions/patch-1
Update Google provider docs url from pricing page to models page
2026-05-09 15:41:51 -05:00
mads-digitial-solutions 92ebbfb5c4 Update provider.toml
Update Google provider docs URL from the pricing page to the models page
2026-05-09 19:49:19 +01:00
Aiden Cline 535fe8c971 Merge pull request #1744 from OpeOginni/fix/bedrock-model-ids
chore(bedrock): Getting rid of legacy Amazon Bedrock model offerings
2026-05-09 13:48:52 -05:00
OpeOginni a3b4bfc16c fix(bedrock): remove uneeded model configurations 2026-05-09 20:35:34 +02:00
OpeOginni d0fcd6f11f fix(bedrock): align models with current docs 2026-05-09 20:29:11 +02:00
OpeOginni e55cd54218 fix(bedrock): remove legacy model entries 2026-05-09 20:21:33 +02:00
OpeOginni 0d73b82b9f fix(bedrock): restore regional model IDs 2026-05-09 20:18:59 +02:00
Aiden Cline 83c7e2b63f Merge pull request #1742 from Adam8234/add-firepass-provider
feat: add Fireworks (Firepass) provider
2026-05-09 12:45:09 -05:00
Adam 83ae4cf813 feat: add Fireworks (Firepass) provider
Adds the Fireworks AI Firepass subscription provider.
- Provider uses a dedicated FIREPASS_API_KEY
- Uses @ai-sdk/openai-compatible SDK
- Includes Kimi K2.6 Turbo (accounts/fireworks/routers/kimi-k2p6-turbo)
- Zero per-token cost since covered by subscription
2026-05-09 12:22:13 -05:00
EriDeLee 77eae6eef7 Update aihubmix models 2026-05-09 20:53:57 +08:00
Aiden Cline 8cbf6ed10e Merge pull request #1736 from vercel/update-vercel-models-1778258030
Update Vercel models
2026-05-08 21:58:40 -05:00
Mattia Cerutti 06d87e4411 chore(copilot): remove deprecated models 2026-05-09 00:19:50 +02:00
Frank 2cb3832618 update zen models 2026-05-08 17:11:05 -04:00
vimtor 950a0446d4 bring back svg loading 2026-05-08 20:18:12 +02:00
vimtor 41fbfb1a17 change sst mention 2026-05-08 20:13:09 +02:00
vimtor cf5045a90b move copy button next to name 2026-05-08 20:12:39 +02:00
vimtor ef739220de lock virtualized table column widths to prevent scroll jitter 2026-05-08 19:07:25 +02:00
github-actions[bot] df960d1a90 chore(vercel): update Vercel model definitions
Auto-generated by weekly workflow from Vercel AI Gateway API.

Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-08 16:33:52 +00:00
vimtor 7294efee15 move row-render.ts to shared.ts 2026-05-08 18:30:14 +02:00
vimtor 1e537147eb remove table row tuple optimization 2026-05-08 18:24:18 +02:00
Aiden Cline 8f2f83ef61 Merge pull request #1735 from slpdy/dev
Create DeepSeek-V4-Pro.toml
2026-05-08 10:59:33 -05:00
Aiden Cline b133426465 Merge pull request #1734 from oskarkocol/chore/update-novita-deepseek-prices
chore: update novita deepseek-v4-pro prices
2026-05-08 10:59:25 -05:00
Aiden Cline dafff5a770 Merge pull request #1730 from smakosh/add-llmgateway-models
Add new LLM Gateway text models (gpt-5.5, grok-4-3, gemini-3.1-flash-lite, qwen3.6, MiMo v2)
2026-05-08 10:58:20 -05:00
smakosh dd894f077f Merge remote-tracking branch 'upstream/dev' into add-llmgateway-models
# Conflicts:
#	providers/google/models/gemini-3.1-flash-lite.toml
2026-05-08 17:46:52 +02:00
smakosh 91590874e7 Revert "fix(models): use canonical entries for qwen3.6-max-preview and grok-4.3"
This reverts commit 70ac6fccda.
2026-05-08 17:43:07 +02:00
vimtor e9f4cecc54 extract shared row rendering module 2026-05-08 17:29:04 +02:00
vimtor fb1ac09883 virtualize model table 2026-05-08 17:07:28 +02:00
Jj a436236146 Create DeepSeek-V4-Pro.toml
Added DeepSeek-v4-Pro model to Nebius provider
2026-05-08 11:03:16 +01:00
oskar 1415b4be97 chore: update novita deepseek prices 2026-05-08 14:21:27 +07:00
Aiden Cline 1437da86e7 Merge pull request #1685 from 8023/dev
Add kimi-k2.6/deepseek-v4 model and EmpirioLabs AI integration for poe.com
2026-05-07 22:38:51 -05:00
Aiden Cline e7d57885d1 Merge pull request #1733 from chl-0537/feature/add-tencent
add model by openrouter
2026-05-07 22:21:07 -05:00
Aiden Cline 8157916515 fix: attachment 2026-05-07 22:12:11 -05:00
Aiden Cline 2e5b87a9c2 Merge pull request #1731 from mikeyp/chore/update-digitalocean-models
Add script to generate/update Digitalocean models
2026-05-07 16:42:09 -05:00
Aiden Cline 92e19432d3 add google gemini 3.1 flash lite 2026-05-07 15:44:25 -05:00
smakosh 70ac6fccda fix(models): use canonical entries for qwen3.6-max-preview and grok-4.3
Apply the canonical TOML provided by the LLM Gateway team for the
Qwen3.6 Max Preview and Grok 4.3 parent definitions, replacing the
upstream-merged variants whose dates and pricing did not match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:34:01 +02:00
smakosh dc3283417d Merge remote-tracking branch 'upstream/dev' into add-llmgateway-models
# Conflicts:
#	providers/alibaba/models/qwen3.6-max-preview.toml
#	providers/llmgateway/models/qwen3.6-max-preview.toml
2026-05-07 22:28:23 +02:00
smakosh 34fd6673e5 chore(llmgateway): add new text models from llmgateway catalog
Add gemini-3.1-flash-lite, grok-4-3, gpt-5.5, gpt-5.5-pro, qwen3.6
and MiMo v2 models that exist in llmgateway.io but were missing
from models.dev. Adds parent definitions for grok-4-3,
gemini-3.1-flash-lite, and qwen3.6-max-preview where they did not
already exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 22:20:52 +02:00
Mike Prasuhn 5df9293314 Add script to generate/update Digitalocean models 2026-05-07 15:28:07 -04:00
Aiden Cline a81a9559d7 Merge pull request #1726 from Ardakilic/chore/sync-kilo-models
Chore: Sync Kilo models with upstream gateway
2026-05-07 13:04:51 -05:00
Aiden Cline 4197bf57a6 Merge pull request #1725 from dpuyosa/chore/venice-grok-costs
Venice: Update grok-4-20 pricing
2026-05-07 13:04:26 -05:00
Aiden Cline d0ac772507 Merge pull request #1717 from juls0730/refactor/mimo-extends/token-plan
refactor(xiaomi-token-plan): extends xiaomi base provider for xiaomi token plans
2026-05-07 13:04:05 -05:00
Aiden Cline 1afaa053e7 Merge pull request #1727 from sergeykonkin/update-nebius-models-2026-05
chore: update nebius provider models
2026-05-07 13:03:46 -05:00
Aiden Cline 9b77ce1c9e Merge pull request #1729 from Sewer56/add-minimax-m27-wafer
Added: MiniMax-M2.7 model for wafer.ai
2026-05-07 12:59:49 -05:00
Aiden Cline 68dc6d1820 Merge pull request #1728 from arafatkatze/codex/openrouter-qwen-cache-pricing
Add OpenRouter Qwen cache pricing
2026-05-07 12:59:29 -05:00
Arafatkatze a1eb5eece3 Add OpenRouter Qwen cache pricing 2026-05-07 10:29:48 -07:00
Sewer56 f7ec2c517f Added: wafer.ai/MiniMax-M2.7 model 2026-05-07 18:12:50 +01:00
Sergey Konkin f98e8ec793 chore: update nebius provider models 2026-05-07 15:37:18 +02:00
Arda Kilicdagi d210066793 chore: sync kilo gw models 2026-05-07 14:17:15 +03:00
Frank 06908cbf36 update zen models 2026-05-07 04:47:22 -04:00
dpuyosa b0614d2088 [venice] Update grok-4-20 pricing
- Lower grok-4-20 and multi-agent input/output costs to latest Venice pricing
2026-05-07 10:29:48 +02:00
mickalchen 32c1c45c52 add openrouter model 2026-05-07 11:22:11 +08:00
Frank 7c033f27e6 update zen models 2026-05-06 23:00:15 -04:00
mickalchen d648e63499 Merge remote-tracking branch 'origin/dev' into feature/add-tencent 2026-05-07 10:54:56 +08:00
Zoe 1353f965b9 refactor(xiaomi-token-plan): extends xiaomi base provider for xiaomi token plan 2026-05-06 18:53:49 -05:00
Isaac Huang 175082d43f Add GMI Cloud provider 2026-05-06 15:12:07 -07:00
Aiden Cline bba0a9c3f4 Merge pull request #1312 from NachoFLizaur/feat/kiro-provider
feat: add Kiro provider with 12 models
2026-05-06 12:16:47 -05:00
Frank 4bdb195178 update zen models 2026-05-06 12:56:58 -04:00
Aiden Cline 12706b7652 Merge pull request #1716 from juls0730/refactor/mimo-extends/zenmux
refactor(zenmux): extends mimo models from xiaomi provider
2026-05-06 10:45:01 -05:00
Aiden Cline d8c76d0c67 Merge pull request #1715 from juls0730/refactor/mimo-extends/qiniu-ai
refactor(qiniu-ai): extends mimo models from xiaomi provider
2026-05-06 10:30:46 -05:00
Aiden Cline 3963dd13d5 Merge pull request #1714 from juls0730/refactor/mimo-extends/openrouter
refactor(openrouter): extends mimo models from xiaomi provider
2026-05-06 10:30:32 -05:00
Aiden Cline 8749a56efa Merge pull request #1711 from juls0730/refactor/mimo-extends/kilo
refactor(kilo/xiaomi): extends mimo models from xiaomi provider
2026-05-06 10:29:31 -05:00
Aiden Cline 25de2ee27d Merge pull request #1718 from juls0730/refactor/mimo-extends/xiaomi
refactor(xiaomi): round xiaomi models to powers of 2 & fix small errors
2026-05-06 10:27:36 -05:00
Aiden Cline 438df7f03c Merge pull request #1723 from CloudFerro/fix/cloudferro-sherlock/minimax-m2.5
fix: cloudferro sherlock - update context values for minimax m2.5
2026-05-06 10:25:54 -05:00
Aiden Cline 7d18558aa3 Merge pull request #1722 from dpuyosa/chore/venice-model-updates
Venice: Remove deprecated models, enable reasoning on gpt-oss-120b
2026-05-06 10:25:36 -05:00
Jan Szypulski c82f736fcc fix: cloudferro sherlock - update context values for minimax m2.5 2026-05-06 10:52:39 +02:00
dpuyosa 6a436805b3 [venice] Remove deprecated models, enable reasoning on gpt-oss-120b
- Remove kimi-k2-thinking, qwen3-coder-480b-a35b-instruct, and venice-uncensored models
- Enable reasoning capability on openai-gpt-oss-120b
2026-05-06 09:44:24 +02:00
Jack ce7823f073 Merge pull request #1720 from anomalyco/fix/opencode-go-kimi-k26-pricing
fix(opencode-go): restore kimi k2.6 pricing
2026-05-06 12:33:24 +08:00
Jack 033efdb7d4 fix(opencode-go): restore kimi k2.6 pricing 2026-05-06 12:32:06 +08:00
Alex-wuhu 0f1855c0a7 provider(novita-ai): use extends for kimi k2.6 2026-05-06 10:41:46 +08:00
Zoe bd9e0c2677 refactor(xiaomi): round xiaomi models to powers of 2 & fix small errors 2026-05-05 18:40:05 -05:00
Zoe 38545d63a5 refactor(zenmux): extends mimo models from xiaomi provider 2026-05-05 18:17:21 -05:00
Zoe 014be328d6 refactor(qiniu-ai): extends mimo models from xiaomi provider 2026-05-05 18:05:39 -05:00
Zoe c139147540 refactor(openrouter): extends mimo models from xiaomi provider 2026-05-05 17:49:39 -05:00
Zoe 59cd93cafc refactor(kilo/xiaomi): extends mimo models from xiaomi provider 2026-05-05 17:13:06 -05:00
Aiden Cline e91db96d83 Merge pull request #1710 from Spherrrical/add-digitalocean-kimi-2-6
feat(digitalocean): add kimi-k2.6 model
2026-05-05 14:07:50 -05:00
Spherrrical d70dd8dcdc feat(digitalocean): add kimi-k2.6 model 2026-05-05 12:05:16 -07:00
Frank b18e681457 update deepseek flash on deepinfra 2026-05-05 14:24:03 -04:00
Aiden Cline b73a6a2130 Merge pull request #1709 from xiaomochn/fix/xiaomi-mimo-v2.5-modalities
fix(xiaomi): swap modalities for MiMo-V2.5 and MiMo-V2.5-Pro
2026-05-05 11:36:43 -05:00
Aiden Cline 153c1cc420 Merge pull request #1614 from Yashwanth-Kumar-26/patch-1
Add Qwen 3.6 27B model configuration
2026-05-05 11:22:43 -05:00
Aiden Cline ca0b30569e update google vertex to include all anthropic models 2026-05-05 11:12:23 -05:00
xiaomochn 8345bfbd06 fix(xiaomi): correct modalities for MiMo V2.5 models across providers
Issues fixed:
1. MiMo-V2.5 and MiMo-V2.5-Pro had their modalities swapped in the
   xiaomi canonical source (affects OpenRouter/ZenMux via extends)
2. Removed 'pdf' from V2.5 models — not a supported input modality
3. Fixed vercel provider: V2.5-Pro incorrectly marked as multimodal
4. Fixed opencode-go and vercel V2.5: added missing 'video', removed pdf

Correct modalities:
- MiMo-V2.5: input = ["text", "image", "audio", "video"]
- MiMo-V2.5-Pro: input = ["text"]

Affected providers: xiaomi, opencode-go, vercel, openrouter (extends),
zenmux (extends)

Fixes #1708
2026-05-05 22:46:44 +08:00
Shoubhit Dash 28c0d9ce23 fix(sarvam): correct output limits 2026-05-05 15:30:47 +05:30
Aiden Cline c16f3da694 Merge pull request #1707 from deathbeam/revert-1664-fix/glm-qwen-ollama-output-limit
Revert "fix(ollama): set glm-5.1 and qwen3.5:397b output limits to match context"
2026-05-04 23:51:53 -05:00
Tomas Slusny 6aa6e55d60 fix(ollam): use correct output limit for qwen3.5:397b
{"error":"max_tokens (262144) exceeds model's maximum output tokens (65536) for model qwen3.5:397b (ref: 8554a681-e6a8-45d7-9fdd-433785eb6c67)"}

Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
2026-05-05 01:31:20 +02:00
Tomas Slusny 9efaf754a5 Revert "fix(ollama): set glm-5.1 and qwen3.5:397b output limits to match context" 2026-05-05 01:04:11 +02:00
Aiden Cline 104e4bdc1f Merge pull request #1684 from TheBaconWizard/add-clarifai-kimi-k2.6
provider(clarifai): add Kimi-K2.6 (moonshotai/chat-completion)
2026-05-04 12:00:33 -05:00
Aiden Cline db16c113f7 Merge pull request #1704 from stylings/stylings/add-openrouter-grok-4-3
feat: add OpenRouter Grok 4.3
2026-05-04 12:00:08 -05:00
Alex 64a122a13d fix: update OpenRouter Grok 4.3 file 2026-05-04 12:44:07 -04:00
Alex fcc6521d1d fix: simplify OpenRouter Grok 4.3 file 2026-05-04 12:41:56 -04:00
Alex c623b4a55f feat: add OpenRouter Grok 4.3 2026-05-04 12:36:33 -04:00
Aiden Cline 1d730fea16 Merge pull request #1696 from cgilly2fast/dev
chore(frogbot): convert firmware provider to frogbot
2026-05-04 10:26:36 -05:00
Aiden Cline 5457215e29 Merge pull request #1650 from PedroACosta/feat/add-dinference-models
feat(dinference): add GLM-5.1 and MiniMax-M2.5 models
2026-05-04 10:26:02 -05:00
Aiden Cline b3ab45990f Merge pull request #1697 from rocuevas9511/feat/add-deepinfra-gemma4
add gemma4 26b a4b and 31b to deepinfra
2026-05-04 10:25:31 -05:00
Aiden Cline d906a07e31 Merge pull request #1703 from dpuyosa/feat/venice-grok-4-3
Venice: Add Grok 4.3 model configuration
2026-05-04 10:25:16 -05:00
dpuyosa 4b1f6edd52 [venice] Add Grok 4.3 model configuration
- Add Grok 4.3 model with 1M context and 32K output
- Configure standard and >200K cost tiers
- Enable text+image input with text output modalities
2026-05-04 09:54:01 +02:00
Aiden Cline a92a2cfe3d Merge pull request #1702 from langyo/fix/glm-5v-turbo-naming
fix: use proper GLM family casing for GLM-5V-Turbo
2026-05-03 16:53:44 -05:00
Aiden Cline 70891f58e5 Merge pull request #1701 from kaeltrn/add-perplexity-agent-opus-4-7-gpt-5-5
Add Claude Opus 4.7 and GPT-5.5 models for perplexity-agent
2026-05-03 16:53:26 -05:00
Aiden Cline 1600c827fc Merge pull request #1698 from tim-mcdonald/add-kimi-k2.6-nvidia
Add Kimi K2.6 model for NVIDIA provider
2026-05-03 16:53:01 -05:00
Aiden Cline 5851cdc136 Merge pull request #1700 from JDinABox/dev
Add Synthetic Kimi-K2.6 model configuration
2026-05-03 16:52:46 -05:00
langyo 4fd0e38c58 fix: use proper GLM family casing for GLM-5V-Turbo
- Rename glm-5v-turbo to GLM-5V-Turbo in zai, zhipuai, and 302ai providers
- Add GLM-5V-Turbo back to zhipuai-coding-plan (removed in #1589)

Ref: #1589
2026-05-04 00:51:54 +08:00
Pedro dd685ea42c refactor(dinference): use extends format for GLM and MiniMax models 2026-05-03 17:54:18 +02:00
kaeltrn 7835298241 Add Claude Opus 4.7 and GPT-5.5 models for perplexity-agent 2026-05-03 20:09:14 +07:00
Isaac Ng c5fbcc2c9b 📦 CHORE: remove senera 2026-05-03 16:17:37 +08:00
Isaac Ng ab2eb51b4e chore(nvidia): align Nemotron endpoint slugs
Replace stale NVIDIA Nemotron entries with the live Build catalog slugs so the local provider catalog matches current free and partner endpoints.
2026-05-03 15:47:19 +08:00
Isaac Ng 8e19ec580c 📦 CHORE: sync latest nvidia model 2026-05-03 15:19:17 +08:00
Isaac Ng 3aecc94c46 chore(nvidia): sync endpoint model catalog
Update NVIDIA model TOMLs to match the live Build endpoint list by removing stale entries and adding missing ones.

This keeps the provider catalog aligned with the current free and partner endpoint inventory.
2026-05-03 14:47:30 +08:00
JD Crawford 2ac7912ee7 use extends format 2026-05-03 01:04:53 -04:00
JD Crawford 9ac5b5f625 Add Synthetic Kimi-K2.6 model configuration 2026-05-03 00:17:37 -04:00
Aiden Cline 8c4d9f4696 Merge pull request #1699 from cfal/qwen3.6-max-preview
providers/alibaba/models/qwen3.6-max-preview.toml: add qwen 3.6 max
2026-05-02 21:35:02 -05:00
cfal c4bb0b4b11 providers/alibaba/models/qwen3.6-max-preview.toml: add qwen 3.6 max 2026-05-03 09:25:31 +08:00
Tim McDonald db4d03c171 Add Kimi K2.6 model for NVIDIA provider 2026-05-02 16:09:37 -06:00
rocuevas9511 60d1d4df77 add gemma4 26b a4b and 31b to deepinfra 2026-05-02 14:26:19 -06:00
Colby Gilbert 31654fc2ef chore(frogbot): convert firmware provider to frogbot 2026-05-02 12:36:20 -07:00
Aiden Cline 01e56b1e1e Merge pull request #1682 from varunrandery/poolside/laguna-openrouter
Add Poolside Laguna series models (OpenRouter)
2026-05-02 14:30:20 -05:00
Aiden Cline 4a7d9275d6 Merge pull request #1695 from hgraca/cortecs
Cortecs
2026-05-02 14:04:00 -05:00
Aiden Cline 978fe11e0c Merge pull request #1694 from cgilly2fast/dev
feat(firmware): add deepseek v4, remove gemini 3 pro add gpt 5.4 min,…
2026-05-02 14:03:14 -05:00
Herberto Graca 0b4198955f refactor(cortecs): use extends for models with canonical bases
Convert 7 Cortecs models to extends format, inheriting from their
canonical provider definitions (deepseek, llama, mistral, alibaba).
Reduces duplication by ~55 lines while preserving Cortecs-specific
cost overrides.
2026-05-02 20:56:11 +02:00
Herberto Graca 66b568b681 Add deepseek-v4-pro model for cortecs 2026-05-02 20:56:11 +02:00
Herberto Graca 589fddeed7 Add codestral-2508 model for cortecs 2026-05-02 20:56:10 +02:00
Herberto Graca 6f75266d42 Add deepseek-v3.2 model for cortecs 2026-05-02 20:56:10 +02:00
Herberto Graca 0f03115ee0 Add deepseek-r1-0528 model for cortecs 2026-05-02 20:56:09 +02:00
Herberto Graca ae2564edc2 Add mixtral-8x7B-instruct-v0.1 model for cortecs 2026-05-02 20:56:09 +02:00
Herberto Graca 629856cf41 Add hermes-4-70b model for cortecs 2026-05-02 20:56:08 +02:00
Herberto Graca 567bc13ab4 Add llama-3.3-70b-instruct model for cortecs 2026-05-02 20:56:08 +02:00
Herberto Graca 5eac173bc5 Add qwen3-235b-a22b-instruct-2507 model for cortecs 2026-05-02 20:56:07 +02:00
Herberto Graca cb0d640afd Add qwen3-coder-30b-a3b-instruct model for cortecs 2026-05-02 20:56:07 +02:00
Herberto Graca b5e27e652f Add nemotron-3-super-120b-a12b model for cortecs 2026-05-02 20:56:06 +02:00
Herberto Graca 650c27411f Add qwen3.5-122b-a10b model for cortecs 2026-05-02 20:56:06 +02:00
Herberto Graca 98c55325bf Add mistral-large-2512 model for cortecs 2026-05-02 20:56:05 +02:00
Herberto Graca 2fdd33b8a7 Add qwen3.5-397b-a17b model for cortecs 2026-05-02 20:55:57 +02:00
Colby Gilbert e4dea0a3d8 feat(firmware): grok 4.3 2026-05-02 10:53:24 -07:00
Colby Gilbert b4b3622bfd feat(firmware): add deepseek v4, remove gemini 3 pro add gpt 5.4 min, add gpt 5.4 nano, add gpt 5.5, and minimax m2.7 2026-05-02 09:23:42 -07:00
Aiden Cline 3b35b5598a Merge pull request #1693 from hgraca/add-deepseek-v4-flash-cortecs
Add deepseek-v4-flash model for cortecs
2026-05-02 10:46:57 -05:00
Aiden Cline d2e16bab34 Merge pull request #1610 from monotykamary/feat/neuralwatt-provider
feat: add neuralwatt provider with 14 models
2026-05-02 10:45:50 -05:00
Tom X Nguyen 051dc6236a fix(neuralwatt): sync model capabilities with provider API 2026-05-02 20:42:00 +07:00
Herberto Graca 5b9dee1f3d Add deepseek-v4-flash model for cortecs 2026-05-02 12:50:59 +02:00
8023 426dfe7be5 fix validate error 2026-05-02 12:15:50 +08:00
8023 fddfb083d1 add EmpirioLabs AI and deepseek v4 2026-05-02 11:14:51 +08:00
8023 4eccfaba87 Add Kimi-K2.6 model configuration file 2026-05-02 11:08:32 +08:00
Jeff Lim ea46d016d3 fix(clarifai): drop redundant cost override for Kimi-K2.6
Clarifai's published pricing ($0.95 input, $4.00 output) matches the
canonical moonshotai/kimi-k2.6, so the explicit [cost] block was just
duplicating upstream. Inherit it via extends instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:20:39 -07:00
Jeff Lim 1bd1449ec5 provider(clarifai): add Kimi-K2.6 (moonshotai/chat-completion)
Extends moonshotai/kimi-k2.6 with Clarifai-specific cost and modalities
(text+image only on Clarifai; cache pricing not exposed).

Model URL: https://clarifai.com/moonshotai/chat-completion/models/Kimi-K2_6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:14:19 -07:00
Varun Randery 7d334a51c9 Add Laguna models 2026-05-01 23:35:13 +01:00
Aiden Cline 10ae0b5a1d Merge pull request #1664 from fernandoenzo/fix/glm-qwen-ollama-output-limit
fix(ollama): set glm-5.1 and qwen3.5:397b output limits to match context
2026-05-01 15:50:22 -05:00
Aiden Cline d25cce170d Merge pull request #1665 from Ardakilic/chore/cleanup-kilo-provider
Chore: Sync Kilo Gateway provider models with upstream API changes and add Owl Alpha model.
2026-05-01 15:49:53 -05:00
Aiden Cline 3e097a5d89 Merge pull request #1671 from hgraca/add-qwen-2.5-72b-instruct-cortecs
Add qwen-2.5-72b-instruct model for cortecs
2026-05-01 15:49:28 -05:00
Aiden Cline 074b38eb98 Merge pull request #1663 from fernandoenzo/fix/minimax-m2.7-ollama-context-output-limit
fix(ollama): set minimax-m2.7 context and output limits to match Ollama API
2026-05-01 14:24:35 -05:00
Aiden Cline d474922588 Merge pull request #1679 from Spherrrical/add-digitalocean-deepseek-v4-pro
feat(digitalocean): add deepseek-v4-pro model
2026-05-01 13:46:50 -05:00
Spherrrical 56ddb9017c feat(digitalocean): add deepseek-v4-pro model 2026-05-01 11:20:09 -07:00
Rohan Taneja a565bbebc0 Merge pull request #1677 from vercel/update-vercel-models-1777652649 2026-05-01 10:47:39 -07:00
github-actions[bot] 5b97fca592 chore(vercel): update Vercel model definitions
Auto-generated by weekly workflow from Vercel AI Gateway API.

Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-01 16:24:16 +00:00
Arda Kilicdagi aec3f94081 feat: owl alpha, chore: sync kilo code upstream
feat: owl alpha, chore: sync kilo code upstream
2026-05-01 14:09:51 +03:00
Fernando Guarini e5c63d671e fix(ollama): set glm-5.1 and qwen3.5:397b output limits to match context 2026-05-01 11:29:49 +02:00
Fernando Guarini 22e87ab31b fix(ollama): set minimax-m2.7 context and output limits to match Ollama API 2026-05-01 11:29:41 +02:00
Shoubhit Dash 90515f1913 provider(sarvam): add chat models 2026-05-01 14:41:47 +05:30
Herberto Graca 97d93676bc Add qwen-2.5-72b-instruct model for cortecs 2026-05-01 09:36:16 +02:00
Alex-wuhu 6c3c4a721c provider(novita-ai): sync latest models 2026-05-01 14:19:17 +08:00
Aiden Cline 692fbd0f19 Merge pull request #1628 from fanweixiao/dev
provider(vivgrid): remove GLM-5, add GPT-5.5 and DeepSeek-v4-Pro model
2026-04-30 23:57:52 -05:00
C.C. Fan c3bd263a67 provider(vivgrid): add deepseek-v4-pro model
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 11:46:39 +08:00
Aiden Cline 9b9685c297 Merge pull request #1658 from v1gnesh/dev
Create grok-4.3.toml
2026-04-30 22:46:08 -05:00
C.C. Fan b3f063da79 provider(vivgrid): use extends for gpt-5.5 instead of duplicating fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 11:45:13 +08:00
C.C. a33d0aea81 Merge branch 'anomalyco:dev' into dev 2026-05-01 11:38:42 +08:00
v1gnesh ca380a136e Create grok-4.3.toml 2026-05-01 08:17:12 +05:30
Arda Kılıçdağı dfa186a768 feat: owl alpha 2026-05-01 01:47:25 +03:00
Aiden Cline d63ffa53e8 Merge pull request #1654 from zainhas/dev
[Together AI] add qwen 3.6 plus
2026-04-30 16:34:28 -05:00
Aiden Cline 284def86ef Merge pull request #1653 from smakosh/feat/llmgateway-add-gpt-5-5-and-qwen3-6
feat(llmgateway): add gpt-5.5, gpt-5.5-pro, qwen3.6-35b-a3b, qwen3.6-plus, qwen3.6-max-preview
2026-04-30 16:34:18 -05:00
Siddharth Dhulipalla c19936565d Remove Fire Pass from Fireworks Kimi K2.5 Turbo description (#1655) 2026-04-30 17:28:36 -04:00
Zain Hasan 73b872e141 output 500_000 2026-04-30 14:22:41 -07:00
Zain Hasan 7373bb5878 [Together AI] add qwen 3.6 plus 2026-04-30 14:21:40 -07:00
smakosh d11c151c25 feat(llmgateway): add gpt-5.5, gpt-5.5-pro, qwen3.6-35b-a3b, qwen3.6-plus, qwen3.6-max-preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 22:05:40 +02:00
Aiden Cline f3f4fea66c Merge pull request #1645 from stylings/stylings/add-mistral-medium-3-5
feat: add Mistral Medium 3.5
2026-04-30 14:37:16 -05:00
Aiden Cline 90116256a6 Merge pull request #1652 from Spherrrical/add-digitalocean-provider
feat(digitalocean): sync model catalog
2026-04-30 14:32:07 -05:00
Spherrrical 5008df8bcc feat(digitalocean): sync model catalog with /v1/models API
Add 16 new models (anthropic, openai, alibaba, deepseek, google,
meta, mistral, nvidia, baai, intfloat, fal-hosted) to match the
current DigitalOcean Gradient AI Platform catalog, and rename
openai-gpt-5-2-pro to openai-gpt-5.2-pro to match the API id.
2026-04-30 12:08:57 -07:00
Alex 696aa80dec revert(openrouter): remove Mistral Medium 3.5 stub 2026-04-30 14:59:56 -04:00
Aiden Cline 7d00d863dc Merge pull request #1647 from dpuyosa/chore/venice-kimi-pricing
Venice: Update Kimi K2.5 and K2.6 pricing and dates
2026-04-30 11:29:08 -05:00
Aiden Cline ad9eb83b8c Merge pull request #1648 from Snat3r/patch-1
Fix casing in model name MiniMax m2.7 foor cortects provider
2026-04-30 11:28:55 -05:00
Aiden Cline 467da9a82c Merge pull request #1649 from berget-ai/feat/berget-mistral-medium-3.5
feat: add Mistral Medium 3.5 128B to berget.ai
2026-04-30 11:28:43 -05:00
Pedro 22786bcf4b feat(dinference): add GLM-5.1 and MiniMax-M2.5 models 2026-04-30 13:43:44 +02:00
Christian Landgren c8d258b7cb feat: add Mistral Medium 3.5 128B to berget.ai 2026-04-30 12:31:28 +02:00
Snat3r 70309829ca Fix casing in model name and update output limit 2026-04-30 11:59:44 +02:00
dpuyosa 71e00f193b [venice] Update Kimi K2.5 and K2.6 pricing and dates
- Bump kimi-k2-5 cache_read from 0.11 to 0.22
- Bump kimi-k2-6 input from 0.7448 to 0.85 and cache_read from 0.1463 to 0.22
- Update last_updated dates to 2026-04-30
2026-04-30 11:18:54 +02:00
Alex d44e724170 feat(openrouter): add Mistral Medium 3.5 2026-04-29 18:58:26 -04:00
Alex 414695db9f feat(mistral): add Mistral Medium 3.5 2026-04-29 18:44:28 -04:00
Aiden Cline e8b5a27723 Merge pull request #1642 from Sewer56/add-wafer-deepseek-v4-pro
feat(wafer.ai): add DeepSeek V4 Pro
2026-04-29 17:14:17 -05:00
Aiden Cline 6dc9d805db Merge pull request #1643 from Sawyerb/patch-1
Delete providers/vercel/models/inception/mercury-coder-small.toml
2026-04-29 17:14:00 -05:00
Aiden Cline bdf56c9111 add kimi k2.6 to azure cognitive services 2026-04-29 17:13:19 -05:00
Sewer56 293cc3e82f feat(wafer.ai): add DeepSeek V4 Pro 2026-04-29 23:12:04 +01:00
Sawyer Birnbaum 7f5bd4f231 Delete providers/vercel/models/inception/mercury-coder-small.toml
Mercury Coder Small has been deprecated. People should use Mercury Edit 2 instead.
2026-04-29 14:50:35 -07:00
Aiden Cline c4826babc5 Merge pull request #1497 from Lydanne/fix/302ai-models
Update 302ai model metadata and add GPT-5.4 configs
2026-04-29 14:26:01 -05:00
Rohan Taneja 91e8bb985e Merge pull request #1641 from vercel/update-vercel-models-1777480545
Update Vercel models
2026-04-29 11:29:45 -07:00
Aiden Cline 56723051d4 Merge pull request #1615 from deaquino/dev
Add Qwen3.5-9B model configuration file to OVHCloud
2026-04-29 13:09:19 -05:00
Aiden Cline 0bb3e55c08 Merge pull request #1640 from dpuyosa/chore/venice-update-pricing
Venice: Update DeepSeek v4 and Qwen 3.6 model pricing and metadata
2026-04-29 13:02:40 -05:00
github-actions[bot] 080b7a328b chore(vercel): update Vercel model definitions
Auto-generated by weekly workflow from Vercel AI Gateway API.

Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-29 16:35:47 +00:00
Misha Skvortsov c59c4aae6c feat(atomic-chat): add curated initial model list
Re-introduces a small curated list of models that ship preconfigured
in Atomic Chat, so opencode users get a working `models.dev` entry
out of the box instead of an empty `models: {}`.

Models (ids match the normalized form returned by Atomic Chat's
/v1/models endpoint, i.e. dots replaced with underscores):

- gemma-4-E4B-it-IQ4_XS
- gemma-4-E4B-it-MLX-4bit
- Qwen3_5-9B-Q4_K_M
- Qwen3_5-9B-MLX-4bit
- Meta-Llama-3_1-8B-Instruct-GGUF

Qwen 3.5 9B dates are taken from the verified providers/venice entry
for the same base model; quantization does not change release dates.

Made-with: Cursor
2026-04-29 17:52:56 +03:00
Frank c5d696583e update zen models 2026-04-29 09:38:00 -04:00
Mike Sukmanowsky 2cb5a99b98 fix: add model card links for Kimi K2 and Kimi K2.5 2026-04-29 09:29:36 -04:00
dpuyosa 70490d892f [venice] Update DeepSeek v4 and Qwen 3.6 model pricing and metadata
- Reduce DeepSeek v4 Flash/Pro input and output pricing
- Add cache_read pricing for DeepSeek v4 models
- Fix Qwen 3.6 27B model name formatting
2026-04-29 09:39:11 +02:00
Aiden Cline f858a85ba6 Merge pull request #1625 from xinrui-z/fix-aihubmix-2026-04-28
fix: sync AIHubMix models (2026-04-28)
2026-04-28 23:03:28 -05:00
Aiden Cline 44e4c92882 Merge pull request #1620 from kill74/add-zai-coding-plan-glm-5v-turbo
Add GLM-5V-Turbo to Z.ai coding plan
2026-04-28 19:28:48 -05:00
Aiden Cline 43eafcb258 Merge pull request #1581 from xiaojiezj/zenmux_0425
feat: add models for zenmux provider
2026-04-28 19:27:38 -05:00
Aiden Cline b382ac7af9 Merge branch 'dev' into zenmux_0425 2026-04-28 19:08:09 -05:00
Tom X Nguyen ca21110644 fix: sync neuralwatt models with updated API pricing and capabilities
The Neuralwatt API now returns accurate pricing and capabilities,
eliminating the need for manual patches (patch.json is now empty).

Changes:
- Update pricing for all 14 models from API (significant changes for
  GLM, GPT-OSS, Qwen, and MiniMax models)
- Devstral Small 2 now supports image input (vision)
- kimi-k2.5-fast now supports image input (vision)
- kimi-k2.6-fast now supports reasoning + image input (was non-reasoning)
- Qwen3.6-35B-A3B now supports reasoning (was non-reasoning)
- GLM models context window: 202,752 → 200,000
- Rename fast variant model IDs to match API (dropped org prefix):
  zai-org/glm-5-fast → glm-5-fast
  zai-org/glm-5.1-fast → glm-5.1-fast
  moonshotai/kimi-k2.5-fast → kimi-k2.5-fast
  moonshotai/kimi-k2.6-fast → kimi-k2.6-fast
  Qwen/qwen3.5-397b-fast → qwen3.5-397b-fast
  Qwen/qwen3.6-35b-fast → qwen3.6-35b-fast
2026-04-29 07:05:23 +07:00
Mike Sukmanowsky 0c2e47e8ba Fix token limits for Amazon Bedrock Kimi K2 models
Correct context and output limits for moonshot.kimi-k2-thinking and
moonshotai.kimi-k2.5 on Amazon Bedrock:
- context: 256_000 → 262_143
- output: 256_000 → 16_000
2026-04-28 17:45:59 -04:00
Aiden Cline 6a0704574b Merge pull request #1621 from YuzhongHuangCS/dev
feat(wandb): Add GLM-5.1
2026-04-28 15:51:43 -05:00
Aiden Cline 1cb1341516 Merge pull request #1635 from stylings/feat/nemotron-3-nano-omni
feat: add Nemotron 3 Nano Omni model
2026-04-28 15:04:07 -05:00
Alex bc47e95427 fix: rename Nemotron Omni metadata 2026-04-28 15:20:24 -04:00
Aiden Cline b071e8add8 Merge pull request #1619 from fernandoenzo/fix/deepseek-v4-pro-ollama-cloud
fix(ollama-cloud): correct deepseek-v4-pro model config
2026-04-28 14:00:42 -05:00
Aiden Cline 23e527753e Merge pull request #1623 from itsnebulalol/dev
feat: add gpt-5.5 pro on openai and openrouter
2026-04-28 14:00:34 -05:00
Aiden Cline e81c045ed0 Merge pull request #1636 from dsingal0/feat/openrouter-deepseek-v4
feat(baseten): add DeepSeek V4 Pro
2026-04-28 13:49:11 -05:00
Dhruv Singal 0c602ca936 feat(baseten): update DeepSeek V4 Pro pricing 2026-04-28 11:45:02 -07:00
Dhruv Singal 9866f84989 feat(baseten): add DeepSeek V4 Pro 2026-04-28 11:38:22 -07:00
Alex 6b397ffe37 fix: align nvidia output limit 2026-04-28 14:35:31 -04:00
Alex bc21596889 fix: drop openrouter provider prefix 2026-04-28 14:22:04 -04:00
Alex 20abba3190 feat: add Nemotron 3 Nano Omni 2026-04-28 14:17:22 -04:00
Dominic Frye 5881bf98a0 fix: enable pdf input modality for gpt-5.5 pro 2026-04-28 13:33:31 -04:00
Aiden Cline 595f7d028c Merge pull request #1632 from rocuevas9511/feat/deepinfra-deepseek-v4-pro
feat: add DeepSeek-V4-Pro to deepinfra
2026-04-28 12:07:22 -05:00
rocuevas9511 c81dec9c5d feat: add DeepSeek-V4-Pro to deepinfra 2026-04-28 10:56:44 -06:00
Guiii 4d45ed25d2 Use extended GLM-5V-Turbo config
Removed various fields and added extends section.
2026-04-28 17:49:34 +01:00
Yuzhong Huang 03cf48de53 use extends instead 2026-04-28 09:19:26 -07:00
Aiden Cline 0d3a284395 Merge pull request #1622 from eduqr/feat/fireworks-ai-deepseek-v4-pro
feat(fireworks-ai): add deepseek-v4-pro
2026-04-28 10:52:54 -05:00
Aiden Cline 5b1bb0fc80 Merge pull request #1624 from shelvick/add-azure-kimi-k2-6
Add Kimi K2.6 to Azure
2026-04-28 10:38:07 -05:00
Aiden Cline 332ebb8811 Merge pull request #1627 from ceoAppsknight/kilo/add-mimo-models
Add Kilo Mimo v2.5 models
2026-04-28 10:37:40 -05:00
Aiden Cline 2111813bd4 Merge pull request #1629 from ndeybach/PR-azure-5.4-limits
fix(azure): correct GPT-5.4 series limits and cleanup
2026-04-28 10:37:01 -05:00
Nils DEYBACH f5b8521af6 fix: use extends and not symlinks 2026-04-28 17:34:44 +02:00
Nils DEYBACH 79481cff40 fix(azure): update GPT-5.4 metadata
Use `extends` for Azure GPT-5.4 variants and keep Azure-specific overrides for
PDF input and omitted fast mode.

Validated with `bun validate`.

Azure runtime manual probing confirmed GPT-5.4 uses the documented 1.05M context /
922K input / 128K output limits.
2026-04-28 14:00:20 +02:00
Nils DEYBACH 40dc356d4c fix(azure): correct GPT-5.4 and GPT-5.4 Pro limits (and convert to extend)
Correct Azure GPT-5.4 and GPT-5.4 Pro limits to `1_050_000` context,
`922_000` input, and `128_000` output based on Azure runtime results and
Microsoft Learn docs. Mini and Nano already matched and are unchanged.

The limits were tested directly (see script at : https://github.com/ndeybach/Azure_endpoint_limit_test_script )
2026-04-28 12:33:27 +02:00
C.C. Fan f929fe89e7 provider(vivgrid): remove GLM-5, add GPT-5.5 model 2026-04-28 16:44:59 +08:00
Syed Assadullah Shah cbd245d454 add Kilo Mimo v2.5 models 2026-04-28 13:04:36 +05:00
xinrui e5289e9b3a fix: sync AIHubMix models (2026-04-28) 2026-04-28 11:33:25 +08:00
Scott Helvick bb623f3ff9 Add Kimi K2.6 to Azure 2026-04-28 02:28:46 +00:00
Dominic Frye 37fffafafc feat: add gpt-5.5 pro on openai and openrouter 2026-04-27 22:24:32 -04:00
eduqr d329310745 feat(fireworks-ai): add deepseek-v4-pro 2026-04-27 21:05:55 -05:00
Yuzhong Huang 8016a6c45a Add GLM-5.1 to wandb provider 2026-04-27 17:35:38 -07:00
Guilherme Sales 1eeaa0b756 Add GLM-5V-Turbo to Z.ai coding plan 2026-04-28 00:47:15 +01:00
Frank dd3533b4e0 update zen models 2026-04-27 19:31:24 -04:00
Fernando Guarini 3a5867834f fix(ollama-cloud): correct deepseek-v4-pro model config
- Remove fields that don't belong in ollama-cloud: temperature, structured_output, knowledge, interleaved
- Set output = context (1048576) per ollama-cloud convention
- Set name to lowercase per ollama-cloud convention
- Reorder fields to match existing ollama-cloud model files
2026-04-28 00:24:54 +02:00
Aiden Cline 1e83bca7a3 Merge pull request #1617 from JoshuaDietz/dev
feat(ollama cloud): add deepseek v4 pro
2026-04-27 16:42:56 -05:00
Aiden Cline cd8853f88b Merge pull request #1616 from fhennerkes/dev
poe: add GPT-5.5 and GPT-5.5-Pro models
2026-04-27 16:08:36 -05:00
Joshua Dietz 23b290c6a8 fix(ollama cloud): fix model name
Model name was inconsistent with naming schema of flash model on ollama cloud
2026-04-27 21:47:30 +02:00
fhennerkes 4e7849cee7 poe: reduce omits in gpt-5.5 extends configs
Inherit family, knowledge, and structured_output from base models
instead of omitting them. Only omit fields that genuinely don't
apply to Poe (provider-specific pricing tiers, different context
limits, opencode-specific provider config).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 12:44:49 -07:00
Joshua Dietz e3e63a7247 feat(ollama cloud): add deepseek v4 pro 2026-04-27 21:42:10 +02:00
Aiden Cline fb297153e4 Merge pull request #1572 from YoshiTabletopGamer/qwen3.5-3.6-alibaba-open
[alibaba] Add remaining open Qwen 3.5 and 3.6 models, fix Qwen-3.5 397B-A17B
2026-04-27 14:36:51 -05:00
fhennerkes e8dd06e0ce poe: use extends format for gpt-5.5-pro
Address PR review comment to use extends format. Inherit from
opencode/gpt-5.5-pro since no openai/gpt-5.5-pro base exists yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 10:49:36 -07:00
fhennerkes bdee3d438b poe: use extends format for gpt-5.5
Address PR review comment to use extends format and inherit from
openai/gpt-5.5 base model.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 10:36:10 -07:00
fhennerkes b410bc3ef2 poe: add GPT-5.5 and GPT-5.5-Pro models 2026-04-27 10:34:52 -07:00
Aiden Cline 870e3d1d26 Merge pull request #1612 from hanouticelina/add-deepseek-v4-for-huggingface
feat(huggingface): add DeepSeek V4 Pro
2026-04-27 11:20:12 -05:00
Aiden Cline 5f21f3b603 Merge pull request #1586 from fernandoenzo/add-ollama-cloud-deepseek-v4-flash
feat(ollama-cloud): add deepseek-v4-flash model
2026-04-27 10:42:53 -05:00
Celina Hanouti 27dad05e25 extend deepseek/deepseek-v4-pro 2026-04-27 16:38:36 +01:00
Aiden Cline 2ed88cbcc0 Merge pull request #1604 from ndeybach/PR-gpt-5.5
feat(azure): add GPT-5.5 model metadata
2026-04-27 10:20:32 -05:00
Nils DEYBACH 4d199c932e fix: base azure-cognitive-services model not on azure
extend of extend does not seem to be supported
2026-04-27 17:04:20 +02:00
Jaime de Aquino 2d142f920c Add Qwen3.5-9B model configuration file 2026-04-27 16:46:04 +02:00
Celina Hanouti 47dea9e551 fix 2026-04-27 15:40:35 +01:00
Celina Hanouti 0d25c3dcac use extends 2026-04-27 15:37:31 +01:00
Aiden Cline 3d7f9256cb Merge pull request #1583 from abliteration-ai/codex/add-abliteration-provider
Add abliteration.ai provider
2026-04-27 09:29:26 -05:00
Aiden Cline 6aa1ebd4be Merge pull request #1595 from Contraboi/contra/add-openrouter-nano-banana-2
feat(openrouter): add Gemini 3.1 flash image preview (Nano Banana 2)
2026-04-27 09:28:23 -05:00
Aiden Cline f9ebebaffd Merge pull request #1601 from shikbupt/alibaba-deepseek
add alibaba-cn deepseek-v4
2026-04-27 09:28:08 -05:00
sk 7b3fe83c09 use extend format 2026-04-27 21:48:26 +08:00
Yashwanth Kumar 0a06b3efc2 Update Qwen model configuration in TOML file 2026-04-27 16:40:28 +05:30
Yashwanth Kumar 90dcbbbcc5 Add Qwen3.6 27B model configuration 2026-04-27 16:29:56 +05:30
Yashwanth Kumar b17f5fd8ae Delete providers/openrouter/models/qwen/qwen-3.6-27b.toml 2026-04-27 16:28:19 +05:30
Yashwanth Kumar 68691ac3f9 Update providers/openrouter/models/qwen/qwen-3.6-27b.toml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-27 16:27:56 +05:30
Yashwanth Kumar 78fe905fcb Add Qwen 3.6 27B model configuration 2026-04-27 16:22:24 +05:30
Nils DEYBACH 6b488802cf fix: parsing error and add context over price
- adds the context over X price capability from parent (awaiting refactor to be correct on exact limit threashold)
- fix parsing since anything must be before extends.
2026-04-27 12:27:47 +02:00
Jack 4d0505b70e Merge pull request #1611 from anomalyco/fix/opencode-go-deepseek-v4-flash-cache-read-20260427
fix(opencode-go): update deepseek v4 flash cache pricing in Go
2026-04-27 17:34:52 +08:00
Jack b729923bd9 fix(opencode-go): correct deepseek v4 flash cache pricing 2026-04-27 17:31:41 +08:00
Celina Hanouti 34c7aa7dfe update context limit 2026-04-27 09:33:57 +01:00
Celina Hanouti 09b4d3548b add support for DeepSeek V4 Pro for Hugging Face provider 2026-04-27 09:31:55 +01:00
xiaojie.zj f1cad8fdc0 feat: add zenmux models 2026-04-27 16:27:46 +08:00
Tom X Nguyen b2f7f57f26 feat: add neuralwatt provider with 14 models
Add Neuralwatt as an OpenAI-compatible inference provider with
energy-aware GPU optimization. Includes 14 models across 6
sub-providers (Mistral, ZAI, OpenAI, Moonshot, MiniMax, Qwen).

Models include reasoning variants (Kimi K2.5/K2.6, GLM 5.1 FP8,
MiniMax M2.5, Qwen3.5 397B, GPT OSS 20B) and fast non-reasoning
variants (Kimi K2.5/K2.6 Fast, GLM 5/5.1 Fast, Qwen3.5/3.6 Fast),
plus Devstral Small 2 and Qwen3.6 35B A3B.

Logo derived from official Neuralwatt favicon (currentColor variant).
Pricing sourced from Neuralwatt's published rates.
2026-04-27 15:01:56 +07:00
Aiden Cline 925d4eba1f Merge pull request #1536 from philipmat/add-openrouter-pareto-code-router
Adds support for openrouter/pareto-code
2026-04-26 23:52:04 -05:00
Aiden Cline bc1e4b870b Merge pull request #1608 from Alex-wuhu/dev
add deepseek-v4, qwen3.6 on novita
2026-04-26 23:16:56 -05:00
Alex-wuhu ef913f9645 refactor: use extends format for novita deepseek v4 models
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 12:00:04 +08:00
Alex-wuhu cec747ade3 fix: add cache_read pricing for deepseek v4 models 2026-04-27 11:09:15 +08:00
Aiden Cline 2ced32d52f Merge pull request #1596 from NathanDrake2406/add-cf-ai-gateway-gpt-5.5
feat(cloudflare-ai-gateway): add openai/gpt-5.5
2026-04-26 21:57:13 -05:00
Alex-wuhu 92cc5a79df feat: add deepseek-v4, qwen3.6 on novita 2026-04-27 10:49:13 +08:00
Aiden Cline 4cf8661f92 Merge pull request #1602 from shikbupt/alibaba-qwen3.6-max
add alibaba-cn qwen3.6 max
2026-04-26 17:48:14 -05:00
Aiden Cline ebe431c0dc Merge pull request #1606 from LightAndy1/dev
Add gemini-3.1-flash-preview for google-vertex
2026-04-26 17:30:05 -05:00
LightAndy 8b2c5f30a0 ✏️ Fix typo 2026-04-26 21:48:54 +03:00
Nils DEYBACH 0fc92e1c47 fix: align limit on base 5.5 model
now that limits were fixed in base, we align azure on it
2026-04-26 20:48:00 +02:00
Nils DEYBACH d73ca9024a Merge remote-tracking branch 'upstream/dev' into PR-gpt-5.5 2026-04-26 20:45:20 +02:00
LightAndy bff48f9fde Merge branch 'anomalyco:dev' into dev 2026-04-26 21:43:13 +03:00
Aiden Cline c5c803f415 fix: ensure openai gpt-5.5 limits are exact 2026-04-26 13:38:56 -05:00
Nils DEYBACH cd69509b48 fix: simplify by extending the azure model from opeani 2026-04-26 20:04:27 +02:00
Aiden Cline 83d15fd756 Merge pull request #1574 from juls0730/dev
feat: add mimo v2.5/pro to xiaomi and openrouter providers
2026-04-26 13:55:57 -04:00
LightAndy b3c09451d9 Add gemini-3.1-flash-preview model configuration 2026-04-26 20:27:17 +03:00
Frank d98bdb5eff Merge pull request #1560 from TigerBeanst/patch-1
fix: opencode go mimo-v2.5 context limit to 1,000,000
2026-04-26 13:16:33 -04:00
Frank ea205913ce update zen models 2026-04-26 12:49:37 -04:00
Frank 3532801639 update zen models 2026-04-26 11:48:32 -04:00
Nils DEYBACH 1bb50141c2 feat(azure): add GPT-5.5 model metadata
## Summary

Adds GPT-5.5 metadata for:

- Azure
- Azure Cognitive Services

The Azure Cognitive Services entry mirrors the existing local convention of full TOML model definitions.

## Sources

- Microsoft Learn lists `gpt-5.5` for Azure OpenAI / Microsoft Foundry with version `2026-04-24`, `1,050,000` context, `922,000` input, `128,000` output, structured outputs, tools, image input, and December 2025 training data.
- Microsoft’s Azure GPT-5.5 announcement lists pricing at `$5.00` input, `$0.50` cached input, and `$30.00` output per 1M tokens.
- Azure Responses API docs list PDF input support for vision-capable models and include `gpt-5.5` version `2026-04-24`.

## Notes

This intentionally does not add `gpt-5.5-pro`, since Azure Learn currently lists `gpt-5.5` but not `gpt-5.5-pro` in the Azure model catalog.

This also intentionally omits OpenAI-specific `context_over_200k` and `experimental.modes.fast` metadata because the Azure sources confirm the standard pricing and limits, but not those OpenAI-specific fields.
2026-04-26 17:45:25 +02:00
sk fa8bdd63fb add alibaba-cn qwen3.6 max 2026-04-26 19:39:41 +08:00
sk 56f64577ce add alibaba-cn deepseek-v4 2026-04-26 19:23:04 +08:00
Nathan Nguyen f726af5767 refactor(cloudflare-ai-gateway): use [extends] for openai/gpt-5.5
The model entry duplicated every field from providers/openai/models/gpt-5.5.toml,
so any future change to the upstream OpenAI definition would silently drift here.

Switch to the `[extends] from = "openai/gpt-5.5"` form already used by sibling
providers (openrouter, requesty), omitting `experimental.modes.fast` since the
gateway does not surface the OpenAI priority service tier. Validation output is
byte-identical to the prior expanded form.
2026-04-26 13:33:39 +10:00
Zoe 4838e3cb9b feat: add mimo v2.5/pro to xiaomi and openrouter providers 2026-04-25 21:32:24 -05:00
Muhammad Mugni Hadi dbe92646c3 chore(chutes): add header comments to generated TOML files
Each generated TOML now includes a comment noting which fields are
auto-managed vs manually overridable on re-run.
2026-04-26 06:52:21 +07:00
Muhammad Mugni Hadi 4717c67054 feat(chutes): add API-driven model generator script
Add generate-chutes.ts that fetches models from https://llm.chutes.ai/v1/models
and generates/updates TOML files, following the same pattern as generate-vercel.ts.

Supports --dry-run, --new-only, and --keep-orphans flags. Auto-deletes TOML files
for models no longer in the API (with empty directory cleanup).

Preserves manually-set fields (family, knowledge, interleaved, status) when merging
with API data. Also syncs current models from the API.
2026-04-26 06:51:16 +07:00
Nathan Nguyen d4c77c14fd feat(cloudflare-ai-gateway): add openai/gpt-5.5
Mirrors the existing direct openai/gpt-5.5 entry under the
cloudflare-ai-gateway provider so opencode and other consumers can
route GPT-5.5 traffic through Cloudflare AI Gateway without hitting
ProviderModelNotFoundError.

Pricing, limits, modalities, and dates copied from
providers/openai/models/gpt-5.5.toml; provider stanza follows the
sibling gpt-5.4 entry (npm = "ai-gateway-provider").
2026-04-26 05:22:29 +10:00
Selmir Nedzibi 96b3d65307 feat(openrouter): add Gemini 3.1 flash image preview (Nano Banana 2) 2026-04-25 21:14:44 +02:00
Aiden Cline b491c29cf9 Merge pull request #1573 from zainhas/dev
[Together AI] add deepseek-v4
2026-04-25 13:47:14 -04:00
Aiden Cline d937abd849 Merge pull request #1539 from manascb1344/fix-xiaomi-provider-ids
feat: add MiMo-V2.5 and MiMo-V2.5-Pro to xiaomi-token-plan providers
2026-04-25 13:33:42 -04:00
Aiden Cline df52175b0c Merge pull request #1580 from LeGazeon/add-nvidia-deepseek-v4-pro/flash
Add NVIDIA DeepSeek-V4 models
2026-04-25 13:32:41 -04:00
Aiden Cline bee8339c07 Merge pull request #1589 from MiyakoMeow/feat/restrict-zai-zhipuai-coding-plan-models
rm: unavailable models in zai/zhipuai coding plan
2026-04-25 13:30:39 -04:00
Aiden Cline 181bf96fa3 Merge pull request #1585 from saju01/add-copilot-gpt-5.5
feat(github-copilot): add gpt-5.5
2026-04-25 13:30:16 -04:00
Aiden Cline 9d49d2fd52 Merge pull request #1587 from smakosh/claude/rebase-add-llmgateway-models-yqKLn
feat(llmgateway): add deepseek-v4-pro, deepseek-v4-flash, kimi-k2.6
2026-04-25 13:29:39 -04:00
Aiden Cline 648776aa85 Merge pull request #1590 from dpuyosa/feat/venice-models
Venice: Add GPT-5.5 and Qwen3.6 model configs
2026-04-25 13:29:03 -04:00
Aiden Cline 421cb099b0 Merge pull request #1591 from dpuyosa/fix/venice-deepseek-family
Venice: Fix DeepSeek V4 Flash family classification
2026-04-25 13:28:54 -04:00
Aiden Cline f458b19994 Merge pull request #1592 from MiyakoMeow/feat/deepseek-1m-context
fix(deepseek): all has 1M context / 384k output / adjusted price
2026-04-25 13:28:45 -04:00
MiyakoMeow d347093b03 feat(deepseek): 1M context / 384k output 2026-04-25 18:58:12 +08:00
MiyakoMeow 3328712262 feat: restrict zai/zhipuai coding plan models to glm-5.1, glm-5-turbo, glm-4.7, glm-4.5-air only
Based on official documentation:
- ZAI DevPack Coding Plan: https://docs.z.ai/devpack/overview
- Zhipu AI BigModel Coding Plan: https://docs.bigmodel.cn/cn/coding-plan/overview

Both providers only officially support the following GLM models for coding plans:
- glm-5.1
- glm-5-turbo
- glm-4.7
- glm-4.5-air

Removed unsupported models from zai-coding-plan:
- glm-4.5, glm-4.5-flash, glm-4.5v
- glm-4.6, glm-4.6v
- glm-4.7-flash, glm-4.7-flashx
- glm-5, glm-5v-turbo

Removed unsupported models from zhipuai-coding-plan:
- glm-4.5, glm-4.5-flash, glm-4.5v
- glm-4.6, glm-4.6v, glm-4.6v-flash
- glm-4.7-flash, glm-4.7-flashx
- glm-5, glm-5v-turbo
2026-04-25 18:49:10 +08:00
dpuyosa 60edc1b52d [venice] Add GPT-5.5 and Qwen3.6 model configs
- Add OpenAI GPT-5.5 with 1M context window and tiered pricing
- Add OpenAI GPT-5.5 Pro with premium pricing and 128K output limit
- Add Qwen3.6 27B with text, image, and video input modalities
2026-04-25 12:41:31 +02:00
dpuyosa eee44cd080 [venice] Fix DeepSeek V4 Flash family classification
- Correct family from "deepseek" to "deepseek-flash" for accurate model categorization
2026-04-25 12:36:06 +02:00
smakosh 048a3235e8 feat(llmgateway): add deepseek-v4-pro, deepseek-v4-flash, kimi-k2.6 2026-04-25 12:19:40 +02:00
Fernando Guarini 1db03ec1e6 feat(ollama-cloud): add deepseek-v4-flash model 2026-04-25 11:24:12 +02:00
Saju Sarangdharan 6d283349ad feat(github-copilot): add gpt-5.5
GitHub Copilot now serves gpt-5.5 (verified via GET https://api.githubcopilot.com/models with a Copilot Enterprise token). Adding the catalog row so downstream consumers (e.g. pi-ai) can route requests.
2026-04-25 10:31:21 +02:00
Abliteration.ai 7e07302ecd add abliteration.ai provider 2026-04-24 22:46:54 -07:00
LeGazeon 8bc407a617 chore: remove deepseek-v4-pro config (duplicated by #1578)
The Pro model configuration was already added via #1578 which
has been merged. Removing the duplicate from this branch to
keep only the Flash variant.
2026-04-25 13:17:12 +08:00
LeGazeon 5305d2bae2 refactor: extend flash config from deepseek base
Remove duplicated fields by inheriting common settings
from providers/deepseek base config via [extends].

This addresses the review comment in #1580
2026-04-25 13:11:26 +08:00
Aiden Cline fee96c27b9 Merge pull request #1578 from panwar-stack/dev
feat(nvidia): add DeepSeek V4 model
2026-04-25 00:41:03 -04:00
Aiden Cline 66520adbc6 Merge pull request #1577 from ezShroom/dev
add openrouter gpt-5.5
2026-04-25 00:40:34 -04:00
Zain Hasan 40714995cc Add interleaved section to DeepSeek-V4-Pro.toml 2026-04-24 18:59:45 -07:00
LeGazeon 66c4896003 Add NVIDIA DeepSeek-V4 models
Add model entries for DeepSeek V4 Pro and DeepSeek V4 Flash to the NVIDIA NIM provider.

## Changes
- Added `providers/nvidia/deepseek-v4-pro.toml`
- Added `providers/nvidia/deepseek-v4-flash.toml`

## Data Sources
- NVIDIA NIM Model Cards:
  - DeepSeek V4 Pro: https://build.nvidia.com/deepseek-ai/deepseek-v4-pro/modelcard
  - DeepSeek V4 Flash: https://build.nvidia.com/deepseek-ai/deepseek-v4-flash/modelcard
2026-04-25 09:57:43 +08:00
panwar-stack 31091f3d4e Rename deepseek-v4.toml to deepseek-v4-pro.toml 2026-04-24 17:09:12 -07:00
panwar-stack 5fe512c1b1 Follow extends pattern
Follow extends pattern
2026-04-24 17:08:49 -07:00
panwar-stack 63efa131e7 feat(nvidia): add DeepSeek V4 model
add DeepSeek V4 model
2026-04-24 17:05:12 -07:00
Shroom 29cd503070 Add gpt-5.5.toml configuration file 2026-04-25 00:08:04 +01:00
Rohan Taneja a9b704c656 Merge pull request #1575 from vercel/update-vercel-models-1777063875 2026-04-24 15:29:39 -07:00
Aiden Cline 0a88e412e5 Merge pull request #1576 from dsingal0/feat/openrouter-deepseek-v4
Add OpenRouter DeepSeek V4 models
2026-04-24 17:42:48 -04:00
Dhruv Singal b46e29ccd5 fix(openrouter): use DeepSeek reasoning content field 2026-04-24 14:06:05 -07:00
Jerilyn Zheng a181b770d6 Update kimi-k2.6.toml 2026-04-24 13:55:27 -07:00
Jerilyn Zheng fa71201f20 Update deepseek-v4-pro.toml 2026-04-24 13:54:56 -07:00
Jerilyn Zheng 3ac17aefb6 Enable open_weights in deepseek-v4-flash configuration 2026-04-24 13:54:34 -07:00
Jerilyn Zheng 1f2ceb91a5 Update qwen-3.6-max-preview.toml 2026-04-24 13:53:57 -07:00
github-actions[bot] d82681900c chore(vercel): update Vercel model definitions
Auto-generated by weekly workflow from Vercel AI Gateway API.

Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-24 20:51:22 +00:00
Zain Hasan 7e296f4cbd ds recommend 384,000 2026-04-24 12:39:09 -07:00
Zain Hasan f202ff51e4 Reduce output limit from 512000 to 300000 2026-04-24 12:13:53 -07:00
Zain Hasan 4085a0b536 Merge branch 'anomalyco:dev' into dev 2026-04-24 12:12:28 -07:00
Zain Hasan 935fdeca65 [Together AI] Add deepseekv4 pro 2026-04-24 12:08:33 -07:00
Frank cef8828fbe update zen models 2026-04-24 14:51:59 -04:00
Frank 9277a23a29 update zen models 2026-04-24 14:50:50 -04:00
Frank d7bfb16b0f update zen models 2026-04-24 14:42:25 -04:00
Frank 37ae6fe7c8 update zen models 2026-04-24 12:11:33 -04:00
Aiden Cline 87ce527476 Merge pull request #1571 from cgilly2fast/dev
fix(firmware): glm 5.1 name
2026-04-24 11:49:51 -04:00
Aiden Cline 34bed30fa1 Merge pull request #1567 from dsingal0/feat/openrouter-deepseek-v4
feat(openrouter): add DeepSeek V4 Pro and V4 Flash
2026-04-24 11:49:35 -04:00
Dhruv Singal 8a0c3cb75f refactor(openrouter): extend official DeepSeek V4 Pro/Flash
Use [extends] from deepseek/ with OpenRouter-specific overrides
(attachment, interleaved reasoning_details, limits).

Made-with: Cursor
2026-04-24 08:43:38 -07:00
Colby Gilbert f65460ac16 fix(firmware): glm 5.1 name 2026-04-24 08:40:19 -07:00
YoshiTabletopGamer 8ea92aed9a [alibaba] Add remaining open Qwen 3.5 models, fix Qwen-3.5 397B-A17B, add open Qwen 3.6 models
- Added Qwen 3.5 122B-A10B
- Added Qwen 3.5 27B
- Added Qwen 3.5 35B-A3B
- Fixed Qwen 3.5 397B-A17B (see below)
- Added Qwen 3.6 27b
- Added Qwen 3.6-35B-A3B

I was not able to find a reliable source for the knowledge cutoff of any of these models.
2025-04 was already set as the cutoff for Qwen 3, and Qwen 3.5 is newer.
All data is from the ModelStudio webpage.
It seems to not include audio, but the ModelStudio page clearly has an audio symbol and the model is capable of this.
And I found no data for a price for reasoning tokens in particular, unlike what was in the file for Qwen 3.5 397B-A17B.
The models are all capable of structured output.
2026-04-24 12:38:52 -03:00
Dhruv Singal f340d82fc3 feat(openrouter): add DeepSeek V4 Pro and V4 Flash
Add model configs aligned with OpenRouter pricing and limits
(1M context, 384K max output, cache read rates from provider page).

Made-with: Cursor
2026-04-24 08:25:17 -07:00
Frank c7431ae24c update zen models 2026-04-24 10:53:10 -04:00
Frank 3d1888b7b5 update zen models 2026-04-24 10:24:34 -04:00
Misha Skvortsov 16a8fa5c20 improve(atomic-chat): drop hardcoded model list per maintainer feedback
Made-with: Cursor
2026-04-24 17:20:26 +03:00
Aiden Cline dcd37ccdbb add deepseek v4 flash 2026-04-24 08:34:03 -04:00
Aiden Cline d18c3f910c Merge pull request #1562 from dpuyosa/update/venice-kimi-pricing
Venice: Update kimi-k2-6 pricing
2026-04-24 08:08:23 -04:00
Aiden Cline 2cec5a492c Merge pull request #1563 from dpuyosa/feat/venice-deepseek-v4
Venice: Add DeepSeek V4 Flash and Pro models
2026-04-24 08:08:13 -04:00
dpuyosa 61dd0ec489 [venice] Add DeepSeek V4 Flash and Pro models
- Add DeepSeek V4 Flash with 1M context, reasoning, and tool support
- Add DeepSeek V4 Pro with 1M context, reasoning, and tool support
- Set pricing and interleaved reasoning_content field for both
2026-04-24 12:21:07 +02:00
dpuyosa c7758204b5 [venice] Update kimi-k2-6 pricing
- Update input, output, and cache_read costs to current rates
- Update last_updated timestamp to 2026-04-24
2026-04-24 12:17:51 +02:00
manascb1344 ed91520aa2 feat: add MiMo-V2.5 and MiMo-V2.5-Pro to xiaomi-token-plan providers 2026-04-24 15:38:55 +05:30
Frank 3e82669a82 Merge pull request #1561 from wenbindu/dev
add deepseek new moels
2026-04-24 03:04:52 -04:00
Frank 1cc0c9c074 sync 2026-04-24 03:03:06 -04:00
TigerBeanst d73d7f6453 fix: opencode go mimo-v2.5 context limit to 1,000,000
https://platform.xiaomimimo.com/docs/pricing
2026-04-24 12:48:34 +08:00
Aiden Cline afb59f86ee Merge pull request #1557 from seffhunnn/dev
feat: add AU Sonnet and Opus models for Amazon Bedrock
2026-04-24 00:30:51 -04:00
wenbindu 05242f68d4 add deepseek new moel 2026-04-24 12:06:19 +08:00
Mohd Saif c1b029dcc1 feat: add AU Opus model for Amazon Bedrock 2026-04-24 03:26:28 +05:30
Mohd Saif bc2dd5137a feat: add AU Sonnet model for Amazon Bedrock 2026-04-24 03:25:38 +05:30
Aiden Cline 99ec4900c7 Merge pull request #1555 from brentdurksen/add-azure-claude-sonnet-4-6
feat(azure): add Claude Sonnet 4.6 model
2026-04-23 17:33:44 -04:00
Brent Durksen a8c124ac9e refactor: use extends to inherit from anthropic/claude-sonnet-4-6 2026-04-23 15:16:38 -06:00
Aiden Cline 0d20a363a9 Merge pull request #1556 from fhennerkes/dev
poe: add GPT-Image-2 model
2026-04-23 17:12:20 -04:00
fhennerkes 3ac613678b poe: add GPT-Image-2 model 2026-04-23 12:38:22 -07:00
Brent Durksen e2ead1b4e6 feat(azure): add Claude Sonnet 4.6 model 2026-04-23 13:37:50 -06:00
Aiden Cline be53c33588 Merge pull request #1550 from BlockListed/cortecs-kimi-k2.6
Add kimi k2.6 to cortecs
2026-04-23 15:26:58 -04:00
Aiden Cline 55cf5fa310 Merge pull request #1554 from mattyatea/add-gpt-5-5
[codex] Add GPT-5.5
2026-04-23 15:17:20 -04:00
mattyatea 3e0fe362f2 add gpt-5.5 model 2026-04-24 04:13:51 +09:00
BlockListed 89d06ae31f add kimi k2.6 to cortecs 2026-04-23 19:49:18 +02:00
Aiden Cline c994b116ae Merge pull request #1542 from u007/patch-1
Add Chutes: Kimi K2.6 TEE
2026-04-23 12:47:49 -04:00
Aiden Cline 833e8f7a66 Merge pull request #1548 from fernandoenzo/fix/gemma4-ollama-output-limit
fix(ollama): set gemma4:31b output limit to match context
2026-04-23 12:46:00 -04:00
Aiden Cline 32bd1427fb Merge pull request #1545 from Alex-wuhu/dev
Add deepseek, gemma, ling, llama, kimi on NovitaAI
2026-04-23 12:36:06 -04:00
Frank ae7672b87e update zen models 2026-04-23 11:11:30 -04:00
Fernando Guarini 9b27cc5a76 fix(ollama): set gemma4:31b output limit to match context
Ollama does not impose official output limits. The existing convention for Gemma models on Ollama (gemma3:4b, gemma3:12b, gemma3:27b) is to set output equal to context. gemma4:31b was the only exception with output=8192 vs context=262144.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-23 11:42:30 +02:00
Alex-wuhu 7014e3e318 feat: add missing Novita AI model configurations
Add 6 models served by Novita:
- deepseek/deepseek-r1-distill-qwen-14b
- deepseek/deepseek-r1-distill-qwen-32b
- google/gemma-3-12b-it
- inclusionai/ling-2.6-1t
- meta-llama/llama-3.2-3b-instruct
- moonshotai/kimi-k2.6

Capabilities, pricing, context, and modalities sourced from Novita's
/v1/models API; family slugs and release dates aligned with existing
same-model entries in the repo.
2026-04-23 14:58:43 +08:00
Frank e0e153e8d6 update zen models 2026-04-23 02:51:28 -04:00
mickalchen a0fbef93e4 revert 2026-04-23 14:41:05 +08:00
mickalchen e4a89229be add model by openrouter 2026-04-23 14:23:06 +08:00
mickalchen 9ad15e97cd add model by openrouter 2026-04-23 14:18:51 +08:00
James e3b4df4e87 Update Kimi-K2.6-TEE.toml
fix reasoning
2026-04-23 13:53:37 +08:00
Aiden Cline e3e2066c83 Merge pull request #1544 from GodTamIt/deepinfra/kimi-k2.6
deepinfra: Add Kimi-K2.6 support
2026-04-23 00:40:50 -04:00
Aiden Cline 208dcd12a2 Merge pull request #1533 from qychen2001/dev
Add Kimi-K2.6 and Qwen3.6-35B-A3B, update Kimi-K2.5 config for siliconflow and siliconflow-cn
2026-04-23 00:40:40 -04:00
Aiden Cline d166444aa1 Merge pull request #1541 from zainhas/dev
[Together AI] add Kimi k2.6 support
2026-04-23 00:40:15 -04:00
Aiden Cline cd35f96e73 Merge pull request #1528 from seffhunnn/dev
Fix incorrect model ID for Gemma 4 26B (Google provider)
2026-04-23 00:39:12 -04:00
Christopher Tam 2b0c21e0b4 deepinfra: Add Kimi-K2.6 support 2026-04-22 23:39:42 -04:00
James 787b0bf9e9 Update Kimi K2.5 TEE to Kimi K2.6 TEE 2026-04-23 10:58:39 +08:00
Zain Hasan 77fbf02ff6 remove interleaved 2026-04-22 16:19:10 -07:00
Zain Hasan 89973fd122 [Together AI] add Kimi k2.6 support 2026-04-22 16:16:08 -07:00
Frank e458a9f5b8 Merge pull request #1540 from dsingal0/feat/baseten-kimi-k2.6
feat(baseten): add Kimi K2.6
2026-04-22 16:59:51 -04:00
Dhruv Singal 61d95afae2 feat(baseten): add Kimi K2.6 2026-04-22 13:53:46 -07:00
Jack 4a2df5e008 Merge pull request #1537 from anomalyco/feat/opencode-go-mimo-v2.5
Feat/opencode go mimo-v2.5-pro & mimo-v2.5
2026-04-23 00:51:49 +08:00
Aiden Cline 3db907f3ce Merge pull request #758 from regolo-ai/dev
Add Regolo-ai Provider
2026-04-22 12:33:02 -04:00
Jack 70d8f9cc6e update mimo v2 output limits to 128k 2026-04-22 23:32:16 +08:00
Jack b9b354ada0 update mimo v2.5 output limits to 128k 2026-04-22 23:17:40 +08:00
Jack 95632ad376 remove mimo-v2.5-omni (renamed to mimo-v2.5) 2026-04-22 23:15:00 +08:00
Philip M 7153506989 Adds support for openrouter/pareto-code
The Pareto Router is a way to have OpenRouter always pick a strong coding model for your needs without committing to a specific one. You express a single min_coding_score preference between 0 and 1, and the router routes your request to a coding model that meets that bar.

The Pareto Router is tuned for coding use cases. Under the hood it keeps a curated shortlist of strong coding models currently available on OpenRouter. The exact shortlist and selection logic evolve over time as new models land and benchmarks shift.
2026-04-22 10:14:46 -05:00
Jack c73bab2e7e providers(opencode-go): rename mimo-v2.5-omni to mimo-v2.5 2026-04-22 23:14:27 +08:00
Jack a783dc808d providers(opencode-go): add mimo v2.5 models and separate v2 families 2026-04-22 23:07:40 +08:00
Daniele Scasciafratte 58e72802bc feat(models): update 2026-04-22 16:05:48 +02:00
Mohd Saif 19233d93c4 fix: remove unnecessary id field 2026-04-22 15:19:47 +05:30
QiyuanChen 7eea45e078 feat(siliconflow-cn): add Kimi-K2.6, Qwen3.6-35B-A3B and update Kimi-K2.5 config 2026-04-22 13:38:34 +08:00
QiyuanChen 001ec226f6 feat(siliconflow): add Kimi-K2.6 and update Kimi-K2.5 config 2026-04-22 13:37:59 +08:00
Jack 32461d5b44 Merge pull request #1532 from chl-0537/feature/add-tencent
Remove tencent token plan
2026-04-22 13:12:06 +08:00
mickalchen 9f078294c0 Remove tencent token plan 2026-04-22 13:07:03 +08:00
Aiden Cline c885ed49cd Merge pull request #1531 from zhiyuan1024/zhiyuan/alibaba-cn_kimi-k2.6
feat(alibaba-cn): add Kimi K2.6 model configuration
2026-04-21 23:41:29 -04:00
Aiden Cline 2fc434062f Merge pull request #1529 from compumike/compumike/fix-openrouter-openai-gpt-5.4-pricing
Fix pricing for openrouter/openai gpt-5.4-[mini,nano] off by 10^6
2026-04-21 23:40:51 -04:00
Aiden Cline dbcb7e6d69 Merge pull request #1530 from cgilly2fast/dev
feat(firmware): kimi k2.6 model
2026-04-21 23:40:22 -04:00
Zhiyuan Hou b58392fc62 feat(alibaba-cn): add Kimi K2.6 model configuration
Signed-off-by: Zhiyuan Hou <zhiyuan2048@outlook.com>
2026-04-22 10:37:37 +08:00
Frank a4818c90ca update zen models 2026-04-21 20:18:35 -04:00
Colby Gilbert 49fbdba49f feat(firmware): kimi k2.6 model 2026-04-21 17:14:46 -07:00
Mike Robbins f2dd4da7f9 Fix pricing for openrouter/openai gpt-5.4-[mini,nano] off by 10^6 2026-04-21 18:41:02 -04:00
Frank a3ed215038 update zen models 2026-04-21 17:43:38 -04:00
Mohd Saif d45df0530b fix: correct Gemma 4 26B model ID for Google provider
Updated model ID from gemma-4-26b-it to gemma-4-26b-a4b-it to match actual Gemini API. Also added missing id field and renamed the file accordingly.
2026-04-22 01:51:23 +05:30
Jack 990531258b Merge pull request #1527 from anomalyco/feat/opencode-go-kimi-k2.6-3x-name
providers(opencode-go): rename kimi k2.6
2026-04-21 22:59:26 +08:00
Jack cd2c9e3b62 providers(opencode-go): rename kimi k2.6 2026-04-21 22:54:53 +08:00
Aiden Cline a114991278 Merge pull request #1505 from rocuevas9511/feat/deepinfra-qwen-3.5-35b
feat: add Qwen 3.5 35B A3B to deepinfra
2026-04-21 10:02:27 -04:00
Aiden Cline 5ff2035cee Merge pull request #1520 from Marenz/add-deepinfra-qwen3.6-35b-a3b
Add Qwen3.6-35B-A3B to Deep Infra
2026-04-21 10:01:56 -04:00
Aiden Cline a5993cc140 Merge pull request #1515 from llc1123/chore/zenmux-update
providers(zenmux): add support for kimi k2.6
2026-04-21 10:00:06 -04:00
Aiden Cline aebe4b6cd0 Merge pull request #1517 from otterDeveloper/kimi2.6-pull
add Firework's kimi k2.6
2026-04-21 09:59:54 -04:00
Aiden Cline 214adb1154 Merge pull request #1522 from ceoAppsknight/kilo/kimi-k2.6
Added kilo/kimi-k2.6
2026-04-21 09:59:33 -04:00
Aiden Cline b301c1f8b6 Merge pull request #1523 from sk0x0y/feature/nanogpt-kimi-k2.6-qwen-3.6
feat(nano-gpt): add Kimi K2.6 and Qwen 3.6 models
2026-04-21 09:58:45 -04:00
Aiden Cline b81c8b385b Merge pull request #1507 from rocuevas9511/feat/deepinfra-qwen-3.5-397b
feat: add Qwen 3.5 397B A17B to deepinfra
2026-04-21 09:58:28 -04:00
rocuevas9511 1caa438b3e fix: remove id field (per Marenz feedback) 2026-04-21 07:35:52 -06:00
rocuevas9511 673bc92f4d fix: remove id field (per Marenz feedback) 2026-04-21 07:35:38 -06:00
Jack 0159eaa158 Merge pull request #1526 from anomalyco/feat/moonshotai-cn-kimi-k2.6
providers(moonshotai-cn): add kimi k2.6
2026-04-21 20:39:05 +08:00
Jack efdc7b9a54 providers(moonshotai-cn): add kimi k2.6 2026-04-21 20:26:19 +08:00
Jack b08721206d Merge pull request #1525 from anomalyco/feat/moonshotai-kimi-k2.6
providers(moonshotai): add kimi k2.6
2026-04-21 19:35:13 +08:00
Jack fe0d4cd9fa providers(moonshotai): add kimi k2.6 2026-04-21 19:33:06 +08:00
sk0x0y 738ad6ed70 feat(nano-gpt): add Kimi K2.6 and Qwen 3.6 model family 2026-04-21 19:22:38 +09:00
Syed Assadullah Shah 19b66fbaf2 Added kilo/kimi-k2.6 2026-04-21 15:19:17 +05:00
Mathias L. Baumann c416836497 Add Qwen3.6-35B-A3B to Deep Infra
35B-total / 3B-active MoE (256 experts, 8 routed + 1 shared).
262K native context, vision + video input, thinking mode, tool calls.
Apache 2.0, $0.20 in / $1.00 out per 1M tokens.
2026-04-21 11:58:04 +02:00
rocuevas9511 059cc4b91f fix: update model id to match DeepInfra API 2026-04-21 00:33:20 -06:00
rocuevas9511 116bd328a6 fix: update model id to match DeepInfra API 2026-04-21 00:31:22 -06:00
Frank aa30ce3ef2 update zen models 2026-04-21 02:12:27 -04:00
Frank 9533a47906 update zen models 2026-04-21 01:20:47 -04:00
Miguel Medina ad18f780f0 add firework's kimi 2.6 2026-04-20 23:12:11 -06:00
粒粒橙 a48519e557 providers(zenmux): add support for kimi k2.6 2026-04-21 10:13:22 +08:00
Aiden Cline 23f5e74392 Merge pull request #1514 from mfbalestra/add/kimi-k2.6-ollama-cloud
providers/ollama-cloud: add kimi-k2.6:cloud
2026-04-20 21:47:51 -04:00
Aiden Cline 7a50ea28a1 Merge pull request #1510 from dpuyosa/feat/venice-add-kimi-k2-6
Venice: Add Kimi K2.6 model configuration
2026-04-20 21:45:49 -04:00
mfbalestra 5944f94197 providers(ollama-cloud): add kimi-k2.6:cloud 2026-04-20 22:45:01 -03:00
Aiden Cline 98732b7d76 Merge pull request #1512 from SomeoneWithOptions/dev
add kimi-K2.6 for OpenRouter provider
2026-04-20 21:44:39 -04:00
SomeoneWithOptions c6412d7e59 add kimi-K2.6 for OpenRouter provider 2026-04-20 18:59:49 -05:00
dpuyosa b5a7a6e974 [venice] Add Kimi K2.6 model configuration
- Add new model definition for Venice provider
- Include cost, limits, and modality specs
- Enable reasoning, tool calling, and image input
2026-04-21 00:40:19 +02:00
Aiden Cline ba7c3d7b0b Merge pull request #1509 from kostiak/patch-1
Add support for Kimi-K2.6 in Kimi For Coding provider
2026-04-20 18:02:04 -04:00
Aiden Cline 53a9a2a36d Merge pull request #1508 from hanouticelina/add-kimi-k2.6-modeling
feat(huggingface): add Kimi K2.6
2026-04-20 18:01:32 -04:00
kostiak 3b6bca9b90 Add support for Kimi-K2.6 for Kimi For Coding provider 2026-04-21 00:24:51 +03:00
Celina Hanouti f5a048060f add support for Kimi-K2.6 for Hugging Face provider 2026-04-20 21:41:14 +01:00
rocuevas9511 9e6178a5e3 fix: update cost for Qwen 3.5 397B A17B 2026-04-20 13:38:45 -06:00
rocuevas9511 e4150361b3 fix: update cost for Qwen 3.5 35B A3B 2026-04-20 13:38:06 -06:00
rocuevas9511 8de0fc059d add Qwen 3.5 397B A17B to deepinfra 2026-04-20 13:34:56 -06:00
rocuevas9511 054733884f add Qwen 3.5 35B A3B to deepinfra 2026-04-20 13:32:54 -06:00
Aiden Cline 3d09981eda Merge pull request #1499 from rovo89/patch-1
[google] Fix cache_read cost in gemini-2.5-flash model
2026-04-20 14:43:53 -04:00
Aiden Cline 6877af7770 Merge pull request #1501 from mchenco/kimi-k2.6
Add Kimi K2.6 to Workers AI and AI Gateway
2026-04-20 14:41:58 -04:00
Nacho F. Lizaur 802985f76c feat: update kiro provider to use kiro-acp-ai-provider, add opus 4.7 2026-04-20 20:13:43 +02:00
mchen 794993fd48 Add Kimi K2.6 to Workers AI and AI Gateway 2026-04-20 13:54:31 -04:00
Jack 00b53a422a separate opencode-go kimi k2 families 2026-04-21 01:00:50 +08:00
Jack 2ccecb6011 Merge pull request #1500 from chl-0537/feature/add-tencent
feat: rename model
2026-04-20 22:49:09 +08:00
mickalchen 9b7e3abf00 rename model 2026-04-20 22:02:37 +08:00
Robert Vollmer 27d6a3d503 [google] Fix cache_read cost in gemini-2.5-flash model
https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash

There's no cache_read_audio, is there?
2026-04-20 15:02:54 +02:00
Lyda 6beb1f8be2 feat(302ai): standardize Claude model metadata and capabilities
- Add family field for all Claude models (claude-haiku, claude-opus, claude-sonnet)
- Standardize knowledge cutoff dates to full date format (YYYY-MM-DD)
- Enable reasoning capability for Claude Opus 4.x and Sonnet 4.x series models
- Add PDF input modality support for claude-opus-4-1-20250805
- Update claude-opus-4-7 context limit to 1,000,000 tokens
2026-04-20 16:10:52 +08:00
Lyda e0c1124fe2 feat(302ai): update GPT model capabilities and specifications
- Add structured_output capability for GPT-4.1, GPT-4o, and GPT-5 series models
- Enable reasoning capability and disable temperature for GPT-5 series models
- Update context limits: GPT-4.1 series to 1,047,576 tokens, GPT-5.4 series to 1,050,000 tokens
- Add input token limits for GPT-5 series models (272,000 or 922,000 tokens)
- Update knowledge cutoffs across GPT-5 series (2024-05-30 to 2025-08-31)
- Add PDF input modality support for GPT-4
2026-04-20 15:58:32 +08:00
Lyda bf4ceb7baa feat(302ai): update GLM model capabilities and knowledge cutoffs
- Enable reasoning capability for GLM-4.5-air, GLM-4.5, GLM-4.5V, and GLM-4.6V models
- Update knowledge cutoff to 2025-04 for GLM-4.5, GLM-4.5V, GLM-4.6, GLM-4.6V, and GLM-4.7
- Add video input modality support for GLM-4.5V and GLM-4.6V
- Add structured_output capability for GLM-5-turbo and GLM-5.1
- Add interleaved reasoning_content field for GLM-4.7, GLM-5, GLM-5-turbo, GLM-5.1, and GLM-5V-turbo
2026-04-20 15:48:29 +08:00
Aiden Cline 1a41934e55 Merge pull request #1448 from Lydanne/dev
feat(302ai): supplement commonly missing models
2026-04-19 22:19:18 -05:00
Aiden Cline aeb4caec9f Merge pull request #1488 from Sewer56/add-wafer-provider
Add wafer.ai provider
2026-04-19 22:19:06 -05:00
Aiden Cline ccb8dcc65f Merge pull request #1493 from rovo89/patch-1
Add context_over_200k for gemini-2.5-pro and adjust cache_read costs
2026-04-19 22:17:45 -05:00
Lyda 435ec1df7b feat(302ai): add claude-opus-4-7 model 2026-04-20 11:14:27 +08:00
Robert Vollmer 7c9d609143 Add context_over_200k for gemini-2.5-pro and adjust cache_read costs
https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro
https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models-2.5 (rounds 0.125 to 0.13)
2026-04-20 00:09:11 +02:00
Aiden Cline add7947164 Merge pull request #1492 from dpuyosa/feat/venice-add-gemma4-uncensored
Venice: Add Gemma 4 and Venice Uncensored 1.2 models
2026-04-19 16:52:28 -05:00
Aiden Cline dd0c1af12a Merge pull request #1491 from dpuyosa/chore/venice-pricing-update
Venice: Update pricing for Grok 4.20 and Qwen3.5 9B
2026-04-19 16:52:15 -05:00
Aiden Cline 0c93cc03be Merge pull request #1485 from anomalyco/more-extends-cases
migrate more providers to extends format
2026-04-19 16:51:58 -05:00
Aiden Cline 62e25b73f8 Merge branch 'dev' into more-extends-cases 2026-04-19 16:47:05 -05:00
Aiden Cline 17093e0031 Merge pull request #1489 from berget-ai/update/berget-prices-gemma4
chore: update berget.ai models - prices and Gemma 4
2026-04-19 16:42:06 -05:00
Aiden Cline ae542978f0 Merge pull request #1490 from BlockListed/cortecs-add-claude-opus-4-7
add claude opus 4.7
2026-04-19 16:41:36 -05:00
dpuyosa 7f16117bba [venice] Add Gemma 4 and Venice Uncensored 1.2 models
- Add Gemma 4 Uncensored with 256K context, image support
- Add Venice Uncensored 1.2 with 128K context, image support
- Both models support tool calls and structured output
2026-04-19 23:41:17 +02:00
dpuyosa 2b96a2d3d6 [venice-models] Update pricing for Grok 4.20 and Qwen3.5 9B
- Update cache_read pricing for Grok 4.20 context_over_200k (0.23 → 0.45)
- Update input cost for Qwen3.5 9B (0.05 → 0.1)
2026-04-19 23:38:20 +02:00
BlockListed 9799a841c6 add claude opus 4.7
yes this model id is correct, cortecs is weird.
2026-04-19 23:13:58 +02:00
Christian Landgren 71c59b4235 chore: update berget.ai models - prices and Gemma 4
- Add Google Gemma 4 31B Instruct model
- Update prices for existing models (EUR to USD conversion)
- Remove non-coding models (bge-reranker, multilingual-e5 embeddings, kb-whisper)
- Remove deprecated Llama-3.1-8B-Instruct

Updated models:
- GLM-4.7: 0.77/2.75 USD/M (was 0.7/2.3)
- Llama-3.3-70B: 0.99/0.99 USD/M (was 0.9/0.9)
- Mistral-Small-3.2: 0.33/0.33 USD/M (was 0.3/0.3)
- GPT-OSS-120B: 0.44/0.99 USD/M (was 0.3/0.9)

New models:
- Gemma-4-31B-it: 0.275/0.55 USD/M

Removed models (not relevant for coding):
- BAAI/bge-reranker-v2-m3 (reranker)
- intfloat/multilingual-e5-large/* (embeddings)
- KBLab/kb-whisper-large (speech-to-text)
- meta-llama/Llama-3.1-8B-Instruct (deprecated)
2026-04-19 12:19:32 +02:00
Sewer56 0568b412aa Add wafer.ai provider with GLM-5.1 and Qwen3.5-397B-A17B models 2026-04-19 03:02:04 +01:00
Aiden Cline 812612465a Merge pull request #1484 from smakosh/feat/llmgateway-new-models
feat: update LLM Gateway to 182 models
2026-04-18 18:37:26 -05:00
Aiden Cline ac8a79dd74 Merge pull request #1487 from WJQSERVER/add/nvidia(nim)-z-ai-glm-5.1
Add Z.AI GLM-5.1 to NVIDIA(NIM)
2026-04-18 18:36:55 -05:00
smakosh 9460d981d4 fix: replace broken extends with concrete glm-4.6v-flash def
zhipuai/glm-4.6v-flash.toml is a symlink to zai/models/glm-4.6v-flash.toml
which does not exist, causing validate to fail with 'Unable to resolve
extends.from'. Inline the concrete definition instead.
2026-04-18 13:36:52 +02:00
smakosh 91db9d81ec feat: update LLM Gateway to 182 models
- Uses extends to reference canonical providers where
  possible (116 models), keeping 66 full definitions
- Only includes active text/chat models
- Adds new models: Claude Opus 4.7, Grok 4 Fast,
  Kimi K2, Mimo V2, GLM 5.1, Qwen 3 Coder, and more

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 13:20:41 +02:00
Jack d8d5f08df6 Merge pull request #1486 from chl-0537/feature/add-tencent
feat: add new provider and model
2026-04-18 17:30:53 +08:00
WJQSERVER 11d5443ab1 follow the nim modelcard change context length to 131072
https://build.nvidia.com/z-ai/glm-5.1/modelcard
Other Properties Related to Input: Supports multi-turn conversations, tool calling, system prompts, and extended agentic sessions. Input context length: 131,072 tokens.
2026-04-18 16:48:29 +08:00
wjqserver 90558e9eed add glm-5.1 2026-04-18 16:41:02 +08:00
Aiden Cline cb7d258e33 migrate more providers to extends format 2026-04-17 23:02:34 -05:00
Frank 2af43dc4f8 update zen models 2026-04-17 19:08:05 -04:00
Aiden Cline 93ddb6b131 Merge pull request #1482 from sopial42/ovhcloud/update-models-clean
chore(ovhcloud): remove 3 models no longer available in AI Endpoints
2026-04-17 16:50:53 -05:00
Aiden Cline 04bf671f18 Merge pull request #1481 from Spherrrical/add-digitalocean-provider
feat(provider): add DigitalOcean provider
2026-04-17 16:49:44 -05:00
Aiden Cline 1f3ba4ba21 Merge pull request #1483 from anomalyco/add-extends-support
feat: add extends support
2026-04-17 16:49:23 -05:00
aadhondt bb1c08dd41 chore(ovhcloud): remove 3 models no longer available in AI Endpoints
- deepseek-r1-distill-llama-70b
- mixtral-8x7b-instruct-v0.1
- qwen2.5-coder-32b-instruct

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 22:38:35 +02:00
Spherrrical 86d0f14397 feat(digitalocean): add DigitalOcean Gradient AI Platform provider
Adds the DigitalOcean provider with 46 models (Anthropic, OpenAI,
Arcee, fal, and DO-hosted open-source/embedding models) served via
the OpenAI-compatible endpoint at https://inference.do-ai.run/v1.
2026-04-17 13:07:47 -07:00
Misha Skvortsov 7336b3619c atomic-chat: add provider with initial blessed models
Adds Atomic Chat as a local OpenAI-compatible provider at
http://127.0.0.1:1337/v1. Includes logo and three curated models:

- unsloth/Qwen3.5-9B-IQ4_XS  (id: Qwen3_5-9B-IQ4_XS)
- unsloth/gemma-4-E4B-it-IQ4_XS  (id: gemma-4-E4B-it-IQ4_XS)
- unsloth/MiniMax-M2.5-UD-TQ1_0  (id: MiniMax-M2_5-UD-TQ1_0)

Model ids match the normalized form returned by Atomic Chat's
/v1/models endpoint (dots replaced with underscores).

Made-with: Cursor
2026-04-17 13:03:53 +03:00
mickalchen 73b81ac027 add tencent provider 2026-04-17 17:27:35 +08:00
Lyda ac7e35af4e feat(302ai): supplement commonly missing models 2026-04-15 17:32:18 +08:00
Nacho F. Lizaur 8f340e1eb2 feat: update kiro provider to use kiro-ai-provider npm package 2026-04-13 23:12:11 +02:00
Nacho F. Lizaur 62da5b0cbd feat: enable reasoning on Kiro Claude models 2026-04-13 19:49:25 +02:00
Nacho F. Lizaur 4e49abc10c feat: add Kiro provider 2026-04-13 19:49:25 +02:00
massaindustries 6ecd9ec509 add qwen-next-coder-2 2026-02-05 09:11:08 +00:00
massaindustries bb42d0b855 add qwen-next-coder 2026-02-05 09:09:30 +00:00
massaindustries a46966efef add-regolo-02 2026-01-29 15:14:02 +00:00
massaindustries 19ace4436f add-regolo-01 2026-01-29 12:36:44 +00:00
1767 changed files with 21553 additions and 10416 deletions
+89
View File
@@ -0,0 +1,89 @@
name: Sync Model Catalogs
on:
schedule:
- cron: "17 8 * * *"
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
sync:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- group: aggregators
title: "chore(sync): update aggregator model catalogs"
branch: automation/sync-models-aggregators
labels: automation,model-sync,sync-group:aggregators,provider:openrouter
- group: cloudflare
title: "chore(sync): update cloudflare model catalogs"
branch: automation/sync-models-cloudflare
labels: automation,model-sync,sync-group:cloudflare,provider:cloudflare-workers-ai
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: dev
- name: Setup Bun
uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Sync model catalogs
run: bun models:sync ${{ matrix.group }}
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID }}
CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN }}
- name: Validate models
run: bun validate
- name: Create pull request
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ matrix.branch }}
LABELS: ${{ matrix.labels }}
TITLE: ${{ matrix.title }}
run: |
if [ -z "$(git status --porcelain -- providers)" ]; then
echo "No model catalog changes found."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "$BRANCH"
git add providers
git commit -m "$TITLE"
git push --force-with-lease origin "$BRANCH"
label_args=()
IFS=',' read -ra labels <<< "$LABELS"
for label in "${labels[@]}"; do
gh label create "$label" --color "0E8A16" --description "Automated model catalog sync" >/dev/null 2>&1 || true
label_args+=(--label "$label")
done
pr_number="$(gh pr list --head "$BRANCH" --base dev --json number --jq '.[0].number')"
if [ -n "$pr_number" ]; then
gh pr edit "$pr_number" --title "$TITLE" --body-file .sync/model-sync-report.md
for label in "${labels[@]}"; do
gh pr edit "$pr_number" --add-label "$label"
done
else
gh pr create --base dev --head "$BRANCH" --title "$TITLE" --body-file .sync/model-sync-report.md "${label_args[@]}"
fi
+1
View File
@@ -3,6 +3,7 @@
.idea
dist
.DS_Store
.sync/
node_modules
data/tokenspeed-monitor.sqlite
data/tokenspeed-monitor.sqlite-shm
+34 -1
View File
@@ -120,6 +120,31 @@ output = ["text"] # Supported output modalities
field = "reasoning_content" # Name of the interleaved field "reasoning_content" or "reasoning_details"
```
#### 3a. Reuse an Existing Model with `extends`
For wrapper providers that mirror a model from another provider, prefer reusing the canonical model definition instead of duplicating the whole file.
Use `extends` only for non-first-party wrappers and mirrors. Do not use it inside the actual lab provider directories that act as the canonical source for a model family, for example `providers/anthropic/`, `providers/openai/`, `providers/google/`, `providers/xai/`, `providers/minimax/`, or `providers/moonshot/`.
```toml
[extends]
from = "anthropic/claude-opus-4-6"
omit = ["experimental.modes.fast"]
[provider]
npm = "@ai-sdk/anthropic"
```
Rules:
- `from` must point to another model using `<provider>/<model-id>`.
- `omit` is optional and removes fields after the inherited model and local overrides are merged.
- You can override any top-level model field locally.
- If you override a nested table like `[cost]`, `[limit]`, or `[modalities]`, include the full values needed for that table.
- `id` still comes from the filename; do not add it to the TOML.
Use `extends` when the wrapper model is materially the same as the source model and only differs by a small set of overrides or omitted fields.
#### 4. Submit a Pull Request
1. Fork this repo
@@ -136,9 +161,17 @@ There's a GitHub Action that will automatically validate your submission against
- Values are within acceptable ranges
- TOML syntax is valid
When converting existing wrapper models to `extends`, compare generated output before and after the change:
```bash
bun run compare:migrations
```
This prints a diff for each changed model TOML so you can confirm the generated JSON only changed where you intended.
### Schema Reference
Models must conform to the following schema, as defined in `app/schemas.ts`.
Models must conform to the following schema, as defined in `packages/core/src/schema.ts`.
**Provider Schema:**
+7 -12
View File
@@ -5,7 +5,7 @@
"": {
"name": "models.dev",
"dependencies": {
"@cloudflare/workers-types": "^4.20250801.0",
"@cloudflare/workers-types": "^4.20260424.1",
"sst": "3.17.23",
},
},
@@ -32,6 +32,7 @@
"packages/web": {
"name": "@models.dev/web",
"dependencies": {
"@tanstack/virtual-core": "^3.14.0",
"hono": "^4.8.0",
"models.dev": "workspace:*",
},
@@ -49,7 +50,7 @@
"zod": "3.24.2",
},
"packages": {
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250801.0", "", {}, "sha512-BQmMdoOGClY23TesgkR1PeGrPvPsSFD/zW7pDzWZHkOEsqkPk2A91h52bP8GbtKYTl1vdaYjQgJlGsP6Ih4G0w=="],
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260424.1", "", {}, "sha512-0DLJ9yEk1KKzPbqop80Gw/P1wkKKzawmipULiJWdBXIBCoMvE0OVWms3IrL/Q/G7tfmPop9yF4XlZ69k9JLYng=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
@@ -57,9 +58,11 @@
"@models.dev/web": ["@models.dev/web@workspace:packages/web"],
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.14.0", "", {}, "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q=="],
"@tsconfig/bun": ["@tsconfig/bun@1.0.8", "", {}, "sha512-JlJaRaS4hBTypxtFe8WhnwV8blf0R+3yehLk8XuyxUYNx6VXsKCjACSCvOYEFUiqlhlBWxtYCn/zRlOb8BzBQg=="],
"@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
@@ -79,7 +82,7 @@
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
"bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
@@ -325,8 +328,6 @@
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"models.dev/@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
@@ -334,11 +335,5 @@
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
"bun-types/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"models.dev/@types/bun/bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
"models.dev/@types/bun/bun-types/@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
"models.dev/@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
}
}
+1
View File
File diff suppressed because one or more lines are too long
+8 -2
View File
@@ -17,13 +17,19 @@
"scripts": {
"validate": "bun ./packages/core/script/validate.ts",
"compare:migrations": "bun ./packages/core/script/compare-model-migrations.ts",
"cloudflare:sync": "bun ./packages/core/script/sync-models.ts cloudflare-workers-ai",
"chutes:generate": "bun ./packages/core/script/generate-chutes.ts",
"databricks:generate": "bun ./packages/core/script/generate-databricks.ts",
"helicone:generate": "bun ./packages/core/script/generate-helicone.ts",
"venice:generate": "bun ./packages/core/script/generate-venice.ts",
"vercel:generate": "bun ./packages/core/script/generate-vercel.ts",
"wandb:generate": "bun ./packages/core/script/generate-wandb.ts"
"wandb:generate": "bun ./packages/core/script/generate-wandb.ts",
"digitalocean:generate": "bun ./packages/core/script/generate-digitalocean.ts",
"ambient:generate": "bun ./packages/core/script/generate-ambient.ts",
"models:sync": "bun ./packages/core/script/sync-models.ts"
},
"dependencies": {
"@cloudflare/workers-types": "^4.20250801.0",
"@cloudflare/workers-types": "^4.20260424.1",
"sst": "3.17.23"
}
}
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env bun
/**
* Generates Ambient model TOML files from https://api.ambient.xyz/v1/models.
*
* Emits `[extends]`-format TOMLs that inherit upstream metadata
* (family, release_date, knowledge, capabilities) from the canonical
* provider model, and override only the fields Ambient's API reports:
* cost, limit, modalities.
*
* Flags:
* --dry-run Preview generated TOMLs without writing files.
*/
import { z } from "zod";
import path from "node:path";
import { mkdir } from "node:fs/promises";
const API_ENDPOINT = "https://api.ambient.xyz/v1/models";
// Allowlist for the initial rollout.
const ALLOWLIST = new Set<string>([
"zai-org/GLM-5.1-FP8",
"moonshotai/kimi-k2.6",
]);
// Maps Ambient model IDs to canonical <provider>/<model> in this repo.
// The generated TOML uses this path as `[extends].from` so capabilities
// and metadata propagate from the upstream provider automatically.
const EXTENDS_MAP: Record<string, string> = {
"zai-org/GLM-5.1-FP8": "zai/glm-5.1",
"moonshotai/kimi-k2.6": "moonshotai/kimi-k2.6",
};
const Pricing = z
.object({
prompt: z.string(),
completion: z.string(),
input_cache_read: z.string().optional(),
input_cache_write: z.string().optional(),
})
.passthrough();
const AmbientModel = z
.object({
id: z.string(),
name: z.string(),
context_length: z.number(),
max_output_length: z.number(),
input_modalities: z.array(z.string()),
output_modalities: z.array(z.string()),
pricing: Pricing,
})
.passthrough();
const AmbientResponse = z
.object({
object: z.literal("list"),
data: z.array(AmbientModel),
})
.passthrough();
const ALLOWED_MODALITIES = new Set(["text", "audio", "image", "video", "pdf"]);
function modalities(values: string[]): string[] {
return values
.map((v) => v.toLowerCase())
.filter((v) => ALLOWED_MODALITIES.has(v));
}
function perMTok(price: string): number {
const n = parseFloat(price);
if (!Number.isFinite(n)) {
throw new Error(`Invalid price: ${price}`);
}
// Round to 6 decimals to absorb float noise from per-token strings.
return Math.round(n * 1_000_000 * 1_000_000) / 1_000_000;
}
function formatToml(
model: z.infer<typeof AmbientModel>,
extendsFrom: string,
): string {
const lines: string[] = [];
lines.push("[extends]");
lines.push(`from = "${extendsFrom}"`);
lines.push("");
lines.push("[cost]");
lines.push(`input = ${perMTok(model.pricing.prompt)}`);
lines.push(`output = ${perMTok(model.pricing.completion)}`);
if (model.pricing.input_cache_read !== undefined) {
lines.push(`cache_read = ${perMTok(model.pricing.input_cache_read)}`);
}
if (model.pricing.input_cache_write !== undefined) {
lines.push(`cache_write = ${perMTok(model.pricing.input_cache_write)}`);
}
lines.push("");
lines.push("[limit]");
lines.push(`context = ${model.context_length}`);
lines.push(`output = ${model.max_output_length}`);
lines.push("");
const input = modalities(model.input_modalities);
const output = modalities(model.output_modalities);
lines.push("[modalities]");
lines.push(`input = [${input.map((m) => `"${m}"`).join(", ")}]`);
lines.push(`output = [${output.map((m) => `"${m}"`).join(", ")}]`);
return lines.join("\n") + "\n";
}
async function main() {
const dryRun = process.argv.includes("--dry-run");
const outDir = path.join(
import.meta.dirname,
"..",
"..",
"..",
"providers",
"ambient",
"models",
);
const res = await fetch(API_ENDPOINT);
if (!res.ok) {
console.error(`Fetch failed: ${res.status} ${res.statusText}`);
process.exit(1);
}
const parsed = AmbientResponse.safeParse(await res.json());
if (!parsed.success) {
console.error("Invalid Ambient response:", parsed.error.issues);
process.exit(1);
}
const selected = parsed.data.data.filter((m) => ALLOWLIST.has(m.id));
const missing = [...ALLOWLIST].filter(
(id) => !selected.some((m) => m.id === id),
);
if (missing.length > 0) {
console.error(`Allowlisted models missing from API: ${missing.join(", ")}`);
process.exit(1);
}
let count = 0;
for (const model of selected) {
const extendsFrom = EXTENDS_MAP[model.id];
if (!extendsFrom) {
console.error(`No EXTENDS_MAP entry for ${model.id}; skipping`);
continue;
}
const filePath = path.join(outDir, `${model.id}.toml`);
const toml = formatToml(model, extendsFrom);
if (dryRun) {
console.log(`--- ${path.relative(process.cwd(), filePath)} ---`);
console.log(toml);
} else {
await mkdir(path.dirname(filePath), { recursive: true });
await Bun.write(filePath, toml);
}
count++;
}
console.log(
`${dryRun ? "Previewed" : "Wrote"} ${count} model file(s) under providers/ambient/models/`,
);
}
await main();
+589
View File
@@ -0,0 +1,589 @@
#!/usr/bin/env bun
/**
* Generates Chutes model TOML files from the Chutes LLM API.
*
* Flags:
* --dry-run: Preview changes without writing files
* --new-only: Only create new models, skip updating existing ones
* --keep-orphans: Don't delete TOML files for models no longer in the API
*/
import { z } from "zod";
import path from "node:path";
import { mkdir } from "node:fs/promises";
import { ModelFamilyValues } from "../src/family.js";
const API_ENDPOINT = "https://llm.chutes.ai/v1/models";
enum SkipZeroFields {
LimitContext = "limit.context",
LimitOutput = "limit.output",
}
const Pricing = z.object({
prompt: z.number().optional(),
completion: z.number().optional(),
input_cache_read: z.number().optional(),
}).passthrough();
const ChutesModel = z.object({
id: z.string(),
created: z.number(),
pricing: Pricing.optional(),
context_length: z.number().optional(),
max_output_length: z.number().optional(),
max_model_len: z.number().optional(),
input_modalities: z.array(z.string()).optional(),
output_modalities: z.array(z.string()).optional(),
supported_features: z.array(z.string()).optional(),
supported_sampling_parameters: z.array(z.string()).optional(),
quantization: z.string().optional(),
}).passthrough();
const ChutesResponse = z.object({
data: z.array(ChutesModel),
}).passthrough();
interface ExistingModel {
name?: string;
family?: string;
attachment?: boolean;
reasoning?: boolean;
tool_call?: boolean;
structured_output?: boolean;
temperature?: boolean;
knowledge?: string;
release_date?: string;
last_updated?: string;
open_weights?: boolean;
interleaved?: boolean | { field: string };
status?: string;
cost?: {
input?: number;
output?: number;
cache_read?: number;
};
limit?: {
context?: number;
output?: number;
};
modalities?: {
input?: string[];
output?: string[];
};
}
interface MergedModel {
name: string;
family?: string;
attachment: boolean;
reasoning: boolean;
tool_call: boolean;
structured_output?: boolean;
temperature: boolean;
knowledge?: string;
release_date: string;
last_updated: string;
open_weights: boolean;
interleaved?: boolean | { field: string };
status?: string;
cost?: {
input: number;
output: number;
cache_read?: number;
};
limit: {
context: number;
output: number;
};
modalities: {
input: string[];
output: string[];
};
}
interface Changes {
field: string;
oldValue: string;
newValue: string;
}
// ── Utility functions ────────────────────────────────────────────────
function timestampToDate(timestamp: number): string {
const date = new Date(timestamp * 1000);
return date.toISOString().slice(0, 10);
}
function getTodayDate(): string {
return new Date().toISOString().slice(0, 10);
}
function formatNumber(n: number): string {
if (n >= 1000) {
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "_");
}
return n.toString();
}
/**
* Humanize a model ID into a readable name.
* Strips the org prefix and replaces hyphens with spaces.
* e.g. "Qwen/Qwen3-32B-TEE" → "Qwen3 32B TEE"
*/
function humanizeModelName(modelId: string): string {
const parts = modelId.split("/");
const modelPart = parts[parts.length - 1];
return modelPart.replace(/-/g, " ");
}
// ── Family inference ───────────
function isSubstring(target: string, family: string): boolean {
return target.toLowerCase().includes(family.toLowerCase());
}
function matchesFamily(target: string, family: string): boolean {
const targetLower = target.toLowerCase();
const familyLower = family.toLowerCase();
let familyIdx = 0;
for (let i = 0; i < targetLower.length && familyIdx < familyLower.length; i++) {
if (targetLower[i] === familyLower[familyIdx]) {
familyIdx++;
}
}
return familyIdx === familyLower.length;
}
function inferFamily(modelId: string, modelName: string): string | undefined {
const sortedFamilies = [...ModelFamilyValues].sort((a, b) => b.length - a.length);
// First pass: try exact substring matches
for (const family of sortedFamilies) {
if (isSubstring(modelId, family)) {
return family;
}
}
for (const family of sortedFamilies) {
if (isSubstring(modelName, family)) {
return family;
}
}
// Second pass: fall back to subsequence matching
for (const family of sortedFamilies) {
if (matchesFamily(modelId, family)) {
return family;
}
}
for (const family of sortedFamilies) {
if (matchesFamily(modelName, family)) {
return family;
}
}
return undefined;
}
// ── Load existing TOML ───────────────────────────────────────────────
async function loadExistingModel(filePath: string): Promise<ExistingModel | null> {
try {
const file = Bun.file(filePath);
if (!(await file.exists())) {
return null;
}
const toml = await import(filePath, { with: { type: "toml" } }).then(
(mod) => mod.default,
);
return toml as ExistingModel;
} catch (e) {
console.warn(`Warning: Failed to parse existing file ${filePath}:`, e);
return null;
}
}
// ── Merge API data with existing TOML ────────────────────────────────
function mergeModel(
apiModel: z.infer<typeof ChutesModel>,
existing: ExistingModel | null,
): MergedModel {
const features = new Set(apiModel.supported_features ?? []);
const samplingParams = new Set(apiModel.supported_sampling_parameters ?? []);
const inputMods = apiModel.input_modalities ?? ["text"];
const outputMods = apiModel.output_modalities ?? ["text"];
// Capabilities from API features
const hasAttachment = inputMods.some((m) =>
m === "image" || m === "video" || m === "pdf",
);
const hasReasoning = features.has("reasoning");
const hasToolCall = features.has("tools");
const hasStructuredOutput = features.has("structured_outputs");
const hasTemperature = samplingParams.size > 0
? samplingParams.has("temperature")
: true; // default true if no sampling params info
// Preserve existing values when available (manually specified)
const modelName = existing?.name ?? humanizeModelName(apiModel.id);
const family = existing?.family ?? inferFamily(apiModel.id, modelName);
const knowledge = existing?.knowledge;
const interleaved = existing?.interleaved;
const status = existing?.status;
// Release date: existing > API created timestamp > today
const releaseDate = existing?.release_date
?? timestampToDate(apiModel.created)
?? getTodayDate();
// Context limit: prefer context_length, fallback to max_model_len
const apiContext = apiModel.context_length ?? apiModel.max_model_len ?? 0;
const contextLimit = apiContext > 0
? apiContext
: (existing?.limit?.context ?? 0);
// Output limit: prefer max_output_length, fallback to existing
const apiOutput = apiModel.max_output_length ?? 0;
const outputLimit = apiOutput > 0
? apiOutput
: (existing?.limit?.output ?? 0);
const merged: MergedModel = {
name: modelName,
family,
attachment: hasAttachment,
reasoning: hasReasoning,
tool_call: hasToolCall,
temperature: hasTemperature,
release_date: releaseDate,
last_updated: getTodayDate(),
open_weights: true, // Chutes hosts open-weight models
...(hasStructuredOutput && { structured_output: hasStructuredOutput }),
...(knowledge && { knowledge }),
...(interleaved !== undefined && { interleaved }),
...(status && { status }),
limit: {
context: contextLimit,
output: outputLimit,
},
modalities: {
input: inputMods,
output: outputMods,
},
};
// Cost: API values are already in USD per 1M tokens — use directly
if (apiModel.pricing) {
const inputPrice = apiModel.pricing.prompt;
const outputPrice = apiModel.pricing.completion;
const cacheReadPrice = apiModel.pricing.input_cache_read;
if (inputPrice !== undefined && outputPrice !== undefined) {
merged.cost = {
input: inputPrice,
output: outputPrice,
...(cacheReadPrice !== undefined && { cache_read: cacheReadPrice }),
};
}
}
return merged;
}
// ── TOML formatting ──────────────────────────────────────────────────
function formatToml(model: MergedModel): string {
const lines: string[] = [];
lines.push(`# Auto-generated by generate-chutes.ts — do not edit pricing, limits, or capabilities.`);
lines.push(`# Manual overrides preserved on re-run: name, family, knowledge, interleaved, status`);
lines.push(`name = "${model.name.replace(/"/g, '\\"')}"`);
if (model.family) {
lines.push(`family = "${model.family}"`);
}
lines.push(`release_date = "${model.release_date}"`);
lines.push(`last_updated = "${model.last_updated}"`);
lines.push(`attachment = ${model.attachment}`);
lines.push(`reasoning = ${model.reasoning}`);
lines.push(`temperature = ${model.temperature}`);
lines.push(`tool_call = ${model.tool_call}`);
if (model.structured_output !== undefined) {
lines.push(`structured_output = ${model.structured_output}`);
}
lines.push(`open_weights = ${model.open_weights}`);
if (model.knowledge) {
lines.push(`knowledge = "${model.knowledge}"`);
}
if (model.status) {
lines.push(`status = "${model.status}"`);
}
if (model.cost) {
lines.push("");
lines.push(`[cost]`);
lines.push(`input = ${model.cost.input}`);
lines.push(`output = ${model.cost.output}`);
if (model.cost.cache_read !== undefined) {
lines.push(`cache_read = ${model.cost.cache_read}`);
}
}
lines.push("");
lines.push(`[limit]`);
lines.push(`context = ${formatNumber(model.limit.context)}`);
lines.push(`output = ${formatNumber(model.limit.output)}`);
lines.push("");
lines.push(`[modalities]`);
lines.push(`input = [${model.modalities.input.map((m) => `"${m}"`).join(", ")}]`);
lines.push(`output = [${model.modalities.output.map((m) => `"${m}"`).join(", ")}]`);
if (model.interleaved !== undefined) {
lines.push("");
if (model.interleaved === true) {
lines.push(`interleaved = true`);
} else if (typeof model.interleaved === "object") {
lines.push(`[interleaved]`);
lines.push(`field = "${model.interleaved.field}"`);
}
}
return lines.join("\n") + "\n";
}
// ── Change detection ─────────────────────────────────────────────────
function detectChanges(
existing: ExistingModel | null,
merged: MergedModel,
): Changes[] {
if (!existing) return [];
const changes: Changes[] = [];
const EPSILON = 0.001;
const shouldSkipZero = (field: string, oldVal: unknown, newVal: unknown): boolean => {
if (!Object.values(SkipZeroFields).includes(field as SkipZeroFields)) {
return false;
}
return (typeof oldVal === "number" && oldVal === 0) || (typeof newVal === "number" && newVal === 0);
};
const formatValue = (val: unknown): string => {
if (typeof val === "number") return formatNumber(val);
if (Array.isArray(val)) return `[${val.join(", ")}]`;
if (val === undefined) return "(none)";
return String(val);
};
const isMaterialPriceDiff = (oldPrice: unknown, newPrice: unknown): boolean => {
if (oldPrice === 0 && newPrice === undefined) return false;
if (oldPrice !== undefined && newPrice !== undefined) {
return Math.abs((oldPrice as number) - (newPrice as number)) > EPSILON;
}
return oldPrice !== newPrice;
};
const compare = (field: string, oldVal: unknown, newVal: unknown) => {
if (shouldSkipZero(field, oldVal, newVal)) return;
const isDiff = field.startsWith("cost.")
? isMaterialPriceDiff(oldVal, newVal)
: JSON.stringify(oldVal) !== JSON.stringify(newVal);
if (isDiff) {
changes.push({
field,
oldValue: formatValue(oldVal),
newValue: formatValue(newVal),
});
}
};
compare("name", existing.name, merged.name);
compare("family", existing.family, merged.family);
compare("attachment", existing.attachment, merged.attachment);
compare("reasoning", existing.reasoning, merged.reasoning);
compare("tool_call", existing.tool_call, merged.tool_call);
compare("structured_output", existing.structured_output, merged.structured_output);
compare("open_weights", existing.open_weights, merged.open_weights);
compare("release_date", existing.release_date, merged.release_date);
compare("cost.input", existing.cost?.input, merged.cost?.input);
compare("cost.output", existing.cost?.output, merged.cost?.output);
compare("cost.cache_read", existing.cost?.cache_read, merged.cost?.cache_read);
compare("limit.context", existing.limit?.context, merged.limit.context);
compare("limit.output", existing.limit?.output, merged.limit.output);
compare("modalities.input", existing.modalities?.input, merged.modalities.input);
compare("modalities.output", existing.modalities?.output, merged.modalities.output);
return changes;
}
// ── Main ─────────────────────────────────────────────────────────────
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const newOnly = args.includes("--new-only");
const keepOrphans = args.includes("--keep-orphans");
const modelsDir = path.join(
import.meta.dirname,
"..",
"..",
"..",
"providers",
"chutes",
"models",
);
console.log(`${dryRun ? "[DRY RUN] " : ""}${newOnly ? "[NEW ONLY] " : ""}${keepOrphans ? "[KEEP ORPHANS] " : ""}Fetching Chutes models from API...`);
const res = await fetch(API_ENDPOINT);
if (!res.ok) {
console.error(`Failed to fetch API: ${res.status} ${res.statusText}`);
process.exit(1);
}
const json = await res.json();
const parsed = ChutesResponse.safeParse(json);
if (!parsed.success) {
console.error("Invalid API response:", parsed.error.errors);
process.exit(1);
}
const apiModels = parsed.data.data;
// Scan existing TOML files
const existingFiles = new Set<string>();
try {
for await (const file of new Bun.Glob("**/*.toml").scan({
cwd: modelsDir,
absolute: false,
})) {
existingFiles.add(file);
}
} catch {
}
console.log(`Found ${apiModels.length} models in API, ${existingFiles.size} existing files\n`);
const apiModelIds = new Set<string>();
let created = 0;
let updated = 0;
let unchanged = 0;
for (const apiModel of apiModels) {
const relativePath = `${apiModel.id}.toml`;
const filePath = path.join(modelsDir, relativePath);
const dirPath = path.dirname(filePath);
apiModelIds.add(relativePath);
const existing = await loadExistingModel(filePath);
const merged = mergeModel(apiModel, existing);
const tomlContent = formatToml(merged);
if (existing === null) {
created++;
if (dryRun) {
console.log(`[DRY RUN] Would create: ${relativePath}`);
console.log(` name = "${merged.name}"`);
if (merged.family) {
console.log(` family = "${merged.family}" (inferred)`);
}
console.log("");
} else {
await mkdir(dirPath, { recursive: true });
await Bun.write(filePath, tomlContent);
console.log(`Created: ${relativePath}`);
}
} else {
if (newOnly) {
unchanged++;
continue;
}
const changes = detectChanges(existing, merged);
const existingContent = await Bun.file(filePath).text();
const formatChanged = existingContent !== tomlContent;
if (changes.length > 0 || formatChanged) {
updated++;
if (dryRun) {
console.log(`[DRY RUN] Would update: ${relativePath}`);
} else {
await mkdir(dirPath, { recursive: true });
await Bun.write(filePath, tomlContent);
console.log(`Updated: ${relativePath}`);
}
for (const change of changes) {
console.log(` ${change.field}: ${change.oldValue}${change.newValue}`);
}
if (changes.length === 0 && formatChanged) {
console.log(` (format-only change)`);
}
console.log("");
} else {
unchanged++;
}
}
}
// Handle orphaned files (on disk but not in API)
const orphaned: string[] = [];
for (const file of existingFiles) {
if (!apiModelIds.has(file)) {
orphaned.push(file);
const orphanPath = path.join(modelsDir, file);
if (keepOrphans) {
console.log(`Orphaned (kept): ${file}`);
} else if (dryRun) {
console.log(`[DRY RUN] Would delete: ${file}`);
} else {
await Bun.file(orphanPath).delete();
console.log(`Deleted: ${file}`);
// Clean up empty parent directories
const parentDir = path.dirname(orphanPath);
try {
const remaining = [];
for await (const entry of new Bun.Glob("*").scan({ cwd: parentDir })) {
remaining.push(entry);
}
if (remaining.length === 0) {
const { rmdir } = await import("node:fs/promises");
await rmdir(parentDir);
console.log(` Removed empty directory: ${path.basename(parentDir)}/`);
}
} catch {
// Directory not empty or other error, ignore
}
}
}
}
console.log("");
if (dryRun) {
console.log(
`Summary: ${created} would be created, ${updated} would be updated, ${unchanged} unchanged, ${orphaned.length} would be deleted`,
);
} else if (keepOrphans) {
console.log(
`Summary: ${created} created, ${updated} updated, ${unchanged} unchanged, ${orphaned.length} orphaned (kept)`,
);
} else {
console.log(
`Summary: ${created} created, ${updated} updated, ${unchanged} unchanged, ${orphaned.length} deleted`,
);
}
}
await main();
+287
View File
@@ -0,0 +1,287 @@
#!/usr/bin/env bun
/**
* Generates Databricks model TOML files from the Foundation Model API endpoint.
*
* Each Databricks endpoint exposes a model from another provider (Anthropic,
* OpenAI, Google, etc.), so the generated TOML uses [extends] to inherit
* canonical metadata from that upstream provider's TOML in models.dev.
*
* Usage:
* DATABRICKS_HOST=<host> DATABRICKS_TOKEN=<pat> bun run databricks:generate
* bun run databricks:generate --workspace <host> --token <pat>
*
* Flags:
* --dry-run: Preview changes without writing files
* --new-only: Only create new models, skip updating existing ones
*/
import { z } from "zod";
import path from "node:path";
import { mkdir, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
const args = process.argv.slice(2);
const flag = (name: string) => {
const i = args.indexOf(`--${name}`);
return i !== -1 ? args[i + 1] : undefined;
};
const dryRun = args.includes("--dry-run");
const newOnly = args.includes("--new-only");
const host = flag("workspace") ?? process.env.DATABRICKS_HOST;
const token = flag("token") ?? process.env.DATABRICKS_TOKEN;
if (!host || !token) {
console.error(
"Usage: DATABRICKS_HOST=<host> DATABRICKS_TOKEN=<pat> bun run databricks:generate",
);
process.exit(1);
}
const workspace = host.replace(/^https?:\/\//, "").replace(/\/$/, "");
const PROVIDERS_DIR = path.join(import.meta.dirname, "..", "..", "..", "providers");
const MODELS_DIR = path.join(PROVIDERS_DIR, "databricks", "models");
// ---------------------------------------------------------------------------
// API schemas
// ---------------------------------------------------------------------------
const FoundationModel = z
.object({
ai_gateway_v2_supported: z.boolean().optional(),
api_types: z.array(z.string()).optional(),
})
.passthrough();
const ServedEntity = z
.object({
foundation_model: FoundationModel.optional(),
})
.passthrough();
const Endpoint = z
.object({
name: z.string(),
config: z
.object({
served_entities: z.array(ServedEntity).optional(),
})
.passthrough()
.optional(),
})
.passthrough();
const FoundationModelsResponse = z
.object({
endpoints: z.array(Endpoint),
})
.passthrough();
// ---------------------------------------------------------------------------
// Canonical resolution: map a Databricks endpoint name to a models.dev entry
// ---------------------------------------------------------------------------
const PREFIX_TO_PROVIDER: [string, string][] = [
["claude-", "anthropic"],
["gpt-", "openai"],
["gemini-", "google"],
["mistral-", "mistral"],
["mixtral-", "mistral"],
];
type Resolution =
| { type: "extends"; from: string }
| { type: "inline"; content: string }
| null;
async function resolveCanonical(endpointName: string): Promise<Resolution> {
const bare = endpointName.replace(/^databricks-/, "");
// Models in provider subdirectories (e.g. openrouter/openai/gpt-oss-*)
// can't use [extends] (schema requires provider/model format), so inline.
if (bare.startsWith("gpt-oss-")) {
const p = path.join(PROVIDERS_DIR, "openrouter", "models", "openai", `${bare}.toml`);
if (existsSync(p)) {
return { type: "inline", content: await readFile(p, "utf8") };
}
}
// Meta Llama: "meta-llama-3-3-70b-instruct" → "llama-3.3-70b-instruct"
if (bare.startsWith("meta-llama-") || bare.startsWith("llama-")) {
const llamaId = bare
.replace(/^meta-llama-/, "llama-")
.replace(/^(llama-\d+)-(\d+)-/, "$1.$2-");
const p = path.join(PROVIDERS_DIR, "llama", "models", `${llamaId}.toml`);
if (existsSync(p)) return { type: "extends", from: `llama/${llamaId}` };
}
for (const [prefix, provider] of PREFIX_TO_PROVIDER) {
if (!bare.startsWith(prefix)) continue;
const exact = path.join(PROVIDERS_DIR, provider, "models", `${bare}.toml`);
if (existsSync(exact)) return { type: "extends", from: `${provider}/${bare}` };
// Try with hyphens-as-dots in version (e.g. gpt-5-4 → gpt-5.4)
const dotted = bare.replace(/^((?:[a-z]+-)+\d+)-(\d)/, "$1.$2");
if (dotted !== bare) {
const dottedExact = path.join(PROVIDERS_DIR, provider, "models", `${dotted}.toml`);
if (existsSync(dottedExact)) return { type: "extends", from: `${provider}/${dotted}` };
}
// Fuzzy: longest filename that shares a prefix with bare or its dotted form
const candidates = [bare, ...(dotted !== bare ? [dotted] : [])];
const files: string[] = [];
try {
for await (const f of new Bun.Glob("*.toml").scan({
cwd: path.join(PROVIDERS_DIR, provider, "models"),
})) {
files.push(f);
}
} catch {
// provider directory may not exist
}
const match = files
.map((f) => f.replace(/\.toml$/, ""))
.filter((id) => candidates.some((c) => id.startsWith(c) || c.startsWith(id)))
.sort((a, b) => b.length - a.length)[0];
if (match) return { type: "extends", from: `${provider}/${match}` };
}
return null;
}
function formatToml(resolution: Resolution, endpointName: string): string {
if (resolution?.type === "extends") {
return `[extends]\nfrom = "${resolution.from}"\n`;
}
if (resolution?.type === "inline") {
return resolution.content;
}
return `# TODO: fill in details for ${endpointName}\nname = "${endpointName}"\n`;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
const IGNORE_PREFIXES = [
"databricks-llama-",
"databricks-meta-llama-",
"databricks-qwen",
"databricks-gemma-",
];
async function main() {
console.log(
`${dryRun ? "[DRY RUN] " : ""}${newOnly ? "[NEW ONLY] " : ""}Fetching Databricks foundation-models...`,
);
const url = `https://${workspace}/api/2.0/serving-endpoints:foundation-models`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
console.error(`Failed to fetch API: ${res.status} ${res.statusText}`);
console.error(await res.text().catch(() => ""));
process.exit(1);
}
const json = await res.json();
const parsed = FoundationModelsResponse.safeParse(json);
if (!parsed.success) {
console.error("Invalid API response:", parsed.error.errors);
process.exit(1);
}
const endpoints = parsed.data.endpoints.filter(
(e) =>
!IGNORE_PREFIXES.some((p) => e.name.startsWith(p)) &&
e.config?.served_entities?.some(
(se) =>
se.foundation_model?.ai_gateway_v2_supported === true &&
se.foundation_model?.api_types?.includes("mlflow/v1/chat/completions"),
),
);
const existingFiles = new Set<string>();
try {
for await (const f of new Bun.Glob("*.toml").scan({ cwd: MODELS_DIR })) {
existingFiles.add(f);
}
} catch {
// directory may not exist yet
}
console.log(
`Found ${endpoints.length} models in API, ${existingFiles.size} existing files\n`,
);
const apiModelIds = new Set<string>();
let created = 0;
let updated = 0;
let unchanged = 0;
for (const ep of endpoints) {
const filename = `${ep.name}.toml`;
apiModelIds.add(filename);
const filePath = path.join(MODELS_DIR, filename);
const resolution = await resolveCanonical(ep.name);
const newContent = formatToml(resolution, ep.name);
const tag = resolution?.type === "extends" ? `extends ${resolution.from}` : resolution?.type ?? "stub";
const existed = existsSync(filePath);
if (!existed) {
created++;
if (dryRun) {
console.log(`[DRY RUN] Would create: ${filename}${tag}`);
} else {
await mkdir(MODELS_DIR, { recursive: true });
await Bun.write(filePath, newContent);
console.log(`Created: ${filename}${tag}`);
}
continue;
}
if (newOnly) {
unchanged++;
continue;
}
const existingContent = await readFile(filePath, "utf8");
if (existingContent === newContent) {
unchanged++;
continue;
}
updated++;
if (dryRun) {
console.log(`[DRY RUN] Would update: ${filename}${tag}`);
} else {
await Bun.write(filePath, newContent);
console.log(`Updated: ${filename}${tag}`);
}
}
const orphaned: string[] = [];
for (const file of existingFiles) {
if (!apiModelIds.has(file)) {
orphaned.push(file);
console.log(`Warning: Orphaned file (not in API): ${file}`);
}
}
console.log("");
if (dryRun) {
console.log(
`Summary: ${created} would be created, ${updated} would be updated, ${unchanged} unchanged, ${orphaned.length} orphaned`,
);
} else {
console.log(
`Summary: ${created} created, ${updated} updated, ${unchanged} unchanged, ${orphaned.length} orphaned`,
);
}
}
await main();
@@ -0,0 +1,732 @@
#!/usr/bin/env bun
/**
* Generates DigitalOcean model TOML files from two public APIs:
*
* - https://api.digitalocean.com/v2/gen-ai/models (model metadata, lifecycle, modalities, limits)
* - https://www.digitalocean.com/api/static-content/v1/products (pricing, including >200k tiers)
*
* The v2 models API requires a DigitalOcean personal access token or model access key,
* read from the DIGITALOCEAN_API_TOKEN environment variable (or --api-key flag).
* The static-content pricing API is public and requires no auth.
*
* Cache pricing (cache_read, cache_write) is NOT available from any DO API and is
* preserved from existing TOML files when present.
*
* Fields the APIs cannot provide (preserved from existing TOMLs, never overwritten):
* family, knowledge, open_weights, interleaved, attachment, release_date,
* cache_read, cache_write
*
* Flags:
* --dry-run Preview changes without writing files
* --new-only Only create new models, skip updating existing ones
* --api-key=<key> DigitalOcean API key (overrides DIGITALOCEAN_API_TOKEN env var)
*/
import { z } from "zod";
import path from "node:path";
import { mkdir } from "node:fs/promises";
import { ModelFamilyValues } from "../src/family.js";
const MODELS_API = "https://api.digitalocean.com/v2/gen-ai/models";
const PRICING_API = "https://www.digitalocean.com/api/static-content/v1/products";
// ---------------------------------------------------------------------------
// v2 models API schema
// ---------------------------------------------------------------------------
const DoModel = z
.object({
id: z.string(),
name: z.string(),
lifecycle_status: z.string(),
type: z.string().optional(),
thinking: z.boolean().optional(),
context_window: z.union([z.number(), z.string()]).optional(),
modalities: z
.object({
input: z.array(z.string()).optional(),
output: z.array(z.string()).optional(),
})
.optional(),
settings: z
.array(
z.object({
name: z.string(),
max: z.number().optional(),
default_value: z.number().optional(),
}),
)
.optional(),
created_at: z.string().optional(),
})
.passthrough();
const DoModelsResponse = z
.object({
models: z.array(DoModel),
})
.passthrough();
// ---------------------------------------------------------------------------
// static-content pricing API schema
// ---------------------------------------------------------------------------
const PricingEntry = z
.object({
name: z.string(),
slug: z.string(),
model: z.string(),
prompt_tokens: z.string().optional(), // "≤200k" | ">200k" | undefined
price: z.object({ rate: z.number() }),
})
.passthrough();
const StaticContentResponse = z
.object({
gradient: z.object({
models: z.array(PricingEntry),
}),
})
.passthrough();
// ---------------------------------------------------------------------------
// Derived pricing map
// ---------------------------------------------------------------------------
interface ModelPricing {
input: number;
output: number;
inputOver200k?: number;
outputOver200k?: number;
}
// Map marketing names from /v1/products to API model IDs from /v2/gen-ai/models.
// The pricing API uses display names, not the machine IDs, so this table is the
// join key. Add entries here when DO adds new models with tiered pricing.
const PRICING_NAME_MAP: Record<string, string> = {
// Anthropic
"claude sonnet 4.6": "anthropic-claude-4.6-sonnet",
"claude sonnet 4.5": "anthropic-claude-4.5-sonnet",
"claude sonnet 4": "anthropic-claude-sonnet-4",
"claude haiku 4.5": "anthropic-claude-haiku-4.5",
"claude opus 4.6": "anthropic-claude-opus-4.6",
"claude opus 4.5": "anthropic-claude-opus-4.5",
"claude opus 4.1": "anthropic-claude-4.1-opus",
"claude opus 4": "anthropic-claude-opus-4",
// OpenAI
"gpt-5.4": "openai-gpt-5.4",
"gpt-5.4 mini": "openai-gpt-5.4-mini",
"gpt-5.4 nano": "openai-gpt-5.4-nano",
"gpt-5.4 pro": "openai-gpt-5.4-pro",
"gpt-5.3-codex": "openai-gpt-5.3-codex",
"gpt-5.2": "openai-gpt-5.2",
"gpt-5.2 pro": "openai-gpt-5.2-pro",
"gpt-5.1-codex-max": "openai-gpt-5.1-codex-max",
"gpt-5": "openai-gpt-5",
"gpt-5 mini": "openai-gpt-5-mini",
"gpt-5 nano": "openai-gpt-5-nano",
"gpt-4.1": "openai-gpt-4.1",
"gpt image 1": "openai-gpt-image-1",
"gpt image 1.5": "openai-gpt-image-1.5",
"gpt-oss-120b": "openai-gpt-oss-120b",
"gpt-oss-20b": "openai-gpt-oss-20b",
"gpt-4o": "openai-gpt-4o",
"gpt-4o mini": "openai-gpt-4o-mini",
"o1": "openai-o1",
"o3-mini": "openai-o3-mini",
// DeepSeek
"deepseek r1 distill llama 70b": "deepseek-r1-distill-llama-70b",
// Llama
"llama 3.3 70b": "llama3.3-70b-instruct",
// DO-hosted
"qwen3-32b": "alibaba-qwen3-32b",
"minimax m2.5 (public preview)": "minimax-m2.5",
"kimi k2.5": "kimi-k2.5",
"nvidia nemotron 3 super 120b (public preview)": "nvidia-nemotron-3-super-120b",
"glm 5": "glm-5",
};
function normalizeDisplayName(raw: string): string {
// Strip " Input Tokens" / " Output Tokens" suffix and lowercase
return raw
.replace(/\s+(input|output)\s+tokens$/i, "")
.trim()
.toLowerCase();
}
function buildPricingMap(entries: z.infer<typeof PricingEntry>[]): Map<string, ModelPricing> {
const map = new Map<string, ModelPricing>();
for (const entry of entries) {
const displayName = normalizeDisplayName(entry.name);
const modelId = PRICING_NAME_MAP[displayName];
if (!modelId) continue;
const isInput = entry.name.toLowerCase().includes("input tokens");
const isOver200k = entry.prompt_tokens === ">200k";
// Round to avoid float noise (e.g. 0.9900000000000001)
const rate = Math.round(entry.price.rate * 10000) / 10000;
const existing = map.get(modelId) ?? ({} as ModelPricing);
if (isInput && isOver200k) existing.inputOver200k = rate;
else if (!isInput && isOver200k) existing.outputOver200k = rate;
else if (isInput) existing.input = rate;
else existing.output = rate;
map.set(modelId, existing);
}
return map;
}
// ---------------------------------------------------------------------------
// Existing TOML shape (fields we read and may preserve)
// ---------------------------------------------------------------------------
interface ExistingModel {
name?: string;
family?: string;
attachment?: boolean;
reasoning?: boolean;
tool_call?: boolean;
structured_output?: boolean;
temperature?: boolean;
knowledge?: string;
release_date?: string;
last_updated?: string;
open_weights?: boolean;
interleaved?: boolean | { field: string };
status?: string;
cost?: {
input?: number;
output?: number;
cache_read?: number;
cache_write?: number;
context_over_200k?: {
input?: number;
output?: number;
cache_read?: number;
cache_write?: number;
context_min?: number;
};
tiers?: Array<{
tier: {
type?: "context";
size: number;
};
input?: number;
output?: number;
cache_read?: number;
cache_write?: number;
}>;
};
limit?: {
context?: number;
input?: number;
output?: number;
};
modalities?: {
input?: string[];
output?: string[];
};
}
async function loadExisting(filePath: string): Promise<ExistingModel | null> {
const file = Bun.file(filePath);
if (!(await file.exists())) return null;
try {
const mod = await import(filePath, { with: { type: "toml" } });
return mod.default as ExistingModel;
} catch (e) {
console.warn(`Warning: failed to parse ${filePath}:`, e);
return null;
}
}
// ---------------------------------------------------------------------------
// Merged model shape (what we write)
// ---------------------------------------------------------------------------
interface MergedModel {
name: string;
family?: string;
attachment: boolean;
reasoning: boolean;
tool_call: boolean;
structured_output?: boolean;
temperature: boolean;
knowledge?: string;
release_date: string;
last_updated: string;
open_weights: boolean;
interleaved?: boolean | { field: string };
status?: string;
cost?: {
input: number;
output: number;
cache_read?: number;
cache_write?: number;
context_over_200k?: {
input: number;
output: number;
cache_read?: number;
cache_write?: number;
context_min?: number;
};
};
limit: {
context: number;
output: number;
};
modalities: {
input: string[];
output: string[];
};
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const VALID_INPUT_MODALITIES = new Set(["text", "audio", "image", "video", "pdf"]);
const VALID_OUTPUT_MODALITIES = new Set(["text", "audio", "image", "video", "pdf"]);
function filterInputModalities(raw: string[]): string[] {
return raw.filter((m) => VALID_INPUT_MODALITIES.has(m));
}
function filterOutputModalities(raw: string[]): string[] {
// "code" is not a valid modality in the schema — map to "text"
return [...new Set(raw.map((m) => (m === "code" ? "text" : m)).filter((m) => VALID_OUTPUT_MODALITIES.has(m)))];
}
function getTodayDate(): string {
return new Date().toISOString().slice(0, 10);
}
function formatNumber(n: number): string {
return n >= 1000 ? n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "_") : n.toString();
}
function inferFamily(modelId: string, modelName: string): string | undefined {
const sorted = [...ModelFamilyValues].sort((a, b) => b.length - a.length);
const targets = [modelId.toLowerCase(), modelName.toLowerCase()];
for (const family of sorted) {
const f = family.toLowerCase();
for (const t of targets) {
if (t.includes(f)) return family;
}
}
return undefined;
}
function getExistingLongContextCost(existing: ExistingModel | null) {
const tier = existing?.cost?.tiers?.find(
(tier) =>
(tier.tier.type === undefined || tier.tier.type === "context") &&
tier.tier.size >= 200_000,
);
if (tier) {
return {
...tier,
context_min: tier.tier.size,
};
}
return existing?.cost?.context_over_200k === undefined
? undefined
: {
...existing.cost.context_over_200k,
context_min: 200_000,
};
}
function getLongContextMin(cost: { context_min?: number }) {
return cost.context_min ?? 200_000;
}
function formatInlineNumber(n: number): string {
return n >= 1000 ? n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "_") : n.toString();
}
// ---------------------------------------------------------------------------
// Merge API data with existing TOML
// ---------------------------------------------------------------------------
function mergeModel(
apiModel: z.infer<typeof DoModel>,
pricing: ModelPricing | undefined,
existing: ExistingModel | null,
): MergedModel {
const rawInput = apiModel.modalities?.input ?? [];
const rawOutput = apiModel.modalities?.output ?? [];
const inputMods = filterInputModalities(rawInput.length > 0 ? rawInput : existing?.modalities?.input ?? ["text"]);
const outputMods = filterOutputModalities(rawOutput.length > 0 ? rawOutput : existing?.modalities?.output ?? ["text"]);
const maxTokensSetting = apiModel.settings?.find((s) => s.name === "max_tokens");
const maxTokens = maxTokensSetting?.max ?? existing?.limit?.output ?? 0;
const rawContext = apiModel.context_window;
const contextWindow =
rawContext !== undefined
? typeof rawContext === "string"
? parseInt(rawContext, 10)
: rawContext
: (existing?.limit?.context ?? 0);
const isDeprecated = apiModel.lifecycle_status === "end_of_life";
// Fields preserved from existing TOML (APIs don't provide these)
const family = existing?.family ?? inferFamily(apiModel.id, apiModel.name);
const knowledge = existing?.knowledge;
const openWeights = existing?.open_weights ?? false;
const interleaved = existing?.interleaved;
const attachment = existing?.attachment ?? inputMods.some((m) => m !== "text");
// reasoning: trust existing if set, else use API thinking flag as a hint
// (thinking flag is unreliable for non-LLM models so gate on output modality)
const isTextOutput = outputMods.includes("text") && !outputMods.includes("image") && !outputMods.includes("video");
const reasoning = existing?.reasoning ?? (isTextOutput && (apiModel.thinking ?? false));
// tool_call: no API signal, preserve existing or default true for text models
const toolCall = existing?.tool_call ?? isTextOutput;
// temperature: no API signal, preserve or default true
const temperature = existing?.temperature ?? true;
// structured_output: no API signal, preserve only
const structuredOutput = existing?.structured_output;
const releaseDate = existing?.release_date ?? apiModel.created_at?.slice(0, 10) ?? getTodayDate();
const merged: MergedModel = {
name: apiModel.name,
family,
attachment,
reasoning,
tool_call: toolCall,
temperature,
release_date: releaseDate,
last_updated: getTodayDate(),
open_weights: openWeights,
...(structuredOutput !== undefined && { structured_output: structuredOutput }),
...(knowledge && { knowledge }),
...(interleaved !== undefined && { interleaved }),
...(isDeprecated && { status: "deprecated" }),
limit: { context: contextWindow, output: maxTokens },
modalities: { input: inputMods, output: outputMods },
};
// Pricing: static-content API is the sole source of truth for prices.
// The v2 models API pricing is intentionally ignored. If a model has no
// entry in the static-content API, preserve existing TOML prices.
const inputPrice = pricing?.input ?? existing?.cost?.input;
const outputPrice = pricing?.output ?? existing?.cost?.output;
if (inputPrice !== undefined && outputPrice !== undefined) {
merged.cost = {
input: inputPrice,
output: outputPrice,
// Always preserve cache pricing — not available from any DO API
...(existing?.cost?.cache_read !== undefined && { cache_read: existing.cost.cache_read }),
...(existing?.cost?.cache_write !== undefined && { cache_write: existing.cost.cache_write }),
};
// Context-tiered pricing (>200k) from the static-content API
const existingLongContextCost = getExistingLongContextCost(existing);
if (pricing?.inputOver200k !== undefined && pricing?.outputOver200k !== undefined) {
merged.cost.context_over_200k = {
input: pricing.inputOver200k,
output: pricing.outputOver200k,
context_min: existingLongContextCost?.context_min ?? 200_000,
...(existingLongContextCost?.cache_read !== undefined && {
cache_read: existingLongContextCost.cache_read,
}),
...(existingLongContextCost?.cache_write !== undefined && {
cache_write: existingLongContextCost.cache_write,
}),
};
} else if (existingLongContextCost) {
// Preserve manually-entered tiered pricing if API has no data
merged.cost.context_over_200k = {
input: existingLongContextCost.input ?? inputPrice,
output: existingLongContextCost.output ?? outputPrice,
context_min: existingLongContextCost.context_min,
...(existingLongContextCost.cache_read !== undefined && {
cache_read: existingLongContextCost.cache_read,
}),
...(existingLongContextCost.cache_write !== undefined && {
cache_write: existingLongContextCost.cache_write,
}),
};
}
}
return merged;
}
// ---------------------------------------------------------------------------
// TOML serialiser
// ---------------------------------------------------------------------------
function formatToml(model: MergedModel): string {
const lines: string[] = [];
lines.push(`name = "${model.name.replace(/"/g, '\\"')}"`);
if (model.family) lines.push(`family = "${model.family}"`);
lines.push(`release_date = "${model.release_date}"`);
lines.push(`last_updated = "${model.last_updated}"`);
lines.push(`attachment = ${model.attachment}`);
lines.push(`reasoning = ${model.reasoning}`);
lines.push(`temperature = ${model.temperature}`);
lines.push(`tool_call = ${model.tool_call}`);
if (model.structured_output !== undefined) lines.push(`structured_output = ${model.structured_output}`);
if (model.knowledge) lines.push(`knowledge = "${model.knowledge}"`);
lines.push(`open_weights = ${model.open_weights}`);
if (model.status) lines.push(`status = "${model.status}"`);
if (model.interleaved !== undefined) {
lines.push("");
if (model.interleaved === true) {
lines.push(`interleaved = true`);
} else if (typeof model.interleaved === "object") {
lines.push(`[interleaved]`);
lines.push(`field = "${model.interleaved.field}"`);
}
}
if (model.cost) {
lines.push("");
lines.push(`[cost]`);
lines.push(`input = ${model.cost.input}`);
lines.push(`output = ${model.cost.output}`);
if (model.cost.cache_read !== undefined) lines.push(`cache_read = ${model.cost.cache_read}`);
if (model.cost.cache_write !== undefined) lines.push(`cache_write = ${model.cost.cache_write}`);
if (model.cost.context_over_200k) {
lines.push("");
lines.push(`[[cost.tiers]]`);
lines.push(`tier = { size = ${formatInlineNumber(getLongContextMin(model.cost.context_over_200k))} }`);
lines.push(`input = ${model.cost.context_over_200k.input}`);
lines.push(`output = ${model.cost.context_over_200k.output}`);
if (model.cost.context_over_200k.cache_read !== undefined)
lines.push(`cache_read = ${model.cost.context_over_200k.cache_read}`);
if (model.cost.context_over_200k.cache_write !== undefined)
lines.push(`cache_write = ${model.cost.context_over_200k.cache_write}`);
}
}
lines.push("");
lines.push(`[limit]`);
lines.push(`context = ${formatNumber(model.limit.context)}`);
lines.push(`output = ${formatNumber(model.limit.output)}`);
lines.push("");
lines.push(`[modalities]`);
lines.push(`input = [${model.modalities.input.map((m) => `"${m}"`).join(", ")}]`);
lines.push(`output = [${model.modalities.output.map((m) => `"${m}"`).join(", ")}]`);
return lines.join("\n") + "\n";
}
// ---------------------------------------------------------------------------
// Change detection
// ---------------------------------------------------------------------------
interface Change {
field: string;
oldValue: string;
newValue: string;
}
function formatValue(val: unknown): string {
if (val === undefined) return "(none)";
if (Array.isArray(val)) return `[${val.join(", ")}]`;
if (typeof val === "number") return formatNumber(val);
return String(val);
}
function detectChanges(existing: ExistingModel | null, merged: MergedModel): Change[] {
if (!existing) return [];
const changes: Change[] = [];
const EPSILON = 0.001;
const compare = (field: string, oldVal: unknown, newVal: unknown) => {
if (oldVal === undefined && newVal === undefined) return;
const isDiff = field.startsWith("cost.")
? Math.abs((oldVal as number ?? 0) - (newVal as number ?? 0)) > EPSILON
: JSON.stringify(oldVal) !== JSON.stringify(newVal);
if (isDiff) changes.push({ field, oldValue: formatValue(oldVal), newValue: formatValue(newVal) });
};
compare("name", existing.name, merged.name);
compare("reasoning", existing.reasoning, merged.reasoning);
compare("tool_call", existing.tool_call, merged.tool_call);
compare("attachment", existing.attachment, merged.attachment);
compare("status", existing.status, merged.status);
compare("cost.input", existing.cost?.input, merged.cost?.input);
compare("cost.output", existing.cost?.output, merged.cost?.output);
const existingLongContextCost = getExistingLongContextCost(existing);
compare("cost.context_over_200k.input", existingLongContextCost?.input, merged.cost?.context_over_200k?.input);
compare("cost.context_over_200k.output", existingLongContextCost?.output, merged.cost?.context_over_200k?.output);
compare("limit.context", existing.limit?.context, merged.limit.context);
compare("limit.output", existing.limit?.output, merged.limit.output);
compare("modalities.input", existing.modalities?.input, merged.modalities.input);
compare("modalities.output", existing.modalities?.output, merged.modalities.output);
return changes;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const newOnly = args.includes("--new-only");
// Resolve API key
const apiKeyArg = args.find((a) => a.startsWith("--api-key"));
const apiKey =
(apiKeyArg?.includes("=") ? apiKeyArg.split("=")[1] : args[args.indexOf(apiKeyArg!) + 1]) ??
process.env.DIGITALOCEAN_API_TOKEN;
if (!apiKey) {
console.error("Error: DIGITALOCEAN_API_TOKEN is required (or pass --api-key=<key>)");
console.error("Get one from: https://cloud.digitalocean.com/account/api/tokens");
process.exit(1);
}
const modelsDir = path.join(import.meta.dirname, "..", "..", "..", "providers", "digitalocean", "models");
const prefix = dryRun ? "[DRY RUN] " : "";
console.log(`${prefix}Fetching DigitalOcean models from API...`);
// Fetch both APIs in parallel
const [modelsRes, pricingRes] = await Promise.all([
fetch(MODELS_API, { headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" } }),
fetch(PRICING_API, { headers: { "User-Agent": "models.dev/digitalocean-sync" } }),
]);
if (!modelsRes.ok) {
console.error(`Failed to fetch models API: ${modelsRes.status} ${modelsRes.statusText}`);
if (modelsRes.status === 401 || modelsRes.status === 403)
console.error("Check your DIGITALOCEAN_API_TOKEN has read access.");
process.exit(1);
}
if (!pricingRes.ok) {
console.error(`Failed to fetch pricing API: ${pricingRes.status} ${pricingRes.statusText}`);
process.exit(1);
}
const modelsParsed = DoModelsResponse.safeParse(await modelsRes.json());
if (!modelsParsed.success) {
console.error("Unexpected models API response:", modelsParsed.error.errors);
process.exit(1);
}
const pricingParsed = StaticContentResponse.safeParse(await pricingRes.json());
if (!pricingParsed.success) {
console.error("Unexpected pricing API response:", pricingParsed.error.errors);
process.exit(1);
}
const apiModels = modelsParsed.data.models;
const pricingMap = buildPricingMap(pricingParsed.data.gradient.models);
// Collect existing TOML filenames for orphan detection
const existingFiles = new Set<string>();
for await (const file of new Bun.Glob("**/*.toml").scan({ cwd: modelsDir, absolute: false })) {
existingFiles.add(file);
}
console.log(`Found ${apiModels.length} models in API, ${existingFiles.size} existing TOML files\n`);
const apiModelFiles = new Set<string>();
let created = 0;
let updated = 0;
let unchanged = 0;
for (const apiModel of apiModels) {
// Skip non-text models that opencode can't use: image, video, audio, embedding, reranking
const outputMods = filterOutputModalities(apiModel.modalities?.output ?? []);
const isTextModel = outputMods.includes("text");
const isEmbedding = apiModel.type === "embedding";
const isReranking = apiModel.type === "reranking";
if (!isTextModel || isEmbedding || isReranking) continue;
// Model IDs may contain slashes (e.g. fal-ai/flux/schnell) — use as subpath
const relativePath = `${apiModel.id}.toml`;
const filePath = path.join(modelsDir, relativePath);
const dirPath = path.dirname(filePath);
apiModelFiles.add(relativePath);
const existing = await loadExisting(filePath);
const pricing = pricingMap.get(apiModel.id);
const merged = mergeModel(apiModel, pricing, existing);
const toml = formatToml(merged);
if (existing === null) {
created++;
if (dryRun) {
console.log(`[DRY RUN] Would create: ${relativePath}`);
console.log(` name = "${merged.name}"`);
if (pricing) console.log(` pricing: $${merged.cost?.input}/$${merged.cost?.output} per M tokens`);
if (merged.family) console.log(` family = "${merged.family}" (inferred)`);
console.log("");
} else {
await mkdir(dirPath, { recursive: true });
await Bun.write(filePath, toml);
console.log(`Created: ${relativePath}`);
}
continue;
}
if (newOnly) {
unchanged++;
continue;
}
const changes = detectChanges(existing, merged);
if (changes.length > 0) {
updated++;
if (dryRun) {
console.log(`[DRY RUN] Would update: ${relativePath}`);
} else {
await mkdir(dirPath, { recursive: true });
await Bun.write(filePath, toml);
console.log(`Updated: ${relativePath}`);
}
for (const c of changes) console.log(` ${c.field}: ${c.oldValue}${c.newValue}`);
console.log("");
} else {
unchanged++;
}
}
// Orphan detection: files in the TOML directory but not in the API
const orphaned: string[] = [];
for (const file of existingFiles) {
if (!apiModelFiles.has(file)) {
orphaned.push(file);
console.log(`Warning: orphaned file (not in API): ${file}`);
}
}
console.log("");
if (dryRun) {
console.log(
`Summary: ${created} would be created, ${updated} would be updated, ${unchanged} unchanged, ${orphaned.length} orphaned`,
);
} else {
console.log(`Summary: ${created} created, ${updated} updated, ${unchanged} unchanged, ${orphaned.length} orphaned`);
}
}
await main();
+44 -5
View File
@@ -162,7 +162,18 @@ interface ExistingModel {
output?: number;
cache_read?: number;
cache_write?: number;
context_min?: number;
};
tiers?: Array<{
tier: {
type?: "context";
size: number;
};
input?: number;
output?: number;
cache_read?: number;
cache_write?: number;
}>;
};
limit?: {
context?: number;
@@ -195,6 +206,30 @@ async function loadExistingModel(filePath: string): Promise<ExistingModel | null
}
}
function getExistingLongContextMin(existing: ExistingModel | null) {
return (
existing?.cost?.tiers?.find(
(tier) =>
(tier.tier.type === undefined || tier.tier.type === "context") &&
tier.tier.size >= 200_000,
)?.tier.size ?? 200_000
);
}
function getExistingLongContextCost(existing: ExistingModel | null) {
return (
existing?.cost?.tiers?.find(
(tier) =>
(tier.tier.type === undefined || tier.tier.type === "context") &&
tier.tier.size >= 200_000,
) ?? existing?.cost?.context_over_200k
);
}
function getLongContextMin(cost: { context_min?: number }) {
return cost.context_min ?? 200_000;
}
interface MergedModel {
name: string;
family?: string;
@@ -219,6 +254,7 @@ interface MergedModel {
output: number;
cache_read?: number;
cache_write?: number;
context_min?: number;
};
};
limit: {
@@ -292,6 +328,7 @@ function mergeModel(
merged.cost.context_over_200k = {
input: spec.pricing.extended.input.usd,
output: spec.pricing.extended.output.usd,
context_min: spec.pricing.extended.context_token_threshold,
...(spec.pricing.extended.cache_input && { cache_read: spec.pricing.extended.cache_input.usd }),
...(spec.pricing.extended.cache_write && { cache_write: spec.pricing.extended.cache_write.usd }),
};
@@ -366,7 +403,8 @@ function formatToml(model: MergedModel): string {
if (model.cost.context_over_200k) {
lines.push("");
lines.push(`[cost.context_over_200k]`);
lines.push(`[[cost.tiers]]`);
lines.push(`tier = { size = ${formatNumber(getLongContextMin(model.cost.context_over_200k))} }`);
lines.push(`input = ${model.cost.context_over_200k.input}`);
lines.push(`output = ${model.cost.context_over_200k.output}`);
if (model.cost.context_over_200k.cache_read !== undefined) {
@@ -438,10 +476,11 @@ function detectChanges(
compare("cost.output", existing.cost?.output, merged.cost?.output);
compare("cost.cache_read", existing.cost?.cache_read, merged.cost?.cache_read);
compare("cost.cache_write", existing.cost?.cache_write, merged.cost?.cache_write);
compare("cost.context_over_200k.input", existing.cost?.context_over_200k?.input, merged.cost?.context_over_200k?.input);
compare("cost.context_over_200k.output", existing.cost?.context_over_200k?.output, merged.cost?.context_over_200k?.output);
compare("cost.context_over_200k.cache_read", existing.cost?.context_over_200k?.cache_read, merged.cost?.context_over_200k?.cache_read);
compare("cost.context_over_200k.cache_write", existing.cost?.context_over_200k?.cache_write, merged.cost?.context_over_200k?.cache_write);
const existingLongContextCost = getExistingLongContextCost(existing);
compare("cost.context_over_200k.input", existingLongContextCost?.input, merged.cost?.context_over_200k?.input);
compare("cost.context_over_200k.output", existingLongContextCost?.output, merged.cost?.context_over_200k?.output);
compare("cost.context_over_200k.cache_read", existingLongContextCost?.cache_read, merged.cost?.context_over_200k?.cache_read);
compare("cost.context_over_200k.cache_write", existingLongContextCost?.cache_write, merged.cost?.context_over_200k?.cache_write);
compare("limit.context", existing.limit?.context, merged.limit.context);
compare("limit.output", existing.limit?.output, merged.limit.output);
compare("modalities.input", existing.modalities?.input, merged.modalities.input);
+428
View File
@@ -0,0 +1,428 @@
#!/usr/bin/env bun
import path from "node:path";
import { mkdir, readdir, rm } from "node:fs/promises";
import { z } from "zod";
import { AuthoredModel, AuthoredModelShape } from "../src/schema.js";
import { cloudflareWorkersAi } from "./sync/cloudflare-workers-ai.js";
import { google } from "./sync/google.js";
import { openrouter } from "./sync/openrouter.js";
import { xai } from "./sync/xai.js";
const ExistingModel = AuthoredModelShape.partial()
.extend({
extends: z
.object({
from: z.string(),
omit: z.array(z.string()).optional(),
})
.strict()
.optional(),
})
.strict();
export type ExistingModel = z.infer<typeof ExistingModel>;
export type SyncedModel = Omit<z.infer<typeof AuthoredModelShape>, "id">;
export interface SyncProvider<SourceModel> {
id: string;
name: string;
modelsDir: string;
skipCreates?: boolean;
sourceID?(model: SourceModel): string;
skippedNotice?(ids: string[]): string[];
fetchModels(): Promise<unknown>;
parseModels(raw: unknown): SourceModel[];
translateModel(
model: SourceModel,
context: { existing(id: string): ExistingModel | undefined },
): { id: string; model: SyncedModel } | undefined;
}
export interface SyncResult {
id: string;
name: string;
status: "changed" | "unchanged";
created: number;
updated: number;
deleted: number;
unchanged: number;
notices: string[];
files: Array<{ status: "created" | "updated" | "deleted"; path: string }>;
}
export const providers: {
"cloudflare-workers-ai": SyncProvider<any>;
google: SyncProvider<any>;
openrouter: SyncProvider<any>;
xai: SyncProvider<any>;
} = {
"cloudflare-workers-ai": cloudflareWorkersAi,
google,
openrouter,
xai,
};
export const groups = {
aggregators: ["openrouter"],
cloudflare: ["cloudflare-workers-ai"],
direct: ["google", "xai"],
} as const;
type ProviderID = keyof typeof providers;
interface SyncOptions {
dryRun?: boolean;
newOnly?: boolean;
}
export async function syncProviderByID(id: ProviderID, options: SyncOptions = {}) {
return syncProvider(providers[id], options);
}
export async function syncProvider<SourceModel>(
provider: SyncProvider<SourceModel>,
options: SyncOptions = {},
): Promise<SyncResult> {
console.log(`\nSyncing ${provider.name}...`);
const existing = await readExisting(provider.modelsDir);
const sourceModels = provider.parseModels(await provider.fetchModels());
const desired = new Map<string, { model: z.infer<typeof AuthoredModel>; content: string }>();
const skippedRemote: string[] = [];
for (const sourceModel of sourceModels) {
const translated = provider.translateModel(sourceModel, {
existing(id) {
return existing.get(`${id}.toml`)?.toml;
},
});
if (translated === undefined) {
if (provider.skipCreates) skippedRemote.push(provider.sourceID?.(sourceModel) ?? "unknown");
continue;
}
const relativePath = `${translated.id}.toml`;
if (provider.skipCreates && !existing.has(relativePath)) {
skippedRemote.push(translated.id);
continue;
}
if (desired.has(relativePath)) {
throw new Error(`Duplicate synced model path: ${provider.id}/${relativePath}`);
}
const parsed = AuthoredModel.safeParse({
id: translated.id,
...translated.model,
});
if (!parsed.success) {
parsed.error.cause = { provider: provider.id, path: relativePath };
throw parsed.error;
}
desired.set(relativePath, {
model: parsed.data,
content: formatToml(parsed.data),
});
}
const files: SyncResult["files"] = [];
let unchanged = 0;
for (const [relativePath, file] of desired) {
const filePath = path.join(provider.modelsDir, relativePath);
const current = existing.get(relativePath);
if (current === undefined) {
files.push({ status: "created", path: filePath });
if (options.dryRun) {
console.log(`Would create ${relativePath}`);
} else {
await mkdir(path.dirname(filePath), { recursive: true });
await Bun.write(filePath, file.content);
}
continue;
}
if (!sameModel(relativePath, current.toml, file.model)) {
if (options.newOnly) {
unchanged++;
continue;
}
files.push({ status: "updated", path: filePath });
if (options.dryRun) {
console.log(`Would update ${relativePath}`);
} else {
if (current.symlink) await rm(filePath, { force: true });
await Bun.write(filePath, file.content);
}
} else {
unchanged++;
}
}
for (const relativePath of existing.keys()) {
if (desired.has(relativePath)) continue;
if (options.newOnly) {
console.log(`Skipping removal in new-only mode: ${relativePath}`);
unchanged++;
continue;
}
const filePath = path.join(provider.modelsDir, relativePath);
files.push({ status: "deleted", path: filePath });
if (options.dryRun) {
console.log(`Would remove ${relativePath}`);
} else {
await rm(filePath, { force: true });
}
}
const result = summarize(provider, files, unchanged, provider.skippedNotice?.(skippedRemote) ?? []);
console.log(
`${options.dryRun ? "Dry run: " : ""}${result.created} created, ${result.updated} updated, ${result.deleted} removed, ${result.unchanged} unchanged`,
);
return result;
}
export async function syncTargets(target: string, options: SyncOptions = {}) {
const ids = target in groups
? groups[target as keyof typeof groups]
: target in providers
? [target as ProviderID]
: undefined;
if (ids === undefined) {
throw new Error(`Unknown sync target: ${target}`);
}
const results: SyncResult[] = [];
for (const id of ids) {
results.push(await syncProviderByID(id as ProviderID, options));
}
return results;
}
async function readExisting(modelsDir: string) {
const existing = new Map<string, { text: string; toml: ExistingModel; symlink: boolean }>();
for (const { file, symlink } of await tomlFiles(modelsDir)) {
const text = await Bun.file(path.join(modelsDir, file)).text();
const parsed = ExistingModel.safeParse(Bun.TOML.parse(text));
if (!parsed.success) {
parsed.error.cause = { path: path.join(modelsDir, file) };
throw parsed.error;
}
existing.set(file, { text, toml: parsed.data, symlink });
}
return existing;
}
async function tomlFiles(root: string, dir = "") {
const result: Array<{ file: string; symlink: boolean }> = [];
for (const entry of await readdir(path.join(root, dir), { withFileTypes: true })) {
const file = path.join(dir, entry.name);
if (entry.isDirectory()) {
result.push(...await tomlFiles(root, file));
} else if (entry.name.endsWith(".toml") && (entry.isFile() || entry.isSymbolicLink())) {
result.push({ file, symlink: entry.isSymbolicLink() });
}
}
return result;
}
function summarize(
provider: { id: string; name: string },
files: SyncResult["files"],
unchanged: number,
notices: string[],
): SyncResult {
return {
id: provider.id,
name: provider.name,
status: files.length > 0 ? "changed" : "unchanged",
created: files.filter((file) => file.status === "created").length,
updated: files.filter((file) => file.status === "updated").length,
deleted: files.filter((file) => file.status === "deleted").length,
unchanged,
notices,
files,
};
}
function sameModel(
relativePath: string,
current: ExistingModel,
desired: z.infer<typeof AuthoredModel>,
) {
const parsed = AuthoredModel.safeParse({
id: relativePath.slice(0, -5),
...current,
});
return parsed.success && stable(parsed.data) === stable(desired);
}
function stable(value: unknown): string {
if (Array.isArray(value)) {
const items = value.map(stable);
const ordered = value.every((item) => item === null || typeof item !== "object")
? items.sort()
: items;
return `[${ordered.join(",")}]`;
}
if (value !== null && typeof value === "object") {
return `{${Object.entries(value)
.filter(([, item]) => item !== undefined)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, item]) => `${JSON.stringify(key)}:${stable(item)}`)
.join(",")}}`;
}
return JSON.stringify(value);
}
async function writeReport(target: string, results: SyncResult[]) {
await mkdir(".sync", { recursive: true });
const lines = [
`Updates model TOMLs for the \`${target}\` sync target.`,
"",
"| Provider | Status | Created | Updated | Deleted |",
"| --- | --- | ---: | ---: | ---: |",
];
for (const result of results) {
lines.push(
`| ${result.name} | ${result.status} | ${result.created} | ${result.updated} | ${result.deleted} |`,
);
}
for (const result of results.filter((item) => item.files.length > 0)) {
lines.push("", `<details><summary>${result.name} changed files</summary>`, "");
for (const file of result.files) {
lines.push(`- ${file.status}: \`${file.path}\``);
}
lines.push("", "</details>");
}
const noticeResults = results.filter((item) => item.notices.length > 0);
if (noticeResults.length > 0) {
lines.push("", "## Notices");
for (const result of noticeResults) {
lines.push("", `### ${result.name}`);
for (const notice of result.notices) {
lines.push(`- ${notice}`);
}
}
}
lines.push("", "This PR was created automatically by the daily model sync workflow.");
await Bun.write(".sync/model-sync-report.md", `${lines.join("\n")}\n`);
}
function quote(value: string) {
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
}
function formatInteger(n: number) {
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, "_");
}
function formatNumber(n: number) {
return Number.isInteger(n) ? formatInteger(n) : String(n);
}
function formatToml(model: z.infer<typeof AuthoredModel>) {
const lines: string[] = [];
lines.push(`name = ${quote(model.name)}`);
if (model.family !== undefined) lines.push(`family = ${quote(model.family)}`);
lines.push(`release_date = ${quote(model.release_date)}`);
lines.push(`last_updated = ${quote(model.last_updated)}`);
lines.push(`attachment = ${model.attachment}`);
lines.push(`reasoning = ${model.reasoning}`);
if (model.temperature !== undefined) lines.push(`temperature = ${model.temperature}`);
lines.push(`tool_call = ${model.tool_call}`);
if (model.structured_output !== undefined) {
lines.push(`structured_output = ${model.structured_output}`);
}
if (model.knowledge !== undefined) lines.push(`knowledge = ${quote(model.knowledge)}`);
lines.push(`open_weights = ${model.open_weights}`);
if (model.status !== undefined) lines.push(`status = ${quote(model.status)}`);
if (model.interleaved !== undefined) {
lines.push("");
if (model.interleaved === true) {
lines.push("interleaved = true");
} else {
lines.push("[interleaved]");
lines.push(`field = ${quote(model.interleaved.field)}`);
}
}
if (model.cost !== undefined) {
lines.push("", "[cost]");
lines.push(`input = ${formatNumber(model.cost.input)}`);
lines.push(`output = ${formatNumber(model.cost.output)}`);
if (model.cost.reasoning !== undefined) {
lines.push(`reasoning = ${formatNumber(model.cost.reasoning)}`);
}
if (model.cost.cache_read !== undefined) {
lines.push(`cache_read = ${formatNumber(model.cost.cache_read)}`);
}
if (model.cost.cache_write !== undefined) {
lines.push(`cache_write = ${formatNumber(model.cost.cache_write)}`);
}
if (model.cost.input_audio !== undefined) {
lines.push(`input_audio = ${formatNumber(model.cost.input_audio)}`);
}
if (model.cost.output_audio !== undefined) {
lines.push(`output_audio = ${formatNumber(model.cost.output_audio)}`);
}
for (const tier of model.cost.tiers ?? []) {
lines.push("", "[[cost.tiers]]");
lines.push(`tier = { size = ${formatInteger(tier.tier.size)} }`);
lines.push(`input = ${formatNumber(tier.input)}`);
lines.push(`output = ${formatNumber(tier.output)}`);
if (tier.reasoning !== undefined) lines.push(`reasoning = ${formatNumber(tier.reasoning)}`);
if (tier.cache_read !== undefined) lines.push(`cache_read = ${formatNumber(tier.cache_read)}`);
if (tier.cache_write !== undefined) lines.push(`cache_write = ${formatNumber(tier.cache_write)}`);
}
}
lines.push("", "[limit]");
lines.push(`context = ${formatInteger(model.limit.context)}`);
if (model.limit.input !== undefined) lines.push(`input = ${formatInteger(model.limit.input)}`);
lines.push(`output = ${formatInteger(model.limit.output)}`);
lines.push("", "[modalities]");
lines.push(`input = [${model.modalities.input.map(quote).join(", ")}]`);
lines.push(`output = [${model.modalities.output.map(quote).join(", ")}]`);
return `${lines.join("\n")}\n`;
}
export async function main(args = process.argv.slice(2)) {
const target = args.find((arg) => !arg.startsWith("-")) ?? "aggregators";
const results = await syncTargets(target, {
dryRun: args.includes("--dry-run"),
newOnly: args.includes("--new-only"),
});
await writeReport(target, results);
console.log("\nSync summary");
for (const result of results) {
console.log(
`${result.name}: ${result.created} created, ${result.updated} updated, ${result.deleted} deleted`,
);
}
}
if (import.meta.main) await main();
@@ -0,0 +1,176 @@
import { z } from "zod";
import type { ExistingModel, SyncProvider } from "../sync-models.js";
import {
buildOpenRouterModel,
OpenRouterModel,
OpenRouterResponse,
} from "./openrouter.js";
const API_BASE = "https://api.cloudflare.com/client/v4/accounts";
const CloudflareOpenRouterResponse = z.object({
result: z.union([OpenRouterResponse, z.array(OpenRouterModel)]).optional(),
result_info: z.object({
page: z.number().optional(),
total_pages: z.number().optional(),
}).passthrough().optional(),
}).passthrough();
const CloudflareModel = z.object({
id: z.string(),
name: z.string(),
created: z.number(),
hugging_face_id: z.string().nullable().optional(),
context_length: z.number(),
max_output_length: z.number().nullable().optional(),
input_modalities: z.array(z.string()).optional(),
output_modalities: z.array(z.string()).optional(),
pricing: z.object({
prompt: z.string(),
completion: z.string(),
internal_reasoning: z.string().optional(),
input_cache_read: z.string().optional(),
input_cache_write: z.string().optional(),
}),
supported_features: z.array(z.string()).optional(),
supported_sampling_parameters: z.array(z.string()).optional(),
}).passthrough();
const CloudflareResponse = z.object({
data: z.array(CloudflareModel),
}).passthrough();
type CloudflareModel = z.infer<typeof CloudflareModel>;
export const cloudflareWorkersAi = {
id: "cloudflare-workers-ai",
name: "Cloudflare Workers AI",
modelsDir: "providers/cloudflare-workers-ai/models",
async fetchModels() {
const accountID = process.env.CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID;
const token = process.env.CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN;
if (accountID === undefined || token === undefined) {
throw new Error(
"Cloudflare Workers AI sync requires CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID and CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN",
);
}
const first = await fetchPage(accountID, token, 1);
const models = parseCloudflareModels(first);
const pageInfo = CloudflareOpenRouterResponse.safeParse(first).success
? CloudflareOpenRouterResponse.parse(first).result_info
: undefined;
for (let page = 2; page <= (pageInfo?.total_pages ?? 1); page++) {
models.push(...parseCloudflareModels(await fetchPage(accountID, token, page)));
}
return { data: models };
},
parseModels(raw) {
return parseCloudflareModels(raw);
},
translateModel(model, context) {
const normalized = normalizeModel(model);
const id = normalized.id.replace(/^workers-ai\//, "");
return {
id,
model: buildWorkersAiModel(normalized, context.existing(id)),
};
},
} satisfies SyncProvider<CloudflareModel>;
function buildWorkersAiModel(model: z.infer<typeof OpenRouterModel>, existing: ExistingModel | undefined) {
const synced = buildOpenRouterModel(model, existing);
return {
...synced,
name: existing?.name ?? synced.name,
release_date: existing?.release_date ?? synced.release_date,
last_updated: existing?.last_updated ?? synced.last_updated,
limit: {
...synced.limit,
output: existing?.limit?.output ?? synced.limit.output,
},
};
}
async function fetchPage(accountID: string, token: string, page: number) {
const url = new URL(`${API_BASE}/${accountID}/ai/models/search`);
url.searchParams.set("format", "openrouter");
url.searchParams.set("per_page", "1000");
url.searchParams.set("page", String(page));
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
throw new Error(
`Cloudflare Workers AI models request failed: ${response.status} ${response.statusText}${await responseDetails(response)}`,
);
}
return response.json();
}
function parseCloudflareModels(raw: unknown) {
const cloudflare = CloudflareResponse.safeParse(raw);
if (cloudflare.success) return cloudflare.data.data;
const direct = OpenRouterResponse.safeParse(raw);
if (direct.success) return direct.data.data;
const wrapped = CloudflareOpenRouterResponse.parse(raw);
if (wrapped.result === undefined) {
throw new Error("Cloudflare Workers AI response did not include model data");
}
return Array.isArray(wrapped.result) ? wrapped.result : wrapped.result.data;
}
function normalizeModel(model: CloudflareModel) {
if ("architecture" in model && "top_provider" in model && "supported_parameters" in model) {
return OpenRouterModel.parse(model);
}
return OpenRouterModel.parse({
id: model.id.startsWith("@cf/") ? model.id : `@cf/${model.id.replace(/^@cf\//, "")}`,
name: model.name,
created: model.created,
hugging_face_id: model.hugging_face_id ?? null,
knowledge_cutoff: null,
context_length: model.context_length,
architecture: {
input_modalities: model.input_modalities ?? ["text"],
output_modalities: model.output_modalities ?? ["text"],
},
pricing: model.pricing,
top_provider: {
context_length: model.context_length,
max_completion_tokens: model.max_output_length ?? null,
},
supported_parameters: [
...model.supported_sampling_parameters ?? [],
...model.supported_features ?? [],
],
});
}
async function responseDetails(response: Response) {
const text = await response.text();
if (text.length === 0) return "";
try {
const body = z.object({
errors: z.array(z.object({
code: z.union([z.string(), z.number()]).optional(),
message: z.string().optional(),
}).passthrough()).optional(),
}).passthrough().parse(JSON.parse(text));
const details = body.errors
?.map((error) => [error.code, error.message].filter(Boolean).join(": "))
.filter((message) => message.length > 0)
.join("; ");
return details === undefined || details.length === 0 ? "" : ` (${details})`;
} catch {
return "";
}
}
+138
View File
@@ -0,0 +1,138 @@
import { z } from "zod";
import type { ExistingModel, SyncProvider, SyncedModel } from "../sync-models.js";
const API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models";
const GoogleModel = z.object({
name: z.string(),
baseModelId: z.string().optional(),
version: z.string().optional(),
displayName: z.string().optional(),
description: z.string().optional(),
inputTokenLimit: z.number().int().nonnegative(),
outputTokenLimit: z.number().int().nonnegative(),
supportedGenerationMethods: z.array(z.string()).optional(),
temperature: z.number().optional(),
topP: z.number().optional(),
topK: z.number().optional(),
maxTemperature: z.number().optional(),
thinking: z.boolean().optional(),
}).passthrough();
const GoogleResponse = z.object({
models: z.array(GoogleModel).optional(),
nextPageToken: z.string().optional(),
}).passthrough();
type GoogleModel = z.infer<typeof GoogleModel>;
export const google = {
id: "google",
name: "Google",
modelsDir: "providers/google/models",
skipCreates: true,
sourceID(model) {
return model.name.replace(/^models\//, "");
},
skippedNotice(ids) {
if (ids.length === 0) return [];
return [
`${ids.length} Google models returned by the API were not created because the Models API does not provide authoritative modalities, pricing, knowledge cutoff, release date, tool calling, or structured output metadata. Existing models are still updated from API-authoritative fields.`,
`Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`,
];
},
async fetchModels() {
const key = process.env.GOOGLE_API_KEY
?? process.env.GEMINI_API_KEY
?? process.env.GOOGLE_GENERATIVE_AI_API_KEY;
if (key === undefined) {
throw new Error("Google sync requires GOOGLE_API_KEY, GEMINI_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY");
}
const models: GoogleModel[] = [];
let pageToken: string | undefined;
do {
const url = new URL(API_ENDPOINT);
url.searchParams.set("key", key);
url.searchParams.set("pageSize", "1000");
if (pageToken !== undefined) url.searchParams.set("pageToken", pageToken);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Google models request failed: ${response.status} ${response.statusText}`);
}
const page = GoogleResponse.parse(await response.json());
models.push(...page.models ?? []);
pageToken = page.nextPageToken;
} while (pageToken !== undefined);
return { models };
},
parseModels(raw) {
return GoogleResponse.parse(raw).models ?? [];
},
translateModel(model, context) {
const id = model.name.replace(/^models\//, "");
const existing = context.existing(id);
if (existing === undefined) return undefined;
return {
id,
model: buildModel(model, existing),
};
},
} satisfies SyncProvider<GoogleModel>;
function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
const name = existing.name;
const releaseDate = existing.release_date;
const lastUpdated = existing.last_updated;
const attachment = existing.attachment;
const reasoning = existing.reasoning;
const toolCall = existing.tool_call;
const openWeights = existing.open_weights;
const limit = existing.limit;
const modalities = existing.modalities;
if (
name === undefined
|| releaseDate === undefined
|| lastUpdated === undefined
|| attachment === undefined
|| reasoning === undefined
|| toolCall === undefined
|| openWeights === undefined
|| limit === undefined
|| modalities === undefined
) {
throw new Error(`Google model ${model.name} has incomplete local TOML metadata required for sync`);
}
return {
name: model.displayName ?? name,
family: existing.family,
release_date: releaseDate,
last_updated: lastUpdated,
attachment,
reasoning: model.thinking ?? reasoning,
temperature: model.temperature !== undefined || model.maxTemperature !== undefined
? true
: existing.temperature,
tool_call: toolCall,
structured_output: existing.structured_output,
knowledge: existing.knowledge,
open_weights: openWeights,
status: existing.status,
interleaved: existing.interleaved,
cost: existing.cost,
limit: {
input: limit.input,
context: model.inputTokenLimit,
output: model.outputTokenLimit,
},
modalities,
};
}
+144
View File
@@ -0,0 +1,144 @@
import { z } from "zod";
import { ModelFamilyValues } from "../../src/family.js";
import type { ExistingModel, SyncProvider } from "../sync-models.js";
const API_ENDPOINT = "https://openrouter.ai/api/v1/models";
export const OpenRouterModel = z.object({
id: z.string(),
name: z.string(),
created: z.number(),
hugging_face_id: z.string().nullable(),
knowledge_cutoff: z.string().nullable(),
context_length: z.number(),
architecture: z.object({
input_modalities: z.array(z.string()),
output_modalities: z.array(z.string()),
}),
pricing: z.object({
prompt: z.string(),
completion: z.string(),
internal_reasoning: z.string().optional(),
input_cache_read: z.string().optional(),
input_cache_write: z.string().optional(),
}),
top_provider: z.object({
context_length: z.number().nullable(),
max_completion_tokens: z.number().nullable(),
}),
supported_parameters: z.array(z.string()),
});
export const OpenRouterResponse = z.object({
data: z.array(OpenRouterModel),
}).passthrough();
export type OpenRouterModel = z.infer<typeof OpenRouterModel>;
export const openrouter = {
id: "openrouter",
name: "OpenRouter",
modelsDir: "providers/openrouter/models",
async fetchModels() {
const headers = process.env.OPENROUTER_API_KEY
? { Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}` }
: undefined;
const response = await fetch(API_ENDPOINT, { headers });
if (!response.ok) {
throw new Error(`OpenRouter request failed: ${response.status} ${response.statusText}`);
}
return response.json();
},
parseModels(raw) {
return OpenRouterResponse.parse(raw).data;
},
translateModel(model, context) {
return {
id: model.id,
model: buildOpenRouterModel(model, context.existing(model.id)),
};
},
} satisfies SyncProvider<OpenRouterModel>;
function dateFromTimestamp(timestamp: number) {
return new Date(timestamp * 1000).toISOString().slice(0, 10);
}
function price(value: string | undefined) {
if (value === undefined) return undefined;
const number = Number(value);
return Number.isFinite(number) && number >= 0
? Math.round(number * 1_000_000_000_000) / 1_000_000
: undefined;
}
type Modality = "text" | "audio" | "image" | "video" | "pdf";
function modalities(values: string[], fallback: Modality[]): Modality[] {
const allowed = new Set<Modality>(["text", "audio", "image", "video", "pdf"]);
const result = values
.map((value) => value.toLowerCase())
.map((value) => value === "file" ? "pdf" : value)
.filter((value): value is Modality => allowed.has(value as Modality));
return [...new Set(result.length > 0 ? result : fallback)];
}
function inferFamily(model: OpenRouterModel, name: string) {
const target = `${model.id} ${name}`.toLowerCase();
return [...ModelFamilyValues]
.sort((a, b) => b.length - a.length)
.find((family) => {
const value = family.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
if (family === "o") {
return new RegExp(`(^|[^a-z0-9])${value}(?=\\d|$|[^a-z0-9])`).test(target);
}
return new RegExp(`(^|[^a-z0-9])${value}(?=$|[^a-z0-9])`).test(target);
});
}
export function buildOpenRouterModel(model: OpenRouterModel, existing: ExistingModel | undefined) {
const params = new Set(model.supported_parameters);
const name = model.name.replace(/^[^:]+:\s+/, "");
const input = modalities(model.architecture.input_modalities, ["text"]);
const output = modalities(model.architecture.output_modalities, ["text"]);
const prompt = price(model.pricing.prompt);
const completion = price(model.pricing.completion);
const reasoning = params.has("reasoning") || params.has("include_reasoning");
const context = model.top_provider.context_length ?? model.context_length;
const family = inferFamily(model, name);
return {
name,
family: existing?.family === "o" && family !== "o"
? family
: (existing?.family ?? family),
release_date: dateFromTimestamp(model.created),
last_updated: dateFromTimestamp(model.created),
attachment: input.some((value) => value !== "text"),
reasoning,
temperature: params.has("temperature"),
tool_call: params.has("tools") || params.has("tool_choice"),
structured_output: params.has("structured_outputs"),
knowledge: model.knowledge_cutoff?.slice(0, 10) ?? existing?.knowledge,
open_weights: Boolean(model.hugging_face_id),
status: existing?.status,
interleaved: existing?.interleaved,
cost: prompt !== undefined && completion !== undefined
? {
input: prompt,
output: completion,
reasoning: reasoning ? price(model.pricing.internal_reasoning) : undefined,
cache_read: price(model.pricing.input_cache_read),
cache_write: price(model.pricing.input_cache_write),
tiers: existing?.cost?.tiers,
}
: existing?.cost,
limit: {
context,
input: existing?.limit?.input,
output: model.top_provider.max_completion_tokens ?? existing?.limit?.output ?? context,
},
modalities: { input, output },
};
}
+205
View File
@@ -0,0 +1,205 @@
import { z } from "zod";
import type { ExistingModel, SyncProvider, SyncedModel } from "../sync-models.js";
const API_BASE = "https://api.x.ai/v1";
const XAIModel = z.object({
id: z.string(),
canonical_id: z.string().optional(),
created: z.number().int().nonnegative(),
aliases: z.array(z.string()).optional(),
input_modalities: z.array(z.string()).optional(),
output_modalities: z.array(z.string()).optional(),
prompt_text_token_price: z.number().int().nonnegative().optional(),
cached_prompt_text_token_price: z.number().int().nonnegative().optional(),
completion_text_token_price: z.number().int().nonnegative().optional(),
max_prompt_length: z.number().int().nonnegative().optional(),
}).passthrough();
const XAIModelList = z.object({
models: z.array(XAIModel),
}).passthrough();
const XAIResponse = z.object({
models: z.array(XAIModel),
});
const XAIAPIKey = z.object({
acls: z.array(z.string()),
}).passthrough();
type XAIModel = z.infer<typeof XAIModel>;
export const xai = {
id: "xai",
name: "xAI",
modelsDir: "providers/xai/models",
skipCreates: true,
sourceID(model) {
return model.id;
},
skippedNotice(ids) {
if (ids.length === 0) return [];
return [
`${ids.length} xAI models returned by the API were not created because the Models API does not provide enough authoritative metadata for the catalog, especially output token limits and some feature/capability flags. Existing models are still updated from API-authoritative fields.`,
`Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`,
];
},
async fetchModels() {
const key = process.env.XAI_API_KEY;
if (key === undefined) throw new Error("xAI sync requires XAI_API_KEY");
await assertFullModelAccess(key);
const models = await Promise.all([
fetchTypedModels(key, "language-models"),
fetchTypedModels(key, "image-generation-models"),
fetchTypedModels(key, "video-generation-models"),
]);
return { models: models.flat() };
},
parseModels(raw) {
const models = XAIResponse.parse(raw).models;
const seen = new Set<string>();
const expanded: XAIModel[] = [];
for (const model of models) {
if (!seen.has(model.id)) {
seen.add(model.id);
expanded.push(model);
}
}
for (const model of models) {
for (const alias of model.aliases ?? []) {
if (seen.has(alias)) continue;
seen.add(alias);
expanded.push({ ...model, id: alias, canonical_id: model.id });
}
}
return expanded;
},
translateModel(model, context) {
const existing = context.existing(model.id);
if (existing === undefined) return undefined;
return {
id: model.id,
model: buildModel(model, existing),
};
},
} satisfies SyncProvider<XAIModel>;
async function assertFullModelAccess(key: string) {
const response = await fetch(`${API_BASE}/api-key`, {
headers: { Authorization: `Bearer ${key}` },
});
if (!response.ok) {
throw new Error(`xAI API key metadata request failed: ${response.status} ${response.statusText}`);
}
const apiKey = XAIAPIKey.parse(await response.json());
if (!apiKey.acls.includes("api-key:model:*")) {
throw new Error("xAI sync requires XAI_API_KEY to include api-key:model:* so the model list is not ACL-filtered");
}
}
async function fetchTypedModels(key: string, endpoint: string) {
const response = await fetch(`${API_BASE}/${endpoint}`, {
headers: { Authorization: `Bearer ${key}` },
});
if (!response.ok) {
throw new Error(`xAI ${endpoint} request failed: ${response.status} ${response.statusText}`);
}
return XAIModelList.parse(await response.json()).models;
}
function dateFromTimestamp(timestamp: number) {
return new Date(timestamp * 1000).toISOString().slice(0, 10);
}
type Modality = "text" | "audio" | "image" | "video" | "pdf";
function modalities(values: string[] | undefined, fallback: Modality[]) {
const allowed = new Set<Modality>(["text", "audio", "image", "video", "pdf"]);
const result = (values ?? [])
.map((value) => value.toLowerCase())
.filter((value): value is Modality => allowed.has(value as Modality));
return [...new Set(result.length > 0 ? result : fallback)];
}
function tokenPrice(value: number | undefined) {
if (value === undefined) return undefined;
return value / 10_000;
}
function cost(model: XAIModel, existing: ExistingModel) {
const input = tokenPrice(model.prompt_text_token_price);
const output = tokenPrice(model.completion_text_token_price);
if (input === undefined || output === undefined) return existing.cost;
return {
input,
output,
reasoning: existing.cost?.reasoning,
cache_read: tokenPrice(model.cached_prompt_text_token_price),
cache_write: existing.cost?.cache_write,
input_audio: existing.cost?.input_audio,
output_audio: existing.cost?.output_audio,
tiers: existing.cost?.tiers,
};
}
function buildModel(model: XAIModel, existing: ExistingModel): SyncedModel {
const name = existing.name;
const attachment = existing.attachment;
const reasoning = existing.reasoning;
const toolCall = existing.tool_call;
const openWeights = existing.open_weights;
const limit = existing.limit;
const releaseDate = existing.release_date;
const lastUpdated = existing.last_updated;
if (
name === undefined
|| attachment === undefined
|| reasoning === undefined
|| toolCall === undefined
|| openWeights === undefined
|| limit === undefined
|| (model.canonical_id !== undefined && releaseDate === undefined)
|| (model.canonical_id !== undefined && lastUpdated === undefined)
) {
throw new Error(`xAI model ${model.id} has incomplete local TOML metadata required for sync`);
}
const input = modalities(model.input_modalities, existing.modalities?.input ?? ["text"]);
const output = modalities(model.output_modalities, existing.modalities?.output ?? ["text"]);
const created = dateFromTimestamp(model.created);
return {
name,
family: existing.family,
release_date: model.canonical_id === undefined ? created : releaseDate!,
last_updated: model.canonical_id === undefined ? created : lastUpdated!,
attachment: input.some((value) => value !== "text"),
reasoning,
temperature: existing.temperature,
tool_call: toolCall,
structured_output: existing.structured_output,
knowledge: existing.knowledge,
open_weights: openWeights,
status: existing.status,
interleaved: existing.interleaved,
cost: cost(model, existing),
limit: {
input: limit.input,
context: model.max_prompt_length ?? limit.context,
output: limit.output,
},
modalities: { input, output },
};
}
+19
View File
@@ -54,12 +54,17 @@ export const ModelFamilyValues = [
// DeepSeek
"deepseek",
"deepseek-thinking",
"deepseek-flash",
"deepseek-flash-free",
"deepseek-flash-think",
// Microsoft Phi
"phi",
// Moonshot Kimi
"kimi",
"kimi-k2.5",
"kimi-k2.6",
"kimi-free",
"kimi-thinking",
@@ -77,6 +82,7 @@ export const ModelFamilyValues = [
// xAI Grok
"grok",
"grok-build",
"grok-vision",
"grok-beta",
@@ -115,6 +121,9 @@ export const ModelFamilyValues = [
// Hunyuan
"hunyuan",
// Hy
"Hy",
// Yi
"yi",
@@ -202,6 +211,10 @@ export const ModelFamilyValues = [
"mimo",
"mimo-pro",
"mimo-omni",
"mimo-v2-pro",
"mimo-v2-omni",
"mimo-v2.5-pro",
"mimo-v2.5",
"mimo-pro-free",
"mimo-omni-free",
"mimo-flash-free",
@@ -277,9 +290,15 @@ export const ModelFamilyValues = [
// RNJ
"rnj",
// Tecent Hy
"hy3",
"hy3-free",
// Ling & Ring (InclusionAI)
"ling",
"ling-flash-free",
"ring",
"ring-1t-free",
// Kat Coder
"kat-coder",
+53 -5
View File
@@ -2,9 +2,9 @@ import path from "path";
import { mergeDeep } from "remeda";
import { z } from "zod";
import { Provider, Model } from "./schema.js";
import { Provider, Model, AuthoredModel, AuthoredModelShape } from "./schema.js";
const ExtendsModel = Model.sourceType()
const ExtendsModel = AuthoredModelShape
.partial()
.extend({
extends: z
@@ -71,12 +71,12 @@ export async function generate(directory: string) {
});
continue;
}
const model = Model.safeParse(toml);
const model = AuthoredModel.safeParse(toml);
if (!model.success) {
model.error.cause = { modelPath, toml };
throw model.error;
}
provider.data.models[modelID] = model.data;
provider.data.models[modelID] = normalizeModelCost(model.data);
}
result[providerID] = provider.data;
}
@@ -144,7 +144,7 @@ export async function generate(directory: string) {
}
}
const model = Model.safeParse(merged);
const model = Model.safeParse(normalizeCost(merged));
if (!model.success) {
model.error.cause = { modelPath: pendingModel.modelPath, toml: merged };
throw model.error;
@@ -155,3 +155,51 @@ export async function generate(directory: string) {
return result;
}
function normalizeModelCost(model: z.infer<typeof AuthoredModel>): Model {
return normalizeCost(model) as Model;
}
function normalizeCost(model: Record<string, unknown>) {
const cost = model.cost;
if (cost === undefined || cost === null || typeof cost !== "object" || Array.isArray(cost)) {
return model;
}
const tiers = (cost as { tiers?: unknown }).tiers;
if (!Array.isArray(tiers)) {
return model;
}
if (tiers.length !== 1) {
return model;
}
const contextOver200k = tiers.find((tier) => {
if (tier === null || typeof tier !== "object" || Array.isArray(tier)) return false;
const tierConfig = (tier as { tier?: unknown }).tier;
if (tierConfig === null || typeof tierConfig !== "object" || Array.isArray(tierConfig)) return false;
const type = (tierConfig as { type?: unknown }).type;
const size = (tierConfig as { size?: unknown }).size;
// context_over_200k is a legacy compatibility field. It intentionally
// includes higher thresholds; cost.tiers carries the exact threshold.
return (
(type === undefined || type === "context") &&
typeof size === "number" &&
size >= 200_000
);
});
if (contextOver200k === undefined) {
return model;
}
const { tier: _tier, ...legacyCost } = contextOver200k as Record<string, unknown>;
return {
...model,
cost: {
...(cost as Record<string, unknown>),
context_over_200k: legacyCost,
},
};
}
+156 -98
View File
@@ -21,110 +21,164 @@ const JsonValue: z.ZodType<JsonValue> = z.lazy(() =>
]),
);
const Cost = z.object({
input: z.number().min(0, "Input price cannot be negative"),
output: z.number().min(0, "Output price cannot be negative"),
reasoning: z.number().min(0, "Input price cannot be negative").optional(),
cache_read: z
.number()
.min(0, "Cache read price cannot be negative")
const Cost = z
.object({
input: z.number().min(0, "Input price cannot be negative"),
output: z.number().min(0, "Output price cannot be negative"),
reasoning: z
.number()
.min(0, "Reasoning price cannot be negative")
.optional(),
cache_read: z
.number()
.min(0, "Cache read price cannot be negative")
.optional(),
cache_write: z
.number()
.min(0, "Cache write price cannot be negative")
.optional(),
input_audio: z
.number()
.min(0, "Audio input price cannot be negative")
.optional(),
output_audio: z
.number()
.min(0, "Audio output price cannot be negative")
.optional(),
});
const CostTier = Cost.extend({
tier: z
.object({
type: z.literal("context").default("context"),
size: z.number().int().min(0, "Context tier size cannot be negative"),
})
.strict(),
}).strict();
const AuthoredCost = Cost.extend({
context_over_200k: z.never().optional(),
tiers: z.array(CostTier).optional(),
});
const OutputCost = Cost.extend({
context_over_200k: Cost.optional(),
tiers: z.array(CostTier).optional(),
});
const ModelBase = z.object({
id: z.string(),
name: z.string().min(1, "Model name cannot be empty"),
family: ModelFamily.optional(),
attachment: z.boolean(),
reasoning: z.boolean(),
tool_call: z.boolean(),
interleaved: z
.union([
z.literal(true),
z
.object({
field: z.enum(["reasoning_content", "reasoning_details"]),
})
.strict(),
])
.optional(),
cache_write: z
.number()
.min(0, "Cache write price cannot be negative")
structured_output: z.boolean().optional(),
temperature: z.boolean().optional(),
knowledge: z
.string()
.regex(/^\d{4}-\d{2}(-\d{2})?$/, {
message: "Must be in YYYY-MM or YYYY-MM-DD format",
})
.optional(),
input_audio: z
.number()
.min(0, "Audio input price cannot be negative")
release_date: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, {
message: "Must be in YYYY-MM or YYYY-MM-DD format",
}),
last_updated: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, {
message: "Must be in YYYY-MM or YYYY-MM-DD format",
}),
modalities: z.object({
input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
}),
open_weights: z.boolean(),
limit: z.object({
context: z.number().min(0, "Context window must be positive"),
input: z.number().min(0, "Input tokens must be positive").optional(),
output: z.number().min(0, "Output tokens must be positive"),
}),
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
experimental: z
.object({
modes: z
.record(
z.object({
cost: Cost.optional(),
provider: z
.object({
body: z.record(JsonValue).optional(),
headers: z.record(z.string()).optional(),
})
.optional(),
}),
)
.optional(),
})
.optional(),
output_audio: z
.number()
.min(0, "Audio output price cannot be negative")
provider: z
.object({
npm: z.string().optional(),
api: z.string().optional(),
shape: z.enum(["responses", "completions"]).optional(),
body: z.record(JsonValue).optional(),
headers: z.record(z.string()).optional(),
})
.optional(),
});
export const Model = z
function refineModel<T extends z.ZodTypeAny>(schema: T) {
return schema
.refine(
(data) => {
return !(data.reasoning === false && data.cost?.reasoning !== undefined);
},
{
message: "Cannot set cost.reasoning when reasoning is false",
path: ["cost", "reasoning"],
},
)
.refine(
(data) => {
const tiers = data.cost?.tiers;
if (tiers === undefined) return true;
const sizes = tiers.map((tier: { tier: { size: number } }) => tier.tier.size);
return new Set(sizes).size === sizes.length;
},
{
message: "Cost context tiers must not have duplicate sizes",
path: ["cost", "tiers"],
},
);
}
export const ModelShape = z
.object({
id: z.string(),
name: z.string().min(1, "Model name cannot be empty"),
family: ModelFamily.optional(),
attachment: z.boolean(),
reasoning: z.boolean(),
tool_call: z.boolean(),
interleaved: z
.union([
z.literal(true),
z
.object({
field: z.enum(["reasoning_content", "reasoning_details"]),
})
.strict(),
])
.optional(),
structured_output: z.boolean().optional(),
temperature: z.boolean().optional(),
knowledge: z
.string()
.regex(/^\d{4}-\d{2}(-\d{2})?$/, {
message: "Must be in YYYY-MM or YYYY-MM-DD format",
})
.optional(),
release_date: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, {
message: "Must be in YYYY-MM or YYYY-MM-DD format",
}),
last_updated: z.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, {
message: "Must be in YYYY-MM or YYYY-MM-DD format",
}),
modalities: z.object({
input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
}),
open_weights: z.boolean(),
cost: Cost.extend({
context_over_200k: Cost.optional(),
}).optional(),
limit: z.object({
context: z.number().min(0, "Context window must be positive"),
input: z.number().min(0, "Input tokens must be positive").optional(),
output: z.number().min(0, "Output tokens must be positive"),
}),
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
experimental: z
.object({
modes: z
.record(
z.object({
cost: Cost.optional(),
provider: z
.object({
body: z.record(JsonValue).optional(),
headers: z.record(z.string()).optional(),
})
.optional(),
}),
)
.optional(),
})
.optional(),
provider: z
.object({
npm: z.string().optional(),
api: z.string().optional(),
shape: z.enum(["responses", "completions"]).optional(),
body: z.record(JsonValue).optional(),
headers: z.record(z.string()).optional(),
})
.optional(),
...ModelBase.shape,
cost: OutputCost.optional(),
})
.strict()
.refine(
(data) => {
return !(data.reasoning === false && data.cost?.reasoning !== undefined);
},
{
message: "Cannot set cost.reasoning when reasoning is false",
path: ["cost", "reasoning"],
},
);
.strict();
export const AuthoredModelShape = z
.object({
...ModelBase.shape,
cost: AuthoredCost.optional(),
})
.strict();
export const Model = refineModel(ModelShape);
export const AuthoredModel = refineModel(AuthoredModelShape);
export type Model = z.infer<typeof Model>;
@@ -150,6 +204,7 @@ export const Provider = z
const isOpenAIcompatible = data.npm === "@ai-sdk/openai-compatible";
const isOpenrouter = data.npm === "@openrouter/ai-sdk-provider";
const isAnthropic = data.npm === "@ai-sdk/anthropic";
const isKiro = data.npm === "kiro-acp-ai-provider";
const hasApi = data.api !== undefined;
return (
@@ -161,17 +216,20 @@ export const Provider = z
isAnthropic ||
// openai: api optional (always allowed)
isOpenAI ||
// kiro: api optional (always allowed)
isKiro ||
// all others: must NOT have api
(!isOpenAI &&
!isOpenAIcompatible &&
!isOpenrouter &&
!isAnthropic &&
!isKiro &&
!hasApi)
);
},
{
message:
"'api' is required for openai-compatible and openrouter, optional for anthropic and openai, forbidden otherwise",
"'api' is required for openai-compatible and openrouter, optional for anthropic, openai, and kiro, forbidden otherwise",
path: ["api"],
},
);
+1
View File
@@ -6,6 +6,7 @@
"build": "./script/build.ts"
},
"dependencies": {
"@tanstack/virtual-core": "^3.14.0",
"hono": "^4.8.0",
"models.dev": "workspace:*"
},
+32 -8
View File
@@ -41,6 +41,8 @@
html,
body {
height: 100%;
overflow: hidden;
font-family: 'Rubik', sans-serif;
line-height: 1.6;
color: var(--color-text);
@@ -205,12 +207,17 @@ header {
}
}
.table-viewport {
height: calc(100svh - var(--header-height));
margin-top: var(--header-height);
overflow: auto;
}
table {
border-collapse: separate;
border-spacing: 0;
font-size: 0.875rem;
width: 100%;
margin-top: var(--header-height);
}
thead,
@@ -218,7 +225,7 @@ tbody {}
table thead th {
position: sticky;
top: var(--header-height);
top: 0;
border-top: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
font-size: 0.75rem;
@@ -264,6 +271,7 @@ td {
text-align: left;
border-bottom: 1px solid var(--color-border);
white-space: nowrap;
height: 48px;
}
tbody {
@@ -319,21 +327,37 @@ tbody {
gap: 0.375rem;
}
.provider-cell span:first-child {
flex: 0 0 auto;
}
.provider-cell img,
.provider-cell svg {
flex: 0 0 auto;
display: block;
width: 1rem;
height: 1rem;
color: var(--color-text-secondary);
}
.virtual-spacer td {
height: inherit;
padding: 0;
border-bottom: 0;
}
.empty-row td {
padding: 2rem 0.75rem;
color: var(--color-text-tertiary);
}
.empty-row div {
position: sticky;
left: 0;
width: calc(100vw - 1.5rem);
text-align: center;
}
.model-id-cell {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
gap: 0.375rem;
}
@@ -546,4 +570,4 @@ dialog {
}
}
}
}
+295 -94
View File
@@ -1,7 +1,53 @@
import {
Virtualizer,
elementScroll,
observeElementOffset,
observeElementRect,
} from "@tanstack/virtual-core";
import {
type TableRow,
renderRow,
escapeHtml,
booleanText,
knowledgeText,
weightsText,
} from "./shared.js";
declare global {
interface Window {
__TABLE_DATA__: TableRow[];
}
}
interface VirtualizedRow extends TableRow {
key: string;
searchText: string;
sortValues: Array<string | number | undefined>;
}
type SortDirection = "asc" | "desc";
const ESTIMATED_ROW_HEIGHT = 48;
const VIRTUAL_OVERSCAN = 5;
const modal = document.getElementById("modal") as HTMLDialogElement;
const modalClose = document.getElementById("close")!;
const help = document.getElementById("help")!;
const search = document.getElementById("search")! as HTMLInputElement;
const viewport = document.getElementById("table-viewport") as HTMLElement;
const tbody = document.getElementById(
"models-table-body"
) as HTMLTableSectionElement;
const headers = Array.from(document.querySelectorAll("th.sortable"));
const columnCount = document.querySelectorAll("thead th").length;
let isLoaded = false;
let allRows: VirtualizedRow[] = [];
let visibleRows: VirtualizedRow[] = [];
let currentSort: { column: number; direction: SortDirection } = {
column: -1,
direction: "asc",
};
/////////////////////////
// URL State Management
@@ -31,10 +77,7 @@ function getColumnNameForURL(headerEl: Element): string {
}
function getColumnIndexByUrlName(name: string): number {
const headers = document.querySelectorAll("th.sortable");
return Array.from(headers).findIndex(
(header) => getColumnNameForURL(header) === name
);
return headers.findIndex((header) => getColumnNameForURL(header) === name);
}
/////////////////////////
@@ -63,32 +106,181 @@ modal.addEventListener("click", (e) => {
});
////////////////////
// Handle Sorting
// Row Data
////////////////////
let currentSort = { column: -1, direction: "asc" };
function lockColumnWidths() {
const ths = document.querySelectorAll("#models-table thead th");
const widths = Array.from(ths).map((th) => th.getBoundingClientRect().width);
function sortTable(column: number, direction: "asc" | "desc") {
const header = document.querySelectorAll("th.sortable")[column];
const columnType = header.getAttribute("data-type");
if (!columnType) return;
const measurementRow = tbody.querySelector('tr[aria-hidden="true"]');
if (measurementRow) measurementRow.remove();
// update state
currentSort = { column, direction };
updateQueryParams({
sort: getColumnNameForURL(header),
order: direction,
});
const table = document.getElementById("models-table")!;
table.style.tableLayout = "fixed";
// sort rows
const tbody = document.querySelector("table tbody")!;
const rows = Array.from(
tbody.querySelectorAll("tr")
) as HTMLTableRowElement[];
rows.sort((a, b) => {
const aValue = getCellValue(a.cells[column], columnType);
const bValue = getCellValue(b.cells[column], columnType);
const colgroup = document.createElement("colgroup");
for (const width of widths) {
const col = document.createElement("col");
col.style.width = `${width}px`;
colgroup.appendChild(col);
}
table.insertBefore(colgroup, table.firstChild);
}
function prepareRow(row: TableRow): VirtualizedRow {
const sortValues: VirtualizedRow["sortValues"] = [
row.providerName,
row.modelName,
row.family,
row.providerId,
row.modelId,
booleanText(row.toolCall),
booleanText(row.reasoning),
row.input.length,
row.output.length,
row.inputCost,
row.outputCost,
row.reasoningCost,
row.cacheReadCost,
row.cacheWriteCost,
row.audioInputCost,
row.audioOutputCost,
row.contextLimit,
row.inputLimit,
row.outputLimit,
row.structuredOutput === undefined
? undefined
: booleanText(row.structuredOutput),
booleanText(row.temperature),
weightsText(row.openWeights),
row.knowledge ? knowledgeText(row.knowledge) : undefined,
row.releaseDate,
row.lastUpdated,
];
const searchableValues = [
row.providerName,
row.modelName,
row.family ?? "",
row.providerId,
row.modelId,
row.releaseDate,
row.lastUpdated,
];
return {
...row,
key: `${row.providerId}/${row.modelId}`,
searchText: searchableValues.join(" ").toLowerCase(),
sortValues,
};
}
////////////////////
// Virtual Table
////////////////////
function getVirtualizerOptions(count: number) {
return {
count,
getScrollElement: () => viewport,
estimateSize: () => ESTIMATED_ROW_HEIGHT,
getItemKey: (index: number) => visibleRows[index]?.key ?? index,
initialRect: {
width: viewport.clientWidth || window.innerWidth,
height: viewport.clientHeight || window.innerHeight,
},
overscan: VIRTUAL_OVERSCAN,
observeElementRect,
observeElementOffset,
scrollToFn: elementScroll,
onChange: () => renderVirtualRows(),
};
}
const virtualizer = new Virtualizer<HTMLElement, HTMLTableRowElement>(
getVirtualizerOptions(0)
);
const cleanupVirtualizer = virtualizer._didMount();
virtualizer._willUpdate();
window.addEventListener("pagehide", () => cleanupVirtualizer());
function renderStatusRow(message: string) {
tbody.innerHTML = `<tr class="empty-row"><td colspan="${columnCount}"><div>${escapeHtml(
message
)}</div></td></tr>`;
}
function setVirtualizerCount(count: number, resetScroll: boolean) {
virtualizer.setOptions(getVirtualizerOptions(count));
virtualizer._willUpdate();
if (resetScroll) virtualizer.scrollToOffset(0);
renderVirtualRows();
}
function renderVirtualRows() {
if (!isLoaded) return;
if (visibleRows.length === 0) {
renderStatusRow("No models found");
return;
}
const virtualRows = virtualizer.getVirtualItems();
if (virtualRows.length === 0) return;
const firstRow = virtualRows[0]!;
const lastRow = virtualRows[virtualRows.length - 1]!;
const paddingTop = firstRow.start;
const paddingBottom = Math.max(virtualizer.getTotalSize() - lastRow.end, 0);
const html: string[] = [];
if (paddingTop > 0) html.push(renderSpacerRow(paddingTop));
for (const virtualRow of virtualRows) {
const row = visibleRows[virtualRow.index];
if (row) html.push(renderRow(row, virtualRow.index));
}
if (paddingBottom > 0) html.push(renderSpacerRow(paddingBottom));
tbody.innerHTML = html.join("");
tbody.querySelectorAll<HTMLTableRowElement>("tr[data-index]").forEach((row) =>
virtualizer.measureElement(row)
);
}
function renderSpacerRow(height: number) {
return `<tr class="virtual-spacer" style="height: ${height}px"><td colspan="${columnCount}"></td></tr>`;
}
function applyRows(resetScroll = true) {
if (!isLoaded) return;
visibleRows = getRowsForDisplay();
setVirtualizerCount(visibleRows.length, resetScroll);
}
////////////////////
// Sorting
////////////////////
function getRowsForDisplay() {
const terms = search.value
.toLowerCase()
.split(",")
.map((term) => term.trim())
.filter(Boolean);
const filteredRows =
terms.length === 0
? allRows
: allRows.filter((row) =>
terms.some((term) => row.searchText.includes(term))
);
if (currentSort.column === -1) return filteredRows;
const columnType = headers[currentSort.column]?.getAttribute("data-type");
if (!columnType) return filteredRows;
return [...filteredRows].sort((a, b) => {
const aValue = a.sortValues[currentSort.column];
const bValue = b.sortValues[currentSort.column];
// Handle undefined values - always sort to bottom
if (aValue === undefined && bValue === undefined) return 0;
if (aValue === undefined) return 1;
if (bValue === undefined) return -1;
@@ -96,45 +288,48 @@ function sortTable(column: number, direction: "asc" | "desc") {
let comparison = 0;
if (columnType === "number" || columnType === "modalities") {
comparison = (aValue as number) - (bValue as number);
} else if (columnType === "boolean") {
comparison = (aValue as string).localeCompare(bValue as string);
} else {
comparison = (aValue as string).localeCompare(bValue as string);
comparison = String(aValue).localeCompare(String(bValue));
}
return direction === "asc" ? comparison : -comparison;
return currentSort.direction === "asc" ? comparison : -comparison;
});
rows.forEach((row) => tbody.appendChild(row));
}
// update sort indicators
const headers = document.querySelectorAll("th.sortable");
function sortTable(
column: number,
direction: SortDirection,
updateURL = true
) {
const header = headers[column];
if (!header?.getAttribute("data-type")) return;
currentSort = { column, direction };
if (updateURL) {
updateQueryParams({
sort: getColumnNameForURL(header),
order: direction,
});
}
updateSortIndicators();
applyRows();
}
function updateSortIndicators() {
headers.forEach((header, i) => {
const indicator = header.querySelector(".sort-indicator")!;
if (i === column) {
indicator.textContent = direction === "asc" ? "↑" : "↓";
} else {
indicator.textContent = "";
}
indicator.textContent =
i === currentSort.column
? currentSort.direction === "asc"
? "↑"
: ""
: "";
});
}
function getCellValue(
cell: HTMLTableCellElement,
type: string
): string | number | undefined {
if (type === "modalities")
return cell.querySelectorAll(".modality-icon").length;
const text = cell.textContent?.trim() || "";
if (text === "-") return;
if (type === "number") return parseFloat(text.replace(/[$,]/g, "")) || 0;
return text;
}
document.querySelectorAll("th.sortable").forEach((header) => {
headers.forEach((header, column) => {
header.addEventListener("click", () => {
const column = Array.from(header.parentElement!.children).indexOf(header);
const direction =
currentSort.column === column && currentSort.direction === "asc"
? "desc"
@@ -144,34 +339,19 @@ document.querySelectorAll("th.sortable").forEach((header) => {
});
///////////////////
// Handle Search
// Search
///////////////////
function filterTable(value: string) {
const lowerCaseValues = value.toLowerCase().split(",").filter(str => str.trim() !== "");
const rows = document.querySelectorAll(
"table tbody tr"
) as NodeListOf<HTMLTableRowElement>;
rows.forEach((row) => {
const cellTexts = Array.from(row.cells).map((cell) =>
cell.textContent!.toLowerCase()
);
const isVisible = lowerCaseValues.length === 0 ||
lowerCaseValues.some((lowerCaseValue) => cellTexts.some((text) => text.includes(lowerCaseValue)));
row.style.display = isVisible ? "" : "none";
});
updateQueryParams({ search: value || null });
}
search.addEventListener("input", () => {
filterTable(search.value);
updateQueryParams({ search: search.value || null });
applyRows();
});
document.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
const key = e.key.toLowerCase();
if ((e.metaKey || e.ctrlKey) && (key === "k" || key === "f")) {
e.preventDefault();
search.focus();
search.select();
}
});
@@ -185,10 +365,7 @@ search.addEventListener("keydown", (e) => {
///////////////////////////////////
// Handle Copy model ID function
///////////////////////////////////
(window as any).copyModelId = async (
button: HTMLButtonElement,
modelId: string
) => {
async function copyModelId(button: HTMLButtonElement, modelId: string) {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(modelId);
@@ -209,7 +386,18 @@ search.addEventListener("keydown", (e) => {
} catch (err) {
console.error("Failed to copy text: ", err);
}
};
}
document.addEventListener("click", (event) => {
if (!(event.target instanceof Element)) return;
const button = event.target.closest<HTMLButtonElement>(
".copy-button[data-model-id]"
);
if (!button) return;
const modelId = button.dataset.modelId;
if (modelId) void copyModelId(button, modelId);
});
///////////////////////////////////
// Initialize State from URL
@@ -217,24 +405,37 @@ search.addEventListener("keydown", (e) => {
function initializeFromURL() {
const params = getQueryParams();
(() => {
const searchQuery = params.get("search");
if (!searchQuery) return;
search.value = searchQuery;
filterTable(searchQuery);
})();
(() => {
const columnName = params.get("sort");
if (!columnName) return;
search.value = params.get("search") ?? "";
currentSort = { column: -1, direction: "asc" };
const columnName = params.get("sort");
if (columnName) {
const columnIndex = getColumnIndexByUrlName(columnName);
if (columnIndex === -1) return;
if (columnIndex !== -1) {
currentSort = {
column: columnIndex,
direction: params.get("order") === "desc" ? "desc" : "asc",
};
}
}
const direction = (params.get("order") as "asc" | "desc") || "asc";
sortTable(columnIndex, direction);
})();
updateSortIndicators();
applyRows(false);
}
document.addEventListener("DOMContentLoaded", initializeFromURL);
function loadRows() {
try {
allRows = window.__TABLE_DATA__.map(prepareRow);
lockColumnWidths();
isLoaded = true;
initializeFromURL();
} catch (error) {
console.error(error);
isLoaded = true;
visibleRows = [];
renderStatusRow("Failed to load model data");
}
}
loadRows();
window.addEventListener("popstate", initializeFromURL);
+55 -232
View File
@@ -6,6 +6,7 @@ import { Fragment } from "hono/jsx";
import { renderToString } from "hono/jsx/dom/server";
import { existsSync } from "fs";
import path from "path";
import { type TableRow, renderRow, getLargestRow } from "./shared.js";
export const Providers = await generate(
path.join(import.meta.dir, "..", "..", "..", "providers")
@@ -38,7 +39,6 @@ const loadProviderSvg = async (providerId: string): Promise<string | null> => {
const file = Bun.file(providerLogoPath);
return await file.text();
}
//
// Fall back to default logo
if (existsSync(defaultLogoPath)) {
const file = Bun.file(defaultLogoPath);
@@ -62,122 +62,47 @@ for (const [providerId] of Object.entries(Providers)) {
}
}
function renderProviderLogo(providerId: string) {
const svgContent = providerLogos.get(providerId) || "";
export const INITIAL_ROW_COUNT = 50;
return <span dangerouslySetInnerHTML={{ __html: svgContent }} />;
}
export const TableRows: TableRow[] = Object.entries(Providers)
.sort(([, providerA], [, providerB]) =>
providerA.name.localeCompare(providerB.name)
)
.flatMap(([providerId, provider]) =>
Object.entries(provider.models)
.filter(([, model]) => model.status !== "alpha")
.sort(([, modelA], [, modelB]) => modelA.name.localeCompare(modelB.name))
.map(([modelId, model]) => ({
providerId,
providerName: provider.name,
providerLogoSvg: providerLogos.get(providerId) || "",
modelId,
modelName: model.name,
family: model.family,
toolCall: model.tool_call,
reasoning: model.reasoning,
input: model.modalities.input,
output: model.modalities.output,
inputCost: model.cost?.input,
outputCost: model.cost?.output,
reasoningCost: model.cost?.reasoning,
cacheReadCost: model.cost?.cache_read,
cacheWriteCost: model.cost?.cache_write,
audioInputCost: model.cost?.input_audio,
audioOutputCost: model.cost?.output_audio,
contextLimit: model.limit.context,
inputLimit: model.limit.input,
outputLimit: model.limit.output,
structuredOutput: model.structured_output,
temperature: model.temperature ?? false,
openWeights: model.open_weights,
knowledge: model.knowledge,
releaseDate: model.release_date,
lastUpdated: model.last_updated,
}))
);
const getModalityIcon = (modality: string) => {
switch (modality) {
case "text":
return (
<span class="modality-icon" data-tooltip="Text">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="4,7 4,4 20,4 20,7"></polyline>
<line x1="9" y1="20" x2="15" y2="20"></line>
<line x1="12" y1="4" x2="12" y2="20"></line>
</svg>
</span>
);
case "image":
return (
<span class="modality-icon" data-tooltip="Image">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect>
<circle cx="9" cy="9" r="2"></circle>
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path>
</svg>
</span>
);
case "audio":
return (
<span class="modality-icon" data-tooltip="Audio">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
<path d="m19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
</svg>
</span>
);
case "video":
return (
<span class="modality-icon" data-tooltip="Video">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m22 8-6 4 6 4V8Z"></path>
<rect width="14" height="12" x="2" y="6" rx="2" ry="2"></rect>
</svg>
</span>
);
case "pdf":
return (
<span class="modality-icon" data-tooltip="PDF">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14,2 14,8 20,8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10,9 9,9 8,9"></polyline>
</svg>
</span>
);
default:
return null;
}
};
const renderCost = (cost?: number) => {
return cost === undefined ? "-" : `$${cost.toFixed(2)}`;
};
const largestRow = getLargestRow(TableRows);
export const Rendered = renderToString(
<Fragment>
@@ -213,7 +138,8 @@ export const Rendered = renderToString(
<button id="help">How to use</button>
</div>
</header>
<table>
<div id="table-viewport" class="table-viewport">
<table id="models-table">
<thead>
<tr>
<th class="sortable" data-type="text">
@@ -342,120 +268,12 @@ export const Rendered = renderToString(
</th>
</tr>
</thead>
<tbody>
{Object.entries(Providers)
.sort(([, providerA], [, providerB]) =>
providerA.name.localeCompare(providerB.name)
)
.flatMap(([providerId, provider]) =>
Object.entries(provider.models)
.filter(([, model]) => model.status !== "alpha")
.sort(([, modelA], [, modelB]) =>
modelA.name.localeCompare(modelB.name)
)
.map(([modelId, model]) => (
<tr key={`${providerId}-${modelId}`}>
<td>
<div class="provider-cell">
{renderProviderLogo(providerId)}
<span>{provider.name}</span>
</div>
</td>
<td>{model.name}</td>
<td>{model.family ?? "-"}</td>
<td>{providerId}</td>
<td>
<div class="model-id-cell">
<span class="model-id-text">{modelId}</span>
<button
class="copy-button"
onclick={`copyModelId(this, '${modelId}')`}
>
<svg
class="copy-icon"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect
width="14"
height="14"
x="8"
y="8"
rx="2"
ry="2"
/>
<path d="m4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
</svg>
<svg
class="check-icon"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="display: none;"
>
<polyline points="20,6 9,17 4,12" />
</svg>
</button>
</div>
</td>
<td>{model.tool_call ? "Yes" : "No"}</td>
<td>{model.reasoning ? "Yes" : "No"}</td>
<td>
<div class="modalities">
{model.modalities.input.map((modality) =>
getModalityIcon(modality)
)}
</div>
</td>
<td>
<div class="modalities">
{model.modalities.output.map((modality) =>
getModalityIcon(modality)
)}
</div>
</td>
<td>{renderCost(model.cost?.input)}</td>
<td>{renderCost(model.cost?.output)}</td>
<td>{renderCost(model.cost?.reasoning)}</td>
<td>{renderCost(model.cost?.cache_read)}</td>
<td>{renderCost(model.cost?.cache_write)}</td>
<td>{renderCost(model.cost?.input_audio)}</td>
<td>{renderCost(model.cost?.output_audio)}</td>
<td>{model.limit.context.toLocaleString()}</td>
<td>{model.limit.input?.toLocaleString() ?? "-"}</td>
<td>{model.limit.output.toLocaleString()}</td>
<td>
{model.structured_output === undefined
? "-"
: model.structured_output
? "Yes"
: "No"}
</td>
<td>{model.temperature ? "Yes" : "No"}</td>
<td>{model.open_weights ? "Open" : "Closed"}</td>
<td>
{model.knowledge ? model.knowledge.substring(0, 7) : "-"}
</td>
<td>{model.release_date}</td>
<td>{model.last_updated}</td>
</tr>
))
)}
</tbody>
</table>
<tbody id="models-table-body" dangerouslySetInnerHTML={{
__html: TableRows.slice(0, INITIAL_ROW_COUNT).map((row, i) => renderRow(row, i)).join('')
+ renderRow(largestRow, -1).replace('<tr', '<tr style="visibility:hidden" aria-hidden="true"')
}} />
</table>
</div>
<dialog id="modal">
<div class="header">
<h2>How to use</h2>
@@ -565,10 +383,15 @@ export const Rendered = renderToString(
>
Edit on GitHub
</a>
<a href="https://sst.dev" target="_blank" rel="noopener noreferrer">
Created by SST
<a href="https://opencode.ai" target="_blank" rel="noopener noreferrer">
Created by OpenCode
</a>
</div>
</dialog>
<script
dangerouslySetInnerHTML={{
__html: `window.__TABLE_DATA__ = ${JSON.stringify(TableRows)}`,
}}
></script>
</Fragment>
);
+176
View File
@@ -0,0 +1,176 @@
export interface TableRow {
providerId: string;
providerName: string;
providerLogoSvg: string;
modelId: string;
modelName: string;
family?: string;
toolCall: boolean;
reasoning: boolean;
input: string[];
output: string[];
inputCost?: number;
outputCost?: number;
reasoningCost?: number;
cacheReadCost?: number;
cacheWriteCost?: number;
audioInputCost?: number;
audioOutputCost?: number;
contextLimit: number;
inputLimit?: number;
outputLimit: number;
structuredOutput?: boolean;
temperature: boolean;
openWeights: boolean;
knowledge?: string;
releaseDate: string;
lastUpdated: string;
}
const MODALITY_ICONS: Record<string, string> = {
text: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4,7 4,4 20,4 20,7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg>`,
image: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><circle cx="9" cy="9" r="2"></circle><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"></path></svg>`,
audio: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="m19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>`,
video: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 8-6 4 6 4V8Z"></path><rect width="14" height="12" x="2" y="6" rx="2" ry="2"></rect></svg>`,
pdf: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14,2 14,8 20,8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10,9 9,9 8,9"></polyline></svg>`,
};
export function escapeHtml(value: string | number) {
return String(value).replace(/[&<>'"]/g, (char) => {
switch (char) {
case "&":
return "&amp;";
case "<":
return "&lt;";
case ">":
return "&gt;";
case "'":
return "&#39;";
case '"':
return "&quot;";
default:
return char;
}
});
}
export function booleanText(value: boolean) {
return value ? "Yes" : "No";
}
export function optionalBooleanText(value?: boolean) {
return value === undefined ? "-" : booleanText(value);
}
export function formatCost(cost?: number) {
return cost === undefined ? "-" : `$${cost.toFixed(2)}`;
}
export function formatNumber(value?: number) {
return value === undefined ? "-" : value.toLocaleString();
}
export function knowledgeText(value?: string) {
return value ? value.substring(0, 7) : "-";
}
export function weightsText(value: boolean) {
return value ? "Open" : "Closed";
}
export function renderModalityIcon(modality: string) {
const label =
modality === "pdf"
? "PDF"
: modality[0]!.toUpperCase() + modality.slice(1);
const icon = MODALITY_ICONS[modality];
if (!icon) return "";
return `<span class="modality-icon" data-tooltip="${label}">${icon}</span>`;
}
export function renderModalities(modalities: string[]) {
return `<div class="modalities">${modalities
.map(renderModalityIcon)
.join("")}</div>`;
}
export function renderCopyButton(modelId: string) {
const escapedModelId = escapeHtml(modelId);
return `<button type="button" class="copy-button" data-model-id="${escapedModelId}" aria-label="Copy model ID"><svg class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="m4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg><svg class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: none;"><polyline points="20,6 9,17 4,12"></polyline></svg></button>`;
}
export function renderRow(row: TableRow, index: number) {
return `<tr data-index="${index}">
<td><div class="provider-cell">${row.providerLogoSvg}<span>${escapeHtml(
row.providerName
)}</span></div></td>
<td>${escapeHtml(row.modelName)}</td>
<td>${escapeHtml(row.family ?? "-")}</td>
<td>${escapeHtml(row.providerId)}</td>
<td><div class="model-id-cell"><span class="model-id-text">${escapeHtml(
row.modelId
)}</span>${renderCopyButton(row.modelId)}</div></td>
<td>${booleanText(row.toolCall)}</td>
<td>${booleanText(row.reasoning)}</td>
<td>${renderModalities(row.input)}</td>
<td>${renderModalities(row.output)}</td>
<td>${formatCost(row.inputCost)}</td>
<td>${formatCost(row.outputCost)}</td>
<td>${formatCost(row.reasoningCost)}</td>
<td>${formatCost(row.cacheReadCost)}</td>
<td>${formatCost(row.cacheWriteCost)}</td>
<td>${formatCost(row.audioInputCost)}</td>
<td>${formatCost(row.audioOutputCost)}</td>
<td>${formatNumber(row.contextLimit)}</td>
<td>${formatNumber(row.inputLimit)}</td>
<td>${formatNumber(row.outputLimit)}</td>
<td>${optionalBooleanText(row.structuredOutput)}</td>
<td>${booleanText(row.temperature)}</td>
<td>${weightsText(row.openWeights)}</td>
<td>${knowledgeText(row.knowledge)}</td>
<td>${escapeHtml(row.releaseDate)}</td>
<td>${escapeHtml(row.lastUpdated)}</td>
</tr>`;
}
export function getLargestRow(rows: TableRow[]): TableRow {
const worst: TableRow = {
providerId: "", providerName: "", providerLogoSvg: "", modelId: "", modelName: "",
toolCall: true, reasoning: true,
input: [], output: [],
contextLimit: 0, outputLimit: 0,
structuredOutput: true, temperature: true, openWeights: false,
releaseDate: "", lastUpdated: "",
};
for (const row of rows) {
if (row.providerName.length > worst.providerName.length) worst.providerName = row.providerName;
if (row.modelName.length > worst.modelName.length) worst.modelName = row.modelName;
if ((row.family ?? "").length > (worst.family ?? "").length) worst.family = row.family;
if (row.providerId.length > worst.providerId.length) worst.providerId = row.providerId;
if (row.modelId.length > worst.modelId.length) worst.modelId = row.modelId;
if ((row.knowledge ?? "").length > (worst.knowledge ?? "").length) worst.knowledge = row.knowledge;
if (row.releaseDate.length > worst.releaseDate.length) worst.releaseDate = row.releaseDate;
if (row.lastUpdated.length > worst.lastUpdated.length) worst.lastUpdated = row.lastUpdated;
if (row.input.length > worst.input.length) worst.input = row.input;
if (row.output.length > worst.output.length) worst.output = row.output;
const costWider = (a: number | undefined, b: number | undefined) =>
b !== undefined && (a === undefined || formatCost(b).length > formatCost(a).length);
if (costWider(worst.inputCost, row.inputCost)) worst.inputCost = row.inputCost;
if (costWider(worst.outputCost, row.outputCost)) worst.outputCost = row.outputCost;
if (costWider(worst.reasoningCost, row.reasoningCost)) worst.reasoningCost = row.reasoningCost;
if (costWider(worst.cacheReadCost, row.cacheReadCost)) worst.cacheReadCost = row.cacheReadCost;
if (costWider(worst.cacheWriteCost, row.cacheWriteCost)) worst.cacheWriteCost = row.cacheWriteCost;
if (costWider(worst.audioInputCost, row.audioInputCost)) worst.audioInputCost = row.audioInputCost;
if (costWider(worst.audioOutputCost, row.audioOutputCost)) worst.audioOutputCost = row.audioOutputCost;
const numWider = (a: number | undefined, b: number | undefined) =>
b !== undefined && (a === undefined || formatNumber(b).length > formatNumber(a).length);
if (numWider(worst.contextLimit as number | undefined, row.contextLimit)) worst.contextLimit = row.contextLimit;
if (numWider(worst.inputLimit, row.inputLimit)) worst.inputLimit = row.inputLimit;
if (numWider(worst.outputLimit as number | undefined, row.outputLimit)) worst.outputLimit = row.outputLimit;
}
return worst;
}
@@ -1,6 +1,6 @@
name = "xAI: Grok 3"
release_date = "2025-02-17"
last_updated = "2025-02-17"
name = "MiniMax-M2.7-highspeed"
release_date = "2026-03-19"
last_updated = "2026-03-19"
attachment = false
reasoning = false
temperature = true
@@ -8,13 +8,12 @@ tool_call = true
open_weights = false
[cost]
input = 3.0
output = 15.0
cache_read = 0.75
input = 0.600
output = 4.800
[limit]
context = 131072
output = 26215
context = 204_800
output = 131_072
[modalities]
input = ["text"]
@@ -1,6 +1,6 @@
name = "Qwen: Qwen-Max "
release_date = "2024-04-03"
last_updated = "2026-03-15"
name = "MiniMax-M2.7"
release_date = "2026-03-19"
last_updated = "2026-03-19"
attachment = false
reasoning = false
temperature = true
@@ -8,13 +8,12 @@ tool_call = true
open_weights = false
[cost]
input = 1.04
output = 4.16
cache_read = 0.32
input = 0.300
output = 1.200
[limit]
context = 32768
output = 8192
context = 204_800
output = 131_072
[modalities]
input = ["text"]
@@ -1,19 +1,17 @@
name = "Claude Sonnet 3.5 v2"
family = "claude-sonnet"
name = "claude-3-5-haiku-20241022"
family = "claude-haiku"
release_date = "2024-10-22"
last_updated = "2024-10-22"
attachment = true
reasoning = false
temperature = true
knowledge = "2024-04"
tool_call = true
open_weights = false
knowledge = "2024-07-31"
[cost]
input = 3.00
output = 15.00
cache_read = 0.30
cache_write = 3.75
input = 0.800
output = 4.000
[limit]
context = 200_000
@@ -1,21 +1,21 @@
name = "Claude Haiku 3"
name = "claude-3-5-haiku-latest"
family = "claude-haiku"
release_date = "2024-03-13"
last_updated = "2024-03-13"
release_date = "2024-10-22"
last_updated = "2024-10-22"
attachment = true
reasoning = false
temperature = true
knowledge = "2024-02"
tool_call = true
open_weights = false
knowledge = "2024-07-31"
[cost]
input = 0.25
output = 1.25
input = 0.800
output = 4.000
[limit]
context = 200_000
output = 4_096
output = 8_192
[modalities]
input = ["text", "image", "pdf"]
@@ -1,12 +1,13 @@
name = "claude-haiku-4-5-20251001"
family = "claude-haiku"
release_date = "2025-10-16"
last_updated = "2025-10-16"
attachment = true
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03"
knowledge = "2025-02-28"
[cost]
input = 1.000
@@ -17,5 +18,5 @@ context = 200_000
output = 64_000
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,22 @@
name = "claude-haiku-4-5"
family = "claude-haiku"
release_date = "2025-10-16"
last_updated = "2025-10-16"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-02-28"
[cost]
input = 1.000
output = 5.000
[limit]
context = 200_000
output = 64_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -1,12 +1,13 @@
name = "claude-opus-4-1-20250805"
family = "claude-opus"
release_date = "2025-08-05"
last_updated = "2025-08-05"
attachment = true
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03"
knowledge = "2025-03-31"
[cost]
input = 15.000
@@ -17,5 +18,5 @@ context = 200_000
output = 32_000
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -1,19 +1,17 @@
name = "Claude Opus 4 (US)"
name = "claude-opus-4-20250514"
family = "claude-opus"
release_date = "2025-05-22"
last_updated = "2025-05-22"
attachment = true
reasoning = true
temperature = true
knowledge = "2024-04"
tool_call = true
open_weights = false
knowledge = "2025-03-31"
[cost]
input = 15.00
output = 75.00
cache_read = 1.50
cache_write = 18.75
input = 15.000
output = 75.000
[limit]
context = 200_000
@@ -1,12 +1,13 @@
name = "claude-opus-4-5-20251101"
family = "claude-opus"
release_date = "2025-11-25"
last_updated = "2025-11-25"
attachment = true
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03"
knowledge = "2025-03-31"
[cost]
input = 5.000
@@ -17,5 +18,5 @@ context = 200_000
output = 64_000
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,22 @@
name = "claude-opus-4-5"
family = "claude-opus"
release_date = "2025-11-25"
last_updated = "2025-11-25"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03-31"
[cost]
input = 5.000
output = 25.000
[limit]
context = 200_000
output = 64_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,21 @@
name = "claude-opus-4-6-thinking"
release_date = "2026-02-06"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-05"
[cost]
input = 5.000
output = 25.000
[limit]
context = 1_000_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,22 @@
name = "claude-opus-4-6"
family = "claude-opus"
release_date = "2026-02-06"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-05-31"
[cost]
input = 5.000
output = 25.000
[limit]
context = 1_000_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,31 @@
name = "claude-opus-4-7"
family = "claude-opus"
release_date = "2026-04-17"
last_updated = "2026-04-17"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2026-01-31"
[cost]
input = 5.000
output = 25.000
cache_read = 0.500
cache_write = 6.250
[[cost.tiers]]
tier = { size = 200_000 }
input = 10.000
output = 37.500
cache_read = 1.000
cache_write = 12.500
[limit]
context = 1_000_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -1,19 +1,17 @@
name = "Claude Sonnet 4"
name = "claude-sonnet-4-20250514"
family = "claude-sonnet"
release_date = "2025-05-22"
last_updated = "2025-05-22"
attachment = true
reasoning = true
temperature = true
knowledge = "2024-04"
tool_call = true
open_weights = false
knowledge = "2025-03-31"
[cost]
input = 3.00
output = 15.00
cache_read = 0.30
cache_write = 3.75
input = 3.000
output = 15.000
[limit]
context = 200_000
@@ -17,5 +17,5 @@ context = 200_000
output = 64_000
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -1,12 +1,13 @@
name = "claude-sonnet-4-5-20250929"
release_date = "2025-09-29"
last_updated = "2025-09-29"
family = "claude-sonnet"
release_date = "2025-09-30"
last_updated = "2025-09-30"
attachment = true
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03"
knowledge = "2025-07-31"
[cost]
input = 3.000
@@ -17,5 +18,5 @@ context = 200_000
output = 64_000
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,22 @@
name = "claude-sonnet-4-5"
family = "claude-sonnet"
release_date = "2025-09-30"
last_updated = "2025-09-30"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-07-31"
[cost]
input = 3.000
output = 15.000
[limit]
context = 200_000
output = 64_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,21 @@
name = "claude-sonnet-4-6-thinking"
release_date = "2026-02-18"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-08"
[cost]
input = 3.000
output = 15.000
[limit]
context = 1_000_000
output = 64_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,22 @@
name = "claude-sonnet-4-6"
family = "claude-sonnet"
release_date = "2026-02-18"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 3.000
output = 15.000
[limit]
context = 1_000_000
output = 64_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,21 @@
name = "gemini-3.1-flash-image-preview"
release_date = "2026-02-27"
last_updated = "2026-02-27"
attachment = true
reasoning = false
temperature = true
tool_call = false
open_weights = false
knowledge = "2025-01"
[cost]
input = 0.500
output = 60.000
[limit]
context = 131_072
output = 32_768
[modalities]
input = ["text", "image", "pdf"]
output = ["text", "image"]
@@ -1,19 +1,17 @@
name = "GLM-4.5"
family = "glm"
release_date = "2025-07-28"
last_updated = "2025-07-28"
name = "glm-4.5-air"
family = "glm-air"
release_date = "2025-07-29"
last_updated = "2025-07-29"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
knowledge = "2025-04"
[cost]
input = 0
output = 0
cache_read = 0
cache_write = 0
input = 0.1143
output = 0.286
[limit]
context = 131_072
@@ -1,21 +1,21 @@
name = "Qwen3 Coder 480B A35B Instruct"
family = "qwen"
release_date = "2025-07-23"
last_updated = "2025-10-04"
name = "glm-4.5-airx"
family = "glm"
release_date = "2025-07-29"
last_updated = "2025-07-29"
attachment = false
reasoning = false
temperature = true
knowledge = "2025-04"
tool_call = true
open_weights = false
knowledge = "2025-04"
[cost]
input = 0.40
output = 1.80
input = 0.572
output = 1.714
[limit]
context = 262144
output = 66536
context = 128_000
output = 16_384
[modalities]
input = ["text"]
+22
View File
@@ -0,0 +1,22 @@
name = "glm-4.5-x"
family = "glm"
release_date = "2025-07-29"
last_updated = "2025-07-29"
attachment = false
reasoning = false
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-04"
[cost]
input = 1.143
output = 2.290
[limit]
context = 128_000
output = 16_384
[modalities]
input = ["text"]
output = ["text"]
+5 -4
View File
@@ -1,19 +1,20 @@
name = "GLM-4.5"
family = "glm"
release_date = "2025-07-29"
last_updated = "2025-07-29"
attachment = false
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2024-10"
open_weights = true
knowledge = "2025-04"
[cost]
input = 0.286
output = 1.142
[limit]
context = 128_000
context = 131_072
output = 98_304
[modalities]
+7 -6
View File
@@ -1,12 +1,13 @@
name = "GLM-4.5V"
release_date = "2025-07-29"
last_updated = "2025-07-29"
family = "glm"
release_date = "2025-08-12"
last_updated = "2025-08-12"
attachment = true
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2024-10"
open_weights = true
knowledge = "2025-04"
[cost]
input = 0.290
@@ -17,5 +18,5 @@ context = 64_000
output = 16_384
[modalities]
input = ["text", "image"]
input = ["text", "image", "video"]
output = ["text"]
+5 -4
View File
@@ -1,19 +1,20 @@
name = "glm-4.6"
family = "glm"
release_date = "2025-09-30"
last_updated = "2025-09-30"
attachment = false
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03"
open_weights = true
knowledge = "2025-04"
[cost]
input = 0.286
output = 1.142
[limit]
context = 200_000
context = 204_800
output = 131_072
[modalities]
+5 -4
View File
@@ -1,12 +1,13 @@
name = "GLM-4.6V"
family = "glm"
release_date = "2025-12-08"
last_updated = "2025-12-08"
attachment = true
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-03"
open_weights = true
knowledge = "2025-04"
[cost]
input = 0.145
@@ -17,5 +18,5 @@ context = 128_000
output = 32_768
[modalities]
input = ["text", "image"]
input = ["text", "image", "video"]
output = ["text"]
@@ -1,19 +1,17 @@
name = "GLM-4.7-FlashX"
name = "glm-4.7-flashx"
family = "glm-flash"
release_date = "2026-01-19"
last_updated = "2026-01-19"
release_date = "2026-01-20"
last_updated = "2026-01-20"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
knowledge = "2025-04"
[cost]
input = 0.07
output = 0.40
cache_read = 0.01
cache_write = 0
input = 0.0715
output = 0.429
[limit]
context = 200_000
+8 -4
View File
@@ -1,19 +1,23 @@
name = "glm-4.7"
family = "glm"
release_date = "2025-12-22"
last_updated = "2025-12-22"
attachment = false
reasoning = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
knowledge = "2025-06"
open_weights = true
knowledge = "2025-04"
[interleaved]
field = "reasoning_content"
[cost]
input = 0.286
output = 1.142
[limit]
context = 200_000
context = 204_800
output = 131_072
[modalities]
+25
View File
@@ -0,0 +1,25 @@
name = "glm-5-turbo"
family = "glm"
release_date = "2026-03-16"
last_updated = "2026-03-16"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = false
[interleaved]
field = "reasoning_content"
[cost]
input = 0.720
output = 3.200
[limit]
context = 200_000
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
+25
View File
@@ -0,0 +1,25 @@
name = "glm-5.1"
family = "glm"
release_date = "2026-04-10"
last_updated = "2026-04-10"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = false
[interleaved]
field = "reasoning_content"
[cost]
input = 0.860
output = 3.500
[limit]
context = 200_000
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
@@ -1,4 +1,4 @@
name = "GLM5"
name = "glm-5"
family = "glm"
release_date = "2026-02-12"
last_updated = "2026-02-12"
@@ -6,19 +6,18 @@ attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.0
output = 0.0
input = 0.600
output = 2.600
[limit]
context = 202752
output = 131000
context = 204_800
output = 131_072
[modalities]
input = ["text"]
+24
View File
@@ -0,0 +1,24 @@
name = "GLM-5V-Turbo"
family = "glm"
release_date = "2026-04-02"
last_updated = "2026-04-02"
attachment = true
reasoning = true
temperature = true
tool_call = true
open_weights = false
[interleaved]
field = "reasoning_content"
[cost]
input = 0.720
output = 3.200
[limit]
context = 200_000
output = 131_072
[modalities]
input = ["text", "image", "video", "audio", "pdf"]
output = ["text"]
@@ -1,4 +1,4 @@
name = "GLM 4.6 (exacto)"
name = "glm-for-coding"
family = "glm"
release_date = "2025-09-30"
last_updated = "2025-09-30"
@@ -6,18 +6,15 @@ attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-09"
open_weights = true
open_weights = false
[cost]
input = 0.60
output = 1.90
cache_read = 0.11
input = 0.086
output = 0.343
[limit]
context = 200_000
output = 128_000
output = 131_072
[modalities]
input = ["text"]
+3 -2
View File
@@ -6,6 +6,7 @@ attachment = true
reasoning = false
temperature = true
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-04"
@@ -14,9 +15,9 @@ input = 0.400
output = 1.600
[limit]
context = 1_000_000
context = 1_047_576
output = 32_768
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
+2 -1
View File
@@ -6,6 +6,7 @@ attachment = true
reasoning = false
temperature = true
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-04"
@@ -14,7 +15,7 @@ input = 0.100
output = 0.400
[limit]
context = 1_000_000
context = 1_047_576
output = 32_768
[modalities]
+3 -2
View File
@@ -6,6 +6,7 @@ attachment = true
reasoning = false
temperature = true
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-04"
@@ -14,9 +15,9 @@ input = 2.000
output = 8.000
[limit]
context = 1_000_000
context = 1_047_576
output = 32_768
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
+2 -1
View File
@@ -6,6 +6,7 @@ attachment = true
reasoning = false
temperature = true
tool_call = true
structured_output = true
open_weights = false
knowledge = "2023-09"
@@ -18,5 +19,5 @@ context = 128_000
output = 16_384
[modalities]
input = ["text", "image"]
input = ["text", "image", "pdf"]
output = ["text"]
+6 -3
View File
@@ -1,12 +1,14 @@
name = "gpt-5-mini"
family = "gpt-mini"
release_date = "2025-08-08"
last_updated = "2025-08-08"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2024-05-30"
[cost]
input = 0.250
@@ -14,6 +16,7 @@ output = 2.000
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
+6 -3
View File
@@ -1,12 +1,14 @@
name = "gpt-5-pro"
family = "gpt-pro"
release_date = "2025-10-08"
last_updated = "2025-10-08"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2024-09-30"
[cost]
input = 15.000
@@ -14,6 +16,7 @@ output = 120.000
[limit]
context = 400_000
input = 272_000
output = 272_000
[modalities]
@@ -1,12 +1,14 @@
name = "gpt-5.1-chat-latest"
family = "gpt-codex"
release_date = "2025-11-14"
last_updated = "2025-11-14"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2024-09-30"
[cost]
input = 1.250
+6 -3
View File
@@ -1,12 +1,14 @@
name = "gpt-5.1"
family = "gpt"
release_date = "2025-11-14"
last_updated = "2025-11-14"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2024-09-30"
[cost]
input = 1.250
@@ -14,6 +16,7 @@ output = 10.000
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
@@ -1,12 +1,14 @@
name = "gpt-5.2-chat-latest"
family = "gpt-codex"
release_date = "2025-12-12"
last_updated = "2025-12-12"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2025-08-31"
[cost]
input = 1.750
+6 -3
View File
@@ -1,12 +1,14 @@
name = "gpt-5.2"
family = "gpt"
release_date = "2025-12-12"
last_updated = "2025-12-12"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2025-08-31"
[cost]
input = 1.750
@@ -14,6 +16,7 @@ output = 14.000
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
@@ -0,0 +1,24 @@
name = "gpt-5.4-mini-2026-03-17"
family = "gpt-mini"
release_date = "2026-03-19"
last_updated = "2026-03-19"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 0.750
output = 4.500
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
input = ["text", "image"]
output = ["text"]
+24
View File
@@ -0,0 +1,24 @@
name = "gpt-5.4-mini"
family = "gpt-mini"
release_date = "2026-03-19"
last_updated = "2026-03-19"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 0.750
output = 4.500
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
input = ["text", "image"]
output = ["text"]
@@ -0,0 +1,24 @@
name = "gpt-5.4-nano-2026-03-17"
family = "gpt-nano"
release_date = "2026-03-19"
last_updated = "2026-03-19"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 0.200
output = 1.250
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
input = ["text", "image"]
output = ["text"]
+24
View File
@@ -0,0 +1,24 @@
name = "gpt-5.4-nano"
family = "gpt-nano"
release_date = "2026-03-19"
last_updated = "2026-03-19"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 0.200
output = 1.250
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
input = ["text", "image"]
output = ["text"]
+31
View File
@@ -0,0 +1,31 @@
name = "gpt-5.4-pro"
family = "gpt-pro"
release_date = "2026-03-05"
last_updated = "2026-03-05"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = false
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 30.000
output = 180.000
cache_read = 0
cache_write = 0
[[cost.tiers]]
tier = { size = 272_000 }
input = 60.000
output = 270.000
[limit]
context = 1_050_000
input = 922_000
output = 128_000
[modalities]
input = ["text", "image"]
output = ["text"]
+31
View File
@@ -0,0 +1,31 @@
name = "gpt-5.4"
family = "gpt"
release_date = "2026-03-05"
last_updated = "2026-03-05"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2025-08-31"
[cost]
input = 2.500
output = 15.000
cache_read = 0.250
cache_write = 0
[[cost.tiers]]
tier = { size = 272_000 }
input = 5.000
output = 22.500
[limit]
context = 1_050_000
input = 922_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
+6 -3
View File
@@ -1,12 +1,14 @@
name = "gpt-5"
family = "gpt"
release_date = "2025-08-08"
last_updated = "2025-08-08"
attachment = true
reasoning = false
temperature = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
open_weights = false
knowledge = "2024-10"
knowledge = "2024-09-30"
[cost]
input = 1.250
@@ -14,6 +16,7 @@ output = 10.000
[limit]
context = 400_000
input = 272_000
output = 128_000
[modalities]
@@ -1,18 +1,15 @@
name = "Grok 4 Fast (Non-Reasoning)"
family = "grok"
release_date = "2025-09-19"
last_updated = "2025-09-19"
name = "grok-4.20-beta-0309-non-reasoning"
release_date = "2026-03-16"
last_updated = "2026-03-16"
attachment = true
reasoning = false
temperature = true
knowledge = "2025-07"
tool_call = true
open_weights = false
[cost]
input = 0.20
output = 0.50
cache_read = 0.05
input = 2.000
output = 6.000
[limit]
context = 2_000_000
@@ -1,18 +1,15 @@
name = "Grok 4 Fast"
family = "grok"
release_date = "2025-09-19"
last_updated = "2025-09-19"
name = "grok-4.20-beta-0309-reasoning"
release_date = "2026-03-16"
last_updated = "2026-03-16"
attachment = true
reasoning = true
temperature = true
knowledge = "2025-07"
tool_call = true
open_weights = false
[cost]
input = 0.20
output = 0.50
cache_read = 0.05
input = 2.000
output = 6.000
[limit]
context = 2_000_000
@@ -1,18 +1,15 @@
name = "Grok 4.1 Fast"
family = "grok"
release_date = "2025-11-19"
last_updated = "2025-11-19"
name = "grok-4.20-multi-agent-beta-0309"
release_date = "2026-03-16"
last_updated = "2026-03-16"
attachment = true
reasoning = true
temperature = true
knowledge = "2025-07"
tool_call = true
open_weights = false
[cost]
input = 0.20
output = 0.50
cache_read = 0.05
input = 2.000
output = 6.000
[limit]
context = 2_000_000
@@ -20,4 +17,4 @@ output = 30_000
[modalities]
input = ["text", "image"]
output = ["text"]
output = ["text"]
+6
View File
@@ -0,0 +1,6 @@
<svg viewBox="0 0 220 32" xmlns="http://www.w3.org/2000/svg">
<text x="0" y="24" fill="currentColor">
<tspan font-size="24">Abliteration</tspan>
<tspan dx="8" dy="2" font-size="12" fill-opacity="0.6">.ai</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 239 B

@@ -0,0 +1,22 @@
name = "Abliterated Model"
release_date = "2026-01-06"
last_updated = "2026-01-06"
attachment = true
reasoning = false
tool_call = true
structured_output = false
temperature = true
open_weights = true
[cost]
input = 3.00
output = 3.00
[limit]
context = 150_000
input = 150_000
output = 8_192
[modalities]
input = ["text", "image"]
output = ["text"]
+5
View File
@@ -0,0 +1,5 @@
name = "abliteration.ai"
env = ["ABLIT_KEY"]
npm = "@ai-sdk/openai-compatible"
api = "https://api.abliteration.ai/v1"
doc = "https://docs.abliteration.ai/models"
@@ -1,22 +0,0 @@
name = "Kimi K2 0905"
family = "kimi"
release_date = "2025-09-05"
last_updated = "2025-09-05"
attachment = false
reasoning = false
temperature = true
tool_call = true
knowledge = "2024-10"
open_weights = true
[cost]
input = 0.55
output = 2.19
[limit]
context = 262_144
output = 262_144
[modalities]
input = ["text"]
output = ["text"]
@@ -0,0 +1,27 @@
name = "DeepSeek V4 Flash (Alibaba Cloud)"
family = "deepseek-flash"
release_date = "2026-04-24"
last_updated = "2026-04-24"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-05"
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.14
output = 0.28
cache_read = 0.028
[limit]
context = 1_000_000
output = 384_000
[modalities]
input = ["text"]
output = ["text"]
@@ -0,0 +1,27 @@
name = "DeepSeek V4 Pro (Alibaba Cloud)"
family = "deepseek-thinking"
release_date = "2026-04-24"
last_updated = "2026-04-24"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-05"
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 1.69
output = 3.38
cache_read = 0.13
[limit]
context = 1_000_000
output = 384_000
[modalities]
input = ["text"]
output = ["text"]
@@ -1,7 +1,7 @@
name = "GLM 4.6 TEE"
name = "GLM-5.1 (Alibaba Cloud)"
family = "glm"
release_date = "2025-12-29"
last_updated = "2026-01-10"
release_date = "2026-03-27"
last_updated = "2026-03-27"
attachment = false
reasoning = true
temperature = true
@@ -9,18 +9,19 @@ tool_call = true
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.40
output = 1.70
cache_read = 0.20
input = 0.84
output = 3.38
cache_read = 0.169
cache_write = 1.05625
[limit]
context = 202_752
output = 65_536
context = 200_000
output = 128_000
[modalities]
input = ["text"]
output = ["text"]
[interleaved]
field = "reasoning_content"
@@ -1,28 +1,33 @@
name = "Claude Opus 4.6 Think"
name = "Claude Opus 4.6 Thinking"
family = "claude-opus"
release_date = "2026-02-05"
last_updated = "2026-02-05"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-05-31"
open_weights = false
[cost]
input = 5.00
output = 25.00
cache_read = 0.30
cache_write = 3.75
[interleaved]
field = "reasoning_content"
[cost.context_over_200k]
input = 6.00
output = 22.00
cache_read = 0.60
cache_write = 7.50
[cost]
input = 5
output = 25
cache_read = 0.5
cache_write = 6.25
[[cost.tiers]]
tier = { size = 200_000 }
input = 10
output = 37.5
cache_read = 1.0
cache_write = 12.5
[limit]
context = 200_000
context = 1_000_000
output = 128_000
[modalities]
+16 -12
View File
@@ -1,28 +1,32 @@
name = "Claude Opus 4.6"
family = "claude-opus"
release_date = "2026-02-05"
last_updated = "2026-02-05"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-05-31"
open_weights = false
[cost]
input = 5.00
output = 25.00
cache_read = 0.30
cache_write = 3.75
interleaved = true
[cost.context_over_200k]
input = 6.00
output = 22.00
cache_read = 0.60
cache_write = 7.50
[cost]
input = 5
output = 25
cache_read = 0.5
cache_write = 6.25
[[cost.tiers]]
tier = { size = 200_000 }
input = 10
output = 37.5
cache_read = 1.0
cache_write = 12.5
[limit]
context = 200_000
context = 1_000_000
output = 128_000
[modalities]
@@ -0,0 +1,35 @@
name = "Claude Opus 4.7 Thinking"
family = "claude-opus"
release_date = "2026-04-16"
last_updated = "2026-04-16"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
knowledge = "2026-01-31"
open_weights = false
[interleaved]
field = "reasoning_content"
[cost]
input = 5
output = 25
cache_read = 0.5
cache_write = 6.25
[[cost.tiers]]
tier = { size = 200_000 }
input = 10
output = 37.5
cache_read = 1.0
cache_write = 12.5
[limit]
context = 1_000_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -0,0 +1,34 @@
name = "Claude Opus 4.7"
family = "claude-opus"
release_date = "2026-04-16"
last_updated = "2026-04-16"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
knowledge = "2026-01-31"
open_weights = false
interleaved = true
[cost]
input = 5
output = 25
cache_read = 0.5
cache_write = 6.25
[[cost.tiers]]
tier = { size = 200_000 }
input = 10
output = 37.5
cache_read = 1.0
cache_write = 12.5
[limit]
context = 1_000_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
@@ -1,28 +1,33 @@
name = "Claude Sonnet 4.6 Think"
name = "Claude Sonnet 4.6 Thinking"
family = "claude-sonnet"
release_date = "2026-02-17"
last_updated = "2026-02-17"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-08-31"
open_weights = false
[interleaved]
field = "reasoning_content"
[cost]
input = 3.00
output = 15.00
cache_read = 0.30
cache_write = 3.75
[cost.context_over_200k]
[[cost.tiers]]
tier = { size = 200_000 }
input = 6.00
output = 22.50
cache_read = 0.60
cache_write = 7.50
[limit]
context = 200_000
context = 1_000_000
output = 64_000
[modalities]
@@ -1,28 +1,32 @@
name = "Claude Sonnet 4.6"
family = "claude-sonnet"
release_date = "2026-02-17"
last_updated = "2026-02-17"
last_updated = "2026-03-13"
attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-08-31"
open_weights = false
interleaved = true
[cost]
input = 3.00
output = 15.00
cache_read = 0.30
cache_write = 3.75
[cost.context_over_200k]
[[cost.tiers]]
tier = { size = 200_000 }
input = 6.00
output = 22.50
cache_read = 0.60
cache_write = 7.50
[limit]
context = 200_000
context = 1_000_000
output = 64_000
[modalities]
@@ -1,28 +0,0 @@
name = "Coding GLM 4.7 Free"
family = "glm"
release_date = "2025-12-22"
last_updated = "2025-12-22"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[interleaved]
field = "reasoning_details"
[cost]
input = 0
output = 0
cache_read = 0
cache_write = 0
[limit]
context = 204800
output = 131072
[modalities]
input = ["text"]
output = ["text"]
@@ -1,27 +0,0 @@
name = "Coding-GLM-4.7"
family = "glm"
release_date = "2025-12-22"
last_updated = "2025-12-22"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[interleaved]
field = "reasoning_details"
[cost]
input = 0.06
output = 0.22
cache_read = 0.01
[limit]
context = 204800
output = 131072
[modalities]
input = ["text"]
output = ["text"]
@@ -1,23 +1,24 @@
name = "GLM-5.1"
family = "glm"
name = "Coding GLM 5.1 (free)"
family = "glm-free"
release_date = "2026-04-11"
last_updated = "2026-04-11"
attachment = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.84
output = 3.38
input = 0
output = 0
[limit]
context = 200000
output = 128000
context = 200_000
output = 128_000
[modalities]
input = ["text"]
@@ -1,4 +1,4 @@
name = "Coding-GLM-5.1"
name = "Coding GLM 5.1"
family = "glm"
release_date = "2026-04-11"
last_updated = "2026-04-11"
@@ -6,7 +6,8 @@ attachment = false
reasoning = true
temperature = true
tool_call = true
open_weights = false
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
@@ -14,10 +15,11 @@ field = "reasoning_content"
[cost]
input = 0.06
output = 0.22
cache_read = 0.013
[limit]
context = 200000
output = 128000
context = 200_000
output = 128_000
[modalities]
input = ["text"]
@@ -1,7 +1,7 @@
name = "Hermes 4 405B FP8 TEE"
family = "nousresearch"
release_date = "2025-12-29"
last_updated = "2026-01-10"
name = "Coding MiniMax M2.7 (Free)"
family = "minimax-free"
release_date = "2026-03-18"
last_updated = "2026-03-18"
attachment = false
reasoning = true
temperature = true
@@ -9,17 +9,17 @@ tool_call = true
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.30
output = 1.20
input = 0
output = 0
[limit]
context = 131_072
output = 65_536
context = 204_800
output = 128_100
[modalities]
input = ["text"]
output = ["text"]
[interleaved]
field = "reasoning_content"
@@ -0,0 +1,25 @@
name = "Coding MiniMax M2.7 Highspeed"
family = "minimax"
release_date = "2026-03-18"
last_updated = "2026-03-18"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.2
output = 0.2
[limit]
context = 204_800
output = 128_100
[modalities]
input = ["text"]
output = ["text"]
@@ -1,7 +1,7 @@
name = "MiniMax M2.1 TEE"
name = "Coding MiniMax M2.7"
family = "minimax"
release_date = "2025-12-29"
last_updated = "2026-01-27"
release_date = "2026-03-18"
last_updated = "2026-03-18"
attachment = false
reasoning = true
temperature = true
@@ -9,17 +9,17 @@ tool_call = true
structured_output = true
open_weights = true
[interleaved]
field = "reasoning_content"
[cost]
input = 0.27
output = 1.12
input = 0.2
output = 0.2
[limit]
context = 196_608
output = 65_536
context = 204_800
output = 128_100
[modalities]
input = ["text"]
output = ["text"]
[interleaved]
field = "reasoning_content"
@@ -0,0 +1,17 @@
name = "Coding Xiaomi MiMo-V2.5-Pro"
family = "mimo-v2.5-pro"
last_updated = "2026-05-13"
[extends]
from = "xiaomi/mimo-v2.5-pro"
[cost]
input = 0.20
output = 0.60
cache_read = 0.04
[[cost.tiers]]
tier = { size = 256_000 }
input = 0.40
output = 1.20
cache_read = 0.08
@@ -0,0 +1,17 @@
name = "Coding Xiaomi MiMo-V2.5"
family = "mimo-v2.5"
last_updated = "2026-05-13"
[extends]
from = "xiaomi/mimo-v2.5"
[cost]
input = 0.08
output = 0.40
cache_read = 0.016
[[cost.tiers]]
tier = { size = 256_000 }
input = 0.16
output = 0.80
cache_read = 0.032

Some files were not shown because too many files have changed in this diff Show More