fix: use live copilot model availability

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-22 11:07:43 +01:00
co-authored by Copilot
parent 9799a959d6
commit 6e623b7bd6
16 changed files with 691 additions and 267 deletions
+249 -19
View File
@@ -1,37 +1,181 @@
import type { SidecarModelCapability } from '@shared/contracts/sidecar';
import type { PatternDefinition, ReasoningEffort } from '@shared/domain/pattern';
export type ModelProvider = 'openai' | 'anthropic' | 'google';
export interface ModelDefinition {
id: string;
name: string;
provider: ModelProvider;
tier: 'premium' | 'standard' | 'fast';
provider?: ModelProvider;
tier?: 'premium' | 'standard' | 'fast';
supportedReasoningEfforts?: ReadonlyArray<ReasoningEffort>;
defaultReasoningEffort?: ReasoningEffort;
}
export const modelCatalog: ModelDefinition[] = [
{ id: 'gpt-5.4', name: 'GPT-5.4', provider: 'openai', tier: 'standard' },
{ id: 'gpt-5.4-mini', name: 'GPT-5.4 Mini', provider: 'openai', tier: 'fast' },
{ id: 'gpt-5.3-codex', name: 'GPT-5.3 Codex', provider: 'openai', tier: 'standard' },
{ id: 'gpt-5.2-codex', name: 'GPT-5.2 Codex', provider: 'openai', tier: 'standard' },
{ id: 'gpt-5.2', name: 'GPT-5.2', provider: 'openai', tier: 'standard' },
{ id: 'gpt-5.1', name: 'GPT-5.1', provider: 'openai', tier: 'standard' },
{ id: 'gpt-4.1', name: 'GPT-4.1', provider: 'openai', tier: 'fast' },
const allReasoningEfforts: ReadonlyArray<ReasoningEffort> = ['low', 'medium', 'high', 'xhigh'];
{ id: 'claude-opus-4.5', name: 'Claude Opus 4.5', provider: 'anthropic', tier: 'premium' },
{ id: 'claude-sonnet-4.5', name: 'Claude Sonnet 4.5', provider: 'anthropic', tier: 'standard' },
{ id: 'claude-sonnet-4', name: 'Claude Sonnet 4', provider: 'anthropic', tier: 'standard' },
{ id: 'claude-haiku-4.5', name: 'Claude Haiku 4.5', provider: 'anthropic', tier: 'fast' },
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro', provider: 'google', tier: 'standard' },
export const modelCatalog: ReadonlyArray<ModelDefinition> = [
{
id: 'gpt-5.4',
name: 'GPT-5.4',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: allReasoningEfforts,
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.4-mini',
name: 'GPT-5.4 mini',
provider: 'openai',
tier: 'fast',
supportedReasoningEfforts: allReasoningEfforts,
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.3-codex',
name: 'GPT-5.3 Codex',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: allReasoningEfforts,
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.2-codex',
name: 'GPT-5.2 Codex',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: allReasoningEfforts,
defaultReasoningEffort: 'high',
},
{
id: 'gpt-5.2',
name: 'GPT-5.2',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.1',
name: 'GPT-5.1',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.1-codex',
name: 'GPT-5.1-Codex',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.1-codex-max',
name: 'GPT-5.1-Codex-Max',
provider: 'openai',
tier: 'standard',
supportedReasoningEfforts: allReasoningEfforts,
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5.1-codex-mini',
name: 'GPT-5.1-Codex-Mini',
provider: 'openai',
tier: 'fast',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-5-mini',
name: 'GPT-5 mini',
provider: 'openai',
tier: 'fast',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'medium',
},
{
id: 'gpt-4.1',
name: 'GPT-4.1',
provider: 'openai',
tier: 'fast',
supportedReasoningEfforts: [],
},
{
id: 'claude-opus-4.6',
name: 'Claude Opus 4.6',
provider: 'anthropic',
tier: 'premium',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'high',
},
{
id: 'claude-opus-4.6-1m',
name: 'Claude Opus 4.6 (1M context)(Internal only)',
provider: 'anthropic',
tier: 'premium',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'high',
},
{
id: 'claude-opus-4.5',
name: 'Claude Opus 4.5',
provider: 'anthropic',
tier: 'premium',
supportedReasoningEfforts: [],
},
{
id: 'claude-sonnet-4.6',
name: 'Claude Sonnet 4.6',
provider: 'anthropic',
tier: 'standard',
supportedReasoningEfforts: ['low', 'medium', 'high'],
defaultReasoningEffort: 'medium',
},
{
id: 'claude-sonnet-4.5',
name: 'Claude Sonnet 4.5',
provider: 'anthropic',
tier: 'standard',
supportedReasoningEfforts: [],
},
{
id: 'claude-sonnet-4',
name: 'Claude Sonnet 4',
provider: 'anthropic',
tier: 'standard',
supportedReasoningEfforts: [],
},
{
id: 'claude-haiku-4.5',
name: 'Claude Haiku 4.5',
provider: 'anthropic',
tier: 'fast',
supportedReasoningEfforts: [],
},
{
id: 'gemini-3-pro-preview',
name: 'Gemini 3 Pro',
provider: 'google',
tier: 'standard',
supportedReasoningEfforts: [],
},
];
export const providerMeta: { id: ModelProvider; label: string }[] = [
const fallbackModelMap = new Map(modelCatalog.map((model) => [model.id, model] as const));
export const providerMeta: ReadonlyArray<{ id: ModelProvider; label: string }> = [
{ id: 'openai', label: 'OpenAI' },
{ id: 'anthropic', label: 'Anthropic' },
{ id: 'google', label: 'Google' },
];
export function findModel(id: string): ModelDefinition | undefined {
return modelCatalog.find((m) => m.id === id);
export function findModel(
id: string,
models: ReadonlyArray<ModelDefinition> = modelCatalog,
): ModelDefinition | undefined {
return models.find((model) => model.id === id);
}
export function inferProvider(modelId: string): ModelProvider | undefined {
@@ -40,3 +184,89 @@ export function inferProvider(modelId: string): ModelProvider | undefined {
if (modelId.startsWith('gemini-')) return 'google';
return undefined;
}
export function buildAvailableModelCatalog(
availableModels?: ReadonlyArray<SidecarModelCapability>,
): ReadonlyArray<ModelDefinition> {
if (!availableModels || availableModels.length === 0) {
return modelCatalog;
}
const providerOrder = new Map(providerMeta.map((provider, index) => [provider.id, index] as const));
return [...availableModels]
.map((model) => {
const fallback = fallbackModelMap.get(model.id);
const provider = fallback?.provider ?? inferProvider(model.id);
return {
id: model.id,
name: model.name || fallback?.name || model.id,
provider,
tier: fallback?.tier,
supportedReasoningEfforts: model.supportedReasoningEfforts,
defaultReasoningEffort: model.defaultReasoningEffort,
} satisfies ModelDefinition;
})
.sort((left, right) => {
const leftProviderOrder =
left.provider !== undefined
? (providerOrder.get(left.provider) ?? Number.MAX_SAFE_INTEGER)
: Number.MAX_SAFE_INTEGER;
const rightProviderOrder =
right.provider !== undefined
? (providerOrder.get(right.provider) ?? Number.MAX_SAFE_INTEGER)
: Number.MAX_SAFE_INTEGER;
const providerCompare = leftProviderOrder - rightProviderOrder;
if (providerCompare !== 0) {
return providerCompare;
}
return left.name.localeCompare(right.name);
});
}
export function getSupportedReasoningEfforts(
model: ModelDefinition | undefined,
): ReadonlyArray<ReasoningEffort> | undefined {
return model?.supportedReasoningEfforts;
}
export function resolveReasoningEffort(
model: ModelDefinition | undefined,
requested: ReasoningEffort | undefined,
): ReasoningEffort | undefined {
const supported = model?.supportedReasoningEfforts;
if (!supported) {
return requested;
}
if (supported.length === 0) {
return undefined;
}
if (requested && supported.includes(requested)) {
return requested;
}
if (model?.defaultReasoningEffort && supported.includes(model.defaultReasoningEffort)) {
return model.defaultReasoningEffort;
}
return supported[0];
}
export function normalizePatternModels(
pattern: PatternDefinition,
models: ReadonlyArray<ModelDefinition>,
): PatternDefinition {
return {
...pattern,
agents: pattern.agents.map((agent) => {
const model = findModel(agent.model, models);
const reasoningEffort = resolveReasoningEffort(model, agent.reasoningEffort);
return reasoningEffort === agent.reasoningEffort ? agent : { ...agent, reasoningEffort };
}),
};
}