🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat(site): add Bedrock mantle protocol selector to provider form - #27156

Merged
evgeniy-scherbina merged 9 commits into
mainfrom
yevhenii/add-bedrock-provider-ui
Jul 16, 2026
Merged

feat(site): add Bedrock mantle protocol selector to provider form#27156
evgeniy-scherbina merged 9 commits into
mainfrom
yevhenii/add-bedrock-provider-ui

Conversation

@evgeniy-scherbina

@evgeniy-scherbina evgeniy-scherbina commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Implements: https://linear.app/codercom/issue/AIGOV-517/add-ui-for-bedrock-mantle

Follow-up to #26745, which added AWS Bedrock mantle support to the backend and modeled it as a protocol field on the Bedrock provider settings.

What changed

  • Adds a Protocol selector to the Bedrock provider form: InvokeModel (default) or Mantle.
  • The form is now protocol-aware:
    • Model / Small-fast model fields are shown only for InvokeModel. Mantle is a passthrough (the client sends the model at request time), so the fields are hidden and omitted from the saved settings.
    • Endpoint validation, placeholder, and hint switch per protocol. Mantle requires a https://bedrock-mantle.{region}.api.aws/anthropic URL; InvokeModel keeps the https://bedrock-runtime.{region}.amazonaws.com shape. The /anthropic suffix is required for mantle because the SDK appends /v1/messages to the base URL.

Manual Testing

Tested the following scenarios:

  1. Creating a new Mantle provider — works.
  2. Creating a new InvokeModel provider — works.
  3. Verifying that an existing InvokeModel provider continues to work.
  4. Upgrading an existing InvokeModel provider to Mantle — works.

Screenshots

InvokeModel

image

Mantle

image

@evgeniy-scherbina
evgeniy-scherbina force-pushed the yevhenii/add-bedrock-provider-ui branch from e7db488 to 96b3d0f Compare July 10, 2026 12:57
evgeniy-scherbina added a commit that referenced this pull request Jul 13, 2026
Implements
https://linear.app/codercom/issue/AIGOV-213/add-bedrock-provider

# AWS Bedrock mantle support in AI Gateway

## Summary

Add support for the AWS Bedrock **mantle** endpoint
(`bedrock-mantle.{region}.api.aws/anthropic/v1/messages`) to AI Gateway.
Mantle serves Claude through the native Anthropic Messages API. We model
it as a `protocol` field on the existing Bedrock provider settings
(`invoke-model` default, or `mantle`) rather than as a new provider
type, and we treat mantle as a pure passthrough: SigV4-sign and forward,
no body translation.

## Background

Claude on AWS Bedrock is reachable through two endpoints, each speaking
exactly one wire protocol:

1. **InvokeModel** (existing): `bedrock-runtime.{region}.amazonaws.com`.
Model id in the URL path, request translated into Bedrock's InvokeModel
format, responses returned as a binary AWS eventstream. This is what AI
Gateway already supported for Bedrock.
2. **Mantle** (this doc):
`bedrock-mantle.{region}.api.aws/anthropic/v1/messages`. Native
Anthropic Messages API: model in the body, plain SSE streaming.

## Why a `protocol` field, not a new provider type

The alternative is to model mantle as its own `ai_provider_type`
(`bedrock-mantle`) alongside `bedrock`. I chose the `protocol` field
instead for two reasons:

1. Mantle reads more like a protocol of Bedrock than a separate
provider. It is the same AWS account, credentials, region, and IAM,
reached over a different wire protocol and host. One Bedrock provider
with two protocols (`invoke-model` default and `mantle`) models that
more organically than two provider types.
2. It avoids a database migration. The `protocol` field lives in the
settings JSON blob (empty resolves to `invoke-model`, so existing
providers are unaffected), whereas a new type means an enum value and
the `ALTER TYPE ... ADD VALUE` migration that goes with it.

## Why passthrough, not translation

The client already emits Bedrock-legal requests in mantle mode:

```sh
export CLAUDE_CODE_USE_MANTLE=1
export CLAUDE_CODE_SKIP_MANTLE_AUTH=1
export ANTHROPIC_BEDROCK_MANTLE_BASE_URL=https://<coder>/api/v2/aibridge/<provider-name>
```

So the gateway just forwards the body and SigV4-signs it (service
`bedrock-mantle`), and skips all the InvokeModel body-translation (model
remap, thinking conversion, beta-flag allowlist, field stripping). This
keeps the mantle path thin and avoids a second copy of translation logic
to maintain.

## Consequences

- Protocol-dependent fields: `model` / `small_fast_model` are used by
InvokeModel but ignored by mantle (the client sends the model), and
`base_url` is required for mantle but optional for InvokeModel.
Validation is protocol-aware.
- No central model control on mantle: because it is a passthrough, the
operator cannot pin the model.
- `region` and the `base_url` host must name the same region (the SigV4
scope must match the endpoint); a mismatch surfaces as `Credential
should be scoped to a valid region`.

## Draft UI

<img width="1100" height="579" alt="image"
src="https://github.com/user-attachments/assets/37bab46d-8958-4a96-9f47-1fef3493e1b6"
/>

## Follow-up PRs:
- #27156
Base automatically changed from yevhenii/add-bedrock-provider to main July 13, 2026 23:44
@evgeniy-scherbina
evgeniy-scherbina force-pushed the yevhenii/add-bedrock-provider-ui branch 2 times, most recently from 5448d68 to ed55d25 Compare July 14, 2026 19:32
@evgeniy-scherbina
evgeniy-scherbina force-pushed the yevhenii/add-bedrock-provider-ui branch from ddb458f to a23bc9e Compare July 15, 2026 17:17
@linear-code

linear-code Bot commented Jul 15, 2026

Copy link
Copy Markdown

AIGOV-517

@evgeniy-scherbina
evgeniy-scherbina marked this pull request as ready for review July 15, 2026 17:27
@evgeniy-scherbina

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-15 20:05 UTC by @evgeniy-scherbina
Spend: $53.63 / $100.00

Review history
  • R1 (2026-07-15): 16 reviewers, 4 Nit, 7 Note, 1 P1, 3 P3, REQUEST_CHANGES. Review
  • R2 (2026-07-15), 4 Nit, 7 Note, 1 P1, 3 P3, COMMENT. Review
  • R3 (2026-07-15): 13 reviewers, 5 Nit, 8 Note, 1 P1, 3 P3, COMMENT. Review

deep-review v0.9.0 | Round 3 | 511c3cd..b02b0cf

Last posted: Round 3, 17 findings (1 P1, 3 P3, 5 Nit, 8 Note), COMMENT. Review

Finding inventory

Finding inventory: PR #27156

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (9e9a986, 87bdcaf); panel verified R3 ProviderForm.tsx:431 Base-URL rewrite in handleBedrockProtocolChange has no test (switch story starts from empty baseUrl) R1 Netero Yes
CRF-2 Nit Author fixed (68b0594); panel verified R3 ProviderForm.tsx:435 BEDROCK_MANTLE_DEFAULT_REGION also used as InvokeModel fallback; name inaccurate R1 Netero, Gon, Robin Yes
CRF-3 Note Author contested; panel holds R3 (no dissent; suggest a PR-description line) ProviderForm.tsx:128 Default Bedrock region changed us-east-2 -> us-east-1, not in PR description R1 Netero Yes
CRF-4 P1 Author fixed (87bdcaf); panel verified R3 (root cause, both directions) ProviderForm.tsx:432 Sequential setFieldValue validates baseUrl against stale protocol; Save stays disabled after protocol switch R1 Meruem Yes
CRF-5 P3 Author fixed (4455552); panel verified R3 ProviderForm.tsx:197 Protocol-conditional Yup schema (mantle URL enforcement, model optional) untested; no story submits a mantle provider R1 Bisky Yes
CRF-6 Note Author acknowledged; panel holds R3 (real path now covered elsewhere) ProviderForm.stories.tsx:143 AddBedrockMantle seeds unreachable state (mantle protocol + invoke-model baseUrl); asserts hint text, not endpoint field R1 Hisoka, Melody Yes
CRF-7 P3 Author fixed (5fb2c5d); panel verified R3 ProviderForm.tsx:215 InvokeModel endpoint validation error lacks the format hint its mantle sibling provides R1 Leorio Yes
CRF-8 Note Author acknowledged; panel holds R3 (4/5; Mafu-san dissent P3; residual: unsourced "recommended by AWS") ProviderForm.tsx:586 "recommended by AWS" unsourced UI claim; default (InvokeModel) contradicts recommendation copy R1 Leorio, Mafu-san Yes
CRF-9 Nit Author acknowledged; panel holds R3 ProviderForm.tsx:594 Endpoint description ternary duplicates the "In the format of " wrapper; only the URL differs R1 Mafuuu, Robin, Meruem, Zoro Yes
CRF-10 Nit Author fixed (b02b0cf); panel verified R3 ProviderForm.tsx:128 providerDefaults.bedrock.baseUrl literal duplicates the new bedrockInvokeModelBaseUrl helper; can drift R1 Robin Yes
CRF-11 Nit Author acknowledged; panel holds R3 (Gon notes production instance; within CRF-11 scope, not re-raised) providerFormApiMap.test.ts:421 Several new comments restate the code / narrate intent rather than describe behavior R1 Gon Yes
CRF-12 Note Author acknowledged; panel holds R3 providerFormApiMap.ts:263 Unrecognized stored protocol renders an empty Select with no visible selection (unreachable today) R1 Nami Yes
CRF-13 Note Author acknowledged; panel holds R3 (client regex is sole shape-enforcer, but failure is visible/fixable) ProviderForm.tsx:49 Mantle URL contract re-encoded in frontend regex, spelled in 4 places; backend/SDK drift risk R1 Pariston, Melody, Zoro Yes
CRF-14 Note Author acknowledged; panel holds R3 ProviderForm.tsx:219 model/smallFastModel schema .when block duplicated; a helper would fold a third protocol-gated field R1 Robin Yes
CRF-15 Note Open (no repliable thread; folded into R1 review body) providerFormApiMap.test.ts:621 providerFormValuesToUpdate has no mantle test row (shared buildBedrockSettings already covered) R1 Bisky Yes
CRF-16 Nit Open ProviderForm.tsx:50 BEDROCK_CANONICAL_URL_REGEX is the only member of the protocol-paired set not named by protocol; rename to BEDROCK_INVOKE_MODEL_URL_REGEX R3 Gon Yes
CRF-17 Note Open ProviderForm.tsx:433 handleBedrockProtocolChange's ?? BEDROCK_DEFAULT_REGION fallback arm and the mantle->InvokeModel reverse switch are untested R3 Bisky, Mafu-san, Netero Yes

Cross-check notes

  • CRF-4 is the headline. Verified against Formik 2.4.9 source: setFieldValue validates setIn(state.values, field, value) (formik.cjs.development.js:723); two synchronous calls in handleBedrockProtocolChange share the same render-closure state.values, so the second call validates the new baseURL against the pre-switch protocol regex. Submit is gated on !form.isValid (ProviderForm.tsx:709) and nothing re-validates until another field changes. Meruem confirmed empirically with a useFormik harness. Fix: single atomic setValues.
  • CRF-4 and CRF-1 are distinct: CRF-1 is missing test coverage of the region-preserving rewrite; CRF-4 is a concrete validation/enablement bug in the same handler. The AddBedrockSwitchToMantle story never asserts isValid or submit-button state, which is precisely why CRF-4 shipped. The coverage gaps (CRF-1, CRF-5) are the reason the P1 was not caught.
  • CRF-9 convergence: four reviewers (Mafuuu, Robin, Meruem, Zoro) independently flagged the same duplicated description wrapper. Cosmetic, kept at Nit.
  • Gon rated the redundant test comment P2; downgraded to Nit. Keep-argument: Gon is the comment specialist and the comment narrates reasoning, violating the house "why not what" rule; it ships permanently under the no-follow-up assumption. Downgrade rationale: the code is correct and there is zero behavioral risk; a convention/style violation where the code works is the definition of a Nit, not a should-fix P2.
  • CRF-13 / CRF-3: these follow the pre-existing BEDROCK_CANONICAL_URL_REGEX pattern and the invoke-model region-from-URL derivation; the PR does not change the invariants that made the pattern safe, so they stay at Note.
  • Security (Kurapika): both endpoint regexes are fully anchored; userinfo bypass, suffix widening, path traversal, and newline injection all rejected. Secrets stay write-only. No new auth/SSRF surface in this diff. Clean.
  • Determinism (Komugi): no findings. Both new stories carry happens-before edges; regexes are non-global (no shared lastIndex). Clean.
  • Ging-ts, Ging-react: no findings.

Contested and acknowledged

CRF-3 (Note, ProviderForm.tsx:128) - Default region us-east-2 -> us-east-1

  • Finding: The new-provider default Bedrock endpoint region changed from us-east-2 to us-east-1, a user-visible default change not called out in the PR description.
  • Author defense (R2, PRRC_..._nD2): "I did it on purpose, from my testing it's easier to setup Bedrock Mantle on us-east-1." Intentional; ties the default to the mantle setup experience.
  • Status: Contested. No panel disposition yet.

CRF-6 (Note, ProviderForm.stories.tsx:143) - AddBedrockMantle unreachable seed state

  • Finding: AddBedrockMantle seeds protocol=mantle with no baseUrl, so it inherits the InvokeModel host; the play asserts only the hint text, not the endpoint field.
  • Author defense (R3): "won't fix, minor." Human decline, severity-only reasoning.
  • Status: Acknowledged. No panel disposition yet.

CRF-8 (Note, ProviderForm.tsx:586) - "recommended by AWS" unsourced claim

  • Finding: The protocol hint asserts "recommended by AWS for new deployments" with no source, and the shipped default (InvokeModel) contradicts the recommendation. R1 flagged this as needing a human decision.
  • Author defense (R3): "won't fix, minor." Human decline, severity-only reasoning; does not address the claim-accuracy concern.
  • Status: Acknowledged. No panel disposition yet.

CRF-9 (Nit, ProviderForm.tsx:594) - Duplicated description wrapper

  • Finding: The Endpoint description ternary duplicates the whole "In the format of " wrapper; four reviewers flagged it.
  • Author defense (R3): "won't fix, minor."
  • Status: Acknowledged. No panel disposition yet.

CRF-11 (Nit, providerFormApiMap.test.ts:421) - Comments restate code

  • Finding: Several new comments restate the code / narrate intent rather than describe behavior.
  • Author defense (R3): "won't fix, minor."
  • Status: Acknowledged. No panel disposition yet.

CRF-12 (Note, providerFormApiMap.ts:263) - Unrecognized protocol renders empty Select

  • Finding: An unrecognized stored protocol renders an empty Select with no visible selection; unreachable today because the backend emits only the two known values.
  • Author defense (R3): "won't fix, minor."
  • Status: Acknowledged. No panel disposition yet.

CRF-13 (Note, ProviderForm.tsx:49) - Mantle URL contract spelled in 4 places

  • Finding: The mantle URL contract is re-encoded client-side and spelled out in four places (regex, builder, hint, error message); drift risk if AWS/SDK revises it.
  • Author defense (R3, PRRC_..._Qx7): "Correct, but I followed pre-existing BEDROCK_CANONICAL_URL_REGEX coupling." Accepts the finding as correct; defends consistency with the established, already-safe pattern.
  • Status: Acknowledged. No panel disposition yet.

CRF-14 (Note, ProviderForm.tsx:219) - Duplicated .when schema blocks

  • Finding: The model and smallFastModel schema .when blocks are duplicated; a helper would fold a third protocol-gated field. The finding itself said two instances is not yet worth an abstraction.
  • Author defense (R3): "won't fix, minor."
  • Status: Acknowledged. No panel disposition yet.

Law analysis

Not run. Effective additions 307 < 1000 threshold.

Round log

Round 1

Netero first pass (1 P3, 1 Nit, 1 Note; mechanical floor clean). Panel of 16: bisky, hisoka, mafu-san, mafuuu, pariston, ging-ts, ging-react, nami, gon, leorio, komugi, melody, kurapika, robin + wildcards meruem, zoro. Result: 1 P1, 3 P3, 4 Nit, 7 Note. Reviewed against 4d884c30e7e2..a23bc9ed6c6b.

Round 2 update

BLOCKED by churn guard. 6 addressed (CRF-1, CRF-2, CRF-4, CRF-5, CRF-7, CRF-10 - fixes claimed, not yet panel-verified), 1 contested (CRF-3, author says the us-east-1 default is intentional), 8 silent (CRF-6, CRF-8, CRF-9, CRF-11, CRF-12, CRF-13, CRF-14, CRF-15 - no code change, no author response). No Netero, no panel. Posted a churn-blocked COMMENT naming each silent finding. Prior REQUEST_CHANGES stands (CRF-4 fix unverified). Reviewed against 511c3cddc081..b02b0cfa93f6.

Round 3 (continued)

PROCEED panel of 13 (bisky, hisoka, mafu-san, mafuuu, pariston, ging-ts, ging-react, nami, komugi, meruem, leorio, gon + wildcard razor) + advisory Netero. All 6 code fixes verified at root cause by Netero and 8 reviewers; CRF-4 (P1) confirmed direction-independent. Dispositions on CRF-3/6/8/9/11/12/13/14 hold (CRF-8 with Mafu-san P3 dissent, 4/5 hold; residual unsourced "recommended by AWS" noted for the human). 2 new: CRF-16 (Nit, rename canonical regex), CRF-17 (Note, untested fallback/reverse-switch). CRF-15 still open, low value. Prior REQUEST_CHANGES dismissed (P1 verified fixed). Posted COMMENT + 6 fix-confirmation replies (resolved). Reviewed against 511c3cddc081..b02b0cfa93f6.

Round 3

Churn guard first returned BLOCKED on a false "no author response" claim; orchestrator verified via pr.json that the human author replied to every threaded finding after round 2 and sent it back with evidence. Corrected verdict: PROCEED. 6 addressed, 7 acknowledged (CRF-6/8/9/11/12/13/14 - six "won't fix, minor", CRF-13 follows pre-existing pattern), 1 contested (CRF-3), CRF-15 has no repliable thread. Head SHA unchanged since R2 (b02b0cfa93f6), so Netero scans this SHA for the first time (R2 was churn-blocked). Panel verifies the claimed CRF-1/2/4/5/7/10 fixes. Reviewed against 511c3cddc081..b02b0cfa93f6.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-tasks

coder-tasks Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

This PR adds a user-facing Protocol selector (InvokeModel vs Mantle) to the Bedrock provider form in AI Settings, with a distinct mantle endpoint format (https://bedrock-mantle.{region}.api.aws/anthropic) and protocol-aware behavior (model fields hidden for Mantle passthrough). The existing Bedrock docs describe only the InvokeModel endpoint and no protocol choice.

Updates Needed

  • docs/ai-coder/ai-gateway/providers.md (Amazon Bedrock section) - Document the new Protocol selector: InvokeModel (default) vs Mantle. Cover the mantle endpoint format https://bedrock-mantle.{region}.api.aws/anthropic (the /anthropic suffix is required), and that Mantle is a passthrough where the model / small-fast-model fields are not configured on the provider. The section currently only lists the https://bedrock-runtime.<region>.amazonaws.com format and the model fields as always required.

Automated review via Coder Agents

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clean, well-tested feature PR. The protocol enumeration is consistent across the Go type, generated TS, the Yup oneOf, and the two SelectItems; the mantle passthrough correctly omits the model fields on the wire and reads legacy/empty protocol back as InvokeModel; the region invariant the backend requires for mantle is guaranteed by the schema-and-parse coupling; and the endpoint regexes are fully anchored against userinfo bypass, suffix widening, and path traversal. The unit suite (96 tests) and both new interaction stories pass.

One blocking issue. handleBedrockProtocolChange updates protocol and baseUrl with two sequential setFieldValue calls, and Formik 2.4.9 validates each against a stale render-closure snapshot of the other field. The result: after a user switches protocol, form.isValid goes false against a fully valid form and the Save button stays disabled until they touch another field. That breaks the feature's own happy path, and the existing switch story never asserts submit-button state, so CI is green. An atomic setValues fixes the class.

Severity count: 1 P1, 3 P3, 4 Nit, 7 Note. The P3s share the theme the P1 exposed: the protocol switch and mantle-submit paths have thin coverage (the rewrite value, the conditional schema branches, and no story that actually submits a mantle provider). Two Notes need a human decision, not a code change: the "recommended by AWS" hint copy is an unsourced third-party claim, and the shipped default (InvokeModel) is the option the copy steers users away from.

On the endpoint error messages, Leorio put it well: the mantle message is a diagnosis while the InvokeModel one is "a doctor saying "you're sick."" Giving both the format string is a one-line fix.

Requesting changes for the P1; everything else is polish or coverage.


site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.test.ts:621

Note [CRF-15] The mantle branch of buildBedrockSettings is verified only through providerFormValuesToCreate; the providerFormValuesToUpdate Bedrock suite has no mantle row. (Bisky)

Low risk: both paths call the same buildBedrockSettings, and create already asserts the model-field omission and protocol: "mantle". Worth one row for symmetry with the existing update Bedrock cases.

🤖

🤖 This review was automatically generated with Coder Agents.

// Switching protocols rewrites the base URL to the matching host, keeping
// the region the user already entered so they do not retype it.
const handleBedrockProtocolChange = (protocol: AIProviderBedrockProtocol) => {
void form.setFieldValue("protocol", protocol);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 [CRF-4] handleBedrockProtocolChange fires two sequential setFieldValue calls, so validation runs against a stale protocol and Save stays disabled after switching protocol even though every value is valid. (Meruem P1)

Formik 2.4.9's setFieldValue(field, value) validates setIn(state.values, field, value), where state.values is the render-closure snapshot. Both calls share that snapshot:

form.setFieldValue("protocol", protocol);                    // validates {mantle, oldInvokeUrl}
form.setFieldValue("baseUrl", bedrockMantleBaseUrl(region)); // validates {invoke-model (stale), mantleUrl}

The last-resolving validation checks the new mantle URL against the InvokeModel regex, sets errors.baseUrl, and form.isValid goes false. Submit is disabled={... || !form.isValid ...} (line 709) and nothing re-validates until another field changes, so the primary happy path (pick Mantle, then Save) is broken; the bug is symmetric on switching back. Verified against the Formik source and Meruem's useFormik harness. Fix atomically so validation runs once against the complete next state:

form.setValues({ ...form.values, protocol, baseUrl });

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 87bdcaf

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified fixed in 87bdcaf. handleBedrockProtocolChange now uses a single atomic form.setValues, so validation runs once against the complete next state; the phantom-disabled-Save class is eliminated in both switch directions, and AddBedrockProtocolSwitchKeepsSaveEnabled asserts Save stays enabled after the switch.

🤖


// Switching protocols rewrites the base URL to the matching host, keeping
// the region the user already entered so they do not retype it.
const handleBedrockProtocolChange = (protocol: AIProviderBedrockProtocol) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [CRF-1] No test asserts the base-URL rewrite performed by handleBedrockProtocolChange. (Netero)

AddBedrockSwitchToMantle only asserts the model fields hide and the hint text flips; it starts from an empty baseUrl, so both the region-preservation branch and the resulting endpoint value go unverified.

A regression that dropped the region or wrote the wrong host would pass CI. Add a story that types an InvokeModel URL with a non-default region, switches to Mantle, and asserts the endpoint input equals https://bedrock-mantle.{that-region}.api.aws/anthropic. The same missing assertion (isValid / submit state) is why CRF-4 was not caught.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 9e9a986

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified fixed in 9e9a986 (with 87bdcaf). AddBedrockProtocolSwitchKeepsSaveEnabled starts from a non-default eu-west-1 InvokeModel URL and asserts the endpoint is rewritten to https://bedrock-mantle.eu-west-1.api.aws/anthropic, covering region preservation on the field value.

🤖

@@ -167,16 +197,38 @@ const makeBedrockSchema = (editing: boolean) =>
name: makeNameSchema(editing),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [CRF-5] The protocol-conditional Bedrock schema (mantle URL enforcement, model/smallFastModel becoming optional under mantle) has no test, and no story submits a mantle provider. (Bisky)

makeBedrockSchema is module-local and never imported by a test... if a regression left model required under mantle, the form becomes permanently unsubmittable and no test would catch it.

The unit tests exercise the mappers, which run regardless of the Yup schema, so the .when("protocol", ...) wiring is unguarded. Add a story that submits a mantle provider: reject the InvokeModel URL shape, then accept bedrock-mantle.{region}.api.aws/anthropic and confirm onSubmit fires with no model fields required.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 4455552

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified fixed in 4455552. AddBedrockMantleRejectsInvokeUrl seeds protocol=mantle with an InvokeModel-shaped URL and asserts submit stays disabled and onSubmit is never called, genuinely exercising the mantle .when schema branch.

🤖

otherwise: (schema) =>
schema.matches(
BEDROCK_CANONICAL_URL_REGEX,
"Endpoint must be a standard AWS Bedrock URL.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 [CRF-7] The InvokeModel endpoint validation error names the problem but not the fix, while its new mantle sibling one line up hands the user the exact format. (Leorio)

mantle: Endpoint must be a Bedrock mantle URL (https://bedrock-mantle.{region}.api.aws/anthropic).
invoke-model: Endpoint must be a standard AWS Bedrock URL.

A user who pasted the wrong shape has no idea what "standard" means. Match the sibling:
Endpoint must be a standard AWS Bedrock URL (https://bedrock-runtime.{region}.amazonaws.com).

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 5fb2c5d

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified fixed in 5fb2c5d. The InvokeModel endpoint error now reads "Endpoint must be a Bedrock InvokeModel URL (https://bedrock-runtime.{region}.amazonaws.com).", matching the format hint its mantle sibling provides.

🤖

void form.setFieldValue("protocol", protocol);
const region =
parseBedrockRegionFromBaseUrl(form.values.baseUrl) ??
BEDROCK_MANTLE_DEFAULT_REGION;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-2] BEDROCK_MANTLE_DEFAULT_REGION is used as the fallback region when switching to InvokeModel too, so the MANTLE in its name is inaccurate. (Netero, Gon, Robin)

The fallback applies regardless of target protocol; switching an unparseable URL to InvokeModel yields bedrock-runtime.us-east-1.amazonaws.com from a constant named for mantle. Rename to BEDROCK_DEFAULT_REGION (or similar) to reflect that it is the shared region default.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 68b0594

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified fixed in 68b0594. BEDROCK_MANTLE_DEFAULT_REGION is renamed to BEDROCK_DEFAULT_REGION at all use sites, so the shared fallback is no longer named for mantle.

🤖

// does not configure model fields (the client sends the model), and the
// endpoint hint points at the mantle host.
export const AddBedrockMantle: Story = {
args: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-6] AddBedrockMantle seeds a state no user can reach: protocol: "mantle" with no baseUrl, so it inherits the InvokeModel host from typeDefaults.bedrock. (Hisoka, Melody)

the rendered form shows a mantle protocol pointing at an InvokeModel endpoint that fails the mantle URL schema.

The play asserts only that the hint text /bedrock-mantle/i renders (the description, not the field value), so the mismatched endpoint goes unchecked. Seed a coherent mantle baseUrl and assert the endpoint field shows it.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't fix, minor

</Select>
<p className="text-xs text-content-secondary m-0">
{isMantle
? "Newer Anthropic-compatible Bedrock endpoint, recommended by AWS for new deployments."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-8] The protocol hint asserts "recommended by AWS for new deployments" with no cited source, and the shipped default contradicts the recommendation. (Leorio, Mafu-san)

Nothing in the diff or the linked backend PR grounds the AWS recommendation; if AWS's positioning changes, the copy goes stale silently. Separately, the selector labels InvokeModel "(default)" while the hint calls it "Legacy... Mantle is recommended", so the shipped default is the option the copy steers users away from. This needs a human decision: source or soften the claim, and decide whether the default and the recommendation should agree.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't fix, minor

// An empty or missing protocol resolves to InvokeModel (legacy rows),
// mirroring the backend. Any other stored value passes through unchanged
// rather than being collapsed to InvokeModel.
const protocol: AIProviderBedrockProtocol = s.protocol || "invoke-model";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-12] An unrecognized stored protocol renders a Protocol selector with no visible selection. (Nami)

s.protocol || "invoke-model" passes any truthy stored value through unchanged; <SelectValue /> has no placeholder and no matching <SelectItem>, so a future server-side protocol the form does not know about would render an empty dropdown, then oneOf blocks save. Unreachable today (backend emits only the two known values); worth knowing before a third protocol lands.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't fix, minor

};

const HTTP_SCHEME_REGEX = /^https?:\/\//i;
// AWS Bedrock InvokeModel URL, e.g. https://bedrock-runtime.{region}.amazonaws.com

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-13] The mantle endpoint contract (host shape + required /anthropic suffix) is re-encoded client-side in BEDROCK_MANTLE_URL_REGEX and spelled out in four places: the regex, bedrockMantleBaseUrl, the description hint, and the schema error message. (Pariston, Melody, Zoro)

If AWS or the SDK revises the mantle URL contract, valid endpoints get rejected client-side or the region parse and schema disagree. This follows the pre-existing BEDROCK_CANONICAL_URL_REGEX coupling, so it is an inference about where a future break would originate, not a change required in this PR.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, but I followed pre-existing BEDROCK_CANONICAL_URL_REGEX coupling.

),
})
.required("Endpoint is required"),
apiKey: Yup.string(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-14] The model and smallFastModel schema entries are the same .when("protocol", { is: p => p !== "mantle", ... }) block twice, differing only in the message. (Robin)

Two instances is not yet worth an abstraction, but a third protocol-gated field would justify a small requiredUnlessMantle(message) helper. Noting the shape so the next addition reuses rather than copies.

🤖

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't fix, minor

@evgeniy-scherbina
evgeniy-scherbina force-pushed the yevhenii/add-bedrock-provider-ui branch from 7ba0a5b to 87bdcaf Compare July 15, 2026 18:38
@evgeniy-scherbina
evgeniy-scherbina force-pushed the yevhenii/add-bedrock-provider-ui branch from 632aa1d to 68b0594 Compare July 15, 2026 19:33
@evgeniy-scherbina

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2. The author addressed the substance quickly: CRF-4 (P1) is now a single atomic form.setValues({ ...form.values, protocol, baseUrl }) with a new story asserting Save stays enabled after the switch; CRF-1, CRF-5, and CRF-7 (the P3s) have code and story fixes; CRF-2 and CRF-10 (Nits) are renamed and de-duplicated; and CRF-3 was answered (the us-east-1 default is intentional, easier mantle setup). Those fixes are claimed but not yet panel-verified; the panel will verify them next round.

Panel re-review is blocked this round because eight findings received no code change and no response. Under this review's no-follow-up assumption, a silent Nit or Note stays in the code forever, so each needs a disposition: fix it, or reply with either a reason it should not change or a tracking ticket.

  • CRF-6 (Note, ProviderForm.stories.tsx:143): AddBedrockMantle still seeds a mantle protocol with an InvokeModel endpoint and asserts only the hint text, not the endpoint field.
  • CRF-8 (Note, ProviderForm.tsx:586): the "recommended by AWS" hint copy is an unsourced third-party claim, and the shipped default (InvokeModel) contradicts it. This one needs a human decision.
  • CRF-9 (Nit, ProviderForm.tsx:594): the Endpoint description ternary still duplicates the whole In the format of <code> wrapper (four reviewers flagged it).
  • CRF-11 (Nit, providerFormApiMap.test.ts:421): comments that restate the code or narrate intent rather than describe behavior.
  • CRF-12 (Note, providerFormApiMap.ts:263): an unrecognized stored protocol renders an empty Select.
  • CRF-13 (Note, ProviderForm.tsx:49): the mantle URL contract is spelled out in four places.
  • CRF-14 (Note, ProviderForm.tsx:219): the two model-field .when schema blocks are duplicated.
  • CRF-15 (Note, providerFormApiMap.test.ts:621): no mantle row in the providerFormValuesToUpdate suite.

None of these is blocking on its own; a one-line reply per thread (fix, ticket, or "won't fix because...") clears the block and lets the panel verify the fixes above. Separately, the doc-check bot flagged that docs/ai-coder/ai-gateway/providers.md still documents only the InvokeModel endpoint and no protocol choice; worth folding into this PR or a follow-up.

The prior REQUEST_CHANGES stands until the panel verifies the CRF-4 fix next round.

🤖 This review was automatically generated with Coder Agents.

@evgeniy-scherbina

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 3. The blocking P1 is fixed at the root cause, so I'm dismissing the earlier changes request. handleBedrockProtocolChange now writes protocol and baseUrl in one atomic form.setValues, so validation runs once against the complete next state and the phantom-disabled-Save class is gone in both switch directions, not just the tested one; Netero and the panel (Meruem, Mafuuu, Pariston, Razor, Nami) verified the mechanism independently. The three P3s are genuinely fixed with tests that have teeth: AddBedrockProtocolSwitchKeepsSaveEnabled starts from a non-default eu-west-1 URL and asserts the rewritten endpoint plus enabled Save (CRF-1, CRF-4), AddBedrockMantleRejectsInvokeUrl blocks submit on the mantle .when branch and asserts onSubmit never fires (CRF-5), and the InvokeModel endpoint error now names its format (CRF-7). The two Nits (CRF-2 rename, CRF-10 helper dedup) are done. Unit suite 96 passing; the panel could not run the Storybook browser project on this runner, so the story verdicts are by reading each play against the rendered tree, not a green run.

The author dispositioned the round-1 Nits/Notes by reply. The panel respects those human decisions and found no new evidence to reopen them, with one residual worth an informed final call: CRF-8's hint says Mantle is "recommended by AWS for new deployments" with no source, while the form ships InvokeModel as the default. Pariston notes the backend itself calls InvokeModel "legacy," so defaulting to it is a sound backward-compatible choice; the only weak part is the unsourced AWS attribution. Source it or soften it, your call. CRF-3 (the us-east-1 default) holds as intentional; consider one line in the PR description so the default change is discoverable.

Two new low-severity items this round, both optional: CRF-16 (rename BEDROCK_CANONICAL_URL_REGEX to match the new protocol-paired naming) and CRF-17 (the fallback-region arm and the mantle->InvokeModel reverse switch have no test). Also still open from round 1: CRF-15 (no mantle row in the providerFormValuesToUpdate suite), low value now that the shared buildBedrockSettings mantle path is covered on the create side.

Hisoka summed the round up: "I came looking for a fight worth having. This one earned the silence."

Dismissing the changes request; the remaining items are polish, coverage, and one editorial call.

🤖 This review was automatically generated with Coder Agents.


const HTTP_SCHEME_REGEX = /^https?:\/\//i;
// AWS Bedrock InvokeModel URL, e.g. https://bedrock-runtime.{region}.amazonaws.com
const BEDROCK_CANONICAL_URL_REGEX =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit [CRF-16] BEDROCK_CANONICAL_URL_REGEX is the only member of the protocol-paired set not named by its protocol. (Gon)

The PR adds the sibling BEDROCK_MANTLE_URL_REGEX and the builder pair bedrockInvokeModelBaseUrl/bedrockMantleBaseUrl... only the regex is named "canonical."

The tell is the // AWS Bedrock InvokeModel URL comment added directly above it to map "canonical" onto "InvokeModel." Rename to BEDROCK_INVOKE_MODEL_URL_REGEX (two call sites) and that clarifying comment becomes redundant. Optional, and tied to the naming convention this PR introduces.

🤖

// the region the user already entered so they do not retype it.
const handleBedrockProtocolChange = (protocol: AIProviderBedrockProtocol) => {
const region =
parseBedrockRegionFromBaseUrl(form.values.baseUrl) ??

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note [CRF-17] handleBedrockProtocolChange's ?? BEDROCK_DEFAULT_REGION fallback and the mantle -> InvokeModel reverse switch are untested. (Bisky, Mafu-san, Netero)

The new switch stories all start from a URL with a parseable region and go InvokeModel -> Mantle. Nothing exercises the fallback arm (blank or non-canonical baseUrl, then switch, expecting us-east-1), and nothing switches the reverse direction. The atomic setValues fix is direction-independent, so the reverse path is low-risk, but the ?? arm is a live branch in new code. A story that seeds baseUrl: "", switches to Mantle, and asserts the endpoint becomes https://bedrock-mantle.us-east-1.api.aws/anthropic proves it.

🤖

…idy bedrock endpoint hint and cover protocol switches

- Collapse the duplicated "In the format of" endpoint description ternary
  into a single wrapper that only swaps the URL.
- Rename BEDROCK_CANONICAL_URL_REGEX to BEDROCK_INVOKE_MODEL_URL_REGEX so it
  is named by protocol like its mantle sibling.
- Add stories covering the Mantle to InvokeModel reverse switch and the
  region fallback when the endpoint has no parseable region.
@jakehwll

Copy link
Copy Markdown
Contributor

🤖 This comment was written by Coder Agents on behalf of Jake Howell.

On the Protocol hint copy (ProviderForm.tsx): the Mantle description says it's "recommended by AWS for new deployments," yet the shipped default is InvokeModel. Agreed with the reviewer that this is probably incorrect/inconsistent as written, either the claim is unsourced or the default should match the recommendation, but I'm treating it as a product/wording call rather than a blocker. Flagging so we make that choice deliberately; happy to leave InvokeModel as the default and just soften the copy if that's the intent.

FYI I also pushed a small commit addressing a few of the review nits: collapsed the duplicated endpoint-hint ternary, renamed BEDROCK_CANONICAL_URL_REGEX -> BEDROCK_INVOKE_MODEL_URL_REGEX, and added stories for the Mantle->InvokeModel reverse switch and the region fallback (all 22 ProviderForm stories pass locally).

@evgeniy-scherbina

Copy link
Copy Markdown
Contributor Author

FYI I also pushed a small commit addressing a few of the review nits: collapsed the duplicated endpoint-hint ternary, renamed BEDROCK_CANONICAL_URL_REGEX -> BEDROCK_INVOKE_MODEL_URL_REGEX, and added stories for the Mantle->InvokeModel reverse switch and the region fallback (all 22 ProviderForm stories pass locally).

I like the changes.

On the Protocol hint copy (ProviderForm.tsx): the Mantle description says it's "recommended by AWS for new deployments," yet the shipped default is InvokeModel. Agreed with the reviewer that this is probably incorrect/inconsistent as written, either the claim is unsourced or the default should match the recommendation

That’s a good point. I left InvokeModel as the default because it was previously the only supported approach, and I didn’t want users to be upgraded to Mantle unintentionally.

I’ll give it some more thought when I add the documentation.

@evgeniy-scherbina
evgeniy-scherbina merged commit 9862f10 into main Jul 16, 2026
26 of 27 checks passed
@evgeniy-scherbina
evgeniy-scherbina deleted the yevhenii/add-bedrock-provider-ui branch July 16, 2026 13:37
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants