Compare commits

...

495 Commits

Author SHA1 Message Date
Aiden Cline 4d9a365f36 [opencode/alibaba] Add reasoning options 2026-06-13 16:00:45 -05:00
Aiden Cline 1ddcd9bc32 Merge pull request #2286 from anomalyco/fix/remove-glm-5.2-standard-apis
fix: remove GLM-5.2 from standard Z.AI APIs
2026-06-13 15:41:30 -05:00
Aiden Cline 7a9b4a167c Merge pull request #2206 from anomalyco/feat/berget-reasoning-options-wave3
[berget] Add reasoning options
2026-06-13 15:40:33 -05:00
Aiden Cline 33d88e41df Merge pull request #2203 from anomalyco/feat/the-grid-ai-reasoning-options-wave3
[the-grid-ai] Add reasoning options
2026-06-13 15:40:11 -05:00
Aiden Cline 300a46cfdc Merge pull request #2201 from anomalyco/feat/neuralwatt-reasoning-options-wave3
[neuralwatt] Add reasoning options
2026-06-13 15:36:53 -05:00
Aiden Cline 0e3c00595b [neuralwatt] Correct reasoning controls 2026-06-13 15:35:31 -05:00
Aiden Cline 1c9b748976 fix: remove GLM-5.2 from standard Z.AI APIs 2026-06-13 15:33:40 -05:00
Aiden Cline cf4ccf92ae Merge pull request #2285 from niushuai1991/add-glm-5.2-config
Add GLM-5.2 model config to zai and zhipuai providers
2026-06-13 15:31:44 -05:00
Aiden Cline a4a9d29c99 [berget] Correct remaining reasoning efforts 2026-06-13 15:30:02 -05:00
Aiden Cline 4670f99aac Merge pull request #2283 from anomalyco/fix/venice-sync-last-updated
fix(venice): preserve model update dates
2026-06-13 15:28:40 -05:00
Aiden Cline 3839c610a6 [berget] Remove unsupported GLM efforts 2026-06-13 15:28:06 -05:00
Aiden Cline b1095231a5 Merge pull request #2215 from anomalyco/feat/dinference-reasoning-options-wave3
[dinference] Mark reasoning controls fixed
2026-06-13 15:27:56 -05:00
Aiden Cline e8333b63a1 Merge pull request #2202 from anomalyco/feat/regolo-ai-reasoning-options-wave3
[regolo-ai] Add reasoning options
2026-06-13 15:27:32 -05:00
Aiden Cline 704ffc7371 Merge pull request #2229 from anomalyco/feat/anyapi-reasoning-options-wave4
[anyapi] Add reasoning options
2026-06-13 15:26:41 -05:00
Aiden Cline 4fdda4f262 [anyapi] Correct Claude reasoning options 2026-06-13 15:20:11 -05:00
Aiden Cline 41c243713f [berget] Correct Kimi reasoning option 2026-06-13 15:16:12 -05:00
Aiden Cline 36024f1bd2 Merge pull request #2218 from anomalyco/feat/gmicloud-reasoning-options-wave3
[gmicloud] Add reasoning options
2026-06-13 15:14:50 -05:00
Aiden Cline bfb0d3722a [gmicloud] Correct Claude reasoning options 2026-06-13 15:12:38 -05:00
Niu Shuai 3877dcf8b6 feat: add GLM-5.2 model config to zai and zhipuai providers 2026-06-14 04:10:00 +08:00
Aiden Cline d171755a90 Merge pull request #2220 from anomalyco/feat/azure-reasoning-options-wave3
[azure] Backfill DeepSeek V4 reasoning options
2026-06-13 14:39:42 -05:00
Aiden Cline 5394af4d63 Merge pull request #2189 from anomalyco/feat/upstage-reasoning-options-wave3
[upstage] Add reasoning options
2026-06-13 14:39:01 -05:00
Aiden Cline b78f6fb53e Merge pull request #2191 from anomalyco/feat/tencent-coding-plan-reasoning-options-wave3
[tencent-coding-plan] Add reasoning options
2026-06-13 14:36:46 -05:00
Aiden Cline 9e3c7d41ff Merge pull request #2200 from anomalyco/feat/modelscope-reasoning-options-wave3
[modelscope] Add reasoning options
2026-06-13 14:36:35 -05:00
Aiden Cline e8b1862dc2 Merge pull request #2195 from anomalyco/feat/minimax-cn-coding-plan-reasoning-options-wave3
[minimax-cn-coding-plan] Complete reasoning options
2026-06-13 14:36:20 -05:00
Aiden Cline 63da7583b5 Merge pull request #2188 from anomalyco/feat/moonshotai-reasoning-options-wave3
[moonshotai] Complete reasoning options
2026-06-13 14:36:03 -05:00
Aiden Cline e5b1221baa fix(venice): preserve model update dates 2026-06-13 14:31:04 -05:00
Aiden Cline 43e502a0bc Merge pull request #2282 from anomalyco/fix/mimo-reasoning-options-audit
fix MiMo reasoning options across providers
2026-06-13 14:16:58 -05:00
Aiden Cline b19a423f31 fix MiMo reasoning options across providers 2026-06-13 14:13:33 -05:00
Aiden Cline 77e04a5f1a Merge pull request #2184 from anomalyco/feat/nova-reasoning-options-wave3
[nova] Add reasoning options
2026-06-13 13:32:23 -05:00
Aiden Cline 2e1a8245c6 Merge pull request #2181 from anomalyco/feat/cloudferro-sherlock-reasoning-options-wave3
[cloudferro-sherlock] Add reasoning options
2026-06-13 13:32:14 -05:00
Aiden Cline b197346558 Merge pull request #2183 from anomalyco/feat/drun-reasoning-options-wave3
[drun] Add reasoning options
2026-06-13 13:32:03 -05:00
Aiden Cline a04024bd21 Merge pull request #2185 from anomalyco/feat/moark-reasoning-options-wave3
[moark] Add reasoning options
2026-06-13 13:31:53 -05:00
Aiden Cline 0186f9e638 Merge pull request #2187 from anomalyco/feat/poolside-reasoning-options-wave3
[poolside] Add reasoning options
2026-06-13 13:31:35 -05:00
Aiden Cline deb664d9c6 Merge pull request #2186 from anomalyco/feat/lucidquery-reasoning-options-wave3
[lucidquery] Add reasoning options
2026-06-13 13:31:26 -05:00
Aiden Cline 8ade756d9b Merge pull request #2182 from anomalyco/feat/inception-reasoning-options-wave3
[inception] Add reasoning options
2026-06-13 13:31:14 -05:00
Aiden Cline fc109cc2c2 Merge pull request #2221 from anomalyco/feat/xiaomi-token-plan-cn-reasoning-options-wave3
[xiaomi-token-plan] Add reasoning toggles
2026-06-13 13:30:47 -05:00
Aiden Cline 0427b955c6 Merge pull request #2222 from anomalyco/feat/ambient-reasoning-options-wave3
[ambient] Add reasoning options
2026-06-13 13:23:26 -05:00
Aiden Cline e1c887294a Merge pull request #2225 from anomalyco/feat/abacus-reasoning-options-wave4
[abacus] Add reasoning options
2026-06-13 13:23:10 -05:00
Aiden Cline 67cbb6e5f4 Merge pull request #2226 from anomalyco/feat/cloudflare-ai-gateway-reasoning-options-wave4
[cloudflare-ai-gateway] Add reasoning options
2026-06-13 13:18:42 -05:00
Aiden Cline 3f183e2962 Merge pull request #2227 from anomalyco/feat/chutes-reasoning-options-wave4
[chutes] Add reasoning options
2026-06-13 13:18:23 -05:00
Aiden Cline b2ada02538 Merge pull request #2238 from anomalyco/feat/digitalocean-reasoning-options-wave4
[digitalocean] Add reasoning options
2026-06-13 13:16:48 -05:00
Aiden Cline 6dcd5d65a0 Merge pull request #2244 from anomalyco/feat/huggingface-reasoning-options-wave4
[huggingface] Add reasoning options
2026-06-13 13:14:23 -05:00
Aiden Cline 9f2265f81f Merge pull request #2281 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-13 13:13:02 -05:00
Aiden Cline b0e4f07b73 Merge pull request #2224 from anomalyco/feat/auriko-reasoning-options-wave4
[auriko] Add reasoning options
2026-06-13 13:12:39 -05:00
Aiden Cline 503b07ddfa Merge pull request #2219 from anomalyco/feat/mistral-reasoning-options-wave3
[mistral] Mark Magistral reasoning controls fixed
2026-06-13 13:11:11 -05:00
Aiden Cline d13507ee94 Merge pull request #2217 from anomalyco/feat/lilac-reasoning-options-wave3
[lilac] Add reasoning options
2026-06-13 13:10:58 -05:00
github-actions[bot] 1f391a1908 chore(sync): update OpenRouter model catalog 2026-06-13 17:46:55 +00:00
Aiden Cline f31519bf52 Merge pull request #2280 from CodeAnimal/az-deepseek-v4
Correct Azure DeepSeek V4 model prices
2026-06-13 11:25:26 -05:00
Aiden Cline d56e8387ba Merge pull request #2274 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-13 11:25:15 -05:00
CodeAnimal 0132340a2c Correct Azure DeepSeek V4 model prices 2026-06-13 17:23:18 +01:00
Aiden Cline ead650e6b0 Merge pull request #2277 from ririnto/feat/zai-coding-plan-glm-5-2-anthropic
Add ZAI Coding Plan GLM-5.2 with Anthropic provider model
2026-06-13 11:19:37 -05:00
Aiden Cline c3beda0405 fix(zai-coding-plan): use default GLM-5.2 provider 2026-06-13 11:17:47 -05:00
Aiden Cline 814770c1c7 Merge dev and reuse Kimi K2.7 metadata 2026-06-13 11:09:49 -05:00
Aiden Cline e3d49ae6e2 Merge pull request #2279 from mathiasloh/feat_add_ollama_cloud_kimi_k27_code
feat(ollama-cloud): add kimi-k2.7-code
2026-06-13 11:07:44 -05:00
Aiden Cline fdabca87a0 Merge pull request #2275 from jubalm/add-zai-glm-5-2
Add ZAI Coding Plan GLM-5.2
2026-06-13 11:07:27 -05:00
github-actions[bot] acd9fa80ad chore(sync): update OpenRouter model catalog 2026-06-13 15:49:42 +00:00
mathias.loh 00c5b75ed9 feat(ollama-cloud): add kimi-k2.7-code 2026-06-13 22:42:54 +08:00
ririnto d210e45dd5 refactor(zai-coding-plan): split GLM-5.2 metadata into model + base_model reference
Move provider-agnostic facts (name, family, dates, capability flags,
limit, modalities) into models/zhipuai/glm-5.2.toml and reference it
via base_model in the provider TOML, which now keeps only provider-
specific fields (reasoning_options, interleaved, cost, per-model
anthropic [provider] override). Per README wrapper-provider guidance.
2026-06-13 22:39:29 +09:00
ririnto c9e026b5de feat(zai-coding-plan): add GLM-5.2 model metadata 2026-06-13 18:04:46 +09:00
Jubal Mabaquiao 3be537dad4 Add ZAI Coding Plan GLM-5.2 2026-06-13 16:45:57 +08:00
Aiden Cline 63a7bc11a9 Merge pull request #2216 from anomalyco/feat/inceptron-reasoning-options-wave3
[inceptron] Add reasoning options
2026-06-13 00:30:24 -05:00
Aiden Cline 0d2db5359d Merge pull request #2258 from anomalyco/feat/alibaba-coding-plan-cn-reasoning-options-final
[alibaba-coding-plan-cn] Add reasoning options
2026-06-13 00:27:45 -05:00
Aiden Cline a2d9f79f7e Merge pull request #2255 from anomalyco/feat/alibaba-coding-plan-reasoning-options-final
[alibaba-coding-plan] Add reasoning options
2026-06-13 00:27:30 -05:00
Aiden Cline e4d366cf4f Merge pull request #2256 from anomalyco/feat/evroc-reasoning-options-final
[evroc] Add reasoning options
2026-06-13 00:27:15 -05:00
Aiden Cline 9631b93843 Merge pull request #2260 from anomalyco/feat/alibaba-token-plan-cn-reasoning-options-final
feat(alibaba-token-plan-cn): add reasoning options
2026-06-13 00:27:01 -05:00
Aiden Cline 5424fbd2de Merge pull request #2270 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-13 00:26:42 -05:00
Aiden Cline 73cbf85a43 Merge pull request #2272 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-13 00:26:27 -05:00
Aiden Cline 097afb7e31 Merge pull request #2273 from zainhas/dev
[Together AI] add minimax m3
2026-06-13 00:26:12 -05:00
Zain Hasan 2c5ebe8219 [Together AI] add minimax m3 2026-06-12 22:12:45 -07:00
Frank 3a7b0ba2dc update zen models 2026-06-13 01:00:30 -04:00
github-actions[bot] e6c0abd6a9 chore(sync): update Vercel AI Gateway model catalog 2026-06-13 03:26:15 +00:00
github-actions[bot] 5288464365 chore(sync): update OpenRouter model catalog 2026-06-13 03:26:13 +00:00
Aiden Cline 7900fcd5a6 Merge pull request #2271 from shzdehmd/dev
feat(fireworks-ai): adding Kimi K2.7 Code, Qwen 3.7 Plus, and Minimax M3; removing deprecated models
2026-06-12 21:51:13 -05:00
Ahmad Shahzad 2df2c13060 feat(fireworks-ai): add K2.7 Code variants, Qwen 3.7 Plus, Minimax M3; remove deprecated models
- Add Kimi K2.7 Code (accounts/fireworks/models/kimi-k2p7-code)

- Add Kimi K2.7 Code Fast (accounts/fireworks/routers/kimi-k2p7-code-fast)

- Add Qwen 3.7 Plus (accounts/fireworks/models/qwen3p7-plus)

- Add Minimax M3 (accounts/fireworks/models/minimax-m3)

- Remove deprecated Kimi K2.5, Minimax M2.5, and Qwen 3.6 Plus
2026-06-13 07:48:41 +05:00
Aiden Cline f846124b1f Merge pull request #2257 from anomalyco/feat/google-reasoning-options-final
[google] Complete reasoning options
2026-06-12 17:47:42 -05:00
Aiden Cline cc81c1843f Merge pull request #2259 from anomalyco/feat/freemodel-reasoning-options-final
[freemodel] Add reasoning options
2026-06-12 17:47:16 -05:00
Aiden Cline c7823958d4 [freemodel] Add Anthropic reasoning controls 2026-06-12 17:42:25 -05:00
Aiden Cline 0178741953 [freemodel] Add OpenAI reasoning efforts 2026-06-12 17:34:32 -05:00
Aiden Cline 75db7752f5 Merge pull request #2156 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-12 17:31:09 -05:00
Aiden Cline 48d1bc2ee6 Merge pull request #2265 from jcraftsman/update-umans-ai-provider
Update Umans AI Coding Plan + add Umans AI (pay-per-token) provider
2026-06-12 17:30:09 -05:00
Aiden Cline d147b31d97 Merge pull request #2266 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-12 17:29:37 -05:00
github-actions[bot] b6fea3d470 chore(sync): update Vercel AI Gateway model catalog 2026-06-12 21:54:31 +00:00
github-actions[bot] d3fe5e3f4e chore(sync): update OpenRouter model catalog 2026-06-12 21:54:30 +00:00
Frank 1280fc51bc update go models 2026-06-12 16:24:50 -04:00
Aiden Cline 0e6590cc7a Merge pull request #2269 from anomalyco/fix/kimi-family-sync
fix(sync): normalize Kimi model families
2026-06-12 14:13:38 -05:00
Aiden Cline 78cb28fe8e fix(sync): normalize Kimi model families 2026-06-12 13:50:14 -05:00
Aiden Cline fcd71901fc Merge pull request #2267 from anomalyco/automation/sync-models-cloudflare-workers-ai
chore(sync): update Cloudflare Workers AI model catalog
2026-06-12 13:40:14 -05:00
github-actions[bot] 5ffe02911a chore(sync): update Cloudflare Workers AI model catalog 2026-06-12 18:04:11 +00:00
Aiden Cline add04f88a2 Merge pull request #2268 from leszek3737/zenmux/k2.7
Add Kimi K2.7 Code and version free model configuration
2026-06-12 12:32:38 -05:00
wassel alazhar 9fdad9f497 Add temperature = false for Kimi and Flash models (locked by Umans) 2026-06-12 19:28:31 +02:00
Leszek 1545b64010 [zenmux] Add Kimi K2.7 Code (Free) model configuration 2026-06-12 19:26:12 +02:00
Adrian Rogala 0382da663b Merge branch 'anomalyco:dev' into zenmux/k2.7 2026-06-12 19:22:21 +02:00
Leszek 0a933025aa [zenmux] Add kimi-k2.7-code model configuration 2026-06-12 19:20:59 +02:00
wassel alazhar 735e5f7f08 Remove umans-flash-beta from Coding Plan (deprecated, sunset 2026-06-07) 2026-06-12 19:16:05 +02:00
wassel alazhar a414765865 Update Umans AI Coding Plan and add Umans AI (pay-per-token) provider
Umans AI Coding Plan (subscription):
- Add umans-kimi-k2.7 (Kimi K2.7 Code) model
- Add umans-flash-beta (deprecated alias for umans-flash)
- Fix umans-glm-5.1: add vision modality (via handoff) and interleaved reasoning
- Fix umans-flash: add interleaved reasoning field
- Fix umans-qwen3.6-35b-a3b: add interleaved reasoning field
- Add reasoning_options to all models
- Add explicit name field to models using base_model

Umans AI (new provider - pay-per-token for orgs):
- New provider for organization service-account usage
- Per-token pricing from the org billing page:
  - Kimi K2.6: /bin/bash.95/.00 (input/output), /bin/bash.20 cache read
  - Kimi K2.7 Code: /bin/bash.95/.00, /bin/bash.19 cache read
  - GLM 5.1: .40/.40, /bin/bash.29 cache read
  - Umans Flash (Qwen3.6-35B-A3B): /bin/bash.15/.00, /bin/bash.05 cache read
  - Umans Coder: routes to Kimi K2.6 rates
- Same endpoint (api.code.umans.ai), different billing model
2026-06-12 19:08:15 +02:00
Aiden Cline 32f2c6de2a Merge pull request #2263 from anomalyco/feat/kimi-k2.7-code
feat(models): add Kimi K2.7 Code
2026-06-12 11:36:40 -05:00
Aiden Cline bd6fc4b145 feat(models): add Kimi K2.7 Code 2026-06-12 11:35:17 -05:00
Aiden Cline a0957ebea8 Merge pull request #2237 from anomalyco/feat/crof-reasoning-options-wave4
[crof] Add reasoning options
2026-06-12 11:17:12 -05:00
Aiden Cline 8b50fa5029 [crof] Add DeepSeek V4 reasoning efforts 2026-06-12 11:01:44 -05:00
Aiden Cline d6f87b6e64 Merge pull request #2233 from anomalyco/feat/gitlab-reasoning-options-wave4
[gitlab] Add reasoning options
2026-06-12 10:51:47 -05:00
Aiden Cline a66ef9ca19 Merge pull request #2209 from anomalyco/feat/meganova-reasoning-options-wave3
[meganova] Add reasoning options
2026-06-12 10:30:41 -05:00
Aiden Cline a48f0722e8 Merge pull request #2240 from anomalyco/feat/fastrouter-reasoning-options-wave4
[fastrouter] Add reasoning options
2026-06-12 10:28:15 -05:00
Aiden Cline aed6a48d73 Merge pull request #2241 from anomalyco/feat/neon-reasoning-options-wave4
[neon] Add reasoning options
2026-06-12 10:27:49 -05:00
Aiden Cline 1b24a035da Merge pull request #2245 from anomalyco/feat/helicone-reasoning-options-wave4
[helicone] Add reasoning options
2026-06-12 10:27:20 -05:00
Aiden Cline 560dceadf3 Merge pull request #2261 from anomalyco/feat/alibaba-token-plan-reasoning-options-final
[alibaba-token-plan] Add reasoning options
2026-06-12 10:26:40 -05:00
Aiden Cline f3607ba954 Merge pull request #2262 from anomalyco/feat/lmstudio-reasoning-options-final
[lmstudio] Add GPT-OSS reasoning options
2026-06-12 10:26:18 -05:00
Aiden Cline 6b90987895 Merge pull request #2248 from anomalyco/automation/sync-models-xai
chore(sync): update xAI model catalog
2026-06-12 10:26:05 -05:00
github-actions[bot] ec6b7af421 chore(sync): update xAI model catalog 2026-06-12 15:25:05 +00:00
Aiden Cline 25c2cf9224 [lmstudio] Add GPT-OSS reasoning options 2026-06-12 10:22:51 -05:00
Aiden Cline a4cdec5316 [alibaba-token-plan] Add reasoning options 2026-06-12 10:21:30 -05:00
Aiden Cline 90f36e0550 [freemodel] Add reasoning options 2026-06-12 10:21:27 -05:00
Aiden Cline f15aad08ef feat(alibaba-token-plan-cn): add reasoning options 2026-06-12 10:21:26 -05:00
Aiden Cline 063429a4ae [digitalocean] Cover remaining reasoning options 2026-06-12 10:20:56 -05:00
Aiden Cline b21c08dc44 [alibaba-coding-plan-cn] Add reasoning options 2026-06-12 10:20:42 -05:00
Aiden Cline 592da7f45c [google] Complete reasoning options 2026-06-12 10:20:36 -05:00
Aiden Cline 717892fd72 [evroc] Add reasoning options 2026-06-12 10:20:26 -05:00
Aiden Cline deef87451d [alibaba-coding-plan] Add reasoning options 2026-06-12 10:20:23 -05:00
Aiden Cline cd3c1f22f7 Merge remote-tracking branch 'origin/dev' into feat/digitalocean-reasoning-options-wave4 2026-06-12 10:19:56 -05:00
Aiden Cline 3f9980168e Merge pull request #2251 from houtanb/dev
Fix release dates
2026-06-12 09:51:58 -05:00
Jack 6f900fa761 Merge pull request #2180 from anomalyco/fix/opencode-go-minimax-m3-pricing
fix(opencode-go): update MiniMax M3 pricing
2026-06-12 21:09:49 +08:00
Houtan Bastani 61a153e6bb Update Mistral Large 2411 release date
Set mistral-large-2411 release_date and last_updated  metadata to 2024-11-18.

Evidence:

https://github.com/mistralai/platform-docs-public/blob/main/src/schema/models/models/mistral-large-2-1-24-11.ts#L9
2026-06-12 11:35:43 +02:00
Houtan Bastani 5a8f9d44d5 Update Gemini 2.5 Flash and Gemini 2.5 Pro release dates
Set Gemini 2.5 Flash and Gemini 2.5 Pro release_date and last_updated metadata to 2025-06-17.

Evidence:

https://ai.google.dev/gemini-api/docs/deprecations#gemini-2.5-flash-models

https://ai.google.dev/gemini-api/docs/deprecations#gemini-2.5-pro-models
2026-06-12 11:35:33 +02:00
Aiden Cline 629ce9b9f7 Merge pull request #2243 from anomalyco/feat/opencode-go-reasoning-options-wave4
[opencode-go] Add reasoning options
2026-06-12 00:07:39 -05:00
Aiden Cline 074022b5ad [huggingface] Correct fixed reasoning controls 2026-06-11 23:58:31 -05:00
Aiden Cline c562522e4f [opencode-go] Normalize DeepSeek V4 efforts 2026-06-11 23:57:47 -05:00
Aiden Cline dd051fd682 [helicone] Add reasoning options 2026-06-11 23:56:41 -05:00
Aiden Cline 074a288d15 [huggingface] Add reasoning options 2026-06-11 23:56:40 -05:00
Aiden Cline 2d0173d177 [opencode-go] Add reasoning options 2026-06-11 23:56:33 -05:00
Aiden Cline df75f7c438 [neon] Add reasoning options 2026-06-11 23:56:10 -05:00
Aiden Cline ffe754d81c [fastrouter] Add reasoning options 2026-06-11 23:56:04 -05:00
Aiden Cline c67d3e84ff [digitalocean] Add reasoning options 2026-06-11 23:55:32 -05:00
Aiden Cline 2f5d53cdf3 [crof] Add reasoning options 2026-06-11 23:54:59 -05:00
Aiden Cline 08b5fd9061 [gitlab] Add reasoning options 2026-06-11 23:54:10 -05:00
Aiden Cline 5aeaaa8d47 [anyapi] Add reasoning options 2026-06-11 23:52:40 -05:00
Aiden Cline 12280f413b [chutes] Add reasoning options 2026-06-11 23:52:31 -05:00
Aiden Cline 1a772fd297 Merge pull request #2223 from anomalyco/feat/google-vertex-reasoning-options-wave3
[google-vertex] Add MaaS reasoning options
2026-06-11 23:52:15 -05:00
Aiden Cline 38c708714f [cloudflare-ai-gateway] Add reasoning options 2026-06-11 23:52:04 -05:00
Aiden Cline 13abc41ac9 [abacus] Add reasoning options 2026-06-11 23:51:42 -05:00
Aiden Cline cf6a5e104d [auriko] Add reasoning options 2026-06-11 23:51:41 -05:00
Aiden Cline 128e0fbd8a [ambient] Add reasoning options 2026-06-11 23:50:16 -05:00
Aiden Cline c63920050a [google-vertex] Add MaaS reasoning options 2026-06-11 23:50:13 -05:00
Aiden Cline 24d2a74f4d [xiaomi-token-plan] Add reasoning toggles 2026-06-11 23:50:05 -05:00
Aiden Cline e62b47a005 [azure] Backfill DeepSeek V4 reasoning options 2026-06-11 23:49:52 -05:00
Aiden Cline eac6d1aaed [mistral] Mark Magistral reasoning controls fixed 2026-06-11 23:49:47 -05:00
Aiden Cline a914876008 [gmicloud] Add reasoning options 2026-06-11 23:49:46 -05:00
Aiden Cline 794203e12c [lilac] Add reasoning options 2026-06-11 23:49:45 -05:00
Aiden Cline 2624678dbe [dinference] Mark reasoning controls fixed 2026-06-11 23:49:42 -05:00
Aiden Cline 2d18ebb301 [inceptron] Mark reasoning controls fixed 2026-06-11 23:49:40 -05:00
Aiden Cline df45483742 Merge pull request #2211 from anomalyco/feat/wafer.ai-reasoning-options-wave3
[wafer.ai] Add reasoning options
2026-06-11 23:49:22 -05:00
Aiden Cline 4ecdcb8f2b Merge pull request #2196 from anomalyco/feat/hpc-ai-reasoning-options-wave3
[hpc-ai] Add reasoning options
2026-06-11 23:48:29 -05:00
Aiden Cline 4644def1ae [hpc-ai] Remove unsupported reasoning efforts 2026-06-11 23:45:03 -05:00
Aiden Cline f9091af5ee Merge pull request #2198 from anomalyco/feat/mixlayer-reasoning-options-wave3
[mixlayer] Add reasoning options
2026-06-11 23:41:20 -05:00
Aiden Cline 7022a48bfc Merge pull request #2205 from anomalyco/feat/vultr-reasoning-options-wave3
[vultr] Add reasoning options
2026-06-11 23:40:45 -05:00
Aiden Cline dda69225d8 Merge pull request #2190 from anomalyco/feat/perplexity-reasoning-options-wave3
[perplexity] Add reasoning options
2026-06-11 23:38:49 -05:00
Aiden Cline 2e020b1dd2 Merge pull request #2194 from anomalyco/feat/minimax-coding-plan-reasoning-options-wave3
[minimax-coding-plan] Complete reasoning options
2026-06-11 23:38:32 -05:00
Aiden Cline 67252bda30 Merge pull request #2197 from anomalyco/feat/submodel-reasoning-options-wave3
[submodel] Add reasoning options
2026-06-11 23:38:20 -05:00
Aiden Cline ac1566f622 Merge pull request #2193 from anomalyco/feat/minimax-reasoning-options-wave3
[minimax] Complete reasoning options
2026-06-11 23:38:02 -05:00
Aiden Cline 48837609aa Merge pull request #2199 from anomalyco/feat/v0-reasoning-options-wave3
[v0] Add reasoning options
2026-06-11 23:37:49 -05:00
Aiden Cline c2a0ff023a Merge pull request #2138 from martinmose/add-zeldoc-provider
feat(provider): add zeldoc provider
2026-06-11 23:28:07 -05:00
Aiden Cline 282821e7b6 Merge pull request #2208 from anomalyco/feat/qihang-ai-reasoning-options-wave3
[qihang-ai] Add reasoning options
2026-06-11 23:25:07 -05:00
Aiden Cline 1b8e53bcbd Merge pull request #2192 from anomalyco/feat/minimax-cn-reasoning-options-wave3
[minimax-cn] Complete reasoning options
2026-06-11 23:14:09 -05:00
Aiden Cline f671147f71 Merge pull request #2207 from anomalyco/feat/scaleway-reasoning-options-wave3
[scaleway] Add reasoning options
2026-06-11 23:13:54 -05:00
Aiden Cline e23e759601 Merge pull request #2204 from anomalyco/feat/clarifai-reasoning-options-wave3
[clarifai] Add reasoning options
2026-06-11 23:13:40 -05:00
Aiden Cline 4b4546f3e3 Merge pull request #2213 from anomalyco/feat/friendli-reasoning-options-wave3
[friendli] Add reasoning options
2026-06-11 23:13:24 -05:00
Aiden Cline 040f0ca995 [wafer.ai] Add DeepSeek V4 effort controls 2026-06-11 23:12:29 -05:00
Aiden Cline 9acac34889 [friendli] Remove unsupported reasoning efforts 2026-06-11 23:11:37 -05:00
Aiden Cline 78ad91f773 Merge pull request #2212 from anomalyco/feat/iflowcn-reasoning-options-wave3
[iflowcn] Add reasoning options
2026-06-11 23:10:34 -05:00
Aiden Cline 4b0aeef538 Merge pull request #2214 from anomalyco/feat/io-net-reasoning-options-wave3
[io-net] Add reasoning options
2026-06-11 23:09:58 -05:00
Aiden Cline 856787cb84 [friendli] Add reasoning options 2026-06-11 23:08:36 -05:00
Aiden Cline 084f0bb4ba [iflowcn] Add reasoning options 2026-06-11 23:08:36 -05:00
Aiden Cline ee1301fb63 [io-net] Add reasoning options 2026-06-11 23:08:36 -05:00
Aiden Cline 502957b778 [wafer.ai] Add reasoning options 2026-06-11 23:08:29 -05:00
Aiden Cline 3fba77ee56 [the-grid-ai] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline 0a6286e468 [regolo-ai] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline 8af96ce933 [berget] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline 4a056bd1ef [meganova] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline 9f11a93d06 [vultr] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline be8d8a2ec2 [qihang-ai] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline d4193dbad6 [scaleway] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline fb6b0985ce [clarifai] Add reasoning options 2026-06-11 23:07:50 -05:00
Aiden Cline 506ca0f7c3 [neuralwatt] Add reasoning options 2026-06-11 23:07:49 -05:00
Aiden Cline 4cafdb31ff [tencent-coding-plan] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline d97aedf7a1 [modelscope] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline 404bba4d1f [minimax-cn-coding-plan] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline 2bb4fe287e [hpc-ai] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline b58c396106 [mixlayer] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline 575f078887 [minimax-coding-plan] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline acf194ec01 [submodel] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline c80ff1ad2c [minimax] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline a41f6f5913 [v0] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline 8dffe03a9c [minimax-cn] Add reasoning options 2026-06-11 23:07:20 -05:00
Aiden Cline 9ea761af1a [perplexity] Add reasoning options 2026-06-11 23:07:07 -05:00
Aiden Cline c10ba76860 [upstage] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline 30a019056f [moonshotai] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline d7c4b11163 [nova] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline 1018ddf139 [cloudferro-sherlock] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline a5e572bcf6 [drun] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline 2d52062693 [moark] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline 6f3b739418 [poolside] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline f52cc7e895 [lucidquery] Add reasoning options 2026-06-11 23:06:33 -05:00
Aiden Cline 473d12c44b [inception] Add reasoning options 2026-06-11 23:06:33 -05:00
Jack 9401952857 chore(opencode-go): preserve MiniMax M3 file ending 2026-06-12 12:01:40 +08:00
Jack 623f5e8ea4 fix(opencode-go): update MiniMax M3 pricing 2026-06-12 11:59:03 +08:00
Aiden Cline 526b4cd543 Merge pull request #2179 from anomalyco/feat/deepseek-reasoning-options-wave2
[deepseek] Complete reasoning options
2026-06-11 22:55:29 -05:00
Aiden Cline b7d165296c Merge pull request #2172 from anomalyco/feat/privatemode-ai-reasoning-options
[privatemode-ai] Add reasoning options
2026-06-11 22:55:21 -05:00
Aiden Cline 655f757925 Merge pull request #2177 from anomalyco/feat/llmtr-reasoning-options
[llmtr] Complete reasoning options
2026-06-11 22:55:07 -05:00
Aiden Cline cce129dfd9 Merge pull request #2173 from anomalyco/feat/zai-coding-plan-reasoning-options
[zai-coding-plan] Complete reasoning options
2026-06-11 22:54:57 -05:00
Aiden Cline 548f3b5869 Merge pull request #2175 from anomalyco/feat/zhipuai-coding-plan-reasoning-options
[zhipuai-coding-plan] Complete reasoning options
2026-06-11 22:54:51 -05:00
Aiden Cline f349a9dde6 Merge pull request #2176 from anomalyco/feat/kuae-cloud-reasoning-options
[kuae-cloud-coding-plan] Add reasoning options
2026-06-11 22:54:34 -05:00
Aiden Cline 05c1041160 [deepseek] Mark reasoner controls fixed 2026-06-11 22:54:19 -05:00
Aiden Cline 215c3dfbc8 Merge pull request #2174 from anomalyco/feat/kimi-for-coding-reasoning-options
[kimi-for-coding] Complete reasoning options
2026-06-11 22:54:16 -05:00
Aiden Cline ddc6ad75a2 Merge pull request #2178 from anomalyco/feat/firepass-reasoning-options
[firepass] Add reasoning options
2026-06-11 22:54:05 -05:00
Aiden Cline 757bee7de8 Merge pull request #2167 from anomalyco/feat/claudinio-reasoning-options
[claudinio] Add reasoning options
2026-06-11 22:53:39 -05:00
Aiden Cline 5ee32a47ae Merge pull request #2170 from anomalyco/feat/bailing-reasoning-options
[bailing] Add reasoning options
2026-06-11 22:53:30 -05:00
Aiden Cline 2c22f4b488 [privatemode-ai] Add reasoning options 2026-06-11 22:51:42 -05:00
Aiden Cline 097296f6c6 [llmtr] Add reasoning options 2026-06-11 22:51:42 -05:00
Aiden Cline 2559ed64c5 [zai-coding-plan] Add reasoning options 2026-06-11 22:51:42 -05:00
Aiden Cline c56ac404d3 [zhipuai-coding-plan] Add reasoning options 2026-06-11 22:51:42 -05:00
Aiden Cline 4681e30afb [kuae-cloud-coding-plan] Add reasoning options 2026-06-11 22:51:42 -05:00
Aiden Cline 94cd023a88 [kimi-for-coding] Add reasoning options 2026-06-11 22:51:42 -05:00
Aiden Cline b5c33b6347 [deepseek] Add reasoning options 2026-06-11 22:51:41 -05:00
Aiden Cline 2014d883e4 [firepass] Add reasoning options 2026-06-11 22:51:41 -05:00
Aiden Cline 2f749b7cf9 [claudinio] Add reasoning options 2026-06-11 22:51:31 -05:00
Aiden Cline d7ab976e3c [bailing] Add reasoning options 2026-06-11 22:51:31 -05:00
Aiden Cline 32066b7856 Merge pull request #2131 from anomalyco/feat/ovhcloud-reasoning-audit
feat(ovhcloud): add reasoning options
2026-06-11 22:42:30 -05:00
Aiden Cline 3ded2fae72 Merge pull request #2130 from anomalyco/audit/nebius-models
[nebius] Audit reasoning controls
2026-06-11 22:38:33 -05:00
Aiden Cline 9771180f83 [nebius] Correct reasoning controls 2026-06-11 22:31:54 -05:00
Aiden Cline fa65113f37 Merge pull request #2162 from mikeyp/chore/update-digitalocean-models
Add anthropic-claude-fable-5 and nemotron-3-ultra-550b to DigitalOcean
2026-06-11 22:19:22 -05:00
Aiden Cline 1ab5784118 Merge pull request #2161 from andrelandgraf/feat/add-neon-provider
Add Neon provider
2026-06-11 22:17:07 -05:00
Mike Prasuhn 411bc157e6 Add anthropic-claude-fable-5 and nemotron-3-ultra-550b to DigitalOcean 2026-06-11 23:09:08 -04:00
Andre Landgraf 58ab76b8f3 Add Neon provider
Neon serves the same Databricks-backed model catalog through its
branch-scoped AI Gateway via an OpenAI-compatible endpoint, so this mirrors
the `databricks` provider's models.

- `api` uses the branch-scoped `NEON_AI_GATEWAY_BASE_URL` + the unified MLflow
  OpenAI-compatible route; `NEON_AI_GATEWAY_TOKEN` is the bearer key. Both are
  emitted by `neonctl env pull`.
- Model ids drop the `databricks-` prefix (the gateway accepts the bare ids),
  so models resolve as `neon/claude-haiku-4-5`, `neon/gpt-5-nano`, etc.
2026-06-11 19:52:37 -07:00
Martin Mose Facondini 8f2607fcdb fix(zeldoc): make logo black 2026-06-12 00:12:49 +02:00
Martin Mose Facondini 23e07a27d2 fix(zeldoc): correct z-code model fields 2026-06-12 00:12:44 +02:00
Aiden Cline 37e8e0cf95 Merge pull request #2086 from anomalyco/feat/openrouter-reasoning-options
feat(openrouter): add reasoning options
2026-06-11 16:21:15 -05:00
Aiden Cline 0e0fe311ab Merge dev into feat/openrouter-reasoning-options 2026-06-11 16:14:33 -05:00
Aiden Cline 3d763d081e fix(openrouter): document Claude effort mapping 2026-06-11 16:14:03 -05:00
Aiden Cline ebbd3416e2 Merge pull request #2154 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-11 16:07:40 -05:00
Aiden Cline a3509f9a3d Merge pull request #2155 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-11 16:07:24 -05:00
github-actions[bot] 71d00e7dfc chore(sync): update Vercel AI Gateway model catalog 2026-06-11 21:06:24 +00:00
github-actions[bot] 26f7b6f6b6 chore(sync): update OpenRouter model catalog 2026-06-11 21:06:21 +00:00
Aiden Cline 270009bd93 fix(openrouter): correct reasoning controls 2026-06-11 15:03:25 -05:00
Aiden Cline 91cc389af3 fix(openrouter): correct Claude reasoning controls 2026-06-11 14:57:34 -05:00
Aiden Cline 1c7b7e8247 Merge dev into feat/openrouter-reasoning-options 2026-06-11 14:27:05 -05:00
Aiden Cline a382026ce9 Merge pull request #2153 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-11 14:21:25 -05:00
github-actions[bot] 1fc7261c14 chore(sync): update Vercel AI Gateway model catalog 2026-06-11 19:15:03 +00:00
Aiden Cline 765dae9f61 Merge pull request #2134 from anomalyco/audit/deepinfra-reasoning
fix(deepinfra): reconcile reasoning controls
2026-06-11 13:02:22 -05:00
Aiden Cline 02cd80a2ab Merge pull request #2032 from anthraxx/alibaba-qwen3.7-plus
add Qwen3.7 Plus model configuration to Alibana coding plan
2026-06-11 12:57:58 -05:00
Aiden Cline fcbd02fa84 Merge pull request #2148 from anomalyco/automation/sync-models-venice
chore(sync): update Venice model catalog
2026-06-11 12:56:57 -05:00
Aiden Cline e07a0bab97 Merge pull request #2132 from anomalyco/chore/fireworks-reasoning
fix(fireworks-ai): reconcile reasoning controls
2026-06-11 12:55:12 -05:00
Aiden Cline ad166f7448 fix(fireworks-ai): verify reasoning toggles 2026-06-11 12:33:12 -05:00
github-actions[bot] 660350c5fa chore(sync): update Venice model catalog 2026-06-11 17:29:06 +00:00
Aiden Cline 54d94bdc79 fix(deepinfra): restore Kimi K2.5 toggle 2026-06-11 12:28:40 -05:00
Aiden Cline 912ee1fe86 fix(deepinfra): restore V4 effort enum 2026-06-11 12:23:47 -05:00
Aiden Cline 9379be8911 Merge pull request #2151 from anomalyco/fix/togetherai-required-reasoning-options
[togetherai] Require reasoning options metadata
2026-06-11 12:21:18 -05:00
Aiden Cline 1ccb247f7f [togetherai] Require reasoning options metadata 2026-06-11 12:05:22 -05:00
Aiden Cline 7d5469898d [nebius] Complete reasoning option coverage 2026-06-11 12:04:48 -05:00
Aiden Cline ec3c4ed8ea fix(fireworks-ai): mark unresolved reasoning controls 2026-06-11 12:04:47 -05:00
Aiden Cline 2b42408582 fix(deepinfra): mark unresolved reasoning controls 2026-06-11 12:04:46 -05:00
Aiden Cline 8521822a96 Merge pull request #2135 from anomalyco/audit/togetherai-reasoning-20260610
Audit Together AI models and reasoning controls
2026-06-11 12:01:15 -05:00
Aiden Cline 6b62d03ac0 chore(deepinfra): remove provider test 2026-06-11 12:00:46 -05:00
Aiden Cline 9df50e0ccc chore(ovhcloud): remove test changes 2026-06-11 12:00:37 -05:00
Aiden Cline 9b3d25aae8 [nebius] Remove provider matrix test 2026-06-11 12:00:36 -05:00
Aiden Cline ea4d10b219 chore(fireworks-ai): remove catalog test 2026-06-11 12:00:36 -05:00
Aiden Cline d3d163dd95 Remove Together provider matrix test 2026-06-11 12:00:35 -05:00
Aiden Cline 78f8fb92fc Merge pull request #2129 from anomalyco/audit/cerebras-models-20260610
[cerebras] Refresh public model catalog
2026-06-11 12:00:23 -05:00
Aiden Cline 62b4296b3d Delete packages/core/test/cerebras.test.ts 2026-06-11 12:00:10 -05:00
Aiden Cline 12057804f5 Merge pull request #2143 from Nindaleth/feature/ghcp-fable
feat(github-copilot): add Claude Fable 5 model
2026-06-11 11:48:42 -05:00
Aiden Cline 127290ebec Merge pull request #2142 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-11 11:45:55 -05:00
Aiden Cline 5abbfce7d6 Merge pull request #2120 from davidfierro/feat/snowflake-cortex-models
feat(snowflake-cortex): add missing but officially supported models
2026-06-11 11:45:36 -05:00
Aiden Cline 6ff6ab4028 Merge pull request #2145 from Omee11/feat/token-plan-cn
feat(alibaba-token-plan-cn): add Alibaba Token Plan (China) provider
2026-06-11 11:36:16 -05:00
Aiden Cline 0f3e0d7337 Merge pull request #2146 from Omee11/feat/token-plan-qwen3.7-plus
feat(alibaba-token-plan): add qwen3.7-plus
2026-06-11 10:51:52 -05:00
github-actions[bot] 8381089b11 chore(sync): update OpenRouter model catalog 2026-06-11 15:28:27 +00:00
Oliver Mee 98bed4baf6 feat(alibaba-token-plan-cn): add Alibaba Token Plan (China) provider 2026-06-11 18:14:20 +08:00
Oliver Mee e55ab2daf8 feat(alibaba-token-plan): add qwen3.7-plus 2026-06-11 18:14:20 +08:00
Radek Liska c60784fab5 feat(github-copilot): add Claude Fable 5 model 2026-06-11 09:15:02 +02:00
Aiden Cline 21581d8f4c test(sync): cover factored reasoning overrides 2026-06-10 23:20:55 -05:00
Aiden Cline 20bfe37a53 fix(sync): resolve changed canonical base 2026-06-10 23:19:32 -05:00
Aiden Cline fc4a781c2f fix(ovhcloud): complete reasoning controls 2026-06-10 23:16:08 -05:00
Aiden Cline 28674e1af4 [cerebras] Assert complete resolved models 2026-06-10 23:16:01 -05:00
Aiden Cline 1e76995f8e Test resolved Together provider matrix 2026-06-10 23:15:34 -05:00
Aiden Cline 0e371b761d fix(sync): resolve reasoning before preservation 2026-06-10 23:14:13 -05:00
Aiden Cline 7a5bf4f56e [cerebras] Test resolved model matrix 2026-06-10 23:13:39 -05:00
Aiden Cline 4c5b17b1db [nebius] Test generated provider matrix 2026-06-10 23:13:35 -05:00
Aiden Cline c4650219c3 fix(sync): drop stale reasoning options 2026-06-10 23:13:06 -05:00
Aiden Cline 9fb0474d1c feat(ovhcloud): add reasoning options 2026-06-10 23:13:06 -05:00
Aiden Cline 8807bb0069 Correct Together reasoning and pricing metadata 2026-06-10 23:12:12 -05:00
Aiden Cline 56426cc834 fix(deepinfra): preserve cache pricing 2026-06-10 23:11:28 -05:00
Aiden Cline 18c35709c5 Merge pull request #2139 from anomalyco/fix/venice-sync-models
Venice: fix synced model metadata
2026-06-10 20:14:38 -05:00
Aiden Cline b618a32341 fix(deepinfra): verify R1 reasoning controls 2026-06-10 20:13:26 -05:00
Aiden Cline f8229eba2e fix(deepinfra): narrow reasoning controls 2026-06-10 20:07:25 -05:00
Aiden Cline 6799ff1078 [nebius] Correct verified reasoning controls 2026-06-10 20:02:58 -05:00
Aiden Cline 236ff0e39b fix(fireworks-ai): add Qwen reasoning budget 2026-06-10 20:02:44 -05:00
Aiden Cline cb4fd81c59 Correct Together Qwen reasoning metadata 2026-06-10 19:51:01 -05:00
Aiden Cline 79e47be952 fix(deepinfra): use model-specific reasoning controls 2026-06-10 19:50:58 -05:00
Aiden Cline 03c161f038 [cerebras] Correct GLM reasoning option 2026-06-10 19:50:19 -05:00
Aiden Cline 38a2f09999 [nebius] Reconcile model lifecycle evidence 2026-06-10 19:49:42 -05:00
Aiden Cline 1f77766834 fix(fireworks-ai): remove unverified toggles 2026-06-10 19:48:27 -05:00
Aiden Cline da1032a1cb [venice] Fix synced model metadata 2026-06-10 19:46:52 -05:00
Aiden Cline 55848d41c6 Merge pull request #2123 from BlockListed/cortecs-add-claude-opus-4-8
add claude opus 4.8 to cortecs
2026-06-10 19:36:26 -05:00
BlockListed e8304a0b0f add claude opus 4.8 to cortecs 2026-06-10 23:48:02 +02:00
Martin Mose Facondini 23b4754d23 rename agentic-coding model to z-code 2026-06-10 23:06:51 +02:00
Martin Mose Facondini 20ffc3909f add zeldoc provider with agentic-coding model 2026-06-10 23:06:21 +02:00
Aiden Cline 09c7f864f2 Audit Together AI model catalog and reasoning 2026-06-10 16:05:11 -05:00
Aiden Cline c71d0c8065 fix(deepinfra): reconcile reasoning controls 2026-06-10 16:05:01 -05:00
Aiden Cline 5a3e0cacea test(fireworks-ai): lock reasoning controls 2026-06-10 16:04:34 -05:00
Aiden Cline f8ccb57731 [nebius] Audit reasoning controls 2026-06-10 16:04:09 -05:00
Aiden Cline c0b03ed655 [cerebras] Refresh public model catalog 2026-06-10 16:03:52 -05:00
Aiden Cline 63feee7eca Merge pull request #2128 from anomalyco/chore/close-stale-pull-requests
Automate stale pull request cleanup
2026-06-10 15:55:45 -05:00
Aiden Cline 233d636579 Merge pull request #2118 from dpuyosa/feat/venice-base-model
Venice: Update generation script to use base_model and reasoning_options
2026-06-10 15:55:01 -05:00
Aiden Cline 337d50d90f Automate stale pull request cleanup 2026-06-10 15:54:40 -05:00
Aiden Cline dfb3f2a421 Merge pull request #2062 from knowhycodata/add-llmtr-provider
feat: add LLMTR provider
2026-06-10 15:53:35 -05:00
Aiden Cline a87f44dc06 [venice] Remove stale generated metadata 2026-06-10 14:59:51 -05:00
Aiden Cline 19cb771778 [venice] Generate metadata for new models 2026-06-10 14:40:06 -05:00
Aiden Cline 1f1a82c280 [venice] Reconcile synced model overrides 2026-06-10 14:24:32 -05:00
Aiden Cline d9b2cd9076 Merge remote-tracking branch 'refs/remotes/contributor/feat/venice-base-model' into feat/venice-base-model 2026-06-10 14:24:21 -05:00
Aiden Cline 0ce7da24b6 [venice] Factor all models through metadata 2026-06-10 14:23:52 -05:00
Aiden Cline 44dabca8de Merge pull request #2093 from jatingomnet/fastrouter_model_update
feat(sync): sync FastRouter model catalog
2026-06-10 14:22:20 -05:00
Aiden Cline 62c62ec36f Merge pull request #2113 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-10 14:21:33 -05:00
Aiden Cline c3dcc2730f Merge pull request #2114 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-10 14:21:16 -05:00
Aiden Cline 4d90460f96 Merge pull request #2127 from leszek3737/zenmux/step-free
feat(zenmux): remove of Step 3.5 Flash (Free) model and add Step 3.7 Flash (Free)
2026-06-10 14:16:59 -05:00
github-actions[bot] 87b37096fc chore(sync): update OpenRouter model catalog 2026-06-10 19:10:45 +00:00
github-actions[bot] 1ea8f58f13 chore(sync): update Vercel AI Gateway model catalog 2026-06-10 19:10:44 +00:00
Aiden Cline 9a13c66393 Merge pull request #2126 from vglafirov/feat/gitlab-claude-fable-5
feat(gitlab): add Claude Fable 5 model
2026-06-10 11:57:34 -05:00
Leszek f8a551f850 Remove of Step 3.5 Flash (Free) model and add Step 3.7 Flash (Free) 2026-06-10 18:55:15 +02:00
Vladimir Glafirov 1751dc2671 feat(gitlab): add Claude Fable 5 model 2026-06-10 18:50:26 +02:00
Aiden Cline d333702d19 Merge pull request #2115 from pawelsierant/dev
Add Claude Fable 5 support for Azure
2026-06-10 11:40:16 -05:00
Aiden Cline 8f323c3a78 Merge pull request #2125 from Nichokas/add-freemodel-provider
feat(providers): add Claude Fable 5 to FreeModel
2026-06-10 11:40:03 -05:00
dpuyosa 8d3c8cfe21 Merge branch 'dev' into feat/venice-base-model 2026-06-10 18:39:23 +02:00
Aiden Cline 1f8adf44e7 Merge pull request #2091 from dpuyosa/feat/venice-models
Venice: Add tencent-hy3-preview and update minimax-m27
2026-06-10 11:13:10 -05:00
Aiden Cline 4201586665 [venice] Remove models missing from API 2026-06-10 11:08:15 -05:00
Nichokas 28d6a54256 feat: add Claude Fable 5 to FreeModel 2026-06-10 18:06:54 +02:00
Aiden Cline 33bceb35a5 Merge remote-tracking branch 'origin/dev' into feat/venice-base-model
# Conflicts:
#	providers/venice/models/claude-fable-5.toml
2026-06-10 11:00:48 -05:00
Aiden Cline 25c3d6cd23 [venice] Migrate generator to sync runner 2026-06-10 10:58:12 -05:00
David Fierro Iglesias 5dcd077370 feat(snowflake-cortex): add officially supported models 2026-06-10 16:29:41 +02:00
Aiden Cline 987ca2d2f8 Merge pull request #2117 from dpuyosa/feat/venice-claude-fable-5
Venice: Add claude-fable-5 model
2026-06-10 09:28:58 -05:00
dpuyosa 07d2e3e23f [venice] Update models with the new generation script version
- Use new `base_model` and `reasoning_options`
2026-06-10 13:37:13 +02:00
dpuyosa 5d5a421b35 [venice] Add claude-fable-5 model
- Add new provider model configuration inheriting from anthropic base
- Enable structured_output and define cost/modalities
- New file: providers/venice/models/claude-fable-5.toml
2026-06-10 13:15:23 +02:00
dpuyosa 63f56867b9 [venice] Add tencent-hy3-preview and update minimax-m27
- Inherit base_model metadata for both models
- Add reasoning_options with effort levels
- Remove redundant fields now provided by base
2026-06-10 13:06:17 +02:00
dpuyosa d4bf232f91 [venice] Add base_model + reasoning_options to generator
- Derive open_weights from base model metadata when present
- Remove open_weights from baseModelOverrides and formatBaseModelToml
- Add temperature comparison in detectChanges for provider models
2026-06-10 12:59:38 +02:00
dpuyosa 0b27d6034d [venice] Add base_model + reasoning_options to generator
- Add base_model lookup via models/ metadata directory
- Support new reasoning field (reasoning_options effort), audio pricing, and full TOML formatting
- Preserve existing fields and emit minimal override TOMLs when base_model present
- Update change detection and formatting for base_model mode
2026-06-10 12:42:39 +02:00
Frank 57caaf88a2 update zen models 2026-06-10 03:55:32 -04:00
Pawel Sierant 3b3d7ac3aa Add Claude Fable 5 support for Azure 2026-06-10 08:37:24 +02:00
jatin.go 015679c420 chore(fastrouter): use base_model for grok-build-0.1 and sarvam models
Addresses PR #2093 review feedback to use base_model inheritance where a
canonical models/ entry exists or can be added.

- providers/fastrouter/models/x-ai/grok-build-0.1.toml: switch to
  base_model = "xai/grok-build-0.1" (canonical already existed); drop
  duplicated/conflicting facts.
- models/sarvam/sarvam-30b.toml, models/sarvam/sarvam-105b.toml: add new
  canonical metadata so multiple sarvam-hosting providers can share it.
- providers/fastrouter/models/sarvam/sarvam-30b.toml,
  providers/fastrouter/models/sarvam/sarvam-105b.toml: switch to
  base_model with only [cost] override.

bun validate exits 0.
2026-06-10 11:22:29 +05:30
Aiden Cline de6034494d Merge pull request #2112 from anomalyco/feat/groq-reasoning-options
fix(groq): reconcile model catalog and reasoning options
2026-06-10 00:29:38 -05:00
Aiden Cline feb387982b fix(groq): reconcile active model catalog 2026-06-10 00:14:39 -05:00
Aiden Cline 3beb135e23 feat(groq): add reasoning options 2026-06-10 00:08:17 -05:00
Aiden Cline eb2dc1750e Merge pull request #2111 from anomalyco/feat/nvidia-reasoning-options
feat(nvidia): add reasoning options
2026-06-09 23:44:39 -05:00
Aiden Cline 0fa6f6a983 feat(schema): support unbounded reasoning budgets 2026-06-09 23:20:21 -05:00
Aiden Cline aba6cae853 fix(nvidia): narrow reasoning controls 2026-06-09 23:16:13 -05:00
Aiden Cline 83e2a3437f feat(nvidia): add reasoning options 2026-06-09 20:48:07 -05:00
Aiden Cline f3d8034335 Merge pull request #2109 from anomalyco/fix/cloudflare-sync-reasoning-options
fix(sync): preserve reasoning options
2026-06-09 19:54:25 -05:00
Aiden Cline 42fbb1d9ca Merge pull request #2067 from CodeAnimal/az-deepseek-v4
Azure DeepSeek-V4-Pro and DeepSeek-V4-Flash
2026-06-09 19:53:46 -05:00
Aiden Cline 431df4b758 fix(sync): preserve authored reasoning options 2026-06-09 19:51:11 -05:00
Aiden Cline e9f798225c Merge pull request #2090 from coder-wangbin/fix/qwen3.7-plus-params
fix(alibaba/qwen3.7-plus): correct max output to 64K and tier size to 256K
2026-06-09 19:47:27 -05:00
Aiden Cline c2a85735d3 fix(cloudflare): preserve reasoning options during sync 2026-06-09 19:47:13 -05:00
Aiden Cline 8bc3d0b602 Merge pull request #2095 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-09 19:45:58 -05:00
Aiden Cline bcfcaccbd3 Merge pull request #2077 from anomalyco/feat/novita-reasoning-options
feat(novita-ai): add reasoning options
2026-06-09 19:45:42 -05:00
github-actions[bot] e0f2b8a542 chore(sync): update OpenRouter model catalog 2026-06-09 23:45:17 +00:00
Aiden Cline e0c0f0202d fix(novita-ai): omit unusable V4 none effort 2026-06-09 17:32:35 -05:00
Aiden Cline 3bc08e0991 Merge pull request #2100 from kites262/feat/add-mimo-v25-pro-ultraspeed
feat(xiaomi): add mimo-v2.5-pro-ultraspeed
2026-06-09 17:28:51 -05:00
Aiden Cline f63d764e53 Merge pull request #2108 from leszek3737/zenmux/claude-fable-5
Fead(zenmux): Add Claude Fable 5 support for Zenmux
2026-06-09 17:28:24 -05:00
Leszek 8a24eecc9a Add Claude Fable 5 for Zenmux 2026-06-09 23:13:00 +02:00
Frank fb13347f59 update zen model 2026-06-09 15:40:00 -04:00
Aiden Cline d06fc9dfc8 fix(novita-ai): add DeepSeek V4 reasoning efforts 2026-06-09 13:47:34 -05:00
Aiden Cline b1867ba865 Merge pull request #2104 from helloimalastair/cloudflare-aig-fable-5
Add Claude Fable 5 for Cloudflare AI Gateway
2026-06-09 13:46:31 -05:00
Aiden Cline e5a8bf7e59 Merge pull request #2105 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-09 13:46:21 -05:00
Aiden Cline a93d3b37f4 Merge pull request #2106 from unexge/push-tkvuvvvrvmlq
Add Claude Fable 5 for Amazon Bedrock
2026-06-09 13:46:06 -05:00
Aiden Cline c08584c555 Merge pull request #2107 from vercel/fix-fable-5-reasoning-options
fix(vercel): declare claude-fable-5 reasoning as effort-only
2026-06-09 13:45:50 -05:00
R-Taneja 62fb2b4437 fix(vercel): declare claude-fable-5 reasoning as effort-only
claude-fable-5 rejects thinking.type=enabled (budget_tokens) and requires
thinking.type=adaptive + output_config.effort. Without reasoning_options,
consumers like OpenCode fall back to the legacy budget_tokens control and
the API errors. Mirror claude-opus-4-8, which is also effort-only.
2026-06-09 11:40:16 -07:00
Burak Varli ab1b725656 Add Claude Fable 5 for Amazon Bedrock
Add us/eu/global cross-region inference profiles and set the knowledge
cutoff on the shared base model.
2026-06-09 18:39:45 +00:00
github-actions[bot] 6c710128ef chore(sync): update Vercel AI Gateway model catalog 2026-06-09 17:56:48 +00:00
helloimalastair ef15783481 add claude fable 5 for cloudflare ai gateway 2026-06-09 10:32:45 -07:00
Rohan Taneja ea3976505e Merge pull request #2103 from vercel/update-vercel-models-claude-fable-5
Add Claude Fable 5 (Vercel AI Gateway)
2026-06-09 10:32:16 -07:00
Aiden Cline b2780222da Merge pull request #2102 from anomalyco/add-anthropic-claude-fable-5
Add Claude Fable 5
2026-06-09 12:30:18 -05:00
R-Taneja 303758f170 chore(vercel): add claude-fable-5 model definition
Generated from the Vercel AI Gateway API (bun run vercel:generate --new-only).
2026-06-09 10:30:10 -07:00
Aiden Cline 259aff58eb Add Claude Fable 5 2026-06-09 12:15:45 -05:00
Frank 22f6dd1b9d update zen models 2026-06-09 12:08:10 -04:00
Aiden Cline 7c6727ffc9 Merge pull request #2094 from tomscohere/cohere-north-mini-code-1.0
[cohere] Add Cohere North-Mini-Code-1.0 and Cohere Command A+
2026-06-09 10:55:42 -05:00
kites262 7a25c3fe9c feat(xiaomi): add mimo-v2.5-pro-ultraspeed 2026-06-09 23:52:25 +08:00
Aiden Cline 57e0020109 Merge pull request #2089 from RISHIKREDDYL/dev
fix(azure-cognitive-services): remove broken symlinks for retired xAI models
2026-06-09 10:48:29 -05:00
Aiden Cline 9b7dbfea77 refactor(cohere): use base models 2026-06-09 09:53:44 -05:00
Aiden Cline 8648cb4778 Merge pull request #2084 from anomalyco/feat/cloudflare-workers-ai-reasoning-options
feat(cloudflare-workers-ai): add reasoning options
2026-06-09 09:52:13 -05:00
tomscohere ca1d731028 Update name 2026-06-09 14:21:49 +00:00
tomscohere f0928f55be Update North mini code to Cohere provider 2026-06-09 14:10:39 +00:00
tomscohere 8fbda849d7 Fix A+ last_updated 2026-06-09 11:55:42 +00:00
tomscohere 938ef111c4 Restore package lock 2026-06-09 11:50:56 +00:00
tomscohere 7b65a7c6de Add North-Mini-Code and Cohere CMDA+ 2026-06-09 11:50:07 +00:00
jatin.go eaffc05654 chore(fastrouter): drop unrequested models from prior sync
Removes ~117 model TOMLs introduced by the merged-in big sync commit and
keeps only the 32 explicitly-requested new models. Also reverts the two
pricing changes (deepseek-r1-distill-llama-70b, z-ai/glm-5) and restores
the two previously-deleted files (moonshotai/kimi-k2.toml, z-ai/glm-4.5)
to their original pre-sync state.

bun validate exits 0.
2026-06-09 16:28:15 +05:30
jatin.go c8adf1e849 Merge branch 'fastrouter_model_update' of https://github.com/jatingomnet/models.dev into fastrouter_model_update 2026-06-09 16:25:01 +05:30
jatin.go 8a8912c39d feat(sync): add new FastRouter models
Adds 32 new model TOMLs matching the latest fastrouter.ai/models listing.

- Anthropic: claude-opus-4.8, claude-sonnet-4.6
- xAI: grok-4.3, grok-build-0.1
- OpenAI: gpt-5.5, gpt-5.5-pro, gpt-5.4-mini, gpt-5.4-nano,
  gpt-5.3-codex, gpt-image-2, gpt-realtime-1.5
- Google: gemini-3.5-flash, gemini-3.1-pro-preview, gemma-4-31b-it,
  gemini-3.1-flash-image-preview, gemini-3-pro-image-preview,
  imagen-4.0-fast, imagen-4.0-ultra, veo3.1, veo3.1-fast, veo3.1-lite
- DeepSeek: deepseek-v4-pro
- MoonshotAI: kimi-k2.6
- Z.AI: glm-5.1
- MiniMax: minimax-m2.7, minimax-m2.7-highspeed
- Sarvam: sarvam-105b, sarvam-30b
- ByteDance: seedance-2
- Alibaba: wanx/wan-v2-6
- Leonardo.AI: lucid-origin, lucid-realism

Uses base_model inheritance where canonical models/ entries exist;
self-contained TOMLs otherwise. bun validate exits 0.
2026-06-09 16:20:29 +05:30
jatin.go c6641ba93d feat(sync): sync FastRouter model catalog
Adds ~117 new model TOMLs, removes 1 stale entry, and updates 2 pricing
files to match the current fastrouter.ai/models listing.

- Remove moonshotai/kimi-k2 (replaced by kimi-k2.5 and kimi-k2.6)
- Fix z-ai/glm-4.5 (missing .toml extension); convert to base_model ref
- Update pricing: deepseek-r1-distill-llama-70b, z-ai/glm-5
- Add Anthropic claude-opus-4.5 through claude-3-5-haiku-20241022
- Add OpenAI gpt-5.x/4.x/3.5, o-series, realtime, image, sora, embeddings
- Add Google gemini-3.x/gemma-4, imagen-4, veo2/veo3/veo3.1 families
- Add xAI grok-4.x/3.x/2, DeepSeek v3.x/v4-pro/R1 variants
- Add Qwen, MoonshotAI, MiniMax, Perplexity, Meta, Mistral, Z.AI, Sarvam
- Add FLUX, ByteDance seedream/seedance, Leonardo AI, Kling, Runway,
  Pika, Pollo, Vidu, Wanx video/image models and ace-step audio

Uses base_model inheritance where canonical models/ entries exist;
self-contained TOMLs otherwise. bun validate exits 0.
2026-06-09 15:40:27 +05:30
dpuyosa fc7493b27b [venice] Add tencent-hy3-preview and update minimax-m27
- Add new tencent-hy3-preview model with cost/limit/modality config
- Update minimax-m27 last_updated and cache_read pricing
2026-06-09 11:56:16 +02:00
wangbin d4d0b483b5 fix(alibaba/qwen3.7-plus): correct output to 64K and tier size to 256K
- output: 16,384 → 65,536 (official max output is 64K)
- tier.size: 128,000 → 256,000 (matches qwen3.6-plus tier threshold)

Verified against official spec:
https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/detail/qwen3.7-plus
Context: 1M | Max Output: 64K | Modalities: text + image + video
2026-06-09 16:57:14 +08:00
CodeAnimal f83b8ea4a1 Introduce base_model and other corrections based on feedback 2026-06-09 09:53:54 +01:00
Ubuntu b66908a347 fix(azure-cognitive-services): remove broken symlinks for retired xAI models 2026-06-09 11:54:19 +05:30
Aiden Cline 37b1d0ac95 fix(cloudflare-workers-ai): preserve schema compatibility 2026-06-08 23:19:03 -05:00
Aiden Cline f674b240c3 Merge pull request #2087 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-08 23:03:09 -05:00
Aiden Cline b233bbdb81 Merge pull request #2075 from anomalyco/feat/azure-reasoning-options
feat(azure): add reasoning options
2026-06-08 23:02:50 -05:00
Aiden Cline 2817f22c8a fix(azure): expose Kimi reasoning toggles 2026-06-08 23:02:12 -05:00
Aiden Cline ec05c9d5f1 Merge pull request #2088 from anomalyco/feat/baseten-model-sync
feat(sync): add Baseten model sync
2026-06-08 22:53:56 -05:00
Aiden Cline f96381a34d feat(sync): add Baseten model sync 2026-06-08 22:51:22 -05:00
Aiden Cline aeecf3b66f fix(novita-ai): add GPT OSS reasoning efforts 2026-06-08 22:41:16 -05:00
Aiden Cline a963e6ae00 Merge pull request #2078 from anomalyco/feat/baseten-reasoning-options
feat(baseten): add reasoning options
2026-06-08 22:28:13 -05:00
github-actions[bot] 075fd9263e chore(sync): update OpenRouter model catalog 2026-06-09 03:25:49 +00:00
Aiden Cline f4bea4e831 Merge pull request #2081 from anomalyco/feat/vertex-reasoning-options
feat(google-vertex): add reasoning options
2026-06-08 22:13:28 -05:00
Aiden Cline dcae17ea26 fix(google-vertex): retain latest Gemini aliases 2026-06-08 22:12:00 -05:00
Aiden Cline ffedd884f7 fix(google-vertex): remove retired models 2026-06-08 22:06:17 -05:00
Aiden Cline e885955e2f Merge pull request #2079 from anomalyco/feat/ollama-cloud-reasoning-options
feat(ollama-cloud): add reasoning options
2026-06-08 21:57:41 -05:00
Aiden Cline bddf7e070c Merge pull request #2083 from anomalyco/feat/deepinfra-reasoning-options
feat(deepinfra): add reasoning options
2026-06-08 21:56:56 -05:00
Aiden Cline 80c55103dd fix(deepinfra): restore DeepSeek V4 effort controls 2026-06-08 21:13:16 -05:00
Aiden Cline 9a8efd2f2f fix(ollama-cloud): expose MiniMax M3 reasoning controls 2026-06-08 21:00:49 -05:00
Aiden Cline 919ee8da23 fix(ollama-cloud): expose DeepSeek max reasoning 2026-06-08 20:49:29 -05:00
Aiden Cline 71f76d7a1b Merge pull request #2080 from anomalyco/feat/fireworks-reasoning-options
feat(fireworks-ai): add reasoning options
2026-06-08 20:46:48 -05:00
Aiden Cline b6e8a23d76 Merge pull request #2085 from anomalyco/feat/xai-reasoning-options
feat(xai): add reasoning options
2026-06-08 20:36:44 -05:00
Aiden Cline f1fb54c7ba test(xai): reflect language model sync fields 2026-06-08 20:34:29 -05:00
Aiden Cline 9d6cfa4c3f fix(xai): preserve reasoning options during sync 2026-06-08 20:30:35 -05:00
Aiden Cline 5e7769cb71 fix(openrouter): expose Claude Opus effort 2026-06-08 20:22:10 -05:00
Aiden Cline add3daacea feat(openrouter): add reasoning options 2026-06-08 20:17:16 -05:00
Aiden Cline 83faa5efe4 feat(xai): add reasoning options 2026-06-08 20:17:02 -05:00
Aiden Cline bf9b74e973 feat(cloudflare-workers-ai): add reasoning options 2026-06-08 20:16:58 -05:00
Aiden Cline 077a047eb0 feat(deepinfra): add reasoning options 2026-06-08 20:16:49 -05:00
Aiden Cline e696b33e0f feat(google-vertex): add reasoning options 2026-06-08 20:16:48 -05:00
Aiden Cline d1f12dd63a feat(fireworks-ai): add reasoning options 2026-06-08 20:16:45 -05:00
Aiden Cline 30406be8f4 feat(ollama-cloud): add reasoning options 2026-06-08 20:16:42 -05:00
Aiden Cline e4768a2d76 feat(baseten): add reasoning options 2026-06-08 20:16:41 -05:00
Aiden Cline c347e8b438 feat(novita-ai): add reasoning options 2026-06-08 20:16:40 -05:00
Aiden Cline 5bb6b2aaf8 Merge pull request #2076 from anomalyco/feat/bedrock-reasoning-options
feat(amazon-bedrock): add reasoning options
2026-06-08 19:36:54 -05:00
Aiden Cline 468e2ad4ca feat(amazon-bedrock): add reasoning options 2026-06-08 19:28:00 -05:00
Aiden Cline 04ba6ca94e feat(azure): add reasoning options 2026-06-08 17:53:46 -05:00
Aiden Cline 0d7d13bb38 Merge pull request #2074 from anomalyco/feat/openai-reasoning-options
feat(openai): add reasoning options
2026-06-08 17:28:22 -05:00
Aiden Cline 6cc99b6a97 feat(openai): add reasoning options 2026-06-08 17:17:21 -05:00
Aiden Cline fe7927f2dd Merge pull request #2061 from Astro-Han/add-qwen3.7-plus-coding-plan-cn
feat: add qwen3.7-plus to alibaba-coding-plan-cn provider
2026-06-08 16:43:46 -05:00
Aiden Cline cef703a91b Merge pull request #2072 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-08 16:43:09 -05:00
Aiden Cline 3cdf2181f5 Merge pull request #2073 from anomalyco/fix/vercel-sync-all-model-types
fix(vercel): sync all gateway model types
2026-06-08 16:42:45 -05:00
Aiden Cline b0e1ed9338 fix(vercel): sync all gateway model types 2026-06-08 16:32:25 -05:00
github-actions[bot] d061339e5d chore(sync): update OpenRouter model catalog 2026-06-08 21:05:13 +00:00
Aiden Cline 26362a4ce3 Merge pull request #2068 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-08 15:55:27 -05:00
Aiden Cline 2b0e0b7c95 Merge pull request #2047 from leszek3737/zenmux-7.06
feat(zenmux): add 11 new model definitions
2026-06-08 15:53:19 -05:00
github-actions[bot] 6a1ef22dcd chore(sync): update OpenRouter model catalog 2026-06-08 19:58:10 +00:00
Leszek 9a576170d4 fix(zenmux): correct Claude Opus 4.8 base model ID 2026-06-08 21:40:32 +02:00
CodeAnimal b821602bbd Add DeepSeek-V4-Flash to Azure provider 2026-06-08 17:21:48 +01:00
CodeAnimal 74bf471580 Add DeepSeek-V4-Pro to Azure provider 2026-06-08 17:21:36 +01:00
knowhy e043cc6da1 refactor(llmtr): use base_model for qwen3-6-35b 2026-06-08 18:19:40 +03:00
knowhy 6c523206aa feat(llmtr): add logo 2026-06-08 18:19:39 +03:00
Aiden Cline bccfdc7b87 Merge pull request #2050 from hgraca/nvidia
Add NVIDIA/NVIDIA Nemotron 3 Ultra
2026-06-08 09:55:16 -05:00
Aiden Cline 7217d11f79 fix(nvidia): use base model for nemotron ultra 2026-06-08 09:41:07 -05:00
Aiden Cline ca035f8d4c Merge pull request #2060 from nathannli/dev
fix(cerebras): deprecate llama3.1-8b
2026-06-08 09:21:54 -05:00
Aiden Cline 047af97a74 Merge pull request #2063 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-08 09:18:59 -05:00
Aiden Cline 3b05a8e199 Merge pull request #2066 from dpuyosa/feat/venice-nemotron
Venice: Update gemma pricing and add nemotron model
2026-06-08 09:18:46 -05:00
github-actions[bot] 67880dc681 chore(sync): update OpenRouter model catalog 2026-06-08 13:46:24 +00:00
dpuyosa dcf7f6d0a3 [venice] Update gemma pricing and add nemotron model
- Update google-gemma-4-31b-it cost/last_updated
- Add nvidia-nemotron-3-ultra-550b-a55b.toml with pricing/limits
2026-06-08 13:30:36 +02:00
Jack 209ce771e9 update minimax-m3 price in go 2026-06-08 19:11:47 +08:00
knowhy 5ccbf972a5 feat(llmtr): add models/sincap.toml 2026-06-08 08:30:28 +03:00
knowhy c178001cca feat(llmtr): add models/magibu-11b-v8.toml 2026-06-08 08:30:27 +03:00
knowhy 69a833ef58 feat(llmtr): add models/trendyol-7b.toml 2026-06-08 08:30:26 +03:00
knowhy 33c79f65b1 feat(llmtr): add models/medgemma-4b.toml 2026-06-08 08:30:25 +03:00
knowhy c4fba0747f feat(llmtr): add models/qwen3-6-35b.toml 2026-06-08 08:30:24 +03:00
knowhy daf5f684b8 feat(llmtr): add models/gemma-4.toml 2026-06-08 08:30:23 +03:00
knowhy fa17e02dc2 feat(llmtr): add provider.toml 2026-06-08 08:30:22 +03:00
Yuhan Lei 4dbd6b6b13 fix: use base_model format instead of full definition 2026-06-08 10:56:26 +08:00
Yuhan Lei ff9199d68c feat: add qwen3.7-plus to alibaba-coding-plan-cn provider
Qwen3.7 Plus is now available on Alibaba Cloud Coding Plan (China).
2026-06-08 10:52:50 +08:00
Nathan Li e46f128b3a fix(cerebras): deprecate llama3.1-8b 2026-06-07 21:28:51 -04:00
Aiden Cline b5a8387a39 Merge pull request #2053 from smakosh/add-llmgateway-minimax-m3-qwen37-plus
feat(llmgateway): add MiniMax M3 and Qwen3.7 Plus
2026-06-07 20:12:32 -05:00
Aiden Cline f55137b165 Merge pull request #2056 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-07 20:12:15 -05:00
Aiden Cline 08607a1ea0 Merge pull request #2057 from anomalyco/automation/sync-models-vercel
chore(sync): update Vercel AI Gateway model catalog
2026-06-07 20:12:07 -05:00
Aiden Cline 4cabf9f282 Merge pull request #2055 from anomalyco/automation/sync-models-cloudflare-workers-ai
chore(sync): update Cloudflare Workers AI model catalog
2026-06-07 20:11:55 -05:00
github-actions[bot] d603abe1d9 chore(sync): update Cloudflare Workers AI model catalog 2026-06-07 23:38:41 +00:00
github-actions[bot] aedf097709 chore(sync): update OpenRouter model catalog 2026-06-07 23:38:39 +00:00
github-actions[bot] dafb6a02a2 chore(sync): update Vercel AI Gateway model catalog 2026-06-07 23:38:37 +00:00
Levente Polyak 15f015fd4a add Qwen3.7 Plus model configuration to Alibana coding plan
Coding-plan models are at a fixed monthly fee.

Link: https://modelstudio.console.alibabacloud.com/eu-central-1?tab=doc#/doc/?type=model&url=3005961
2026-06-07 21:54:13 +02:00
Leszek c02cf9ee89 refactor(zenmux): centralize model definitions and simplify provider configs
This refactors Zenmux model configurations by:
- Moving comprehensive model properties (e.g., limits, modalities) from `providers/zenmux/models/` to the shared `models/` directory.
- Introducing `base_model` references in `providers/zenmux/models/` files, which now primarily specify provider-specific attributes like `cost`.
- Updating parameters for `qwen3.7-plus`, `gpt-5.5-instant`, and `step-3.7-flash` during this reorganization.
2026-06-07 20:22:01 +02:00
Aiden Cline f112360043 Merge pull request #2052 from anomalyco/fix/google-sync-preserve-base-models
fix(google): compact sync and add Vertex TTS
2026-06-07 12:49:43 -05:00
Aiden Cline e6aca83545 feat(google-vertex): add Gemini 2.5 TTS models 2026-06-07 12:38:21 -05:00
smakosh 257a4fd79c fix(llmgateway): use base_model inheritance for MiniMax M3 and Qwen3.7 Plus
Upstream renamed the inheritance keyword from [extends].from to
base_model. Switch both new llmgateway entries to base_model so they
inherit the shared canonical metadata and only override llmgateway cost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:29:09 +01:00
Aiden Cline 7feb291963 fix(google): remove unsupported TTS model ID 2026-06-07 12:28:33 -05:00
smakosh 3e8bec11b5 Merge remote-tracking branch 'upstream/dev' into add-llmgateway-minimax-m3-qwen37-plus
# Conflicts:
#	providers/alibaba/models/qwen3.7-plus.toml
#	providers/minimax/models/MiniMax-M3.toml
2026-06-07 18:21:42 +01:00
smakosh d322c49fc5 feat(llmgateway): add MiniMax M3 and Qwen3.7 Plus
Add two new text models from the LLM Gateway catalog
(https://api.llmgateway.io/v1/models), each as an llmgateway entry
extending a canonical provider model:

- minimax-m3 -> minimax/MiniMax-M3
- qwen3.7-plus -> alibaba/qwen3.7-plus

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:20:10 +01:00
Aiden Cline a067d458a9 Merge pull request #2030 from Nichokas/add-freemodel-provider
feat(freemodel): add FreeModel.dev provider (Anthropic + OpenAI formats)
2026-06-07 12:17:29 -05:00
Aiden Cline 9c0aaa8482 chore(google): sync image context limit 2026-06-07 12:17:23 -05:00
Aiden Cline 317334dc33 fix(google): preserve synced base models 2026-06-07 12:16:58 -05:00
Aiden Cline 134906e37c Merge pull request #2051 from anomalyco/refactor/vercel-shared-sync
chore(sync): update Vercel model catalog
2026-06-07 12:06:18 -05:00
Aiden Cline f473f985d9 Merge pull request #2049 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-07 12:05:21 -05:00
Frank b8f06211b0 update zen models 2026-06-07 12:48:22 -04:00
github-actions[bot] 8b742acb8b chore(sync): update OpenRouter model catalog 2026-06-07 16:46:19 +00:00
Herberto Graca 0645480326 Add NVIDIA/NVIDIA Nemotron 3 Ultra 2026-06-07 16:47:31 +02:00
Leszek 8d2f754dd6 feat(zenmux): add 11 new model definitions
New models: claude-opus-4.8, gemini-3.1-flash-lite, gemini-3.5-flash, ring-2.6-1t, minimax-m3, gpt-5.5-instant, qwen3.7-max, qwen3.7-plus, step-3.7-flash, grok-4.3, grok-build-0.1
2026-06-07 13:56:12 +02:00
Nichokas 72bba2a4a3 fix: Update logo to comply with the guidelines 2026-06-07 10:36:11 +02:00
Aiden Cline 3cfa5e6583 Merge pull request #2041 from anomalyco/refactor/vercel-shared-sync
refactor(sync): migrate Vercel to shared runner
2026-06-07 00:22:31 -05:00
Aiden Cline 4a01190179 Merge pull request #2043 from anomalyco/automation/sync-models-cloudflare-workers-ai
chore(sync): update Cloudflare Workers AI model catalog
2026-06-07 00:04:35 -05:00
Aiden Cline a00c4b4feb Merge pull request #2044 from anomalyco/automation/sync-models-openrouter
chore(sync): update OpenRouter model catalog
2026-06-07 00:04:04 -05:00
github-actions[bot] b32b126540 chore(sync): update Cloudflare Workers AI model catalog 2026-06-07 03:26:37 +00:00
github-actions[bot] 4f296dcf55 chore(sync): update OpenRouter model catalog 2026-06-07 03:26:36 +00:00
Nichokas 14709d66d7 feat(freemodel): add provider logo
Addresses review feedback on #2030 — provider was missing a logo.svg.
2026-06-07 01:36:32 +02:00
Nichokas 88abd70b24 refactor(freemodel): merge into one provider with per-model hosts
opencode exposes freemodel as a single provider behind one login. Move the
four GPT models out of the separate `freemodel-codex` provider and into
`freemodel`, giving each a per-model `[provider]` override
(`@ai-sdk/openai-compatible`, https://api.freemodel.dev/v1) so the Claude
models keep the provider default (`@ai-sdk/anthropic`, cc.freemodel.dev)
and the GPT models route to the OpenAI host. Removes `freemodel-codex`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:32:29 +02:00
Nichokas 84ed973170 feat(freemodel): add FreeModel.dev provider (Anthropic + OpenAI formats)
Adds two provider entries for freemodel.dev, a gateway exposing two model
sets depending on the API format:

- freemodel: Anthropic-format endpoint (cc.freemodel.dev) serving Claude
  models, via @ai-sdk/anthropic
- freemodel-codex: OpenAI-compatible endpoint (api.freemodel.dev) serving
  GPT/Codex models, via @ai-sdk/openai-compatible

Models inherit metadata via base_model and are priced at the providers'
standard rates; freemodel additionally charges cache_write at the input
rate for the OpenAI models.
2026-06-05 20:55:11 +02:00
1760 changed files with 7126 additions and 4248 deletions
@@ -0,0 +1,68 @@
name: Close stale pull requests
on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
close-stale-pull-requests:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v8
env:
REVIEWER: rekram1-node
with:
script: |
const { owner, repo } = context.repo
const now = Date.now()
const weekAgo = now - 7 * 24 * 60 * 60 * 1000
const monthAgo = now - 30 * 24 * 60 * 60 * 1000
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
per_page: 100,
})
const feedbackPulls = new Set()
for (const qualifier of ["commenter", "reviewed-by"]) {
const results = await github.paginate(
github.rest.search.issuesAndPullRequests,
{
q: `repo:${owner}/${repo} is:pr is:open ${qualifier}:${process.env.REVIEWER}`,
per_page: 100,
},
)
for (const result of results) feedbackPulls.add(result.number)
}
for (const pull of pulls) {
const updatedAt = Date.parse(pull.updated_at)
const monthStale = updatedAt < monthAgo
const feedbackStale = updatedAt < weekAgo && feedbackPulls.has(pull.number)
if (!monthStale && !feedbackStale) continue
const reason = monthStale
? "it has not been updated in 30 days"
: `it has not been updated in 7 days after feedback from @${process.env.REVIEWER}`
await github.rest.issues.createComment({
owner,
repo,
issue_number: pull.number,
body: `Closing this pull request as stale because ${reason}. Feel free to reopen it or submit a new pull request if the work is resumed.`,
})
await github.rest.pulls.update({
owner,
repo,
pull_number: pull.number,
state: "closed",
})
}
+4 -2
View File
@@ -63,7 +63,9 @@ jobs:
- name: Sync model catalogs
run: bun models:sync ${{ matrix.provider }}
env:
BASETEN_API_KEY: ${{ secrets.BASETEN_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
@@ -81,7 +83,7 @@ jobs:
LABELS: automation,model-sync,provider:${{ matrix.provider }}
TITLE: "chore(sync): update ${{ matrix.name }} model catalog"
run: |
if [ -z "$(git status --porcelain -- providers)" ]; then
if [ -z "$(git status --porcelain -- models providers)" ]; then
echo "No model catalog changes found."
exit 0
fi
@@ -90,7 +92,7 @@ jobs:
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git fetch --no-tags --depth=1 origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH" || true
git checkout -B "$BRANCH"
git add providers
git add models providers
git commit -m "$TITLE"
git push --force-with-lease origin "$BRANCH"
+3 -3
View File
@@ -10,9 +10,9 @@ knowledge = "2025-04"
open_weights = false
[limit]
context = 131_072
output = 16_384
context = 1_000_000
output = 64_000
[modalities]
input = ["text"]
input = ["text", "image"]
output = ["text"]
+18
View File
@@ -0,0 +1,18 @@
name = "Claude Fable 5"
family = "claude-fable"
release_date = "2026-06-09"
last_updated = "2026-06-09"
attachment = true
reasoning = true
temperature = false
tool_call = true
open_weights = false
knowledge = "2026-01-31"
[limit]
context = 1_000_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
+19
View File
@@ -0,0 +1,19 @@
name = "Command A Plus"
family = "command-a"
release_date = "2026-05-20"
last_updated = "2026-06-09"
attachment = true
reasoning = true
temperature = true
knowledge = "2025-04-01"
tool_call = true
open_weights = true
structured_output = true
[limit]
context = 128_000
output = 64_000
[modalities]
input = ["text", "image"]
output = ["text"]
+19
View File
@@ -0,0 +1,19 @@
name = "North Mini Code"
family = "north"
release_date = "2026-06-09"
last_updated = "2026-06-09"
attachment = false
reasoning = true
temperature = true
structured_output = true
knowledge = "2025-09-23"
tool_call = true
open_weights = true
[limit]
context = 256_000
output = 64_000
[modalities]
input = ["text"]
output = ["text"]
+2 -2
View File
@@ -1,7 +1,7 @@
name = "Gemini 2.5 Flash"
family = "gemini-flash"
release_date = "2025-03-20"
last_updated = "2025-06-05"
release_date = "2025-06-17"
last_updated = "2025-06-17"
attachment = true
reasoning = true
temperature = true
+18
View File
@@ -0,0 +1,18 @@
name = "Gemini 2.5 Pro TTS"
family = "gemini-pro"
release_date = "2025-09-30"
last_updated = "2025-12-10"
attachment = false
reasoning = false
temperature = false
tool_call = false
knowledge = "2025-01"
open_weights = false
[limit]
context = 32_768
output = 16_384
[modalities]
input = ["text"]
output = ["audio"]
+2 -2
View File
@@ -1,7 +1,7 @@
name = "Gemini 2.5 Pro"
family = "gemini-pro"
release_date = "2025-03-20"
last_updated = "2025-06-05"
release_date = "2025-06-17"
last_updated = "2025-06-17"
attachment = true
reasoning = true
temperature = true
+2 -2
View File
@@ -1,7 +1,7 @@
name = "Mistral Large 2.1"
family = "mistral-large"
release_date = "2024-11-01"
last_updated = "2024-11-04"
release_date = "2024-11-18"
last_updated = "2024-11-18"
attachment = false
reasoning = false
temperature = true
+1 -1
View File
@@ -1,5 +1,5 @@
name = "Kimi K2.5"
family = "kimi-k2.5"
family = "kimi-k2"
release_date = "2026-01"
last_updated = "2026-01"
attachment = false
+1 -1
View File
@@ -1,5 +1,5 @@
name = "Kimi K2.6"
family = "kimi-k2.6"
family = "kimi-k2"
release_date = "2026-04-21"
last_updated = "2026-04-21"
attachment = true
+23
View File
@@ -0,0 +1,23 @@
name = "Kimi K2.7 Code"
family = "kimi-k2"
release_date = "2026-06-12"
last_updated = "2026-06-12"
attachment = true
reasoning = true
temperature = false
tool_call = true
structured_output = true
knowledge = "2025-01"
open_weights = true
[limit]
context = 262_144
output = 262_144
[modalities]
input = ["text", "image", "video"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/moonshotai/Kimi-K2.7-Code"
+19
View File
@@ -0,0 +1,19 @@
name = "GPT-5.5 Instant"
release_date = "2026-05-05"
last_updated = "2026-05-28"
attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
knowledge = "2025-12-01"
open_weights = false
[limit]
context = 400_000
input = 400_000
output = 128_000
[modalities]
input = ["text", "image", "pdf"]
output = ["text"]
+17
View File
@@ -0,0 +1,17 @@
name = "Sarvam 105B"
family = "sarvam"
release_date = "2025-09-01"
last_updated = "2025-09-01"
attachment = false
reasoning = true
temperature = true
tool_call = true
open_weights = true
[limit]
context = 131_072
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
+17
View File
@@ -0,0 +1,17 @@
name = "Sarvam 30B"
family = "sarvam"
release_date = "2026-02-18"
last_updated = "2026-02-18"
attachment = false
reasoning = true
temperature = true
tool_call = true
open_weights = true
[limit]
context = 128_000
output = 128_000
[modalities]
input = ["text"]
output = ["text"]
+22
View File
@@ -0,0 +1,22 @@
name = "Step 3.7 Flash"
release_date = "2026-05-29"
last_updated = "2026-05-29"
attachment = true
reasoning = true
temperature = true
tool_call = true
knowledge = "2026-01-01"
open_weights = true
[limit]
context = 256_000
input = 256_000
output = 256_000
[modalities]
input = ["text", "image"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/stepfun-ai/Step-3.7-Flash"
+2 -1
View File
@@ -6,10 +6,11 @@ attachment = true
reasoning = false
temperature = true
tool_call = true
structured_output = true
open_weights = false
[limit]
context = 2_000_000
context = 1_000_000
output = 30_000
[modalities]
+2 -1
View File
@@ -6,10 +6,11 @@ attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = false
[limit]
context = 2_000_000
context = 1_000_000
output = 30_000
[modalities]
+1
View File
@@ -6,6 +6,7 @@ attachment = true
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = false
[limit]
@@ -0,0 +1,22 @@
name = "MiMo-V2.5-Pro-UltraSpeed"
family = "mimo"
release_date = "2026-06-08"
last_updated = "2026-06-09"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2024-12"
open_weights = true
[limit]
context = 1_048_576
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/XiaomiMiMo/MiMo-V2.5-Pro-FP4-DFlash"
+18
View File
@@ -0,0 +1,18 @@
name = "GLM-5.2"
family = "glm"
release_date = "2026-06-13"
last_updated = "2026-06-13"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = false
[limit]
context = 1_000_000
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
+2 -1
View File
@@ -18,11 +18,12 @@
"test": "bun test",
"validate": "bun ./packages/core/script/validate.ts",
"compare:migrations": "bun ./packages/core/script/compare-model-migrations.ts",
"baseten:sync": "bun ./packages/core/script/sync-models.ts baseten",
"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",
"venice:sync": "bun ./packages/core/script/sync-models.ts venice",
"vercel:generate": "bun ./packages/core/script/sync-models.ts vercel",
"wandb:generate": "bun ./packages/core/script/generate-wandb.ts",
"digitalocean:generate": "bun ./packages/core/script/generate-digitalocean.ts",
+4 -1
View File
@@ -13,7 +13,7 @@ import { z } from "zod";
import path from "node:path";
import { existsSync, readFileSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { ModelFamilyValues } from "../src/family.js";
import { inferKimiFamily, ModelFamilyValues } from "../src/family.js";
const API_ENDPOINT = "https://llm.chutes.ai/v1/models";
const MODEL_METADATA_DIR = path.join(import.meta.dirname, "..", "..", "..", "models");
@@ -261,6 +261,9 @@ function matchesFamily(target: string, family: string): boolean {
}
function inferFamily(modelId: string, modelName: string): string | undefined {
const kimiFamily = inferKimiFamily(modelId, modelName);
if (kimiFamily !== undefined) return kimiFamily;
const sortedFamilies = [...ModelFamilyValues].sort((a, b) => b.length - a.length);
// First pass: try exact substring matches
@@ -26,7 +26,7 @@
import { z } from "zod";
import path from "node:path";
import { mkdir } from "node:fs/promises";
import { ModelFamilyValues } from "../src/family.js";
import { inferKimiFamily, 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";
@@ -142,7 +142,7 @@ const PRICING_NAME_MAP: Record<string, string> = {
// DO-hosted
"qwen3-32b": "alibaba-qwen3-32b",
"minimax m2.5 (public preview)": "minimax-m2.5",
"kimi k2.5": "kimi-k2.5",
"kimi k2.5": "kimi-k2",
"nvidia nemotron 3 super 120b (public preview)": "nvidia-nemotron-3-super-120b",
"glm 5": "glm-5",
};
@@ -311,6 +311,9 @@ function formatNumber(n: number): string {
}
function inferFamily(modelId: string, modelName: string): string | undefined {
const kimiFamily = inferKimiFamily(modelId, modelName);
if (kimiFamily !== undefined) return kimiFamily;
const sorted = [...ModelFamilyValues].sort((a, b) => b.length - a.length);
const targets = [modelId.toLowerCase(), modelName.toLowerCase()];
for (const family of sorted) {
@@ -4,6 +4,8 @@ import { mkdir } from "node:fs/promises";
import path from "node:path";
import { z } from "zod";
import { inferKimiFamily } from "../src/family.js";
// Friendli API endpoint
const API_ENDPOINT = "https://api.friendli.ai/serverless/v1/models";
@@ -53,6 +55,9 @@ const familyPatterns: [RegExp, string][] = [
];
function inferFamily(modelId: string, modelName: string): string | undefined {
const kimiFamily = inferKimiFamily(modelId, modelName);
if (kimiFamily !== undefined) return kimiFamily;
for (const [pattern, family] of familyPatterns) {
if (pattern.test(modelId) || pattern.test(modelName)) {
return family;
-653
View File
@@ -1,653 +0,0 @@
#!/usr/bin/env bun
import { z } from "zod";
import path from "node:path";
import { readdir } from "node:fs/promises";
import { ModelFamilyValues } from "../src/family.js";
// Venice API endpoint
const API_ENDPOINT = "https://api.venice.ai/api/v1/models?type=text";
// Zod schemas for API response validation
const Capabilities = z
.object({
optimizedForCode: z.boolean().optional(),
quantization: z.string().optional(),
supportsAudioInput: z.boolean().optional(),
supportsFunctionCalling: z.boolean().optional(),
supportsLogProbs: z.boolean().optional(),
supportsReasoning: z.boolean().optional(),
supportsResponseSchema: z.boolean().optional(),
supportsVideoInput: z.boolean().optional(),
supportsVision: z.boolean().optional(),
supportsWebSearch: z.boolean().optional(),
})
.passthrough();
const PricingTier = z.object({ usd: z.number(), diem: z.number().optional() }).passthrough();
const ExtendedPricing = z
.object({
context_token_threshold: z.number(),
input: PricingTier,
output: PricingTier,
cache_input: PricingTier.optional(),
cache_write: PricingTier.optional(),
})
.passthrough();
const Pricing = z
.object({
input: PricingTier,
output: PricingTier,
cache_input: PricingTier.optional(),
cache_write: PricingTier.optional(),
extended: ExtendedPricing.optional(),
})
.passthrough();
const ModelSpec = z
.object({
pricing: Pricing.optional(),
availableContextTokens: z.number(),
maxCompletionTokens: z.number().optional(),
capabilities: Capabilities,
constraints: z.any().optional(),
name: z.string(),
modelSource: z.string().optional(),
offline: z.boolean().optional(),
privacy: z.string().optional(),
traits: z.array(z.string()).optional(),
})
.passthrough();
const VeniceModel = z
.object({
created: z.number(),
id: z.string(),
model_spec: ModelSpec,
object: z.string(),
owned_by: z.string(),
type: z.string(),
})
.passthrough();
const VeniceResponse = z
.object({
data: z.array(VeniceModel),
object: z.string(),
type: z.string(),
})
.passthrough();
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);
for (const family of sortedFamilies) {
if (matchesFamily(modelId, family)) {
return family;
}
}
for (const family of sortedFamilies) {
if (matchesFamily(modelName, family)) {
return family;
}
}
return undefined;
}
function buildInputModalities(capabilities: z.infer<typeof Capabilities>): string[] {
const mods: string[] = ["text"];
if (capabilities.supportsVision) mods.push("image");
if (capabilities.supportsAudioInput) mods.push("audio");
if (capabilities.supportsVideoInput) mods.push("video");
return mods;
}
function formatNumber(n: number): string {
if (n >= 1000) {
// Format with underscores for readability (e.g., 131_072)
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "_");
}
return n.toString();
}
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);
}
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;
reasoning?: 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[];
};
provider?: {
npm?: string;
api?: string;
};
}
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;
}
}
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;
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[];
};
}
function mergeModel(
apiModel: z.infer<typeof VeniceModel>,
existing: ExistingModel | null,
): MergedModel {
const spec = apiModel.model_spec;
const caps = spec.capabilities;
const contextTokens = spec.availableContextTokens;
const outputTokens = spec.maxCompletionTokens ?? Math.floor(contextTokens / 4);
const openWeights = spec.modelSource?.toLowerCase().includes("huggingface") ?? false;
const inputModalities = buildInputModalities(caps);
if (existing?.modalities?.input?.includes("pdf") && !inputModalities.includes("pdf")) {
inputModalities.push("pdf");
}
const attachment =
caps.supportsVision === true ||
caps.supportsAudioInput === true ||
caps.supportsVideoInput === true;
const merged: MergedModel = {
name: spec.name,
attachment,
reasoning: caps.supportsReasoning === true,
tool_call: caps.supportsFunctionCalling === true,
temperature: true,
release_date: timestampToDate(apiModel.created),
last_updated: getTodayDate(),
open_weights: openWeights,
limit: {
context: contextTokens,
output: outputTokens,
},
modalities: {
input: inputModalities,
output: ["text"],
},
};
// structured_output only if true
if (caps.supportsResponseSchema === true) {
merged.structured_output = true;
}
// Cost from API
if (spec.pricing) {
merged.cost = {
input: spec.pricing.input.usd,
output: spec.pricing.output.usd,
...(spec.pricing.cache_input && { cache_read: spec.pricing.cache_input.usd }),
...(spec.pricing.cache_write && { cache_write: spec.pricing.cache_write.usd }),
};
// Extended pricing maps to context_over_200k
if (spec.pricing.extended) {
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 }),
};
}
}
const inferred = inferFamily(apiModel.id, spec.name);
merged.family = inferred ?? existing?.family;
// Preserve manual fields from existing
if (existing?.knowledge) {
merged.knowledge = existing.knowledge;
}
if (existing?.interleaved !== undefined) {
merged.interleaved = existing.interleaved;
}
if (existing?.status !== undefined) {
merged.status = existing.status;
}
return merged;
}
function formatToml(model: MergedModel): string {
const lines: string[] = [];
// Basic fields
lines.push(`name = "${model.name.replace(/"/g, '\\"')}"`);
if (model.family) {
lines.push(`family = "${model.family}"`);
}
lines.push(`attachment = ${model.attachment}`);
lines.push(`reasoning = ${model.reasoning}`);
lines.push(`tool_call = ${model.tool_call}`);
if (model.structured_output !== undefined) {
lines.push(`structured_output = ${model.structured_output}`);
}
lines.push(`temperature = ${model.temperature}`);
if (model.knowledge) {
lines.push(`knowledge = "${model.knowledge}"`);
}
lines.push(`release_date = "${model.release_date}"`);
lines.push(`last_updated = "${model.last_updated}"`);
lines.push(`open_weights = ${model.open_weights}`);
if (model.status) {
lines.push(`status = "${model.status}"`);
}
// Interleaved section (if present)
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}"`);
}
}
// Cost section
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 = ${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) {
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}`);
}
}
}
// Limit section
lines.push("");
lines.push(`[limit]`);
lines.push(`context = ${formatNumber(model.limit.context)}`);
lines.push(`output = ${formatNumber(model.limit.output)}`);
// Modalities section
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";
}
interface Changes {
field: string;
oldValue: string;
newValue: string;
}
function detectChanges(
existing: ExistingModel | null,
merged: MergedModel,
): Changes[] {
if (!existing) return [];
const changes: Changes[] = [];
const compare = (field: string, oldVal: unknown, newVal: unknown) => {
const oldStr = JSON.stringify(oldVal);
const newStr = JSON.stringify(newVal);
if (oldStr !== newStr) {
changes.push({
field,
oldValue: formatValue(oldVal),
newValue: formatValue(newVal),
});
}
};
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);
};
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("cost.cache_write", existing.cost?.cache_write, merged.cost?.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);
return changes;
}
async function main() {
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const modelsDir = path.join(
import.meta.dirname,
"..",
"..",
"..",
"providers",
"venice",
"models",
);
// Check for API key from CLI argument or environment variable
let apiKey: string | null = null;
// Check CLI args for --api-key=xxx or --api-key xxx
const apiKeyArgIndex = args.findIndex((arg) => arg.startsWith("--api-key"));
if (apiKeyArgIndex !== -1) {
const arg = args[apiKeyArgIndex];
if (arg?.includes("=")) {
apiKey = arg.split("=")[1] ?? null;
} else if (args[apiKeyArgIndex + 1]) {
apiKey = args[apiKeyArgIndex + 1] ?? null;
}
}
// Fall back to environment variable
if (!apiKey) {
apiKey = process.env.VENICE_API_KEY ?? null;
}
const includeAlpha = apiKey !== null;
if (dryRun) {
console.log(
`[DRY RUN] Fetching Venice models from API${includeAlpha ? " (including alpha models)" : ""}...`,
);
} else {
console.log(
`Fetching Venice models from API${includeAlpha ? " (including alpha models)" : ""}...`,
);
}
// Fetch API data
const fetchOptions: RequestInit = {};
if (apiKey) {
fetchOptions.headers = {
Authorization: `Bearer ${apiKey}`,
};
}
const res = await fetch(API_ENDPOINT, fetchOptions);
if (!res.ok) {
console.error(`Failed to fetch API: ${res.status} ${res.statusText}`);
if (res.status === 401) {
console.error("Invalid API key. Please check your VENICE_API_KEY.");
}
process.exit(1);
}
const json = await res.json();
const parsed = VeniceResponse.safeParse(json);
if (!parsed.success) {
console.error("Invalid API response:", parsed.error.errors);
process.exit(1);
}
const apiModels = parsed.data.data;
// Get existing files
const existingFiles = new Set<string>();
try {
const files = await readdir(modelsDir);
for (const file of files) {
if (file.endsWith(".toml")) {
existingFiles.add(file);
}
}
} catch {
// Directory might not exist yet
}
console.log(`Found ${apiModels.length} models in API, ${existingFiles.size} existing files\n`);
// Track API model IDs for orphan detection
const apiModelIds = new Set<string>();
let created = 0;
let updated = 0;
let unchanged = 0;
for (const apiModel of apiModels) {
const safeId = apiModel.id.replace(/\//g, "-");
const filename = `${safeId}.toml`;
const filePath = path.join(modelsDir, filename);
apiModelIds.add(filename);
const existing = await loadExistingModel(filePath);
const merged = mergeModel(apiModel, existing);
const tomlContent = formatToml(merged);
if (existing === null) {
// New file
created++;
if (dryRun) {
console.log(`[DRY RUN] Would create: ${filename}`);
console.log(` name = "${merged.name}"`);
if (merged.family) {
console.log(` family = "${merged.family}" (inferred)`);
}
console.log("");
} else {
await Bun.write(filePath, tomlContent);
console.log(`Created: ${filename}`);
}
} else {
// Check for changes
const changes = detectChanges(existing, merged);
if (changes.length > 0) {
updated++;
if (dryRun) {
console.log(`[DRY RUN] Would update: ${filename}`);
} else {
await Bun.write(filePath, tomlContent);
console.log(`Updated: ${filename}`);
}
for (const change of changes) {
console.log(` ${change.field}: ${change.oldValue}${change.newValue}`);
}
console.log("");
} else {
unchanged++;
}
}
}
// Check for orphaned files
const orphaned: string[] = [];
for (const file of existingFiles) {
if (!apiModelIds.has(file)) {
orphaned.push(file);
console.log(`Warning: Orphaned file (not in API): ${file}`);
}
}
// Summary
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();
+4 -1
View File
@@ -3,7 +3,7 @@
import path from "node:path";
import { mkdir } from "node:fs/promises";
import { z } from "zod";
import { ModelFamilyValues } from "../src/family.js";
import { inferKimiFamily, ModelFamilyValues } from "../src/family.js";
const API_ENDPOINT = "https://trace.wandb.ai/inference/analysis/artificialanalysis/models";
@@ -176,6 +176,9 @@ function matchesFamily(target: string, family: string): boolean {
}
function inferFamily(modelId: string, modelName: string): string | undefined {
const kimiFamily = inferKimiFamily(modelId, modelName);
if (kimiFamily !== undefined) return kimiFamily;
const sortedFamilies = [...ModelFamilyValues].sort((a, b) => b.length - a.length);
for (const family of sortedFamilies) {
+11 -2
View File
@@ -26,6 +26,7 @@ export const ModelFamilyValues = [
"claude-haiku",
"claude-sonnet",
"claude-opus",
"claude-fable",
// Gemini style
"gemini",
@@ -65,8 +66,7 @@ export const ModelFamilyValues = [
// Moonshot Kimi
"kimi",
"kimi-k2.5",
"kimi-k2.6",
"kimi-k2",
"kimi-free",
"kimi-thinking",
@@ -102,6 +102,8 @@ export const ModelFamilyValues = [
"command-r",
"command-a",
"command-light",
"north",
"north-free",
// AI21 Jamba
"jamba",
@@ -420,3 +422,10 @@ export const ModelFamilyValues = [
export const ModelFamily = z.enum(ModelFamilyValues);
export type ModelFamily = z.infer<typeof ModelFamily>;
export function inferKimiFamily(...values: string[]): ModelFamily | undefined {
const target = values.join(" ").toLowerCase();
if (/kimi[^a-z0-9]*k2(?:[^a-z0-9]*\d+)?[^a-z0-9]*thinking/.test(target)) return "kimi-thinking";
if (/kimi[\s_-]*k2/.test(target)) return "kimi-k2";
return undefined;
}
+2 -2
View File
@@ -25,7 +25,7 @@ const ReasoningEffortValue = z.preprocess(
(value) => (value === "null" ? null : value),
z.union([
z.null(),
z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]),
z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max", "default"]),
]),
);
@@ -47,7 +47,7 @@ const ReasoningOption = z
type: z.literal("budget_tokens"),
min: z
.number()
.min(0, "Minimum reasoning budget cannot be negative")
.min(-1, "Minimum reasoning budget cannot be less than -1")
.optional(),
max: z
.number()
+145 -18
View File
@@ -3,12 +3,14 @@ import { lstat, mkdir, readdir, rm } from "node:fs/promises";
import { mergeDeep } from "remeda";
import { z } from "zod";
import { AuthoredModel, AuthoredModelShape } from "../schema.js";
import { AuthoredModel, AuthoredModelShape, ModelMetadata } from "../schema.js";
import { baseten } from "./providers/baseten.js";
import { cloudflareWorkersAi } from "./providers/cloudflare-workers-ai.js";
import { google } from "./providers/google.js";
import { openrouter } from "./providers/openrouter.js";
import { ovhcloud } from "./providers/ovhcloud.js";
import { vercel } from "./providers/vercel.js";
import { venice } from "./providers/venice.js";
import { xai } from "./providers/xai.js";
const ExistingModelType = AuthoredModelShape.partial()
@@ -39,14 +41,17 @@ export type ExistingModel = z.infer<typeof ExistingModelType>;
export type SyncedFullModel = Omit<z.infer<typeof AuthoredModelShape>, "id">;
export type SyncedBaseModel = Omit<z.infer<typeof SyncedBaseModel>, "id">;
export type SyncedModel = SyncedFullModel | SyncedBaseModel;
export type SyncedMetadata = Omit<z.infer<typeof ModelMetadata>, "id">;
export interface SyncProvider<SourceModel> {
id: string;
name: string;
modelsDir: string;
metadataNamespace?: string;
skipCreates?: boolean;
deleteMissing?: boolean;
preserveSymlinks?: boolean;
preserveBaseModels?: boolean;
sameModel?(current: ExistingModel, desired: SyncedModel): boolean;
missingNotice?(paths: string[]): string[];
sourceID?(model: SourceModel): string;
@@ -56,7 +61,7 @@ export interface SyncProvider<SourceModel> {
translateModel(
model: SourceModel,
context: { existing(id: string): ExistingModel | undefined },
): { id: string; model: SyncedModel } | undefined;
): { id: string; model: SyncedModel; metadata?: { id: string; model: SyncedMetadata } } | undefined;
}
export interface SyncResult {
@@ -72,25 +77,29 @@ export interface SyncResult {
}
export const providers: {
baseten: SyncProvider<any>;
"cloudflare-workers-ai": SyncProvider<any>;
google: SyncProvider<any>;
openrouter: SyncProvider<any>;
ovhcloud: SyncProvider<any>;
vercel: SyncProvider<any>;
venice: SyncProvider<any>;
xai: SyncProvider<any>;
} = {
baseten,
"cloudflare-workers-ai": cloudflareWorkersAi,
google,
openrouter,
ovhcloud,
vercel,
venice,
xai,
};
export const groups = {
aggregators: ["openrouter", "vercel"],
cloudflare: ["cloudflare-workers-ai"],
direct: ["google", "ovhcloud", "xai"],
direct: ["baseten", "google", "ovhcloud", "venice", "xai"],
} as const;
type ProviderID = keyof typeof providers;
@@ -110,9 +119,12 @@ export async function syncProvider<SourceModel>(
): Promise<SyncResult> {
console.log(`\nSyncing ${provider.name}...`);
const { models: existing, brokenSymlinks } = await readExisting(provider.modelsDir);
const existingState = await readExisting(provider.modelsDir);
const { models: existing, brokenSymlinks } = existingState;
let { modelMetadata } = existingState;
const sourceModels = provider.parseModels(await provider.fetchModels());
const desired = new Map<string, { model: z.infer<typeof SyncedAuthoredModel>; content: string }>();
const desiredMetadata = new Map<string, { model: z.infer<typeof ModelMetadata>; content: string }>();
const skippedRemote: string[] = [];
for (const sourceModel of sourceModels) {
@@ -122,7 +134,7 @@ export async function syncProvider<SourceModel>(
},
});
if (translated === undefined) {
if (provider.skipCreates) skippedRemote.push(provider.sourceID?.(sourceModel) ?? "unknown");
if (provider.sourceID !== undefined) skippedRemote.push(provider.sourceID(sourceModel));
continue;
}
@@ -136,9 +148,46 @@ export async function syncProvider<SourceModel>(
throw new Error(`Duplicate synced model path: ${provider.id}/${relativePath}`);
}
if (translated.metadata !== undefined) {
const parsedMetadata = ModelMetadata.safeParse({
id: translated.metadata.id,
...stripUndefined(translated.metadata.model),
});
if (!parsedMetadata.success) {
parsedMetadata.error.cause = { provider: provider.id, metadata: translated.metadata.id };
throw parsedMetadata.error;
}
const metadataPath = `${translated.metadata.id}.toml`;
if (desiredMetadata.has(metadataPath)) throw new Error(`Duplicate synced metadata path: ${metadataPath}`);
desiredMetadata.set(metadataPath, {
model: parsedMetadata.data,
content: formatMetadataToml(parsedMetadata.data),
});
}
const translatedModel = provider.preserveBaseModels === false
? translated.model
: preserveBaseModel(translated.model, existing.get(relativePath)?.authored);
const translatedBase = "base_model" in translatedModel ? translatedModel.base_model : undefined;
let resolvedReasoning: boolean | undefined;
if (translatedBase !== undefined) {
if (translated.metadata?.id === translatedBase) {
resolvedReasoning = translated.metadata.model.reasoning;
} else {
modelMetadata ??= await readModelMetadata(provider.modelsDir);
const canonicalReasoning = modelMetadata[translatedBase]?.reasoning;
resolvedReasoning = typeof canonicalReasoning === "boolean" ? canonicalReasoning : undefined;
}
} else {
resolvedReasoning = existing.get(relativePath)?.toml.reasoning;
}
const parsed = SyncedAuthoredModel.safeParse(stripUndefined({
id: translated.id,
...preserveBaseModel(translated.model, existing.get(relativePath)?.authored),
...preserveReasoningOptions(
translatedModel,
existing.get(relativePath)?.authored,
resolvedReasoning,
),
}));
if (!parsed.success) {
parsed.error.cause = { provider: provider.id, path: relativePath };
@@ -154,6 +203,48 @@ export async function syncProvider<SourceModel>(
const files: SyncResult["files"] = [];
let unchanged = 0;
const metadataDir = modelMetadataDir(provider.modelsDir);
for (const [relativePath, file] of desiredMetadata) {
const filePath = path.join(metadataDir, relativePath);
const currentFile = Bun.file(filePath);
const current = await currentFile.exists()
? ModelMetadata.safeParse({
id: relativePath.slice(0, -5),
...Bun.TOML.parse(await currentFile.text()) as Record<string, unknown>,
})
: undefined;
if (current?.success && stable(current.data) === stable(file.model)) continue;
files.push({ status: current === undefined ? "created" : "updated", path: filePath });
if (options.dryRun) {
console.log(`Would ${current === undefined ? "create" : "update"} metadata ${relativePath}`);
} else {
await mkdir(path.dirname(filePath), { recursive: true });
await Bun.write(filePath, file.content);
}
}
if (provider.metadataNamespace !== undefined) {
if (!/^[a-z0-9-]+$/.test(provider.metadataNamespace)) {
throw new Error(`Invalid metadata namespace: ${provider.metadataNamespace}`);
}
const namespaceDir = path.join(metadataDir, provider.metadataNamespace);
for (const { file } of await tomlFiles(namespaceDir)) {
const relativePath = path.join(provider.metadataNamespace, file);
if (desiredMetadata.has(relativePath) || provider.deleteMissing === false) continue;
if (options.newOnly) {
console.log(`Skipping metadata removal in new-only mode: ${relativePath}`);
continue;
}
const filePath = path.join(metadataDir, relativePath);
files.push({ status: "deleted", path: filePath });
if (options.dryRun) {
console.log(`Would remove metadata ${relativePath}`);
} else {
await rm(filePath, { force: true });
}
}
}
for (const [relativePath, file] of desired) {
const filePath = path.join(provider.modelsDir, relativePath);
const current = existing.get(relativePath);
@@ -242,6 +333,22 @@ export function preserveBaseModel(model: SyncedModel, existing: ExistingModel |
};
}
export function preserveReasoningOptions(
model: SyncedModel,
existing: ExistingModel | undefined,
resolvedReasoning: boolean | undefined = existing?.reasoning,
): SyncedModel {
if ((model.reasoning ?? resolvedReasoning) === false) {
const { reasoning_options: _reasoningOptions, ...withoutReasoningOptions } = model;
return withoutReasoningOptions as SyncedModel;
}
if (model.reasoning_options !== undefined || existing?.reasoning_options === undefined) return model;
return {
...model,
reasoning_options: existing.reasoning_options,
};
}
export async function syncTargets(target: string, options: SyncOptions = {}) {
const ids = target in groups
? groups[target as keyof typeof groups]
@@ -307,7 +414,7 @@ async function readExisting(modelsDir: string) {
existing.set(file, { authored, toml, symlink });
}
return { models: existing, brokenSymlinks };
return { models: existing, brokenSymlinks, modelMetadata };
}
async function isSymlink(filePath: string) {
@@ -320,8 +427,7 @@ async function isSymlink(filePath: string) {
}
async function readModelMetadata(modelsDir: string) {
const root = path.dirname(path.dirname(path.dirname(modelsDir)));
const metadataDir = path.join(root, "models");
const metadataDir = modelMetadataDir(modelsDir);
const result: Record<string, Record<string, unknown>> = {};
for await (const modelPath of new Bun.Glob("**/*.toml").scan({
@@ -339,6 +445,10 @@ async function readModelMetadata(modelsDir: string) {
return result;
}
function modelMetadataDir(modelsDir: string) {
return path.join(path.dirname(path.dirname(path.dirname(modelsDir))), "models");
}
function resolveBaseModel(
authored: ExistingModel,
modelMetadata: Record<string, Record<string, unknown>>,
@@ -585,15 +695,19 @@ function formatToml(model: z.infer<typeof SyncedAuthoredModel>) {
if (model.open_weights !== undefined) lines.push(`open_weights = ${model.open_weights}`);
if (model.status !== undefined) lines.push(`status = ${quote(model.status)}`);
for (const option of model.reasoning_options ?? []) {
lines.push("", "[[reasoning_options]]");
lines.push(`type = ${quote(option.type)}`);
if (option.type === "effort") {
lines.push(`values = [${option.values.map(formatReasoningValue).join(", ")}]`);
}
if (option.type === "budget_tokens") {
if (option.min !== undefined) lines.push(`min = ${formatInteger(option.min)}`);
if (option.max !== undefined) lines.push(`max = ${formatInteger(option.max)}`);
if (model.reasoning_options?.length === 0) {
lines.push("reasoning_options = []");
} else {
for (const option of model.reasoning_options ?? []) {
lines.push("", "[[reasoning_options]]");
lines.push(`type = ${quote(option.type)}`);
if (option.type === "effort") {
lines.push(`values = [${option.values.map(formatReasoningValue).join(", ")}]`);
}
if (option.type === "budget_tokens") {
if (option.min !== undefined) lines.push(`min = ${formatInteger(option.min)}`);
if (option.max !== undefined) lines.push(`max = ${formatInteger(option.max)}`);
}
}
}
@@ -658,6 +772,19 @@ function formatToml(model: z.infer<typeof SyncedAuthoredModel>) {
return `${lines.join("\n")}\n`;
}
function formatMetadataToml(model: z.infer<typeof ModelMetadata>) {
const content = formatToml(model as unknown as z.infer<typeof SyncedAuthoredModel>).trimEnd();
const lines = [content];
for (const weight of model.weights ?? []) {
lines.push("", "[[weights]]");
if (weight.label !== undefined) lines.push(`label = ${quote(weight.label)}`);
lines.push(`url = ${quote(weight.url)}`);
if (weight.format !== undefined) lines.push(`format = ${quote(weight.format)}`);
if (weight.quantization !== undefined) lines.push(`quantization = ${quote(weight.quantization)}`);
}
return `${lines.join("\n")}\n`;
}
export async function main(args = process.argv.slice(2)) {
if (args.includes("--list-providers")) {
console.log(JSON.stringify(syncProviderMatrix()));
+188
View File
@@ -0,0 +1,188 @@
import { z } from "zod";
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
import { factorBaseModel, resolveCanonicalBaseModel } from "./openrouter.js";
const API_ENDPOINT = "https://inference.baseten.co/v1/models";
const Price = z.union([z.string(), z.number()]);
export const BasetenModel = z.object({
id: z.string().min(1),
name: z.string().min(1),
context_length: z.number().int().positive(),
max_completion_tokens: z.number().int().positive(),
input_modalities: z.array(z.string()),
output_modalities: z.array(z.string()),
pricing: z.object({
prompt: Price,
completion: Price,
}).passthrough(),
supported_features: z.array(z.string()),
supported_sampling_parameters: z.array(z.string()),
}).passthrough();
export const BasetenResponse = z.object({
data: z.array(BasetenModel),
}).passthrough();
export type BasetenModel = z.infer<typeof BasetenModel>;
export const baseten = {
id: "baseten",
name: "Baseten",
modelsDir: "providers/baseten/models",
deleteMissing: false,
sourceID(model) {
return model.id;
},
skippedNotice(ids) {
if (ids.length === 0) return [];
return [
`${ids.length} Baseten models were not created because their slugs could not be mapped exactly to provider-agnostic metadata.`,
`Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`,
];
},
missingNotice(paths) {
if (paths.length === 0) return [];
return [
`${paths.length} local Baseten models were absent from the catalog and were retained for manual lifecycle review.`,
`Retained local paths: ${paths.map((item) => `\`${item}\``).join(", ")}`,
];
},
async fetchModels() {
const key = process.env.BASETEN_API_KEY;
if (key === undefined) throw new Error("Baseten sync requires BASETEN_API_KEY");
return fetchBasetenModels(key);
},
parseModels(raw) {
return BasetenResponse.parse(raw).data;
},
translateModel(model, context) {
const existing = context.existing(model.id);
const baseModel = existing === undefined
? resolveBasetenBaseModel(model.id)
: existing.base_model;
if (existing === undefined && baseModel === undefined) return undefined;
if (
existing === undefined
&& (price(model.pricing.prompt) === undefined || price(model.pricing.completion) === undefined)
) return undefined;
return {
id: model.id,
model: buildBasetenModel(model, existing, baseModel),
};
},
} satisfies SyncProvider<BasetenModel>;
export async function fetchBasetenModels(
key: string,
fetcher: typeof fetch = fetch,
) {
const response = await fetcher(API_ENDPOINT, {
headers: { Authorization: `Api-Key ${key}` },
});
if (!response.ok) {
throw new Error(`Baseten models request failed: ${response.status} ${response.statusText}`);
}
return BasetenResponse.parse(await response.json());
}
function price(value: string | number | undefined) {
if (value === undefined || value === "") return undefined;
const number = Number(value);
return Number.isFinite(number) && number >= 0
? Math.round(number * 1_000_000_000_000) / 1_000_000
: undefined;
}
export function buildBasetenModel(
model: BasetenModel,
existing: ExistingModel | undefined,
baseModel = existing === undefined ? resolveBasetenBaseModel(model.id) : existing.base_model,
): SyncedModel {
const features = new Set(model.supported_features);
const samplingParameters = new Set(model.supported_sampling_parameters);
const input = modalities(model.input_modalities, existing?.modalities?.input ?? ["text"]);
const output = modalities(model.output_modalities, existing?.modalities?.output ?? ["text"]);
const inputCost = price(model.pricing.prompt);
const outputCost = price(model.pricing.completion);
const cost = inputCost !== undefined && outputCost !== undefined
? {
input: inputCost,
output: outputCost,
reasoning: existing?.cost?.reasoning,
cache_read: existing?.cost?.cache_read,
cache_write: existing?.cost?.cache_write,
tiers: existing?.cost?.tiers,
}
: existing?.cost;
const limit = {
context: model.context_length,
input: existing?.limit?.input,
output: model.max_completion_tokens,
};
const values: Partial<SyncedFullModel> = {
name: model.name ?? existing?.name,
family: existing?.family,
release_date: existing?.release_date,
last_updated: existing?.last_updated,
attachment: input.some((value) => value !== "text"),
reasoning: features.has("reasoning") || existing?.reasoning,
reasoning_options: existing?.reasoning_options,
temperature: samplingParameters.has("temperature"),
tool_call: features.has("tools") || existing?.tool_call,
structured_output: features.has("structured_outputs") || existing?.structured_output,
knowledge: existing?.knowledge,
open_weights: existing?.open_weights,
status: existing?.status,
interleaved: existing?.interleaved,
cost,
limit,
modalities: { input, output },
};
if (baseModel !== undefined) {
if (limit.context === undefined || limit.output === undefined) {
throw new Error(`Baseten model ${model.id} has incomplete token limits required for sync`);
}
return factorBaseModel(baseModel, values, limit, existing?.base_model_omit);
}
const required = z.object({
name: z.string(),
release_date: z.string(),
last_updated: z.string(),
open_weights: z.boolean(),
cost: z.object({ input: z.number(), output: z.number() }),
}).safeParse(values);
if (!required.success) {
throw new Error(`Baseten model ${model.id} has incomplete local metadata required for sync`);
}
return values as SyncedFullModel;
}
export function resolveBasetenBaseModel(id: string) {
const [prefix, ...parts] = id.split("/");
if (prefix === undefined || parts.length === 0) return undefined;
const canonicalPrefix = {
"deepseek-ai": "deepseek",
MiniMaxAI: "minimax",
moonshotai: "moonshotai",
nvidia: "nvidia",
"zai-org": "zai",
}[prefix];
if (canonicalPrefix === undefined) return resolveCanonicalBaseModel(id);
return resolveCanonicalBaseModel(`${canonicalPrefix}/${parts.join("/").toLowerCase()}`);
}
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())
.filter((value): value is Modality => allowed.has(value as Modality));
return [...new Set(result.length > 0 ? result : fallback)];
}
@@ -96,7 +96,7 @@ export const cloudflareWorkersAi = {
},
} satisfies SyncProvider<CloudflareModel>;
function buildWorkersAiModel(
export function buildWorkersAiModel(
model: z.infer<typeof OpenRouterModel>,
existing: ExistingModel | undefined,
): SyncedModel {
@@ -108,11 +108,14 @@ function buildWorkersAiModel(
max_completion_tokens: existing?.limit?.output ?? model.top_provider.max_completion_tokens,
},
};
const synced = buildOpenRouterModel(
source,
existing,
existing?.base_model ?? resolveCloudflareBaseModel(model),
);
const synced = {
...buildOpenRouterModel(
source,
existing,
existing?.base_model ?? resolveCloudflareBaseModel(model),
),
reasoning_options: existing?.reasoning_options,
};
if ("base_model" in synced) return synced;
return {
...synced,
+9 -6
View File
@@ -1,6 +1,7 @@
import { z } from "zod";
import type { ExistingModel, SyncProvider, SyncedModel } from "../index.js";
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
import { factorBaseModel } from "./openrouter.js";
const API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models";
@@ -81,12 +82,12 @@ export const google = {
return {
id,
model: buildModel(model, existing),
model: buildGoogleModel(model, existing),
};
},
} satisfies SyncProvider<GoogleModel>;
function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
export function buildGoogleModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
const name = existing.name;
const releaseDate = existing.release_date;
const lastUpdated = existing.last_updated;
@@ -111,9 +112,7 @@ function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
throw new Error(`Google model ${model.name} has incomplete local TOML metadata required for sync`);
}
return {
base_model: existing.base_model,
base_model_omit: existing.base_model_omit,
const synced: SyncedFullModel = {
name: model.displayName ?? name,
family: existing.family,
release_date: releaseDate,
@@ -138,4 +137,8 @@ function buildModel(model: GoogleModel, existing: ExistingModel): SyncedModel {
},
modalities,
};
return existing.base_model === undefined
? synced
: factorBaseModel(existing.base_model, synced, synced.limit, existing.base_model_omit);
}
+12 -1
View File
@@ -2,7 +2,7 @@ import { z } from "zod";
import { readFileSync, readdirSync } from "node:fs";
import path from "node:path";
import { ModelFamilyValues } from "../../family.js";
import { inferKimiFamily, ModelFamilyValues } from "../../family.js";
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
const API_ENDPOINT = "https://openrouter.ai/api/v1/models";
@@ -113,6 +113,9 @@ function modalities(values: string[], fallback: Modality[]): Modality[] {
}
function inferFamily(model: OpenRouterModel, name: string) {
const kimiFamily = inferKimiFamily(model.id, name);
if (kimiFamily !== undefined) return kimiFamily;
const target = `${model.id} ${name}`.toLowerCase();
return [...ModelFamilyValues]
.sort((a, b) => b.length - a.length)
@@ -286,6 +289,14 @@ function baseModelOverrides(
function inheritedOverride(value: unknown, inherited: unknown): unknown {
if (value === undefined) return undefined;
if (sameInheritedValue(value, inherited)) return undefined;
if (isPlainObject(value) && isPlainObject(inherited)) {
const overrides = Object.fromEntries(
Object.entries(value)
.map(([key, item]) => [key, inheritedOverride(item, inherited[key])])
.filter(([, item]) => item !== undefined),
);
return Object.keys(overrides).length > 0 ? overrides : undefined;
}
return stripUndefined(value);
}
@@ -121,6 +121,7 @@ export function buildOvhcloudModel(
last_updated: lastUpdated,
attachment,
reasoning,
reasoning_options: reasoning ? existing?.reasoning_options : undefined,
temperature: temperature || undefined,
tool_call: toolCall,
structured_output: structuredOutput || undefined,
+241
View File
@@ -0,0 +1,241 @@
import { readdirSync } from "node:fs";
import path from "node:path";
import { z } from "zod";
import { inferKimiFamily, ModelFamilyValues } from "../../family.js";
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
import { factorBaseModel } from "./openrouter.js";
const API_ENDPOINT = "https://api.venice.ai/api/v1/models?type=text";
const MODELS_DIR = path.join(import.meta.dirname, "..", "..", "..", "..", "..", "models");
const Capabilities = z.object({
supportsAudioInput: z.boolean().optional(),
supportsE2EE: z.boolean().optional(),
supportsFunctionCalling: z.boolean().optional(),
supportsReasoning: z.boolean().optional(),
supportsReasoningEffort: z.boolean().optional(),
reasoningEffortOptions: z.array(z.string()).optional(),
supportsResponseSchema: z.boolean().optional(),
supportsVideoInput: z.boolean().optional(),
supportsVision: z.boolean().optional(),
}).passthrough();
const PricingTier = z.object({
usd: z.number().nonnegative(),
}).passthrough();
const ExtendedPricing = z.object({
context_token_threshold: z.number().int().nonnegative(),
input: PricingTier,
output: PricingTier,
cache_input: PricingTier.optional(),
cache_write: PricingTier.optional(),
}).passthrough();
const Pricing = z.object({
input: PricingTier,
output: PricingTier,
cache_input: PricingTier.optional(),
cache_write: PricingTier.optional(),
extended: ExtendedPricing.optional(),
}).passthrough();
const ModelSpec = z.object({
pricing: Pricing.optional(),
availableContextTokens: z.number().int().nonnegative(),
maxCompletionTokens: z.number().int().nonnegative().optional(),
capabilities: Capabilities,
name: z.string().min(1),
modelSource: z.string().optional(),
}).passthrough();
export const VeniceModel = z.object({
created: z.number(),
id: z.string().min(1),
model_spec: ModelSpec,
}).passthrough();
export const VeniceResponse = z.object({
data: z.array(VeniceModel),
}).passthrough();
export type VeniceModel = z.infer<typeof VeniceModel>;
type ReasoningEffort = "default" | "max" | "low" | "high" | "none" | "medium" | "minimal" | "xhigh";
interface MetadataEntry {
id: string;
filename: string;
normalizedFull: string;
normalizedFilename: string;
}
let metadataEntries: MetadataEntry[] | undefined;
const BASE_MODEL_ALIASES: Record<string, string> = {
"claude-opus-4-6-fast": "anthropic/claude-opus-4-6",
"claude-opus-4-7-fast": "anthropic/claude-opus-4-7",
"claude-opus-4-8-fast": "anthropic/claude-opus-4-8",
};
export const venice = {
id: "venice",
name: "Venice",
modelsDir: "providers/venice/models",
preserveBaseModels: false,
async fetchModels() {
const headers = process.env.VENICE_API_KEY
? { Authorization: `Bearer ${process.env.VENICE_API_KEY}` }
: undefined;
const response = await fetch(API_ENDPOINT, { headers });
if (!response.ok) {
throw new Error(`Venice models request failed: ${response.status} ${response.statusText}`);
}
return response.json();
},
parseModels(raw) {
return VeniceResponse.parse(raw).data;
},
translateModel(model, context) {
if (model.model_spec.capabilities.supportsE2EE === true) return undefined;
const id = model.id.replaceAll("/", "-");
const existing = context.existing(id);
const existingBase = existing?.base_model?.startsWith("venice/") === false ? existing.base_model : undefined;
const resolvedBase = existingBase ?? resolveVeniceBaseModel(model.id, model.model_spec.name);
return {
id,
model: buildVeniceModel(model, existing, resolvedBase ?? null),
};
},
} satisfies SyncProvider<VeniceModel>;
export function buildVeniceModel(
model: VeniceModel,
existing: ExistingModel | undefined,
baseModel: string | null | undefined = existing?.base_model ?? resolveVeniceBaseModel(model.id, model.model_spec.name),
today = new Date().toISOString().slice(0, 10),
): SyncedModel {
const spec = model.model_spec;
const capabilities = spec.capabilities;
const input = [
"text" as const,
...(capabilities.supportsVision ? ["image" as const] : []),
...(capabilities.supportsAudioInput ? ["audio" as const] : []),
...(capabilities.supportsVideoInput ? ["video" as const] : []),
...(existing?.modalities?.input.includes("pdf") ? ["pdf" as const] : []),
];
const limit = {
context: spec.availableContextTokens,
input: existing?.limit?.input,
output: spec.maxCompletionTokens ?? Math.floor(spec.availableContextTokens / 4),
};
const reasoningEfforts = capabilities.reasoningEffortOptions?.filter(isReasoningEffort);
const reasoningOptions = reasoningEfforts?.length
? [{ type: "effort" as const, values: reasoningEfforts }]
: [];
const cost = spec.pricing === undefined
? existing?.cost
: {
input: spec.pricing.input.usd,
output: spec.pricing.output.usd,
reasoning: existing?.cost?.reasoning,
cache_read: spec.pricing.cache_input?.usd,
cache_write: spec.pricing.cache_write?.usd,
input_audio: existing?.cost?.input_audio,
output_audio: existing?.cost?.output_audio,
tiers: spec.pricing.extended === undefined
? existing?.cost?.tiers
: [{
tier: { type: "context" as const, size: spec.pricing.extended.context_token_threshold },
input: spec.pricing.extended.input.usd,
output: spec.pricing.extended.output.usd,
cache_read: spec.pricing.extended.cache_input?.usd,
cache_write: spec.pricing.extended.cache_write?.usd,
}],
};
const authoritative = {
name: spec.name,
attachment: input.some((value) => value !== "text"),
reasoning: capabilities.supportsReasoning === true,
reasoning_options: reasoningOptions,
tool_call: capabilities.supportsFunctionCalling === true,
structured_output: capabilities.supportsResponseSchema === true ? true : undefined,
temperature: undefined,
cost,
limit,
modalities: { input: [...new Set(input)], output: ["text" as const] },
};
const releaseDate = new Date(model.created * 1000).toISOString().slice(0, 10);
const values: SyncedFullModel = {
...authoritative,
family: baseModel == null ? inferFamily(model.id, spec.name) ?? existing?.family : existing?.family,
release_date: releaseDate,
last_updated: existing?.last_updated ?? today,
knowledge: existing?.knowledge,
open_weights: spec.modelSource?.toLowerCase().includes("huggingface")
?? existing?.open_weights
?? false,
status: existing?.status,
interleaved: existing?.interleaved,
};
return baseModel == null
? values
: factorBaseModel(baseModel, values, limit, existing?.base_model_omit);
}
export function resolveVeniceBaseModel(id: string, name: string) {
const alias = BASE_MODEL_ALIASES[id];
if (alias !== undefined) return alias;
const entries = getMetadataEntries();
const normalizedID = normalize(id);
const normalizedName = normalize(name);
const ranked = [
entries.filter((entry) => entry.normalizedFull === normalizedID),
entries.filter((entry) => entry.normalizedFilename === normalizedID),
entries.filter((entry) => entry.normalizedFilename === normalizedName),
];
return ranked.find((matches) => matches.length === 1)?.[0]?.id;
}
function getMetadataEntries() {
if (metadataEntries !== undefined) return metadataEntries;
metadataEntries = [];
for (const provider of readdirSync(MODELS_DIR, { withFileTypes: true })) {
if (!provider.isDirectory()) continue;
for (const file of readdirSync(path.join(MODELS_DIR, provider.name), { withFileTypes: true })) {
if (!file.isFile() || !file.name.endsWith(".toml")) continue;
const filename = file.name.slice(0, -5);
metadataEntries.push({
id: `${provider.name}/${filename}`,
filename,
normalizedFull: normalize(`${provider.name}/${filename}`),
normalizedFilename: normalize(filename),
});
}
}
return metadataEntries;
}
function normalize(value: string) {
return value.toLowerCase().replaceAll(/[^a-z0-9]/g, "");
}
function isReasoningEffort(value: string): value is ReasoningEffort {
return ["default", "max", "low", "high", "none", "medium", "minimal", "xhigh"].includes(value);
}
function inferFamily(id: string, name: string) {
const kimiFamily = inferKimiFamily(id, name);
if (kimiFamily !== undefined) return kimiFamily;
const target = `${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);
});
}
+15 -10
View File
@@ -1,6 +1,6 @@
import { z } from "zod";
import { ModelFamilyValues } from "../../family.js";
import { inferKimiFamily, ModelFamilyValues } from "../../family.js";
import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js";
import { factorBaseModel, resolveCanonicalBaseModel } from "./openrouter.js";
@@ -47,11 +47,7 @@ export const vercel = {
id: "vercel",
name: "Vercel AI Gateway",
modelsDir: "providers/vercel/models",
deleteMissing: false,
preserveSymlinks: true,
missingNotice(paths) {
return paths.map((model) => `Vercel model is no longer returned by the API: ${model}`);
},
async fetchModels() {
const response = await fetch(API_ENDPOINT);
if (!response.ok) {
@@ -63,9 +59,6 @@ export const vercel = {
return VercelResponse.parse(raw).data;
},
translateModel(model, context) {
if (model.type === "image" || model.type === "video" || model.type === "reranking") {
return undefined;
}
return {
id: model.id,
model: buildVercelModel(model, context.existing(model.id)),
@@ -101,7 +94,9 @@ export function buildVercelModel(model: VercelModel, existing: ExistingModel | u
reasoning: existing?.reasoning ?? tags.has("reasoning"),
reasoning_options: existing?.reasoning_options,
temperature: true,
tool_call: existing?.tool_call ?? tags.has("tool-use"),
tool_call: model.type === "language"
? existing?.tool_call ?? tags.has("tool-use")
: tags.has("tool-use"),
structured_output: existing?.structured_output,
knowledge: existing?.knowledge,
open_weights: existing?.open_weights ?? false,
@@ -114,7 +109,13 @@ export function buildVercelModel(model: VercelModel, existing: ExistingModel | u
modalities: {
input: ["text", tags.has("vision") ? "image" : undefined, tags.has("file-input") ? "pdf" : undefined]
.filter((value): value is "text" | "image" | "pdf" => value !== undefined),
output: tags.has("image-generation") ? ["text", "image"] : ["text"],
output: model.type === "image"
? ["image"]
: model.type === "video"
? ["video"]
: tags.has("image-generation")
? ["text", "image"]
: ["text"],
},
};
@@ -152,6 +153,9 @@ function buildCost(pricing: VercelModel["pricing"], existing?: ExistingModel["co
}
function inferFamily(modelID: string, name: string) {
const kimiFamily = inferKimiFamily(modelID, name);
if (kimiFamily !== undefined) return kimiFamily;
const targets = [modelID, name].map((value) => value.toLowerCase());
const families = [...ModelFamilyValues].sort((a, b) => b.length - a.length);
return families.find((family) => targets.some((target) => target.includes(family.toLowerCase())))
@@ -175,6 +179,7 @@ function sameVercelModel(current: ExistingModel, desired: SyncedModel) {
[current.family, desiredModel.family],
[current.attachment, desiredModel.attachment],
[current.reasoning, desiredModel.reasoning],
[current.reasoning_options, desiredModel.reasoning_options],
[current.tool_call, desiredModel.tool_call],
[current.structured_output, desiredModel.structured_output],
[current.open_weights, desiredModel.open_weights],
+4 -3
View File
@@ -29,7 +29,7 @@ const XAIAPIKey = z.object({
acls: z.array(z.string()),
}).passthrough();
type XAIModel = z.infer<typeof XAIModel>;
export type XAIModel = z.infer<typeof XAIModel>;
export const xai = {
id: "xai",
@@ -87,7 +87,7 @@ export const xai = {
return {
id: model.id,
model: buildModel(model, existing),
model: buildXAIModel(model, existing),
};
},
} satisfies SyncProvider<XAIModel>;
@@ -159,7 +159,7 @@ function cost(model: XAIModel, existing: ExistingModel) {
};
}
function buildModel(model: XAIModel, existing: ExistingModel): SyncedModel {
export function buildXAIModel(model: XAIModel, existing: ExistingModel): SyncedModel {
const name = existing.name;
const attachment = existing.attachment;
const reasoning = existing.reasoning;
@@ -195,6 +195,7 @@ function buildModel(model: XAIModel, existing: ExistingModel): SyncedModel {
last_updated: model.canonical_id === undefined ? created : lastUpdated!,
attachment: input.some((value) => value !== "text"),
reasoning,
reasoning_options: existing.reasoning_options,
temperature: existing.temperature,
tool_call: toolCall,
structured_output: existing.structured_output,
+150
View File
@@ -0,0 +1,150 @@
import { afterEach, expect, test } from "bun:test";
import path from "node:path";
import { mkdir, mkdtemp } from "node:fs/promises";
import os from "node:os";
import { syncProvider } from "../src/sync/index.js";
import {
BasetenResponse,
baseten,
buildBasetenModel,
fetchBasetenModels,
type BasetenModel,
} from "../src/sync/providers/baseten.js";
const catalogModel: BasetenModel = {
id: "zai-org/GLM-5.1",
name: "GLM 5.1",
context_length: 128_000,
max_completion_tokens: 32_000,
input_modalities: ["text"],
output_modalities: ["text"],
pricing: {
prompt: "0.00000012",
completion: "0.0000005",
},
supported_features: ["reasoning", "reasoning_effort", "tools", "structured_outputs"],
supported_sampling_parameters: ["temperature", "top_p"],
};
const newCatalogModel: BasetenModel = {
...catalogModel,
};
afterEach(() => {
baseten.modelsDir = "providers/baseten/models";
baseten.fetchModels = async () => {
const key = process.env.BASETEN_API_KEY;
if (key === undefined) throw new Error("Baseten sync requires BASETEN_API_KEY");
return fetchBasetenModels(key);
};
});
test("Baseten maps authoritative fields and preserves curated metadata", () => {
const synced = buildBasetenModel(catalogModel, {
name: "Old name",
release_date: "2025-08-05",
last_updated: "2025-09-01",
attachment: false,
reasoning: true,
reasoning_options: [{ type: "effort", values: ["low", "high"] }],
tool_call: true,
open_weights: true,
status: "deprecated",
interleaved: { field: "reasoning_content" },
base_model: "zhipuai/glm-5.1",
base_model_omit: ["limit.input"],
cost: { input: 0.1, output: 0.4, cache_write: 0.2 },
limit: { context: 64_000, output: 16_000 },
modalities: { input: ["text"], output: ["text"] },
});
expect(synced).toMatchObject({
base_model: "zhipuai/glm-5.1",
base_model_omit: ["limit.input"],
reasoning_options: [{ type: "effort", values: ["low", "high"] }],
status: "deprecated",
interleaved: { field: "reasoning_content" },
cost: { input: 0.12, output: 0.5, cache_write: 0.2 },
limit: { context: 128_000, output: 32_000 },
});
});
test("Baseten preserves curated reasoning when an opt-in capability is omitted", () => {
const synced = buildBasetenModel({
...catalogModel,
supported_features: ["tools", "structured_outputs"],
}, {
name: "GLM 5.1",
release_date: "2026-05-20",
last_updated: "2026-05-20",
attachment: false,
reasoning: true,
reasoning_options: [{ type: "toggle" }],
tool_call: true,
open_weights: true,
cost: { input: 1, output: 4 },
limit: { context: 100_000, output: 50_000 },
modalities: { input: ["text"], output: ["text"] },
});
expect(synced).toMatchObject({
reasoning: true,
reasoning_options: [{ type: "toggle" }],
});
});
test("Baseten sync adds exact base models, retains missing entries, and is idempotent", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "models-dev-baseten-"));
const modelsDir = path.join(root, "providers", "baseten", "models");
const metadataDir = path.join(root, "models", "zhipuai");
await mkdir(path.join(modelsDir, "stale"), { recursive: true });
await mkdir(metadataDir, { recursive: true });
await Bun.write(
path.join(metadataDir, "glm-5.1.toml"),
Bun.file(path.join(import.meta.dirname, "../../../models/zhipuai/glm-5.1.toml")),
);
await Bun.write(path.join(modelsDir, "stale", "model.toml"), [
'name = "Retained"',
'release_date = "2025-01-01"',
'last_updated = "2025-01-01"',
"attachment = false",
"reasoning = false",
"tool_call = false",
"open_weights = false",
"[cost]",
"input = 1",
"output = 1",
"[limit]",
"context = 1000",
"output = 100",
"[modalities]",
'input = ["text"]',
'output = ["text"]',
"",
].join("\n"));
baseten.modelsDir = modelsDir;
baseten.fetchModels = async () => ({ data: [newCatalogModel] });
const first = await syncProvider(baseten);
const second = await syncProvider(baseten);
expect(first.created).toBe(1);
expect(first.deleted).toBe(0);
expect(first.notices.join(" ")).toContain("stale/model.toml");
expect(second).toMatchObject({ created: 0, updated: 0, deleted: 0 });
});
test("Baseten rejects malformed catalog responses", () => {
expect(() => BasetenResponse.parse({ data: "broken" })).toThrow();
});
test("Baseten rejects non-success API responses", async () => {
const fetcher = async () => new Response("unauthorized", {
status: 401,
statusText: "Unauthorized",
});
expect(fetchBasetenModels("fixture-key", fetcher as typeof fetch))
.rejects.toThrow("Baseten models request failed: 401 Unauthorized");
});
@@ -0,0 +1,35 @@
import { expect, test } from "bun:test";
import { buildWorkersAiModel } from "../src/sync/providers/cloudflare-workers-ai.js";
import type { OpenRouterModel } from "../src/sync/providers/openrouter.js";
test("Cloudflare Workers AI sync preserves reasoning options", () => {
const model: OpenRouterModel = {
id: "@cf/nvidia/nemotron-3-120b-a12b",
name: "Nemotron 3 Super 120B",
created: 1_773_187_200,
hugging_face_id: "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16",
knowledge_cutoff: null,
context_length: 256_000,
architecture: {
input_modalities: ["text"],
output_modalities: ["text"],
},
pricing: {
prompt: "0.0000005",
completion: "0.0000015",
},
top_provider: {
context_length: 256_000,
max_completion_tokens: 256_000,
},
supported_parameters: ["reasoning", "tools", "temperature"],
};
const synced = buildWorkersAiModel(model, {
base_model: "nvidia/nemotron-3-super-120b-a12b",
reasoning_options: [{ type: "toggle" }],
});
expect(synced.reasoning_options).toEqual([{ type: "toggle" }]);
});
+15
View File
@@ -0,0 +1,15 @@
import { expect, test } from "bun:test";
import { inferKimiFamily } from "../src/family.js";
test("Kimi family inference ignores K2 versions", () => {
expect(inferKimiFamily("moonshotai/kimi-k2.5")).toBe("kimi-k2");
expect(inferKimiFamily("moonshotai/kimi-k2.7-code")).toBe("kimi-k2");
expect(inferKimiFamily("Kimi K2.6")).toBe("kimi-k2");
});
test("Kimi family inference preserves thinking variants", () => {
expect(inferKimiFamily("moonshotai/kimi-k2-thinking")).toBe("kimi-thinking");
expect(inferKimiFamily("Kimi K2.5 Thinking")).toBe("kimi-thinking");
expect(inferKimiFamily("moonshotai/kimi-k2.6:thinking")).toBe("kimi-thinking");
});
+35
View File
@@ -0,0 +1,35 @@
import { expect, test } from "bun:test";
import { buildGoogleModel } from "../src/sync/providers/google.js";
test("Google sync keeps base models compact", () => {
const synced = buildGoogleModel({
name: "models/gemini-3-pro-image-preview",
displayName: "Nano Banana Pro",
inputTokenLimit: 131_072,
outputTokenLimit: 32_768,
temperature: 1,
thinking: true,
}, {
base_model: "google/gemini-3-pro-image-preview",
name: "Nano Banana Pro",
family: "gemini-pro",
release_date: "2025-11-20",
last_updated: "2025-11-20",
attachment: true,
reasoning: true,
temperature: true,
tool_call: false,
knowledge: "2025-01",
open_weights: false,
cost: { input: 2, output: 120 },
limit: { context: 65_536, output: 32_768 },
modalities: { input: ["text", "image"], output: ["text", "image"] },
});
expect(synced).toEqual({
base_model: "google/gemini-3-pro-image-preview",
cost: { input: 2, output: 120 },
limit: { context: 131_072 },
});
});
+128
View File
@@ -3,6 +3,7 @@ import path from "node:path";
import { mkdtemp, mkdir, readlink, symlink } from "node:fs/promises";
import os from "node:os";
import { AuthoredModelShape } from "../src/schema.js";
import { syncProvider, type SyncProvider, type SyncedFullModel } from "../src/sync/index.js";
const model: SyncedFullModel = {
@@ -18,6 +19,28 @@ const model: SyncedFullModel = {
modalities: { input: ["text"], output: ["text"] },
};
test("reasoning budgets allow only the -1 negative sentinel", () => {
const authored = { id: "model", ...model };
expect(AuthoredModelShape.safeParse({
...authored,
reasoning_options: [{ type: "budget_tokens", min: -1, max: 32_768 }],
}).success).toBe(true);
expect(AuthoredModelShape.safeParse({
...authored,
reasoning_options: [{ type: "budget_tokens", min: -2, max: 32_768 }],
}).success).toBe(false);
});
test("reasoning efforts accept the provider default value", () => {
expect(AuthoredModelShape.safeParse({
id: "model",
...model,
reasoning: true,
reasoning_options: [{ type: "effort", values: ["none", "default"] }],
}).success).toBe(true);
});
async function fixture() {
const root = await mkdtemp(path.join(os.tmpdir(), "models-dev-sync-"));
const modelsDir = path.join(root, "providers", "test", "models");
@@ -97,3 +120,108 @@ test("non-deleting sync reports missing broken symlinks", async () => {
expect(result.notices).toEqual(["missing: model.toml"]);
expect(await readlink(filePath)).toBe("missing.toml");
});
test("sync preserves authored reasoning options omitted by a translator", async () => {
const { modelsDir } = await fixture();
const filePath = path.join(modelsDir, "model.toml");
await Bun.write(filePath, `name = "Old name"
release_date = "2026-01-01"
last_updated = "2026-01-01"
attachment = false
reasoning = true
tool_call = false
open_weights = false
[[reasoning_options]]
type = "effort"
values = ["low", "high"]
[[reasoning_options]]
type = "budget_tokens"
min = -1
max = 32768
[cost]
input = 1
output = 2
[limit]
context = 1000
output = 100
[modalities]
input = ["text"]
output = ["text"]
`);
const sync = provider(modelsDir, ["model"]);
sync.translateModel = (id) => ({
id,
model: { ...model, reasoning: true },
});
const first = await syncProvider(sync);
const content = await Bun.file(filePath).text();
const second = await syncProvider(sync);
expect(first.updated).toBe(1);
expect(content).toContain("[[reasoning_options]]");
expect(content).toContain('values = ["low", "high"]');
expect(content).toContain("min = -1");
expect(content).toContain("max = 32_768");
expect(second.updated).toBe(0);
expect(second.unchanged).toBe(1);
});
test("sync writes metadata returned by a provider translator", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "models-dev-sync-metadata-"));
const modelsDir = path.join(root, "providers", "test", "models");
await mkdir(modelsDir, { recursive: true });
const sync = provider(modelsDir, ["model"]);
sync.translateModel = () => ({
id: "model",
model: {
base_model: "test/model",
reasoning_options: [],
cost: { input: 1, output: 2 },
},
metadata: {
id: "test/model",
model: {
name: "Model",
release_date: "2026-06-10",
last_updated: "2026-06-10",
attachment: false,
reasoning: false,
tool_call: true,
open_weights: false,
limit: { context: 1_000, output: 100 },
modalities: { input: ["text"], output: ["text"] },
},
},
});
const first = await syncProvider(sync);
const second = await syncProvider(sync);
expect(first).toMatchObject({ created: 2, updated: 0 });
expect(second).toMatchObject({ created: 0, updated: 0 });
expect(await Bun.file(path.join(root, "models", "test", "model.toml")).text()).toContain('name = "Model"');
});
test("sync removes missing metadata only from its owned namespace", async () => {
const { root, modelsDir } = await fixture();
const ownedDir = path.join(root, "models", "test");
const otherDir = path.join(root, "models", "other");
await mkdir(ownedDir, { recursive: true });
await mkdir(otherDir, { recursive: true });
await Bun.write(path.join(ownedDir, "stale.toml"), 'name = "Stale"\n');
await Bun.write(path.join(otherDir, "retained.toml"), 'name = "Retained"\n');
const sync = provider(modelsDir, []);
sync.metadataNamespace = "test";
const result = await syncProvider(sync);
expect(result.deleted).toBe(1);
expect(await Bun.file(path.join(ownedDir, "stale.toml")).exists()).toBe(false);
expect(await Bun.file(path.join(otherDir, "retained.toml")).exists()).toBe(true);
});
+167
View File
@@ -0,0 +1,167 @@
import { expect, test } from "bun:test";
import { readdirSync } from "node:fs";
import path from "node:path";
import {
buildVeniceModel,
resolveVeniceBaseModel,
venice,
VeniceResponse,
type VeniceModel,
} from "../src/sync/providers/venice.js";
const catalogModel: VeniceModel = {
id: "openai-gpt-54",
created: 1_772_668_800,
model_spec: {
name: "GPT-5.4",
availableContextTokens: 400_000,
maxCompletionTokens: 128_000,
modelSource: "OpenAI",
capabilities: {
supportsVision: true,
supportsReasoning: true,
supportsReasoningEffort: true,
reasoningEffortOptions: ["none", "low", "medium", "high"],
supportsFunctionCalling: true,
supportsResponseSchema: true,
},
pricing: {
input: { usd: 3.13 },
output: { usd: 18.75 },
cache_input: { usd: 0.313 },
extended: {
context_token_threshold: 200_000,
input: { usd: 6.26 },
output: { usd: 28.125 },
},
},
},
};
test("Venice resolves flattened IDs to canonical metadata", () => {
expect(resolveVeniceBaseModel("openai-gpt-54", "GPT-5.4")).toBe("openai/gpt-5.4");
expect(resolveVeniceBaseModel("claude-opus-4-8-fast", "Claude Opus 4.8 Fast"))
.toBe("anthropic/claude-opus-4-8");
});
test("Venice emits empty reasoning options when efforts are unavailable", () => {
const synced = buildVeniceModel({
...catalogModel,
id: "reasoning-without-efforts",
model_spec: {
...catalogModel.model_spec,
name: "Reasoning Without Efforts",
capabilities: {
...catalogModel.model_spec.capabilities,
reasoningEffortOptions: [],
},
},
}, undefined, undefined, "2026-06-10");
expect(synced).toMatchObject({ reasoning: true, reasoning_options: [] });
});
test("Venice does not infer temperature support", () => {
const synced = buildVeniceModel(catalogModel, undefined, null, "2026-06-10");
expect(synced.temperature).toBeUndefined();
});
test("Venice skips E2EE models", () => {
const translated = venice.translateModel({
...catalogModel,
id: "e2ee-test-model",
model_spec: {
...catalogModel.model_spec,
capabilities: { ...catalogModel.model_spec.capabilities, supportsE2EE: true },
},
}, { existing: () => undefined });
expect(translated).toBeUndefined();
});
test("Venice uses boundary-aware family matching", () => {
const synced = buildVeniceModel({
...catalogModel,
id: "google-gemma-4-31b-it",
model_spec: { ...catalogModel.model_spec, name: "Google Gemma 4 31B Instruct" },
}, undefined, null, "2026-06-10");
expect(synced).toMatchObject({ family: "gemma" });
});
test("Venice maps API fields without bumping inherited model timestamps", () => {
const synced = buildVeniceModel(catalogModel, {
base_model: "openai/gpt-5.4",
name: "GPT-5.4",
family: "gpt",
release_date: "2026-03-05",
last_updated: "2026-03-09",
attachment: true,
reasoning: true,
tool_call: true,
structured_output: true,
temperature: true,
open_weights: false,
interleaved: { field: "reasoning_content" },
cost: { input: 3, output: 18, input_audio: 4 },
limit: { context: 400_000, output: 128_000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
}, "openai/gpt-5.4", "2026-06-10");
expect(synced).toMatchObject({
base_model: "openai/gpt-5.4",
base_model_omit: ["limit.input"],
last_updated: "2026-03-09",
reasoning_options: [{ type: "effort", values: ["none", "low", "medium", "high"] }],
interleaved: { field: "reasoning_content" },
cost: {
input: 3.13,
output: 18.75,
cache_read: 0.313,
input_audio: 4,
tiers: [{ tier: { type: "context", size: 200_000 }, input: 6.26, output: 28.125 }],
},
});
expect(synced).not.toHaveProperty("family");
expect(synced).not.toHaveProperty("release_date");
expect(synced).not.toHaveProperty("open_weights");
expect(synced).not.toHaveProperty("modalities");
expect(synced).not.toHaveProperty("temperature");
});
test("Venice preserves last_updated when authoritative data is unchanged", () => {
const providerModel = {
...catalogModel,
id: "venice-only-test-model",
model_spec: { ...catalogModel.model_spec, name: "Venice Only Test Model" },
};
const full = buildVeniceModel(providerModel, undefined, undefined, "2026-06-10");
if ("base_model" in full) throw new Error("Expected a full provider model fixture");
const synced = buildVeniceModel(providerModel, full, undefined, "2026-06-11");
expect(synced).toMatchObject({ last_updated: "2026-06-10" });
});
test("Venice rejects malformed responses", () => {
expect(() => VeniceResponse.parse({ data: [{ id: "broken" }] })).toThrow();
});
test("Venice models use only canonical metadata and declare reasoning options", async () => {
const root = path.join(import.meta.dirname, "..", "..", "..");
const modelsDir = path.join(root, "providers", "venice", "models");
for (const file of readdirSync(modelsDir).filter((item) => item.endsWith(".toml"))) {
const model = Bun.TOML.parse(await Bun.file(path.join(modelsDir, file)).text()) as {
base_model?: string;
reasoning_options?: unknown[];
};
expect(model.reasoning_options, file).toBeDefined();
if (model.base_model !== undefined) {
expect(model.base_model.startsWith("venice/"), file).toBe(false);
expect(await Bun.file(path.join(root, "models", `${model.base_model}.toml`)).exists(), file).toBe(true);
}
expect(file.startsWith("e2ee-"), file).toBe(false);
}
});
+37 -1
View File
@@ -1,6 +1,6 @@
import { expect, test } from "bun:test";
import { buildVercelModel, type VercelModel } from "../src/sync/providers/vercel.js";
import { buildVercelModel, type VercelModel, vercel } from "../src/sync/providers/vercel.js";
const model: VercelModel = {
id: "openai/gpt-test",
@@ -64,6 +64,42 @@ test("Vercel models preserve curated metadata and missing limits", () => {
expect(synced.limit).toEqual({ context: 64_000, input: 48_000, output: 16_000 });
});
test("Vercel non-language models use API tool capabilities", () => {
const synced = buildVercelModel({
...model,
type: "image",
tags: [],
}, {
tool_call: true,
});
expect(synced.tool_call).toBe(false);
});
test("Vercel sync includes non-language model types", () => {
for (const [type, output] of [
["image", ["image"]],
["video", ["video"]],
["reranking", ["text"]],
] as const) {
const source = {
...model,
id: `test/${type}`,
type,
tags: [],
context_window: 0,
max_tokens: 0,
pricing: undefined,
};
expect(vercel.translateModel(source, { existing: () => undefined })).toBeDefined();
expect(buildVercelModel(source, undefined)).toMatchObject({
tool_call: false,
modalities: { input: ["text"], output },
});
}
});
test("Vercel models use canonical metadata when available", () => {
const synced = buildVercelModel({
...model,
+30
View File
@@ -0,0 +1,30 @@
import { expect, test } from "bun:test";
import { buildXAIModel, type XAIModel } from "../src/sync/providers/xai.js";
const model: XAIModel = {
id: "grok-test",
created: 1_700_000_000,
input_modalities: ["text"],
output_modalities: ["text"],
prompt_text_token_price: 10_000,
completion_text_token_price: 20_000,
};
test("xAI sync preserves reasoning options", () => {
const synced = buildXAIModel(model, {
name: "Grok Test",
release_date: "2024-01-01",
last_updated: "2024-01-01",
attachment: false,
reasoning: true,
reasoning_options: [{ type: "effort", values: ["none", "high"] }],
tool_call: true,
open_weights: false,
limit: { context: 2_000_000, output: 30_000 },
modalities: { input: ["text"], output: ["text"] },
});
expect(synced.reasoning_options).toEqual([{ type: "effort", values: ["none", "high"] }]);
expect(synced.limit?.context).toBe(2_000_000);
});
@@ -4,6 +4,7 @@ release_date = "2024-11-28"
last_updated = "2024-11-28"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-07-01"
last_updated = "2025-07-01"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-04-29"
last_updated = "2025-04-29"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-07-22"
last_updated = "2025-07-22"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-02-19"
last_updated = "2025-02-19"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2024-10-31"
@@ -4,6 +4,7 @@ release_date = "2025-10-15"
last_updated = "2025-10-15"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-02-28"
@@ -4,6 +4,7 @@ release_date = "2025-08-05"
last_updated = "2025-08-05"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = false
@@ -4,6 +4,7 @@ release_date = "2025-05-14"
last_updated = "2025-05-14"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = false
@@ -4,6 +4,7 @@ release_date = "2025-11-01"
last_updated = "2025-11-01"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-03-31"
@@ -4,6 +4,7 @@ release_date = "2026-02-05"
last_updated = "2026-02-05"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-05-31"
@@ -4,6 +4,7 @@ release_date = "2025-05-14"
last_updated = "2025-05-14"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = false
@@ -4,6 +4,7 @@ release_date = "2025-09-29"
last_updated = "2025-09-29"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-07-31"
@@ -4,6 +4,7 @@ release_date = "2026-02-17"
last_updated = "2026-02-17"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-08-31"
@@ -4,6 +4,7 @@ release_date = "2025-01-20"
last_updated = "2025-01-20"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-06-01"
last_updated = "2025-06-01"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-06-15"
last_updated = "2025-06-15"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-01-20"
last_updated = "2025-01-20"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2025-03-20"
last_updated = "2025-06-05"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-01"
@@ -4,6 +4,7 @@ release_date = "2025-03-25"
last_updated = "2025-03-25"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-01"
@@ -4,6 +4,7 @@ release_date = "2025-12-17"
last_updated = "2025-12-17"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
knowledge = "2025-01"
@@ -4,6 +4,7 @@ release_date = "2026-03-01"
last_updated = "2026-03-01"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
structured_output = true
@@ -4,6 +4,7 @@ release_date = "2026-02-19"
last_updated = "2026-02-19"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
structured_output = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-09-15"
last_updated = "2025-09-15"
attachment = false
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-09-30"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-08-07"
last_updated = "2025-08-07"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-05-30"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-08-07"
last_updated = "2025-08-07"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-05-30"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2025-11-13"
last_updated = "2025-11-13"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-09-30"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2025-11-13"
last_updated = "2025-11-13"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-09-30"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2025-11-13"
last_updated = "2025-11-13"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-09-30"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-11-13"
last_updated = "2025-11-13"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-09-30"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2026-01-01"
last_updated = "2026-01-01"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
knowledge = "2024-09-30"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2025-12-11"
last_updated = "2025-12-11"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2025-08-31"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-12-11"
last_updated = "2025-12-11"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2025-08-31"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2026-03-01"
last_updated = "2026-03-01"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = false
@@ -4,6 +4,7 @@ release_date = "2026-02-05"
last_updated = "2026-02-05"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2025-08-31"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2026-02-05"
last_updated = "2026-02-05"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2025-08-31"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2026-03-05"
last_updated = "2026-03-05"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2025-08-31"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-08-07"
last_updated = "2025-08-07"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-09-30"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-07-09"
last_updated = "2025-07-09"
attachment = true
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = false
@@ -1,5 +1,5 @@
name = "Kimi K2 Turbo Preview"
family = "kimi"
family = "kimi-k2"
release_date = "2025-07-08"
last_updated = "2025-07-08"
attachment = false
+2 -1
View File
@@ -1,9 +1,10 @@
name = "Kimi K2.5"
family = "kimi"
family = "kimi-k2"
release_date = "2026-01"
last_updated = "2026-01"
attachment = false
reasoning = true
reasoning_options = []
structured_output = true
temperature = true
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2024-12-20"
last_updated = "2025-01-29"
attachment = false
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-05"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-06-10"
last_updated = "2025-06-10"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-05"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-04-16"
last_updated = "2025-04-16"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-05"
tool_call = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-04-16"
last_updated = "2025-04-16"
attachment = true
reasoning = true
reasoning_options = []
temperature = false
knowledge = "2024-05"
tool_call = true
@@ -4,6 +4,7 @@ release_date = "2025-08-05"
last_updated = "2025-08-05"
attachment = true
reasoning = true
reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }]
temperature = true
tool_call = true
open_weights = true
+1
View File
@@ -4,6 +4,7 @@ release_date = "2025-05-28"
last_updated = "2025-05-28"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = false
@@ -4,6 +4,7 @@ release_date = "2025-07-28"
last_updated = "2025-07-28"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -4,6 +4,7 @@ release_date = "2026-02-11"
last_updated = "2026-02-11"
attachment = false
reasoning = true
reasoning_options = []
temperature = true
tool_call = true
open_weights = true
@@ -1,4 +1,5 @@
base_model = "xiaomi/mimo-v2.5-pro"
reasoning_options = [{ type = "toggle" }]
name = "Coding Xiaomi MiMo-V2.5-Pro"
family = "mimo-v2.5-pro"
last_updated = "2026-05-13"
@@ -1,4 +1,5 @@
base_model = "xiaomi/mimo-v2.5"
reasoning_options = [{ type = "toggle" }]
name = "Coding Xiaomi MiMo-V2.5"
family = "mimo-v2.5"
last_updated = "2026-05-13"
+1 -1
View File
@@ -1,5 +1,5 @@
name = "Kimi K2.5"
family = "kimi-k2.5"
family = "kimi-k2"
release_date = "2026-01"
last_updated = "2026-01"
attachment = true
+1 -1
View File
@@ -1,5 +1,5 @@
name = "Kimi K2.6"
family = "kimi-k2.6"
family = "kimi-k2"
release_date = "2026-04-21"
last_updated = "2026-04-21"
attachment = true
@@ -1,4 +1,5 @@
base_model = "xiaomi/mimo-v2.5"
reasoning_options = [{ type = "toggle" }]
name = "Xiaomi MiMo-V2.5 (free)"
family = "mimo-v2.5"
last_updated = "2026-05-13"

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