Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.31.0"
".": "1.32.0"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.32.0 (2026-04-07)

Full Changelog: [v1.31.0...v1.32.0](https://github.com/anthropics/anthropic-sdk-go/compare/v1.31.0...v1.32.0)

### Features

* **bedrock:** add AnthropicBedrockMantle client ([#704](https://github.com/anthropics/anthropic-sdk-go/issues/704)) ([058e8fa](https://github.com/anthropics/anthropic-sdk-go/commit/058e8fa51bcdaf3eaa8d9c4dfb51606647eb6fae))

## 1.31.0 (2026-04-07)

Full Changelog: [v1.30.0...v1.31.0](https://github.com/anthropics/anthropic-sdk-go/compare/v1.30.0...v1.31.0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Or explicitly add the dependency:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/anthropics/anthropic-sdk-go@v1.31.0'
go get -u 'github.com/anthropics/anthropic-sdk-go@v1.32.0'
```

<!-- x-release-please-end -->
Expand Down
126 changes: 126 additions & 0 deletions bedrock/bedrockmantle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package bedrock

import (
"context"
"fmt"

"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/internal/awsauth"
"github.com/anthropics/anthropic-sdk-go/option"
)

const mantleServiceName = "bedrock-mantle"

// MantleClientConfig holds the configuration for creating an Anthropic Bedrock Mantle client.
type MantleClientConfig struct {
// APIKey is the Anthropic API key for x-api-key authentication.
// Takes precedence over AWS credentials. When no AWS auth args are set, falls back
// to the AWS_BEARER_TOKEN_BEDROCK environment variable (then ANTHROPIC_AWS_API_KEY)
// before trying SigV4.
APIKey string

// AWSAccessKey is the AWS access key ID for SigV4 authentication.
// Must be paired with AWSSecretAccessKey. When unset, credentials are resolved
// via the default AWS credential chain (env vars, shared credentials file, IAM roles, etc.).
AWSAccessKey string

// AWSSecretAccessKey is the AWS secret access key for SigV4 authentication.
// When unset, credentials are resolved via the default AWS credential chain
// (env vars, shared credentials file, IAM roles, etc.).
AWSSecretAccessKey string

// AWSSessionToken is the optional AWS session token for temporary credentials.
// When unset, resolved via the default AWS credential chain if applicable.
AWSSessionToken string

// AWSProfile is the AWS named profile for credential resolution via the provider chain.
AWSProfile string

// AWSRegion is the AWS region for the base URL and SigV4 signing.
// Resolved by precedence: MantleClientConfig.AWSRegion > AWS_REGION env var.
AWSRegion string

// BaseURL overrides the default base URL.
// Resolved by precedence: MantleClientConfig.BaseURL > ANTHROPIC_BEDROCK_MANTLE_BASE_URL env >
// https://bedrock-mantle.{region}.api.aws/anthropic
BaseURL string

// SkipAuth skips Mantle-specific authentication (API key and SigV4).
// This is useful when a gateway or proxy handles authentication on your behalf.
// Note: when using [NewMantleClient], the base SDK may still send an X-Api-Key header
// if the ANTHROPIC_API_KEY environment variable is set.
SkipAuth bool
}

// MantleClient provides access to the Anthropic Bedrock Mantle API.
// Only the Messages API (/v1/messages) and its subpaths are supported.
type MantleClient struct {
Options []option.RequestOption
Messages anthropic.MessageService
Beta MantleBetaService
}

// MantleBetaService exposes only the beta resources supported by Bedrock Mantle.
type MantleBetaService struct {
Options []option.RequestOption
Messages anthropic.BetaMessageService
}

// NewMantleClient creates a new Bedrock Mantle client with the given configuration.
// Only the Messages API (/v1/messages) and its subpaths are supported on Bedrock Mantle.
//
// Any additional [option.RequestOption] values are applied after the client's
// internal options (base URL, auth, etc.), so they can be used to set custom
// headers, timeouts, middleware, and other request-level settings.
//
// Auth is resolved by precedence:
// 1. APIKey arg (x-api-key header)
// 2. AWSAccessKey + AWSSecretAccessKey args (SigV4)
// 3. AWSProfile arg (SigV4 via provider chain)
// 4. AWS_BEARER_TOKEN_BEDROCK env var, then ANTHROPIC_AWS_API_KEY (x-api-key header)
// 5. Default AWS credential chain (SigV4)
func NewMantleClient(ctx context.Context, cfg MantleClientConfig, opts ...option.RequestOption) (*MantleClient, error) {
baseOpts, err := awsauth.CreateClientOptions(ctx, mantleToInternalConfig(cfg), mantleResolveParams())
if err != nil {
return nil, err
}

// We intentionally do not call anthropic.DefaultClientOptions() here.
// The Mantle client resolves its own base URL, auth, and workspace ID — the
// base SDK defaults (ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL) do not apply.
//
// User-provided opts are appended last so they take highest precedence.
opts = append(baseOpts, opts...)

return &MantleClient{
Options: opts,
Messages: anthropic.NewMessageService(opts...),
Beta: MantleBetaService{
Options: opts,
Messages: anthropic.NewBetaMessageService(opts...),
},
}, nil
}

func mantleResolveParams() awsauth.ResolveParams {
return awsauth.ResolveParams{
EnvAPIKey: "AWS_BEARER_TOKEN_BEDROCK",
EnvAPIKeyFallback: "ANTHROPIC_AWS_API_KEY",
EnvBaseURL: "ANTHROPIC_BEDROCK_MANTLE_BASE_URL",
DeriveBaseURL: func(region string) string { return fmt.Sprintf("https://bedrock-mantle.%s.api.aws/anthropic", region) },
ServiceName: mantleServiceName,
}
}

func mantleToInternalConfig(cfg MantleClientConfig) awsauth.ClientConfig {
return awsauth.ClientConfig{
APIKey: cfg.APIKey,
AWSAccessKey: cfg.AWSAccessKey,
AWSSecretAccessKey: cfg.AWSSecretAccessKey,
AWSSessionToken: cfg.AWSSessionToken,
AWSProfile: cfg.AWSProfile,
AWSRegion: cfg.AWSRegion,
BaseURL: cfg.BaseURL,
SkipAuth: cfg.SkipAuth,
}
}
141 changes: 141 additions & 0 deletions bedrock/bedrockmantle_live_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package bedrock_test

import (
"context"
"os"
"testing"

"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/bedrock"
)

// Live integration tests for Bedrock Mantle. Skipped unless ANTHROPIC_LIVE=1.
//
// Required env vars vary by auth mode:
//
// API key mode: AWS_BEARER_TOKEN_BEDROCK (or ANTHROPIC_AWS_API_KEY),
// AWS_REGION
//
// SigV4 mode: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
//
// Run: ANTHROPIC_LIVE=1 go test ./bedrock/... -run TestLiveMantle -v

func skipUnlessLive(t *testing.T) {
t.Helper()
if os.Getenv("ANTHROPIC_LIVE") != "1" {
t.Skip("set ANTHROPIC_LIVE=1 to run live integration tests")
}
}

func requireEnv(t *testing.T, names ...string) {
t.Helper()
for _, name := range names {
if os.Getenv(name) == "" {
t.Fatalf("required env var %s is not set", name)
}
}
}

func liveModel() string {
if m := os.Getenv("ANTHROPIC_LIVE_MODEL"); m != "" {
return m
}
return "claude-sonnet-4-6"
}

func sendMantleMessage(t *testing.T, client *bedrock.MantleClient) {
t.Helper()

message, err := client.Messages.New(context.Background(), anthropic.MessageNewParams{
Model: liveModel(),
MaxTokens: 32,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Say exactly: hello")),
},
})
if err != nil {
t.Fatalf("Messages.New failed: %v", err)
}
if len(message.Content) == 0 {
t.Fatal("expected non-empty content in response")
}
t.Logf("response: %s", message.Content[0].Text)
}

func TestLiveMantleAPIKey(t *testing.T) {
skipUnlessLive(t)
requireEnv(t, "AWS_REGION")

// Need at least one of these for the API key
apiKey := os.Getenv("AWS_BEARER_TOKEN_BEDROCK")
if apiKey == "" {
apiKey = os.Getenv("ANTHROPIC_AWS_API_KEY")
}
if apiKey == "" {
t.Fatal("required env var AWS_BEARER_TOKEN_BEDROCK or ANTHROPIC_AWS_API_KEY is not set")
}

client, err := bedrock.NewMantleClient(context.Background(), bedrock.MantleClientConfig{
APIKey: apiKey,
})
if err != nil {
t.Fatalf("NewMantleClient failed: %v", err)
}

sendMantleMessage(t, client)
}

func TestLiveMantleSigV4ExplicitCreds(t *testing.T) {
skipUnlessLive(t)
requireEnv(t, "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION")

client, err := bedrock.NewMantleClient(context.Background(), bedrock.MantleClientConfig{
AWSAccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
AWSSecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
AWSSessionToken: os.Getenv("AWS_SESSION_TOKEN"),
})
if err != nil {
t.Fatalf("NewMantleClient failed: %v", err)
}

sendMantleMessage(t, client)
}

func TestLiveMantleSigV4DefaultChain(t *testing.T) {
skipUnlessLive(t)
requireEnv(t, "AWS_REGION")

// Clear all API key env vars so the default AWS credential chain is used
t.Setenv("AWS_BEARER_TOKEN_BEDROCK", "")
t.Setenv("ANTHROPIC_AWS_API_KEY", "")
t.Setenv("ANTHROPIC_API_KEY", "")

client, err := bedrock.NewMantleClient(context.Background(), bedrock.MantleClientConfig{})
if err != nil {
t.Fatalf("NewMantleClient failed (default AWS credential chain): %v", err)
}

sendMantleMessage(t, client)
}

func TestLiveMantleSigV4ProfileFromCredentialsFile(t *testing.T) {
skipUnlessLive(t)
requireEnv(t, "AWS_REGION", "AWS_PROFILE")

// Clear explicit creds and API keys so the SDK must resolve from ~/.aws/credentials
t.Setenv("AWS_BEARER_TOKEN_BEDROCK", "")
t.Setenv("ANTHROPIC_AWS_API_KEY", "")
t.Setenv("ANTHROPIC_API_KEY", "")
t.Setenv("AWS_ACCESS_KEY_ID", "")
t.Setenv("AWS_SECRET_ACCESS_KEY", "")
t.Setenv("AWS_SESSION_TOKEN", "")

client, err := bedrock.NewMantleClient(context.Background(), bedrock.MantleClientConfig{
AWSProfile: os.Getenv("AWS_PROFILE"),
})
if err != nil {
t.Fatalf("NewMantleClient failed (profile from credentials file): %v", err)
}

sendMantleMessage(t, client)
}
Loading
Loading