Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f953c8c
feat: add AWS Bedrock mantle endpoint to AI Gateway
evgeniy-scherbina Jul 7, 2026
bc6ba36
chore(aibridge): defer Bedrock mantle UI and docs to follow-up
evgeniy-scherbina Jul 9, 2026
28bfd2a
Merge remote-tracking branch 'origin/main' into yevhenii/add-bedrock-…
evgeniy-scherbina Jul 9, 2026
989e298
ci: make fmt
evgeniy-scherbina Jul 10, 2026
d20f41e
ci: make gen
evgeniy-scherbina Jul 13, 2026
6cd6d58
fix(codersdk): reject unknown bedrock protocol in provider validation
evgeniy-scherbina Jul 13, 2026
00e8b76
fix(codersdk): enforce mantle region requirement on provider update
evgeniy-scherbina Jul 13, 2026
7419630
fix(aibridge): guard nil bedrock runtime in mantle options
evgeniy-scherbina Jul 13, 2026
4db66ee
test(aibridge): assert mantle path leaves request body untouched
evgeniy-scherbina Jul 13, 2026
0db20ff
test(codersdk): make invoke-model region test enter the validation br…
evgeniy-scherbina Jul 13, 2026
d941a12
fix(aibridge): wrap bedrock config validation errors with protocol co…
evgeniy-scherbina Jul 13, 2026
be4c152
fix(aibridge): add diagnostic context to bedrock signing errors
evgeniy-scherbina Jul 13, 2026
3912b98
CR's fixes
evgeniy-scherbina Jul 13, 2026
e46723d
CR's fixes
evgeniy-scherbina Jul 13, 2026
f1bc953
refactor(aibridge): name the bedrock credential resolution timeout
evgeniy-scherbina Jul 13, 2026
5b0ebfb
docs(aibridge): expand PRM acronym in BedrockPRMUserAgent comment
evgeniy-scherbina Jul 13, 2026
7a807a3
Merge remote-tracking branch 'origin/main' into yevhenii/add-bedrock-…
evgeniy-scherbina Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions aibridge/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package config
import (
"time"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/aibridge/keypool"
)

Expand All @@ -25,13 +27,34 @@ type Anthropic struct {
SendActorHeaders bool
}

// BedrockProtocol selects which AWS Bedrock wire protocol a provider targets.
type BedrockProtocol string

const (
// BedrockProtocolInvokeModel is the legacy InvokeModel protocol
// (bedrock-runtime.{region}.amazonaws.com), which translates the native
// Messages request into Bedrock's InvokeModel format. It is the default
// for the zero value.
BedrockProtocolInvokeModel BedrockProtocol = "invoke-model"
// BedrockProtocolMantle is the mantle protocol
// (bedrock-mantle.{region}.api.aws/anthropic/v1/messages). It is a
// passthrough: the gateway forwards the native Messages request body
// unchanged and only applies AWS SigV4 signing (service bedrock-mantle).
BedrockProtocolMantle BedrockProtocol = "mantle"
)

type AWSBedrock struct {
Region string
AccessKey, AccessKeySecret string
Model, SmallFastModel string
// If set, requests will be sent to this URL instead of the default AWS Bedrock endpoint
// (https://bedrock-runtime.{region}.amazonaws.com).
// This is useful for routing requests through a proxy or for testing.
// BaseURL configures the upstream Bedrock endpoint.
//
// For InvokeModel, it is optional. When empty, requests use the default
// https://bedrock-runtime.{region}.amazonaws.com endpoint. Set it to route
// InvokeModel requests through a proxy or test server.
//
// For mantle, it is required and must be the Messages API prefix without
// /v1/messages, e.g. https://bedrock-mantle.{region}.api.aws/anthropic.
BaseURL string
// RoleARN, when set, is assumed via STS before calling Bedrock. The base
// identity (static keys or the AWS SDK default credential chain, e.g.
Expand All @@ -42,6 +65,35 @@ type AWSBedrock struct {
// It is meaningful only alongside RoleARN and must match the
// sts:ExternalId condition on the target role's trust policy.
ExternalID string
// Protocol selects the Bedrock wire protocol. The zero value behaves as
// BedrockProtocolInvokeModel.
Protocol BedrockProtocol
}

// Validate verifies protocol-specific Bedrock configuration.
func (c AWSBedrock) Validate() error {
switch c.Protocol {
case "", BedrockProtocolInvokeModel:
if c.Region == "" && c.BaseURL == "" {
return xerrors.New("region or base url required")

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-27] The same Validate() function names one field two ways: the InvokeModel branch returns "region or base url required" (space, prose) and the new mantle branch returns "base_url required" (underscore, field form). Only base_url matches what the operator types in settings. The base url string is pre-existing, but the mantle branch is new and sits a few lines away, so aligning them is this PR's to make. Suggest "region or base_url required" so both point at the field the operator edits.

(Leorio)

🤖

}
if c.Model == "" {
return xerrors.New("model required")
}
if c.SmallFastModel == "" {
return xerrors.New("small fast model required")
}
case BedrockProtocolMantle:
if c.Region == "" {
return xerrors.New("region required")
}
if c.BaseURL == "" {
return xerrors.New("base_url required")
}
default:
return xerrors.Errorf("unknown bedrock protocol: %q", c.Protocol)
}
return nil
}

// OpenAI carries configuration for an OpenAI provider.
Expand Down
113 changes: 113 additions & 0 deletions aibridge/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package config_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/aibridge/config"
)

func TestAWSBedrockValidate(t *testing.T) {
t.Parallel()

tests := []struct {
name string
cfg config.AWSBedrock
errorMsg string
}{
{
name: "invoke model valid",
cfg: config.AWSBedrock{
Region: "us-east-1",
Model: "anthropic.claude-sonnet",
SmallFastModel: "anthropic.claude-haiku",
},
},
{
name: "invoke model valid with base url instead of region",
cfg: config.AWSBedrock{
BaseURL: "https://bedrock-runtime.example.com",
Model: "anthropic.claude-sonnet",
SmallFastModel: "anthropic.claude-haiku",
},
},
{
name: "invoke model missing region and base url",
cfg: config.AWSBedrock{
Model: "anthropic.claude-sonnet",
SmallFastModel: "anthropic.claude-haiku",
},
errorMsg: "region or base url required",
},
{
name: "invoke model missing model",
cfg: config.AWSBedrock{
Region: "us-east-1",
SmallFastModel: "anthropic.claude-haiku",
},
errorMsg: "model required",
},
{
name: "invoke model missing small fast model",
cfg: config.AWSBedrock{
Region: "us-east-1",
Model: "anthropic.claude-sonnet",
},
errorMsg: "small fast model required",
},
{
name: "unknown protocol rejected",
cfg: config.AWSBedrock{
Protocol: config.BedrockProtocol("unknown"),
},
errorMsg: "unknown bedrock protocol",
},
{
name: "mantle valid official api prefix",
cfg: config.AWSBedrock{
Region: "us-east-1",
BaseURL: "https://bedrock-mantle.us-east-1.api.aws/anthropic",
Protocol: config.BedrockProtocolMantle,
},
},
{
name: "mantle valid proxy api prefix",

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-26] The two "mantle valid" rows (official api prefix at L67, proxy api prefix at L74) both land in case BedrockProtocolMantle, which only checks Region != "" and BaseURL != "". The BaseURL value never changes the branch, so the second row adds no coverage, and the official/proxy names imply the validator distinguishes an official bedrock-mantle.*.api.aws prefix from an arbitrary proxy URL. It does not. A reader who later adds prefix validation will believe it is already tested. Collapse to one row, or rename both to describe what is actually asserted (any non-empty base URL passes).

(Bisky)

🤖

cfg: config.AWSBedrock{
Region: "us-east-1",
BaseURL: "https://proxy.internal/proxy",
Protocol: config.BedrockProtocolMantle,
},
},
{
name: "mantle missing region",
cfg: config.AWSBedrock{
BaseURL: "https://bedrock-mantle.us-east-1.api.aws",
Protocol: config.BedrockProtocolMantle,
},
errorMsg: "region required",
},
{
name: "mantle missing base url",
cfg: config.AWSBedrock{
Region: "us-east-1",
Protocol: config.BedrockProtocolMantle,
},
errorMsg: "base_url required",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := tt.cfg.Validate()
if tt.errorMsg != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errorMsg)
return
}
require.NoError(t, err)
})
}
}
Loading
Loading