Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

429 lines
15 KiB
TypeScript

import type {
ProviderConfig,
ProviderId,
ThinkingCapability,
ThinkingEffort,
ThinkingLevel,
ThinkingRequestAdapter,
} from '@/lib/types/provider';
export function getModelMetadataKey(providerId: string, modelId: string): string {
return `${providerId}:${modelId}`;
}
function effortCapability(
requestAdapter: ThinkingRequestAdapter,
effortValues: ThinkingEffort[],
defaultEffort: ThinkingEffort,
): ThinkingCapability {
return {
control: 'effort',
requestAdapter,
effortValues,
defaultEffort,
defaultMode: effortValues.includes('none') ? 'disabled' : 'enabled',
toggleable: effortValues.includes('none'),
budgetAdjustable: true,
defaultEnabled: !effortValues.includes('none'),
};
}
function levelCapability(
levelValues: ThinkingLevel[],
defaultLevel: ThinkingLevel,
): ThinkingCapability {
return {
control: 'level',
requestAdapter: 'google',
levelValues,
defaultLevel,
defaultMode: 'enabled',
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
};
}
function toggleCapability(
requestAdapter: ThinkingRequestAdapter,
defaultEnabled = true,
): ThinkingCapability {
return {
control: 'toggle',
requestAdapter,
defaultMode: defaultEnabled ? 'enabled' : 'disabled',
toggleable: true,
budgetAdjustable: false,
defaultEnabled,
};
}
function toggleBudgetCapability(
requestAdapter: ThinkingRequestAdapter,
range: { min: number; max: number; step?: number; allowDynamic?: boolean; disableValue?: number },
defaultEnabled = false,
defaultBudgetTokens?: number,
): ThinkingCapability {
return {
control: 'toggle-budget',
requestAdapter,
budgetRange: range,
defaultBudgetTokens,
defaultMode: defaultEnabled ? 'enabled' : 'disabled',
toggleable: true,
budgetAdjustable: true,
defaultEnabled,
};
}
function budgetOnlyCapability(
requestAdapter: ThinkingRequestAdapter,
range: { min: number; max: number; step?: number; allowDynamic?: boolean },
defaultBudgetTokens?: number,
): ThinkingCapability {
return {
control: 'budget-only',
requestAdapter,
budgetRange: range,
defaultBudgetTokens,
defaultMode: 'enabled',
toggleable: false,
budgetAdjustable: true,
defaultEnabled: true,
};
}
const fixedThinkingCapability: ThinkingCapability = {
control: 'none',
requestAdapter: 'none',
defaultMode: 'enabled',
toggleable: false,
budgetAdjustable: false,
defaultEnabled: true,
};
const anthropicManualBudgetByEffort: Partial<Record<ThinkingEffort, number>> = {
low: 4096,
medium: 10240,
high: 32768,
max: 64000,
};
const anthropicManualEffort: ThinkingCapability = {
control: 'effort',
requestAdapter: 'anthropic',
effortValues: ['none', 'low', 'medium', 'high', 'max'],
defaultEffort: 'medium',
defaultMode: 'enabled',
toggleable: true,
budgetAdjustable: true,
defaultEnabled: true,
anthropicThinking: {
type: 'enabled',
budgetByEffort: anthropicManualBudgetByEffort,
},
};
const anthropicAdaptiveEffort: ThinkingCapability = {
...anthropicManualEffort,
anthropicThinking: { type: 'adaptive' },
};
const anthropicBudget: ThinkingCapability = toggleBudgetCapability(
'anthropic',
{ min: 1024, max: 64000, step: 1024 },
false,
1024,
);
const anthropicOpus47Effort: ThinkingCapability = {
...anthropicAdaptiveEffort,
effortValues: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
};
const deepseekEffort: ThinkingCapability = {
control: 'effort',
requestAdapter: 'deepseek',
effortValues: ['none', 'high', 'max'],
defaultEffort: 'high',
defaultMode: 'enabled',
toggleable: true,
budgetAdjustable: true,
defaultEnabled: true,
};
const glm52Effort: ThinkingCapability = {
control: 'effort',
requestAdapter: 'glm',
effortValues: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
defaultEffort: 'max',
defaultMode: 'enabled',
toggleable: true,
budgetAdjustable: true,
defaultEnabled: true,
};
const hunyuanHy3Effort: ThinkingCapability = {
control: 'effort',
requestAdapter: 'hunyuan',
effortValues: ['none', 'low', 'high'],
defaultEffort: 'none',
defaultMode: 'disabled',
toggleable: true,
budgetAdjustable: true,
defaultEnabled: false,
};
const lemonadeToggleBudget = toggleBudgetCapability(
'lemonade',
{ min: 0, max: 81920, step: 1024, disableValue: 0 },
false,
);
const qwenBudgetEnabled = toggleBudgetCapability(
'qwen',
{ min: 0, max: 81920, step: 1024, disableValue: 0 },
true,
);
const qwenBudgetDisabled = toggleBudgetCapability(
'qwen',
{ min: 0, max: 81920, step: 1024, disableValue: 0 },
false,
);
const siliconflowBudget = budgetOnlyCapability(
'siliconflow',
{ min: 128, max: 32768, step: 1024 },
4096,
);
const siliconflowToggleBudget = toggleBudgetCapability(
'siliconflow',
{ min: 128, max: 32768, step: 1024, disableValue: 0 },
true,
4096,
);
const doubaoMode: ThinkingCapability = {
control: 'mode',
requestAdapter: 'doubao',
defaultMode: 'auto',
toggleable: true,
budgetAdjustable: false,
defaultEnabled: true,
};
const doubaoSeed20Effort: ThinkingCapability = {
control: 'effort',
requestAdapter: 'doubao',
effortValues: ['minimal', 'low', 'medium', 'high'],
defaultEffort: 'medium',
defaultMode: 'enabled',
toggleable: true,
budgetAdjustable: true,
defaultEnabled: true,
};
const minimaxM3Thinking = toggleCapability('anthropic', false);
const THINKING_CAPABILITIES: Record<string, ThinkingCapability> = {
[getModelMetadataKey('openai', 'gpt-5.5')]: effortCapability(
'openai',
['low', 'medium', 'high', 'xhigh'],
'medium',
),
[getModelMetadataKey('openai', 'gpt-5.4-pro')]: effortCapability(
'openai',
['medium', 'high', 'xhigh'],
'medium',
),
[getModelMetadataKey('openai', 'gpt-5.4')]: effortCapability(
'openai',
['none', 'low', 'medium', 'high', 'xhigh'],
'none',
),
[getModelMetadataKey('openai', 'gpt-5.4-mini')]: effortCapability(
'openai',
['none', 'low', 'medium', 'high', 'xhigh'],
'none',
),
[getModelMetadataKey('openai', 'gpt-5.4-nano')]: effortCapability(
'openai',
['none', 'low', 'medium', 'high', 'xhigh'],
'none',
),
[getModelMetadataKey('anthropic', 'claude-opus-4-8')]: anthropicOpus47Effort,
[getModelMetadataKey('anthropic', 'claude-opus-4-7')]: anthropicOpus47Effort,
[getModelMetadataKey('anthropic', 'claude-opus-4-6')]: anthropicAdaptiveEffort,
[getModelMetadataKey('anthropic', 'claude-sonnet-4-6')]: anthropicAdaptiveEffort,
[getModelMetadataKey('anthropic', 'claude-sonnet-4-5')]: anthropicManualEffort,
[getModelMetadataKey('anthropic', 'claude-haiku-4-5')]: anthropicBudget,
[getModelMetadataKey('google', 'gemini-3.5-flash')]: levelCapability(
['minimal', 'low', 'medium', 'high'],
'medium',
),
[getModelMetadataKey('google', 'gemini-3.1-pro-preview')]: levelCapability(
['minimal', 'low', 'medium', 'high'],
'high',
),
[getModelMetadataKey('google', 'gemini-3-flash-preview')]: levelCapability(
['minimal', 'low', 'medium', 'high'],
'high',
),
[getModelMetadataKey('google', 'gemini-2.5-flash')]: toggleBudgetCapability(
'google',
{ min: 0, max: 24576, step: 1024, allowDynamic: true, disableValue: 0 },
true,
-1,
),
[getModelMetadataKey('google', 'gemini-2.5-flash-lite')]: toggleBudgetCapability(
'google',
{ min: 0, max: 24576, step: 1024, allowDynamic: true, disableValue: 0 },
false,
0,
),
[getModelMetadataKey('google', 'gemini-2.5-pro')]: budgetOnlyCapability(
'google',
{ min: 128, max: 32768, step: 1024, allowDynamic: true },
-1,
),
[getModelMetadataKey('glm', 'glm-5.2')]: glm52Effort,
[getModelMetadataKey('glm', 'glm-5.1')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-5v-turbo')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-5')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-4.7')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-4.7-flashx')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-4.7-flash')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-4.6')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-4.6v')]: toggleCapability('glm'),
[getModelMetadataKey('glm', 'glm-4.6v-flash')]: toggleCapability('glm'),
[getModelMetadataKey('qwen', 'qwen3.7-plus')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.7-max')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.6-max-preview')]: qwenBudgetDisabled,
[getModelMetadataKey('qwen', 'qwen3.6-plus')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.6-plus-2026-04-02')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.6-flash')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.6-flash-2026-04-16')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.6-35b-a3b')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.5-flash')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3.5-plus')]: qwenBudgetEnabled,
[getModelMetadataKey('qwen', 'qwen3-max')]: qwenBudgetDisabled,
[getModelMetadataKey('qwen', 'qwen3-vl-plus')]: qwenBudgetDisabled,
[getModelMetadataKey('deepseek', 'deepseek-v4-pro')]: deepseekEffort,
[getModelMetadataKey('deepseek', 'deepseek-v4-flash')]: deepseekEffort,
[getModelMetadataKey('kimi', 'kimi-k2.7-code')]: fixedThinkingCapability,
[getModelMetadataKey('kimi', 'kimi-k2.7-code-highspeed')]: fixedThinkingCapability,
[getModelMetadataKey('kimi', 'kimi-k2.6')]: toggleCapability('kimi'),
[getModelMetadataKey('kimi', 'kimi-k2.5')]: toggleCapability('kimi'),
[getModelMetadataKey('kimi', 'kimi-k2-thinking')]: toggleCapability('kimi'),
[getModelMetadataKey('siliconflow', 'deepseek-ai/DeepSeek-V3.2')]: siliconflowToggleBudget,
[getModelMetadataKey('siliconflow', 'deepseek-ai/DeepSeek-R1')]: siliconflowBudget,
[getModelMetadataKey('siliconflow', 'deepseek-ai/DeepSeek-R1-Distill-Qwen-7B')]:
siliconflowBudget,
[getModelMetadataKey('siliconflow', 'Qwen/Qwen3-VL-32B-Instruct')]: siliconflowToggleBudget,
[getModelMetadataKey('siliconflow', 'THUDM/GLM-4.1V-9B-Thinking')]: siliconflowBudget,
[getModelMetadataKey('siliconflow', 'THUDM/GLM-Z1-Rumination-32B-0414')]: siliconflowBudget,
[getModelMetadataKey('doubao', 'doubao-seed-2-1-pro-260628')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-2-1-turbo-260628')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-evolving')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-character-260628')]: toggleCapability('doubao'),
[getModelMetadataKey('doubao', 'doubao-seed-2-0-pro-260215')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-2-0-lite-260215')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-2-0-mini-260215')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-1-8-251228')]: doubaoMode,
// Volcengine Ark Agent Plan exposes the Seed 2.0 family under dotted aliases
// (the token-plan preset seeds these). They're the same native Doubao models
// as the catalog ids above, served from the plan's own endpoint, so they
// carry the identical doubao thinking control. (Cross-vendor models the plan
// also serves — deepseek/minimax/glm/kimi via ark's OpenAI-compatible path —
// are intentionally NOT mapped here: their native thinking transport doesn't
// apply through that gateway.)
[getModelMetadataKey('doubao', 'doubao-seed-2.0-pro')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-2.0-code')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-2.0-lite')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'doubao-seed-2.0-mini')]: doubaoSeed20Effort,
// Cross-vendor models the Ark Agent Plan also serves through its
// OpenAI-compatible endpoint (all under the `doubao` provider id). Verified
// against a live plan key: each accepts the gateway's unified `reasoning_effort`
// field (low/medium/high) and actually reasons, so they share the doubao
// effort adapter — which sends `minimal` (not `none`) to disable, matching
// what the plan endpoint accepts. The token-plan preset seeds these aliases.
[getModelMetadataKey('doubao', 'deepseek-v4-pro')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'deepseek-v4-flash')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'glm-5.2')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'kimi-k2.7-code')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'kimi-k2.6')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'minimax-m3')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'minimax-m2.7')]: doubaoSeed20Effort,
[getModelMetadataKey('doubao', 'ark-code-latest')]: doubaoSeed20Effort,
[getModelMetadataKey('openrouter', 'deepseek/deepseek-v4-pro')]: effortCapability(
'openrouter',
['low', 'medium', 'high'],
'medium',
),
[getModelMetadataKey('openrouter', 'deepseek/deepseek-v4-flash')]: effortCapability(
'openrouter',
['low', 'medium', 'high'],
'medium',
),
[getModelMetadataKey('grok', 'grok-4.20-reasoning')]: fixedThinkingCapability,
[getModelMetadataKey('grok', 'grok-4.20-multi-agent')]: fixedThinkingCapability,
[getModelMetadataKey('grok', 'grok-4-1-fast-reasoning')]: fixedThinkingCapability,
[getModelMetadataKey('minimax', 'MiniMax-M3')]: minimaxM3Thinking,
[getModelMetadataKey('minimax', 'MiniMax-M2.7')]: fixedThinkingCapability,
[getModelMetadataKey('tencent-hunyuan', 'hy3-preview')]: hunyuanHy3Effort,
[getModelMetadataKey('xiaomi', 'mimo-v2.5-pro')]: toggleCapability('xiaomi'),
[getModelMetadataKey('xiaomi', 'mimo-v2-pro')]: toggleCapability('xiaomi'),
[getModelMetadataKey('xiaomi', 'mimo-v2.5')]: toggleCapability('xiaomi'),
[getModelMetadataKey('xiaomi', 'mimo-v2-omni')]: toggleCapability('xiaomi'),
[getModelMetadataKey('xiaomi', 'mimo-v2-flash')]: toggleCapability('xiaomi'),
[getModelMetadataKey('lemonade', 'Qwen3-4B-GGUF')]: lemonadeToggleBudget,
[getModelMetadataKey('lemonade', 'Qwen3.5-4B-GGUF')]: lemonadeToggleBudget,
[getModelMetadataKey('lemonade', 'Gemma-4-26B-A4B-it-GGUF')]: lemonadeToggleBudget,
[getModelMetadataKey('lemonade', 'gpt-oss-20b')]: lemonadeToggleBudget,
[getModelMetadataKey('lemonade', 'GPT-OSS-20B-GGUF')]: lemonadeToggleBudget,
};
export function getCatalogThinkingCapability(
providerId: string,
modelId: string,
): ThinkingCapability | undefined {
const exact = THINKING_CAPABILITIES[getModelMetadataKey(providerId, modelId)];
if (exact) return exact;
if (providerId === 'lemonade') {
return lemonadeToggleBudget;
}
return undefined;
}
export function applyModelMetadata(providers: Record<ProviderId, ProviderConfig>): void {
for (const provider of Object.values(providers)) {
for (const model of provider.models) {
const thinking = getCatalogThinkingCapability(provider.id, model.id);
if (thinking) {
model.capabilities = {
...model.capabilities,
thinking,
};
}
}
}
}