-
Notifications
You must be signed in to change notification settings - Fork 406
Open
Description
Problem
Currently, models.json has two modes for providers:
- Override-only: Set
baseUrl/apiKey/headerswithoutmodelsarray → keeps all built-in models - Full replacement: Define
modelsarray → replaces ALL models for that provider
This makes it impossible to customize a single model (e.g., set OpenRouter routing for one model) without replacing the entire provider's model list.
Use case: I want to route anthropic/claude-sonnet-4 through Bedrock on OpenRouter while keeping all other OpenRouter models unchanged:
{
"providers": {
"openrouter": {
"models": [...] // Would need to copy ALL openrouter models just to change one
}
}
}Proposal
Add modelOverrides field that merges with built-in models:
{
"providers": {
"openrouter": {
"modelOverrides": {
"anthropic/claude-sonnet-4": {
"compat": { "openRouterRouting": { "only": ["amazon-bedrock"] } }
},
"anthropic/claude-opus-4": {
"compat": { "openRouterRouting": { "order": ["anthropic", "amazon-bedrock"] } }
}
}
}
}
}Override fields would be deep-merged with the built-in model definition.
Implementation Sketch
1. Schema changes (model-registry.ts)
// Add override schema (subset of ModelDefinitionSchema, all optional)
const ModelOverrideSchema = Type.Object({
name: Type.Optional(Type.String()),
reasoning: Type.Optional(Type.Boolean()),
cost: Type.Optional(Type.Partial(/* cost fields */)),
contextWindow: Type.Optional(Type.Number()),
maxTokens: Type.Optional(Type.Number()),
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
compat: Type.Optional(OpenAICompatSchema),
});
const ProviderConfigSchema = Type.Object({
baseUrl: Type.Optional(Type.String()),
apiKey: Type.Optional(Type.String()),
api: Type.Optional(Type.String()),
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
authHeader: Type.Optional(Type.Boolean()),
models: Type.Optional(Type.Array(ModelDefinitionSchema)),
modelOverrides: Type.Optional(Type.Record(Type.String(), ModelOverrideSchema)), // NEW
});2. Loading logic (loadCustomModels)
// In CustomModelsResult, add:
modelOverrides: Map<string, Map<string, ModelOverride>>; // provider -> modelId -> override
// When parsing config:
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
if (providerConfig.models?.length) {
replacedProviders.add(providerName);
} else {
overrides.set(providerName, { baseUrl, headers, apiKey });
if (providerConfig.modelOverrides) {
modelOverrides.set(providerName, new Map(Object.entries(providerConfig.modelOverrides)));
}
}
}3. Apply overrides (loadBuiltInModels)
private loadBuiltInModels(
replacedProviders: Set<string>,
overrides: Map<string, ProviderOverride>,
modelOverrides: Map<string, Map<string, ModelOverride>> // NEW
): Model<Api>[] {
return getProviders()
.filter((p) => !replacedProviders.has(p))
.flatMap((provider) => {
const models = getModels(provider as KnownProvider);
const override = overrides.get(provider);
const perModelOverrides = modelOverrides.get(provider);
return models.map((m) => {
let model = override ? applyProviderOverride(m, override) : m;
const modelOverride = perModelOverrides?.get(m.id);
if (modelOverride) {
model = deepMerge(model, modelOverride); // Deep merge compat, headers, etc.
}
return model;
});
});
}I'm willing to implement this if the proposal is accepted.
Metadata
Metadata
Assignees
Labels
No labels