@linxiraos/pi-catalog 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -8
- package/README.md +9 -8
- package/dist/types/build.d.ts +13 -8
- package/dist/types/cline-pass-model-id.d.ts +4 -0
- package/dist/types/compat/anthropic.d.ts +10 -4
- package/dist/types/compat/auth-ids.d.ts +4 -0
- package/dist/types/compat/auth.d.ts +7 -0
- package/dist/types/compat/axes.d.ts +50 -0
- package/dist/types/compat/behavior.d.ts +69 -0
- package/dist/types/compat/cascade.d.ts +37 -0
- package/dist/types/{variant-collapse.d.ts → compat/collapse.d.ts} +60 -35
- package/dist/types/compat/openai.d.ts +7 -26
- package/dist/types/compat/resolve.d.ts +15 -0
- package/dist/types/compat/revision.d.ts +41 -0
- package/dist/types/compat/taxonomy.d.ts +93 -0
- package/dist/types/compat/types.d.ts +565 -0
- package/dist/types/discovery/antigravity.d.ts +1 -1
- package/dist/types/discovery/cursor-proto.d.ts +6 -6
- package/dist/types/discovery/cursor.d.ts +5 -3
- package/dist/types/discovery/devin-proto.d.ts +363 -0
- package/dist/types/discovery/gemini-cli.d.ts +1 -1
- package/dist/types/identity/id.d.ts +1 -0
- package/dist/types/identity/index.d.ts +3 -3
- package/dist/types/identity/reference.d.ts +0 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model-thinking.d.ts +6 -24
- package/dist/types/provider-models/cline-pass.d.ts +22 -0
- package/dist/types/provider-models/descriptors.d.ts +20 -1
- package/dist/types/provider-models/models-dev-policies.d.ts +2 -1
- package/dist/types/provider-models/openai-compat.d.ts +59 -46
- package/dist/types/provider-models/special.d.ts +23 -2
- package/dist/types/types.d.ts +177 -13
- package/dist/types/wire/cline-pass.d.ts +21 -0
- package/dist/types/wire/devin-proto.d.ts +14 -0
- package/dist/types/wire/devin.d.ts +36 -0
- package/dist/types/wire/github-copilot.d.ts +31 -12
- package/package.json +6 -4
- package/src/build.ts +165 -48
- package/src/cline-pass-model-id.ts +11 -0
- package/src/compat/anthropic.ts +15 -136
- package/src/compat/auth-ids.ts +157 -0
- package/src/compat/auth.ts +60 -0
- package/src/compat/axes.ts +290 -0
- package/src/compat/behavior.ts +207 -0
- package/src/compat/cascade.ts +230 -0
- package/src/{variant-collapse.ts → compat/collapse.ts} +381 -773
- package/src/compat/openai.ts +17 -848
- package/src/compat/resolve.ts +1229 -0
- package/src/compat/revision.ts +154 -0
- package/src/compat/rules/README.md +315 -0
- package/src/compat/rules/auth/_order.kdl +13 -0
- package/src/compat/rules/auth/abliteration.kdl +9 -0
- package/src/compat/rules/auth/aiand.kdl +9 -0
- package/src/compat/rules/auth/aimlapi.kdl +3 -0
- package/src/compat/rules/auth/alibaba-coding-plan.kdl +5 -0
- package/src/compat/rules/auth/alibaba-token-plan.kdl +11 -0
- package/src/compat/rules/auth/amazon-bedrock.kdl +5 -0
- package/src/compat/rules/auth/anthropic.kdl +53 -0
- package/src/compat/rules/auth/azure.kdl +3 -0
- package/src/compat/rules/auth/baseten.kdl +9 -0
- package/src/compat/rules/auth/bedrock-mantle.kdl +5 -0
- package/src/compat/rules/auth/cerebras.kdl +9 -0
- package/src/compat/rules/auth/cline-pass.kdl +12 -0
- package/src/compat/rules/auth/cloudflare-ai-gateway.kdl +4 -0
- package/src/compat/rules/auth/coreweave.kdl +9 -0
- package/src/compat/rules/auth/cursor.kdl +5 -0
- package/src/compat/rules/auth/deepinfra.kdl +11 -0
- package/src/compat/rules/auth/deepseek.kdl +10 -0
- package/src/compat/rules/auth/devin.kdl +28 -0
- package/src/compat/rules/auth/exa.kdl +9 -0
- package/src/compat/rules/auth/firepass.kdl +14 -0
- package/src/compat/rules/auth/fireworks.kdl +16 -0
- package/src/compat/rules/auth/github-copilot.kdl +7 -0
- package/src/compat/rules/auth/gitlab-duo-agent.kdl +23 -0
- package/src/compat/rules/auth/gitlab-duo.kdl +24 -0
- package/src/compat/rules/auth/gmi-cloud.kdl +9 -0
- package/src/compat/rules/auth/google-antigravity.kdl +28 -0
- package/src/compat/rules/auth/google-gemini-cli.kdl +26 -0
- package/src/compat/rules/auth/google-vertex.kdl +5 -0
- package/src/compat/rules/auth/google.kdl +3 -0
- package/src/compat/rules/auth/groq.kdl +3 -0
- package/src/compat/rules/auth/huggingface.kdl +9 -0
- package/src/compat/rules/auth/kagi.kdl +9 -0
- package/src/compat/rules/auth/kilo.kdl +4 -0
- package/src/compat/rules/auth/kimi-code.kdl +18 -0
- package/src/compat/rules/auth/litellm.kdl +8 -0
- package/src/compat/rules/auth/llama.cpp.kdl +10 -0
- package/src/compat/rules/auth/lm-studio.kdl +7 -0
- package/src/compat/rules/auth/meta.kdl +9 -0
- package/src/compat/rules/auth/minimax-code-cn.kdl +9 -0
- package/src/compat/rules/auth/minimax-code.kdl +9 -0
- package/src/compat/rules/auth/minimax.kdl +3 -0
- package/src/compat/rules/auth/mistral.kdl +3 -0
- package/src/compat/rules/auth/moonshot.kdl +9 -0
- package/src/compat/rules/auth/nanogpt.kdl +9 -0
- package/src/compat/rules/auth/novita.kdl +12 -0
- package/src/compat/rules/auth/nvidia.kdl +11 -0
- package/src/compat/rules/auth/ollama-cloud.kdl +8 -0
- package/src/compat/rules/auth/ollama.kdl +9 -0
- package/src/compat/rules/auth/openai-codex-device.kdl +20 -0
- package/src/compat/rules/auth/openai-codex.kdl +33 -0
- package/src/compat/rules/auth/openai.kdl +3 -0
- package/src/compat/rules/auth/opencode-go.kdl +10 -0
- package/src/compat/rules/auth/opencode-zen.kdl +9 -0
- package/src/compat/rules/auth/openrouter.kdl +32 -0
- package/src/compat/rules/auth/parallel.kdl +9 -0
- package/src/compat/rules/auth/perplexity.kdl +7 -0
- package/src/compat/rules/auth/qianfan.kdl +9 -0
- package/src/compat/rules/auth/qwen-portal.kdl +9 -0
- package/src/compat/rules/auth/sakana.kdl +9 -0
- package/src/compat/rules/auth/siliconflow-cn.kdl +9 -0
- package/src/compat/rules/auth/siliconflow.kdl +9 -0
- package/src/compat/rules/auth/synthetic.kdl +9 -0
- package/src/compat/rules/auth/tavily.kdl +9 -0
- package/src/compat/rules/auth/together.kdl +14 -0
- package/src/compat/rules/auth/umans.kdl +9 -0
- package/src/compat/rules/auth/venice.kdl +9 -0
- package/src/compat/rules/auth/vercel-ai-gateway.kdl +8 -0
- package/src/compat/rules/auth/vllm.kdl +9 -0
- package/src/compat/rules/auth/wafer-serverless.kdl +11 -0
- package/src/compat/rules/auth/xai-oauth.kdl +27 -0
- package/src/compat/rules/auth/xai.kdl +9 -0
- package/src/compat/rules/auth/xiaomi-token-plan-ams.kdl +9 -0
- package/src/compat/rules/auth/xiaomi-token-plan-cn.kdl +9 -0
- package/src/compat/rules/auth/xiaomi-token-plan-sgp.kdl +9 -0
- package/src/compat/rules/auth/xiaomi.kdl +6 -0
- package/src/compat/rules/auth/yolo-auto.kdl +11 -0
- package/src/compat/rules/auth/zai-coding-plan.kdl +29 -0
- package/src/compat/rules/auth/zai.kdl +9 -0
- package/src/compat/rules/auth/zenmux.kdl +9 -0
- package/src/compat/rules/auth/zhipu-coding-plan.kdl +9 -0
- package/src/compat/rules/classes/amazon.kdl +21 -0
- package/src/compat/rules/classes/anthropic.kdl +285 -0
- package/src/compat/rules/classes/baidu.kdl +9 -0
- package/src/compat/rules/classes/bytedance.kdl +9 -0
- package/src/compat/rules/classes/cohere.kdl +7 -0
- package/src/compat/rules/classes/deepseek.kdl +80 -0
- package/src/compat/rules/classes/gemini.kdl +107 -0
- package/src/compat/rules/classes/gemma.kdl +14 -0
- package/src/compat/rules/classes/glm.kdl +115 -0
- package/src/compat/rules/classes/gpt-oss.kdl +16 -0
- package/src/compat/rules/classes/kimi.kdl +113 -0
- package/src/compat/rules/classes/meta.kdl +15 -0
- package/src/compat/rules/classes/mimo.kdl +32 -0
- package/src/compat/rules/classes/minimax.kdl +79 -0
- package/src/compat/rules/classes/mistral.kdl +19 -0
- package/src/compat/rules/classes/openai.kdl +42 -0
- package/src/compat/rules/classes/qwen.kdl +72 -0
- package/src/compat/rules/classes/stepfun.kdl +10 -0
- package/src/compat/rules/classes/xai.kdl +45 -0
- package/src/compat/rules/providers/abliteration.kdl +8 -0
- package/src/compat/rules/providers/aiand.kdl +19 -0
- package/src/compat/rules/providers/aimlapi.kdl +24 -0
- package/src/compat/rules/providers/alibaba-coding-plan.kdl +20 -0
- package/src/compat/rules/providers/alibaba-token-plan.kdl +35 -0
- package/src/compat/rules/providers/amazon-bedrock.kdl +36 -0
- package/src/compat/rules/providers/anthropic.kdl +24 -0
- package/src/compat/rules/providers/azure.kdl +19 -0
- package/src/compat/rules/providers/baseten.kdl +19 -0
- package/src/compat/rules/providers/bedrock-mantle.kdl +15 -0
- package/src/compat/rules/providers/cerebras.kdl +19 -0
- package/src/compat/rules/providers/cline-pass.kdl +8 -0
- package/src/compat/rules/providers/cloudflare-ai-gateway.kdl +26 -0
- package/src/compat/rules/providers/coreweave.kdl +16 -0
- package/src/compat/rules/providers/cursor.kdl +52 -0
- package/src/compat/rules/providers/deepseek.kdl +21 -0
- package/src/compat/rules/providers/firepass.kdl +15 -0
- package/src/compat/rules/providers/fireworks.kdl +43 -0
- package/src/compat/rules/providers/github-copilot.kdl +62 -0
- package/src/compat/rules/providers/gitlab-duo.kdl +24 -0
- package/src/compat/rules/providers/gmi-cloud.kdl +8 -0
- package/src/compat/rules/providers/google-antigravity.kdl +59 -0
- package/src/compat/rules/providers/google-gemini-cli.kdl +12 -0
- package/src/compat/rules/providers/google-vertex.kdl +25 -0
- package/src/compat/rules/providers/google.kdl +17 -0
- package/src/compat/rules/providers/groq.kdl +8 -0
- package/src/compat/rules/providers/huggingface.kdl +24 -0
- package/src/compat/rules/providers/kilo.kdl +51 -0
- package/src/compat/rules/providers/kimi-code.kdl +37 -0
- package/src/compat/rules/providers/llama.cpp.kdl +6 -0
- package/src/compat/rules/providers/lm-studio.kdl +6 -0
- package/src/compat/rules/providers/meta.kdl +10 -0
- package/src/compat/rules/providers/minimax-code-cn.kdl +13 -0
- package/src/compat/rules/providers/minimax-code.kdl +13 -0
- package/src/compat/rules/providers/minimax.kdl +6 -0
- package/src/compat/rules/providers/mistral.kdl +8 -0
- package/src/compat/rules/providers/moonshot.kdl +36 -0
- package/src/compat/rules/providers/nanogpt.kdl +100 -0
- package/src/compat/rules/providers/novita.kdl +34 -0
- package/src/compat/rules/providers/nvidia.kdl +27 -0
- package/src/compat/rules/providers/ollama-cloud.kdl +20 -0
- package/src/compat/rules/providers/ollama.kdl +12 -0
- package/src/compat/rules/providers/openai-codex.kdl +121 -0
- package/src/compat/rules/providers/openai.kdl +82 -0
- package/src/compat/rules/providers/opencode-go.kdl +97 -0
- package/src/compat/rules/providers/opencode-zen.kdl +103 -0
- package/src/compat/rules/providers/openrouter.kdl +66 -0
- package/src/compat/rules/providers/qianfan.kdl +8 -0
- package/src/compat/rules/providers/sakana.kdl +10 -0
- package/src/compat/rules/providers/synthetic.kdl +16 -0
- package/src/compat/rules/providers/together.kdl +22 -0
- package/src/compat/rules/providers/umans.kdl +28 -0
- package/src/compat/rules/providers/venice.kdl +46 -0
- package/src/compat/rules/providers/vercel-ai-gateway.kdl +73 -0
- package/src/compat/rules/providers/vllm.kdl +8 -0
- package/src/compat/rules/providers/wafer-serverless.kdl +35 -0
- package/src/compat/rules/providers/xai-oauth.kdl +48 -0
- package/src/compat/rules/providers/xai.kdl +50 -0
- package/src/compat/rules/providers/xiaomi-token-plan-ams.kdl +9 -0
- package/src/compat/rules/providers/xiaomi-token-plan-cn.kdl +9 -0
- package/src/compat/rules/providers/xiaomi-token-plan-sgp.kdl +9 -0
- package/src/compat/rules/providers/xiaomi.kdl +22 -0
- package/src/compat/rules/providers/yolo-auto.kdl +16 -0
- package/src/compat/rules/providers/zai.kdl +36 -0
- package/src/compat/rules/providers/zenmux.kdl +108 -0
- package/src/compat/rules/providers/zhipu-coding-plan.kdl +49 -0
- package/src/compat/rules/runtime/behavior.kdl +254 -0
- package/src/compat/rules/taxonomy/_collapse.kdl +747 -0
- package/src/compat/rules/taxonomy/_discovery.kdl +60 -0
- package/src/compat/rules/taxonomy/ai21.kdl +6 -0
- package/src/compat/rules/taxonomy/amazon.kdl +8 -0
- package/src/compat/rules/taxonomy/anthropic.kdl +17 -0
- package/src/compat/rules/taxonomy/baidu.kdl +5 -0
- package/src/compat/rules/taxonomy/bytedance.kdl +5 -0
- package/src/compat/rules/taxonomy/cohere.kdl +6 -0
- package/src/compat/rules/taxonomy/deepseek.kdl +18 -0
- package/src/compat/rules/taxonomy/gemini.kdl +9 -0
- package/src/compat/rules/taxonomy/gemma.kdl +3 -0
- package/src/compat/rules/taxonomy/glm.kdl +17 -0
- package/src/compat/rules/taxonomy/gpt-oss.kdl +11 -0
- package/src/compat/rules/taxonomy/kimi.kdl +20 -0
- package/src/compat/rules/taxonomy/meta.kdl +10 -0
- package/src/compat/rules/taxonomy/mimo.kdl +7 -0
- package/src/compat/rules/taxonomy/minimax.kdl +9 -0
- package/src/compat/rules/taxonomy/mistral.kdl +7 -0
- package/src/compat/rules/taxonomy/openai.kdl +43 -0
- package/src/compat/rules/taxonomy/qwen.kdl +33 -0
- package/src/compat/rules/taxonomy/stepfun.kdl +5 -0
- package/src/compat/rules/taxonomy/unknown.kdl +3 -0
- package/src/compat/rules/taxonomy/xai.kdl +12 -0
- package/src/compat/rules.d.json.ts +5 -0
- package/src/compat/rules.json +16933 -0
- package/src/compat/taxonomy.ts +452 -0
- package/src/compat/types.ts +586 -0
- package/src/discovery/antigravity.ts +5 -6
- package/src/discovery/codex.ts +12 -7
- package/src/discovery/cursor-proto.ts +7 -7
- package/src/discovery/cursor.ts +47 -58
- package/src/discovery/devin-proto.ts +613 -0
- package/src/discovery/devin.ts +358 -52
- package/src/discovery/gemini-cli.ts +14 -11
- package/src/discovery/gemini.ts +2 -0
- package/src/identity/dialect.ts +2 -2
- package/src/identity/id.ts +12 -0
- package/src/identity/index.ts +3 -3
- package/src/identity/reference.ts +30 -4
- package/src/index.ts +1 -1
- package/src/model-manager.ts +4 -4
- package/src/model-thinking.ts +6 -810
- package/src/model-tokenizer.ts +37 -44
- package/src/models.json +86187 -23440
- package/src/models.ts +6 -1
- package/src/provider-models/bundled-references.ts +3 -1
- package/src/provider-models/cline-pass.ts +226 -0
- package/src/provider-models/descriptors.ts +18 -0
- package/src/provider-models/google.ts +14 -5
- package/src/provider-models/models-dev-policies.ts +4 -29
- package/src/provider-models/ollama.ts +15 -2
- package/src/provider-models/openai-compat.ts +794 -520
- package/src/provider-models/special.ts +186 -2
- package/src/types.ts +197 -11
- package/src/wire/cline-pass.ts +35 -0
- package/src/wire/devin-proto.ts +30 -0
- package/src/wire/devin.ts +72 -0
- package/src/wire/github-copilot.ts +49 -7
- package/dist/types/compat/bedrock.d.ts +0 -3
- package/dist/types/compat/devin.d.ts +0 -9
- package/dist/types/identity/classify.d.ts +0 -69
- package/dist/types/identity/family.d.ts +0 -243
- package/dist/types/identity/markers.d.ts +0 -4
- package/dist/types/openai-pricing.d.ts +0 -17
- package/src/compat/bedrock.ts +0 -153
- package/src/compat/devin.ts +0 -12
- package/src/identity/classify.ts +0 -242
- package/src/identity/family.ts +0 -496
- package/src/identity/markers.ts +0 -49
- package/src/openai-pricing.ts +0 -29
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { USER_AGENT } from "@linxiraos/pi-utils";
|
|
2
2
|
import * as logger from "@linxiraos/pi-utils/logger";
|
|
3
|
+
import { toClinePassPublicModelId } from "../cline-pass-model-id";
|
|
4
|
+
import {
|
|
5
|
+
apiRouteExactModelIds,
|
|
6
|
+
apiRouteFor,
|
|
7
|
+
isExcludedModel,
|
|
8
|
+
isLikelyOpenAIResponsesId,
|
|
9
|
+
modelLimitsFor,
|
|
10
|
+
pricingPeerFor,
|
|
11
|
+
} from "../compat/behavior";
|
|
3
12
|
import { xaiResponsesReasoningEffortMap } from "../compat/openai";
|
|
13
|
+
import { resolveModelPolicy } from "../compat/resolve";
|
|
14
|
+
import { compareRevision, parseRevision } from "../compat/revision";
|
|
15
|
+
import { billingVariantPlain, classifyModel, discoveryVocabulary } from "../compat/taxonomy";
|
|
4
16
|
import {
|
|
5
17
|
DEFAULT_OPENAI_COMPATIBLE_DISCOVERY_TIMEOUT_MS,
|
|
6
18
|
fetchOpenAICompatibleModels,
|
|
@@ -10,47 +22,46 @@ import {
|
|
|
10
22
|
import { Effort, THINKING_EFFORTS } from "../effort";
|
|
11
23
|
import { FIREWORKS_FAST_SUFFIX, toFireworksPublicModelId } from "../fireworks-model-id";
|
|
12
24
|
import { getBundledModelReferenceIndex } from "../identity/bundled";
|
|
13
|
-
import {
|
|
14
|
-
anthropicModelSupportsThinking,
|
|
15
|
-
isGlm52ReasoningEffortModelId,
|
|
16
|
-
isGlmVisionModelId,
|
|
17
|
-
isGrokReasoningEffortCapable,
|
|
18
|
-
isKimiK3ModelId,
|
|
19
|
-
isKimiModelId,
|
|
20
|
-
isMuseSparkModelId,
|
|
21
|
-
isQwen38PlusTemplateEffortModelId,
|
|
22
|
-
isReasoningGlmModelId,
|
|
23
|
-
} from "../identity/family";
|
|
24
25
|
import { resolveModelReference } from "../identity/reference";
|
|
25
26
|
import type { ModelManagerOptions, ModelsDevFallback } from "../model-manager";
|
|
26
27
|
import { type GeneratedProvider, getBundledModels } from "../models";
|
|
27
|
-
import {
|
|
28
|
-
import type {
|
|
29
|
-
Api,
|
|
30
|
-
FetchImpl,
|
|
31
|
-
LongContextTokenCost,
|
|
32
|
-
Model,
|
|
33
|
-
ModelSpec,
|
|
34
|
-
OpenAICompat,
|
|
35
|
-
Provider,
|
|
36
|
-
ThinkingConfig,
|
|
37
|
-
} from "../types";
|
|
28
|
+
import type { Api, FetchImpl, Model, ModelSpec, OpenAICompat, Provider, ThinkingConfig, TokenCost } from "../types";
|
|
38
29
|
import { discoveryFetch, isAnthropicOAuthToken, isRecord, toBoolean, toNumber, toPositiveNumber } from "../utils";
|
|
39
30
|
import { ALIBABA_TOKEN_PLAN_BASE_URL, parseAlibabaTokenPlanCredential } from "../wire/alibaba-token-plan";
|
|
31
|
+
import { CLINEPASS_API_BASE_URL, clinePassClientHeaders } from "../wire/cline-pass";
|
|
40
32
|
import { CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL } from "../wire/cloudflare-ai-gateway";
|
|
41
33
|
import { coreWeaveProjectHeaders } from "../wire/coreweave";
|
|
42
34
|
import {
|
|
43
35
|
COPILOT_API_HEADERS,
|
|
36
|
+
COPILOT_DISCOVERY_HEADERS,
|
|
44
37
|
discoverGitHubCopilotApiEndpoint,
|
|
45
38
|
getGitHubCopilotBaseUrl,
|
|
46
39
|
isPersonalGitHubCopilotBaseUrl,
|
|
40
|
+
mergeCopilotApiHeaders,
|
|
47
41
|
parseGitHubCopilotApiKey,
|
|
48
42
|
} from "../wire/github-copilot";
|
|
49
43
|
import { createBundledReferenceMap, createReferenceResolver, toModelSpec } from "./bundled-references";
|
|
50
44
|
import { getDefaultModelDiscoveryBaseUrl, resolveModelCacheProviderId } from "./cache-provider-id";
|
|
45
|
+
import { getClinePassModelMetadata } from "./cline-pass";
|
|
51
46
|
import type { ModelManagerConfig } from "./descriptor-types";
|
|
52
47
|
import { filterModelsDevCatalogRows } from "./models-dev-policies";
|
|
53
48
|
|
|
49
|
+
function revisionAtLeast(revision: string | undefined, floor: string): boolean {
|
|
50
|
+
if (revision === undefined) return false;
|
|
51
|
+
const parsedRevision = parseRevision(revision);
|
|
52
|
+
const parsedFloor = parseRevision(floor);
|
|
53
|
+
return (
|
|
54
|
+
parsedRevision !== undefined && parsedFloor !== undefined && compareRevision(parsedRevision, parsedFloor) >= 0
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isGlmReasoningIdentity(provider: string, modelId: string, floor: string): boolean {
|
|
59
|
+
const identity = classifyModel(provider, modelId, { lenient: true });
|
|
60
|
+
if (identity.class !== "glm" || !revisionAtLeast(identity.revision, floor)) return false;
|
|
61
|
+
if (identity.family === "flash") return revisionAtLeast(identity.revision, "5.3");
|
|
62
|
+
return identity.family === undefined || identity.family === "air" || identity.family === "turbo";
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
const MODELS_DEV_URL = "https://catalog.stencil.so/models.json.zstd";
|
|
55
66
|
|
|
56
67
|
/** Little-endian magic number opening every zstd frame (RFC 8878). */
|
|
@@ -83,6 +94,7 @@ export interface ModelsDevModel {
|
|
|
83
94
|
name?: string;
|
|
84
95
|
tool_call?: boolean;
|
|
85
96
|
reasoning?: boolean;
|
|
97
|
+
reasoning_options?: Array<{ type?: string; values?: string[]; min?: number; max?: number }>;
|
|
86
98
|
limit?: {
|
|
87
99
|
context?: number;
|
|
88
100
|
output?: number;
|
|
@@ -430,7 +442,6 @@ async function fetchOllamaNativeModels(
|
|
|
430
442
|
provider: "ollama",
|
|
431
443
|
baseUrl,
|
|
432
444
|
reasoning: metadata.reasoning ?? false,
|
|
433
|
-
thinking: metadata.thinking,
|
|
434
445
|
input: metadata.input ?? ["text"],
|
|
435
446
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
436
447
|
contextWindow: metadata.contextWindow,
|
|
@@ -458,7 +469,6 @@ interface OllamaResolvedMetadata {
|
|
|
458
469
|
maxTokens: number;
|
|
459
470
|
capabilities?: string[];
|
|
460
471
|
reasoning?: boolean;
|
|
461
|
-
thinking?: ThinkingConfig;
|
|
462
472
|
input?: ("text" | "image")[];
|
|
463
473
|
}
|
|
464
474
|
|
|
@@ -467,7 +477,6 @@ interface OllamaShowMetadata {
|
|
|
467
477
|
maxTokens?: number;
|
|
468
478
|
capabilities?: string[];
|
|
469
479
|
reasoning?: boolean;
|
|
470
|
-
thinking?: ThinkingConfig;
|
|
471
480
|
input?: ("text" | "image")[];
|
|
472
481
|
}
|
|
473
482
|
|
|
@@ -492,13 +501,6 @@ function getOllamaCapabilities(value: unknown): string[] | undefined {
|
|
|
492
501
|
return value.filter((item): item is string => typeof item === "string");
|
|
493
502
|
}
|
|
494
503
|
|
|
495
|
-
function getOllamaThinkingConfig(capabilities: string[] | undefined): ThinkingConfig | undefined {
|
|
496
|
-
if (!capabilities?.includes("thinking")) {
|
|
497
|
-
return undefined;
|
|
498
|
-
}
|
|
499
|
-
return { mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] };
|
|
500
|
-
}
|
|
501
|
-
|
|
502
504
|
/**
|
|
503
505
|
* Query Ollama's `/api/show` endpoint for a single model and pull native
|
|
504
506
|
* context and capability metadata from the response. Returns `undefined` when
|
|
@@ -526,7 +528,6 @@ async function fetchOllamaShowMetadata(
|
|
|
526
528
|
maxTokens: contextWindow ? OLLAMA_DEFAULT_MAX_TOKENS : undefined,
|
|
527
529
|
capabilities,
|
|
528
530
|
reasoning: capabilities ? capabilities.includes("thinking") : undefined,
|
|
529
|
-
thinking: getOllamaThinkingConfig(capabilities),
|
|
530
531
|
input: capabilities
|
|
531
532
|
? capabilities.includes("vision")
|
|
532
533
|
? (["text", "image"] as Array<"text" | "image">)
|
|
@@ -571,17 +572,6 @@ function createOllamaMetadataResolver(
|
|
|
571
572
|
};
|
|
572
573
|
}
|
|
573
574
|
|
|
574
|
-
const OPENAI_NON_RESPONSES_PREFIXES = [
|
|
575
|
-
"text-embedding",
|
|
576
|
-
"whisper-",
|
|
577
|
-
"tts-",
|
|
578
|
-
"omni-moderation",
|
|
579
|
-
"omni-transcribe",
|
|
580
|
-
"omni-speech",
|
|
581
|
-
"gpt-image-",
|
|
582
|
-
"gpt-realtime",
|
|
583
|
-
] as const;
|
|
584
|
-
|
|
585
575
|
function isLikelyOpenAIResponsesModelId(
|
|
586
576
|
id: string,
|
|
587
577
|
references?: ReadonlyMap<string, ModelSpec<"openai-responses">>,
|
|
@@ -593,35 +583,9 @@ function isLikelyOpenAIResponsesModelId(
|
|
|
593
583
|
if (references?.has(trimmed)) {
|
|
594
584
|
return true;
|
|
595
585
|
}
|
|
596
|
-
|
|
597
|
-
if (OPENAI_NON_RESPONSES_PREFIXES.some(prefix => normalized.startsWith(prefix))) {
|
|
598
|
-
return false;
|
|
599
|
-
}
|
|
600
|
-
if (normalized.includes("embedding")) {
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
603
|
-
return (
|
|
604
|
-
normalized.startsWith("gpt-") ||
|
|
605
|
-
normalized.startsWith("o1") ||
|
|
606
|
-
normalized.startsWith("o3") ||
|
|
607
|
-
normalized.startsWith("o4") ||
|
|
608
|
-
normalized.startsWith("chatgpt")
|
|
609
|
-
);
|
|
586
|
+
return isLikelyOpenAIResponsesId(trimmed);
|
|
610
587
|
}
|
|
611
588
|
|
|
612
|
-
const NANO_GPT_NON_TEXT_MODEL_TOKENS = [
|
|
613
|
-
"embedding",
|
|
614
|
-
"image",
|
|
615
|
-
"vision",
|
|
616
|
-
"audio",
|
|
617
|
-
"speech",
|
|
618
|
-
"transcribe",
|
|
619
|
-
"moderation",
|
|
620
|
-
"realtime",
|
|
621
|
-
"whisper",
|
|
622
|
-
"tts",
|
|
623
|
-
] as const;
|
|
624
|
-
|
|
625
589
|
/** Regex matching NanoGPT `:thinking` suffixed model IDs (with or without a level). */
|
|
626
590
|
const NANO_GPT_THINKING_SUFFIX_RE = /:thinking(:[^:]+)?$/;
|
|
627
591
|
|
|
@@ -633,7 +597,7 @@ function isLikelyNanoGptTextModelId(id: string): boolean {
|
|
|
633
597
|
if (NANO_GPT_THINKING_SUFFIX_RE.test(normalized)) {
|
|
634
598
|
return false;
|
|
635
599
|
}
|
|
636
|
-
return !
|
|
600
|
+
return !isExcludedModel("nanogpt", normalized);
|
|
637
601
|
}
|
|
638
602
|
|
|
639
603
|
type SimpleProviderDiscoveryHeaders = Record<string, string> | (() => Record<string, string> | undefined);
|
|
@@ -929,30 +893,23 @@ export function umansModelManagerOptions(config?: UmansModelManagerConfig): Mode
|
|
|
929
893
|
// ---------------------------------------------------------------------------
|
|
930
894
|
|
|
931
895
|
const OPENAI_API_BASE_URL = "https://api.openai.com/v1";
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
inputThreshold: 272_000,
|
|
950
|
-
input: 4,
|
|
951
|
-
output: 18,
|
|
952
|
-
cacheRead: 0.4,
|
|
953
|
-
cacheWrite: 5,
|
|
954
|
-
},
|
|
955
|
-
} as const satisfies Readonly<Record<"luna" | "sol" | "terra", LongContextTokenCost>>;
|
|
896
|
+
// Curated seed pricing for approval-gated aliases stencil.so does not list
|
|
897
|
+
// yet; upstream catalog rows outrank these once discovery serves them.
|
|
898
|
+
/** Standard GPT-5.6 Sol rates used by the Daybreak Blue aliases. */
|
|
899
|
+
const OPENAI_GPT_56_SOL_STANDARD_COST = {
|
|
900
|
+
input: 5,
|
|
901
|
+
output: 30,
|
|
902
|
+
cacheRead: 0.5,
|
|
903
|
+
cacheWrite: 6.25,
|
|
904
|
+
} as const satisfies TokenCost;
|
|
905
|
+
|
|
906
|
+
/** Standard GPT-5.6 Cyber rates used by the Daybreak Red aliases. */
|
|
907
|
+
const OPENAI_GPT_56_CYBER_STANDARD_COST = {
|
|
908
|
+
input: 12.5,
|
|
909
|
+
output: 75,
|
|
910
|
+
cacheRead: 1.25,
|
|
911
|
+
cacheWrite: 15.625,
|
|
912
|
+
} as const satisfies TokenCost;
|
|
956
913
|
|
|
957
914
|
export interface OpenAIModelManagerConfig {
|
|
958
915
|
apiKey?: string;
|
|
@@ -986,10 +943,9 @@ export const OPENAI_DAYBREAK_CURATED_FALLBACK_MODELS: readonly ModelSpec<"openai
|
|
|
986
943
|
baseUrl: OPENAI_API_BASE_URL,
|
|
987
944
|
reasoning: true,
|
|
988
945
|
input: ["text", "image"],
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
},
|
|
946
|
+
// The >272K long-context tier is rule-owned (`providers/openai.kdl`
|
|
947
|
+
// long-context-cost) and baked at build time.
|
|
948
|
+
cost: OPENAI_GPT_56_SOL_STANDARD_COST,
|
|
993
949
|
contextWindow: 1_050_000,
|
|
994
950
|
maxTokens: 128_000,
|
|
995
951
|
},
|
|
@@ -1019,32 +975,21 @@ export const OPENAI_DAYBREAK_CURATED_FALLBACK_MODELS: readonly ModelSpec<"openai
|
|
|
1019
975
|
},
|
|
1020
976
|
];
|
|
1021
977
|
|
|
1022
|
-
/** First-party gpt-5.6 SKUs that accept `reasoning: { mode: "pro" }` on the Responses APIs. */
|
|
1023
|
-
const OPENAI_PRO_REASONING_BASE_IDS: Record<string, true> = {
|
|
1024
|
-
"gpt-5.6-luna": true,
|
|
1025
|
-
"gpt-5.6-sol": true,
|
|
1026
|
-
"gpt-5.6-terra": true,
|
|
1027
|
-
};
|
|
1028
|
-
/**
|
|
1029
|
-
* Providers whose generated pro aliases this pass owns. `openai-codex` stays in
|
|
1030
|
-
* the sweep so stale aliases from earlier snapshots are dropped on regen, but
|
|
1031
|
-
* projection is `openai`-only — subscription (Codex) auth does not offer pro
|
|
1032
|
-
* reasoning.
|
|
1033
|
-
*/
|
|
1034
|
-
const OPENAI_PRO_REASONING_SWEEP_PROVIDERS: Record<string, true> = { openai: true, "openai-codex": true };
|
|
1035
|
-
|
|
1036
978
|
/**
|
|
1037
979
|
* A row this generator pass owns: one of the derived `gpt-5.6-*-pro` alias ids
|
|
1038
980
|
* on a swept provider that carries the generated `reasoningMode` marker.
|
|
1039
981
|
* A real upstream model occupying the same id has no `reasoningMode` and is
|
|
1040
982
|
* never touched.
|
|
983
|
+
* The reviewed base-id and sweep tables live in the compiled discovery
|
|
984
|
+
* vocabulary (`pro-reasoning-alias` / `pro-reasoning-sweep`).
|
|
1041
985
|
*/
|
|
1042
986
|
function isGeneratedOpenAIProReasoningAlias(model: ModelSpec<Api>): boolean {
|
|
987
|
+
const { proReasoningAliases, proReasoningSweep } = discoveryVocabulary();
|
|
1043
988
|
return (
|
|
1044
|
-
|
|
989
|
+
proReasoningSweep.includes(model.provider) &&
|
|
1045
990
|
model.reasoningMode !== undefined &&
|
|
1046
991
|
model.id.endsWith("-pro") &&
|
|
1047
|
-
|
|
992
|
+
Object.values(proReasoningAliases).some(bases => bases.includes(model.id.slice(0, -"-pro".length)))
|
|
1048
993
|
);
|
|
1049
994
|
}
|
|
1050
995
|
|
|
@@ -1064,8 +1009,8 @@ export function projectOpenAIProReasoningAliases(models: readonly ModelSpec<Api>
|
|
|
1064
1009
|
const ids = new Set(kept.map(model => `${model.provider}/${model.id}`));
|
|
1065
1010
|
const out = [...kept];
|
|
1066
1011
|
for (const model of kept) {
|
|
1067
|
-
|
|
1068
|
-
if (!
|
|
1012
|
+
const bases = discoveryVocabulary().proReasoningAliases[model.provider];
|
|
1013
|
+
if (!bases?.includes(model.id)) continue;
|
|
1069
1014
|
const aliasId = `${model.id}-pro`;
|
|
1070
1015
|
const aliasKey = `${model.provider}/${aliasId}`;
|
|
1071
1016
|
if (ids.has(aliasKey)) continue;
|
|
@@ -1197,18 +1142,6 @@ export function groqModelManagerOptions(config?: GroqModelManagerConfig): ModelM
|
|
|
1197
1142
|
// 3. Cerebras
|
|
1198
1143
|
// ---------------------------------------------------------------------------
|
|
1199
1144
|
|
|
1200
|
-
const CEREBRAS_IMAGE_INPUT_MODEL_IDS = new Set(["gemma-4-31b"]);
|
|
1201
|
-
|
|
1202
|
-
function applyCerebrasDiscoveryOverrides(model: ModelSpec<"openai-completions">): ModelSpec<"openai-completions"> {
|
|
1203
|
-
if (!CEREBRAS_IMAGE_INPUT_MODEL_IDS.has(model.id)) {
|
|
1204
|
-
return model;
|
|
1205
|
-
}
|
|
1206
|
-
return {
|
|
1207
|
-
...model,
|
|
1208
|
-
input: ["text", "image"],
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
1145
|
export interface CerebrasModelManagerConfig {
|
|
1213
1146
|
apiKey?: string;
|
|
1214
1147
|
baseUrl?: string;
|
|
@@ -1224,8 +1157,7 @@ export function cerebrasModelManagerOptions(
|
|
|
1224
1157
|
defaultBaseUrl: "https://api.cerebras.ai/v1",
|
|
1225
1158
|
config,
|
|
1226
1159
|
requireApiKey: true,
|
|
1227
|
-
mapModel: (entry, defaults, reference) =>
|
|
1228
|
-
applyCerebrasDiscoveryOverrides(mapWithBundledReference(entry, defaults, reference)),
|
|
1160
|
+
mapModel: (entry, defaults, reference) => mapWithBundledReference(entry, defaults, reference),
|
|
1229
1161
|
});
|
|
1230
1162
|
}
|
|
1231
1163
|
|
|
@@ -1534,64 +1466,31 @@ export interface XaiModelManagerConfig {
|
|
|
1534
1466
|
fetch?: FetchImpl;
|
|
1535
1467
|
}
|
|
1536
1468
|
|
|
1537
|
-
// xAI bills the whole request at 2x the base rates once the prompt reaches
|
|
1538
|
-
// 200K tokens, for every current Grok SKU that carries the two-tier card
|
|
1539
|
-
// (grok-4.3/4.5/4.6, every grok-4.20 variant, grok-build-0.1). Source:
|
|
1540
|
-
// docs.x.ai/developers/models. Computing the tier from the base keeps it in
|
|
1541
|
-
// lockstep with stencil.so price updates instead of a hand-maintained table.
|
|
1542
|
-
const XAI_LONG_CONTEXT_THRESHOLD = 200_000;
|
|
1543
|
-
const XAI_LONG_CONTEXT_MULTIPLIER = 2;
|
|
1544
|
-
|
|
1545
1469
|
// SuperGrok surfaces a few models under IDs that differ from their public
|
|
1546
1470
|
// `xai` catalog equivalent, so the exact-ID price fallback misses them. Map
|
|
1547
1471
|
// the OAuth ID to the paid ID it mirrors.
|
|
1548
|
-
|
|
1549
|
-
"grok-4.20-multi-agent-0309": "grok-4.20-multi-agent-beta-latest",
|
|
1550
|
-
};
|
|
1551
|
-
|
|
1552
|
-
function xaiHasLongContextTier(modelId: string): boolean {
|
|
1553
|
-
return (
|
|
1554
|
-
modelId === "grok-4.3" ||
|
|
1555
|
-
modelId === "grok-4.5" ||
|
|
1556
|
-
modelId === "grok-4.6" ||
|
|
1557
|
-
modelId === "grok-build-0.1" ||
|
|
1558
|
-
modelId.startsWith("grok-4.20")
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1472
|
+
// The alias map lives in the `pricing-peer` behavior rule.
|
|
1562
1473
|
function hasTokenPrice(cost: ModelSpec["cost"]): boolean {
|
|
1563
1474
|
return cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0;
|
|
1564
1475
|
}
|
|
1565
1476
|
|
|
1566
|
-
function withXaiLongContextTier(model: ModelSpec): ModelSpec {
|
|
1567
|
-
if (!xaiHasLongContextTier(model.id) || !hasTokenPrice(model.cost)) return model;
|
|
1568
|
-
const longContext: LongContextTokenCost = {
|
|
1569
|
-
inputThreshold: XAI_LONG_CONTEXT_THRESHOLD,
|
|
1570
|
-
inputThresholdInclusive: true,
|
|
1571
|
-
input: model.cost.input * XAI_LONG_CONTEXT_MULTIPLIER,
|
|
1572
|
-
output: model.cost.output * XAI_LONG_CONTEXT_MULTIPLIER,
|
|
1573
|
-
cacheRead: model.cost.cacheRead * XAI_LONG_CONTEXT_MULTIPLIER,
|
|
1574
|
-
cacheWrite: model.cost.cacheWrite * XAI_LONG_CONTEXT_MULTIPLIER,
|
|
1575
|
-
};
|
|
1576
|
-
return { ...model, cost: { ...model.cost, longContext } };
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
1477
|
/**
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1478
|
+
* Mirrors exact public-model prices onto matching SuperGrok catalog rows.
|
|
1479
|
+
* The >200K long-context tier itself is rule-owned (`classes/xai.kdl`
|
|
1480
|
+
* `long-context-cost` multiplier axis) and derives at build time.
|
|
1582
1481
|
*/
|
|
1583
1482
|
export function applyXaiCatalogPricing(models: readonly ModelSpec[]): ModelSpec[] {
|
|
1584
|
-
const pricedModels = models.map(model => (model.provider === "xai" ? withXaiLongContextTier(model) : model));
|
|
1585
1483
|
const publicCosts = new Map(
|
|
1586
|
-
|
|
1484
|
+
models
|
|
1587
1485
|
.filter(model => model.provider === "xai" && hasTokenPrice(model.cost))
|
|
1588
1486
|
.map(model => [model.id, model.cost]),
|
|
1589
1487
|
);
|
|
1590
1488
|
|
|
1591
|
-
return
|
|
1489
|
+
return models.map(model => {
|
|
1592
1490
|
if (model.provider !== "xai-oauth" || hasTokenPrice(model.cost)) return model;
|
|
1593
|
-
const
|
|
1594
|
-
const publicCost =
|
|
1491
|
+
const peer = pricingPeerFor("xai-oauth", model.id);
|
|
1492
|
+
const publicCost =
|
|
1493
|
+
publicCosts.get(model.id) ?? (peer && peer.peerId !== model.id ? publicCosts.get(peer.peerId) : undefined);
|
|
1595
1494
|
return publicCost ? { ...model, cost: { ...publicCost } } : model;
|
|
1596
1495
|
});
|
|
1597
1496
|
}
|
|
@@ -1630,9 +1529,8 @@ interface XAICuratedModel {
|
|
|
1630
1529
|
/**
|
|
1631
1530
|
* Whether xAI accepts the `reasoning.effort` wire param for this model.
|
|
1632
1531
|
* Default true. When false: the picker hides the effort dial (via
|
|
1633
|
-
* getSupportedEfforts in model-thinking.ts) AND the wire omits the param
|
|
1634
|
-
*
|
|
1635
|
-
* single allowlist shared by this curated layer and the compat builder.
|
|
1532
|
+
* getSupportedEfforts in model-thinking.ts) AND the wire omits the param.
|
|
1533
|
+
* Both read the compat engine's resolved reasoning-effort axes.
|
|
1636
1534
|
*/
|
|
1637
1535
|
supportsReasoningEffort?: boolean;
|
|
1638
1536
|
/**
|
|
@@ -1702,15 +1600,13 @@ export const XAI_OAUTH_CURATED_MODELS: readonly XAICuratedModel[] = [
|
|
|
1702
1600
|
// xAI /v1/models returns chat, image, voice, and STT entries. Tool surfaces
|
|
1703
1601
|
// route through dedicated tools (generate_image, tts) with their own model
|
|
1704
1602
|
// strings; the chat picker MUST exclude these prefixes or selecting them 400s.
|
|
1705
|
-
const XAI_NON_CHAT_PREFIXES = ["grok-imagine-", "grok-stt-", "grok-voice-"] as const;
|
|
1706
|
-
|
|
1707
1603
|
function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): ModelSpec<"openai-responses"> {
|
|
1708
1604
|
const compat = {
|
|
1709
|
-
...
|
|
1605
|
+
...model.compat,
|
|
1710
1606
|
includeEncryptedReasoning: model.compat?.includeEncryptedReasoning ?? true,
|
|
1711
1607
|
filterReasoningHistory: model.compat?.filterReasoningHistory ?? false,
|
|
1712
1608
|
supportsImageDetailOriginal: model.compat?.supportsImageDetailOriginal ?? false,
|
|
1713
|
-
omitReasoningEffort: model.compat?.omitReasoningEffort ??
|
|
1609
|
+
omitReasoningEffort: model.compat?.omitReasoningEffort ?? resolveModelPolicy(model).compat.omitReasoningEffort,
|
|
1714
1610
|
};
|
|
1715
1611
|
return { ...model, compat };
|
|
1716
1612
|
}
|
|
@@ -1724,8 +1620,8 @@ function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): Model
|
|
|
1724
1620
|
* Bake first-party xAI Responses effort-dial metadata onto a catalog spec.
|
|
1725
1621
|
*
|
|
1726
1622
|
* models.dev marks many Grok SKUs as reasoners and the thinking rebake would
|
|
1727
|
-
* otherwise emit a default `minimal/low/medium/high` dial. api.x.ai
|
|
1728
|
-
* accepts `reasoning.effort` for
|
|
1623
|
+
* otherwise emit a default `minimal/low/medium/high` dial. api.x.ai
|
|
1624
|
+
* accepts `reasoning.effort` only for ids selected by the compat rule —
|
|
1729
1625
|
* off-allowlist reasoners (`grok-code-fast-1`, `grok-build-0.1`,
|
|
1730
1626
|
* `grok-4.20-0309-reasoning`, …) 400 if the param is sent. SuperGrok
|
|
1731
1627
|
* (`xai-oauth`) already curates this via {@link mergeCuratedIntoModel}; paid
|
|
@@ -1735,9 +1631,10 @@ function withXaiOAuthCompatDefaults(model: ModelSpec<"openai-responses">): Model
|
|
|
1735
1631
|
* Explicit `compat.supportsReasoningEffort` / `omitReasoningEffort` win.
|
|
1736
1632
|
*/
|
|
1737
1633
|
export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-responses">): ModelSpec<"openai-responses"> {
|
|
1738
|
-
const effortCapable =
|
|
1634
|
+
const effortCapable =
|
|
1635
|
+
model.compat?.supportsReasoningEffort ?? resolveModelPolicy(model).compat.supportsReasoningEffort;
|
|
1739
1636
|
const compat = {
|
|
1740
|
-
...
|
|
1637
|
+
...model.compat,
|
|
1741
1638
|
supportsReasoningEffort: effortCapable,
|
|
1742
1639
|
omitReasoningEffort: model.compat?.omitReasoningEffort ?? !effortCapable,
|
|
1743
1640
|
};
|
|
@@ -1767,14 +1664,16 @@ export function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai-respons
|
|
|
1767
1664
|
// param, so a map on those specs is dead weight.
|
|
1768
1665
|
// The effort-dial pair (`supportsReasoningEffort`/`omitReasoningEffort`) is
|
|
1769
1666
|
// authoritative: a stale flag on `base` (previous snapshot or dynamic fetch)
|
|
1770
|
-
// must not outlive
|
|
1667
|
+
// must not outlive a compat-rule change.
|
|
1771
1668
|
function mergeCuratedIntoModel(
|
|
1772
1669
|
base: ModelSpec<"openai-responses">,
|
|
1773
1670
|
curated: XAICuratedModel,
|
|
1774
1671
|
): ModelSpec<"openai-responses"> {
|
|
1775
|
-
const effortCapable =
|
|
1672
|
+
const effortCapable =
|
|
1673
|
+
curated.supportsReasoningEffort ??
|
|
1674
|
+
resolveModelPolicy({ ...base, id: curated.id, provider: "xai-oauth" }).compat.supportsReasoningEffort;
|
|
1776
1675
|
const compat = {
|
|
1777
|
-
...
|
|
1676
|
+
...base.compat,
|
|
1778
1677
|
includeEncryptedReasoning: base.compat?.includeEncryptedReasoning ?? true,
|
|
1779
1678
|
filterReasoningHistory: false,
|
|
1780
1679
|
supportsImageDetailOriginal: base.compat?.supportsImageDetailOriginal ?? false,
|
|
@@ -1820,7 +1719,7 @@ function mergeCuratedIntoModel(
|
|
|
1820
1719
|
* in original order.
|
|
1821
1720
|
*/
|
|
1822
1721
|
function applyXAIOAuthCuration(dynamic: readonly ModelSpec<"openai-responses">[]): ModelSpec<"openai-responses">[] {
|
|
1823
|
-
const filtered = dynamic.filter(e => !
|
|
1722
|
+
const filtered = dynamic.filter(e => !isExcludedModel("xai-oauth", e.id));
|
|
1824
1723
|
|
|
1825
1724
|
const byId = new Map<string, ModelSpec<"openai-responses">>(filtered.map(e => [e.id, e]));
|
|
1826
1725
|
for (const curated of XAI_OAUTH_CURATED_MODELS) {
|
|
@@ -1935,18 +1834,10 @@ export function xaiOAuthModelManagerOptions(
|
|
|
1935
1834
|
// 6.4 AIML API
|
|
1936
1835
|
// ---------------------------------------------------------------------------
|
|
1937
1836
|
|
|
1938
|
-
const AIML_API_NON_CHAT_MODEL_ID_PATTERN =
|
|
1939
|
-
/(?:^|[/:._-])(?:audio|embed|embedding|embeddings|i2i|i2v|image|speech|t2i|t2v|tts|video)(?:$|[/:._-])/i;
|
|
1940
|
-
|
|
1941
|
-
const AIML_API_NON_CHAT_MODEL_ID_SUBSTRINGS = ["dall-e", "dalle", "flux", "imagen", "sora", "veo", "whisper"] as const;
|
|
1942
|
-
|
|
1943
1837
|
export function isLikelyAimlApiChatModelId(id: string): boolean {
|
|
1944
1838
|
const normalized = id.trim().toLowerCase();
|
|
1945
1839
|
if (!normalized) return false;
|
|
1946
|
-
return (
|
|
1947
|
-
!AIML_API_NON_CHAT_MODEL_ID_PATTERN.test(normalized) &&
|
|
1948
|
-
!AIML_API_NON_CHAT_MODEL_ID_SUBSTRINGS.some(token => normalized.includes(token))
|
|
1949
|
-
);
|
|
1840
|
+
return !isExcludedModel("aimlapi", normalized);
|
|
1950
1841
|
}
|
|
1951
1842
|
|
|
1952
1843
|
export interface AimlApiModelManagerConfig {
|
|
@@ -2002,35 +1893,12 @@ export interface SiliconFlowModelManagerConfig {
|
|
|
2002
1893
|
* completions — and carries no per-model type field, so non-chat entries are
|
|
2003
1894
|
* dropped by id to keep the picker usable.
|
|
2004
1895
|
*/
|
|
2005
|
-
const SILICONFLOW_NON_CHAT_MODEL_TOKENS = [
|
|
2006
|
-
"embedding",
|
|
2007
|
-
"reranker",
|
|
2008
|
-
"bge-",
|
|
2009
|
-
"bce-",
|
|
2010
|
-
"stable-diffusion",
|
|
2011
|
-
"image",
|
|
2012
|
-
"flux",
|
|
2013
|
-
"kolors",
|
|
2014
|
-
"sensevoice",
|
|
2015
|
-
"cosyvoice",
|
|
2016
|
-
"fish-speech",
|
|
2017
|
-
"indextts",
|
|
2018
|
-
"sovits",
|
|
2019
|
-
"whisper",
|
|
2020
|
-
"hunyuanvideo",
|
|
2021
|
-
"wan2",
|
|
2022
|
-
"ltx-video",
|
|
2023
|
-
"speech",
|
|
2024
|
-
"moderator",
|
|
2025
|
-
"tts",
|
|
2026
|
-
] as const;
|
|
2027
|
-
|
|
2028
1896
|
export function isLikelySiliconFlowChatModelId(id: string): boolean {
|
|
2029
1897
|
const normalized = id.trim().toLowerCase();
|
|
2030
1898
|
if (!normalized) {
|
|
2031
1899
|
return false;
|
|
2032
1900
|
}
|
|
2033
|
-
return !
|
|
1901
|
+
return !isExcludedModel("siliconflow", normalized);
|
|
2034
1902
|
}
|
|
2035
1903
|
|
|
2036
1904
|
/**
|
|
@@ -2176,10 +2044,14 @@ export function zhipuCodingPlanModelManagerOptions(
|
|
|
2176
2044
|
_context: OpenAICompatibleModelMapperContext<"openai-completions">,
|
|
2177
2045
|
): ModelSpec<"openai-completions"> => {
|
|
2178
2046
|
const id = defaults.id;
|
|
2047
|
+
const identity = classifyModel("zhipu-coding-plan", id, { lenient: true });
|
|
2179
2048
|
return {
|
|
2180
2049
|
...defaults,
|
|
2181
|
-
reasoning:
|
|
2182
|
-
|
|
2050
|
+
reasoning:
|
|
2051
|
+
isGlmReasoningIdentity("zhipu-coding-plan", id, "4.5") || identity.thinkingVariant === true,
|
|
2052
|
+
// Vision-capable families are rule-owned (`providers/zhipu-coding-plan.kdl`
|
|
2053
|
+
// input-modalities) and corrected at build time.
|
|
2054
|
+
input: ["text"],
|
|
2183
2055
|
compat: {
|
|
2184
2056
|
thinkingFormat: "zai",
|
|
2185
2057
|
reasoningContentField: "reasoning_content",
|
|
@@ -2231,10 +2103,9 @@ export const FIREWORKS_KIMI_K27_CODE_MAX_TOKENS = 65_536;
|
|
|
2231
2103
|
* Fireworks' reported `max_completion_tokens` (65,536) instead.
|
|
2232
2104
|
*/
|
|
2233
2105
|
export function isFireworksKimiK2ModelId(modelId: string): boolean {
|
|
2234
|
-
const
|
|
2235
|
-
if (
|
|
2236
|
-
|
|
2237
|
-
return /\/kimi-k2(?:p\d+)?(?:[._-]|$)/.test(trimmed);
|
|
2106
|
+
const identity = classifyModel("fireworks", modelId, { lenient: true });
|
|
2107
|
+
if (identity.class !== "kimi" || identity.family === "k2.7-code") return false;
|
|
2108
|
+
return identity.family?.startsWith("k2") === true;
|
|
2238
2109
|
}
|
|
2239
2110
|
|
|
2240
2111
|
/**
|
|
@@ -2255,7 +2126,8 @@ export function clampFireworksKimiMaxTokens(modelId: string, candidate: number |
|
|
|
2255
2126
|
export const KIMI_K27_CODE_RECOMMENDED_MAX_TOKENS = 32_768;
|
|
2256
2127
|
|
|
2257
2128
|
export function isKimiK27CodeModelId(modelId: string): boolean {
|
|
2258
|
-
|
|
2129
|
+
const identity = classifyModel("fireworks", modelId, { lenient: true });
|
|
2130
|
+
return identity.class === "kimi" && identity.family === "k2.7-code";
|
|
2259
2131
|
}
|
|
2260
2132
|
|
|
2261
2133
|
export function clampKimiK27CodeMaxTokens(modelId: string, candidate: number): number;
|
|
@@ -2319,7 +2191,7 @@ export function stripFireworksDeepSeekThinkingToggle(
|
|
|
2319
2191
|
model: ModelSpec<"openai-completions">,
|
|
2320
2192
|
publicModelId: string,
|
|
2321
2193
|
): ModelSpec<"openai-completions"> {
|
|
2322
|
-
if (!
|
|
2194
|
+
if (!isDeepseekV4Generation("fireworks", publicModelId)) return model;
|
|
2323
2195
|
const compat = model.compat;
|
|
2324
2196
|
if (!compat?.extraBody || !("thinking" in compat.extraBody)) return model;
|
|
2325
2197
|
|
|
@@ -2582,6 +2454,304 @@ export function firepassModelManagerOptions(
|
|
|
2582
2454
|
};
|
|
2583
2455
|
}
|
|
2584
2456
|
|
|
2457
|
+
const CLINEPASS_MODELS_URL = `${CLINEPASS_API_BASE_URL}/ai/cline/recommended-models`;
|
|
2458
|
+
const CLINEPASS_FALLBACK_CONTEXT_WINDOW = 128_000;
|
|
2459
|
+
const CLINEPASS_FALLBACK_MAX_TOKENS = 8_192;
|
|
2460
|
+
|
|
2461
|
+
function buildClinePassFallbackModel(id: string): ModelSpec<"openai-completions"> {
|
|
2462
|
+
return {
|
|
2463
|
+
id,
|
|
2464
|
+
name: id,
|
|
2465
|
+
api: "openai-completions",
|
|
2466
|
+
provider: "cline-pass",
|
|
2467
|
+
baseUrl: CLINEPASS_API_BASE_URL,
|
|
2468
|
+
reasoning: true,
|
|
2469
|
+
input: ["text"],
|
|
2470
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
2471
|
+
contextWindow: CLINEPASS_FALLBACK_CONTEXT_WINDOW,
|
|
2472
|
+
maxTokens: CLINEPASS_FALLBACK_MAX_TOKENS,
|
|
2473
|
+
compat: { supportsReasoningEffort: false },
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
/**
|
|
2478
|
+
* Live reference catalog mirroring Cline's own client enrichment: the official
|
|
2479
|
+
* CLI fills roster metadata from OpenRouter's public `/models` catalog (their
|
|
2480
|
+
* gateway routes through it), keyed by full id and by slug. Consulted only for
|
|
2481
|
+
* ids the bundled reference has no real upstream entry for: the bundle stays
|
|
2482
|
+
* authoritative for known ids, and unknown ids ride fresh live data instead of
|
|
2483
|
+
* the conservative constants until the next regen.
|
|
2484
|
+
*/
|
|
2485
|
+
interface ClinePassLiveCatalogEntry {
|
|
2486
|
+
name?: string;
|
|
2487
|
+
contextWindow?: number;
|
|
2488
|
+
maxTokens?: number;
|
|
2489
|
+
cost?: ModelSpec<"openai-completions">["cost"];
|
|
2490
|
+
reasoning?: boolean;
|
|
2491
|
+
input?: ("text" | "image")[];
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
interface ClinePassLiveCatalog {
|
|
2495
|
+
byId: ReadonlyMap<string, ClinePassLiveCatalogEntry>;
|
|
2496
|
+
bySlug: ReadonlyMap<string, ClinePassLiveCatalogEntry>;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
const CLINE_PASS_LIVE_CATALOG_URL = "https://openrouter.ai/api/v1/models";
|
|
2500
|
+
|
|
2501
|
+
/** Enrichment tier, never authoritative: any failure degrades to the bundled path. */
|
|
2502
|
+
async function fetchClinePassLiveCatalog(fetchImpl: FetchImpl): Promise<ClinePassLiveCatalog | undefined> {
|
|
2503
|
+
try {
|
|
2504
|
+
const response = await withCatalogDiscoveryTimeout(5_000, signal =>
|
|
2505
|
+
fetchImpl(CLINE_PASS_LIVE_CATALOG_URL, { signal }),
|
|
2506
|
+
);
|
|
2507
|
+
if (!response.ok) return undefined;
|
|
2508
|
+
const payload: unknown = await response.json();
|
|
2509
|
+
if (!isRecord(payload) || !Array.isArray(payload.data)) return undefined;
|
|
2510
|
+
const byId = new Map<string, ClinePassLiveCatalogEntry>();
|
|
2511
|
+
const bySlug = new Map<string, ClinePassLiveCatalogEntry>();
|
|
2512
|
+
for (const raw of payload.data) {
|
|
2513
|
+
if (!isRecord(raw) || typeof raw.id !== "string" || !raw.id) continue;
|
|
2514
|
+
const pricing = isRecord(raw.pricing) ? raw.pricing : undefined;
|
|
2515
|
+
const topProvider = isRecord(raw.top_provider) ? raw.top_provider : undefined;
|
|
2516
|
+
const params = Array.isArray(raw.supported_parameters) ? raw.supported_parameters : undefined;
|
|
2517
|
+
const modality = String(isRecord(raw.architecture) ? (raw.architecture.modality ?? "") : "");
|
|
2518
|
+
const contextWindow = toPositiveNumber(raw.context_length, 0);
|
|
2519
|
+
const maxTokens = toPositiveNumber(topProvider?.max_completion_tokens, 0);
|
|
2520
|
+
const entry: ClinePassLiveCatalogEntry = {
|
|
2521
|
+
...(typeof raw.name === "string" && raw.name ? { name: raw.name.replace(/^[^:]+:\s*/, "") } : {}),
|
|
2522
|
+
...(contextWindow > 0 ? { contextWindow } : {}),
|
|
2523
|
+
...(maxTokens > 0 ? { maxTokens } : {}),
|
|
2524
|
+
...(pricing
|
|
2525
|
+
? {
|
|
2526
|
+
cost: {
|
|
2527
|
+
input: parseFloat(String(pricing.prompt ?? "0")) * 1_000_000,
|
|
2528
|
+
output: parseFloat(String(pricing.completion ?? "0")) * 1_000_000,
|
|
2529
|
+
cacheRead: parseFloat(String(pricing.input_cache_read ?? "0")) * 1_000_000,
|
|
2530
|
+
cacheWrite: parseFloat(String(pricing.input_cache_write ?? "0")) * 1_000_000,
|
|
2531
|
+
},
|
|
2532
|
+
}
|
|
2533
|
+
: {}),
|
|
2534
|
+
...(params ? { reasoning: params.includes("reasoning") } : {}),
|
|
2535
|
+
...(modality
|
|
2536
|
+
? { input: modality.includes("image") ? (["text", "image"] as const) : (["text"] as const) }
|
|
2537
|
+
: {}),
|
|
2538
|
+
};
|
|
2539
|
+
byId.set(raw.id, entry);
|
|
2540
|
+
// Cline's `buildModelsNameMap` algorithm: roster ids are lab-less slugs
|
|
2541
|
+
// ("glm-5.2"), so the catalog is also keyed by its last id segment.
|
|
2542
|
+
const slug = raw.id.split("/").at(-1);
|
|
2543
|
+
if (slug) bySlug.set(slug, entry);
|
|
2544
|
+
}
|
|
2545
|
+
return byId.size > 0 ? { byId, bySlug } : undefined;
|
|
2546
|
+
} catch {
|
|
2547
|
+
return undefined;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
/** Zero legs mean "unpriced upstream", not "free" — pass models bill quota regardless. */
|
|
2552
|
+
function hasClinePassBillableCost(cost: ModelSpec<"openai-completions">["cost"] | undefined): boolean {
|
|
2553
|
+
return cost !== undefined && (cost.input > 0 || cost.output > 0);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
function buildCuratedClinePassModel(
|
|
2557
|
+
id: string,
|
|
2558
|
+
tier: "subscription" | "free",
|
|
2559
|
+
): ModelSpec<"openai-completions"> | undefined {
|
|
2560
|
+
const metadata = getClinePassModelMetadata(id);
|
|
2561
|
+
if (!metadata || metadata.tier !== tier) return undefined;
|
|
2562
|
+
return {
|
|
2563
|
+
...buildClinePassFallbackModel(id),
|
|
2564
|
+
name: metadata.name,
|
|
2565
|
+
contextWindow: metadata.contextWindow,
|
|
2566
|
+
maxTokens: metadata.maxTokens,
|
|
2567
|
+
input: metadata.input,
|
|
2568
|
+
cost: metadata.cost,
|
|
2569
|
+
reasoning: metadata.reasoning,
|
|
2570
|
+
thinking: metadata.thinking,
|
|
2571
|
+
compat: {
|
|
2572
|
+
...(metadata.thinking === undefined ? { supportsReasoningEffort: false } : {}),
|
|
2573
|
+
...(tier === "free" ? { wireModelIdMode: "raw" as const } : {}),
|
|
2574
|
+
},
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
/**
|
|
2579
|
+
* Subscription models bill against plan quota, not per token — but the catalog still
|
|
2580
|
+
* carries the upstream list price so cost display reads as API-equivalent spend. Same
|
|
2581
|
+
* policy as the codex/antigravity generator fixups and the zai PAYG descriptor swap
|
|
2582
|
+
* (issue #5598: an all-$0 roster surfaces every model as "free" in the picker, which
|
|
2583
|
+
* was treated as a bug there). The price comes from the bundled upstream reference,
|
|
2584
|
+
* so it works offline and survives roster refreshes (`preferDiscoveryCost` never lets
|
|
2585
|
+
* a zero overwrite it). Ids with no upstream reference keep the honest zero until
|
|
2586
|
+
* models.dev prices them.
|
|
2587
|
+
*/
|
|
2588
|
+
function buildClinePassSubscriptionModel(
|
|
2589
|
+
id: string,
|
|
2590
|
+
references: ReadonlyMap<string, ModelSpec<"openai-completions">>,
|
|
2591
|
+
liveCatalog?: ClinePassLiveCatalog,
|
|
2592
|
+
): ModelSpec<"openai-completions"> {
|
|
2593
|
+
const curated = buildCuratedClinePassModel(id, "subscription");
|
|
2594
|
+
if (curated) return curated;
|
|
2595
|
+
const base = references.get(id) ?? buildClinePassFallbackModel(id);
|
|
2596
|
+
const reference = resolveModelReference(id, getBundledModelReferenceIndex());
|
|
2597
|
+
// A reference pointing back at the ClinePass bundle means the last regen
|
|
2598
|
+
// knew nothing about the id and encoded fallback constants — treat as none.
|
|
2599
|
+
const upstream = reference && reference.provider !== "cline-pass" ? reference : undefined;
|
|
2600
|
+
// Same degeneracy on the bundle side: a bundled entry whose limits are the
|
|
2601
|
+
// fallback pair was written by a regen that had no upstream data for the id.
|
|
2602
|
+
const baseLimitsAreFallback =
|
|
2603
|
+
base.contextWindow === CLINEPASS_FALLBACK_CONTEXT_WINDOW && base.maxTokens === CLINEPASS_FALLBACK_MAX_TOKENS;
|
|
2604
|
+
if (upstream) {
|
|
2605
|
+
// Known to the bundle with real limits: overlay the list price only.
|
|
2606
|
+
// Otherwise full enrichment, like the free-tier path.
|
|
2607
|
+
return references.has(id) && !baseLimitsAreFallback
|
|
2608
|
+
? { ...base, cost: upstream.cost }
|
|
2609
|
+
: {
|
|
2610
|
+
...base,
|
|
2611
|
+
name: references.has(id) ? base.name : upstream.name,
|
|
2612
|
+
reasoning: upstream.reasoning,
|
|
2613
|
+
input: upstream.input,
|
|
2614
|
+
cost: upstream.cost,
|
|
2615
|
+
contextWindow: upstream.contextWindow,
|
|
2616
|
+
maxTokens: upstream.maxTokens,
|
|
2617
|
+
...(upstream.reasoning ? {} : { thinking: undefined }),
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
const live = liveCatalog?.bySlug.get(id);
|
|
2621
|
+
if (!live) return base;
|
|
2622
|
+
return {
|
|
2623
|
+
...base,
|
|
2624
|
+
...(live.name ? { name: live.name } : {}),
|
|
2625
|
+
...(live.contextWindow ? { contextWindow: live.contextWindow } : {}),
|
|
2626
|
+
...(live.maxTokens ? { maxTokens: live.maxTokens } : {}),
|
|
2627
|
+
...(hasClinePassBillableCost(live.cost) ? { cost: live.cost } : {}),
|
|
2628
|
+
...(live.reasoning !== undefined ? { reasoning: live.reasoning } : {}),
|
|
2629
|
+
...(live.input ? { input: live.input } : {}),
|
|
2630
|
+
...(live.reasoning === false ? { thinking: undefined } : {}),
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
/**
|
|
2635
|
+
* Free-tier entries arrive as full OpenRouter-style ids (`deepseek/deepseek-v4-flash`,
|
|
2636
|
+
* `poolside/laguna-s-2.1:free`) and ride usage billing at $0 outside the subscription
|
|
2637
|
+
* quota — their cost is genuinely zero, unlike the subscription roster's list-price
|
|
2638
|
+
* display. Limits and modalities resolve through the bundled upstream reference (its
|
|
2639
|
+
* candidate expansion treats provider prefixes and trailing `:free`-style markers as
|
|
2640
|
+
* identity-preserving), so known models self-enrich offline. Compat and thinking are
|
|
2641
|
+
* deliberately NOT inherited: those describe the reference's native host, while the
|
|
2642
|
+
* Cline gateway dialect is resolved per provider/family. The raw wire tag is
|
|
2643
|
+
* bucket-derived — the gateway already namespaces these ids, so the cline-pass prefix
|
|
2644
|
+
* transform must never apply to them.
|
|
2645
|
+
*/
|
|
2646
|
+
function buildClinePassFreeModel(id: string, liveCatalog?: ClinePassLiveCatalog): ModelSpec<"openai-completions"> {
|
|
2647
|
+
const curated = buildCuratedClinePassModel(id, "free");
|
|
2648
|
+
if (curated) return curated;
|
|
2649
|
+
const reference = resolveModelReference(id, getBundledModelReferenceIndex());
|
|
2650
|
+
const upstream = reference && reference.provider !== "cline-pass" ? reference : undefined;
|
|
2651
|
+
// Free ids are OpenRouter-native, so the live catalog matches by full id
|
|
2652
|
+
// before falling back to the slug — the official client's own lookup order.
|
|
2653
|
+
const live = liveCatalog?.byId.get(id) ?? liveCatalog?.bySlug.get(id.split("/").at(-1) ?? id);
|
|
2654
|
+
// References can carry their own free markers (e.g. OpenRouter-style
|
|
2655
|
+
// "Laguna S 2.1 (free)"); strip before applying our single tier suffix.
|
|
2656
|
+
const baseName = (upstream?.name ?? live?.name ?? id.split("/").at(-1) ?? id)
|
|
2657
|
+
.replace(/\s*\(free\)\s*$/i, "")
|
|
2658
|
+
.replace(/:free$/i, "")
|
|
2659
|
+
.trim();
|
|
2660
|
+
const fallback = buildClinePassFallbackModel(id);
|
|
2661
|
+
const reasoning = upstream?.reasoning ?? live?.reasoning ?? fallback.reasoning;
|
|
2662
|
+
return {
|
|
2663
|
+
...fallback,
|
|
2664
|
+
name: `${baseName || id} (free)`,
|
|
2665
|
+
reasoning,
|
|
2666
|
+
input: upstream?.input ?? live?.input ?? fallback.input,
|
|
2667
|
+
contextWindow: upstream?.contextWindow ?? live?.contextWindow ?? fallback.contextWindow,
|
|
2668
|
+
maxTokens: upstream?.maxTokens ?? live?.maxTokens ?? fallback.maxTokens,
|
|
2669
|
+
// A non-reasoning reference must not carry the Cline effort ladder.
|
|
2670
|
+
...(reasoning ? {} : { thinking: undefined }),
|
|
2671
|
+
compat: { supportsReasoningEffort: false, wireModelIdMode: "raw" },
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
async function fetchClinePassModels(
|
|
2676
|
+
fetchImpl: FetchImpl,
|
|
2677
|
+
references: ReadonlyMap<string, ModelSpec<"openai-completions">>,
|
|
2678
|
+
): Promise<ModelSpec<"openai-completions">[]> {
|
|
2679
|
+
// The roster is authoritative for ids; the live reference catalog is a pure
|
|
2680
|
+
// enrichment tier fetched alongside and tolerated away (offline → bundle).
|
|
2681
|
+
const [response, liveCatalog] = await Promise.all([
|
|
2682
|
+
withCatalogDiscoveryTimeout(5_000, signal =>
|
|
2683
|
+
fetchImpl(CLINEPASS_MODELS_URL, { signal, headers: clinePassClientHeaders() }),
|
|
2684
|
+
),
|
|
2685
|
+
fetchClinePassLiveCatalog(fetchImpl),
|
|
2686
|
+
]);
|
|
2687
|
+
if (!response.ok) {
|
|
2688
|
+
throw new Error(`Failed to fetch ClinePass models: HTTP ${response.status}`);
|
|
2689
|
+
}
|
|
2690
|
+
const payload: unknown = await response.json();
|
|
2691
|
+
if (!isRecord(payload) || !Array.isArray(payload.clinePass)) {
|
|
2692
|
+
throw new Error("ClinePass model catalog response is missing clinePass");
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
const models = new Map<string, ModelSpec<"openai-completions">>();
|
|
2696
|
+
for (const entry of payload.clinePass) {
|
|
2697
|
+
if (!isRecord(entry) || typeof entry.id !== "string") {
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
const wireId = entry.id.trim();
|
|
2701
|
+
if (!wireId.startsWith("cline-pass/")) {
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
const id = toClinePassPublicModelId(wireId).trim();
|
|
2705
|
+
if (!id) {
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
models.set(id, buildClinePassSubscriptionModel(id, references, liveCatalog));
|
|
2709
|
+
}
|
|
2710
|
+
if (models.size === 0) {
|
|
2711
|
+
throw new Error("ClinePass model catalog contains no valid model IDs");
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
// Free tier rides the same key and gateway at $0 outside the subscription
|
|
2715
|
+
// quota. Optional overlay: entries validate individually, a cline-pass/-shaped
|
|
2716
|
+
// entry belongs to the pass bucket, pass ids win any collision, and pass-first
|
|
2717
|
+
// insertion order keeps the registry's first-available fallback on a
|
|
2718
|
+
// subscription model.
|
|
2719
|
+
const freeEntries = Array.isArray(payload.free) ? payload.free : [];
|
|
2720
|
+
for (const entry of freeEntries) {
|
|
2721
|
+
if (!isRecord(entry) || typeof entry.id !== "string") {
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
const id = entry.id.trim();
|
|
2725
|
+
if (!id || id.startsWith("cline-pass/") || models.has(id)) {
|
|
2726
|
+
continue;
|
|
2727
|
+
}
|
|
2728
|
+
models.set(id, buildClinePassFreeModel(id, liveCatalog));
|
|
2729
|
+
}
|
|
2730
|
+
return [...models.values()];
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
/**
|
|
2734
|
+
* Cline's OpenAI-compatible `/models` route is absent, but its public
|
|
2735
|
+
* recommended-models endpoint is the authoritative ClinePass roster: the
|
|
2736
|
+
* `clinePass` bucket lists subscription models and the optional `free` bucket
|
|
2737
|
+
* adds $0 usage-billing models selectable with the same key. Metadata resolves
|
|
2738
|
+
* in three tiers: the current Cline-published metadata snapshot first, bundled
|
|
2739
|
+
* upstream references and OpenRouter enrichment for future ids second, then
|
|
2740
|
+
* conservative constants. Newly added ids remain usable before the next regen;
|
|
2741
|
+
* known ids retain exact Cline limits, plan pricing, and reasoning controls.
|
|
2742
|
+
*/
|
|
2743
|
+
export function clinePassModelManagerOptions(config?: ModelManagerConfig): ModelManagerOptions<"openai-completions"> {
|
|
2744
|
+
const references = new Map(
|
|
2745
|
+
(getBundledModels("cline-pass") as Model<"openai-completions">[]).map(model => [model.id, toModelSpec(model)]),
|
|
2746
|
+
);
|
|
2747
|
+
const fetchImpl = discoveryFetch(config?.fetch);
|
|
2748
|
+
return {
|
|
2749
|
+
providerId: "cline-pass",
|
|
2750
|
+
dynamicModelsAuthoritative: true,
|
|
2751
|
+
fetchDynamicModels: () => fetchClinePassModels(fetchImpl, references),
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2585
2755
|
// ---------------------------------------------------------------------------
|
|
2586
2756
|
// 7.7 Wafer Serverless
|
|
2587
2757
|
// ---------------------------------------------------------------------------
|
|
@@ -2629,6 +2799,8 @@ export function resolveWaferServerlessThinkingFormat(
|
|
|
2629
2799
|
modelId: string,
|
|
2630
2800
|
upstreamProvider: unknown,
|
|
2631
2801
|
): WaferThinkingFormat | undefined {
|
|
2802
|
+
// residue: parses Wafer's upstream-provider metadata FIELD (vendor
|
|
2803
|
+
// spellings), not a model id; the id fallback below is classify-driven.
|
|
2632
2804
|
const upstream = typeof upstreamProvider === "string" ? upstreamProvider.trim().toLowerCase() : "";
|
|
2633
2805
|
if (upstream) {
|
|
2634
2806
|
if (
|
|
@@ -2650,7 +2822,8 @@ export function resolveWaferServerlessThinkingFormat(
|
|
|
2650
2822
|
// Older Wafer snapshots (and some endpoint responses) do not carry the
|
|
2651
2823
|
// upstream-provider hint. Only GLM/Kimi need a sparse override: qwen and
|
|
2652
2824
|
// deepseek IDs are resolved safely by `buildOpenAICompat` from the model id.
|
|
2653
|
-
|
|
2825
|
+
const identity = classifyModel("wafer-serverless", modelId, { lenient: true });
|
|
2826
|
+
return isGlmReasoningIdentity("wafer-serverless", modelId, "4.5") || identity.class === "kimi" ? "zai" : undefined;
|
|
2654
2827
|
}
|
|
2655
2828
|
|
|
2656
2829
|
function mapWaferModel(
|
|
@@ -2782,56 +2955,12 @@ function openCodeBaseUrlForApi(api: Api, basePath: string): string {
|
|
|
2782
2955
|
// /v1/models discovery mapper in openCodeModelManagerOptions — the gateways
|
|
2783
2956
|
// list ids that models.dev omits entirely (muse-spark-1.2[-contributor] on
|
|
2784
2957
|
// opencode-go, #8957), so discovery cannot rely on bundled references alone.
|
|
2785
|
-
//
|
|
2786
|
-
//
|
|
2787
|
-
// minimax-m3) with `provider.npm = "@ai-sdk/anthropic"`, but the Zen gateway
|
|
2788
|
-
// only serves them at https://opencode.ai/zen/v1/chat/completions (verified
|
|
2789
|
-
// against the live /v1/models response — minimax-m3-free is listed there, and
|
|
2790
|
-
// the gateway has no /v1/messages route for it). Without this override the
|
|
2791
|
-
// resolver POSTs anthropic-shaped requests to /v1/messages and the UI surfaces
|
|
2792
|
-
// raw <invoke>/<|minimax|>/<tool_call> markup (#1617).
|
|
2793
|
-
const OPENCODE_ZEN_API_ID_OVERRIDES: Readonly<Record<string, Api>> = {
|
|
2794
|
-
"minimax-m3": "openai-completions",
|
|
2795
|
-
"minimax-m3-free": "openai-completions",
|
|
2796
|
-
};
|
|
2797
|
-
// OpenCode Go: models.dev declares minimax-m2.7 / qwen3.5-plus / qwen3.6-plus
|
|
2798
|
-
// (and now also minimax-m3) with `provider.npm = "@ai-sdk/anthropic"`, but
|
|
2799
|
-
// the OpenCode Go gateway only serves them at
|
|
2800
|
-
// `https://opencode.ai/zen/go/v1/chat/completions` (verified against
|
|
2801
|
-
// https://opencode.ai/zen/go/v1/models and the upstream endpoint table at
|
|
2802
|
-
// https://opencode.ai/docs/go/#endpoints — minimax-m2.5 works the same way
|
|
2803
|
-
// and lacks an `npm` field on models.dev so it already falls through to the
|
|
2804
|
-
// openai-completions default). Without this override the resolver would POST
|
|
2805
|
-
// anthropic-style requests to /v1/messages and the gateway would return its
|
|
2806
|
-
// `Page Not Found` HTML (issue #887 for the qwen/m2.7 entries; minimax-m3
|
|
2807
|
-
// and minimax-m3-free added under #1617 for the same root cause).
|
|
2808
|
-
//
|
|
2809
|
-
// deepseek-v4-flash is the inverse case: it falls through to
|
|
2810
|
-
// openai-completions by default, but the Go gateway's
|
|
2811
|
-
// /zen/go/v1/chat/completions route does not work for this model while
|
|
2812
|
-
// /zen/go/v1/responses does (user-verified against the live gateway,
|
|
2813
|
-
// 2026-08-08; Flash only — deepseek-v4-pro serves fine on chat completions).
|
|
2814
|
-
//
|
|
2815
|
-
// muse-spark-1.2 / muse-spark-1.2-contributor are the same inverse case, but
|
|
2816
|
-
// worse: models.dev does not list them under opencode-go at all, so they have
|
|
2817
|
-
// no bundled reference and only exist via live gateway discovery. Without the
|
|
2818
|
-
// discovery-side pin they default to openai-completions even though the
|
|
2819
|
-
// gateway only serves them at /zen/go/v1/responses (@ai-sdk/openai per
|
|
2820
|
-
// https://opencode.ai/docs/go/#endpoints). The completions parser then closes
|
|
2821
|
-
// the stream with no finish_reason on every tool-call turn (#8957).
|
|
2822
|
-
const OPENCODE_GO_API_ID_OVERRIDES: Readonly<Record<string, Api>> = {
|
|
2823
|
-
"deepseek-v4-flash": "openai-responses",
|
|
2824
|
-
"muse-spark-1.2": "openai-responses",
|
|
2825
|
-
"muse-spark-1.2-contributor": "openai-responses",
|
|
2826
|
-
"minimax-m2.7": "openai-completions",
|
|
2827
|
-
"minimax-m3": "openai-completions",
|
|
2828
|
-
"minimax-m3-free": "openai-completions",
|
|
2829
|
-
"qwen3.5-plus": "openai-completions",
|
|
2830
|
-
"qwen3.6-plus": "openai-completions",
|
|
2831
|
-
};
|
|
2958
|
+
// The per-id gateway-verified route pins live in the `api-routes` behavior
|
|
2959
|
+
// rules (`runtime/behavior.kdl`; #887, #1617, #8957).
|
|
2832
2960
|
// Runtime-discovered rows cached before model-identity corrections retain
|
|
2833
2961
|
// stale capability metadata until the authoritative catalog TTL expires.
|
|
2834
2962
|
const OPENCODE_CACHE_MIGRATION_MODEL_IDS = ["glm-5.3-flash"] as const;
|
|
2963
|
+
const OPENCODE_ZEN_CACHE_MIGRATION_MODEL_IDS = ["gemini-3.7-flash"] as const;
|
|
2835
2964
|
|
|
2836
2965
|
// Billing-variant suffixes the OpenCode gateways append to a base model id
|
|
2837
2966
|
// without changing its transport (`deepseek-v4-flash-free`,
|
|
@@ -2862,7 +2991,13 @@ function openCodeModelManagerOptions(
|
|
|
2862
2991
|
const siblingReferences = createBundledReferenceMap<Api>(
|
|
2863
2992
|
providerId === "opencode-go" ? "opencode-zen" : "opencode-go",
|
|
2864
2993
|
);
|
|
2865
|
-
const apiOverrides =
|
|
2994
|
+
const apiOverrides = (id: string): Api | undefined => {
|
|
2995
|
+
// Route pins live in the `api-routes` behavior rules.
|
|
2996
|
+
const route = apiRouteFor(providerId, id)?.api;
|
|
2997
|
+
return route === "openai-completions" || route === "openai-responses" || route === "anthropic-messages"
|
|
2998
|
+
? route
|
|
2999
|
+
: undefined;
|
|
3000
|
+
};
|
|
2866
3001
|
// Routes a discovered id with no same-provider metadata. models.dev lags
|
|
2867
3002
|
// the gateway (muse-spark-1.2[-contributor] shipped gateway-first, #8957),
|
|
2868
3003
|
// so borrow the openai-responses route from the sibling gateway or the
|
|
@@ -2881,8 +3016,8 @@ function openCodeModelManagerOptions(
|
|
|
2881
3016
|
const resolveApi = (id: string, defaultApi: Api): Api => {
|
|
2882
3017
|
const base = openCodeBaseModelId(id);
|
|
2883
3018
|
return (
|
|
2884
|
-
apiOverrides
|
|
2885
|
-
(base ? apiOverrides
|
|
3019
|
+
apiOverrides(id) ??
|
|
3020
|
+
(base ? apiOverrides(base) : undefined) ??
|
|
2886
3021
|
references.get(id)?.api ??
|
|
2887
3022
|
fallbackApi(id, base) ??
|
|
2888
3023
|
defaultApi
|
|
@@ -2894,8 +3029,12 @@ function openCodeModelManagerOptions(
|
|
|
2894
3029
|
dynamicModelsAuthoritative: true,
|
|
2895
3030
|
// Per-id route pins and capability migrations are cache identity:
|
|
2896
3031
|
// without this, rows cached before a correction keep the stale route or
|
|
2897
|
-
// thinking surface until TTL expiry (#8957, #9960).
|
|
2898
|
-
dropCachedModelIdsOnStaticMismatch: [
|
|
3032
|
+
// thinking surface until TTL expiry (#8957, #9960, #10543).
|
|
3033
|
+
dropCachedModelIdsOnStaticMismatch: [
|
|
3034
|
+
...apiRouteExactModelIds(providerId),
|
|
3035
|
+
...OPENCODE_CACHE_MIGRATION_MODEL_IDS,
|
|
3036
|
+
...(providerId === "opencode-zen" ? OPENCODE_ZEN_CACHE_MIGRATION_MODEL_IDS : []),
|
|
3037
|
+
],
|
|
2899
3038
|
modelsDev: {
|
|
2900
3039
|
fetch: () => fetchRevalidatedWellKnownModelsWithTimeout(config?.fetch),
|
|
2901
3040
|
map: payload => {
|
|
@@ -2922,12 +3061,13 @@ function openCodeModelManagerOptions(
|
|
|
2922
3061
|
// stencil fallback; the fallback never selects a transport.
|
|
2923
3062
|
const api = resolveApi(defaults.id, defaults.api);
|
|
2924
3063
|
const baseUrl = openCodeBaseUrlForApi(api, basePath);
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
//
|
|
2928
|
-
//
|
|
2929
|
-
// (`reasoning: false`).
|
|
2930
|
-
// and restore the
|
|
3064
|
+
const lineage = museSparkLineageSpec(defaults.id);
|
|
3065
|
+
if (lineage) {
|
|
3066
|
+
// Gateway lists Muse Spark as bare ids with no capability
|
|
3067
|
+
// metadata and often no local bundled row, so the generic
|
|
3068
|
+
// defaults would hide the effort dial (`reasoning: false`).
|
|
3069
|
+
// Keep the pinned/fallback route and restore the lineage's
|
|
3070
|
+
// thinking surface.
|
|
2931
3071
|
return {
|
|
2932
3072
|
...(reference ?? defaults),
|
|
2933
3073
|
id: defaults.id,
|
|
@@ -2936,10 +3076,16 @@ function openCodeModelManagerOptions(
|
|
|
2936
3076
|
provider: providerId,
|
|
2937
3077
|
baseUrl,
|
|
2938
3078
|
reasoning: true,
|
|
2939
|
-
input: reference?.input ??
|
|
2940
|
-
thinking: reference?.thinking ??
|
|
2941
|
-
contextWindow: toPositiveNumber(
|
|
2942
|
-
|
|
3079
|
+
input: reference?.input ?? lineage.input,
|
|
3080
|
+
thinking: reference?.thinking ?? lineage.thinking,
|
|
3081
|
+
contextWindow: toPositiveNumber(
|
|
3082
|
+
entry.context_length,
|
|
3083
|
+
reference?.contextWindow ?? lineage.contextWindow,
|
|
3084
|
+
),
|
|
3085
|
+
maxTokens: toPositiveNumber(
|
|
3086
|
+
entry.max_completion_tokens,
|
|
3087
|
+
reference?.maxTokens ?? lineage.maxTokens,
|
|
3088
|
+
),
|
|
2943
3089
|
};
|
|
2944
3090
|
}
|
|
2945
3091
|
if (!reference) {
|
|
@@ -3015,7 +3161,9 @@ export function ollamaModelManagerOptions(config?: OllamaModelManagerConfig): Mo
|
|
|
3015
3161
|
model.contextWindow = metadata.contextWindow;
|
|
3016
3162
|
if (metadata.reasoning !== undefined) {
|
|
3017
3163
|
model.reasoning = metadata.reasoning;
|
|
3018
|
-
|
|
3164
|
+
// The effort ladder is rule-owned (`providers/ollama.kdl`);
|
|
3165
|
+
// derived at build time from the reasoning flag.
|
|
3166
|
+
model.thinking = undefined;
|
|
3019
3167
|
}
|
|
3020
3168
|
if (metadata.input) {
|
|
3021
3169
|
model.input = metadata.input;
|
|
@@ -3117,7 +3265,7 @@ export function openrouterModelManagerOptions(
|
|
|
3117
3265
|
? topProvider.max_completion_tokens
|
|
3118
3266
|
: baseModel.maxTokens,
|
|
3119
3267
|
...(!supportsToolChoice && {
|
|
3120
|
-
compat: { ...
|
|
3268
|
+
compat: { ...baseModel.compat, supportsToolChoice: false },
|
|
3121
3269
|
}),
|
|
3122
3270
|
};
|
|
3123
3271
|
},
|
|
@@ -3158,7 +3306,7 @@ function isZenMuxAnthropicModel(entry: OpenAICompatibleModelRecord, modelId: str
|
|
|
3158
3306
|
if (typeof entry.owned_by === "string" && entry.owned_by.toLowerCase() === "anthropic") {
|
|
3159
3307
|
return true;
|
|
3160
3308
|
}
|
|
3161
|
-
return modelId
|
|
3309
|
+
return classifyModel("zenmux", modelId, { lenient: true }).class === "anthropic";
|
|
3162
3310
|
}
|
|
3163
3311
|
|
|
3164
3312
|
function getZenMuxPricingValue(pricings: Record<string, unknown> | undefined, key: string): number {
|
|
@@ -3352,6 +3500,20 @@ export const ALIBABA_TOKEN_PLAN_STATIC_MODELS: readonly ModelSpec<"openai-comple
|
|
|
3352
3500
|
},
|
|
3353
3501
|
compat: ALIBABA_TOKEN_PLAN_QWEN_EFFORT_COMPAT,
|
|
3354
3502
|
},
|
|
3503
|
+
{
|
|
3504
|
+
id: "qwen3.8-flash",
|
|
3505
|
+
name: "Qwen3.8 Flash",
|
|
3506
|
+
api: "openai-completions",
|
|
3507
|
+
provider: "alibaba-token-plan",
|
|
3508
|
+
baseUrl: ALIBABA_TOKEN_PLAN_BASE_URL,
|
|
3509
|
+
reasoning: true,
|
|
3510
|
+
input: ["text", "image"],
|
|
3511
|
+
cost: ALIBABA_TOKEN_PLAN_COST,
|
|
3512
|
+
contextWindow: 1_000_000,
|
|
3513
|
+
maxTokens: 131_072,
|
|
3514
|
+
thinking: ALIBABA_TOKEN_PLAN_REASONING,
|
|
3515
|
+
compat: ALIBABA_TOKEN_PLAN_COMPAT,
|
|
3516
|
+
},
|
|
3355
3517
|
{
|
|
3356
3518
|
id: "qwen3.7-max",
|
|
3357
3519
|
name: "Qwen3.7 Max",
|
|
@@ -3430,82 +3592,9 @@ export const ALIBABA_TOKEN_PLAN_STATIC_MODELS: readonly ModelSpec<"openai-comple
|
|
|
3430
3592
|
},
|
|
3431
3593
|
];
|
|
3432
3594
|
|
|
3433
|
-
/**
|
|
3434
|
-
* Metadata for Alibaba Token Plan models that are dynamically discovered but not
|
|
3435
|
-
* in the static catalog. Context window and max tokens are sourced from
|
|
3436
|
-
* official model documentation and provider catalogs. Unknown future models
|
|
3437
|
-
* remain available with null limits instead of assigning unsafe guessed limits.
|
|
3438
|
-
*/
|
|
3439
|
-
export interface AlibabaTokenPlanModelLimits {
|
|
3440
|
-
contextWindow: number;
|
|
3441
|
-
maxTokens: number;
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
export const ALIBABA_TOKEN_PLAN_DISCOVERED_MODEL_LIMITS: Readonly<Record<string, AlibabaTokenPlanModelLimits>> = {
|
|
3445
|
-
"qwen3.6-plus": {
|
|
3446
|
-
contextWindow: 1_000_000,
|
|
3447
|
-
maxTokens: 65_536,
|
|
3448
|
-
},
|
|
3449
|
-
"qwen3.8-max": {
|
|
3450
|
-
contextWindow: 1_000_000,
|
|
3451
|
-
maxTokens: 131_072,
|
|
3452
|
-
},
|
|
3453
|
-
"deepseek-v4-flash": {
|
|
3454
|
-
contextWindow: 1_000_000,
|
|
3455
|
-
maxTokens: 384_000,
|
|
3456
|
-
},
|
|
3457
|
-
"deepseek-v4-flash-0731": {
|
|
3458
|
-
contextWindow: 1_000_000,
|
|
3459
|
-
maxTokens: 384_000,
|
|
3460
|
-
},
|
|
3461
|
-
"deepseek-v4-pro-0813": {
|
|
3462
|
-
contextWindow: 1_000_000,
|
|
3463
|
-
maxTokens: 384_000,
|
|
3464
|
-
},
|
|
3465
|
-
"deepseek-v3.2": {
|
|
3466
|
-
contextWindow: 131_072,
|
|
3467
|
-
maxTokens: 65_536,
|
|
3468
|
-
},
|
|
3469
|
-
"glm-5.1": {
|
|
3470
|
-
contextWindow: 202_752,
|
|
3471
|
-
maxTokens: 128_000,
|
|
3472
|
-
},
|
|
3473
|
-
"glm-5": {
|
|
3474
|
-
contextWindow: 202_752,
|
|
3475
|
-
maxTokens: 16_384,
|
|
3476
|
-
},
|
|
3477
|
-
"kimi-k2.7-code": {
|
|
3478
|
-
contextWindow: 262_144,
|
|
3479
|
-
maxTokens: 262_144,
|
|
3480
|
-
},
|
|
3481
|
-
"kimi-k2.6": {
|
|
3482
|
-
contextWindow: 262_144,
|
|
3483
|
-
maxTokens: 262_144,
|
|
3484
|
-
},
|
|
3485
|
-
"kimi-k2.5": {
|
|
3486
|
-
contextWindow: 262_144,
|
|
3487
|
-
maxTokens: 98_304,
|
|
3488
|
-
},
|
|
3489
|
-
"minimax-m2.5": {
|
|
3490
|
-
contextWindow: 196_608,
|
|
3491
|
-
maxTokens: 32_768,
|
|
3492
|
-
},
|
|
3493
|
-
};
|
|
3494
|
-
|
|
3495
|
-
const ALIBABA_TOKEN_PLAN_NON_CHAT_MODEL_PREFIXES = [
|
|
3496
|
-
"fun-asr",
|
|
3497
|
-
"happyhorse-",
|
|
3498
|
-
"qwen-audio-",
|
|
3499
|
-
"qwen-image-",
|
|
3500
|
-
"text-embedding-",
|
|
3501
|
-
"wan2.7-",
|
|
3502
|
-
] as const;
|
|
3503
|
-
|
|
3504
3595
|
function isAlibabaTokenPlanChatModelId(id: string): boolean {
|
|
3505
3596
|
const normalized = id.trim().toLowerCase();
|
|
3506
|
-
return (
|
|
3507
|
-
normalized.length > 0 && !ALIBABA_TOKEN_PLAN_NON_CHAT_MODEL_PREFIXES.some(prefix => normalized.startsWith(prefix))
|
|
3508
|
-
);
|
|
3597
|
+
return normalized.length > 0 && !isExcludedModel("alibaba-token-plan", normalized);
|
|
3509
3598
|
}
|
|
3510
3599
|
|
|
3511
3600
|
export interface AlibabaTokenPlanModelManagerConfig {
|
|
@@ -3547,16 +3636,18 @@ export function alibabaTokenPlanModelManagerOptions(
|
|
|
3547
3636
|
};
|
|
3548
3637
|
}
|
|
3549
3638
|
const normalizedId = defaults.id.trim().toLowerCase();
|
|
3550
|
-
|
|
3639
|
+
// Discovery carries no limit fields; the `model-limits` behavior
|
|
3640
|
+
// rules pin values from official model documentation.
|
|
3641
|
+
const limits = modelLimitsFor("alibaba-token-plan", normalizedId);
|
|
3551
3642
|
const enriched = limits
|
|
3552
3643
|
? {
|
|
3553
3644
|
...defaults,
|
|
3554
|
-
contextWindow: limits.contextWindow,
|
|
3555
|
-
maxTokens: limits.maxTokens,
|
|
3645
|
+
contextWindow: limits.context ?? defaults.contextWindow,
|
|
3646
|
+
maxTokens: limits.maxTokens ?? defaults.maxTokens,
|
|
3556
3647
|
}
|
|
3557
3648
|
: defaults;
|
|
3558
3649
|
|
|
3559
|
-
if (
|
|
3650
|
+
if (isDeepseekV4Generation("alibaba-token-plan", normalizedId)) {
|
|
3560
3651
|
return {
|
|
3561
3652
|
...enriched,
|
|
3562
3653
|
reasoning: true,
|
|
@@ -3685,7 +3776,12 @@ function kimiSupportsReasoning(entry: OpenAICompatibleModelRecord, modelId: stri
|
|
|
3685
3776
|
case "no":
|
|
3686
3777
|
return false;
|
|
3687
3778
|
default:
|
|
3688
|
-
|
|
3779
|
+
// Without a reasoning bit, the classified thinking-variant fact names
|
|
3780
|
+
// the reasoning sibling.
|
|
3781
|
+
return (
|
|
3782
|
+
entry.supports_reasoning === true ||
|
|
3783
|
+
classifyModel("kimi-code", modelId, { lenient: true }).thinkingVariant === true
|
|
3784
|
+
);
|
|
3689
3785
|
}
|
|
3690
3786
|
}
|
|
3691
3787
|
|
|
@@ -3701,15 +3797,13 @@ function mapKimiApiFormat(protocol: unknown): OpenAICompat["kimiApiFormat"] {
|
|
|
3701
3797
|
* per-family caps instead of a blanket constant. Values match models.dev's
|
|
3702
3798
|
* `kimi-for-coding` and `moonshotai` kimi-k3 entries. See #6711.
|
|
3703
3799
|
*/
|
|
3704
|
-
export const KIMI_CODE_K3_MAX_TOKENS = 131_072;
|
|
3705
|
-
export const KIMI_CODE_FOR_CODING_MAX_TOKENS = 32_768;
|
|
3706
|
-
|
|
3707
3800
|
/** Fallback output cap for Kimi Code families without a documented ceiling (legacy K2 discovery rows). */
|
|
3708
3801
|
export const KIMI_CODE_DEFAULT_MAX_TOKENS = 32_000;
|
|
3709
3802
|
|
|
3710
3803
|
/**
|
|
3711
|
-
* Resolve a Kimi Code model's output ceiling
|
|
3712
|
-
*
|
|
3804
|
+
* Resolve a Kimi Code model's output ceiling; the per-family caps are
|
|
3805
|
+
* rule-owned (`runtime/behavior.kdl` model-limits), everything else ->
|
|
3806
|
+
* `fallback`.
|
|
3713
3807
|
*/
|
|
3714
3808
|
export function kimiCodeMaxTokens(modelId: string, fallback?: number): number;
|
|
3715
3809
|
export function kimiCodeMaxTokens(modelId: string, fallback: number | null): number | null;
|
|
@@ -3717,10 +3811,7 @@ export function kimiCodeMaxTokens(
|
|
|
3717
3811
|
modelId: string,
|
|
3718
3812
|
fallback: number | null = KIMI_CODE_DEFAULT_MAX_TOKENS,
|
|
3719
3813
|
): number | null {
|
|
3720
|
-
|
|
3721
|
-
if (id.startsWith("k3")) return KIMI_CODE_K3_MAX_TOKENS;
|
|
3722
|
-
if (id.startsWith("kimi-for-coding")) return KIMI_CODE_FOR_CODING_MAX_TOKENS;
|
|
3723
|
-
return fallback;
|
|
3814
|
+
return modelLimitsFor("kimi-code", modelId.trim().toLowerCase())?.maxTokens ?? fallback;
|
|
3724
3815
|
}
|
|
3725
3816
|
|
|
3726
3817
|
export function kimiCodeModelManagerOptions(
|
|
@@ -3753,7 +3844,9 @@ export function kimiCodeModelManagerOptions(
|
|
|
3753
3844
|
...defaults,
|
|
3754
3845
|
name: typeof entry.display_name === "string" ? entry.display_name : defaults.name,
|
|
3755
3846
|
reasoning,
|
|
3756
|
-
|
|
3847
|
+
// K2.5's misreported vision support is rule-owned
|
|
3848
|
+
// (`providers/kimi-code.kdl` input-modalities).
|
|
3849
|
+
input: entry.supports_image_in === true ? ["text", "image"] : ["text"],
|
|
3757
3850
|
contextWindow: typeof entry.context_length === "number" ? entry.context_length : 262144,
|
|
3758
3851
|
maxTokens: kimiCodeMaxTokens(id),
|
|
3759
3852
|
thinking,
|
|
@@ -3838,7 +3931,7 @@ export async function fetchLmStudioNativeModelMetadata(
|
|
|
3838
3931
|
try {
|
|
3839
3932
|
const response = await fetchImpl(`${nativeBaseUrl}/api/v0/models`, {
|
|
3840
3933
|
method: "GET",
|
|
3841
|
-
headers: { Accept: "application/json", ...
|
|
3934
|
+
headers: { Accept: "application/json", ...options?.headers },
|
|
3842
3935
|
signal,
|
|
3843
3936
|
});
|
|
3844
3937
|
if (!response.ok) {
|
|
@@ -4189,9 +4282,10 @@ export function basetenModelManagerOptions(
|
|
|
4189
4282
|
// model. Only models with a verified Baseten reasoning policy are enabled
|
|
4190
4283
|
// here; an unknown model may use a different reasoning wire shape or effort
|
|
4191
4284
|
// vocabulary, which OMP must not guess.
|
|
4285
|
+
const identity = classifyModel("baseten", defaults.id, { lenient: true });
|
|
4192
4286
|
const isSupportedBasetenReasoningModel =
|
|
4193
|
-
|
|
4194
|
-
|
|
4287
|
+
(identity.class === "kimi" && identity.family === "k3") ||
|
|
4288
|
+
isGlmReasoningIdentity("baseten", defaults.id, "5.2") ||
|
|
4195
4289
|
defaults.id === "openai/gpt-oss-120b" ||
|
|
4196
4290
|
defaults.id === "deepseek-ai/DeepSeek-V4-Pro";
|
|
4197
4291
|
const reasoning =
|
|
@@ -4266,55 +4360,25 @@ export function coreWeaveModelManagerOptions(
|
|
|
4266
4360
|
|
|
4267
4361
|
const META_MODEL_API_BASE_URL = "https://api.meta.ai/v1";
|
|
4268
4362
|
const META_MUSE_SPARK_COST = { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 } as const;
|
|
4363
|
+
// Contributor SKUs (`-contributor`): same model, discounted because prompts
|
|
4364
|
+
// are used for training.
|
|
4365
|
+
const META_MUSE_SPARK_CONTRIBUTOR_COST = { input: 0.1, output: 0.2, cacheRead: 0.002, cacheWrite: 0 } as const;
|
|
4269
4366
|
const META_MUSE_SPARK_THINKING: ThinkingConfig = {
|
|
4270
4367
|
mode: "effort",
|
|
4271
4368
|
efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh],
|
|
4272
4369
|
};
|
|
4273
4370
|
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
provider: "meta",
|
|
4280
|
-
baseUrl: META_MODEL_API_BASE_URL,
|
|
4281
|
-
reasoning: true,
|
|
4282
|
-
input: ["text", "image"],
|
|
4283
|
-
cost: META_MUSE_SPARK_COST,
|
|
4284
|
-
contextWindow: 1_048_576,
|
|
4285
|
-
maxTokens: 131_072,
|
|
4286
|
-
thinking: META_MUSE_SPARK_THINKING,
|
|
4287
|
-
compat: {
|
|
4288
|
-
supportsReasoningEffort: true,
|
|
4289
|
-
includeEncryptedReasoning: true,
|
|
4290
|
-
},
|
|
4291
|
-
},
|
|
4292
|
-
{
|
|
4293
|
-
id: "muse-spark-1.2",
|
|
4294
|
-
name: "Muse Spark 1.2",
|
|
4295
|
-
api: "openai-responses",
|
|
4296
|
-
provider: "meta",
|
|
4297
|
-
baseUrl: META_MODEL_API_BASE_URL,
|
|
4298
|
-
reasoning: true,
|
|
4299
|
-
input: ["text", "image"],
|
|
4300
|
-
cost: META_MUSE_SPARK_COST,
|
|
4301
|
-
contextWindow: 1_048_576,
|
|
4302
|
-
maxTokens: 131_072,
|
|
4303
|
-
thinking: META_MUSE_SPARK_THINKING,
|
|
4304
|
-
compat: {
|
|
4305
|
-
supportsReasoningEffort: true,
|
|
4306
|
-
includeEncryptedReasoning: true,
|
|
4307
|
-
},
|
|
4308
|
-
},
|
|
4309
|
-
{
|
|
4310
|
-
id: "muse-spark-1.2-contributor",
|
|
4311
|
-
name: "Muse Spark 1.2 Contributor (Data Used for Training)",
|
|
4371
|
+
function museSparkSpec(revision: string, tier: "standard" | "contributor"): ModelSpec<"openai-responses"> {
|
|
4372
|
+
const contributor = tier === "contributor";
|
|
4373
|
+
return {
|
|
4374
|
+
id: contributor ? `muse-spark-${revision}-contributor` : `muse-spark-${revision}`,
|
|
4375
|
+
name: contributor ? `Muse Spark ${revision} (C)` : `Muse Spark ${revision}`,
|
|
4312
4376
|
api: "openai-responses",
|
|
4313
4377
|
provider: "meta",
|
|
4314
4378
|
baseUrl: META_MODEL_API_BASE_URL,
|
|
4315
4379
|
reasoning: true,
|
|
4316
4380
|
input: ["text", "image"],
|
|
4317
|
-
cost:
|
|
4381
|
+
cost: contributor ? META_MUSE_SPARK_CONTRIBUTOR_COST : META_MUSE_SPARK_COST,
|
|
4318
4382
|
contextWindow: 1_048_576,
|
|
4319
4383
|
maxTokens: 131_072,
|
|
4320
4384
|
thinking: META_MUSE_SPARK_THINKING,
|
|
@@ -4322,9 +4386,42 @@ export const META_MUSE_STATIC_MODELS: readonly ModelSpec<"openai-responses">[] =
|
|
|
4322
4386
|
supportsReasoningEffort: true,
|
|
4323
4387
|
includeEncryptedReasoning: true,
|
|
4324
4388
|
},
|
|
4325
|
-
}
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
/**
|
|
4393
|
+
* Muse Spark revisions served by Meta's first-party Responses API. Meta's
|
|
4394
|
+
* `/v1/models` lists bare ids with no capability metadata, so every revision
|
|
4395
|
+
* must be seeded here or discovery yields a text-only, non-reasoning model
|
|
4396
|
+
* with an unknown context window.
|
|
4397
|
+
*/
|
|
4398
|
+
export const META_MUSE_STATIC_MODELS: readonly ModelSpec<"openai-responses">[] = [
|
|
4399
|
+
museSparkSpec("1.1", "standard"),
|
|
4400
|
+
museSparkSpec("1.2", "standard"),
|
|
4401
|
+
museSparkSpec("1.2", "contributor"),
|
|
4402
|
+
museSparkSpec("1.3", "standard"),
|
|
4403
|
+
museSparkSpec("1.3", "contributor"),
|
|
4326
4404
|
];
|
|
4327
4405
|
|
|
4406
|
+
const META_MUSE_MODEL_BY_ID: Partial<Record<string, ModelSpec<"openai-responses">>> = Object.fromEntries(
|
|
4407
|
+
META_MUSE_STATIC_MODELS.map(model => [model.id, model]),
|
|
4408
|
+
);
|
|
4409
|
+
|
|
4410
|
+
/**
|
|
4411
|
+
* Lineage reference for a Muse Spark revision Meta ships before the seed
|
|
4412
|
+
* lists it. Every revision so far has kept the 1M window, the effort ladder,
|
|
4413
|
+
* and per-tier pricing, so a new one inherits them (with its own display
|
|
4414
|
+
* name) instead of surfacing as a text-only model with no limits. Only ids
|
|
4415
|
+
* that classify into the `muse-spark` family with a revision qualify.
|
|
4416
|
+
*/
|
|
4417
|
+
function museSparkLineageSpec(id: string): ModelSpec<"openai-responses"> | undefined {
|
|
4418
|
+
const identity = classifyModel("meta", id, { lenient: true });
|
|
4419
|
+
if (identity.family !== "muse-spark" || identity.revision === undefined) return undefined;
|
|
4420
|
+
const [major, minor, patch] = parseRevision(identity.revision) ?? [0, 0, 0];
|
|
4421
|
+
const revision = patch === 0 ? `${major}.${minor}` : identity.revision;
|
|
4422
|
+
return museSparkSpec(revision, billingVariantPlain(id) === undefined ? "standard" : "contributor");
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4328
4425
|
// ---------------------------------------------------------------------------
|
|
4329
4426
|
// 15.76 Amazon Bedrock Mantle
|
|
4330
4427
|
// ---------------------------------------------------------------------------
|
|
@@ -4459,7 +4556,15 @@ export function metaModelManagerOptions(config?: MetaModelManagerConfig): ModelM
|
|
|
4459
4556
|
defaultBaseUrl: META_MODEL_API_BASE_URL,
|
|
4460
4557
|
config,
|
|
4461
4558
|
requireApiKey: true,
|
|
4462
|
-
|
|
4559
|
+
filterModel: (_entry, model) => !isExcludedModel("meta", model.id),
|
|
4560
|
+
// The seed backs ids the bundle has not been regenerated with yet;
|
|
4561
|
+
// the lineage spec backs revisions the seed has not been taught.
|
|
4562
|
+
mapModel: (entry, defaults, reference) =>
|
|
4563
|
+
mapWithBundledReference(
|
|
4564
|
+
entry,
|
|
4565
|
+
defaults,
|
|
4566
|
+
reference ?? META_MUSE_MODEL_BY_ID[defaults.id] ?? museSparkLineageSpec(defaults.id),
|
|
4567
|
+
),
|
|
4463
4568
|
}),
|
|
4464
4569
|
staticModels: META_MUSE_STATIC_MODELS,
|
|
4465
4570
|
};
|
|
@@ -4488,10 +4593,6 @@ export interface MoonshotModelManagerConfig {
|
|
|
4488
4593
|
* through OpenAI-style `reasoning_effort` (see `buildOpenAICompat`).
|
|
4489
4594
|
*/
|
|
4490
4595
|
const MOONSHOT_KIMI_K3_COST = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 } as const;
|
|
4491
|
-
const MOONSHOT_KIMI_K3_CONTEXT_WINDOW = 1_048_576;
|
|
4492
|
-
const MOONSHOT_KIMI_K3_MAX_TOKENS = 131_072;
|
|
4493
|
-
const MOONSHOT_KIMI_K3_THINKING: ThinkingConfig = { mode: "effort", efforts: [Effort.Max], requiresEffort: true };
|
|
4494
|
-
|
|
4495
4596
|
export function moonshotModelManagerOptions(
|
|
4496
4597
|
config?: MoonshotModelManagerConfig,
|
|
4497
4598
|
): ModelManagerOptions<"openai-completions"> {
|
|
@@ -4509,39 +4610,29 @@ export function moonshotModelManagerOptions(
|
|
|
4509
4610
|
const id = model.id.toLowerCase();
|
|
4510
4611
|
// Kimi K3 is discovered but has no bundled/models.dev reference, so the
|
|
4511
4612
|
// generic dynamic defaults would report it "Free" with no capabilities
|
|
4512
|
-
// (#5756).
|
|
4513
|
-
//
|
|
4514
|
-
//
|
|
4515
|
-
//
|
|
4516
|
-
|
|
4517
|
-
if (!reference &&
|
|
4613
|
+
// (#5756). Modalities, limits, and the reasoning surface are rule-owned
|
|
4614
|
+
// (`providers/moonshot.kdl`, `runtime/behavior.kdl` model-limits) and
|
|
4615
|
+
// baked at build time; the mapper seeds only the official pricing when
|
|
4616
|
+
// the endpoint reports zero cost, plus the structured reasoning fact.
|
|
4617
|
+
const identity = classifyModel("moonshot", id, { lenient: true });
|
|
4618
|
+
if (!reference && identity.class === "kimi" && identity.family === "k3") {
|
|
4518
4619
|
const isZeroCost = model.cost.input === 0 && model.cost.output === 0 && model.cost.cacheRead === 0;
|
|
4620
|
+
const limits = modelLimitsFor("moonshot", id) ?? modelLimitsFor("moonshot", "kimi-k3");
|
|
4519
4621
|
return {
|
|
4520
4622
|
...model,
|
|
4521
4623
|
reasoning: true,
|
|
4522
|
-
input: ["text", "image"],
|
|
4523
4624
|
cost: isZeroCost ? { ...MOONSHOT_KIMI_K3_COST } : model.cost,
|
|
4524
|
-
contextWindow: model.contextWindow ??
|
|
4525
|
-
maxTokens: model.maxTokens ??
|
|
4526
|
-
thinking: model.thinking ?? { ...MOONSHOT_KIMI_K3_THINKING },
|
|
4625
|
+
contextWindow: model.contextWindow ?? limits?.context ?? null,
|
|
4626
|
+
maxTokens: model.maxTokens ?? limits?.maxTokens ?? null,
|
|
4527
4627
|
};
|
|
4528
4628
|
}
|
|
4529
|
-
// Moonshot's K2.x
|
|
4530
|
-
//
|
|
4531
|
-
//
|
|
4532
|
-
|
|
4533
|
-
// the bundled K2.5 metadata for every K2.x id we discover.
|
|
4534
|
-
const isKimiK2Reasoning = id.includes("thinking") || /(^|\/)kimi-k2(?:\.\d+)?(?:[-:]|$)/.test(id);
|
|
4535
|
-
const isVision = id.includes("vision") || id.includes("vl") || /(^|\/)kimi-k2(?:\.\d+)?(?:[-:]|$)/.test(id);
|
|
4629
|
+
// Moonshot's K2.x generation and its thinking variants reason on the
|
|
4630
|
+
// native API (#2113); the effort ladder and modalities are rule-owned
|
|
4631
|
+
// and derived at build time from this flag.
|
|
4632
|
+
const isKimiK2Generation = identity.class === "kimi" && identity.family?.startsWith("k2") === true;
|
|
4536
4633
|
return {
|
|
4537
4634
|
...model,
|
|
4538
|
-
reasoning:
|
|
4539
|
-
input: isVision ? ["text", "image"] : model.input,
|
|
4540
|
-
thinking:
|
|
4541
|
-
model.thinking ??
|
|
4542
|
-
(isKimiK2Reasoning
|
|
4543
|
-
? { mode: "effort", efforts: [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High] }
|
|
4544
|
-
: undefined),
|
|
4635
|
+
reasoning: identity.thinkingVariant === true || isKimiK2Generation || model.reasoning,
|
|
4545
4636
|
};
|
|
4546
4637
|
},
|
|
4547
4638
|
});
|
|
@@ -4820,7 +4911,114 @@ export function aiandModelManagerOptions(config?: AiandModelManagerConfig): Mode
|
|
|
4820
4911
|
}
|
|
4821
4912
|
|
|
4822
4913
|
// ---------------------------------------------------------------------------
|
|
4823
|
-
// 16.7
|
|
4914
|
+
// 16.7 Abliteration (abliteration.ai)
|
|
4915
|
+
// ---------------------------------------------------------------------------
|
|
4916
|
+
|
|
4917
|
+
const ABLITERATION_DEFAULT_BASE_URL = "https://api.abliteration.ai/v1";
|
|
4918
|
+
const ABLITERATION_MODEL_COST = { input: 3, output: 3, cacheRead: 0.3, cacheWrite: 0 } as const;
|
|
4919
|
+
const ABLITERATION_LARGE_COST = { input: 5, output: 5, cacheRead: 0.5, cacheWrite: 0 } as const;
|
|
4920
|
+
|
|
4921
|
+
function normalizeAbliterationBaseUrl(baseUrl: string | undefined): string {
|
|
4922
|
+
const value = baseUrl?.trim() || ABLITERATION_DEFAULT_BASE_URL;
|
|
4923
|
+
const normalized = value.replace(/\/+$/, "");
|
|
4924
|
+
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
function createAbliterationStaticModel(
|
|
4928
|
+
id: string,
|
|
4929
|
+
name: string,
|
|
4930
|
+
cost: ModelSpec<"openai-responses">["cost"],
|
|
4931
|
+
contextWindow: number,
|
|
4932
|
+
maxTokens: number,
|
|
4933
|
+
input: ModelSpec<"openai-responses">["input"],
|
|
4934
|
+
): ModelSpec<"openai-responses"> {
|
|
4935
|
+
return {
|
|
4936
|
+
id,
|
|
4937
|
+
name,
|
|
4938
|
+
api: "openai-responses",
|
|
4939
|
+
provider: "abliteration",
|
|
4940
|
+
baseUrl: ABLITERATION_DEFAULT_BASE_URL,
|
|
4941
|
+
reasoning: true,
|
|
4942
|
+
input: [...input],
|
|
4943
|
+
cost: { ...cost },
|
|
4944
|
+
contextWindow,
|
|
4945
|
+
maxTokens,
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
|
|
4949
|
+
/**
|
|
4950
|
+
* Documented abliteration.ai catalog (docs.abliteration.ai/models, 2026-09)
|
|
4951
|
+
* bundled so the provider is usable when generation and first boot have no
|
|
4952
|
+
* live key. The `/v1/models` response is authoritative once discovery runs.
|
|
4953
|
+
* Rows carry only the documented limits and pricing; the per-model reasoning
|
|
4954
|
+
* ladders, alias map and wire quirks come from `rules/classes/glm.kdl` and
|
|
4955
|
+
* `rules/providers/abliteration.kdl` via `buildModel`.
|
|
4956
|
+
*/
|
|
4957
|
+
export const ABLITERATION_STATIC_MODELS: readonly ModelSpec<"openai-responses">[] = [
|
|
4958
|
+
createAbliterationStaticModel("abliterated-model", "Abliterated Model", ABLITERATION_MODEL_COST, 262_144, 262_134, [
|
|
4959
|
+
"text",
|
|
4960
|
+
"image",
|
|
4961
|
+
]),
|
|
4962
|
+
createAbliterationStaticModel(
|
|
4963
|
+
"abliterated-model-large-v2",
|
|
4964
|
+
"Abliterated Model Large V2",
|
|
4965
|
+
ABLITERATION_LARGE_COST,
|
|
4966
|
+
1_000_000,
|
|
4967
|
+
999_990,
|
|
4968
|
+
["text"],
|
|
4969
|
+
),
|
|
4970
|
+
createAbliterationStaticModel(
|
|
4971
|
+
"abliterated-model-large",
|
|
4972
|
+
"Abliterated Model Large",
|
|
4973
|
+
ABLITERATION_LARGE_COST,
|
|
4974
|
+
1_000_000,
|
|
4975
|
+
999_990,
|
|
4976
|
+
["text"],
|
|
4977
|
+
),
|
|
4978
|
+
];
|
|
4979
|
+
|
|
4980
|
+
const ABLITERATION_STATIC_MODEL_BY_ID = new Map(ABLITERATION_STATIC_MODELS.map(model => [model.id, model] as const));
|
|
4981
|
+
const ABLITERATION_STATIC_MODEL_IDS = ABLITERATION_STATIC_MODELS.map(model => model.id);
|
|
4982
|
+
|
|
4983
|
+
export interface AbliterationModelManagerConfig {
|
|
4984
|
+
apiKey?: string;
|
|
4985
|
+
baseUrl?: string;
|
|
4986
|
+
fetch?: FetchImpl;
|
|
4987
|
+
}
|
|
4988
|
+
|
|
4989
|
+
export function abliterationModelManagerOptions(
|
|
4990
|
+
config?: AbliterationModelManagerConfig,
|
|
4991
|
+
): ModelManagerOptions<"openai-responses"> {
|
|
4992
|
+
const apiKey = config?.apiKey;
|
|
4993
|
+
const baseUrl = normalizeAbliterationBaseUrl(config?.baseUrl);
|
|
4994
|
+
const references = createBundledReferenceMap<"openai-responses">("abliteration");
|
|
4995
|
+
return {
|
|
4996
|
+
providerId: "abliteration",
|
|
4997
|
+
dynamicModelsAuthoritative: true,
|
|
4998
|
+
dropCachedModelIdsOnStaticMismatch: ABLITERATION_STATIC_MODEL_IDS,
|
|
4999
|
+
...(apiKey && {
|
|
5000
|
+
fetchDynamicModels: () =>
|
|
5001
|
+
fetchOpenAICompatibleModels({
|
|
5002
|
+
api: "openai-responses",
|
|
5003
|
+
provider: "abliteration",
|
|
5004
|
+
baseUrl,
|
|
5005
|
+
apiKey,
|
|
5006
|
+
mapModel: (entry, defaults) => {
|
|
5007
|
+
const reference = references.get(defaults.id) ?? ABLITERATION_STATIC_MODEL_BY_ID.get(defaults.id);
|
|
5008
|
+
const model = mapWithBundledReference(entry, defaults, reference);
|
|
5009
|
+
// Every abliteration.ai model reasons; `/v1/models` carries no
|
|
5010
|
+
// capability metadata, so an unseeded id would otherwise lose
|
|
5011
|
+
// its effort dial.
|
|
5012
|
+
return reference ? model : { ...model, reasoning: true };
|
|
5013
|
+
},
|
|
5014
|
+
fetch: config?.fetch,
|
|
5015
|
+
}),
|
|
5016
|
+
}),
|
|
5017
|
+
};
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
// ---------------------------------------------------------------------------
|
|
5021
|
+
// 16.8 Yolo-Auto (yolo-auto.com)
|
|
4824
5022
|
// ---------------------------------------------------------------------------
|
|
4825
5023
|
|
|
4826
5024
|
const YOLO_AUTO_BASE_URL = "https://yolo-auto.com/v1";
|
|
@@ -5048,7 +5246,7 @@ export function xiaomiModelManagerOptions(
|
|
|
5048
5246
|
provider: providerId,
|
|
5049
5247
|
baseUrl: url,
|
|
5050
5248
|
apiKey,
|
|
5051
|
-
filterModel: (_entry, model) => !
|
|
5249
|
+
filterModel: (_entry, model) => !isExcludedModel(providerId, model.id),
|
|
5052
5250
|
mapModel: (entry, defaults) => {
|
|
5053
5251
|
const reference = references.get(defaults.id);
|
|
5054
5252
|
const model = mapWithBundledReference(entry, defaults, reference);
|
|
@@ -5735,6 +5933,7 @@ export function vllmModelManagerOptions(config?: VllmModelManagerConfig): ModelM
|
|
|
5735
5933
|
apiKey,
|
|
5736
5934
|
mapModel: (entry, defaults) => {
|
|
5737
5935
|
const model = mapWithBundledReference(entry, defaults, references.get(defaults.id));
|
|
5936
|
+
const identity = classifyModel("vllm", model.id, { lenient: true });
|
|
5738
5937
|
return {
|
|
5739
5938
|
...model,
|
|
5740
5939
|
contextWindow: toPositiveNumber(entry.max_model_len, model.contextWindow),
|
|
@@ -5742,7 +5941,8 @@ export function vllmModelManagerOptions(config?: VllmModelManagerConfig): ModelM
|
|
|
5742
5941
|
// open weights always think (the template cannot disable it), so
|
|
5743
5942
|
// light up the effort dial; buildModel derives the template
|
|
5744
5943
|
// ladder from the id + local-backend compat.
|
|
5745
|
-
reasoning:
|
|
5944
|
+
reasoning:
|
|
5945
|
+
model.reasoning || (identity.class === "qwen" && revisionAtLeast(identity.revision, "3.8")),
|
|
5746
5946
|
};
|
|
5747
5947
|
},
|
|
5748
5948
|
fetch: config?.fetch,
|
|
@@ -5818,13 +6018,6 @@ export interface GithubCopilotModelManagerConfig {
|
|
|
5818
6018
|
fetch?: FetchImpl;
|
|
5819
6019
|
}
|
|
5820
6020
|
|
|
5821
|
-
const COPILOT_ANTHROPIC_MODEL_PATTERN = /^claude-(haiku|sonnet|opus|fable|mythos)-\d/;
|
|
5822
|
-
const isCopilotResponsesModelId = (modelId: string): boolean =>
|
|
5823
|
-
modelId === "grok-4.5" ||
|
|
5824
|
-
modelId === "grok-4.6" ||
|
|
5825
|
-
modelId.startsWith("gpt-5") ||
|
|
5826
|
-
modelId.startsWith("oswe") ||
|
|
5827
|
-
modelId.startsWith("mai-");
|
|
5828
6021
|
const COPILOT_CACHE_INVALIDATED_MODEL_IDS = [
|
|
5829
6022
|
"grok-4.5",
|
|
5830
6023
|
"grok-4.5-1m",
|
|
@@ -5834,12 +6027,8 @@ const COPILOT_CACHE_INVALIDATED_MODEL_IDS = [
|
|
|
5834
6027
|
];
|
|
5835
6028
|
|
|
5836
6029
|
function inferCopilotApi(modelId: string): Api {
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
}
|
|
5840
|
-
if (isCopilotResponsesModelId(modelId)) {
|
|
5841
|
-
return "openai-responses";
|
|
5842
|
-
}
|
|
6030
|
+
const route = apiRouteFor("github-copilot", modelId);
|
|
6031
|
+
if (route?.api === "anthropic-messages" || route?.api === "openai-responses") return route.api;
|
|
5843
6032
|
return "openai-completions";
|
|
5844
6033
|
}
|
|
5845
6034
|
|
|
@@ -5998,8 +6187,8 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
5998
6187
|
providerId: "github-copilot",
|
|
5999
6188
|
cacheProviderId: resolveModelCacheProviderId("github-copilot", { apiKey: rawApiKey, baseUrl }),
|
|
6000
6189
|
dropCachedModelIdsOnStaticMismatch: COPILOT_CACHE_INVALIDATED_MODEL_IDS,
|
|
6001
|
-
// COPILOT_API_HEADERS are compile-time
|
|
6002
|
-
//
|
|
6190
|
+
// COPILOT_API_HEADERS are compile-time wire identity constants, not
|
|
6191
|
+
// credentials. The cache omits all request headers for
|
|
6003
6192
|
// safety and can only restore them from a bundled static entry — so a
|
|
6004
6193
|
// Copilot model with no bundled reference (e.g. a freshly served
|
|
6005
6194
|
// claude-opus-5 and its synthesized -1m sibling) is dropped on offline
|
|
@@ -6019,7 +6208,7 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
6019
6208
|
provider: "github-copilot",
|
|
6020
6209
|
baseUrl: requestBaseUrl,
|
|
6021
6210
|
apiKey,
|
|
6022
|
-
headers:
|
|
6211
|
+
headers: COPILOT_DISCOVERY_HEADERS,
|
|
6023
6212
|
mapModel: (
|
|
6024
6213
|
entry: OpenAICompatibleModelRecord,
|
|
6025
6214
|
defaults: ModelSpec<Api>,
|
|
@@ -6079,6 +6268,10 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
6079
6268
|
maxTokens !== null
|
|
6080
6269
|
? Math.min(contextWindow, defaultContextMax + maxTokens)
|
|
6081
6270
|
: contextWindow;
|
|
6271
|
+
const unreferencedIdentity =
|
|
6272
|
+
reference === undefined && api === "anthropic-messages"
|
|
6273
|
+
? classifyModel("github-copilot", defaults.id, { lenient: true })
|
|
6274
|
+
: undefined;
|
|
6082
6275
|
const base: ModelSpec<Api> = reference
|
|
6083
6276
|
? {
|
|
6084
6277
|
...reference,
|
|
@@ -6089,10 +6282,7 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
6089
6282
|
input,
|
|
6090
6283
|
contextWindow: defaultTierWindow,
|
|
6091
6284
|
maxTokens,
|
|
6092
|
-
headers:
|
|
6093
|
-
...COPILOT_API_HEADERS,
|
|
6094
|
-
...(getProviderReferences().get(defaults.id)?.headers ?? {}),
|
|
6095
|
-
},
|
|
6285
|
+
headers: mergeCopilotApiHeaders(getProviderReferences().get(defaults.id)?.headers),
|
|
6096
6286
|
...(api === "openai-completions"
|
|
6097
6287
|
? {
|
|
6098
6288
|
compat: {
|
|
@@ -6111,7 +6301,7 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
6111
6301
|
input,
|
|
6112
6302
|
contextWindow: defaultTierWindow,
|
|
6113
6303
|
maxTokens,
|
|
6114
|
-
headers:
|
|
6304
|
+
headers: mergeCopilotApiHeaders(),
|
|
6115
6305
|
// Copilot's `/models` advertises no reasoning bit, so a
|
|
6116
6306
|
// thinking-capable Claude with no bundled reference would
|
|
6117
6307
|
// fall back to `reasoning: false` and lose its effort dial.
|
|
@@ -6120,7 +6310,8 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
6120
6310
|
// (<= 3.5) over the Messages proxy is not handed a fabricated
|
|
6121
6311
|
// dial it would reject; a modern reference-less model (e.g.
|
|
6122
6312
|
// claude-opus-5) is marked so `buildModel` derives the ladder.
|
|
6123
|
-
...(
|
|
6313
|
+
...(unreferencedIdentity?.class === "anthropic" &&
|
|
6314
|
+
revisionAtLeast(unreferencedIdentity.revision, "3.7")
|
|
6124
6315
|
? { reasoning: true }
|
|
6125
6316
|
: {}),
|
|
6126
6317
|
...(api === "openai-completions"
|
|
@@ -6400,7 +6591,7 @@ function resolveApiByRules(
|
|
|
6400
6591
|
|
|
6401
6592
|
function createOpenCodeApiResolution(
|
|
6402
6593
|
basePath: string,
|
|
6403
|
-
|
|
6594
|
+
providerId: "opencode-zen" | "opencode-go",
|
|
6404
6595
|
): {
|
|
6405
6596
|
defaultResolution: { api: Api; baseUrl: string };
|
|
6406
6597
|
rules: ApiResolutionRule[];
|
|
@@ -6410,8 +6601,11 @@ function createOpenCodeApiResolution(
|
|
|
6410
6601
|
// - openai-completions / openai-responses / google-generative-ai → /v1
|
|
6411
6602
|
// - anthropic-messages → bare basePath (the Anthropic client appends /v1/messages)
|
|
6412
6603
|
const baseUrlForApi = (api: Api): string => (api === "anthropic-messages" ? basePath : completionsBaseUrl);
|
|
6413
|
-
|
|
6414
|
-
|
|
6604
|
+
// Per-id route pins live in the `api-routes` behavior rules.
|
|
6605
|
+
const overrideRules: ApiResolutionRule[] = (
|
|
6606
|
+
["openai-completions", "openai-responses", "anthropic-messages"] as const
|
|
6607
|
+
).map(api => ({
|
|
6608
|
+
matches: modelId => apiRouteFor(providerId, modelId)?.api === api,
|
|
6415
6609
|
resolved: { api, baseUrl: baseUrlForApi(api) },
|
|
6416
6610
|
}));
|
|
6417
6611
|
return {
|
|
@@ -6437,19 +6631,32 @@ function createOpenCodeApiResolution(
|
|
|
6437
6631
|
}
|
|
6438
6632
|
|
|
6439
6633
|
// Resolver rules for the models.dev descriptor path; the per-id pins live in
|
|
6440
|
-
//
|
|
6441
|
-
|
|
6442
|
-
const
|
|
6443
|
-
"https://opencode.ai/zen",
|
|
6444
|
-
OPENCODE_ZEN_API_ID_OVERRIDES,
|
|
6445
|
-
);
|
|
6446
|
-
const OPENCODE_GO_API_RESOLUTION = createOpenCodeApiResolution(
|
|
6447
|
-
"https://opencode.ai/zen/go",
|
|
6448
|
-
OPENCODE_GO_API_ID_OVERRIDES,
|
|
6449
|
-
);
|
|
6634
|
+
// the `api-routes` behavior rules, which also drive the live-discovery mapper.
|
|
6635
|
+
const OPENCODE_ZEN_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen", "opencode-zen");
|
|
6636
|
+
const OPENCODE_GO_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen/go", "opencode-go");
|
|
6450
6637
|
|
|
6451
6638
|
const COPILOT_BASE_URL = "https://api.githubcopilot.com";
|
|
6452
6639
|
|
|
6640
|
+
const ZAI_ANTHROPIC_BASE_URL = "https://api.z.ai/api/anthropic";
|
|
6641
|
+
// The `zai` catalog provider is the GLM Coding Plan: `/login zai` validates and
|
|
6642
|
+
// stores credentials against the coding-plan endpoint (see auth/zai.kdl), so
|
|
6643
|
+
// the native OpenAI transport must ride the coding-plan base rather than the
|
|
6644
|
+
// general PAYG `/api/paas/v4`, which would bypass plan quota or fail auth.
|
|
6645
|
+
const ZAI_OPENAI_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
|
6646
|
+
|
|
6647
|
+
/** Resolves the transport and endpoint for one Z.AI model catalog entry. */
|
|
6648
|
+
export function resolveZaiApi(modelId: string): { api: "anthropic-messages" | "openai-completions"; baseUrl: string } {
|
|
6649
|
+
const api = apiRouteFor("zai", modelId)?.api ?? "anthropic-messages";
|
|
6650
|
+
switch (api) {
|
|
6651
|
+
case "anthropic-messages":
|
|
6652
|
+
return { api, baseUrl: ZAI_ANTHROPIC_BASE_URL };
|
|
6653
|
+
case "openai-completions":
|
|
6654
|
+
return { api, baseUrl: ZAI_OPENAI_BASE_URL };
|
|
6655
|
+
default:
|
|
6656
|
+
throw new Error(`Unsupported Z.AI API route: ${api}`);
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
|
|
6453
6660
|
const COPILOT_DEFAULT_RESOLUTION = {
|
|
6454
6661
|
api: "openai-completions",
|
|
6455
6662
|
baseUrl: COPILOT_BASE_URL,
|
|
@@ -6457,11 +6664,11 @@ const COPILOT_DEFAULT_RESOLUTION = {
|
|
|
6457
6664
|
|
|
6458
6665
|
const COPILOT_API_RESOLUTION_RULES: readonly ApiResolutionRule[] = [
|
|
6459
6666
|
{
|
|
6460
|
-
matches: modelId =>
|
|
6667
|
+
matches: modelId => apiRouteFor("github-copilot", modelId)?.api === "anthropic-messages",
|
|
6461
6668
|
resolved: { api: "anthropic-messages", baseUrl: COPILOT_BASE_URL },
|
|
6462
6669
|
},
|
|
6463
6670
|
{
|
|
6464
|
-
matches:
|
|
6671
|
+
matches: modelId => apiRouteFor("github-copilot", modelId)?.api === "openai-responses",
|
|
6465
6672
|
resolved: { api: "openai-responses", baseUrl: COPILOT_BASE_URL },
|
|
6466
6673
|
},
|
|
6467
6674
|
];
|
|
@@ -6536,11 +6743,8 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_BEDROCK: readonly ModelsDevProviderDescrip
|
|
|
6536
6743
|
api: "bedrock-converse-stream",
|
|
6537
6744
|
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
6538
6745
|
filterModel: (id, m) => {
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
if (id.startsWith("amazon.titan-text-express") || id.startsWith("mistral.mistral-7b-instruct-v0"))
|
|
6542
|
-
return false;
|
|
6543
|
-
return true;
|
|
6746
|
+
// Undrivable SKUs are rule-owned (`runtime/behavior.kdl` exclude-models).
|
|
6747
|
+
return m.tool_call === true && !isExcludedModel("amazon-bedrock", id);
|
|
6544
6748
|
},
|
|
6545
6749
|
transformModel: (model, modelId, m) => {
|
|
6546
6750
|
const crossRegionId = bedrockCrossRegionId(modelId);
|
|
@@ -6555,7 +6759,10 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_BEDROCK: readonly ModelsDevProviderDescrip
|
|
|
6555
6759
|
// without these rows the catalog only has commercial geos (`us.`/`eu.`/…)
|
|
6556
6760
|
// and model resolution rejects the GovCloud id (or misroutes commercial
|
|
6557
6761
|
// geos onto us-east-1 with GovCloud credentials → 403).
|
|
6558
|
-
|
|
6762
|
+
// Fan out only bare vendor-namespaced source ids (`anthropic.<model>`);
|
|
6763
|
+
// ids already carrying a geo prefix (`us.`/`eu.`/`global.`/`au.`…)
|
|
6764
|
+
// are this fan-out's own output shape.
|
|
6765
|
+
if (modelId.split(".", 1)[0] === "anthropic") {
|
|
6559
6766
|
const displayName = toModelName(m.name, modelId);
|
|
6560
6767
|
return [
|
|
6561
6768
|
bedrockModel,
|
|
@@ -6576,19 +6783,57 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_BEDROCK: readonly ModelsDevProviderDescrip
|
|
|
6576
6783
|
},
|
|
6577
6784
|
];
|
|
6578
6785
|
|
|
6786
|
+
const CLINEPASS_REASONING_BUDGET_RATIOS: Readonly<Record<Effort, number>> = {
|
|
6787
|
+
[Effort.Minimal]: 0.1,
|
|
6788
|
+
[Effort.Low]: 0.2,
|
|
6789
|
+
[Effort.Medium]: 0.5,
|
|
6790
|
+
[Effort.High]: 0.8,
|
|
6791
|
+
[Effort.XHigh]: 0.95,
|
|
6792
|
+
[Effort.Max]: 1,
|
|
6793
|
+
};
|
|
6794
|
+
|
|
6795
|
+
function buildClinePassThinking(raw: ModelsDevModel, model: ModelSpec<Api>): ThinkingConfig | undefined {
|
|
6796
|
+
const effortValues = raw.reasoning_options?.find(option => option.type === "effort")?.values;
|
|
6797
|
+
if (effortValues) {
|
|
6798
|
+
const efforts = THINKING_EFFORTS.filter(effort => effortValues.includes(effort));
|
|
6799
|
+
return efforts.length > 0 ? { mode: "effort", efforts } : undefined;
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6802
|
+
const budget = raw.reasoning_options?.find(option => option.type === "budget_tokens");
|
|
6803
|
+
if (!budget) return undefined;
|
|
6804
|
+
const modelMaxTokens = model.maxTokens ?? CLINEPASS_FALLBACK_MAX_TOKENS;
|
|
6805
|
+
const maximum = toPositiveNumber(budget.max, modelMaxTokens) ?? modelMaxTokens;
|
|
6806
|
+
const minimum = toPositiveNumber(budget.min, 1) ?? 1;
|
|
6807
|
+
const scale = Math.min(modelMaxTokens, maximum);
|
|
6808
|
+
return {
|
|
6809
|
+
mode: "budget",
|
|
6810
|
+
efforts: THINKING_EFFORTS,
|
|
6811
|
+
effortBudgets: Object.fromEntries(
|
|
6812
|
+
THINKING_EFFORTS.map(effort => [
|
|
6813
|
+
effort,
|
|
6814
|
+
Math.min(Math.max(Math.floor(scale * CLINEPASS_REASONING_BUDGET_RATIOS[effort]), minimum), maximum),
|
|
6815
|
+
]),
|
|
6816
|
+
),
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
|
|
6820
|
+
/**
|
|
6821
|
+
* DeepSeek's V4-generation lineage (`v4`, `v4-flash`, `v4.1-pro`) by
|
|
6822
|
+
* structured family. The native roster serves only V4-era SKUs today; the
|
|
6823
|
+
* `tool_call` gate on the descriptor filters the rest.
|
|
6824
|
+
*/
|
|
6825
|
+
function isDeepseekV4Generation(providerId: string, modelId: string): boolean {
|
|
6826
|
+
const identity = classifyModel(providerId, modelId, { lenient: true });
|
|
6827
|
+
if (identity.class !== "deepseek") return false;
|
|
6828
|
+
return identity.family === "v4" || identity.family === "flash" || identity.family === "pro";
|
|
6829
|
+
}
|
|
6830
|
+
|
|
6579
6831
|
const MODELS_DEV_PROVIDER_DESCRIPTORS_CORE: readonly ModelsDevProviderDescriptor[] = [
|
|
6580
6832
|
// --- Anthropic ---
|
|
6581
6833
|
anthropicMessagesDescriptor("anthropic", "anthropic", "https://api.anthropic.com", {
|
|
6582
6834
|
filterModel: (id, m) => {
|
|
6583
6835
|
if (m.tool_call !== true) return false;
|
|
6584
|
-
|
|
6585
|
-
id.startsWith("claude-3-5-haiku") ||
|
|
6586
|
-
id.startsWith("claude-3-7-sonnet") ||
|
|
6587
|
-
id === "claude-3-opus-20240229" ||
|
|
6588
|
-
id === "claude-3-sonnet-20240229"
|
|
6589
|
-
)
|
|
6590
|
-
return false;
|
|
6591
|
-
return true;
|
|
6836
|
+
return !isExcludedModel("anthropic", id);
|
|
6592
6837
|
},
|
|
6593
6838
|
}),
|
|
6594
6839
|
// --- Google ---
|
|
@@ -6604,19 +6849,46 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CORE: readonly ModelsDevProviderDescriptor
|
|
|
6604
6849
|
openAiCompletionsDescriptor("groq", "groq", "https://api.groq.com/openai/v1"),
|
|
6605
6850
|
// --- Cerebras ---
|
|
6606
6851
|
openAiCompletionsDescriptor("cerebras", "cerebras", "https://api.cerebras.ai/v1"),
|
|
6852
|
+
// --- ClinePass ---
|
|
6853
|
+
// The live roster owns membership; this snapshot pins Cline's exact limits,
|
|
6854
|
+
// pricing, modalities, and per-model reasoning controls for known ids.
|
|
6855
|
+
openAiCompletionsDescriptor("cline-pass", "cline-pass", "https://api.cline.bot/api/v1", {
|
|
6856
|
+
transformModel: (model, modelId, raw) => {
|
|
6857
|
+
const id = toClinePassPublicModelId(modelId);
|
|
6858
|
+
const metadata = getClinePassModelMetadata(id);
|
|
6859
|
+
if (metadata) {
|
|
6860
|
+
return {
|
|
6861
|
+
...model,
|
|
6862
|
+
id,
|
|
6863
|
+
name: metadata.name,
|
|
6864
|
+
contextWindow: metadata.contextWindow,
|
|
6865
|
+
maxTokens: metadata.maxTokens,
|
|
6866
|
+
input: metadata.input,
|
|
6867
|
+
cost: metadata.cost,
|
|
6868
|
+
reasoning: metadata.reasoning,
|
|
6869
|
+
thinking: metadata.thinking,
|
|
6870
|
+
compat:
|
|
6871
|
+
metadata.thinking === undefined ? { ...model.compat, supportsReasoningEffort: false } : model.compat,
|
|
6872
|
+
};
|
|
6873
|
+
}
|
|
6874
|
+
return {
|
|
6875
|
+
...model,
|
|
6876
|
+
id,
|
|
6877
|
+
thinking: model.reasoning ? buildClinePassThinking(raw, model) : undefined,
|
|
6878
|
+
};
|
|
6879
|
+
},
|
|
6880
|
+
}),
|
|
6607
6881
|
// --- Together ---
|
|
6608
6882
|
openAiCompletionsDescriptor("togetherai", "together", "https://api.together.xyz/v1"),
|
|
6609
6883
|
// --- CoreWeave Serverless Inference ---
|
|
6610
6884
|
openAiCompletionsDescriptor("wandb", "coreweave", "https://api.inference.wandb.ai/v1", {
|
|
6611
6885
|
transformModel: model => {
|
|
6612
|
-
|
|
6886
|
+
// CoreWeave's roster omits the reasoning bit for gpt-oss; flag it and
|
|
6887
|
+
// let the class rules own the effort ladder.
|
|
6888
|
+
if (classifyModel("coreweave", model.id, { lenient: true }).class !== "gpt-oss") {
|
|
6613
6889
|
return model;
|
|
6614
6890
|
}
|
|
6615
|
-
return {
|
|
6616
|
-
...model,
|
|
6617
|
-
reasoning: true,
|
|
6618
|
-
thinking: { mode: "effort", efforts: [Effort.Low, Effort.Medium, Effort.High] },
|
|
6619
|
-
};
|
|
6891
|
+
return { ...model, reasoning: true };
|
|
6620
6892
|
},
|
|
6621
6893
|
}),
|
|
6622
6894
|
// --- NVIDIA ---
|
|
@@ -6631,7 +6903,7 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CORE: readonly ModelsDevProviderDescriptor
|
|
|
6631
6903
|
openAiCompletionsDescriptor("deepseek", "deepseek", "https://api.deepseek.com", {
|
|
6632
6904
|
// Only ship the v4 family as built-ins; older deepseek-chat / deepseek-reasoner
|
|
6633
6905
|
// ids are kept off the catalog until the issue thread asks for them.
|
|
6634
|
-
filterModel: (id, m) => m.tool_call === true &&
|
|
6906
|
+
filterModel: (id, m) => m.tool_call === true && isDeepseekV4Generation("deepseek", id),
|
|
6635
6907
|
compat: {
|
|
6636
6908
|
// DeepSeek V4 effort remapping is derived in model-thinking metadata; this
|
|
6637
6909
|
// descriptor keeps only transport-shape compat.
|
|
@@ -6662,7 +6934,9 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDe
|
|
|
6662
6934
|
// SKU as "Free" in `/models`. The PAYG key carries the real per-token rates for
|
|
6663
6935
|
// the identical model ids, so the enumerated token costs line up with the other
|
|
6664
6936
|
// subscription providers for comparison (issue #5598).
|
|
6665
|
-
anthropicMessagesDescriptor("zai", "zai",
|
|
6937
|
+
anthropicMessagesDescriptor("zai", "zai", ZAI_ANTHROPIC_BASE_URL, {
|
|
6938
|
+
resolveApi: modelId => resolveZaiApi(modelId),
|
|
6939
|
+
}),
|
|
6666
6940
|
// --- Umans AI ---
|
|
6667
6941
|
// Source the pay-as-you-go catalog: the coding-plan key publishes subscription
|
|
6668
6942
|
// costs as zero, while `/models/info` omits pricing entirely. The generator
|
|
@@ -6862,7 +7136,7 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_SPECIALIZED: readonly ModelsDevProviderDes
|
|
|
6862
7136
|
openAiCompletionsDescriptor("zenmux", "zenmux", ZENMUX_OPENAI_BASE_URL, {
|
|
6863
7137
|
filterModel: filterActiveToolCallModels,
|
|
6864
7138
|
resolveApi: modelId => {
|
|
6865
|
-
if (
|
|
7139
|
+
if (apiRouteFor("zenmux", modelId)?.api === "anthropic-messages") {
|
|
6866
7140
|
return { api: "anthropic-messages" as const, baseUrl: ZENMUX_ANTHROPIC_BASE_URL };
|
|
6867
7141
|
}
|
|
6868
7142
|
return { api: "openai-completions" as const, baseUrl: ZENMUX_OPENAI_BASE_URL };
|