429 lines
15 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|