mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 12:27:41 +08:00
fix(provider-registry): address catalog review findings
- Iterate variant/quant/date id stripping to a fixpoint (shared by canonOf and normalizeModelId) so canonicalization is idempotent; drop `-medium` from variant suffixes (it ate Mistral tier names). Pins via new invariants: base ids are canonOf fixpoints. - Restore hunyuan + tencent-cloud-ti provider sources (lost in regeneration), delete retired cephalon (16518), restore groq apiFeatures.serviceTier, drop the stray duplicate bedrock gpt-oss-120b-1:0 override (+ invariant: duplicated keys need a self variant). - Gate prefix-inherited web-search by text input+output modalities so TTS/transcription SKUs no longer carry it (+ generalized invariant). - Stamp data/*.json version with a content hash instead of the generation date, so any content change re-runs the preset seeders (date collision with base made SeedRunner skip TokenHub on existing DBs). - Fix stale src/labs + src/provider remediation text in ci.yml and .gitattributes. - ppio's qwen3-235b-a22b-thinking override becomes a named standalone (its base row now folds into qwen3-235b-a22b). - Regenerated data/*.json (includes upstream drift since last run). Addresses https://github.com/CherryHQ/cherry-studio/pull/16401#pullrequestreview-4618897871 Signed-off-by: suyao <sy20010504@gmail.com>
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
- 'patches/**'
|
||||
|
||||
catalog-hand-edit-check:
|
||||
# provider-registry data/*.json are GENERATED from src/labs + src/provider. Catch a hand-edit:
|
||||
# provider-registry data/*.json are GENERATED from src/creators + src/providers. Catch a hand-edit:
|
||||
# if the data changed but no source under src/ or scripts/ did, the JSON was edited directly.
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
- name: Reject hand-edited catalog
|
||||
if: steps.filter.outputs.data == 'true' && steps.filter.outputs.source == 'false'
|
||||
run: |
|
||||
echo "::error::packages/provider-registry/data/*.json changed but no src/ or scripts/ change — that's a hand-edit. The data files are generated: edit src/labs|src/provider and run \`pnpm --filter @cherrystudio/provider-registry generate\`, then commit. Never hand-edit data/*.json."
|
||||
echo "::error::packages/provider-registry/data/*.json changed but no src/ or scripts/ change — that's a hand-edit. The data files are generated: edit src/creators|src/providers and run \`pnpm --filter @cherrystudio/provider-registry generate\`, then commit. Never hand-edit data/*.json."
|
||||
exit 1
|
||||
|
||||
changeset-check:
|
||||
|
||||
2
packages/provider-registry/.gitattributes
vendored
2
packages/provider-registry/.gitattributes
vendored
@@ -1,4 +1,4 @@
|
||||
# Generated artifacts — produced by `pnpm generate` from src/labs + src/provider.
|
||||
# Generated artifacts — produced by `pnpm generate` from src/creators + src/providers.
|
||||
# Never hand-edit; collapse them in PR diffs.
|
||||
data/models.json linguist-generated=true
|
||||
data/providers.json linguist-generated=true
|
||||
|
||||
286
packages/provider-registry/data/models.json
generated
286
packages/provider-registry/data/models.json
generated
@@ -2417,11 +2417,11 @@
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.08
|
||||
"perMillionTokens": 0.117
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.5
|
||||
"perMillionTokens": 0.45499999999999996
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2448,20 +2448,20 @@
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "structured-output"],
|
||||
"contextWindow": 131072,
|
||||
"id": "qwen3-30b-a3b-thinking",
|
||||
"id": "qwen3-30b-a3b",
|
||||
"inputModalities": ["text"],
|
||||
"metadata": {},
|
||||
"name": "qwen3-30b-a3b-thinking",
|
||||
"name": "qwen3-30b-a3b",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "alibaba",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.08
|
||||
"perMillionTokens": 0.13
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.39999999999999997
|
||||
"perMillionTokens": 1.56
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2485,46 +2485,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "structured-output"],
|
||||
"contextWindow": 262144,
|
||||
"id": "qwen3-235b-a22b-thinking",
|
||||
"inputModalities": ["text"],
|
||||
"metadata": {},
|
||||
"name": "qwen3-235b-a22b-thinking",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "alibaba",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.14950000000000002
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1.495
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "structured-output"],
|
||||
"contextWindow": 131072,
|
||||
"id": "qwen3-30b-a3b",
|
||||
"inputModalities": ["text"],
|
||||
"metadata": {},
|
||||
"name": "qwen3-30b-a3b",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "alibaba",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.12
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contextWindow": 128000,
|
||||
"id": "qwen-2-5-coder-32b-instruct",
|
||||
@@ -3006,6 +2966,39 @@
|
||||
"supportedEfforts": ["low", "medium", "high", "max"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "structured-output", "file-input"],
|
||||
"contextWindow": 1000000,
|
||||
"family": "claude-fable",
|
||||
"id": "claude-fable-5",
|
||||
"inputModalities": ["text", "image"],
|
||||
"maxOutputTokens": 128000,
|
||||
"metadata": {},
|
||||
"name": "Claude Fable 5",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "anthropic",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1
|
||||
},
|
||||
"cacheWrite": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 12.5
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 10
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 50
|
||||
}
|
||||
},
|
||||
"reasoning": {
|
||||
"supportedEfforts": ["low", "medium", "high", "max"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": [
|
||||
"function-call",
|
||||
@@ -3380,39 +3373,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "structured-output", "file-input"],
|
||||
"contextWindow": 1000000,
|
||||
"family": "claude-fable",
|
||||
"id": "claude-fable-5",
|
||||
"inputModalities": ["text", "image"],
|
||||
"maxOutputTokens": 128000,
|
||||
"metadata": {},
|
||||
"name": "Claude Fable 5",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "anthropic",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1
|
||||
},
|
||||
"cacheWrite": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 12.5
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 10
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 50
|
||||
}
|
||||
},
|
||||
"reasoning": {
|
||||
"supportedEfforts": ["low", "medium", "high", "max"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "file-input", "web-search"],
|
||||
"contextWindow": 200000,
|
||||
@@ -5802,11 +5762,11 @@
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.19999999999999998
|
||||
"perMillionTokens": 0.24
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.77
|
||||
"perMillionTokens": 0.8999999999999999
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7015,7 +6975,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-generation", "web-search"],
|
||||
"capabilities": ["audio-generation"],
|
||||
"contextWindow": 8192,
|
||||
"family": "gemini-flash",
|
||||
"id": "gemini-2-5-flash-preview-tts",
|
||||
@@ -7187,7 +7147,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-generation", "web-search"],
|
||||
"capabilities": ["audio-generation"],
|
||||
"contextWindow": 8192,
|
||||
"family": "gemini-flash",
|
||||
"id": "gemini-2-5-pro-preview-tts",
|
||||
@@ -7417,7 +7377,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-generation", "web-search"],
|
||||
"capabilities": ["audio-generation"],
|
||||
"contextWindow": 32768,
|
||||
"family": "gemini-pro",
|
||||
"id": "gemini-2-5-pro-tts",
|
||||
@@ -7439,7 +7399,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-generation", "web-search"],
|
||||
"capabilities": ["audio-generation"],
|
||||
"contextWindow": 32768,
|
||||
"family": "gemini-flash",
|
||||
"id": "gemini-2-5-flash-tts",
|
||||
@@ -8396,10 +8356,10 @@
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "structured-output"],
|
||||
"contextWindow": 32768,
|
||||
"id": "lfm-2-5-1-2b-thinking",
|
||||
"id": "lfm-2-5-1-2b",
|
||||
"inputModalities": ["text"],
|
||||
"metadata": {},
|
||||
"name": "lfm-2-5-1-2b-thinking",
|
||||
"name": "lfm-2-5-1-2b",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "liquidai",
|
||||
"pricing": {
|
||||
@@ -8455,7 +8415,7 @@
|
||||
"capabilities": ["reasoning"],
|
||||
"contextWindow": 32768,
|
||||
"family": "longcat",
|
||||
"id": "longcat-flash-thinking",
|
||||
"id": "longcat-flash",
|
||||
"inputModalities": ["text"],
|
||||
"maxOutputTokens": 32768,
|
||||
"metadata": {},
|
||||
@@ -9673,14 +9633,15 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "image-recognition", "file-input"],
|
||||
"contextWindow": 128000,
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "structured-output", "file-input"],
|
||||
"contextWindow": 262144,
|
||||
"family": "mistral-medium",
|
||||
"id": "mistral",
|
||||
"id": "mistral-medium",
|
||||
"inputModalities": ["text", "image"],
|
||||
"maxOutputTokens": 64000,
|
||||
"maxOutputTokens": 262144,
|
||||
"metadata": {},
|
||||
"name": "Mistral Medium 3.1",
|
||||
"openWeights": true,
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "mistral",
|
||||
"pricing": {
|
||||
@@ -9692,6 +9653,9 @@
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2
|
||||
}
|
||||
},
|
||||
"reasoning": {
|
||||
"supportedEfforts": ["none", "high"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -9928,7 +9892,7 @@
|
||||
"capabilities": ["function-call", "reasoning"],
|
||||
"contextWindow": 128000,
|
||||
"family": "magistral-medium",
|
||||
"id": "magistral",
|
||||
"id": "magistral-medium",
|
||||
"inputModalities": ["text"],
|
||||
"maxOutputTokens": 16384,
|
||||
"metadata": {},
|
||||
@@ -10130,32 +10094,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "structured-output", "file-input"],
|
||||
"contextWindow": 262144,
|
||||
"family": "mistral-medium",
|
||||
"id": "mistral-medium",
|
||||
"inputModalities": ["text", "image"],
|
||||
"maxOutputTokens": 262144,
|
||||
"metadata": {},
|
||||
"name": "Mistral Medium 3.5",
|
||||
"openWeights": true,
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "mistral",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1.5
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 7.5
|
||||
}
|
||||
},
|
||||
"reasoning": {
|
||||
"supportedEfforts": ["none", "high"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "file-input"],
|
||||
"contextWindow": 256000,
|
||||
@@ -11048,11 +10986,11 @@
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.55
|
||||
"perMillionTokens": 0.66
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 3.1999999999999997
|
||||
"perMillionTokens": 3.41
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -11462,26 +11400,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning", "image-recognition", "audio-recognition", "video-recognition"],
|
||||
"contextWindow": 256000,
|
||||
"id": "nemotron-3-nano-omni-30b-a3b-reasoning",
|
||||
"inputModalities": ["text", "audio", "image", "video"],
|
||||
"metadata": {},
|
||||
"name": "nemotron-3-nano-omni-30b-a3b-reasoning",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "nvidia",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["image-recognition", "image-generation", "file-input"],
|
||||
"family": "gpt-image",
|
||||
@@ -12040,7 +11958,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-recognition", "web-search"],
|
||||
"capabilities": ["audio-recognition"],
|
||||
"family": "gpt",
|
||||
"id": "gpt-4o-transcribe",
|
||||
"inputModalities": ["audio"],
|
||||
@@ -12230,7 +12148,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-recognition", "web-search"],
|
||||
"capabilities": ["audio-recognition"],
|
||||
"family": "o-mini",
|
||||
"id": "gpt-4o-mini-transcribe",
|
||||
"inputModalities": ["audio"],
|
||||
@@ -14225,31 +14143,6 @@
|
||||
"supportedEfforts": ["low", "medium", "high"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning"],
|
||||
"contextWindow": 32768,
|
||||
"id": "step-1-32k",
|
||||
"inputModalities": ["text"],
|
||||
"maxOutputTokens": 32768,
|
||||
"metadata": {},
|
||||
"name": "Step 1 (32K)",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "stepfun",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.41
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2.05
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 9.59
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning"],
|
||||
"contextWindow": 16384,
|
||||
@@ -14275,6 +14168,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-generation"],
|
||||
"family": "step",
|
||||
"id": "step-tts-2",
|
||||
"inputModalities": ["text"],
|
||||
"metadata": {},
|
||||
"name": "Step TTS 2",
|
||||
"outputModalities": ["audio"],
|
||||
"ownedBy": "stepfun"
|
||||
},
|
||||
{
|
||||
"capabilities": ["function-call", "reasoning"],
|
||||
"contextWindow": 32768,
|
||||
"id": "step-1-32k",
|
||||
"inputModalities": ["text"],
|
||||
"maxOutputTokens": 32768,
|
||||
"metadata": {},
|
||||
"name": "Step 1 (32K)",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "stepfun",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.41
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2.05
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 9.59
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["reasoning"],
|
||||
"contextWindow": 256000,
|
||||
@@ -17238,6 +17166,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-generation"],
|
||||
"family": "step",
|
||||
"id": "stepaudio-2-5-tts",
|
||||
"inputModalities": ["text"],
|
||||
"metadata": {},
|
||||
"name": "StepAudio 2.5 TTS",
|
||||
"outputModalities": ["audio"],
|
||||
"ownedBy": "stepfun"
|
||||
},
|
||||
{
|
||||
"capabilities": ["audio-recognition"],
|
||||
"family": "step",
|
||||
"id": "stepaudio-2-5-asr",
|
||||
"inputModalities": ["audio"],
|
||||
"metadata": {},
|
||||
"name": "StepAudio 2.5 ASR",
|
||||
"outputModalities": ["text"],
|
||||
"ownedBy": "stepfun"
|
||||
},
|
||||
{
|
||||
"capabilities": ["video-generation"],
|
||||
"family": "veo",
|
||||
@@ -17474,5 +17422,5 @@
|
||||
"ownedBy": "vercel"
|
||||
}
|
||||
],
|
||||
"version": "2026.07.01"
|
||||
"version": "f3aa40d6c14fa452"
|
||||
}
|
||||
|
||||
342
packages/provider-registry/data/provider-models.json
generated
342
packages/provider-registry/data/provider-models.json
generated
@@ -237,8 +237,7 @@
|
||||
},
|
||||
{
|
||||
"apiModelId": "doubao-seed-1-6-thinking-250715",
|
||||
"modelId": "doubao-seed-1-6-thinking",
|
||||
"name": "doubao-seed-1-6-thinking-250715",
|
||||
"modelId": "doubao-seed-1-6",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -2029,21 +2028,6 @@
|
||||
},
|
||||
"providerId": "aws-bedrock"
|
||||
},
|
||||
{
|
||||
"apiModelId": "openai.gpt-oss-120b-1:0",
|
||||
"modelId": "gpt-oss-120b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.15
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.6
|
||||
}
|
||||
},
|
||||
"providerId": "aws-bedrock"
|
||||
},
|
||||
{
|
||||
"apiModelId": "openai.gpt-oss-20b",
|
||||
"modelId": "gpt-oss-20b",
|
||||
@@ -2628,6 +2612,22 @@
|
||||
},
|
||||
"providerId": "aws-bedrock"
|
||||
},
|
||||
{
|
||||
"apiModelId": "gemma-4-31b",
|
||||
"modelId": "gemma-4-31b",
|
||||
"name": "Gemma 4 31B IT",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.99
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1.49
|
||||
}
|
||||
},
|
||||
"providerId": "cerebras"
|
||||
},
|
||||
{
|
||||
"apiModelId": "zai-glm-4.7",
|
||||
"modelId": "glm-4-7",
|
||||
@@ -2873,6 +2873,29 @@
|
||||
},
|
||||
"providerId": "copilot"
|
||||
},
|
||||
{
|
||||
"apiModelId": "claude-sonnet-5",
|
||||
"modelId": "claude-sonnet-5",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.2
|
||||
},
|
||||
"cacheWrite": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2.5
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 10
|
||||
}
|
||||
},
|
||||
"providerId": "copilot"
|
||||
},
|
||||
{
|
||||
"apiModelId": "gemini-2.5-pro",
|
||||
"modelId": "gemini-2-5-pro",
|
||||
@@ -3120,6 +3143,45 @@
|
||||
},
|
||||
"providerId": "copilot"
|
||||
},
|
||||
{
|
||||
"apiModelId": "kimi-k2.7-code",
|
||||
"modelId": "kimi-k2-7-code",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.19
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.95
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 4
|
||||
}
|
||||
},
|
||||
"providerId": "copilot"
|
||||
},
|
||||
{
|
||||
"apiModelId": "mai-code-1-flash-picker",
|
||||
"modelId": "mai-code-1-flash-picker",
|
||||
"name": "MAI-Code-1-Flash",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.075
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.75
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 4.5
|
||||
}
|
||||
},
|
||||
"providerId": "copilot"
|
||||
},
|
||||
{
|
||||
"apiModelId": "qwen-image",
|
||||
"imageGeneration": {
|
||||
@@ -4663,6 +4725,29 @@
|
||||
},
|
||||
"providerId": "gateway"
|
||||
},
|
||||
{
|
||||
"apiModelId": "anthropic/claude-fable-5",
|
||||
"modelId": "claude-fable-5",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1
|
||||
},
|
||||
"cacheWrite": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 12.5
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 10
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 50
|
||||
}
|
||||
},
|
||||
"providerId": "gateway"
|
||||
},
|
||||
{
|
||||
"apiModelId": "anthropic/claude-haiku-4.5",
|
||||
"modelId": "claude-haiku-4-5",
|
||||
@@ -6952,7 +7037,7 @@
|
||||
},
|
||||
{
|
||||
"apiModelId": "mistral/magistral-medium",
|
||||
"modelId": "magistral",
|
||||
"modelId": "magistral-medium",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -7315,21 +7400,6 @@
|
||||
},
|
||||
"providerId": "gateway"
|
||||
},
|
||||
{
|
||||
"apiModelId": "mistral/mistral-medium",
|
||||
"modelId": "mistral",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.4
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2
|
||||
}
|
||||
},
|
||||
"providerId": "gateway"
|
||||
},
|
||||
{
|
||||
"apiModelId": "mistral/mistral-large-3",
|
||||
"modelId": "mistral-large-3",
|
||||
@@ -7345,6 +7415,21 @@
|
||||
},
|
||||
"providerId": "gateway"
|
||||
},
|
||||
{
|
||||
"apiModelId": "mistral/mistral-medium",
|
||||
"modelId": "mistral-medium",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.4
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 2
|
||||
}
|
||||
},
|
||||
"providerId": "gateway"
|
||||
},
|
||||
{
|
||||
"apiModelId": "mistral/mistral-medium-3.5",
|
||||
"modelId": "mistral-medium-3-5",
|
||||
@@ -9676,24 +9761,9 @@
|
||||
},
|
||||
"providerId": "huggingface"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-235B-A22B",
|
||||
"modelId": "qwen3-235b-a22b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.2
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.8
|
||||
}
|
||||
},
|
||||
"providerId": "huggingface"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-235B-A22B-Thinking-2507",
|
||||
"modelId": "qwen3-235b-a22b-thinking",
|
||||
"modelId": "qwen3-235b-a22b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -10069,6 +10139,21 @@
|
||||
},
|
||||
"providerId": "modelscope"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-235B-A22B-Thinking-2507",
|
||||
"modelId": "qwen3-235b-a22b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
}
|
||||
},
|
||||
"providerId": "modelscope"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-235B-A22B-Instruct-2507",
|
||||
"modelId": "qwen3-235b-a22b-instruct",
|
||||
@@ -10086,8 +10171,8 @@
|
||||
"providerId": "modelscope"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-235B-A22B-Thinking-2507",
|
||||
"modelId": "qwen3-235b-a22b-thinking",
|
||||
"apiModelId": "Qwen/Qwen3-30B-A3B-Thinking-2507",
|
||||
"modelId": "qwen3-30b-a3b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -10115,21 +10200,6 @@
|
||||
},
|
||||
"providerId": "modelscope"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-30B-A3B-Thinking-2507",
|
||||
"modelId": "qwen3-30b-a3b-thinking",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
}
|
||||
},
|
||||
"providerId": "modelscope"
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
|
||||
"modelId": "qwen3-coder-30b-a3b-instruct",
|
||||
@@ -11509,6 +11579,29 @@
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "anthropic/claude-fable-5",
|
||||
"modelId": "claude-fable-5",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1
|
||||
},
|
||||
"cacheWrite": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 12.5
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 10
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 50
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "~anthropic/claude-fable-latest",
|
||||
"modelId": "claude-fable-latest",
|
||||
@@ -12055,11 +12148,11 @@
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.2
|
||||
"perMillionTokens": 0.24
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.77
|
||||
"perMillionTokens": 0.9
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -12176,15 +12269,15 @@
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.02
|
||||
"perMillionTokens": 0.018
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.098
|
||||
"perMillionTokens": 0.089
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.196
|
||||
"perMillionTokens": 0.18
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -14196,15 +14289,15 @@
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.11
|
||||
"perMillionTokens": 0.14
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.55
|
||||
"perMillionTokens": 0.66
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 3.2
|
||||
"perMillionTokens": 3.41
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -14234,15 +14327,15 @@
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.11
|
||||
"perMillionTokens": 0.14
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.55
|
||||
"perMillionTokens": 0.66
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 3.2
|
||||
"perMillionTokens": 3.41
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -14351,6 +14444,22 @@
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "poolside/laguna-xs-2.1:free",
|
||||
"modelId": "laguna-xs-2-1",
|
||||
"name": "Laguna XS 2.1 (free)",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "liquid/lfm-2-24b-a2b",
|
||||
"modelId": "lfm-2-24b-a2b",
|
||||
@@ -14367,8 +14476,8 @@
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "liquid/lfm-2.5-1.2b-instruct:free",
|
||||
"modelId": "lfm-2-5-1-2b-instruct",
|
||||
"apiModelId": "liquid/lfm-2.5-1.2b-thinking:free",
|
||||
"modelId": "lfm-2-5-1-2b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -14382,8 +14491,8 @@
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "liquid/lfm-2.5-1.2b-thinking:free",
|
||||
"modelId": "lfm-2-5-1-2b-thinking",
|
||||
"apiModelId": "liquid/lfm-2.5-1.2b-instruct:free",
|
||||
"modelId": "lfm-2-5-1-2b-instruct",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -14734,17 +14843,13 @@
|
||||
"apiModelId": "minimax/minimax-m2",
|
||||
"modelId": "minimax-m2",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.03
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.255
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1
|
||||
"perMillionTokens": 1.02
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -14759,11 +14864,11 @@
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.29
|
||||
"perMillionTokens": 0.3
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.95
|
||||
"perMillionTokens": 1.2
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -15160,7 +15265,7 @@
|
||||
},
|
||||
{
|
||||
"apiModelId": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
||||
"modelId": "nemotron-3-nano-omni-30b-a3b-reasoning",
|
||||
"modelId": "nemotron-3-nano-omni-30b-a3b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -15654,24 +15759,9 @@
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "qwen/qwen3-235b-a22b",
|
||||
"modelId": "qwen3-235b-a22b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.455
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 1.82
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "qwen/qwen3-235b-a22b-thinking-2507",
|
||||
"modelId": "qwen3-235b-a22b-thinking",
|
||||
"modelId": "qwen3-235b-a22b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -15685,16 +15775,16 @@
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "qwen/qwen3-30b-a3b",
|
||||
"apiModelId": "qwen/qwen3-30b-a3b-thinking-2507",
|
||||
"modelId": "qwen3-30b-a3b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.12
|
||||
"perMillionTokens": 0.13
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.5
|
||||
"perMillionTokens": 1.56
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -15714,25 +15804,6 @@
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "qwen/qwen3-30b-a3b-thinking-2507",
|
||||
"modelId": "qwen3-30b-a3b-thinking",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.08
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.08
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.4
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
},
|
||||
{
|
||||
"apiModelId": "qwen/qwen3-32b",
|
||||
"modelId": "qwen3-32b",
|
||||
@@ -16013,17 +16084,13 @@
|
||||
"apiModelId": "qwen/qwen3-8b",
|
||||
"modelId": "qwen3-8b",
|
||||
"pricing": {
|
||||
"cacheRead": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.05
|
||||
},
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.05
|
||||
"perMillionTokens": 0.117
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.4
|
||||
"perMillionTokens": 0.455
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -16276,11 +16343,11 @@
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.08
|
||||
"perMillionTokens": 0.117
|
||||
},
|
||||
"output": {
|
||||
"currency": "USD",
|
||||
"perMillionTokens": 0.5
|
||||
"perMillionTokens": 0.455
|
||||
}
|
||||
},
|
||||
"providerId": "openrouter"
|
||||
@@ -17452,6 +17519,7 @@
|
||||
},
|
||||
"modelId": "qwen3-235b-a22b-thinking",
|
||||
"modelVariants": ["235b"],
|
||||
"name": "Qwen3 235B A22B Thinking 2507",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "CNY",
|
||||
@@ -18382,7 +18450,7 @@
|
||||
},
|
||||
{
|
||||
"apiModelId": "Qwen/Qwen3-235B-A22B-Thinking-2507",
|
||||
"modelId": "qwen3-235b-a22b-thinking",
|
||||
"modelId": "qwen3-235b-a22b",
|
||||
"pricing": {
|
||||
"input": {
|
||||
"currency": "USD",
|
||||
@@ -19680,5 +19748,5 @@
|
||||
"providerId": "zhipu"
|
||||
}
|
||||
],
|
||||
"version": "2026.07.01"
|
||||
"version": "39112f1353bd9bbb"
|
||||
}
|
||||
|
||||
68
packages/provider-registry/data/providers.json
generated
68
packages/provider-registry/data/providers.json
generated
@@ -269,29 +269,6 @@
|
||||
},
|
||||
"name": "302.AI"
|
||||
},
|
||||
{
|
||||
"apiFeatures": {
|
||||
"arrayContent": false
|
||||
},
|
||||
"defaultChatEndpoint": "openai-chat-completions",
|
||||
"description": "Cephalon - AI model provider",
|
||||
"endpointConfigs": {
|
||||
"openai-chat-completions": {
|
||||
"adapterFamily": "openai-compatible",
|
||||
"baseUrl": "https://cephalon.cloud/user-center/v1/model"
|
||||
}
|
||||
},
|
||||
"id": "cephalon",
|
||||
"metadata": {
|
||||
"website": {
|
||||
"apiKey": "https://cephalon.cloud/api",
|
||||
"docs": "https://cephalon.cloud/",
|
||||
"models": "https://cephalon.cloud/model",
|
||||
"official": "https://cephalon.cloud/"
|
||||
}
|
||||
},
|
||||
"name": "Cephalon"
|
||||
},
|
||||
{
|
||||
"defaultChatEndpoint": "openai-chat-completions",
|
||||
"description": "LANYUN - AI model provider",
|
||||
@@ -849,6 +826,9 @@
|
||||
"name": "MiniMax"
|
||||
},
|
||||
{
|
||||
"apiFeatures": {
|
||||
"serviceTier": true
|
||||
},
|
||||
"defaultChatEndpoint": "openai-chat-completions",
|
||||
"description": "Groq - AI model provider",
|
||||
"endpointConfigs": {
|
||||
@@ -1081,6 +1061,46 @@
|
||||
},
|
||||
"name": "Xirang"
|
||||
},
|
||||
{
|
||||
"defaultChatEndpoint": "openai-chat-completions",
|
||||
"description": "hunyuan - AI model provider",
|
||||
"endpointConfigs": {
|
||||
"openai-chat-completions": {
|
||||
"adapterFamily": "openai-compatible",
|
||||
"baseUrl": "https://api.hunyuan.cloud.tencent.com"
|
||||
}
|
||||
},
|
||||
"id": "hunyuan",
|
||||
"metadata": {
|
||||
"website": {
|
||||
"apiKey": "https://console.cloud.tencent.com/hunyuan/api-key",
|
||||
"docs": "https://cloud.tencent.com/document/product/1729/111007",
|
||||
"models": "https://cloud.tencent.com/document/product/1729/104753",
|
||||
"official": "https://cloud.tencent.com/product/hunyuan"
|
||||
}
|
||||
},
|
||||
"name": "hunyuan"
|
||||
},
|
||||
{
|
||||
"defaultChatEndpoint": "openai-chat-completions",
|
||||
"description": "Tencent Cloud TI - AI model provider",
|
||||
"endpointConfigs": {
|
||||
"openai-chat-completions": {
|
||||
"adapterFamily": "openai-compatible",
|
||||
"baseUrl": "https://api.lkeap.cloud.tencent.com"
|
||||
}
|
||||
},
|
||||
"id": "tencent-cloud-ti",
|
||||
"metadata": {
|
||||
"website": {
|
||||
"apiKey": "https://console.cloud.tencent.com/lkeap/api",
|
||||
"docs": "https://cloud.tencent.com/document/product/1772",
|
||||
"models": "https://console.cloud.tencent.com/tione/v2/aimarket",
|
||||
"official": "https://cloud.tencent.com/product/ti"
|
||||
}
|
||||
},
|
||||
"name": "Tencent Cloud TI"
|
||||
},
|
||||
{
|
||||
"defaultChatEndpoint": "openai-chat-completions",
|
||||
"description": "TokenHub - AI model provider",
|
||||
@@ -1369,5 +1389,5 @@
|
||||
"presetProviderId": "minimax"
|
||||
}
|
||||
],
|
||||
"version": "2026.07.01"
|
||||
"version": "0544e8f799859c83"
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ import {
|
||||
stripAggregatorPrefixes,
|
||||
stripBedrockRevision,
|
||||
stripBedrockVendorPrefix,
|
||||
stripDateSnapshot,
|
||||
stripQuantization,
|
||||
stripVariantSuffixes
|
||||
stripVariantQuantDateSuffixes
|
||||
} from '../src/utils/normalize'
|
||||
|
||||
// strip the same org/host routing prefixes the runtime resolver does (zai-org-, databricks-, …),
|
||||
@@ -22,15 +20,12 @@ import {
|
||||
// bedrock cross-vendor `[region.]vendor.` / `vendor-` prefix (shared with the runtime).
|
||||
const base = (id: string) => stripBedrockVendorPrefix(stripAggregatorPrefixes(id.toLowerCase().split('/').pop()!))
|
||||
|
||||
// Minus param-size stripping — the catalog keeps `qwen3-235b` ≠ `qwen3-30b`. Order matters: strip the
|
||||
// `-thinking`/`-free` variant BEFORE the date so the date ends the token.
|
||||
// Minus param-size stripping — the catalog keeps `qwen3-235b` ≠ `qwen3-30b`.
|
||||
export const canonOf = (id: string): string => {
|
||||
let s = base(id) // split('/').pop, lowercase, strip aggregator + bedrock-vendor prefix
|
||||
s = stripBedrockRevision(s) // bedrock arn revision: claude-…-v1:0 / …:0 (keeps whisper-v3)
|
||||
s = expandKnownPrefixes(s) // mm- → minimax-
|
||||
s = stripVariantSuffixes(s) // -free / -thinking / -tee / -low / :free / (free) …
|
||||
s = stripQuantization(s) // -fp8 / -fp16 / -awq …
|
||||
s = stripDateSnapshot(s) // trailing release-date stamps + @tag (shared with the runtime resolver)
|
||||
s = stripVariantQuantDateSuffixes(s) // variant/quant/date suffixes, iterated to a fixpoint
|
||||
s = normalizeVersionSeparators(s) // 4.6 → 4-6
|
||||
return s
|
||||
.replace(/[^a-z0-9-]/g, '-')
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* tsx scripts/generate-catalog.ts --write # write both JSON files
|
||||
* tsx scripts/generate-catalog.ts --report # also dump /tmp/gen-*.txt review files
|
||||
*/
|
||||
import { createHash } from 'node:crypto'
|
||||
import * as fs from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -38,9 +39,19 @@ const PROVIDERS_PATH = path.join(__dirname, '../data/providers.json')
|
||||
const PROVIDER_MODELS_PATH = path.join(__dirname, '../data/provider-models.json')
|
||||
const WRITE = process.argv.includes('--write')
|
||||
const REPORT = process.argv.includes('--report')
|
||||
// Stamp generated files with the generation date (YYYY.MM.DD). Upstream (models.dev/OpenRouter) is read
|
||||
// live by default; set MODELSDEV_CACHE / OPENROUTER_CACHE to a local file to cache it during dev.
|
||||
const VERSION = new Date().toISOString().slice(0, 10).replace(/-/g, '.')
|
||||
// Each artifact's `version` is a hash of its own (version-less, key-sorted) content: equal content ⇒
|
||||
// equal version, ANY content change ⇒ new version. Seeders (`PresetProviderSeeder` via `SeedRunner`)
|
||||
// skip when the journal version matches, so a date stamp would let a same-day regeneration change
|
||||
// content without changing version — and the seed would silently never run. Upstream (models.dev/
|
||||
// OpenRouter) is read live by default; set MODELSDEV_CACHE / OPENROUTER_CACHE to cache it during dev.
|
||||
const contentVersion = (body: unknown): string =>
|
||||
createHash('sha256').update(JSON.stringify(body)).digest('hex').slice(0, 16)
|
||||
|
||||
/** Key-sort `body`, stamp `version: contentVersion(body)`, and serialize — the single write shape. */
|
||||
const stampAndSerialize = (body: Record<string, unknown>): string => {
|
||||
const sorted = sortKeys(body)
|
||||
return JSON.stringify(sortKeys({ ...sorted, version: contentVersion(sorted) }), null, 2) + '\n'
|
||||
}
|
||||
|
||||
// Canonicalization (`canonOf`) + prefix matching (`prefixHit`) live in `./canonicalize` so they can be
|
||||
// unit-tested / reused without running this script's generation IIFE.
|
||||
@@ -214,9 +225,13 @@ function buildModels(index: Index, claimed: Map<string, string>): Map<string, an
|
||||
// which of its models carry it, as DATA, via `webSearch` id-prefixes. Union onto upstream capabilities.
|
||||
const creatorWebSearch = new Map(CREATORS.map((l) => [l.id, l.webSearch ?? []]))
|
||||
for (const m of models.values()) {
|
||||
// An image-generation model never inherits a text sibling's web search just for sharing its prefix
|
||||
// (`gpt-5-image*` ride the `gpt-5` prefix). web-search is a text capability — skip the image rows.
|
||||
// web-search is a TEXT-CHAT capability: a non-chat SKU never inherits it just for sharing a chat
|
||||
// sibling's prefix. Skip image rows (`gpt-5-image*` ride `gpt-5`; they output text too, so the
|
||||
// modality gate alone won't catch them) and any row that doesn't converse in text on both sides —
|
||||
// TTS (text→audio) and transcription (audio→text). Hand-listed capabilities are unaffected.
|
||||
if ((m.capabilities ?? []).includes('image-generation')) continue
|
||||
if (!(m.inputModalities ?? ['text']).includes('text') || !(m.outputModalities ?? ['text']).includes('text'))
|
||||
continue
|
||||
if ((creatorWebSearch.get(m.ownedBy) ?? []).some((p) => prefixHit(m.id, p)))
|
||||
m.capabilities = [...new Set([...(m.capabilities ?? []), 'web-search'])]
|
||||
}
|
||||
@@ -228,13 +243,12 @@ function buildModels(index: Index, claimed: Map<string, string>): Map<string, an
|
||||
* `fetchModels` / `overrides` are dropped), with `description` templated as `"{name} - AI model provider"`.
|
||||
* Array order follows PROVIDERS; `sortKeys` orders each provider's keys.
|
||||
*/
|
||||
function buildProviders(): { providers: ProviderEntry[]; version: string } {
|
||||
function buildProviders(): ProviderEntry[] {
|
||||
// oxlint-disable-next-line no-unused-vars
|
||||
const providers = PROVIDERS.map(({ modelsDevProvider, fetchModels, overrides, ...conn }) => ({
|
||||
return PROVIDERS.map(({ modelsDevProvider, fetchModels, overrides, ...conn }) => ({
|
||||
...conn,
|
||||
description: `${conn.name} - AI model provider`
|
||||
}))
|
||||
return { providers, version: VERSION }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,7 +258,7 @@ function buildProviders(): { providers: ProviderEntry[]; version: string } {
|
||||
* `modelsDevProvider`, one row per served model carrying that listing's PRICING. `modelId` resolves to a
|
||||
* base row or is standalone with a `name`.
|
||||
*/
|
||||
function buildProviderModels(md: ModelsDevApi, baseIds: Set<string>): { root: any; rows: number } {
|
||||
function buildProviderModels(md: ModelsDevApi, baseIds: Set<string>): { overrides: any[] } {
|
||||
const seen = new Set<string>()
|
||||
const rows: any[] = []
|
||||
const variantsKey = (o: any): string => (o.modelVariants ?? []).slice().sort().join(',')
|
||||
@@ -283,7 +297,7 @@ function buildProviderModels(md: ModelsDevApi, baseIds: Set<string>): { root: an
|
||||
}
|
||||
}
|
||||
rows.sort((a, b) => `${a.providerId} ${a.modelId}`.localeCompare(`${b.providerId} ${b.modelId}`))
|
||||
return { root: { overrides: rows, version: VERSION }, rows: rows.length }
|
||||
return { overrides: rows }
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
@@ -324,14 +338,14 @@ void (async () => {
|
||||
const { metadata, ...rest } = m
|
||||
return { ...rest, ...(metadata ? { metadata } : {}) }
|
||||
})
|
||||
fs.writeFileSync(MODELS_PATH, JSON.stringify(sortKeys({ models: list, version: VERSION }), null, 2) + '\n')
|
||||
fs.writeFileSync(MODELS_PATH, stampAndSerialize({ models: list }))
|
||||
console.log(`\nWROTE ${MODELS_PATH} (${list.length} models).`)
|
||||
|
||||
const providers = buildProviders()
|
||||
fs.writeFileSync(PROVIDERS_PATH, JSON.stringify(sortKeys(providers), null, 2) + '\n')
|
||||
console.log(`WROTE ${PROVIDERS_PATH} (${providers.providers.length} providers).`)
|
||||
fs.writeFileSync(PROVIDERS_PATH, stampAndSerialize({ providers }))
|
||||
console.log(`WROTE ${PROVIDERS_PATH} (${providers.length} providers).`)
|
||||
|
||||
const pm = buildProviderModels(md, new Set(models.keys()))
|
||||
fs.writeFileSync(PROVIDER_MODELS_PATH, JSON.stringify(sortKeys(pm.root), null, 2) + '\n')
|
||||
console.log(`WROTE ${PROVIDER_MODELS_PATH} (${pm.rows} rows).`)
|
||||
fs.writeFileSync(PROVIDER_MODELS_PATH, stampAndSerialize(pm))
|
||||
console.log(`WROTE ${PROVIDER_MODELS_PATH} (${pm.overrides.length} rows).`)
|
||||
})()
|
||||
|
||||
@@ -12,6 +12,7 @@ import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { canonOf } from '../../scripts/canonicalize'
|
||||
import { ModelListSchema } from '../schemas/model'
|
||||
import { ProviderModelListSchema } from '../schemas/provider-models'
|
||||
|
||||
@@ -23,10 +24,13 @@ const models = modelsRaw.models as Array<{
|
||||
contextWindow?: number
|
||||
maxOutputTokens?: number
|
||||
capabilities?: string[]
|
||||
inputModalities?: string[]
|
||||
outputModalities?: string[]
|
||||
}>
|
||||
const overrides = providerModelsRaw.overrides as Array<{
|
||||
providerId: string
|
||||
modelId: string
|
||||
apiModelId?: string
|
||||
name?: string
|
||||
}>
|
||||
|
||||
@@ -51,6 +55,13 @@ describe('catalog invariants (data/*.json)', () => {
|
||||
expect(ids.filter((id) => JUNK.test(id))).toEqual([])
|
||||
})
|
||||
|
||||
// Canonicalization must be idempotent on the artifact itself: a base id the canonicalizer would
|
||||
// still fold (id !== canonOf(id)) means a variant/date row leaked through a non-fixpoint strip pass
|
||||
// and coexists with (or shadows) its true base row.
|
||||
it('every base id is a canonicalization fixpoint (id === canonOf(id))', () => {
|
||||
expect(ids.filter((id) => canonOf(id) !== id)).toEqual([])
|
||||
})
|
||||
|
||||
it('every override resolves to a base row or carries a standalone name', () => {
|
||||
const broken = overrides
|
||||
.filter((o) => !baseIds.has(o.modelId) && !o.name)
|
||||
@@ -71,6 +82,34 @@ describe('catalog invariants (data/*.json)', () => {
|
||||
expect(offenders).toEqual([])
|
||||
})
|
||||
|
||||
// web-search is a text-chat capability: a model that doesn't converse in text on both sides (TTS is
|
||||
// text→audio, transcription is audio→text, embedders output vector) must never carry it. The generator
|
||||
// gates prefix-inherited web-search by modality; this catches any row slipping back in.
|
||||
it('no non-text-chat model carries web-search (tts / transcription / embedding)', () => {
|
||||
const offenders = models
|
||||
.filter((m) => m.capabilities?.includes('web-search'))
|
||||
.filter(
|
||||
(m) => !(m.inputModalities ?? ['text']).includes('text') || !(m.outputModalities ?? ['text']).includes('text')
|
||||
)
|
||||
.map((m) => m.id)
|
||||
expect(offenders).toEqual([])
|
||||
})
|
||||
|
||||
// The loader's canonical override index (`overrideByKey`) resolves a duplicated key to the self
|
||||
// variant (`apiModelId === modelId`) — see registry-loader.ts `buildOverrideIndex`. A duplicated key
|
||||
// with NO self variant makes the winning override file-order-dependent, so forbid it.
|
||||
it('every duplicated providerId::modelId key contains a self variant', () => {
|
||||
const byKey = new Map<string, Array<(typeof overrides)[number]>>()
|
||||
for (const o of overrides) {
|
||||
const key = `${o.providerId}::${o.modelId}`
|
||||
byKey.set(key, [...(byKey.get(key) ?? []), o])
|
||||
}
|
||||
const orderDependent = [...byKey.entries()]
|
||||
.filter(([, rows]) => rows.length > 1 && !rows.some((r) => (r.apiModelId ?? r.modelId) === r.modelId))
|
||||
.map(([key]) => key)
|
||||
expect(orderDependent).toEqual([])
|
||||
})
|
||||
|
||||
it('maxOutputTokens never exceeds contextWindow', () => {
|
||||
const bad = models.filter((m) => m.contextWindow && m.maxOutputTokens && m.maxOutputTokens > m.contextWindow)
|
||||
expect(bad.map((m) => m.id)).toEqual([])
|
||||
|
||||
@@ -451,14 +451,6 @@ export default defineProvider({
|
||||
input: { currency: 'USD', perMillionTokens: 0.15 },
|
||||
output: { currency: 'USD', perMillionTokens: 0.35 }
|
||||
}
|
||||
},
|
||||
{
|
||||
modelId: 'gpt-oss-120b',
|
||||
apiModelId: 'openai.gpt-oss-120b-1:0',
|
||||
pricing: {
|
||||
input: { currency: 'USD', perMillionTokens: 0.15 },
|
||||
output: { currency: 'USD', perMillionTokens: 0.6 }
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { openaiCompatible } from './types'
|
||||
|
||||
export default openaiCompatible({
|
||||
id: 'cephalon',
|
||||
name: 'Cephalon',
|
||||
baseUrl: 'https://cephalon.cloud/user-center/v1/model',
|
||||
website: {
|
||||
apiKey: 'https://cephalon.cloud/api',
|
||||
docs: 'https://cephalon.cloud/',
|
||||
models: 'https://cephalon.cloud/model',
|
||||
official: 'https://cephalon.cloud/'
|
||||
},
|
||||
apiFeatures: {
|
||||
arrayContent: false
|
||||
}
|
||||
})
|
||||
@@ -10,6 +10,9 @@ export default defineProvider({
|
||||
baseUrl: 'https://api.groq.com/openai'
|
||||
}
|
||||
},
|
||||
apiFeatures: {
|
||||
serviceTier: true
|
||||
},
|
||||
metadata: {
|
||||
website: {
|
||||
apiKey: 'https://console.groq.com/keys',
|
||||
|
||||
21
packages/provider-registry/src/providers/hunyuan.ts
Normal file
21
packages/provider-registry/src/providers/hunyuan.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineProvider } from './types'
|
||||
|
||||
export default defineProvider({
|
||||
id: 'hunyuan',
|
||||
name: 'hunyuan',
|
||||
defaultChatEndpoint: 'openai-chat-completions',
|
||||
endpointConfigs: {
|
||||
'openai-chat-completions': {
|
||||
adapterFamily: 'openai-compatible',
|
||||
baseUrl: 'https://api.hunyuan.cloud.tencent.com'
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
website: {
|
||||
apiKey: 'https://console.cloud.tencent.com/hunyuan/api-key',
|
||||
docs: 'https://cloud.tencent.com/document/product/1729/111007',
|
||||
models: 'https://cloud.tencent.com/document/product/1729/104753',
|
||||
official: 'https://cloud.tencent.com/product/hunyuan'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -8,7 +8,6 @@ import p_azure_openai from './azure-openai'
|
||||
import p_baichuan from './baichuan'
|
||||
import p_baidu_cloud from './baidu-cloud'
|
||||
import p_burncloud from './burncloud'
|
||||
import p_cephalon from './cephalon'
|
||||
import p_cerebras from './cerebras'
|
||||
import p_cherryin from './cherryin'
|
||||
import p_copilot from './copilot'
|
||||
@@ -24,6 +23,7 @@ import p_gpustack from './gpustack'
|
||||
import p_grok from './grok'
|
||||
import p_groq from './groq'
|
||||
import p_huggingface from './huggingface'
|
||||
import p_hunyuan from './hunyuan'
|
||||
import p_hyperbolic from './hyperbolic'
|
||||
import p_infini from './infini'
|
||||
import p_jina from './jina'
|
||||
@@ -52,6 +52,7 @@ import p_qiniu from './qiniu'
|
||||
import p_silicon from './silicon'
|
||||
import p_sophnet from './sophnet'
|
||||
import p_stepfun from './stepfun'
|
||||
import p_tencent_cloud_ti from './tencent-cloud-ti'
|
||||
import p_together from './together'
|
||||
import p_tokenhub from './tokenhub'
|
||||
import type { Provider } from './types'
|
||||
@@ -76,7 +77,6 @@ export const PROVIDERS: Provider[] = [
|
||||
p_aionly,
|
||||
p_burncloud,
|
||||
p_302ai,
|
||||
p_cephalon,
|
||||
p_lanyun,
|
||||
p_ph8,
|
||||
p_sophnet,
|
||||
@@ -113,6 +113,8 @@ export const PROVIDERS: Provider[] = [
|
||||
p_perplexity,
|
||||
p_modelscope,
|
||||
p_xirang,
|
||||
p_hunyuan,
|
||||
p_tencent_cloud_ti,
|
||||
p_tokenhub,
|
||||
p_baidu_cloud,
|
||||
p_gpustack,
|
||||
|
||||
@@ -237,6 +237,9 @@ export default openaiCompatible({
|
||||
apiModelId: 'qwen/qwen3-235b-a22b-thinking-2507',
|
||||
limits: { maxOutputTokens: 114688 },
|
||||
modelId: 'qwen3-235b-a22b-thinking',
|
||||
// The canonicalizer folds `-thinking` into the base line, so this id is no longer a base row —
|
||||
// keep the distinctly-priced thinking SKU selectable as a named standalone.
|
||||
name: 'Qwen3 235B A22B Thinking 2507',
|
||||
modelVariants: ['235b'],
|
||||
pricing: { input: { currency: 'CNY', perMillionTokens: 2 }, output: { currency: 'CNY', perMillionTokens: 20 } }
|
||||
},
|
||||
|
||||
21
packages/provider-registry/src/providers/tencent-cloud-ti.ts
Normal file
21
packages/provider-registry/src/providers/tencent-cloud-ti.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineProvider } from './types'
|
||||
|
||||
export default defineProvider({
|
||||
id: 'tencent-cloud-ti',
|
||||
name: 'Tencent Cloud TI',
|
||||
defaultChatEndpoint: 'openai-chat-completions',
|
||||
endpointConfigs: {
|
||||
'openai-chat-completions': {
|
||||
adapterFamily: 'openai-compatible',
|
||||
baseUrl: 'https://api.lkeap.cloud.tencent.com'
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
website: {
|
||||
apiKey: 'https://console.cloud.tencent.com/lkeap/api',
|
||||
docs: 'https://cloud.tencent.com/document/product/1772',
|
||||
models: 'https://console.cloud.tencent.com/tione/v2/aimarket',
|
||||
official: 'https://cloud.tencent.com/product/ti'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -10,10 +10,13 @@ import { CURRENCY, objectValues } from './enums'
|
||||
export const ModelIdSchema = z.string().min(1)
|
||||
export const ProviderIdSchema = z.string().min(1)
|
||||
|
||||
/** Version string (e.g., "2026-03-09" or "2026.03.09") */
|
||||
export const VersionSchema = z.string().regex(/^\d{4}[-./]\d{2}[-./]\d{2}$/, {
|
||||
message: 'Version must be a date-like string (e.g., YYYY-MM-DD or YYYY.MM.DD)'
|
||||
})
|
||||
/**
|
||||
* Opaque change-detection token for a generated artifact. The generator stamps it with a hash of the
|
||||
* artifact's content, so equal content ⇒ equal version and ANY content change ⇒ new version — that's
|
||||
* the contract seeders rely on (`SeedRunner` skips a seeder whose journal version matches). Older
|
||||
* artifacts carried a generation date; treat the value as opaque, never parse it.
|
||||
*/
|
||||
export const VersionSchema = z.string().min(1)
|
||||
|
||||
/** ISO 8601 datetime timestamp */
|
||||
export const ISOTimestampSchema = z.iso.datetime()
|
||||
|
||||
@@ -80,7 +80,9 @@ export const HYPHEN_VARIANT_SUFFIXES = [
|
||||
'-low',
|
||||
'-high',
|
||||
'-minimal',
|
||||
'-medium',
|
||||
// NOTE: `-medium` is intentionally NOT here — it's a real model-tier name (`mistral-medium`,
|
||||
// `devstral-medium`), so stripping it as a reasoning-effort variant eats the tier and produces
|
||||
// bogus stems (`mistral`, `devstral`).
|
||||
'-nothink',
|
||||
'-no-think',
|
||||
'-ssvip',
|
||||
@@ -251,6 +253,21 @@ export function stripDateSnapshot(modelId: string): string {
|
||||
return modelId.replace(/@.*$/, '').replace(DATE_SNAPSHOT_PATTERN, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate variant → quantization → date stripping to a fixpoint. A single pass is order-dependent —
|
||||
* a trailing date shields an inner variant from the `endsWith` check (`…-thinking-2507` only exposes
|
||||
* `-thinking` after the date is gone) — so without the loop canonicalization is not idempotent.
|
||||
* SHARED by the build canonicalizer (`scripts/canonicalize.ts`) and the runtime resolver below.
|
||||
*/
|
||||
export function stripVariantQuantDateSuffixes(modelId: string): string {
|
||||
let result = modelId
|
||||
for (;;) {
|
||||
const next = stripDateSnapshot(stripQuantization(stripVariantSuffixes(result)))
|
||||
if (next === result) return result
|
||||
result = next
|
||||
}
|
||||
}
|
||||
|
||||
export function extractParameterSize(modelId: string): string | undefined {
|
||||
const match = modelId.match(PARAMETER_SIZE_PATTERN)
|
||||
return match ? match[1].toLowerCase() : undefined
|
||||
@@ -273,10 +290,13 @@ export function normalizeModelId(modelId: string): string {
|
||||
baseName = stripBedrockVendorPrefix(baseName)
|
||||
baseName = stripBedrockRevision(baseName)
|
||||
baseName = expandKnownPrefixes(baseName)
|
||||
baseName = stripVariantSuffixes(baseName)
|
||||
baseName = stripQuantization(baseName)
|
||||
baseName = stripDateSnapshot(baseName)
|
||||
baseName = stripParameterSize(baseName)
|
||||
// Parameter size joins the fixpoint loop too: stripping `-30b` can expose a variant suffix and
|
||||
// vice versa, so iterate the whole strip stage until stable.
|
||||
for (;;) {
|
||||
const next = stripParameterSize(stripVariantQuantDateSuffixes(baseName))
|
||||
if (next === baseName) break
|
||||
baseName = next
|
||||
}
|
||||
baseName = normalizeVersionSeparators(baseName)
|
||||
// Underscores are an interchangeable separator (HF-style `bce-embedding-base_v1`). The catalog folds
|
||||
// them to `-` (every base id is dash-only), so fold here too or such ids would never resolve.
|
||||
|
||||
Reference in New Issue
Block a user