fix(coding-agent): honor provider compat in models.json closes #2062

This commit is contained in:
Mario Zechner
2026-03-14 04:54:55 +01:00
parent 62064b2b64
commit bdf2419ac3
3 changed files with 155 additions and 10 deletions

View File

@@ -238,6 +238,125 @@ describe("ModelRegistry", () => {
}
});
test("provider-level compat applies to custom models", () => {
writeRawModelsJson({
demo: {
baseUrl: "https://example.com/v1",
apiKey: "DEMO_KEY",
api: "openai-completions",
compat: {
supportsUsageInStreaming: false,
maxTokensField: "max_tokens",
},
models: [
{
id: "demo-model",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000,
maxTokens: 100,
},
],
},
});
const registry = new ModelRegistry(authStorage, modelsJsonPath);
const compat = registry.find("demo", "demo-model")?.compat as OpenAICompletionsCompat | undefined;
expect(compat?.supportsUsageInStreaming).toBe(false);
expect(compat?.maxTokensField).toBe("max_tokens");
});
test("model-level compat overrides provider-level compat for custom models", () => {
writeRawModelsJson({
demo: {
baseUrl: "https://example.com/v1",
apiKey: "DEMO_KEY",
api: "openai-completions",
compat: {
supportsUsageInStreaming: false,
maxTokensField: "max_tokens",
},
models: [
{
id: "demo-model",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000,
maxTokens: 100,
compat: {
supportsUsageInStreaming: true,
maxTokensField: "max_completion_tokens",
},
},
],
},
});
const registry = new ModelRegistry(authStorage, modelsJsonPath);
const compat = registry.find("demo", "demo-model")?.compat as OpenAICompletionsCompat | undefined;
expect(compat?.supportsUsageInStreaming).toBe(true);
expect(compat?.maxTokensField).toBe("max_completion_tokens");
});
test("provider-level compat applies to built-in models", () => {
writeRawModelsJson({
openrouter: {
compat: {
supportsUsageInStreaming: false,
supportsStrictMode: false,
},
},
});
const registry = new ModelRegistry(authStorage, modelsJsonPath);
const models = getModelsForProvider(registry, "openrouter");
expect(models.length).toBeGreaterThan(0);
for (const model of models) {
const compat = model.compat as OpenAICompletionsCompat | undefined;
expect(compat?.supportsUsageInStreaming).toBe(false);
expect(compat?.supportsStrictMode).toBe(false);
}
});
test("compat schema accepts reasoningEffortMap and supportsStrictMode", () => {
writeRawModelsJson({
demo: {
baseUrl: "https://example.com/v1",
apiKey: "DEMO_KEY",
api: "openai-completions",
models: [
{
id: "demo-model",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000,
maxTokens: 100,
compat: {
reasoningEffortMap: {
minimal: "default",
high: "max",
},
supportsStrictMode: false,
},
},
],
},
});
const registry = new ModelRegistry(authStorage, modelsJsonPath);
const compat = registry.find("demo", "demo-model")?.compat as OpenAICompletionsCompat | undefined;
expect(registry.getError()).toBeUndefined();
expect(compat?.reasoningEffortMap).toEqual({ minimal: "default", high: "max" });
expect(compat?.supportsStrictMode).toBe(false);
});
test("model-level baseUrl overrides provider-level baseUrl for custom models", () => {
writeRawModelsJson({
"opencode-go": {