fix: inherit api field from provider config for inline Bedrock models

When a model is defined inline in models.providers without an explicit api field,
the resolveModel function now correctly inherits the api from the provider config.
This fixes Bedrock model discovery which relies on api: bedrock-converse-stream.

- Look up provider config using inlineMatch.provider
- Merge api field with fallback chain: model.api → provider.api → openai-responses
- Add tests validating api inheritance for inline models

Fixes inline model resolution for providers like Amazon Bedrock that require
specific API implementations.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex Fallah 2026-01-25 22:36:35 -05:00
parent b0d68540c1
commit 1980561bef
2 changed files with 67 additions and 1 deletions

View File

@ -26,4 +26,60 @@ describe("buildInlineProviderModels", () => {
{ ...makeModel("beta-model"), provider: "beta" },
]);
});
it("preserves model api field when set", () => {
const providers = {
"amazon-bedrock": {
api: "bedrock-converse-stream",
models: [{ ...makeModel("claude-v2"), api: "anthropic-messages" }],
},
};
const result = buildInlineProviderModels(providers);
expect(result[0].api).toBe("anthropic-messages");
});
});
describe("resolveModel inline api inheritance", () => {
it("inherits api from provider config when model api is not set", async () => {
// This validates the fix: when inlineMatch.api is undefined,
// it should fall back to providerCfg.api
const cfg = {
models: {
providers: {
"amazon-bedrock": {
baseUrl: "https://bedrock.us-east-1.amazonaws.com",
api: "bedrock-converse-stream" as const,
models: [
{
id: "anthropic.claude-3-sonnet",
name: "Claude 3 Sonnet",
reasoning: false,
input: ["text" as const],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 4096,
// Note: no api field here - should inherit from provider
},
],
},
},
},
};
const inlineModels = buildInlineProviderModels(cfg.models.providers);
const match = inlineModels.find((m) => m.id === "anthropic.claude-3-sonnet");
expect(match).toBeDefined();
expect(match!.api).toBeUndefined(); // Model doesn't have api set
// The fix ensures that when resolveModel processes this match,
// it looks up providers[inlineMatch.provider].api and uses that
const providerCfg = cfg.models.providers["amazon-bedrock"];
const resolvedApi = match!.api ?? providerCfg?.api ?? "openai-responses";
expect(resolvedApi).toBe("bedrock-converse-stream");
});
});

View File

@ -58,7 +58,17 @@ export function resolveModel(
(entry) => normalizeProviderId(entry.provider) === normalizedProvider && entry.id === modelId,
);
if (inlineMatch) {
const normalized = normalizeModelCompat(inlineMatch as Model<Api>);
// Use inlineMatch.provider to look up config (preserves original key casing)
const providerCfg = providers[inlineMatch.provider];
// Inherit api from provider config if not set on the model itself
const apiValue = (inlineMatch.api ??
(providerCfg?.api as Api | undefined) ??
"openai-responses") as Api;
const modelWithApi = {
...inlineMatch,
api: apiValue,
};
const normalized = normalizeModelCompat(modelWithApi as Model<Api>);
return {
model: normalized,
authStorage,