Detailed changes
@@ -6,6 +6,7 @@ require (
cloud.google.com/go/auth v0.17.0
github.com/anthropics/anthropic-sdk-go v1.10.0
github.com/aws/aws-sdk-go-v2 v1.39.3
+ github.com/aws/smithy-go v1.23.1
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5
github.com/charmbracelet/x/json v0.2.0
github.com/go-viper/mapstructure/v2 v2.4.0
@@ -37,7 +38,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
- github.com/aws/smithy-go v1.23.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
@@ -73,7 +73,7 @@ require (
// NOTE(@andreynering): Temporarily pinning @fantasy branch with fixes:
// https://github.com/charmbracelet/anthropic-sdk-go/commits/fantasy/
-replace github.com/anthropics/anthropic-sdk-go => github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251010172108-7b952cdeeb9d
+replace github.com/anthropics/anthropic-sdk-go => github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251020200929-fdb68318b7af
// NOTE(@andreynering): Temporarily pinning @fantasy branch with fixes:
// https://github.com/charmbracelet/go-genai/commits/fantasy/
@@ -42,8 +42,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudr
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
-github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251010172108-7b952cdeeb9d h1:qP7F7r7aVY7AReYHHgkQ79weuUEZK7zXtDtSEydYb0w=
-github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251010172108-7b952cdeeb9d/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
+github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251020200929-fdb68318b7af h1:iPwFVe5v46OfhqxKXSJ4J0YWf8XzthTnWyrim2yGFnU=
+github.com/charmbracelet/anthropic-sdk-go v0.0.0-20251020200929-fdb68318b7af/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
github.com/charmbracelet/go-genai v0.0.0-20251009191514-c6fa9e37d847 h1:Oyo6YZ59iygXWNUlRozIOFHO4WUG9cNFhiUYCTq4AnU=
github.com/charmbracelet/go-genai v0.0.0-20251009191514-c6fa9e37d847/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg=
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 h1:DTSZxdV9qQagD4iGcAt9RgaRBZtJl01bfKgdLzUzUPI=
@@ -122,7 +122,7 @@ func WithHTTPClient(client option.HTTPClient) Option {
func (a *provider) LanguageModel(modelID string) (fantasy.LanguageModel, error) {
clientOptions := make([]option.RequestOption, 0, 5+len(a.options.headers))
- if a.options.apiKey != "" {
+ if a.options.apiKey != "" && !a.options.useBedrock {
clientOptions = append(clientOptions, option.WithAPIKey(a.options.apiKey))
}
if a.options.baseURL != "" {
@@ -157,10 +157,10 @@ func (a *provider) LanguageModel(modelID string) (fantasy.LanguageModel, error)
)
}
if a.options.useBedrock {
- if a.options.skipAuth {
+ if a.options.skipAuth || a.options.apiKey != "" {
clientOptions = append(
clientOptions,
- bedrock.WithConfig(dummyBedrockConfig),
+ bedrock.WithConfig(bedrockBasicAuthConfig(a.options.apiKey)),
)
} else {
clientOptions = append(
@@ -1,14 +1,16 @@
package anthropic
import (
- "context"
+ "cmp"
+ "os"
"github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/smithy-go/auth/bearer"
)
-var dummyBedrockConfig = aws.Config{
- Region: "us-east-1",
- Credentials: aws.CredentialsProviderFunc(func(context.Context) (aws.Credentials, error) {
- return aws.Credentials{}, nil
- }),
+func bedrockBasicAuthConfig(apiKey string) aws.Config {
+ return aws.Config{
+ Region: cmp.Or(os.Getenv("AWS_REGION"), "us-east-1"),
+ BearerAuthTokenProvider: bearer.StaticTokenProvider{Token: bearer.Token{Value: apiKey}},
+ }
}
@@ -36,6 +36,13 @@ func New(opts ...Option) fantasy.Provider {
)
}
+// WithAPIKey sets the access token for the Bedrock provider.
+func WithAPIKey(apiKey string) Option {
+ return func(o *options) {
+ o.anthropicOptions = append(o.anthropicOptions, anthropic.WithAPIKey(apiKey))
+ }
+}
+
// WithHeaders sets the headers for the Bedrock provider.
func WithHeaders(headers map[string]string) Option {
return func(o *options) {
@@ -1,6 +1,7 @@
FANTASY_ANTHROPIC_API_KEY=
FANTASY_AZURE_API_KEY=
FANTASY_AZURE_BASE_URL=
+FANTASY_BEDROCK_API_KEY=
FANTASY_GEMINI_API_KEY=
FANTASY_GROQ_API_KEY=
FANTASY_OPENAI_API_KEY=
@@ -2,6 +2,7 @@ package providertests
import (
"net/http"
+ "os"
"testing"
"charm.land/fantasy"
@@ -17,6 +18,10 @@ func TestBedrockCommon(t *testing.T) {
})
}
+func TestBedrockBasicAuth(t *testing.T) {
+ testSimple(t, builderPair{"bedrock-anthropic-claude-3-sonnet", buildersBedrockBasicAuth, nil})
+}
+
func builderBedrockClaude3Sonnet(r *recorder.Recorder) (fantasy.LanguageModel, error) {
provider := bedrock.New(
bedrock.WithHTTPClient(&http.Client{Transport: r}),
@@ -40,3 +45,12 @@ func builderBedrockClaude3Haiku(r *recorder.Recorder) (fantasy.LanguageModel, er
)
return provider.LanguageModel("us.anthropic.claude-3-haiku-20240307-v1:0")
}
+
+func buildersBedrockBasicAuth(r *recorder.Recorder) (fantasy.LanguageModel, error) {
+ provider := bedrock.New(
+ bedrock.WithHTTPClient(&http.Client{Transport: r}),
+ bedrock.WithAPIKey(os.Getenv("FANTASY_BEDROCK_API_KEY")),
+ bedrock.WithSkipAuth(true),
+ )
+ return provider.LanguageModel("us.anthropic.claude-3-sonnet-20240229-v1:0")
+}
@@ -0,0 +1,32 @@
+---
+version: 2
+interactions:
+- id: 0
+ request:
+ proto: HTTP/1.1
+ proto_major: 1
+ proto_minor: 1
+ content_length: 211
+ host: ""
+ body: '{"max_tokens":4000,"messages":[{"content":[{"text":"Say hi in Portuguese","type":"text"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"anthropic_version":"bedrock-2023-05-31"}'
+ headers:
+ Accept:
+ - application/json
+ Content-Type:
+ - application/json
+ User-Agent:
+ - Anthropic/Go 1.14.0
+ url: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-3-sonnet-20240229-v1%3A0/invoke
+ method: POST
+ response:
+ proto: HTTP/2.0
+ proto_major: 2
+ proto_minor: 0
+ content_length: 248
+ body: '{"id":"msg_bdrk_01SLPR8DXPtQG4ryAsmuQrA9","type":"message","role":"assistant","model":"claude-3-sonnet-20240229","content":[{"type":"text","text":"Olรก!"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":16,"output_tokens":7}}'
+ headers:
+ Content-Type:
+ - application/json
+ status: 200 OK
+ code: 200
+ duration: 3.764905916s
@@ -0,0 +1,76 @@
+---
+version: 2
+interactions:
+- id: 0
+ request:
+ proto: HTTP/1.1
+ proto_major: 1
+ proto_minor: 1
+ content_length: 211
+ host: ""
+ body: '{"max_tokens":4000,"messages":[{"content":[{"text":"Say hi in Portuguese","type":"text"}],"role":"user"}],"system":[{"text":"You are a helpful assistant","type":"text"}],"anthropic_version":"bedrock-2023-05-31"}'
+ headers:
+ Accept:
+ - application/json
+ Content-Type:
+ - application/json
+ User-Agent:
+ - Anthropic/Go 1.14.0
+ url: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-3-sonnet-20240229-v1%3A0/invoke-with-response-stream
+ method: POST
+ response:
+ proto: HTTP/2.0
+ proto_major: 2
+ proto_minor: 0
+ content_length: -1
+ body: !!binary |
+ AAABvQAAAEunJ1hxCzpldmVudC10eXBlBwAFY2h1bmsNOmNvbnRlbnQtdHlwZQcAEGFwcG
+ xpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJieXRlcyI6ImV5SjBlWEJs
+ SWpvaWJXVnpjMkZuWlY5emRHRnlkQ0lzSW0xbGMzTmhaMlVpT25zaWFXUWlPaUp0YzJkZl
+ ltUnlhMTh3TVVjM1ZUWXlaMU4zYWxwU1ptMVVTMkpDWjNGQmExSWlMQ0owZVhCbElqb2li
+ V1Z6YzJGblpTSXNJbkp2YkdVaU9pSmhjM05wYzNSaGJuUWlMQ0p0YjJSbGJDSTZJbU5zWV
+ hWa1pTMHpMWE52Ym01bGRDMHlNREkwTURJeU9TSXNJbU52Ym5SbGJuUWlPbHRkTENKemRH
+ OXdYM0psWVhOdmJpSTZiblZzYkN3aWMzUnZjRjl6WlhGMVpXNWpaU0k2Ym5Wc2JDd2lkWE
+ 5oWjJVaU9uc2lhVzV3ZFhSZmRHOXJaVzV6SWpveE5pd2liM1YwY0hWMFgzUnZhMlZ1Y3lJ
+ Nk1uMTlmUT09IiwicCI6ImFiYyJ9vnwv4QAAAPgAAABL/Ghc7Qs6ZXZlbnQtdHlwZQcABW
+ NodW5rDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH
+ AAVldmVudHsiYnl0ZXMiOiJleUowZVhCbElqb2lZMjl1ZEdWdWRGOWliRzlqYTE5emRHRn
+ lkQ0lzSW1sdVpHVjRJam93TENKamIyNTBaVzUwWDJKc2IyTnJJanA3SW5SNWNHVWlPaUow
+ WlhoMElpd2lkR1Y0ZENJNklpSjlmUT09IiwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dX
+ Z3eHl6In2rrhIPAAAA+gAAAEuGqA+NCzpldmVudC10eXBlBwAFY2h1bmsNOmNvbnRlbnQt
+ dHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJieXRlcy
+ I6ImV5SjBlWEJsSWpvaVkyOXVkR1Z1ZEY5aWJHOWphMTlrWld4MFlTSXNJbWx1WkdWNElq
+ b3dMQ0prWld4MFlTSTZleUowZVhCbElqb2lkR1Y0ZEY5a1pXeDBZU0lzSW5SbGVIUWlPaU
+ pQYkNKOWZRPT0iLCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQiJ9FHa6UgAA
+ AQ0AAABLt+BQZQs6ZXZlbnQtdHlwZQcABWNodW5rDTpjb250ZW50LXR5cGUHABBhcHBsaW
+ NhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiYnl0ZXMiOiJleUowZVhCbElq
+ b2lZMjl1ZEdWdWRGOWliRzlqYTE5a1pXeDBZU0lzSW1sdVpHVjRJam93TENKa1pXeDBZU0
+ k2ZXlKMGVYQmxJam9pZEdWNGRGOWtaV3gwWVNJc0luUmxlSFFpT2lMRG9TSjlmUT09Iiwi
+ cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVIn
+ 3UdODmAAAA9AAAAEs5mLHsCzpldmVudC10eXBlBwAFY2h1bmsNOmNvbnRlbnQtdHlwZQcA
+ EGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJieXRlcyI6ImV5Sj
+ BlWEJsSWpvaVkyOXVkR1Z1ZEY5aWJHOWphMTlrWld4MFlTSXNJbWx1WkdWNElqb3dMQ0pr
+ Wld4MFlTSTZleUowZVhCbElqb2lkR1Y0ZEY5a1pXeDBZU0lzSW5SbGVIUWlPaUloSW4xOS
+ IsInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eiJ9l8ofGQAAANMAAABLSnlC+As6
+ ZXZlbnQtdHlwZQcABWNodW5rDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDT
+ ptZXNzYWdlLXR5cGUHAAVldmVudHsiYnl0ZXMiOiJleUowZVhCbElqb2lZMjl1ZEdWdWRG
+ OWliRzlqYTE5emRHOXdJaXdpYVc1a1pYZ2lPakI5IiwicCI6ImFiY2RlZmdoaWprbG1ub3
+ BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVlcifUu+MSwAAAEuAAAASzGBBbEL
+ OmV2ZW50LXR5cGUHAAVjaHVuaw06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg
+ 06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImJ5dGVzIjoiZXlKMGVYQmxJam9pYldWemMyRm5a
+ VjlrWld4MFlTSXNJbVJsYkhSaElqcDdJbk4wYjNCZmNtVmhjMjl1SWpvaVpXNWtYM1IxY2
+ 00aUxDSnpkRzl3WDNObGNYVmxibU5sSWpwdWRXeHNmU3dpZFhOaFoyVWlPbnNpYjNWMGNI
+ VjBYM1J2YTJWdWN5STZOMzE5IiwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QU
+ JDREVGR0hJSktMTU5PUFFSU1RVViJ9tMfCKgAAAVMAAABLMMMhzws6ZXZlbnQtdHlwZQcA
+ BWNodW5rDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cG
+ UHAAVldmVudHsiYnl0ZXMiOiJleUowZVhCbElqb2liV1Z6YzJGblpWOXpkRzl3SWl3aVlX
+ MWhlbTl1TFdKbFpISnZZMnN0YVc1MmIyTmhkR2x2YmsxbGRISnBZM01pT25zaWFXNXdkWF
+ JVYjJ0bGJrTnZkVzUwSWpveE5pd2liM1YwY0hWMFZHOXJaVzVEYjNWdWRDSTZOeXdpYVc1
+ MmIyTmhkR2x2Ymt4aGRHVnVZM2tpT2pVMk5Dd2labWx5YzNSQ2VYUmxUR0YwWlc1amVTST
+ ZOVFEyZlgwPSIsInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQyJ9KKxn3Q==
+ headers:
+ Content-Type:
+ - application/vnd.amazon.eventstream
+ status: 200 OK
+ code: 200
+ duration: 693.876083ms