fix: require bedrock model fields for the invoke-model protocol - #27846
Conversation
dff49f5 to
efcdd25
Compare
efcdd25 to
ff26bd1
Compare
|
AIGOV-564 |
|
/coder-agents-review |
|
Chat: Review posted | View chat Review history
deep-review v0.9.0 | Round 1 | Last posted: Round 1, 6 findings (1 P2, 3 P3, 2 Note), COMMENT. Review Finding inventoryFinding inventory, PR #27846Findings
Contested and acknowledgedNone. Round logRound 1Netero-only (first pass, P2 gate). 1 P2, 3 P3, 2 Note. Panel not yet spawned. Orchestrator verification notes:
About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
First-pass review only. These are mechanical findings from a single first-pass reviewer; the full review panel has not reviewed this PR yet and will do so after these are addressed.
The fix itself is well targeted: the validator is scoped to invoke-model so mantle keeps forwarding the client's model, the doc comment on validateAIProviderBedrockModels explains the runtime consequence instead of restating the code, and TestAIProviderRequest_ValidationInSync is the right shape for a cross-layer duplicate, it asserts both validators independently so drift fails the build rather than silently returning. Test density is 84.6% and every branch of the new function is exercised.
1 P2, 3 P3, 2 Notes.
The P2 is the one that matters: AIGOV-564 is fixed for the API but not for CODER_AI_GATEWAY_PROVIDER_N_BEDROCK_*. Env-seeded providers reach the same 404-on-every-route state, and there is no BEDROCK_PROTOCOL env key, so every env-seeded Bedrock provider resolves to invoke-model and therefore always needs both models. The settings struct the check needs is already built in readAIProviders.
One process note: the existing Bedrock settings are only valid for type=anthropic subtest was edited to add model fields so it keeps reaching the type check. That edit is the visible symptom of an error-precedence change, described in the comment on validateAIProviderBedrockModels. Worth deciding deliberately rather than absorbing into a fixture.
Verbatim from the reviewer, on the deleted IsConfigured rationale: "the reason now lives everywhere except the definition it constrains."
cli/server.go:3237
P2 [CRF-1] The env-seeding path still accepts a bedrock provider with no model fields, so the exact 404-on-every-route failure this PR fixes for the API remains reachable through CODER_AI_GATEWAY_PROVIDER_N_BEDROCK_*. (Netero)
readAIProviderspost-parse validation (cli/server.go:3200-3275) checks type/BEDROCK_* consistency, key exclusivity, and credential pairing, but never requiresBEDROCK_MODEL/BEDROCK_SMALL_FAST_MODEL. [...] There is also noBEDROCK_PROTOCOLenv key in the parser's switch (cli/server.go:3344-3388), so every env-seeded Bedrock provider resolves toinvoke-modeland therefore always needs both models.
Result of
CODER_AI_GATEWAY_PROVIDER_0_TYPE=bedrock+CODER_AI_GATEWAY_PROVIDER_0_BEDROCK_REGION=us-east-2and nothing else: startup seeds the row,buildProviderreturns an error fromruntimeCfg.Validate(), the provider never enters the route table, and the only signal is a WARN.
Verified independently: the post-parse loop builds settings via NewAIProviderBedrockSettings and only asks IsBedrockConfigured; the key switch has no BEDROCK_PROTOCOL case; config.AWSBedrock.Validate requires both models for invoke-model.
This is the same root cause as the API bug, in an unpatched sibling. Fixing one entry point and leaving the other means the linked issue can recur through env config with no API-visible error, only a startup WARN. Call the same check on the settings already constructed in readAIProviders so env and API reject the identical shape.
🤖
🤖 This review was automatically generated with Coder Agents.
| // every request to it would 404. The mantle protocol forwards the client's | ||
| // model unchanged and needs neither field. | ||
| func validateAIProviderBedrockModels(b AIProviderBedrockSettings) []ValidationError { | ||
| if b.ResolvedProtocol() != AIProviderBedrockProtocolInvokeModel { |
There was a problem hiding this comment.
P3 [CRF-2] The models gate fires on any non-nil bedrock blob, including one that is not a Bedrock provider at all, so a bearer-token Anthropic create carrying settings.bedrock: {} is now rejected with a Bedrock model error. (Netero)
Every other Bedrock gate in this area first asks whether the blob is a Bedrock provider: create's own
type=bedrock requires bedrock settingsusesIsConfigured(),bedrockConfigreturns nil for an unconfigured blob [...]validateAIProviderBedrockModelsskips that question.
create {type: anthropic, api_keys: [sk-test], settings.bedrock: {}} -> settings.model: model is required for the invoke-model protocol settings.small_fast_model: small_fast_model is required for the invoke-model protocol
The error names fields the caller has no reason to set. There is a second consequence on the PATCH path: req.Validate() runs before the handler's type check, so a PATCH placing bedrock settings on an openai provider without models now returns "model is required for the invoke-model protocol" instead of Bedrock settings are only valid for type=anthropic or type=bedrock. That is what forced the fixture edit at coderd/ai_providers_test.go:588-598; the existing subtest now only reaches the type check because it supplies models.
One correction to the reviewer's proposed fix. IsBedrockConfigured(baseURL, b) is baseURL != "" || b.IsConfigured(), and base URL is required on every provider, so that gate would fire for every request carrying a bedrock blob, which is the current behavior. The runtime's own "is this Bedrock" test is IsConfigured() on the settings blob alone (cli/aibridged.go:341-348, whose comment states base URL cannot serve as a detection signal), and a bedrock-typed provider with IsConfigured() == false is already rejected on create and refused at build time. Gating on IsConfigured() is what makes the API agree with the runtime.
Not reachable from the UI: providerFormApiMap.ts only emits a bedrock blob when type === "bedrock". API clients can hit it.
🤖
| }) | ||
| requireMissingModels(t, err) | ||
|
|
||
| // The same PATCH with both models is accepted, and the stored provider |
There was a problem hiding this comment.
P3 [CRF-3] The comment claims the rejected PATCH left the stored provider untouched, but no assertion can show that; the check passes either way. (Netero)
The rejected PATCH (line 649-656) sets
Region: "us-west-2". The following accepted PATCH setsRegion: "us-west-2"as well, and the assertions run only after it. If a future change let a validation failure partially apply, this test would still pass. Same for the model fields: they are only ever read after the successful PATCH.
A comment asserting coverage that does not exist is worse than no comment: the next reader trusts it and does not add the assertion. Either read the provider back between the rejected and the accepted PATCH and assert Region == "us-east-2" with both models at their created values, or drop the second clause of the comment.
🤖
| // indicating that the operator wants the provider to authenticate via | ||
| // AWS Bedrock rather than as a bearer-token Anthropic provider. | ||
| // | ||
| // Model and SmallFastModel are intentionally excluded: they have |
There was a problem hiding this comment.
P3 [CRF-4] The PR deletes the rationale for why Model and SmallFastModel are excluded from IsConfigured(), and the rationale is still true. (Netero)
Those defaults still exist:
codersdk/deployment.go:1992(global.anthropic.claude-sonnet-4-5-20250929-v1:0) and:2002(global.anthropic.claude-haiku-4-5-20251001-v1:0) [...] Three other sites still repeat the same reason as a secondhand claim:cli/server.go:3404-3407,coderd/ai_providers_migrate.go:219-220, and this file's own surviving "have no defaults" sentence.
the reason now lives everywhere except the definition it constrains, so the next reader who wants a stronger Bedrock detection signal sees only "region and credentials have no defaults" and no statement that adding
Modelwould misfire on every deployment running the defaults.
Confirmed both default declarations are still present at those lines. This PR makes the models required at the API, which is exactly the change that invites a reader to also add them to IsConfigured(), so this is the worst moment to remove the sentence explaining why that breaks. Restore it.
🤖
| return nil | ||
| } | ||
|
|
||
| // validateAIProviderBedrockModels requires the model identifiers that the |
There was a problem hiding this comment.
Note [CRF-5] Existing rows that already carry the broken shape are unaffected by this PR. (Netero)
Validation only guards new writes. Rows seeded from env, inserted by
dbgen, or promoted byBackfillBedrockProviderType[...] keep 404ing with a WARN until an operator re-saves them through the UI, which now supplies both models. Nothing in the diff detects or reports them.
Recorded, not a request to change this PR. Whether AIGOV-564 is closed for deployments already in the broken state, and whether that needs a backfill or a surfaced provider-build status, is a human call. Say which one you intend, here or in the issue.
🤖
|
|
||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/coder/coder/v2/aibridge/config" |
There was a problem hiding this comment.
Note [CRF-6] codersdk's external test package now imports aibridge/config. (Netero)
No cycle:
aibridge/configimports nocodersdk[...] and the test file ispackage codersdk_test. The dependency is deliberate, since the point ofTestAIProviderRequest_ValidationInSyncis to fail when the two validators drift.
Recorded so a future layering check does not read it as an accident.
🤖
Implements: https://linear.app/codercom/issue/AIGOV-564/aibridge-bedrock-provider-skipped-404-on-all-routes-when-settings-omit
Improves validation when creating and updating AI providers: a Bedrock provider using the
invoke-modelprotocol now requiresmodelandsmall_fast_model.This brings API validation in sync with the UI, which already required both fields.