fix: resolve model config provider via ai_provider_id (#26660)

-  `site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts`):
`providerFormValuesToCreate` returns `type: "bedrock"` when the form
type is `bedrock`, instead of `type: "anthropic"` with Bedrock settings.
- Updated `providerFormApiMap.test.ts` to expect `type:
"bedrock"` and `MockAIProviderBedrock` fixture in `entities.ts` to
`type: "bedrock"`. Added Bedrock fixtures to `ModelsPageView` stories
asserting the AWS Bedrock icon renders correctly.

Refs: [CODAGT-549](https://linear.app/codercom/issue/CODAGT-549/bedrock-models-use-anthropic-styling-and-icons)

> 🤖
This commit is contained in:
Cian Johnston
2026-06-29 11:04:58 +01:00
committed by GitHub
parent 72980d3e65
commit 86a7bc9fb0
5 changed files with 57 additions and 15 deletions
@@ -5,7 +5,9 @@ import type { ChatModelConfig } from "#/api/typesGenerated";
import ModelsPageView from "./ModelsPageView";
import {
MockAnthropicProviderState,
MockBedrockProviderState,
MockOpenAIProviderState,
mockBedrockClaude,
mockClaude,
mockDisabledModel,
mockGPT5,
@@ -17,8 +19,12 @@ const meta: Meta<typeof ModelsPageView> = {
args: {
isLoading: false,
error: null,
models: [mockGPT5, mockClaude, mockDisabledModel],
providerStates: [MockOpenAIProviderState, MockAnthropicProviderState],
models: [mockGPT5, mockClaude, mockDisabledModel, mockBedrockClaude],
providerStates: [
MockOpenAIProviderState,
MockAnthropicProviderState,
MockBedrockProviderState,
],
},
parameters: {
reactRouter: reactRouterParameters({
@@ -45,6 +51,12 @@ export const Default: Story = {
await expect(canvas.getByText("Claude Sonnet 4.5")).toBeInTheDocument();
await expect(canvas.getAllByText("OpenAI").length).toBeGreaterThan(0);
await expect(canvas.getByText("Anthropic")).toBeInTheDocument();
await expect(
canvas.getByText("Claude Sonnet 4.5 (Bedrock)"),
).toBeInTheDocument();
await expect(canvas.getByText("AWS Bedrock")).toBeInTheDocument();
// The Bedrock model config should render the Bedrock provider icon.
await expect(canvas.getByAltText("AWS Bedrock")).toBeInTheDocument();
await expect(canvas.getAllByText("Enabled").length).toBeGreaterThan(0);
await expect(canvas.getByText("Default")).toBeInTheDocument();
await expect(canvas.getByText("Disabled")).toBeInTheDocument();
@@ -83,6 +83,31 @@ export const MockAnthropicProviderState: ProviderState = {
modelConfigs: [mockClaude],
};
const MockBedrockProviderConfig: ChatProviderConfig = {
...MockOpenAIProviderConfig,
id: "prov-bedrock",
provider: "bedrock",
display_name: "AWS Bedrock",
};
export const mockBedrockClaude: ChatModelConfig = {
...mockClaude,
id: "model-bedrock-claude",
provider: "bedrock",
ai_provider_id: "prov-bedrock",
model: "anthropic.claude-sonnet-4-5",
display_name: "Claude Sonnet 4.5 (Bedrock)",
};
export const MockBedrockProviderState: ProviderState = {
...MockOpenAIProviderState,
key: "prov-bedrock",
provider: "bedrock",
label: "AWS Bedrock",
providerConfig: MockBedrockProviderConfig,
modelConfigs: [mockBedrockClaude],
};
export const MockCopilotProviderState: ProviderState = {
...MockOpenAIProviderState,
key: "prov-copilot",
@@ -139,8 +139,12 @@ describe("parseBedrockRegionFromBaseUrl", () => {
});
describe("isBedrockProvider", () => {
it("recognises a discriminated bedrock provider", () => {
expect(isBedrockProvider(MockAIProviderBedrock)).toBe(true);
it("recognises a legacy bedrock provider stored as type=anthropic", () => {
const provider: AIProvider = {
...MockAIProviderBedrock,
type: "anthropic",
};
expect(isBedrockProvider(provider)).toBe(true);
});
it("recognises a provider with explicit bedrock type", () => {
@@ -332,9 +336,9 @@ describe("providerFormValuesToCreate", () => {
});
describe("Bedrock", () => {
it('maps Bedrock to a wire `type:"anthropic"`', () => {
it('maps Bedrock to a wire `type:"bedrock"`', () => {
const req = providerFormValuesToCreate(baseBedrockFormValues);
expect(req.type).toBe("anthropic");
expect(req.type).toBe("bedrock");
});
it("derives the region from a canonical AWS URL", () => {
@@ -669,10 +673,10 @@ describe("aiProviderToFormValues", () => {
expect(values.smallFastModel).toBe("anthropic.claude-haiku-4-5");
});
it("seeds Bedrock form values from an explicit Bedrock provider type", () => {
it("seeds Bedrock form values from a legacy anthropic-typed provider", () => {
const provider: AIProvider = {
...MockAIProviderBedrock,
type: "bedrock",
type: "anthropic",
};
const values = aiProviderToFormValues(provider);
expect(values.type).toBe("bedrock");
@@ -723,12 +727,12 @@ describe("aiProviderToFormValues", () => {
it("handles a Bedrock provider whose settings are null", () => {
// `isBedrockProvider` will return false, so the provider falls
// through to the anthropic branch. The helper must not throw.
// through to the generic branch. The helper must not throw.
const provider: AIProvider = {
...MockAIProviderBedrock,
settings: null as unknown as AIProvider["settings"],
};
const values = aiProviderToFormValues(provider);
expect(values.type).toBe("anthropic");
expect(values.type).toBe("bedrock");
});
});
@@ -146,7 +146,7 @@ export const providerFormValuesToCreate = (
values.roleArn.trim(),
);
return {
type: "anthropic",
type: "bedrock",
...base,
settings: settings as AIProviderSettings,
};
+5 -4
View File
@@ -5574,13 +5574,14 @@ export const MockAIProviderAnthropic: TypesGen.AIProvider = {
};
/**
* Bedrock providers come over the wire with `type: "anthropic"` and a
* `settings._type: "bedrock"` discriminator. `isBedrockProvider` and the
* backend (see `coderd/ai_providers.go`) enforce this convention.
* Bedrock providers come over the wire with `type: "bedrock"` and a
* `settings._type: "bedrock"` discriminator. `isBedrockProvider` checks
* both the type and the settings discriminator, and also accepts legacy
* providers stored as `type: "anthropic"` with Bedrock settings.
*/
export const MockAIProviderBedrock: TypesGen.AIProvider = {
id: "9c2e3b41-2e9f-4c97-9a4f-2e1a3d8f9f21",
type: "anthropic",
type: "bedrock",
name: "bedrock",
display_name: "Bedrock",
base_url: "https://bedrock-runtime.us-east-2.amazonaws.com",