@@ -10,6 +10,10 @@ import (
1010 "sort"
1111 "strings"
1212
13+ "github.com/aws/aws-sdk-go-v2/aws"
14+ "github.com/aws/aws-sdk-go-v2/credentials"
15+ awsbedrock "github.com/aws/aws-sdk-go-v2/service/bedrock"
16+ bedrocktypes "github.com/aws/aws-sdk-go-v2/service/bedrock/types"
1317 "github.com/google/generative-ai-go/genai"
1418 "google.golang.org/api/iterator"
1519 googleoption "google.golang.org/api/option"
@@ -26,6 +30,7 @@ type ModelListRequest struct {
2630 ProviderType string
2731 APIKey string
2832 BaseURL string
33+ Settings map [string ]string
2934}
3035
3136type ModelLister func (ctx context.Context , req ModelListRequest ) ([]ModelInfo , error )
@@ -53,11 +58,15 @@ type copilotTokenResponse struct {
5358// ListModels fetches available models from the given provider type.
5459// Only requires an API key and optional base URL — no saved provider needed.
5560func ListModels (ctx context.Context , providerType , apiKey , baseURL string ) ([]ModelInfo , error ) {
61+ return ListModelsWithSettings (ctx , providerType , apiKey , baseURL , nil )
62+ }
63+
64+ func ListModelsWithSettings (ctx context.Context , providerType , apiKey , baseURL string , settings map [string ]string ) ([]ModelInfo , error ) {
5665 lister , ok := modelListers [providerType ]
5766 if ! ok {
5867 return nil , fmt .Errorf ("unsupported provider type: %s" , providerType )
5968 }
60- return lister (ctx , ModelListRequest {ProviderType : providerType , APIKey : apiKey , BaseURL : baseURL })
69+ return lister (ctx , ModelListRequest {ProviderType : providerType , APIKey : apiKey , BaseURL : baseURL , Settings : settings })
6170}
6271
6372var modelListers = map [string ]ModelLister {
@@ -90,8 +99,8 @@ var modelListers = map[string]ModelLister{
9099 "openai_azure" : func (context.Context , ModelListRequest ) ([]ModelInfo , error ) {
91100 return nil , dynamicModelListingUnsupported ("openai_azure" )
92101 },
93- "anthropic_bedrock" : func (context.Context , ModelListRequest ) ([]ModelInfo , error ) {
94- return nil , dynamicModelListingUnsupported ( "anthropic_bedrock" )
102+ "anthropic_bedrock" : func (ctx context.Context , req ModelListRequest ) ([]ModelInfo , error ) {
103+ return listBedrockModels ( ctx , req )
95104 },
96105 "anthropic_vertex" : func (context.Context , ModelListRequest ) ([]ModelInfo , error ) {
97106 return nil , dynamicModelListingUnsupported ("anthropic_vertex" )
@@ -604,6 +613,146 @@ func dynamicModelListingUnsupported(providerType string) error {
604613 return fmt .Errorf ("%s: dynamic model listing is not implemented for this provider; specify a custom model ID" , providerType )
605614}
606615
616+ type bedrockModelListConfig struct {
617+ Region string
618+ AccessKeyID string
619+ SecretAccessKey string
620+ SessionToken string
621+ }
622+
623+ type bedrockFoundationModelLister interface {
624+ ListFoundationModels (context.Context , * awsbedrock.ListFoundationModelsInput , ... func (* awsbedrock.Options )) (* awsbedrock.ListFoundationModelsOutput , error )
625+ }
626+
627+ func bedrockModelListConfigFromRequest (req ModelListRequest ) (bedrockModelListConfig , error ) {
628+ cfg := bedrockModelListConfig {
629+ Region : req .Settings ["region" ],
630+ AccessKeyID : req .Settings ["access_key_id" ],
631+ SecretAccessKey : req .APIKey ,
632+ SessionToken : req .Settings ["session_token" ],
633+ }
634+ if strings .TrimSpace (req .APIKey ) != "" && strings .HasPrefix (strings .TrimSpace (req .APIKey ), "{" ) {
635+ var secret struct {
636+ Region string `json:"region"`
637+ AccessKeyID string `json:"access_key_id"`
638+ SecretAccessKey string `json:"secret_access_key"`
639+ SessionToken string `json:"session_token"`
640+ }
641+ if err := json .Unmarshal ([]byte (req .APIKey ), & secret ); err != nil {
642+ return bedrockModelListConfig {}, fmt .Errorf ("anthropic_bedrock: parse credential JSON: %w" , err )
643+ }
644+ cfg .SecretAccessKey = ""
645+ if cfg .Region == "" {
646+ cfg .Region = secret .Region
647+ }
648+ if cfg .AccessKeyID == "" {
649+ cfg .AccessKeyID = secret .AccessKeyID
650+ }
651+ if cfg .SecretAccessKey == "" {
652+ cfg .SecretAccessKey = secret .SecretAccessKey
653+ }
654+ if cfg .SessionToken == "" {
655+ cfg .SessionToken = secret .SessionToken
656+ }
657+ }
658+ if cfg .Region == "" {
659+ cfg .Region = defaultBedrockRegion
660+ }
661+ cfg .AccessKeyID = strings .TrimSpace (cfg .AccessKeyID )
662+ cfg .SecretAccessKey = strings .TrimSpace (cfg .SecretAccessKey )
663+ cfg .SessionToken = strings .TrimSpace (cfg .SessionToken )
664+ if cfg .AccessKeyID == "" {
665+ return bedrockModelListConfig {}, fmt .Errorf ("anthropic_bedrock: access_key_id setting is required for model discovery" )
666+ }
667+ if cfg .SecretAccessKey == "" {
668+ return bedrockModelListConfig {}, fmt .Errorf ("anthropic_bedrock: secret access key is required for model discovery" )
669+ }
670+ return cfg , nil
671+ }
672+
673+ func listBedrockModels (ctx context.Context , req ModelListRequest ) ([]ModelInfo , error ) {
674+ if err := ValidateBaseURL (req .BaseURL ); err != nil {
675+ return nil , err
676+ }
677+ cfg , err := bedrockModelListConfigFromRequest (req )
678+ if err != nil {
679+ return nil , err
680+ }
681+ awsCfg := aws.Config {
682+ Region : cfg .Region ,
683+ Credentials : credentials .NewStaticCredentialsProvider (cfg .AccessKeyID , cfg .SecretAccessKey , cfg .SessionToken ),
684+ HTTPClient : modelHTTPClient ,
685+ }
686+ opts := []func (* awsbedrock.Options ){}
687+ if req .BaseURL != "" {
688+ opts = append (opts , func (o * awsbedrock.Options ) {
689+ o .BaseEndpoint = aws .String (strings .TrimRight (req .BaseURL , "/" ))
690+ })
691+ }
692+ return listBedrockModelsFromAPI (ctx , awsbedrock .NewFromConfig (awsCfg , opts ... ))
693+ }
694+
695+ func listBedrockModelsFromAPI (ctx context.Context , api bedrockFoundationModelLister ) ([]ModelInfo , error ) {
696+ out , err := api .ListFoundationModels (ctx , & awsbedrock.ListFoundationModelsInput {
697+ ByProvider : aws .String ("Anthropic" ),
698+ ByOutputModality : bedrocktypes .ModelModalityText ,
699+ ByInferenceType : bedrocktypes .InferenceTypeOnDemand ,
700+ })
701+ if err != nil {
702+ return nil , fmt .Errorf ("anthropic_bedrock: list foundation models: %w" , err )
703+ }
704+ models := make ([]ModelInfo , 0 , len (out .ModelSummaries ))
705+ for _ , summary := range out .ModelSummaries {
706+ id := strings .TrimSpace (aws .ToString (summary .ModelId ))
707+ if id == "" {
708+ continue
709+ }
710+ if providerName := strings .TrimSpace (aws .ToString (summary .ProviderName )); providerName != "" && ! strings .EqualFold (providerName , "Anthropic" ) {
711+ continue
712+ }
713+ if len (summary .OutputModalities ) > 0 && ! modelModalitiesContain (summary .OutputModalities , bedrocktypes .ModelModalityText ) {
714+ continue
715+ }
716+ if len (summary .InferenceTypesSupported ) > 0 && ! inferenceTypesContain (summary .InferenceTypesSupported , bedrocktypes .InferenceTypeOnDemand ) {
717+ continue
718+ }
719+ name := strings .TrimSpace (aws .ToString (summary .ModelName ))
720+ if name == "" {
721+ name = id
722+ }
723+ providerName := strings .TrimSpace (aws .ToString (summary .ProviderName ))
724+ if providerName != "" && ! strings .HasPrefix (strings .ToLower (name ), strings .ToLower (providerName )+ " " ) {
725+ name = providerName + " " + name
726+ }
727+ models = append (models , ModelInfo {ID : id , Name : name })
728+ }
729+ if len (models ) == 0 {
730+ return nil , fmt .Errorf ("anthropic_bedrock: no selectable Anthropic text models returned" )
731+ }
732+ sort .Slice (models , func (i , j int ) bool {
733+ return models [i ].ID < models [j ].ID
734+ })
735+ return models , nil
736+ }
737+
738+ func modelModalitiesContain (values []bedrocktypes.ModelModality , want bedrocktypes.ModelModality ) bool {
739+ for _ , v := range values {
740+ if v == want {
741+ return true
742+ }
743+ }
744+ return false
745+ }
746+
747+ func inferenceTypesContain (values []bedrocktypes.InferenceType , want bedrocktypes.InferenceType ) bool {
748+ for _ , v := range values {
749+ if v == want {
750+ return true
751+ }
752+ }
753+ return false
754+ }
755+
607756// listOllamaModels lists models available on a local Ollama server.
608757func listOllamaModels (ctx context.Context , baseURL string ) ([]ModelInfo , error ) {
609758 c := NewOllamaClient (baseURL )
0 commit comments