mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
802 lines
24 KiB
TypeScript
802 lines
24 KiB
TypeScript
/**
|
|
* Model Catalog - Available models for CLI Proxy providers
|
|
*
|
|
* Ships with CCS to provide users with interactive model selection.
|
|
* Models are mapped to their internal names used by the proxy backend.
|
|
*/
|
|
|
|
import type { CLIProxyProvider } from './types';
|
|
import {
|
|
isAntigravityProvider,
|
|
migrateDeniedAntigravityModelAliases,
|
|
normalizeModelIdForProvider,
|
|
} from './ai-providers/model-id-normalizer';
|
|
import {
|
|
AGY_GEMINI_PRO_COMPATIBILITY_IDS,
|
|
AGY_GEMINI_PRO_HIGH_ID,
|
|
AGY_GEMINI_PRO_LOW_ID,
|
|
} from '../shared/agy-gemini-pro-compatibility';
|
|
import { stripModelConfigurationSuffixes } from '../shared/extended-context-utils';
|
|
import { GEMINI_MINOR_VERSION_COMPATIBILITY_IDS } from '../shared/gemini-minor-version-compatibility';
|
|
|
|
/**
|
|
* Thinking support configuration for a model.
|
|
* Defines how thinking/reasoning budget can be controlled.
|
|
*/
|
|
export interface ThinkingSupport {
|
|
/** Type of thinking control: 'budget' (token count), 'levels' (named levels), 'none' */
|
|
type: 'budget' | 'levels' | 'none';
|
|
/** Minimum budget tokens (for budget type) */
|
|
min?: number;
|
|
/** Maximum budget tokens (for budget type) */
|
|
max?: number;
|
|
/** Valid level names (for levels type) */
|
|
levels?: string[];
|
|
/** Maximum reasoning effort level (caps effort at this level for levels type) */
|
|
maxLevel?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
/** Whether zero/disabled thinking is allowed */
|
|
zeroAllowed?: boolean;
|
|
/** Whether dynamic/auto thinking is allowed */
|
|
dynamicAllowed?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Model entry definition
|
|
*/
|
|
export interface ModelEntry {
|
|
/** Literal model name to put in settings.json */
|
|
id: string;
|
|
/** Human-readable name for display */
|
|
name: string;
|
|
/** Access tier indicator - 'ultra' for Claude, 'pro' for premium Gemini, 'free' for basic */
|
|
tier?: 'free' | 'pro' | 'ultra';
|
|
/** Optional description for the model */
|
|
description?: string;
|
|
/** Model has known issues - show warning when selected */
|
|
broken?: boolean;
|
|
/** Issue URL for broken models */
|
|
issueUrl?: string;
|
|
/** Model is deprecated - show warning when selected */
|
|
deprecated?: boolean;
|
|
/** Deprecation reason/message */
|
|
deprecationReason?: string;
|
|
/** Thinking/reasoning support configuration */
|
|
thinking?: ThinkingSupport;
|
|
/** Whether model supports 1M extended context window (appends [1m] suffix) */
|
|
extendedContext?: boolean;
|
|
/** Whether model can read image inputs natively without the Image transformer */
|
|
nativeImageInput?: boolean;
|
|
/** Additional Codex service-tier suffixes supported by this model. */
|
|
codexServiceTiers?: Array<'fast'>;
|
|
}
|
|
|
|
/**
|
|
* Provider catalog definition
|
|
*/
|
|
export interface ProviderCatalog {
|
|
provider: CLIProxyProvider;
|
|
displayName: string;
|
|
models: ModelEntry[];
|
|
defaultModel: string;
|
|
}
|
|
|
|
/**
|
|
* Model catalog for providers that support interactive configuration
|
|
*
|
|
* Models listed in order of recommendation (top = best)
|
|
*/
|
|
export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> = {
|
|
agy: {
|
|
provider: 'agy',
|
|
displayName: 'Antigravity',
|
|
defaultModel: 'claude-opus-4-6-thinking',
|
|
models: [
|
|
{
|
|
id: 'claude-opus-4-6-thinking',
|
|
name: 'Claude Opus 4.6 Thinking',
|
|
description: 'Latest flagship, extended thinking',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 128000,
|
|
zeroAllowed: true,
|
|
dynamicAllowed: true,
|
|
},
|
|
// TODO: Re-enable when Antigravity backend supports 1M context (currently 256k)
|
|
// extendedContext: true,
|
|
extendedContext: false,
|
|
},
|
|
{
|
|
id: 'claude-sonnet-4-6',
|
|
name: 'Claude Sonnet 4.6',
|
|
description: 'Latest Sonnet with thinking budget support',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 64000,
|
|
zeroAllowed: true,
|
|
dynamicAllowed: true,
|
|
},
|
|
},
|
|
{
|
|
id: AGY_GEMINI_PRO_HIGH_ID,
|
|
name: 'Gemini 3.1 Pro High',
|
|
description: 'Current Antigravity Gemini Pro route with higher reasoning budget',
|
|
nativeImageInput: true,
|
|
thinking: { type: 'none' },
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: AGY_GEMINI_PRO_LOW_ID,
|
|
name: 'Gemini 3.1 Pro Low',
|
|
description: 'Current Antigravity Gemini Pro route with the lighter quota tier',
|
|
nativeImageInput: true,
|
|
thinking: { type: 'none' },
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'gemini-3-1-flash-preview',
|
|
name: 'Gemini Flash',
|
|
description: 'Latest Gemini Flash model via Antigravity',
|
|
nativeImageInput: true,
|
|
thinking: { type: 'levels', levels: ['low', 'high'], dynamicAllowed: true },
|
|
extendedContext: true,
|
|
},
|
|
],
|
|
},
|
|
gemini: {
|
|
provider: 'gemini',
|
|
displayName: 'Gemini',
|
|
defaultModel: 'gemini-2.5-pro',
|
|
models: [
|
|
{
|
|
id: 'gemini-3.1-pro-preview',
|
|
name: 'Gemini 3.1 Pro',
|
|
tier: 'pro',
|
|
description: 'Latest Gemini Pro model, requires paid Google account',
|
|
nativeImageInput: true,
|
|
thinking: { type: 'levels', levels: ['low', 'high'], dynamicAllowed: true },
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'gemini-3-flash-preview',
|
|
name: 'Gemini Flash',
|
|
tier: 'pro',
|
|
description: 'Latest Gemini Flash model, requires paid Google account',
|
|
nativeImageInput: true,
|
|
thinking: { type: 'levels', levels: ['low', 'high'], dynamicAllowed: true },
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'gemini-2.5-pro',
|
|
name: 'Gemini 2.5 Pro',
|
|
description: 'Stable, works with free Google account',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 128,
|
|
max: 32768,
|
|
zeroAllowed: false,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
],
|
|
},
|
|
codex: {
|
|
provider: 'codex',
|
|
displayName: 'Copilot Codex',
|
|
defaultModel: 'gpt-5.4',
|
|
models: [
|
|
{
|
|
id: 'gpt-5.6-sol',
|
|
name: 'GPT-5.6 Sol',
|
|
description: 'Latest frontier agentic coding model.',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
codexServiceTiers: ['fast'],
|
|
},
|
|
{
|
|
id: 'gpt-5.6-terra',
|
|
name: 'GPT-5.6 Terra',
|
|
description: 'Balanced agentic coding model for everyday work.',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
codexServiceTiers: ['fast'],
|
|
},
|
|
{
|
|
id: 'gpt-5.6-luna',
|
|
name: 'GPT-5.6 Luna',
|
|
description: 'Fast and affordable agentic coding model.',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
codexServiceTiers: ['fast'],
|
|
},
|
|
{
|
|
id: 'gpt-5.5',
|
|
name: 'GPT-5.5',
|
|
tier: 'pro',
|
|
description:
|
|
'Newest Codex-released GPT-5 family model; falls back to GPT-5.4 on free plans',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
codexServiceTiers: ['fast'],
|
|
},
|
|
{
|
|
id: 'gpt-5.4',
|
|
name: 'GPT-5.4',
|
|
description: 'Recommended Codex default for most coding and agentic tasks',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
codexServiceTiers: ['fast'],
|
|
},
|
|
{
|
|
id: 'gpt-5.4-mini',
|
|
name: 'GPT-5.4 Mini',
|
|
description: 'Fast, lower-cost Codex option for lighter tasks and haiku-tier routing',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high'],
|
|
maxLevel: 'high',
|
|
dynamicAllowed: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'gpt-5.3-codex',
|
|
name: 'GPT-5.3 Codex',
|
|
tier: 'pro',
|
|
description: 'Previous flagship coding model whose capabilities now power GPT-5.4',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'gpt-5.3-codex-spark',
|
|
name: 'GPT-5.3 Codex Spark',
|
|
tier: 'pro',
|
|
description:
|
|
'Research preview model for ChatGPT Pro subscribers, optimized for near-instant coding iteration',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'gpt-5.2',
|
|
name: 'GPT-5.2',
|
|
description: 'Previous general-purpose Codex model',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh'],
|
|
maxLevel: 'xhigh',
|
|
dynamicAllowed: false,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
xai: {
|
|
provider: 'xai',
|
|
displayName: 'xAI (Grok)',
|
|
defaultModel: 'grok-build-0.1',
|
|
models: [
|
|
{
|
|
id: 'grok-build-0.1',
|
|
name: 'Grok Build 0.1',
|
|
description: 'Fast coding model for agentic software engineering workflows',
|
|
},
|
|
{
|
|
id: 'grok-4.5',
|
|
name: 'Grok 4.5',
|
|
description: 'Frontier model for coding, engineering, and agentic workflows',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high'],
|
|
zeroAllowed: false,
|
|
},
|
|
},
|
|
{
|
|
id: 'grok-4.3',
|
|
name: 'Grok 4.3',
|
|
description: 'General-purpose Grok model with a one-million-token context window',
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['none', 'low', 'medium', 'high'],
|
|
zeroAllowed: true,
|
|
},
|
|
},
|
|
{
|
|
id: 'grok-4.20-0309-reasoning',
|
|
name: 'Grok 4.20 0309 Reasoning',
|
|
description: 'Reasoning model with a two-million-token context window',
|
|
},
|
|
{
|
|
id: 'grok-4.20-0309-non-reasoning',
|
|
name: 'Grok 4.20 0309 Non Reasoning',
|
|
description: 'Non-reasoning model with a two-million-token context window',
|
|
},
|
|
{
|
|
id: 'grok-4.20-multi-agent-0309',
|
|
name: 'Grok 4.20 Multi Agent 0309',
|
|
description: 'Multi-agent model with a two-million-token context window',
|
|
thinking: { type: 'levels', levels: ['low', 'medium', 'high'] },
|
|
},
|
|
{
|
|
id: 'grok-3-mini',
|
|
name: 'Grok 3 Mini',
|
|
description: 'Compact reasoning model',
|
|
thinking: { type: 'levels', levels: ['low', 'medium', 'high'] },
|
|
},
|
|
{
|
|
id: 'grok-3-mini-fast',
|
|
name: 'Grok 3 Mini Fast',
|
|
description: 'Faster compact reasoning model',
|
|
thinking: { type: 'levels', levels: ['low', 'medium', 'high'] },
|
|
},
|
|
{
|
|
id: 'grok-composer-2.5-fast',
|
|
name: 'Grok Composer 2.5 Fast',
|
|
description: 'Fast Composer model for lightweight coding tasks',
|
|
},
|
|
],
|
|
},
|
|
qoder: {
|
|
provider: 'qoder',
|
|
displayName: 'Qoder',
|
|
defaultModel: 'qoder/auto',
|
|
models: [
|
|
{
|
|
id: 'qoder/auto',
|
|
name: 'Qoder Auto',
|
|
description: 'Auto selects the best Qoder model for your prompt',
|
|
},
|
|
{
|
|
id: 'qoder/ultimate',
|
|
name: 'Qoder Ultimate',
|
|
description: 'Highest quality Qoder tier',
|
|
},
|
|
{
|
|
id: 'qoder/performance',
|
|
name: 'Qoder Performance',
|
|
description: 'Balanced quality and speed',
|
|
},
|
|
{
|
|
id: 'qoder/efficient',
|
|
name: 'Qoder Efficient',
|
|
description: 'Cost-efficient Qoder tier',
|
|
},
|
|
{
|
|
id: 'qoder/lite',
|
|
name: 'Qoder Lite',
|
|
description: 'Fastest and most affordable Qoder tier',
|
|
},
|
|
{
|
|
id: 'qoder/qmodel',
|
|
name: 'Qwen 3.6 Plus (via Qoder)',
|
|
description: 'Qwen 3.6 Plus frontier model',
|
|
},
|
|
{
|
|
id: 'qoder/dmodel',
|
|
name: 'DeepSeek V4 Pro (via Qoder)',
|
|
description: 'DeepSeek V4 Pro frontier model',
|
|
},
|
|
{
|
|
id: 'qoder/dfmodel',
|
|
name: 'DeepSeek V4 Flash (via Qoder)',
|
|
description: 'DeepSeek V4 Flash frontier model',
|
|
},
|
|
{
|
|
id: 'qoder/gm51model',
|
|
name: 'GLM 5.1 (via Qoder)',
|
|
description: 'GLM 5.1 frontier model',
|
|
},
|
|
{
|
|
id: 'qoder/kmodel',
|
|
name: 'Kimi K2.6 (via Qoder)',
|
|
description: 'Kimi K2.6 frontier model',
|
|
},
|
|
{
|
|
id: 'qoder/mmodel',
|
|
name: 'MiniMax M2.7 (via Qoder)',
|
|
description: 'MiniMax M2.7 frontier model',
|
|
},
|
|
],
|
|
},
|
|
kimi: {
|
|
provider: 'kimi',
|
|
displayName: 'Kimi (Moonshot)',
|
|
defaultModel: 'kimi-k2.5',
|
|
models: [
|
|
{
|
|
id: 'kimi-k2.5',
|
|
name: 'Kimi K2.5',
|
|
description: 'Latest multimodal model (262K context)',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 32000,
|
|
zeroAllowed: true,
|
|
dynamicAllowed: true,
|
|
},
|
|
},
|
|
{
|
|
id: 'kimi-k2-thinking',
|
|
name: 'Kimi K2 Thinking',
|
|
description: 'Extended reasoning model',
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 32000,
|
|
zeroAllowed: true,
|
|
dynamicAllowed: true,
|
|
},
|
|
},
|
|
{
|
|
id: 'kimi-k2',
|
|
name: 'Kimi K2',
|
|
description: 'Flagship coding model',
|
|
},
|
|
],
|
|
},
|
|
claude: {
|
|
provider: 'claude',
|
|
displayName: 'Claude (Anthropic)',
|
|
defaultModel: 'claude-sonnet-5',
|
|
models: [
|
|
{
|
|
id: 'claude-sonnet-5',
|
|
name: 'Claude Sonnet 5',
|
|
description: 'Latest Sonnet model',
|
|
nativeImageInput: true,
|
|
// Sonnet 5 uses adaptive thinking; manual budget_tokens is rejected with 400.
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
maxLevel: 'max',
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-fable-5',
|
|
name: 'Claude Fable 5',
|
|
description: 'Most powerful model',
|
|
nativeImageInput: true,
|
|
// New tier above Opus. Same adaptive-thinking surface as Opus 4.8:
|
|
// Anthropic accepts only effort levels; manual budget_tokens is rejected with 400.
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
maxLevel: 'max',
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-opus-5',
|
|
name: 'Claude Opus 5',
|
|
description: 'Latest premium model',
|
|
nativeImageInput: true,
|
|
// Opus 5 (released 2026-07-14) uses adaptive thinking, matching Sonnet 5
|
|
// and Opus 4.8: Anthropic accepts only effort levels; manual budget_tokens
|
|
// is rejected with 400. Proxy metadata reports zero + dynamic allowed.
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
maxLevel: 'max',
|
|
zeroAllowed: true,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-opus-4-8',
|
|
name: 'Claude Opus 4.8',
|
|
description: 'Latest flagship model',
|
|
nativeImageInput: true,
|
|
// Mirrors 4.7: Anthropic accepts only adaptive thinking levels on the
|
|
// current Opus generation; manual budget_tokens is rejected with 400.
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
maxLevel: 'max',
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-opus-4-7',
|
|
name: 'Claude Opus 4.7',
|
|
description: 'Previous flagship model',
|
|
nativeImageInput: true,
|
|
// Opus 4.7 only supports adaptive thinking on the Anthropic API; manual
|
|
// thinking.type: "enabled" with budget_tokens is rejected with 400.
|
|
// Expose effort levels; the proxy translates these into adaptive effort.
|
|
// `max` is a distinct adaptive effort above `xhigh` exposed by Anthropic.
|
|
thinking: {
|
|
type: 'levels',
|
|
levels: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
maxLevel: 'max',
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-opus-4-6',
|
|
name: 'Claude Opus 4.6',
|
|
description: 'Older flagship model',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 128000,
|
|
zeroAllowed: false,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-sonnet-4-6',
|
|
name: 'Claude Sonnet 4.6',
|
|
description: 'Balanced performance and speed',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 128000,
|
|
zeroAllowed: false,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-opus-4-5-20251101',
|
|
name: 'Claude Opus 4.5',
|
|
description: 'Most capable Claude model',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 128000,
|
|
zeroAllowed: false,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-sonnet-4-5-20250929',
|
|
name: 'Claude Sonnet 4.5',
|
|
description: 'Balanced performance and speed',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 128000,
|
|
zeroAllowed: false,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-sonnet-4-20250514',
|
|
name: 'Claude Sonnet 4',
|
|
description: 'Previous generation Sonnet',
|
|
nativeImageInput: true,
|
|
thinking: {
|
|
type: 'budget',
|
|
min: 1024,
|
|
max: 128000,
|
|
zeroAllowed: false,
|
|
dynamicAllowed: true,
|
|
},
|
|
extendedContext: true,
|
|
},
|
|
{
|
|
id: 'claude-haiku-4-5-20251001',
|
|
name: 'Claude Haiku 4.5',
|
|
description: 'Fast and efficient',
|
|
nativeImageInput: true,
|
|
thinking: { type: 'none' },
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Check if provider supports interactive model configuration
|
|
*/
|
|
export function supportsModelConfig(provider: CLIProxyProvider): boolean {
|
|
return provider in MODEL_CATALOG;
|
|
}
|
|
|
|
/**
|
|
* Get catalog for provider
|
|
*/
|
|
export function getProviderCatalog(provider: CLIProxyProvider): ProviderCatalog | undefined {
|
|
return MODEL_CATALOG[provider];
|
|
}
|
|
|
|
/**
|
|
* Suggest a supported replacement model from the provider catalog.
|
|
* Prefers the provider default unless it matches the excluded model or is itself broken.
|
|
*/
|
|
export function getSuggestedReplacementModel(
|
|
provider: CLIProxyProvider,
|
|
excludedModelId?: string
|
|
): string | undefined {
|
|
const catalog = MODEL_CATALOG[provider];
|
|
if (!catalog) return undefined;
|
|
|
|
const excludedId = excludedModelId ? findModel(provider, excludedModelId)?.id : undefined;
|
|
const defaultModel = findModel(provider, catalog.defaultModel);
|
|
if (defaultModel && !defaultModel.broken && defaultModel.id !== excludedId) {
|
|
return defaultModel.id;
|
|
}
|
|
|
|
return catalog.models.find((model) => !model.broken && model.id !== excludedId)?.id;
|
|
}
|
|
|
|
/**
|
|
* Find model entry by ID
|
|
* Note: Model IDs are normalized to lowercase for case-insensitive comparison
|
|
*/
|
|
export function findModel(provider: CLIProxyProvider, modelId: string): ModelEntry | undefined {
|
|
const catalog = MODEL_CATALOG[provider];
|
|
if (!catalog || !modelId) return undefined;
|
|
const normalizedId = stripModelConfigurationSuffixes(modelId).trim().toLowerCase();
|
|
const providerNormalizedId = normalizeModelIdForProvider(normalizedId, provider)
|
|
.trim()
|
|
.toLowerCase();
|
|
const lookupCandidates = new Set([normalizedId, providerNormalizedId]);
|
|
if (isAntigravityProvider(provider)) {
|
|
const migratedRaw = migrateDeniedAntigravityModelAliases(normalizedId).trim().toLowerCase();
|
|
const migratedProvider = migrateDeniedAntigravityModelAliases(providerNormalizedId)
|
|
.trim()
|
|
.toLowerCase();
|
|
lookupCandidates.add(migratedRaw);
|
|
lookupCandidates.add(migratedProvider);
|
|
}
|
|
|
|
for (const candidate of [...lookupCandidates]) {
|
|
const compatibilityId =
|
|
GEMINI_MINOR_VERSION_COMPATIBILITY_IDS[
|
|
candidate as keyof typeof GEMINI_MINOR_VERSION_COMPATIBILITY_IDS
|
|
];
|
|
if (compatibilityId) {
|
|
lookupCandidates.add(compatibilityId);
|
|
}
|
|
|
|
if (isAntigravityProvider(provider)) {
|
|
const agyCompatibilityId =
|
|
AGY_GEMINI_PRO_COMPATIBILITY_IDS[
|
|
candidate as keyof typeof AGY_GEMINI_PRO_COMPATIBILITY_IDS
|
|
];
|
|
if (agyCompatibilityId) {
|
|
lookupCandidates.add(agyCompatibilityId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return catalog.models.find((m) => lookupCandidates.has(m.id.toLowerCase()));
|
|
}
|
|
|
|
/**
|
|
* Check if model has known issues
|
|
*/
|
|
export function isModelBroken(provider: CLIProxyProvider, modelId: string): boolean {
|
|
const model = findModel(provider, modelId);
|
|
return model?.broken === true;
|
|
}
|
|
|
|
/**
|
|
* Get issue URL for broken model
|
|
*/
|
|
export function getModelIssueUrl(provider: CLIProxyProvider, modelId: string): string | undefined {
|
|
const model = findModel(provider, modelId);
|
|
return model?.issueUrl;
|
|
}
|
|
|
|
/**
|
|
* Check if model is deprecated
|
|
*/
|
|
export function isModelDeprecated(provider: CLIProxyProvider, modelId: string): boolean {
|
|
const model = findModel(provider, modelId);
|
|
return model?.deprecated === true;
|
|
}
|
|
|
|
/**
|
|
* Get deprecation reason for deprecated model
|
|
*/
|
|
export function getModelDeprecationReason(
|
|
provider: CLIProxyProvider,
|
|
modelId: string
|
|
): string | undefined {
|
|
const model = findModel(provider, modelId);
|
|
return model?.deprecationReason;
|
|
}
|
|
|
|
/**
|
|
* Get thinking support configuration for a model
|
|
*/
|
|
export function getModelThinkingSupport(
|
|
provider: CLIProxyProvider,
|
|
modelId: string
|
|
): ThinkingSupport | undefined {
|
|
const model = findModel(provider, modelId);
|
|
return model?.thinking;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum reasoning effort level for a model.
|
|
* Returns undefined if model has no cap or is not in catalog.
|
|
*/
|
|
export function getModelMaxLevel(
|
|
provider: CLIProxyProvider,
|
|
modelId: string
|
|
): ThinkingSupport['maxLevel'] | undefined {
|
|
const thinking = getModelThinkingSupport(provider, modelId);
|
|
return thinking?.maxLevel;
|
|
}
|
|
|
|
/**
|
|
* Check if model supports thinking/reasoning
|
|
*/
|
|
export function supportsThinking(provider: CLIProxyProvider, modelId: string): boolean {
|
|
const thinking = getModelThinkingSupport(provider, modelId);
|
|
return thinking !== undefined && thinking.type !== 'none';
|
|
}
|
|
|
|
/**
|
|
* Check if model supports extended context (1M tokens).
|
|
* Returns true if model has extendedContext: true in catalog.
|
|
*/
|
|
export function supportsExtendedContext(provider: CLIProxyProvider, modelId: string): boolean {
|
|
if (provider === 'xai') return false;
|
|
const model = findModel(provider, modelId);
|
|
return model?.extendedContext === true;
|
|
}
|
|
|
|
/**
|
|
* Check if a model can read image inputs natively.
|
|
*/
|
|
export function supportsNativeImageInput(provider: CLIProxyProvider, modelId: string): boolean {
|
|
const model = findModel(provider, modelId);
|
|
return model?.nativeImageInput === true;
|
|
}
|
|
|
|
/**
|
|
* Check if model is a native Gemini model (not Claude via Antigravity).
|
|
* Native Gemini models get extended context auto-enabled.
|
|
*/
|
|
export function isNativeGeminiModel(modelId: string): boolean {
|
|
const lower = modelId.toLowerCase();
|
|
return lower.startsWith('gemini-');
|
|
}
|