diff --git a/aibridge/config/config.go b/aibridge/config/config.go index b01282b0cc7..acc1192319f 100644 --- a/aibridge/config/config.go +++ b/aibridge/config/config.go @@ -3,6 +3,8 @@ package config import ( "time" + "golang.org/x/xerrors" + "github.com/coder/coder/v2/aibridge/keypool" ) @@ -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. @@ -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") + } + 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. diff --git a/aibridge/config/config_test.go b/aibridge/config/config_test.go new file mode 100644 index 00000000000..f2d80fbc7b2 --- /dev/null +++ b/aibridge/config/config_test.go @@ -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", + 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) + }) + } +} diff --git a/aibridge/intercept/messages/base.go b/aibridge/intercept/messages/base.go index 4eb8ef3a50e..0d6ed533b54 100644 --- a/aibridge/intercept/messages/base.go +++ b/aibridge/intercept/messages/base.go @@ -1,10 +1,14 @@ package messages import ( + "bytes" "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" + "io" "math" "net/http" "strconv" @@ -17,6 +21,7 @@ import ( "github.com/anthropics/anthropic-sdk-go/shared" "github.com/anthropics/anthropic-sdk-go/shared/constant" "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -64,6 +69,22 @@ var bedrockSupportedBetaFlags = map[string]bool{ "tool-examples-2025-10-29": true, } +// BedrockPRMUserAgent is Coder's AWS attribution marker for outbound +// Bedrock requests. +// +// It is appended to Bedrock User-Agent headers so AWS can recognize the +// traffic as Coder-associated Bedrock usage. +const BedrockPRMUserAgent = "sdk-ua-app-id/APN_1.1%2Fpc_cdfmjwn8i6u8l9fwz8h82e4w3%24" + +// bedrockMantleSigningService is the AWS SigV4 service name for mantle. +const bedrockMantleSigningService = "bedrock-mantle" + +func appendBedrockPRMUserAgent(req *http.Request) { + if ua := req.Header.Get("User-Agent"); ua != "" { + req.Header.Set("User-Agent", ua+" "+BedrockPRMUserAgent) + } +} + // BedrockRuntime carries everything a Bedrock-backed interception needs: the // static Bedrock config plus the AWS credentials provider. type BedrockRuntime struct { @@ -109,12 +130,29 @@ func (i *interceptionBase) CorrelatingToolCallID() *string { return i.reqPayload.correlatingToolCallID() } +// isBedrockMantle reports whether the interception targets the Bedrock mantle +// protocol. +func (i *interceptionBase) isBedrockMantle() bool { + return i.bedrock != nil && i.bedrock.Cfg.Protocol == aibconfig.BedrockProtocolMantle +} + +// isBedrockInvokeModel reports whether the interception targets the Bedrock +// InvokeModel protocol. +func (i *interceptionBase) isBedrockInvokeModel() bool { + return i.bedrock != nil && + (i.bedrock.Cfg.Protocol == "" || i.bedrock.Cfg.Protocol == aibconfig.BedrockProtocolInvokeModel) +} + func (i *interceptionBase) Model() string { if len(i.reqPayload) == 0 { return "coder-aibridge-unknown" } - if i.bedrock != nil { + // InvokeModel is the only protocol that remaps the model: it replaces the + // client's model with the operator-configured one. Every other case (mantle + // passthrough, non-Bedrock providers) returns the model the client sent in + // the body. + if i.isBedrockInvokeModel() { model := i.bedrock.Cfg.Model if i.isSmallFastModel() { model = i.bedrock.Cfg.SmallFastModel @@ -245,15 +283,25 @@ func (i *interceptionBase) newMessagesService(ctx context.Context, opts ...optio opts = append(opts, option.WithMiddleware(mw)) } - if i.bedrock != nil { + if i.isBedrockInvokeModel() { ctx, cancel := context.WithTimeout(ctx, time.Second*30) defer cancel() - bedrockOpts, err := i.withAWSBedrockOptions(ctx) + bedrockOpts, err := i.withBedrockInvokeModelOptions(ctx) + if err != nil { + return anthropic.MessageService{}, err + } + opts = append(opts, bedrockOpts...) + i.augmentRequestForBedrockInvokeModel() + } + + if i.isBedrockMantle() { + ctx, cancel := context.WithTimeout(ctx, time.Second*30) + defer cancel() + bedrockOpts, err := i.withBedrockMantleOptions(ctx) if err != nil { return anthropic.MessageService{}, err } opts = append(opts, bedrockOpts...) - i.augmentRequestForBedrock() } return anthropic.NewMessageService(opts...), nil @@ -267,23 +315,18 @@ func (i *interceptionBase) withBody() option.RequestOption { return option.WithRequestBody("application/json", []byte(i.reqPayload)) } -// withAWSBedrockOptions returns request options for authenticating with AWS Bedrock. +// withBedrockInvokeModelOptions returns request options for the AWS Bedrock +// InvokeModel protocol. // // Credentials come from i.bedrock.Creds. It is a shared credentials cache, so the per-request Retrieve() // below is served from that cache and does not re-resolve or re-assume on every request. -func (i *interceptionBase) withAWSBedrockOptions(ctx context.Context) ([]option.RequestOption, error) { +func (i *interceptionBase) withBedrockInvokeModelOptions(ctx context.Context) ([]option.RequestOption, error) { if i.bedrock == nil { return nil, xerrors.New("nil bedrock runtime") } cfg := i.bedrock.Cfg - if cfg.Region == "" && cfg.BaseURL == "" { - return nil, xerrors.New("region or base url required") - } - if cfg.Model == "" { - return nil, xerrors.New("model required") - } - if cfg.SmallFastModel == "" { - return nil, xerrors.New("small fast model required") + if err := cfg.Validate(); err != nil { + return nil, err } // Fail fast: ensure credentials can be resolved before signing. Served from @@ -300,9 +343,7 @@ func (i *interceptionBase) withAWSBedrockOptions(ctx context.Context) ([]option. var out []option.RequestOption out = append(out, option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { - if ua := req.Header.Get("User-Agent"); ua != "" { - req.Header.Set("User-Agent", ua+" sdk-ua-app-id/APN_1.1%2Fpc_cdfmjwn8i6u8l9fwz8h82e4w3%24") - } + appendBedrockPRMUserAgent(req) return next(req) })) out = append(out, bedrock.WithConfig(awsCfg)) @@ -315,11 +356,66 @@ func (i *interceptionBase) withAWSBedrockOptions(ctx context.Context) ([]option. return out, nil } -// augmentRequestForBedrock will change the model used for the request since AWS Bedrock doesn't support +// withBedrockMantleOptions returns request options for the AWS Bedrock mantle +// endpoint (bedrock-mantle.{region}.api.aws/anthropic/v1/messages). It speaks +// the native Messages wire format, so this middleware only SigV4-signs the +// request (service "bedrock-mantle") and forwards it; the response is plain +// SSE. +func (i *interceptionBase) withBedrockMantleOptions(ctx context.Context) ([]option.RequestOption, error) { + cfg := i.bedrock.Cfg + if err := cfg.Validate(); err != nil { + return nil, err + } + + // Fail fast: ensure credentials can be resolved before signing. Served from + // the shared cache on most requests (no network); on the cold or refresh + // path this performs the actual STS/IMDS call. + if _, err := i.bedrock.Creds.Retrieve(ctx); err != nil { + return nil, xerrors.Errorf("resolve AWS credentials: %w", err) + } + + signer := v4.NewSigner() + var out []option.RequestOption + out = append(out, option.WithBaseURL(cfg.BaseURL)) + // Appended last so it runs innermost (right before the HTTP send) and signs + // the request after all other headers are set. + out = append(out, option.WithMiddleware(func(req *http.Request, next option.MiddlewareNext) (*http.Response, error) { + appendBedrockPRMUserAgent(req) + + creds, err := i.bedrock.Creds.Retrieve(req.Context()) + if err != nil { + return nil, err + } + + // SigV4 requires a payload hash, so read the body to hash it and then + // restore it for the downstream HTTP client to send. + var body []byte + if req.Body != nil { + var err error + body, err = io.ReadAll(req.Body) + if err != nil { + return nil, err + } + _ = req.Body.Close() + req.Body = io.NopCloser(bytes.NewReader(body)) + req.ContentLength = int64(len(body)) + } + + hash := sha256.Sum256(body) + if err := signer.SignHTTP(req.Context(), creds, req, hex.EncodeToString(hash[:]), bedrockMantleSigningService, cfg.Region, time.Now()); err != nil { + return nil, err + } + return next(req) + })) + + return out, nil +} + +// augmentRequestForBedrockInvokeModel will change the model used for the request since AWS Bedrock doesn't support // Anthropics' model names. It also converts adaptive thinking to enabled with a budget for models that // don't support adaptive thinking natively, or enabled thinking to adaptive for models that only support // adaptive (Opus 4.7+). -func (i *interceptionBase) augmentRequestForBedrock() { +func (i *interceptionBase) augmentRequestForBedrockInvokeModel() { if i.bedrock == nil { return } diff --git a/aibridge/intercept/messages/base_internal_test.go b/aibridge/intercept/messages/base_internal_test.go index 714f32b2c0f..eef99ea86e1 100644 --- a/aibridge/intercept/messages/base_internal_test.go +++ b/aibridge/intercept/messages/base_internal_test.go @@ -183,7 +183,7 @@ func TestAWSBedrockValidation(t *testing.T) { Creds: credentials.NewStaticCredentialsProvider("test-key", "test-secret", ""), }, } - opts, err := base.withAWSBedrockOptions(context.Background()) + opts, err := base.withBedrockInvokeModelOptions(context.Background()) if tt.expectError { require.Error(t, err) @@ -198,12 +198,12 @@ func TestAWSBedrockValidation(t *testing.T) { // TestAWSBedrockOptionsRequireRuntime verifies that option assembly fails when // the Bedrock runtime was not set. This should never happen in practice, since -// withAWSBedrockOptions is only called when i.bedrock != nil. +// withBedrockInvokeModelOptions is only called when i.bedrock != nil. func TestAWSBedrockOptionsRequireRuntime(t *testing.T) { t.Parallel() base := &interceptionBase{} - _, err := base.withAWSBedrockOptions(context.Background()) + _, err := base.withBedrockInvokeModelOptions(context.Background()) require.Error(t, err) require.Contains(t, err.Error(), "nil bedrock runtime") } @@ -800,7 +800,7 @@ func TestAugmentRequestForBedrock_AdaptiveThinking(t *testing.T) { logger: slog.Make(), } - i.augmentRequestForBedrock() + i.augmentRequestForBedrockInvokeModel() thinkingType := gjson.GetBytes(i.reqPayload, "thinking.type") if tc.expectThinkingType == "" { @@ -1126,3 +1126,75 @@ func TestWriteUpstreamError(t *testing.T) { }) } } + +// TestBedrockMantleIsPassthrough verifies a mantle provider reports the mantle +// protocol and that Model() returns the client's model. +func TestBedrockMantleIsPassthrough(t *testing.T) { + t.Parallel() + + i := &interceptionBase{ + reqPayload: mustMessagesPayload(t, + `{"model":"anthropic.claude-opus-4-8","max_tokens":10000,"thinking":{"type":"adaptive"},"metadata":{"user_id":"u123"},"context_management":{"type":"auto"}}`), + bedrock: &BedrockRuntime{ + Cfg: config.AWSBedrock{ + Region: "us-east-1", + Protocol: config.BedrockProtocolMantle, + }, + }, + logger: slog.Make(), + } + + require.True(t, i.isBedrockMantle()) + require.False(t, i.isBedrockInvokeModel()) + require.Equal(t, "anthropic.claude-opus-4-8", i.Model()) +} + +// TestAWSMantleOptionsValidation verifies the mantle protocol requires a +// region (it scopes the SigV4 signature) but NOT model fields. +func TestAWSMantleOptionsValidation(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfg config.AWSBedrock + errorMsg string + }{ + { + name: "valid without model fields", + cfg: config.AWSBedrock{ + Region: "us-east-1", + BaseURL: "https://bedrock-mantle.us-east-1.api.aws/anthropic", + Protocol: config.BedrockProtocolMantle, + }, + }, + { + name: "missing region even with base url", + cfg: config.AWSBedrock{ + BaseURL: "https://proxy.internal", + Protocol: config.BedrockProtocolMantle, + }, + errorMsg: "region required", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + base := &interceptionBase{ + bedrock: &BedrockRuntime{ + Cfg: tt.cfg, + Creds: credentials.NewStaticCredentialsProvider("test-key", "test-secret", ""), + }, + } + opts, err := base.withBedrockMantleOptions(context.Background()) + if tt.errorMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + } else { + require.NoError(t, err) + require.NotEmpty(t, opts) + } + }) + } +} diff --git a/aibridge/internal/integrationtest/bridge_internal_test.go b/aibridge/internal/integrationtest/bridge_internal_test.go index 024652928e7..1b515735da4 100644 --- a/aibridge/internal/integrationtest/bridge_internal_test.go +++ b/aibridge/internal/integrationtest/bridge_internal_test.go @@ -36,6 +36,7 @@ import ( "github.com/coder/coder/v2/aibridge/config" "github.com/coder/coder/v2/aibridge/fixtures" "github.com/coder/coder/v2/aibridge/intercept" + "github.com/coder/coder/v2/aibridge/intercept/messages" "github.com/coder/coder/v2/aibridge/internal/testutil" "github.com/coder/coder/v2/aibridge/mcp" "github.com/coder/coder/v2/aibridge/provider" @@ -377,7 +378,7 @@ func TestAWSBedrockIntegration(t *testing.T) { // Verify PRM attribution is appended to the User-Agent header. ua := received[0].Header.Get("User-Agent") - require.Contains(t, ua, "sdk-ua-app-id/APN_1.1%2Fpc_cdfmjwn8i6u8l9fwz8h82e4w3%24", + require.Contains(t, ua, messages.BedrockPRMUserAgent, "expected AWS PRM attribution in User-Agent header") interceptions := bridgeServer.Recorder.RecordedInterceptions() @@ -388,6 +389,62 @@ func TestAWSBedrockIntegration(t *testing.T) { } }) + // The mantle protocol is a passthrough: the client's model is forwarded in the body + // without remapping, only SigV4 signing (service "bedrock-mantle") is applied. + t.Run("mantle/v1/messages", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(t.Context(), testutil.WaitLong) + t.Cleanup(cancel) + + fix := fixtures.Parse(t, fixtures.AntSingleBuiltinTool) + upstream := testutil.NewMockUpstream(ctx, t, testutil.NewFixtureResponse(fix)) + + // Mantle needs only region + credentials for signing (no Model fields: + // the client supplies the model). + bedrockCfg := &config.AWSBedrock{ + Region: "us-west-2", + AccessKey: "test-access-key", + AccessKeySecret: "test-secret-key", + BaseURL: upstream.URL + "/anthropic", // Use the mock server. + Protocol: config.BedrockProtocolMantle, + } + // The client's model must be forwarded unchanged. + wantModel := gjson.GetBytes(fix.Request(), "model").String() + require.NotEmpty(t, wantModel) + + bridgeServer := newBridgeTestServer(ctx, t, upstream.URL, + withCustomProvider(aibridgetest.NewAnthropicProvider(t, anthropicCfg(upstream.URL, apiKey), bedrockCfg)), + ) + + resp, err := bridgeServer.makeRequest(t, http.MethodPost, pathAnthropicMessages, fix.Request()) + require.NoError(t, err) + defer resp.Body.Close() + + received := upstream.ReceivedRequests() + require.Len(t, received, 1) + + // Native passthrough: /anthropic Messages path, model kept in the body + // unchanged. + require.Equal(t, "/anthropic/v1/messages", received[0].Path) + require.Equal(t, wantModel, gjson.GetBytes(received[0].Body, "model").String(), + "model should be forwarded unchanged") + + // SigV4-signed for the bedrock-mantle service. + authHeader := received[0].Header.Get("Authorization") + require.True(t, strings.HasPrefix(authHeader, "AWS4-HMAC-SHA256"), "missing SigV4 auth: %q", authHeader) + require.Contains(t, authHeader, "/bedrock-mantle/aws4_request", + "signature must be scoped to the bedrock-mantle service") + + require.Contains(t, received[0].Header.Get("User-Agent"), + messages.BedrockPRMUserAgent) + + interceptions := bridgeServer.Recorder.RecordedInterceptions() + require.Len(t, interceptions, 1) + require.Equal(t, wantModel, interceptions[0].Model) + bridgeServer.Recorder.VerifyAllInterceptionsEnded(t) + }) + // Tests that Bedrock-incompatible fields are stripped and adaptive thinking // is handled correctly per model. Different Bedrock model names trigger // different behavior for beta flag filtering and field stripping. diff --git a/cli/aibridged.go b/cli/aibridged.go index 0fd2ae598dc..75ded313c97 100644 --- a/cli/aibridged.go +++ b/cli/aibridged.go @@ -214,6 +214,7 @@ func protoToProviderSpec(pp *proto.AIProvider) aiProviderSpec { ) bedrock.RoleARN = b.GetRoleArn() bedrock.ExternalID = b.GetExternalId() + bedrock.Protocol = codersdk.AIProviderBedrockProtocol(b.GetProtocol()) spec.Bedrock = ptr.Ref(bedrock) } return spec @@ -356,6 +357,7 @@ func bedrockConfig(baseURL string, bedrock *codersdk.AIProviderBedrockSettings) SmallFastModel: bedrockSettings.SmallFastModel, RoleARN: bedrockSettings.RoleARN, ExternalID: bedrockSettings.ExternalID, + Protocol: config.BedrockProtocol(bedrockSettings.ResolvedProtocol()), } } diff --git a/coderd/aibridged/proto/aibridged.pb.go b/coderd/aibridged/proto/aibridged.pb.go index fe68d6ee205..5964870f426 100644 --- a/coderd/aibridged/proto/aibridged.pb.go +++ b/coderd/aibridged/proto/aibridged.pb.go @@ -1680,6 +1680,9 @@ type AIProviderKindBedrock struct { SmallFastModel string `protobuf:"bytes,5,opt,name=small_fast_model,json=smallFastModel,proto3" json:"small_fast_model,omitempty"` RoleArn string `protobuf:"bytes,6,opt,name=role_arn,json=roleArn,proto3" json:"role_arn,omitempty"` ExternalId string `protobuf:"bytes,7,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` + // protocol selects the Bedrock wire protocol ("invoke-model" or "mantle"). + // Empty fallbacks to invoke-model. + Protocol string `protobuf:"bytes,8,opt,name=protocol,proto3" json:"protocol,omitempty"` } func (x *AIProviderKindBedrock) Reset() { @@ -1763,6 +1766,13 @@ func (x *AIProviderKindBedrock) GetExternalId() string { return "" } +func (x *AIProviderKindBedrock) GetProtocol() string { + if x != nil { + return x.Protocol + } + return "" +} + var File_coderd_aibridged_proto_aibridged_proto protoreflect.FileDescriptor var file_coderd_aibridged_proto_aibridged_proto_rawDesc = []byte{ @@ -2063,7 +2073,7 @@ var file_coderd_aibridged_proto_aibridged_proto_rawDesc = []byte{ 0x72, 0x6f, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x42, 0x65, 0x64, 0x72, 0x6f, 0x63, 0x6b, 0x52, 0x07, 0x62, 0x65, 0x64, 0x72, 0x6f, 0x63, - 0x6b, 0x22, 0xf6, 0x01, 0x0a, 0x15, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x6b, 0x22, 0x92, 0x02, 0x0a, 0x15, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x4b, 0x69, 0x6e, 0x64, 0x42, 0x65, 0x64, 0x72, 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6b, 0x65, @@ -2078,83 +2088,85 @@ var file_coderd_aibridged_proto_aibridged_proto_rawDesc = []byte{ 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x61, 0x72, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x41, 0x72, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x32, 0xa9, 0x04, 0x0a, 0x08, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x59, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x68, 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x25, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x56, 0x0a, 0x11, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, - 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, - 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x12, 0x52, - 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x68, 0x6f, 0x75, 0x67, 0x68, - 0x74, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xeb, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x13, 0x47, 0x65, - 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x73, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, - 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, - 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, - 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xaa, 0x01, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, - 0x49, 0x73, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, - 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x42, 0x75, 0x64, 0x67, 0x65, - 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x42, 0x75, 0x64, 0x67, 0x65, - 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x32, 0xbc, 0x01, 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x4d, 0x0a, 0x0e, 0x47, 0x65, - 0x74, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x57, 0x61, 0x74, - 0x63, 0x68, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x49, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x49, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, - 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, - 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x32, 0xa9, 0x04, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x59, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, + 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, + 0x0a, 0x17, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, + 0x11, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, + 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x6f, 0x6f, 0x6c, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x12, 0x20, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d, 0x6f, 0x64, 0x65, + 0x6c, 0x54, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x4d, 0x6f, + 0x64, 0x65, 0x6c, 0x54, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x32, 0xeb, 0x01, 0x0a, 0x0f, 0x4d, 0x43, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x5c, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x21, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7a, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, + 0x74, 0x4d, 0x43, 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x43, + 0x50, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0xaa, 0x01, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x12, + 0x47, 0x0a, 0x0c, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x12, + 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x49, 0x73, 0x42, 0x75, + 0x64, 0x67, 0x65, 0x74, 0x45, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x12, 0x1e, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x45, 0x78, 0x63, + 0x65, 0x65, 0x64, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x73, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x45, 0x78, 0x63, + 0x65, 0x65, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbc, 0x01, + 0x0a, 0x14, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x4d, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x49, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, + 0x65, 0x74, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x49, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x49, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x64, + 0x2f, 0x61, 0x69, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/coderd/aibridged/proto/aibridged.proto b/coderd/aibridged/proto/aibridged.proto index 41795abdb9a..49fb453d6ff 100644 --- a/coderd/aibridged/proto/aibridged.proto +++ b/coderd/aibridged/proto/aibridged.proto @@ -235,4 +235,7 @@ message AIProviderKindBedrock { string small_fast_model = 5; string role_arn = 6; string external_id = 7; + // protocol selects the Bedrock wire protocol ("invoke-model" or "mantle"). + // Empty fallbacks to invoke-model. + string protocol = 8; } diff --git a/coderd/aibridgedserver/aibridgedserver.go b/coderd/aibridgedserver/aibridgedserver.go index cdfe739d027..e2750907485 100644 --- a/coderd/aibridgedserver/aibridgedserver.go +++ b/coderd/aibridgedserver/aibridgedserver.go @@ -1109,6 +1109,7 @@ func aiProviderToProto(row database.AIProvider, keys []database.AIProviderKey) ( SmallFastModel: settings.Bedrock.SmallFastModel, RoleArn: settings.Bedrock.RoleARN, ExternalId: settings.Bedrock.ExternalID, + Protocol: string(settings.Bedrock.Protocol), } } diff --git a/codersdk/aiproviders.go b/codersdk/aiproviders.go index 8956587b0b0..3ceb04589cf 100644 --- a/codersdk/aiproviders.go +++ b/codersdk/aiproviders.go @@ -283,6 +283,13 @@ func (req CreateAIProviderRequest) Validate() []ValidationError { Detail: "external_id is server-generated and cannot be set", }) } + // The Mantle protocol signs requests with SigV4, which requires a region. + if req.Settings.Bedrock.ResolvedProtocol() == AIProviderBedrockProtocolMantle && req.Settings.Bedrock.Region == "" { + validations = append(validations, ValidationError{ + Field: "settings.region", + Detail: "region is required for the mantle protocol", + }) + } } if req.Type == AIProviderTypeCopilot && len(req.APIKeys) > 0 { validations = append(validations, ValidationError{ diff --git a/codersdk/aiproviders_bedrock.go b/codersdk/aiproviders_bedrock.go index be17f6c95d0..688e18b4710 100644 --- a/codersdk/aiproviders_bedrock.go +++ b/codersdk/aiproviders_bedrock.go @@ -8,6 +8,23 @@ const AIProviderSettingsTypeBedrock = "bedrock" // AIProviderBedrockSettings. const AIProviderBedrockSettingsVersion = 1 +// AIProviderBedrockProtocol selects which AWS Bedrock wire protocol a provider +// targets. +type AIProviderBedrockProtocol string + +const ( + // AIProviderBedrockProtocolInvokeModel 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. + AIProviderBedrockProtocolInvokeModel AIProviderBedrockProtocol = "invoke-model" + // AIProviderBedrockProtocolMantle 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). + AIProviderBedrockProtocolMantle AIProviderBedrockProtocol = "mantle" +) + // AIProviderBedrockSettings configures providers that authenticate // against AWS Bedrock. AccessKey and AccessKeySecret are write-only: // servers strip them from GET and list responses. Both secret fields @@ -40,6 +57,19 @@ type AIProviderBedrockSettings struct { // reject any client-supplied value that differs from the stored one (an // update may echo the stored value back). ExternalID string `json:"external_id,omitempty"` + // Protocol selects the Bedrock wire protocol. An empty value resolves to + // AIProviderBedrockProtocolInvokeModel, so existing rows keep the legacy + // behavior. + Protocol AIProviderBedrockProtocol `json:"protocol,omitempty"` +} + +// ResolvedProtocol returns the configured protocol, mapping the empty value to +// the legacy InvokeModel protocol. +func (b AIProviderBedrockSettings) ResolvedProtocol() AIProviderBedrockProtocol { + if b.Protocol == "" { + return AIProviderBedrockProtocolInvokeModel + } + return b.Protocol } // IsConfigured reports whether any load-bearing Bedrock field is set, diff --git a/codersdk/aiproviders_test.go b/codersdk/aiproviders_test.go index a0ce28ec4ea..5fd1dd238ca 100644 --- a/codersdk/aiproviders_test.go +++ b/codersdk/aiproviders_test.go @@ -212,3 +212,47 @@ func TestAIProviderRequest_ValidateRoleARN(t *testing.T) { }) } } + +func TestAIProviderRequest_ValidateBedrockMantle(t *testing.T) { + t.Parallel() + + hasFieldError := func(vs []codersdk.ValidationError, field string) bool { + for _, v := range vs { + if v.Field == field { + return true + } + } + return false + } + + t.Run("MantleRequiresRegion", func(t *testing.T) { + t.Parallel() + create := codersdk.CreateAIProviderRequest{ + Type: codersdk.AIProviderTypeBedrock, + Name: "bedrock", + BaseURL: "https://bedrock-mantle.us-east-1.api.aws", + Settings: codersdk.AIProviderSettings{ + Bedrock: &codersdk.AIProviderBedrockSettings{ + Protocol: codersdk.AIProviderBedrockProtocolMantle, + }, + }, + } + require.True(t, hasFieldError(create.Validate(), "settings.region")) + + create.Settings.Bedrock.Region = "us-east-1" + require.False(t, hasFieldError(create.Validate(), "settings.region")) + }) + + t.Run("InvokeModelDoesNotRequireRegionField", func(t *testing.T) { + t.Parallel() + // The default (invoke-model) protocol keeps its existing rules; the + // mantle-specific region check must not fire for it. + create := codersdk.CreateAIProviderRequest{ + Type: codersdk.AIProviderTypeBedrock, + Name: "bedrock", + BaseURL: "https://bedrock-runtime.us-east-1.amazonaws.com", + Settings: codersdk.AIProviderSettings{}, + } + require.False(t, hasFieldError(create.Validate(), "settings.region")) + }) +} diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7095d7de7ab..55a67b560f0 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -313,6 +313,14 @@ export interface AIProvider { readonly updated_at: string; } +// From codersdk/aiproviders_bedrock.go +export type AIProviderBedrockProtocol = "invoke-model" | "mantle"; + +export const AIProviderBedrockProtocols: AIProviderBedrockProtocol[] = [ + "invoke-model", + "mantle", +]; + // From codersdk/aiproviders_bedrock.go /** * AIProviderBedrockSettings configures providers that authenticate @@ -362,6 +370,12 @@ export interface AIProviderBedrockSettings { * update may echo the stored value back). */ readonly external_id?: string; + /** + * Protocol selects the Bedrock wire protocol. An empty value resolves to + * AIProviderBedrockProtocolInvokeModel, so existing rows keep the legacy + * behavior without a schema version bump. + */ + readonly protocol?: AIProviderBedrockProtocol; } // From codersdk/aiproviders_bedrock.go