fix(coding-agent): allow model-level baseUrl override in models.json closes #1777

This commit is contained in:
Mario Zechner 2026-03-03 15:46:58 +01:00
parent 7bd4c45d81
commit 1912f0336b
2 changed files with 42 additions and 3 deletions

View file

@ -70,6 +70,7 @@ const ModelDefinitionSchema = Type.Object({
id: Type.String({ minLength: 1 }),
name: Type.Optional(Type.String({ minLength: 1 })),
api: Type.Optional(Type.String({ minLength: 1 })),
baseUrl: Type.Optional(Type.String({ minLength: 1 })),
reasoning: Type.Optional(Type.Boolean()),
input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
cost: Type.Optional(
@ -469,15 +470,15 @@ export class ModelRegistry {
}
}
// baseUrl is validated to exist for providers with models
// Apply defaults for optional fields
// Provider baseUrl is required when custom models are defined.
// Individual models can override it with modelDef.baseUrl.
const defaultCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
models.push({
id: modelDef.id,
name: modelDef.name ?? modelDef.id,
api: api as Api,
provider: providerName,
baseUrl: providerConfig.baseUrl!,
baseUrl: modelDef.baseUrl ?? providerConfig.baseUrl!,
reasoning: modelDef.reasoning ?? false,
input: (modelDef.input ?? ["text"]) as ("text" | "image")[],
cost: modelDef.cost ?? defaultCost,
@ -682,6 +683,7 @@ export interface ProviderConfigInput {
id: string;
name: string;
api?: Api;
baseUrl?: string;
reasoning: boolean;
input: ("text" | "image")[];
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };

View file

@ -237,6 +237,43 @@ describe("ModelRegistry", () => {
}
});
test("model-level baseUrl overrides provider-level baseUrl for custom models", () => {
writeRawModelsJson({
"opencode-go": {
baseUrl: "https://opencode.ai/zen/go/v1",
apiKey: "TEST_KEY",
models: [
{
id: "minimax-m2.5",
api: "anthropic-messages",
baseUrl: "https://opencode.ai/zen/go",
reasoning: true,
input: ["text"],
cost: { input: 0.3, output: 1.2, cacheRead: 0.03, cacheWrite: 0 },
contextWindow: 204800,
maxTokens: 131072,
},
{
id: "glm-5",
api: "openai-completions",
reasoning: true,
input: ["text"],
cost: { input: 1, output: 3.2, cacheRead: 0.2, cacheWrite: 0 },
contextWindow: 204800,
maxTokens: 131072,
},
],
},
});
const registry = new ModelRegistry(authStorage, modelsJsonPath);
const m25 = registry.find("opencode-go", "minimax-m2.5");
const glm5 = registry.find("opencode-go", "glm-5");
expect(m25?.baseUrl).toBe("https://opencode.ai/zen/go");
expect(glm5?.baseUrl).toBe("https://opencode.ai/zen/go/v1");
});
test("modelOverrides still apply when provider also defines models", () => {
writeRawModelsJson({
openrouter: {