diff --git a/core/bifrost.go b/core/bifrost.go index 3ce37dfd0f..25990c1fca 100644 --- a/core/bifrost.go +++ b/core/bifrost.go @@ -1432,7 +1432,7 @@ func (bifrost *Bifrost) selectKeyFromProviderForModel(ctx *context.Context, prov // filter out keys which dont support the model, if the key has no models, it is supported for all models var supportedKeys []schemas.Key for _, key := range keys { - if (slices.Contains(key.Models, model) && (strings.TrimSpace(key.Value) != "" || providerKey == schemas.Vertex)) || len(key.Models) == 0 { + if (slices.Contains(key.Models, model) && (strings.TrimSpace(key.Value) != "" || canProviderKeyValueBeEmpty(providerKey))) || len(key.Models) == 0 { supportedKeys = append(supportedKeys, key) } } diff --git a/core/providers/bedrock.go b/core/providers/bedrock.go index 7dde654bf5..e4e2bad017 100644 --- a/core/providers/bedrock.go +++ b/core/providers/bedrock.go @@ -195,7 +195,6 @@ type BedrockStreamMetadataEvent struct { type BedrockProvider struct { logger schemas.Logger // Logger for provider operations client *http.Client // HTTP client for API requests - meta schemas.MetaConfig // Bedrock-specific configuration networkConfig schemas.NetworkConfig // Network configuration including extra headers sendBackRawResponse bool // Whether to include raw response in BifrostResponse } @@ -227,10 +226,6 @@ func releaseBedrockChatResponse(resp *BedrockChatResponse) { func NewBedrockProvider(config *schemas.ProviderConfig, logger schemas.Logger) (*BedrockProvider, error) { config.CheckAndSetDefaults() - if config.MetaConfig == nil { - return nil, fmt.Errorf("meta config is not set") - } - client := &http.Client{Timeout: time.Second * time.Duration(config.NetworkConfig.DefaultRequestTimeoutInSeconds)} // Pre-warm response pools @@ -242,7 +237,6 @@ func NewBedrockProvider(config *schemas.ProviderConfig, logger schemas.Logger) ( return &BedrockProvider{ logger: logger, client: client, - meta: config.MetaConfig, networkConfig: config.NetworkConfig, sendBackRawResponse: config.SendBackRawResponse, }, nil @@ -256,19 +250,10 @@ func (provider *BedrockProvider) GetProviderKey() schemas.ModelProvider { // CompleteRequest sends a request to Bedrock's API and handles the response. // It constructs the API URL, sets up AWS authentication, and processes the response. // Returns the response body or an error if the request fails. -func (provider *BedrockProvider) completeRequest(ctx context.Context, requestBody map[string]interface{}, path string, accessKey string) ([]byte, *schemas.BifrostError) { - if provider.meta == nil { - return nil, &schemas.BifrostError{ - IsBifrostError: false, - Error: schemas.ErrorField{ - Message: "meta config for bedrock is not provided", - }, - } - } - +func (provider *BedrockProvider) completeRequest(ctx context.Context, requestBody map[string]interface{}, path string, config schemas.BedrockKeyConfig) ([]byte, *schemas.BifrostError) { region := "us-east-1" - if provider.meta.GetRegion() != nil { - region = *provider.meta.GetRegion() + if config.Region != nil { + region = *config.Region } jsonBody, err := sonic.Marshal(requestBody) @@ -307,8 +292,8 @@ func (provider *BedrockProvider) completeRequest(ctx context.Context, requestBod // Set any extra headers from network config setExtraHeadersHTTP(req, provider.networkConfig.ExtraHeaders, nil) - if provider.meta.GetSecretAccessKey() != nil { - if err := signAWSRequest(req, accessKey, *provider.meta.GetSecretAccessKey(), provider.meta.GetSessionToken(), region, "bedrock"); err != nil { + if config.SecretKey != "" { + if err := signAWSRequest(req, config.AccessKey, config.SecretKey, config.SessionToken, region, "bedrock"); err != nil { return nil, err } } else { @@ -822,13 +807,17 @@ func (provider *BedrockProvider) prepareTextCompletionParams(params map[string]i // It formats the request, sends it to Bedrock, and processes the response. // Returns a BifrostResponse containing the completion results or an error if the request fails. func (provider *BedrockProvider) TextCompletion(ctx context.Context, model string, key schemas.Key, text string, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { + if key.BedrockKeyConfig == nil { + return nil, newConfigurationError("bedrock key config is not provided", schemas.Bedrock) + } + preparedParams := provider.prepareTextCompletionParams(prepareParams(params), model) requestBody := mergeConfig(map[string]interface{}{ "prompt": text, }, preparedParams) - body, err := provider.completeRequest(ctx, requestBody, fmt.Sprintf("%s/invoke", model), key.Value) + body, err := provider.completeRequest(ctx, requestBody, fmt.Sprintf("%s/invoke", model), *key.BedrockKeyConfig) if err != nil { return nil, err } @@ -908,6 +897,10 @@ func (provider *BedrockProvider) extractToolsFromHistory(messages []schemas.Bifr // It formats the request, sends it to Bedrock, and processes the response. // Returns a BifrostResponse containing the completion results or an error if the request fails. func (provider *BedrockProvider) ChatCompletion(ctx context.Context, model string, key schemas.Key, messages []schemas.BifrostMessage, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { + if key.BedrockKeyConfig == nil { + return nil, newConfigurationError("bedrock key config is not provided", schemas.Bedrock) + } + messageBody, err := provider.prepareChatCompletionMessages(messages, model) if err != nil { return nil, err @@ -939,17 +932,17 @@ func (provider *BedrockProvider) ChatCompletion(ctx context.Context, model strin // Format the path with proper model identifier path := fmt.Sprintf("%s/converse", model) - if provider.meta != nil && provider.meta.GetInferenceProfiles() != nil { - if inferenceProfileId, ok := provider.meta.GetInferenceProfiles()[model]; ok { - if provider.meta.GetARN() != nil { - encodedModelIdentifier := url.PathEscape(fmt.Sprintf("%s/%s", *provider.meta.GetARN(), inferenceProfileId)) + if key.BedrockKeyConfig.Deployments != nil { + if inferenceProfileId, ok := key.BedrockKeyConfig.Deployments[model]; ok { + if key.BedrockKeyConfig.ARN != nil { + encodedModelIdentifier := url.PathEscape(fmt.Sprintf("%s/%s", *key.BedrockKeyConfig.ARN, inferenceProfileId)) path = fmt.Sprintf("%s/converse", encodedModelIdentifier) } } } // Create the signed request - responseBody, err := provider.completeRequest(ctx, requestBody, path, key.Value) + responseBody, err := provider.completeRequest(ctx, requestBody, path, *key.BedrockKeyConfig) if err != nil { return nil, err } @@ -1088,7 +1081,7 @@ func signAWSRequest(req *http.Request, accessKey, secretKey string, sessionToken AccessKeyID: accessKey, SecretAccessKey: secretKey, } - if sessionToken != nil { + if sessionToken != nil && *sessionToken != "" { creds.SessionToken = *sessionToken } return creds, nil @@ -1118,18 +1111,30 @@ func signAWSRequest(req *http.Request, accessKey, secretKey string, sessionToken // Embedding generates embeddings for the given input text(s) using Amazon Bedrock. // Supports Titan and Cohere embedding models. Returns a BifrostResponse containing the embedding(s) and any error that occurred. func (provider *BedrockProvider) Embedding(ctx context.Context, model string, key schemas.Key, input *schemas.EmbeddingInput, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { + if key.BedrockKeyConfig == nil { + return nil, newConfigurationError("bedrock key config is not provided", schemas.Bedrock) + } + switch { case strings.HasPrefix(model, "amazon.titan-embed-text"): - return provider.handleTitanEmbedding(ctx, model, key.Value, input, params) + return provider.handleTitanEmbedding(ctx, model, *key.BedrockKeyConfig, input, params) case strings.HasPrefix(model, "cohere.embed"): - return provider.handleCohereEmbedding(ctx, model, key.Value, input, params) + return provider.handleCohereEmbedding(ctx, model, *key.BedrockKeyConfig, input, params) default: return nil, newConfigurationError("embedding is not supported for this Bedrock model", schemas.Bedrock) } } // handleTitanEmbedding handles embedding requests for Amazon Titan models. -func (provider *BedrockProvider) handleTitanEmbedding(ctx context.Context, model string, key string, input *schemas.EmbeddingInput, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { +func (provider *BedrockProvider) handleTitanEmbedding(ctx context.Context, model string, config schemas.BedrockKeyConfig, input *schemas.EmbeddingInput, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { + // Titan Text Embeddings V1/V2 - only supports single text input + if len(input.Texts) == 0 { + return nil, newConfigurationError("no input text provided for embedding", schemas.Bedrock) + } + if len(input.Texts) > 1 { + return nil, newConfigurationError("Amazon Titan embedding models support only single text input, received multiple texts", schemas.Bedrock) + } + requestBody := map[string]interface{}{ "inputText": input.Texts[0], } @@ -1148,7 +1153,7 @@ func (provider *BedrockProvider) handleTitanEmbedding(ctx context.Context, model // Properly escape model name for URL path to ensure AWS SIGv4 signing works correctly path := url.PathEscape(model) + "/invoke" - rawResponse, err := provider.completeRequest(ctx, requestBody, path, key) + rawResponse, err := provider.completeRequest(ctx, requestBody, path, config) if err != nil { return nil, err } @@ -1192,7 +1197,11 @@ func (provider *BedrockProvider) handleTitanEmbedding(ctx context.Context, model } // handleCohereEmbedding handles embedding requests for Cohere models on Bedrock. -func (provider *BedrockProvider) handleCohereEmbedding(ctx context.Context, model string, key string, input *schemas.EmbeddingInput, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { +func (provider *BedrockProvider) handleCohereEmbedding(ctx context.Context, model string, config schemas.BedrockKeyConfig, input *schemas.EmbeddingInput, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) { + if len(input.Texts) == 0 { + return nil, newConfigurationError("no input text provided for embedding", schemas.Bedrock) + } + requestBody := map[string]interface{}{ "texts": input.Texts, "input_type": "search_document", @@ -1203,7 +1212,7 @@ func (provider *BedrockProvider) handleCohereEmbedding(ctx context.Context, mode // Properly escape model name for URL path to ensure AWS SIGv4 signing works correctly path := url.PathEscape(model) + "/invoke" - rawResponse, err := provider.completeRequest(ctx, requestBody, path, key) + rawResponse, err := provider.completeRequest(ctx, requestBody, path, config) if err != nil { return nil, err } @@ -1255,6 +1264,10 @@ func (provider *BedrockProvider) handleCohereEmbedding(ctx context.Context, mode // It formats the request, sends it to Bedrock, and processes the streaming response. // Returns a channel for streaming BifrostResponse objects or an error if the request fails. func (provider *BedrockProvider) ChatCompletionStream(ctx context.Context, postHookRunner schemas.PostHookRunner, model string, key schemas.Key, messages []schemas.BifrostMessage, params *schemas.ModelParameters) (chan *schemas.BifrostStream, *schemas.BifrostError) { + if key.BedrockKeyConfig == nil { + return nil, newConfigurationError("bedrock key config is not provided", schemas.Bedrock) + } + messageBody, err := provider.prepareChatCompletionMessages(messages, model) if err != nil { return nil, err @@ -1286,22 +1299,18 @@ func (provider *BedrockProvider) ChatCompletionStream(ctx context.Context, postH // Format the path with proper model identifier for streaming path := fmt.Sprintf("%s/converse-stream", model) - if provider.meta != nil && provider.meta.GetInferenceProfiles() != nil { - if inferenceProfileId, ok := provider.meta.GetInferenceProfiles()[model]; ok { - if provider.meta.GetARN() != nil { - encodedModelIdentifier := url.PathEscape(fmt.Sprintf("%s/%s", *provider.meta.GetARN(), inferenceProfileId)) + if key.BedrockKeyConfig.Deployments != nil { + if inferenceProfileId, ok := key.BedrockKeyConfig.Deployments[model]; ok { + if key.BedrockKeyConfig.ARN != nil { + encodedModelIdentifier := url.PathEscape(fmt.Sprintf("%s/%s", *key.BedrockKeyConfig.ARN, inferenceProfileId)) path = fmt.Sprintf("%s/converse-stream", encodedModelIdentifier) } } } - if provider.meta == nil { - return nil, newConfigurationError("meta config for bedrock is not provided", schemas.Bedrock) - } - region := "us-east-1" - if provider.meta.GetRegion() != nil { - region = *provider.meta.GetRegion() + if key.BedrockKeyConfig.Region != nil { + region = *key.BedrockKeyConfig.Region } // Create the streaming request @@ -1320,8 +1329,8 @@ func (provider *BedrockProvider) ChatCompletionStream(ctx context.Context, postH setExtraHeadersHTTP(req, provider.networkConfig.ExtraHeaders, nil) // Sign the request for AWS - if provider.meta.GetSecretAccessKey() != nil { - if signErr := signAWSRequest(req, key.Value, *provider.meta.GetSecretAccessKey(), provider.meta.GetSessionToken(), region, "bedrock"); signErr != nil { + if key.BedrockKeyConfig.SecretKey != "" { + if signErr := signAWSRequest(req, key.BedrockKeyConfig.AccessKey, key.BedrockKeyConfig.SecretKey, key.BedrockKeyConfig.SessionToken, region, "bedrock"); signErr != nil { return nil, signErr } } else { diff --git a/core/providers/vertex.go b/core/providers/vertex.go index 4909db226c..fb3d8786fe 100644 --- a/core/providers/vertex.go +++ b/core/providers/vertex.go @@ -173,7 +173,7 @@ func (provider *VertexProvider) ChatCompletion(ctx context.Context, model string region := key.VertexKeyConfig.Region if region == "" { - return nil, newConfigurationError("region is not set in meta config", schemas.Vertex) + return nil, newConfigurationError("region is not set in key config", schemas.Vertex) } url := fmt.Sprintf("https://%s-aiplatform.googleapis.com/v1beta1/projects/%s/locations/%s/endpoints/openapi/chat/completions", region, projectID, region) @@ -340,7 +340,7 @@ func (provider *VertexProvider) ChatCompletionStream(ctx context.Context, postHo region := key.VertexKeyConfig.Region if region == "" { - return nil, newConfigurationError("region is not set in meta config", schemas.Vertex) + return nil, newConfigurationError("region is not set in key config", schemas.Vertex) } client, err := getAuthClient(key) diff --git a/core/schemas/account.go b/core/schemas/account.go index 1572624e0c..aa4d66a45a 100644 --- a/core/schemas/account.go +++ b/core/schemas/account.go @@ -6,12 +6,13 @@ import "context" // Key represents an API key and its associated configuration for a provider. // It contains the key value, supported models, and a weight for load balancing. type Key struct { - ID string `json:"id"` // The unique identifier for the key (not used by bifrost, but can be used by users to identify the key) - Value string `json:"value"` // The actual API key value - Models []string `json:"models"` // List of models this key can access - Weight float64 `json:"weight"` // Weight for load balancing between multiple keys - AzureKeyConfig *AzureKeyConfig `json:"azure_key_config,omitempty"` // Azure-specific key configuration - VertexKeyConfig *VertexKeyConfig `json:"vertex_key_config,omitempty"` // Vertex-specific key configuration + ID string `json:"id"` // The unique identifier for the key (not used by bifrost, but can be used by users to identify the key) + Value string `json:"value"` // The actual API key value + Models []string `json:"models"` // List of models this key can access + Weight float64 `json:"weight"` // Weight for load balancing between multiple keys + AzureKeyConfig *AzureKeyConfig `json:"azure_key_config,omitempty"` // Azure-specific key configuration + VertexKeyConfig *VertexKeyConfig `json:"vertex_key_config,omitempty"` // Vertex-specific key configuration + BedrockKeyConfig *BedrockKeyConfig `json:"bedrock_key_config,omitempty"` // AWS Bedrock-specific key configuration } // AzureKeyConfig represents the Azure-specific configuration. @@ -30,6 +31,17 @@ type VertexKeyConfig struct { AuthCredentials string `json:"auth_credentials,omitempty"` } +// BedrockKeyConfig represents the AWS Bedrock-specific configuration. +// It contains AWS-specific settings required for authentication and service access. +type BedrockKeyConfig struct { + AccessKey string `json:"access_key,omitempty"` // AWS access key for authentication + SecretKey string `json:"secret_key,omitempty"` // AWS secret access key for authentication + SessionToken *string `json:"session_token,omitempty"` // AWS session token for temporary credentials + Region *string `json:"region,omitempty"` // AWS region for service access + ARN *string `json:"arn,omitempty"` // Amazon Resource Name for resource identification + Deployments map[string]string `json:"deployments,omitempty"` // Mapping of model identifiers to inference profiles +} + // Account defines the interface for managing provider accounts and their configurations. // It provides methods to access provider-specific settings, API keys, and configurations. type Account interface { diff --git a/core/schemas/meta/bedrock.go b/core/schemas/meta/bedrock.go deleted file mode 100644 index fe3561c11b..0000000000 --- a/core/schemas/meta/bedrock.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package meta provides provider-specific configuration structures and schemas. -// This file contains the AWS Bedrock-specific configuration implementation. - -package meta - -// BedrockMetaConfig represents the AWS Bedrock-specific configuration. -// It contains AWS-specific settings required for authentication and service access. -type BedrockMetaConfig struct { - SecretAccessKey string `json:"secret_access_key,omitempty"` // AWS secret access key for authentication - Region *string `json:"region,omitempty"` // AWS region for service access - SessionToken *string `json:"session_token,omitempty"` // AWS session token for temporary credentials - ARN *string `json:"arn,omitempty"` // Amazon Resource Name for resource identification - InferenceProfiles map[string]string `json:"inference_profiles,omitempty"` // Mapping of model identifiers to inference profiles -} - -// GetSecretAccessKey returns the AWS secret access key. -// This is used for AWS API authentication. -func (c *BedrockMetaConfig) GetSecretAccessKey() *string { - return &c.SecretAccessKey -} - -// GetRegion returns the AWS region. -// This specifies which AWS region the service should be accessed from. -func (c *BedrockMetaConfig) GetRegion() *string { - return c.Region -} - -// GetSessionToken returns the AWS session token. -// This is used for temporary credentials in AWS authentication. -func (c *BedrockMetaConfig) GetSessionToken() *string { - return c.SessionToken -} - -// GetARN returns the Amazon Resource Name. -// This uniquely identifies AWS resources. -func (c *BedrockMetaConfig) GetARN() *string { - return c.ARN -} - -// GetInferenceProfiles returns the inference profiles mapping. -// This maps model identifiers to their corresponding inference profiles. -func (c *BedrockMetaConfig) GetInferenceProfiles() map[string]string { - return c.InferenceProfiles -} diff --git a/core/schemas/provider.go b/core/schemas/provider.go index cae26ecace..f2b70c1c9b 100644 --- a/core/schemas/provider.go +++ b/core/schemas/provider.go @@ -47,21 +47,6 @@ var DefaultNetworkConfig = NetworkConfig{ RetryBackoffMax: DefaultRetryBackoffMax, } -// MetaConfig defines the interface for provider-specific configuration. -// Check /meta folder for implemented provider-specific meta configurations. -type MetaConfig interface { - // GetSecretAccessKey returns the secret access key for authentication - GetSecretAccessKey() *string - // GetRegion returns the region for the provider - GetRegion() *string - // GetSessionToken returns the session token for authentication - GetSessionToken() *string - // GetARN returns the Amazon Resource Name (ARN) - GetARN() *string - // GetInferenceProfiles returns the inference profiles - GetInferenceProfiles() map[string]string -} - // ConcurrencyAndBufferSize represents configuration for concurrent operations and buffer sizes. type ConcurrencyAndBufferSize struct { Concurrency int `json:"concurrency"` // Number of concurrent operations. Also used as the initial pool size for the provider reponses. @@ -101,7 +86,6 @@ type ProxyConfig struct { // in your account interface implementation. type ProviderConfig struct { NetworkConfig NetworkConfig `json:"network_config"` // Network configuration - MetaConfig MetaConfig `json:"meta_config,omitempty"` // Provider-specific configuration ConcurrencyAndBufferSize ConcurrencyAndBufferSize `json:"concurrency_and_buffer_size"` // Concurrency settings // Logger instance, can be provided by the user or bifrost default logger is used if not provided Logger Logger `json:"logger"` diff --git a/core/utils.go b/core/utils.go index 495dd6616f..0c67ff4d01 100644 --- a/core/utils.go +++ b/core/utils.go @@ -17,6 +17,13 @@ func providerRequiresKey(providerKey schemas.ModelProvider) bool { return providerKey != schemas.Ollama && providerKey != schemas.SGL } +// canProviderKeyValueBeEmpty returns true if the given provider allows the API key to be empty. +// Some providers like Vertex and Bedrock have their credentials in additional key configs.. +func canProviderKeyValueBeEmpty(providerKey schemas.ModelProvider) bool { + return providerKey == schemas.Vertex || providerKey == schemas.Bedrock +} + +// isStreamRequestType returns true if the given request type is a stream request. func isStreamRequestType(reqType RequestType) bool { return reqType == ChatCompletionStreamRequest || reqType == SpeechStreamRequest || reqType == TranscriptionStreamRequest } diff --git a/docs/contributing/code-conventions.md b/docs/contributing/code-conventions.md index dddd780680..fa27276937 100644 --- a/docs/contributing/code-conventions.md +++ b/docs/contributing/code-conventions.md @@ -243,7 +243,6 @@ core/ ├── bifrost.go # Core types ├── provider.go # Provider interfaces ├── plugin.go # Plugin types - └── meta/ # Provider-specific metadata ``` ### **Import Organization** diff --git a/docs/contributing/provider.md b/docs/contributing/provider.md index ef05ac45e4..1a84c2979e 100644 --- a/docs/contributing/provider.md +++ b/docs/contributing/provider.md @@ -80,22 +80,6 @@ type Provider interface { } ``` -### **Meta Configuration Support** - -Some providers require additional configuration beyond API keys. Bifrost supports this through meta configs: - -```go -// In core/schemas/meta/yourprovider.go -type YourProviderMetaConfig struct { - // Add provider-specific fields - Endpoint string `json:"endpoint"` // e.g., Custom API endpoint - Region string `json:"region"` // e.g., Cloud region - ProjectID string `json:"project_id"` // e.g., Cloud project identifier - - // ... other fields (check /core/schemas/provider.go) -} -``` - ### **Provider Structure Template** ```go @@ -506,7 +490,7 @@ func providerRequiresKey(providerKey schemas.ModelProvider) bool { ## 🌐 **Integration with HTTP Transport** -The HTTP transport layer requires specific changes to handle provider configuration, meta configs, and model patterns. +The HTTP transport layer requires specific changes to handle provider configuration, and model patterns. ### **1. Provider Recognition** @@ -535,106 +519,7 @@ func GetProviderFromModel(model string) schemas.ModelProvider { } ``` -### **2. Meta Configuration Support** - -If your provider needs additional configuration beyond API keys, you'll need to implement meta config support: - -1. **Define Meta Config Structure** (`core/schemas/meta/yourprovider.go`): - -```go -type YourProviderMetaConfig struct { - Endpoint string `json:"endpoint"` // Custom API endpoint - Region string `json:"region"` // Cloud region - ProjectID string `json:"project_id"` // Project identifier -} - -func (c *YourProviderMetaConfig) GetType() string { - return "yourprovider" -} -``` - -2. **Update Store Meta Config Processing** (`transports/bifrost-http/lib/store.go`): - -Add your provider to these three functions: - -```go -// A. Add to parseMetaConfig -func (s *ConfigStore) parseMetaConfig(rawMetaConfig json.RawMessage, provider schemas.ModelProvider) (*schemas.MetaConfig, error) { - switch provider { - // ... existing cases - case schemas.YourProvider: - var config meta.YourProviderMetaConfig - if err := json.Unmarshal(rawMetaConfig, &config); err != nil { - return nil, fmt.Errorf("failed to unmarshal meta config: %w", err) - } - var metaConfig schemas.MetaConfig = &config - return &metaConfig, nil - } - return nil, fmt.Errorf("unsupported provider for meta config: %s", provider) -} - -// B. Add to processMetaConfigEnvVars -func (s *ConfigStore) processMetaConfigEnvVars(rawMetaConfig json.RawMessage, provider schemas.ModelProvider) (json.RawMessage, error) { - switch provider { - // ... existing cases - case schemas.YourProvider: - var config meta.YourProviderMetaConfig - if err := json.Unmarshal(rawMetaConfig, &config); err != nil { - return nil, fmt.Errorf("failed to unmarshal meta config: %w", err) - } - - // Process each field that might contain env vars - endpoint, envVar, err := s.processEnvValue(config.Endpoint) - if err != nil { - return nil, err - } - if envVar != "" { - s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ - EnvVar: envVar, - Provider: string(provider), - KeyType: "meta_config", - ConfigPath: fmt.Sprintf("providers.%s.meta_config.endpoint", provider), - }) - } - config.Endpoint = endpoint - - // Process other fields similarly... - - return json.Marshal(config) - } - return rawMetaConfig, nil -} - -// C. Add to GetProviderConfig for redaction -func (s *ConfigStore) GetProviderConfig(provider schemas.ModelProvider) (*ProviderConfig, error) { - // ... existing code ... - - if configCopy.MetaConfig != nil { - switch m := (*configCopy.MetaConfig).(type) { - // ... existing cases - case *meta.YourProviderMetaConfig: - config := *m - - // Redact or show env vars for each field - path := fmt.Sprintf("providers.%s.meta_config.endpoint", provider) - if envVar, ok := envVarsByPath[path]; ok { - config.Endpoint = "env." + envVar - } else { - config.Endpoint = RedactKey(config.Endpoint) - } - - // Handle other fields... - - var metaConfig schemas.MetaConfig = &config - configCopy.MetaConfig = &metaConfig - } - } - - return &configCopy, nil -} -``` - -### **3. Testing HTTP Transport Integration** +### **2. Testing HTTP Transport Integration** Add integration tests in `tests/transports-integrations/`: @@ -644,9 +529,11 @@ Add integration tests in `tests/transports-integrations/`: def test_yourprovider_config(): config = { "provider": "yourprovider", - "meta_config": { - "endpoint": "env.YOURPROVIDER_ENDPOINT", - "region": "us-east-1" + "keys": [ + { + "value": "env.YOURPROVIDER_API_KEY", + "models": ["*"] + } } } # Test config validation @@ -683,17 +570,12 @@ Document the configuration format for users: "models": ["*"] } ], - "meta_config": { - "endpoint": "env.YOURPROVIDER_ENDPOINT", - "region": "env.YOURPROVIDER_REGION", - "project_id": "env.YOURPROVIDER_PROJECT_ID" - } } } } ``` -Note: API key handling is automatic - you only need to implement the meta config processing if your provider requires additional configuration beyond API keys. +Note: API key handling is automatic - you only need to implement the key config processing if your provider requires additional configuration beyond API keys. --- diff --git a/docs/usage/go-package/account.md b/docs/usage/go-package/account.md index 06cc627818..58070cec5c 100644 --- a/docs/usage/go-package/account.md +++ b/docs/usage/go-package/account.md @@ -143,9 +143,14 @@ func (a *MultiProviderAccount) GetKeysForProvider(ctx *context.Context, provider case schemas.Bedrock: return []schemas.Key{{ - Value: os.Getenv("BEDROCK_API_KEY"), Models: []string{"anthropic.claude-3-sonnet-20240229-v1:0"}, Weight: 1.0, + BedrockKeyConfig: &schemas.BedrockKeyConfig{ + AccessKey: os.Getenv("AWS_ACCESS_KEY"), + SecretKey: os.Getenv("AWS_SECRET_KEY"), + SessionToken: bifrost.Ptr(os.Getenv("AWS_SESSION_TOKEN")), + Region: bifrost.Ptr("us-east-1"), + }, }}, nil case schemas.Vertex: diff --git a/docs/usage/http-transport/configuration/providers.md b/docs/usage/http-transport/configuration/providers.md index 6dc7734e6b..e39449eb0d 100644 --- a/docs/usage/http-transport/configuration/providers.md +++ b/docs/usage/http-transport/configuration/providers.md @@ -32,7 +32,6 @@ Provider configuration in `config.json` defines: - **Supported models** for each provider - **Network settings** and retry behavior - **Concurrency controls** and performance tuning -- **Provider-specific metadata** (regions, endpoints, etc.) ```json { @@ -135,14 +134,19 @@ Provider configuration in `config.json` defines: "bedrock": { "keys": [ { - "value": "env.BEDROCK_API_KEY", "models": [ "anthropic.claude-v2:1", "mistral.mixtral-8x7b-instruct-v0:1", "mistral.mistral-large-2402-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0" ], - "weight": 1.0 + "weight": 1.0, + "bedrock_key_config": { + "access_key": "env.AWS_ACCESS_KEY", + "secret_key": "env.AWS_SECRET_ACCESS_KEY", + "session_token": "env.AWS_SESSION_TOKEN", + "region": "us-east-1" + } } ], "network_config": { @@ -151,10 +155,6 @@ Provider configuration in `config.json` defines: "retry_backoff_initial_ms": 100, "retry_backoff_max_ms": 2000 }, - "meta_config": { - "secret_access_key": "env.AWS_SECRET_ACCESS_KEY", - "region": "us-east-1" - }, "concurrency_and_buffer_size": { "concurrency": 3, "buffer_size": 10 @@ -433,15 +433,16 @@ For production workloads: "bedrock": { "keys": [ { - "value": "env.BEDROCK_API_KEY", "models": ["anthropic.claude-3-sonnet-20240229-v1:0"], "weight": 1.0 + "bedrock_key_config": { + "access_key": "env.AWS_ACCESS_KEY", + "secret_key": "env.AWS_SECRET_ACCESS_KEY", + "session_token": "env.AWS_SESSION_TOKEN", + "region": "us-east-1" + } } ], - "meta_config": { - "secret_access_key": "env.AWS_SECRET_ACCESS_KEY", - "region": "us-east-1" - }, "concurrency_and_buffer_size": { "concurrency": 8, "buffer_size": 40 @@ -480,8 +481,8 @@ export OPENAI_API_KEY="sk-..." export ANTHROPIC_API_KEY="sk-ant-..." # AWS Bedrock -export BEDROCK_API_KEY="your-access-key" -export AWS_SECRET_ACCESS_KEY="your-secret-key" +export AWS_ACCESS_KEY="your-access-key" +export AWS_SECRET_KEY="your-secret-key" # Azure OpenAI export AZURE_API_KEY="your-azure-key" @@ -506,8 +507,9 @@ docker run -p 8080:8080 \ -v $(pwd):/app/data \ -e OPENAI_API_KEY \ -e ANTHROPIC_API_KEY \ - -e BEDROCK_API_KEY \ + -e AWS_ACCESS_KEY \ -e AWS_SECRET_ACCESS_KEY \ + -e AWS_SESSION_TOKEN \ maximhq/bifrost # Legacy: Direct config.json mount @@ -515,8 +517,9 @@ docker run -p 8080:8080 \ -v $(pwd)/config.json:/app/config/config.json \ -e OPENAI_API_KEY \ -e ANTHROPIC_API_KEY \ - -e BEDROCK_API_KEY \ + -e AWS_ACCESS_KEY \ -e AWS_SECRET_ACCESS_KEY \ + -e AWS_SESSION_TOKEN \ maximhq/bifrost ``` diff --git a/docs/usage/http-transport/openapi.json b/docs/usage/http-transport/openapi.json index 6cb2fc10c4..92eae709ac 100644 --- a/docs/usage/http-transport/openapi.json +++ b/docs/usage/http-transport/openapi.json @@ -2635,11 +2635,6 @@ "network_config": { "$ref": "#/components/schemas/NetworkConfig" }, - "meta_config": { - "type": "object", - "additionalProperties": true, - "description": "Provider-specific metadata" - }, "concurrency_and_buffer_size": { "$ref": "#/components/schemas/ConcurrencyAndBufferSize" }, @@ -2662,11 +2657,6 @@ "network_config": { "$ref": "#/components/schemas/NetworkConfig" }, - "meta_config": { - "type": "object", - "additionalProperties": true, - "description": "Provider-specific metadata" - }, "concurrency_and_buffer_size": { "$ref": "#/components/schemas/ConcurrencyAndBufferSize" }, @@ -2692,11 +2682,6 @@ "network_config": { "$ref": "#/components/schemas/NetworkConfig" }, - "meta_config": { - "type": "object", - "additionalProperties": true, - "description": "Provider-specific metadata" - }, "concurrency_and_buffer_size": { "$ref": "#/components/schemas/ConcurrencyAndBufferSize" }, @@ -2788,6 +2773,43 @@ } }, "description": "Vertex key configuration" + }, + "bedrock_key_config": { + "type": "object", + "properties": { + "access_key": { + "type": "string", + "description": "Bedrock access key", + "example": "env.AWS_ACCESS_KEY" + }, + "secret_key": { + "type": "string", + "description": "Bedrock secret key", + "example": "env.AWS_SECRET_KEY" + }, + "session_token": { + "type": "string", + "description": "Bedrock session token", + "example": "env.AWS_SESSION_TOKEN" + }, + "region": { + "type": "string", + "description": "Bedrock region", + "example": "us-east-1" + }, + "arn": { + "type": "string", + "description": "Bedrock ARN", + "example": "arn:aws:iam::123456789012:role/BedrockRole" + }, + "deployments": { + "type": "object", + "description": "Bedrock deployments", + "example": { + "gpt-4o": "gpt-4o-deployment" + } + } + } } } }, diff --git a/docs/usage/key-management.md b/docs/usage/key-management.md index 73fd3977a9..2fa81e5bd0 100644 --- a/docs/usage/key-management.md +++ b/docs/usage/key-management.md @@ -542,15 +542,17 @@ func (a *MyAccount) GetKeysForProvider(ctx *context.Context, provider schemas.Mo "bedrock": { "keys": [ { - "value": "env.AWS_ACCESS_KEY_ID", "models": ["anthropic.claude-3-5-sonnet-20241022-v2:0"], - "weight": 1.0 + "weight": 1.0, + "bedrock_key_config": { + "access_key": "env.AWS_ACCESS_KEY_ID", + "secret_key": "env.AWS_SECRET_ACCESS_KEY", + "session_token": "env.AWS_SESSION_TOKEN", + "region": "us-east-1", + "arn": "arn:aws:iam::123456789012:role/BedrockRole" + } } ], - "meta_config": { - "region": "us-east-1", - "secret_access_key": "env.AWS_SECRET_ACCESS_KEY" - } } } } diff --git a/docs/usage/providers.md b/docs/usage/providers.md index 7cf54f5e4e..aa4be1925e 100644 --- a/docs/usage/providers.md +++ b/docs/usage/providers.md @@ -258,15 +258,17 @@ func useWithFallback(bf *bifrost.Bifrost) { "bedrock": { "keys": [ { - "value": "env.AWS_ACCESS_KEY_ID", "models": ["anthropic.claude-3-5-sonnet-20241022-v2:0"], - "weight": 1.0 + "weight": 1.0, + "bedrock_key_config": { + "access_key": "env.AWS_ACCESS_KEY_ID", + "secret_key": "env.AWS_SECRET_ACCESS_KEY", + "session_token": "env.AWS_SESSION_TOKEN", + "region": "us-east-1", + "arn": "arn:aws:iam::123456789012:role/BedrockRole" + } } ], - "meta_config": { - "region": "us-east-1", - "secret_access_key": "env.AWS_SECRET_ACCESS_KEY" - } } } } diff --git a/tests/core-providers/config/account.go b/tests/core-providers/config/account.go index 3b593d442a..642697d6c1 100644 --- a/tests/core-providers/config/account.go +++ b/tests/core-providers/config/account.go @@ -11,7 +11,6 @@ import ( bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/core/schemas/meta" ) // TestScenarios defines the comprehensive test scenarios @@ -95,9 +94,14 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx *context.Context case schemas.Bedrock: return []schemas.Key{ { - Value: os.Getenv("BEDROCK_API_KEY"), Models: []string{"anthropic.claude-v2:1", "mistral.mixtral-8x7b-instruct-v0:1", "mistral.mistral-large-2402-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0"}, Weight: 1.0, + BedrockKeyConfig: &schemas.BedrockKeyConfig{ + AccessKey: os.Getenv("AWS_ACCESS_KEY"), + SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), + SessionToken: bifrost.Ptr(os.Getenv("AWS_SESSION_TOKEN")), + Region: bifrost.Ptr(getEnvWithDefault("AWS_REGION", "us-east-1")), + }, }, }, nil case schemas.Cohere: @@ -189,10 +193,6 @@ func (account *ComprehensiveTestAccount) GetConfigForProvider(providerKey schema RetryBackoffInitial: 100 * time.Millisecond, RetryBackoffMax: 2 * time.Second, }, - MetaConfig: &meta.BedrockMetaConfig{ - SecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), - Region: bifrost.Ptr(getEnvWithDefault("AWS_REGION", "us-east-1")), - }, ConcurrencyAndBufferSize: schemas.ConcurrencyAndBufferSize{ Concurrency: 3, BufferSize: 10, diff --git a/tests/core-providers/go.mod b/tests/core-providers/go.mod index 885a472723..f2dbd643e4 100644 --- a/tests/core-providers/go.mod +++ b/tests/core-providers/go.mod @@ -7,6 +7,8 @@ require ( github.com/stretchr/testify v1.10.0 ) +replace github.com/maximhq/bifrost/core => ../../core + require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/andybalholm/brotli v1.1.1 // indirect @@ -23,16 +25,21 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect github.com/aws/smithy-go v1.22.3 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/goccy/go-json v0.10.5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mark3labs/mcp-go v0.32.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/cast v1.7.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.60.0 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/text v0.24.0 // indirect diff --git a/tests/core-providers/go.sum b/tests/core-providers/go.sum index 418c746d6a..37105f554f 100644 --- a/tests/core-providers/go.sum +++ b/tests/core-providers/go.sum @@ -28,34 +28,51 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/Xv github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8= github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= -github.com/maximhq/bifrost/core v1.1.11 h1:ir/LLG7xFbavocsa60VWMzDM7uK9E1GKVvyQA27WWF0= -github.com/maximhq/bifrost/core v1.1.11/go.mod h1:yMRCncTgKYBIrECSRVxMbY3BL8CjLbipJlc644jryxc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= @@ -64,6 +81,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= @@ -72,5 +91,7 @@ golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/transports/bifrost-http/handlers/providers.go b/transports/bifrost-http/handlers/providers.go index fd6dd1987a..3f086bbe8c 100644 --- a/transports/bifrost-http/handlers/providers.go +++ b/transports/bifrost-http/handlers/providers.go @@ -12,7 +12,6 @@ import ( "github.com/fasthttp/router" bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/core/schemas/meta" "github.com/maximhq/bifrost/transports/bifrost-http/lib" "github.com/valyala/fasthttp" ) @@ -38,7 +37,6 @@ type AddProviderRequest struct { Provider schemas.ModelProvider `json:"provider"` Keys []schemas.Key `json:"keys"` // API keys for the provider NetworkConfig *schemas.NetworkConfig `json:"network_config,omitempty"` // Network-related settings - MetaConfig *map[string]interface{} `json:"meta_config,omitempty"` // Provider-specific metadata ConcurrencyAndBufferSize *schemas.ConcurrencyAndBufferSize `json:"concurrency_and_buffer_size,omitempty"` // Concurrency settings ProxyConfig *schemas.ProxyConfig `json:"proxy_config,omitempty"` // Proxy configuration SendBackRawResponse *bool `json:"send_back_raw_response,omitempty"` // Include raw response in BifrostResponse @@ -48,7 +46,6 @@ type AddProviderRequest struct { type UpdateProviderRequest struct { Keys []schemas.Key `json:"keys"` // API keys for the provider NetworkConfig schemas.NetworkConfig `json:"network_config"` // Network-related settings - MetaConfig *map[string]interface{} `json:"meta_config,omitempty"` // Provider-specific metadata ConcurrencyAndBufferSize schemas.ConcurrencyAndBufferSize `json:"concurrency_and_buffer_size"` // Concurrency settings ProxyConfig *schemas.ProxyConfig `json:"proxy_config,omitempty"` // Proxy configuration SendBackRawResponse *bool `json:"send_back_raw_response,omitempty"` // Include raw response in BifrostResponse @@ -59,7 +56,6 @@ type ProviderResponse struct { Name schemas.ModelProvider `json:"name"` Keys []schemas.Key `json:"keys"` // API keys for the provider NetworkConfig schemas.NetworkConfig `json:"network_config"` // Network-related settings - MetaConfig *schemas.MetaConfig `json:"meta_config"` // Provider-specific metadata ConcurrencyAndBufferSize schemas.ConcurrencyAndBufferSize `json:"concurrency_and_buffer_size"` // Concurrency settings ProxyConfig *schemas.ProxyConfig `json:"proxy_config"` // Proxy configuration SendBackRawResponse bool `json:"send_back_raw_response"` // Include raw response in BifrostResponse @@ -188,17 +184,6 @@ func (h *ProviderHandler) AddProvider(ctx *fasthttp.RequestCtx) { SendBackRawResponse: req.SendBackRawResponse != nil && *req.SendBackRawResponse, } - // Handle meta config if provided - if req.MetaConfig != nil && len(*req.MetaConfig) > 0 { - // Convert to appropriate meta config type based on provider - metaConfig, err := h.convertToProviderMetaConfig(req.Provider, *req.MetaConfig) - if err != nil { - SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("Invalid meta config: %v", err), h.logger) - return - } - config.MetaConfig = metaConfig - } - // Add provider to store (env vars will be processed by store) if err := h.store.AddProvider(req.Provider, config); err != nil { h.logger.Warn(fmt.Sprintf("Failed to add provider %s: %v", req.Provider, err)) @@ -288,17 +273,6 @@ func (h *ProviderHandler) UpdateProvider(ctx *fasthttp.RequestCtx) { } config.Keys = keys - // Handle meta config if provided - if req.MetaConfig != nil && len(*req.MetaConfig) > 0 { - // Merge new meta config with old, preserving redacted values - metaConfig, err := h.mergeMetaConfig(provider, oldConfigRaw.MetaConfig, *req.MetaConfig) - if err != nil { - SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("Invalid meta config: %v", err), h.logger) - return - } - config.MetaConfig = metaConfig - } - if req.ConcurrencyAndBufferSize.Concurrency == 0 { SendError(ctx, fasthttp.StatusBadRequest, "Concurrency must be greater than 0", h.logger) return @@ -383,33 +357,6 @@ func (h *ProviderHandler) DeleteProvider(ctx *fasthttp.RequestCtx) { SendJSON(ctx, response, h.logger) } -// convertToProviderMetaConfig converts a generic map to the appropriate provider-specific meta config -func (h *ProviderHandler) convertToProviderMetaConfig(provider schemas.ModelProvider, metaConfigMap map[string]interface{}) (*schemas.MetaConfig, error) { - if len(metaConfigMap) == 0 { - return nil, nil - } - - // Convert map to JSON and then to specific meta config type - metaConfigJSON, err := json.Marshal(metaConfigMap) - if err != nil { - return nil, fmt.Errorf("failed to marshal meta config: %w", err) - } - - switch provider { - case schemas.Bedrock: - var bedrockMetaConfig meta.BedrockMetaConfig - if err := json.Unmarshal(metaConfigJSON, &bedrockMetaConfig); err != nil { - return nil, fmt.Errorf("failed to unmarshal Bedrock meta config: %w", err) - } - var metaConfig schemas.MetaConfig = &bedrockMetaConfig - return &metaConfig, nil - - default: - // For providers that don't support meta config, return nil - return nil, nil - } -} - // mergeKeys merges new keys with old, preserving values that are redacted in the new config func (h *ProviderHandler) mergeKeys(provider schemas.ModelProvider, oldRawKeys []schemas.Key, oldRedactedKeys []schemas.Key, keysToAdd []schemas.Key, keysToDelete []schemas.Key, keysToUpdate []schemas.Key) ([]schemas.Key, error) { // Clean up environment variables for deleted and updated keys @@ -499,46 +446,6 @@ func (h *ProviderHandler) mergeKeys(provider schemas.ModelProvider, oldRawKeys [ return resultKeys, nil } -// mergeMetaConfig merges new meta config with old, preserving values that are redacted in the new config -func (h *ProviderHandler) mergeMetaConfig(provider schemas.ModelProvider, oldConfig *schemas.MetaConfig, newConfigMap map[string]interface{}) (*schemas.MetaConfig, error) { - if oldConfig == nil || len(newConfigMap) == 0 { - return h.convertToProviderMetaConfig(provider, newConfigMap) - } - - switch provider { - case schemas.Bedrock: - var newBedrockConfig meta.BedrockMetaConfig - newConfigJSON, _ := json.Marshal(newConfigMap) - if err := json.Unmarshal(newConfigJSON, &newBedrockConfig); err != nil { - return nil, fmt.Errorf("failed to unmarshal new Bedrock meta config: %w", err) - } - - oldBedrockConfig, ok := (*oldConfig).(*meta.BedrockMetaConfig) - if !ok { - return nil, fmt.Errorf("existing meta config type mismatch: expected BedrockMetaConfig") - } - - // Preserve old values if new ones are redacted - if lib.IsRedacted(newBedrockConfig.SecretAccessKey) { - newBedrockConfig.SecretAccessKey = oldBedrockConfig.SecretAccessKey - } - if newBedrockConfig.Region != nil && oldBedrockConfig.Region != nil && lib.IsRedacted(*newBedrockConfig.Region) { - newBedrockConfig.Region = oldBedrockConfig.Region - } - if newBedrockConfig.SessionToken != nil && oldBedrockConfig.SessionToken != nil && lib.IsRedacted(*newBedrockConfig.SessionToken) { - newBedrockConfig.SessionToken = oldBedrockConfig.SessionToken - } - if newBedrockConfig.ARN != nil && oldBedrockConfig.ARN != nil && lib.IsRedacted(*newBedrockConfig.ARN) { - newBedrockConfig.ARN = oldBedrockConfig.ARN - } - - var metaConfig schemas.MetaConfig = &newBedrockConfig - return &metaConfig, nil - default: - return nil, nil - } -} - func (h *ProviderHandler) getProviderResponseFromConfig(provider schemas.ModelProvider, config lib.ProviderConfig) ProviderResponse { if config.NetworkConfig == nil { config.NetworkConfig = &schemas.DefaultNetworkConfig @@ -551,7 +458,6 @@ func (h *ProviderHandler) getProviderResponseFromConfig(provider schemas.ModelPr Name: provider, Keys: config.Keys, NetworkConfig: *config.NetworkConfig, - MetaConfig: config.MetaConfig, ConcurrencyAndBufferSize: *config.ConcurrencyAndBufferSize, ProxyConfig: config.ProxyConfig, SendBackRawResponse: config.SendBackRawResponse, diff --git a/transports/bifrost-http/lib/account.go b/transports/bifrost-http/lib/account.go index f7b1192328..fa3fed19ed 100644 --- a/transports/bifrost-http/lib/account.go +++ b/transports/bifrost-http/lib/account.go @@ -11,7 +11,7 @@ import ( // BaseAccount implements the Account interface for Bifrost. // It manages provider configurations using a in-memory store for persistent storage. -// All data processing (environment variables, meta configs) is done upfront in the store. +// All data processing (environment variables, key configs) is done upfront in the store. type BaseAccount struct { store *ConfigStore // store for in-memory configuration } @@ -50,7 +50,7 @@ func (baseAccount *BaseAccount) GetKeysForProvider(ctx *context.Context, provide } // GetConfigForProvider returns the complete configuration for a specific provider. -// Configuration is already fully processed (environment variables, meta configs) by the store. +// Configuration is already fully processed (environment variables, key configs) by the store. // Implements the Account interface. func (baseAccount *BaseAccount) GetConfigForProvider(providerKey schemas.ModelProvider) (*schemas.ProviderConfig, error) { if baseAccount.store == nil { @@ -74,10 +74,6 @@ func (baseAccount *BaseAccount) GetConfigForProvider(providerKey schemas.ModelPr providerConfig.NetworkConfig = schemas.DefaultNetworkConfig } - if config.MetaConfig != nil { - providerConfig.MetaConfig = *config.MetaConfig - } - if config.ConcurrencyAndBufferSize != nil { providerConfig.ConcurrencyAndBufferSize = *config.ConcurrencyAndBufferSize } else { diff --git a/transports/bifrost-http/lib/config.go b/transports/bifrost-http/lib/config.go index a0c95cd0a0..e1e805494f 100644 --- a/transports/bifrost-http/lib/config.go +++ b/transports/bifrost-http/lib/config.go @@ -18,11 +18,10 @@ type ClientConfig struct { } // ProviderConfig represents the configuration for a specific AI model provider. -// It includes API keys, network settings, provider-specific metadata, and concurrency settings. +// It includes API keys, network settings, and concurrency settings. type ProviderConfig struct { Keys []schemas.Key `json:"keys"` // API keys for the provider with UUIDs NetworkConfig *schemas.NetworkConfig `json:"network_config,omitempty"` // Network-related settings - MetaConfig *schemas.MetaConfig `json:"-"` // Provider-specific metadata ConcurrencyAndBufferSize *schemas.ConcurrencyAndBufferSize `json:"concurrency_and_buffer_size,omitempty"` // Concurrency settings ProxyConfig *schemas.ProxyConfig `json:"proxy_config,omitempty"` // Proxy configuration SendBackRawResponse bool `json:"send_back_raw_response"` // Include raw response in BifrostResponse diff --git a/transports/bifrost-http/lib/models.go b/transports/bifrost-http/lib/models.go index 323035ace9..26dbaf63b9 100644 --- a/transports/bifrost-http/lib/models.go +++ b/transports/bifrost-http/lib/models.go @@ -6,7 +6,6 @@ import ( "time" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/core/schemas/meta" "gorm.io/gorm" ) @@ -23,8 +22,6 @@ type DBProvider struct { Name string `gorm:"type:varchar(50);uniqueIndex;not null" json:"name"` // ModelProvider as string NetworkConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.NetworkConfig ConcurrencyBufferJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.ConcurrencyAndBufferSize - MetaConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.MetaConfig - MetaConfigType string `gorm:"type:varchar(20)" json:"-"` // Type of meta config ("bedrock", etc.) ProxyConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.ProxyConfig SendBackRawResponse bool `json:"send_back_raw_response"` CreatedAt time.Time `gorm:"index;not null" json:"created_at"` @@ -36,7 +33,6 @@ type DBProvider struct { // Virtual fields for runtime use (not stored in DB) NetworkConfig *schemas.NetworkConfig `gorm:"-" json:"network_config,omitempty"` ConcurrencyAndBufferSize *schemas.ConcurrencyAndBufferSize `gorm:"-" json:"concurrency_and_buffer_size,omitempty"` - MetaConfig *schemas.MetaConfig `gorm:"-" json:"meta_config,omitempty"` ProxyConfig *schemas.ProxyConfig `gorm:"-" json:"proxy_config,omitempty"` // Foreign keys Models []DBModel `gorm:"foreignKey:ProviderID;constraint:OnDelete:CASCADE" json:"models"` @@ -71,10 +67,19 @@ type DBKey struct { VertexRegion *string `gorm:"type:varchar(100)" json:"vertex_region,omitempty"` VertexAuthCredentials *string `gorm:"type:text" json:"vertex_auth_credentials,omitempty"` + // Bedrock config fields (embedded) + BedrockAccessKey *string `gorm:"type:varchar(255)" json:"bedrock_access_key,omitempty"` + BedrockSecretKey *string `gorm:"type:text" json:"bedrock_secret_key,omitempty"` + BedrockSessionToken *string `gorm:"type:text" json:"bedrock_session_token,omitempty"` + BedrockRegion *string `gorm:"type:varchar(100)" json:"bedrock_region,omitempty"` + BedrockARN *string `gorm:"type:text" json:"bedrock_arn,omitempty"` + BedrockDeploymentsJSON *string `gorm:"type:text" json:"-"` // JSON serialized map[string]string + // Virtual fields for runtime use (not stored in DB) - Models []string `gorm:"-" json:"models"` - AzureKeyConfig *schemas.AzureKeyConfig `gorm:"-" json:"azure_key_config,omitempty"` - VertexKeyConfig *schemas.VertexKeyConfig `gorm:"-" json:"vertex_key_config,omitempty"` + Models []string `gorm:"-" json:"models"` + AzureKeyConfig *schemas.AzureKeyConfig `gorm:"-" json:"azure_key_config,omitempty"` + VertexKeyConfig *schemas.VertexKeyConfig `gorm:"-" json:"vertex_key_config,omitempty"` + BedrockKeyConfig *schemas.BedrockKeyConfig `gorm:"-" json:"bedrock_key_config,omitempty"` } // DBMCPClient represents an MCP client configuration in the database @@ -116,7 +121,7 @@ type DBEnvKey struct { ID uint `gorm:"primaryKey;autoIncrement" json:"id"` EnvVar string `gorm:"type:varchar(255);index;not null" json:"env_var"` Provider string `gorm:"type:varchar(50);index" json:"provider"` // Empty for MCP/client configs - KeyType string `gorm:"type:varchar(50);not null" json:"key_type"` // "api_key", "azure_config", "vertex_config", "meta_config", "connection_string" + KeyType string `gorm:"type:varchar(50);not null" json:"key_type"` // "api_key", "azure_config", "vertex_config", "bedrock_config", "connection_string" ConfigPath string `gorm:"type:varchar(500);not null" json:"config_path"` // Descriptive path of where this env var is used KeyID string `gorm:"type:varchar(255);index" json:"key_id"` // Key UUID (empty for non-key configs) CreatedAt time.Time `gorm:"index;not null" json:"created_at"` @@ -150,22 +155,6 @@ func (p *DBProvider) BeforeSave(tx *gorm.DB) error { p.ConcurrencyBufferJSON = string(data) } - if p.MetaConfig != nil { - data, err := json.Marshal(*p.MetaConfig) - if err != nil { - return err - } - p.MetaConfigJSON = string(data) - - // Set meta config type for proper deserialization - switch (*p.MetaConfig).(type) { - case *meta.BedrockMetaConfig: - p.MetaConfigType = "bedrock" - default: - - } - } - if p.ProxyConfig != nil { data, err := json.Marshal(p.ProxyConfig) if err != nil { @@ -195,6 +184,15 @@ func (k *DBKey) BeforeSave(tx *gorm.DB) error { k.AzureDeploymentsJSON = &deployments } + if k.BedrockKeyConfig != nil && k.BedrockKeyConfig.Deployments != nil { + data, err := json.Marshal(k.BedrockKeyConfig.Deployments) + if err != nil { + return err + } + deployments := string(data) + k.BedrockDeploymentsJSON = &deployments + } + return nil } @@ -261,24 +259,6 @@ func (p *DBProvider) AfterFind(tx *gorm.DB) error { p.ConcurrencyAndBufferSize = &config } - if p.MetaConfigJSON != "" { - var metaConfig schemas.MetaConfig - - switch p.MetaConfigType { - case "bedrock": - var bedrockConfig meta.BedrockMetaConfig - if err := json.Unmarshal([]byte(p.MetaConfigJSON), &bedrockConfig); err != nil { - return err - } - metaConfig = &bedrockConfig - default: - // Unknown meta config type, skip - return nil - } - - p.MetaConfig = &metaConfig - } - if p.ProxyConfigJSON != "" { var proxyConfig schemas.ProxyConfig if err := json.Unmarshal([]byte(p.ProxyConfigJSON), &proxyConfig); err != nil { @@ -331,6 +311,30 @@ func (k *DBKey) AfterFind(tx *gorm.DB) error { k.VertexKeyConfig = config } + // Reconstruct Bedrock config if fields are present + if k.BedrockAccessKey != nil { + bedrockConfig := &schemas.BedrockKeyConfig{ + AccessKey: *k.BedrockAccessKey, + SessionToken: k.BedrockSessionToken, + Region: k.BedrockRegion, + ARN: k.BedrockARN, + } + + if k.BedrockSecretKey != nil { + bedrockConfig.SecretKey = *k.BedrockSecretKey + } + + if k.BedrockDeploymentsJSON != nil { + var deployments map[string]string + if err := json.Unmarshal([]byte(*k.BedrockDeploymentsJSON), &deployments); err != nil { + return err + } + bedrockConfig.Deployments = deployments + } + + k.BedrockKeyConfig = bedrockConfig + } + return nil } diff --git a/transports/bifrost-http/lib/store.go b/transports/bifrost-http/lib/store.go index ba846c99fc..3e31b00cc9 100644 --- a/transports/bifrost-http/lib/store.go +++ b/transports/bifrost-http/lib/store.go @@ -16,7 +16,6 @@ import ( "github.com/google/uuid" bifrost "github.com/maximhq/bifrost/core" "github.com/maximhq/bifrost/core/schemas" - "github.com/maximhq/bifrost/core/schemas/meta" "gorm.io/gorm" ) @@ -29,7 +28,7 @@ import ( // - Thread-safe operations with read-write mutexes // - Real-time configuration updates via HTTP API // - Automatic database persistence for all changes -// - Support for provider-specific key configurations (Azure, Vertex) and meta configurations (Bedrock) +// - Support for provider-specific key configurations (Azure, Vertex, Bedrock) type ConfigStore struct { mu sync.RWMutex muMCP sync.RWMutex @@ -51,9 +50,9 @@ type ConfigStore struct { type EnvKeyInfo struct { EnvVar string // The environment variable name (without env. prefix) Provider string // The provider this key belongs to (empty for core/mcp configs) - KeyType string // Type of key (e.g., "api_key", "azure_config", "vertex_config", "meta_config", "connection_string") + KeyType string // Type of key (e.g., "api_key", "azure_config", "vertex_config", "bedrock_config", "connection_string") ConfigPath string // Path in config where this env var is used - KeyID string // The key ID this env var belongs to (empty for non-key configs like meta_config, connection_string) + KeyID string // The key ID this env var belongs to (empty for non-key configs like bedrock_config, connection_string) } var DefaultClientConfig = ClientConfig{ @@ -97,8 +96,7 @@ func NewConfigStore(logger schemas.Logger, db *gorm.DB, configPath string) (*Con // This method handles: // - JSON config file parsing // - Environment variable substitution for API keys (env.VARIABLE_NAME) -// - Key-level config processing for Azure and Vertex (Endpoint, APIVersion, ProjectID, Region, AuthCredentials) -// - Provider-specific meta config processing (Bedrock only) +// - Key-level config processing for Azure, Vertex, and Bedrock (Endpoint, APIVersion, ProjectID, Region, AuthCredentials) // - Case conversion for provider names (e.g., "OpenAI" -> "openai") // - In-memory storage for ultra-fast access during request processing // - Graceful handling of missing config files @@ -150,44 +148,12 @@ func (s *ConfigStore) LoadFromConfig(configPath string) error { return fmt.Errorf("failed to unmarshal providers: %w", err) } - // Create a temporary structure to unmarshal the full JSON with proper meta configs - var tempConfig struct { - Providers map[string]struct { - MetaConfig json.RawMessage `json:"meta_config"` - } `json:"providers"` - } - - if err := json.Unmarshal(data, &tempConfig); err != nil { - return fmt.Errorf("failed to unmarshal configuration file: %w", err) - } - // Process each provider configuration for rawProviderName, cfg := range rawProviders { newEnvKeys := make(map[string]struct{}) provider := schemas.ModelProvider(strings.ToLower(rawProviderName)) - // Process meta config if it exists - if tempProvider, exists := tempConfig.Providers[rawProviderName]; exists && len(tempProvider.MetaConfig) > 0 { - processedMetaConfig, envKeys, err := s.processMetaConfigEnvVars(tempProvider.MetaConfig, provider) - - if err != nil { - s.cleanupEnvKeys(string(provider), "", envKeys) - s.logger.Warn(fmt.Sprintf("failed to process env vars in meta config for %s: %v", provider, err)) - continue - } - - // Parse and set the meta config - metaConfig, err := s.parseMetaConfig(processedMetaConfig, provider) - if err != nil { - s.cleanupEnvKeys(string(provider), "", envKeys) - s.logger.Warn(fmt.Sprintf("failed to process meta config for %s: %v", provider, err)) - continue - } else { - cfg.MetaConfig = metaConfig - } - } - // Process environment variables in keys (including key-level configs) for i, key := range cfg.Keys { if key.ID == "" { @@ -232,6 +198,15 @@ func (s *ConfigStore) LoadFromConfig(configPath string) error { continue } } + + // Process Bedrock key config if present + if key.BedrockKeyConfig != nil { + if err := s.processBedrockKeyConfigEnvVars(&cfg.Keys[i], provider, i, newEnvKeys); err != nil { + s.cleanupEnvKeys(string(provider), "", newEnvKeys) + s.logger.Warn(fmt.Sprintf("failed to process Bedrock key config env vars for %s: %v", provider, err)) + continue + } + } } processedProviders[provider] = cfg @@ -281,8 +256,7 @@ func (s *ConfigStore) autoMigrate() error { // This method handles: // - Database configuration loading // - Environment variable substitution for API keys (env.VARIABLE_NAME) -// - Key-level config processing for Azure and Vertex (Endpoint, APIVersion, ProjectID, Region, AuthCredentials) -// - Provider-specific meta config processing (Bedrock only) +// - Key-level config processing for Azure, Vertex, and Bedrock (Endpoint, APIVersion, ProjectID, Region, AuthCredentials) // - In-memory storage for ultra-fast access during request processing // - Auto-detection of providers from environment variables if database is empty func (s *ConfigStore) LoadFromDatabase() error { @@ -366,12 +340,13 @@ func (s *ConfigStore) loadProvidersFromDB() error { keys := make([]schemas.Key, len(dbProvider.Keys)) for i, dbKey := range dbProvider.Keys { keys[i] = schemas.Key{ - ID: dbKey.KeyID, - Value: dbKey.Value, - Models: dbKey.Models, - Weight: dbKey.Weight, - AzureKeyConfig: dbKey.AzureKeyConfig, - VertexKeyConfig: dbKey.VertexKeyConfig, + ID: dbKey.KeyID, + Value: dbKey.Value, + Models: dbKey.Models, + Weight: dbKey.Weight, + AzureKeyConfig: dbKey.AzureKeyConfig, + VertexKeyConfig: dbKey.VertexKeyConfig, + BedrockKeyConfig: dbKey.BedrockKeyConfig, } } @@ -379,7 +354,6 @@ func (s *ConfigStore) loadProvidersFromDB() error { Keys: keys, NetworkConfig: dbProvider.NetworkConfig, ConcurrencyAndBufferSize: dbProvider.ConcurrencyAndBufferSize, - MetaConfig: dbProvider.MetaConfig, ProxyConfig: dbProvider.ProxyConfig, SendBackRawResponse: dbProvider.SendBackRawResponse, } @@ -527,8 +501,7 @@ func (s *ConfigStore) writeConfigToFile(configPath string) error { if key.AzureKeyConfig.APIVersion != nil { path = fmt.Sprintf("providers.%s.keys[%s].azure_key_config.api_version", provider, key.ID) if envVar, ok := envVarsByPath[path]; ok { - apiVersion := "env." + envVar - azureConfig.APIVersion = &apiVersion + azureConfig.APIVersion = bifrost.Ptr("env." + envVar) } else { azureConfig.APIVersion = key.AzureKeyConfig.APIVersion } @@ -567,6 +540,54 @@ func (s *ConfigStore) writeConfigToFile(configPath string) error { redactedKeys[i].VertexKeyConfig = vertexConfig } + + // Restore Bedrock key config if present + if key.BedrockKeyConfig != nil { + bedrockConfig := &schemas.BedrockKeyConfig{ + Deployments: key.BedrockKeyConfig.Deployments, + } + + // Restore AccessKey + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.access_key", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.AccessKey = "env." + envVar + } else { + bedrockConfig.AccessKey = key.BedrockKeyConfig.AccessKey + } + + // Restore SecretKey + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.secret_key", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.SecretKey = "env." + envVar + } else { + bedrockConfig.SecretKey = key.BedrockKeyConfig.SecretKey + } + + // Restore SessionToken + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.session_token", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.SessionToken = bifrost.Ptr("env." + envVar) + } else { + bedrockConfig.SessionToken = key.BedrockKeyConfig.SessionToken + } + + // Restore Region + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.region", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.Region = bifrost.Ptr("env." + envVar) + } else { + bedrockConfig.Region = key.BedrockKeyConfig.Region + } + + // Restore ARN + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.arn", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.ARN = bifrost.Ptr("env." + envVar) + } else { + bedrockConfig.ARN = key.BedrockKeyConfig.ARN + } + + } } // Create provider config with restored env references @@ -582,12 +603,6 @@ func (s *ConfigStore) writeConfigToFile(configPath string) error { providerConfig["concurrency_and_buffer_size"] = config.ConcurrencyAndBufferSize } - // Handle meta config with env variable restoration - if config.MetaConfig != nil { - restoredMetaConfig := s.restoreMetaConfigEnvVars(provider, *config.MetaConfig, envVarsByPath) - providerConfig["meta_config"] = restoredMetaConfig - } - output.Providers[providerName] = providerConfig } @@ -644,56 +659,6 @@ func (s *ConfigStore) getRestoredMCPConfig(envVarsByPath map[string]string) *sch return mcpConfigCopy } -// restoreMetaConfigEnvVars creates a copy of meta config with env variable references restored -func (s *ConfigStore) restoreMetaConfigEnvVars(provider schemas.ModelProvider, metaConfig schemas.MetaConfig, envVarsByPath map[string]string) interface{} { - switch m := metaConfig.(type) { - case *meta.BedrockMetaConfig: - bedrockConfig := *m // Copy the struct - - // Restore secret access key if it came from env var - path := fmt.Sprintf("providers.%s.meta_config.secret_access_key", provider) - if envVar, ok := envVarsByPath[path]; ok { - bedrockConfig.SecretAccessKey = "env." + envVar - } - // Otherwise keep actual value (no asterisk redaction) - - // Restore region if it came from env var - if bedrockConfig.Region != nil { - path = fmt.Sprintf("providers.%s.meta_config.region", provider) - if envVar, ok := envVarsByPath[path]; ok { - region := "env." + envVar - bedrockConfig.Region = ®ion - } - // Otherwise keep actual value (no asterisk redaction) - } - - // Restore session token if it came from env var - if bedrockConfig.SessionToken != nil { - path = fmt.Sprintf("providers.%s.meta_config.session_token", provider) - if envVar, ok := envVarsByPath[path]; ok { - sessionToken := "env." + envVar - bedrockConfig.SessionToken = &sessionToken - } - // Otherwise keep actual value (no asterisk redaction) - } - - // Restore ARN if it came from env var - if bedrockConfig.ARN != nil { - path = fmt.Sprintf("providers.%s.meta_config.arn", provider) - if envVar, ok := envVarsByPath[path]; ok { - arn := "env." + envVar - bedrockConfig.ARN = &arn - } - // Otherwise keep actual value (no asterisk redaction) - } - - return bedrockConfig - - default: - return metaConfig - } -} - // SaveConfig writes the current configuration back to the database func (s *ConfigStore) SaveConfig() error { // Save client config @@ -750,7 +715,6 @@ func (s *ConfigStore) saveProvidersToDB() error { Name: string(providerName), NetworkConfig: providerConfig.NetworkConfig, ConcurrencyAndBufferSize: providerConfig.ConcurrencyAndBufferSize, - MetaConfig: providerConfig.MetaConfig, ProxyConfig: providerConfig.ProxyConfig, SendBackRawResponse: providerConfig.SendBackRawResponse, } @@ -764,13 +728,14 @@ func (s *ConfigStore) saveProvidersToDB() error { dbKeys := make([]DBKey, 0, len(providerConfig.Keys)) for _, key := range providerConfig.Keys { dbKey := DBKey{ - ProviderID: dbProvider.ID, - KeyID: key.ID, - Value: key.Value, - Models: key.Models, - Weight: key.Weight, - AzureKeyConfig: key.AzureKeyConfig, - VertexKeyConfig: key.VertexKeyConfig, + ProviderID: dbProvider.ID, + KeyID: key.ID, + Value: key.Value, + Models: key.Models, + Weight: key.Weight, + AzureKeyConfig: key.AzureKeyConfig, + VertexKeyConfig: key.VertexKeyConfig, + BedrockKeyConfig: key.BedrockKeyConfig, } // Handle Azure config @@ -786,6 +751,15 @@ func (s *ConfigStore) saveProvidersToDB() error { dbKey.VertexAuthCredentials = &key.VertexKeyConfig.AuthCredentials } + // Handle Bedrock config + if key.BedrockKeyConfig != nil { + dbKey.BedrockAccessKey = &key.BedrockKeyConfig.AccessKey + dbKey.BedrockSecretKey = &key.BedrockKeyConfig.SecretKey + dbKey.BedrockSessionToken = key.BedrockKeyConfig.SessionToken + dbKey.BedrockRegion = key.BedrockKeyConfig.Region + dbKey.BedrockARN = key.BedrockKeyConfig.ARN + } + dbKeys = append(dbKeys, dbKey) } @@ -865,121 +839,6 @@ func (s *ConfigStore) saveEnvKeysToDB() error { return nil } -// parseMetaConfig converts raw JSON to the appropriate provider-specific meta config interface -func (s *ConfigStore) parseMetaConfig(rawMetaConfig json.RawMessage, provider schemas.ModelProvider) (*schemas.MetaConfig, error) { - switch provider { - case schemas.Bedrock: - var bedrockMetaConfig meta.BedrockMetaConfig - if err := json.Unmarshal(rawMetaConfig, &bedrockMetaConfig); err != nil { - return nil, fmt.Errorf("failed to unmarshal Bedrock meta config: %w", err) - } - var metaConfig schemas.MetaConfig = &bedrockMetaConfig - return &metaConfig, nil - } - - return nil, fmt.Errorf("unsupported provider for meta config: %s", provider) -} - -// processMetaConfigEnvVars processes environment variables in provider-specific meta configurations. -// This method handles the provider-specific meta config structures and processes environment -// variables in their fields, ensuring type safety and proper field handling. -// -// Supported providers and their processed fields: -// - Bedrock: SecretAccessKey, Region, SessionToken, ARN -// -// For unsupported providers, the meta config is returned unchanged. -// This approach ensures type safety while supporting environment variable substitution. -func (s *ConfigStore) processMetaConfigEnvVars(rawMetaConfig json.RawMessage, provider schemas.ModelProvider) (json.RawMessage, map[string]struct{}, error) { - // Track new environment variables - newEnvKeys := make(map[string]struct{}) - - switch provider { - case schemas.Bedrock: - var bedrockMetaConfig meta.BedrockMetaConfig - if err := json.Unmarshal(rawMetaConfig, &bedrockMetaConfig); err != nil { - return nil, newEnvKeys, fmt.Errorf("failed to unmarshal Bedrock meta config: %w", err) - } - - secretAccessKey, envVar, err := s.processEnvValue(bedrockMetaConfig.SecretAccessKey) - if err != nil { - return nil, newEnvKeys, err - } - if envVar != "" { - newEnvKeys[envVar] = struct{}{} - s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ - EnvVar: envVar, - Provider: string(provider), - KeyType: "meta_config", - ConfigPath: fmt.Sprintf("providers.%s.meta_config.secret_access_key", provider), - KeyID: "", // Empty for meta config entries - }) - } - bedrockMetaConfig.SecretAccessKey = secretAccessKey - - if bedrockMetaConfig.Region != nil { - region, envVar, err := s.processEnvValue(*bedrockMetaConfig.Region) - if err != nil { - return nil, newEnvKeys, err - } - if envVar != "" { - newEnvKeys[envVar] = struct{}{} - s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ - EnvVar: envVar, - Provider: string(provider), - KeyType: "meta_config", - ConfigPath: fmt.Sprintf("providers.%s.meta_config.region", provider), - KeyID: "", // Empty for meta config entries - }) - } - bedrockMetaConfig.Region = ®ion - } - - if bedrockMetaConfig.SessionToken != nil { - sessionToken, envVar, err := s.processEnvValue(*bedrockMetaConfig.SessionToken) - if err != nil { - return nil, newEnvKeys, err - } - if envVar != "" { - newEnvKeys[envVar] = struct{}{} - s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ - EnvVar: envVar, - Provider: string(provider), - KeyType: "meta_config", - ConfigPath: fmt.Sprintf("providers.%s.meta_config.session_token", provider), - KeyID: "", // Empty for meta config entries - }) - } - bedrockMetaConfig.SessionToken = &sessionToken - } - - if bedrockMetaConfig.ARN != nil { - arn, envVar, err := s.processEnvValue(*bedrockMetaConfig.ARN) - if err != nil { - return nil, newEnvKeys, err - } - if envVar != "" { - newEnvKeys[envVar] = struct{}{} - s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ - EnvVar: envVar, - Provider: string(provider), - KeyType: "meta_config", - ConfigPath: fmt.Sprintf("providers.%s.meta_config.arn", provider), - KeyID: "", // Empty for meta config entries - }) - } - bedrockMetaConfig.ARN = &arn - } - - processedJSON, err := json.Marshal(bedrockMetaConfig) - if err != nil { - return nil, newEnvKeys, fmt.Errorf("failed to marshal processed Bedrock meta config: %w", err) - } - return processedJSON, newEnvKeys, nil - } - - return rawMetaConfig, newEnvKeys, nil -} - // GetProviderConfigRaw retrieves the raw, unredacted provider configuration from memory. // This method is for internal use only, particularly by the account implementation. // @@ -1092,8 +951,7 @@ func (s *ConfigStore) GetProviderConfigRedacted(provider schemas.ModelProvider) if key.AzureKeyConfig.APIVersion != nil { path = fmt.Sprintf("providers.%s.keys[%s].azure_key_config.api_version", provider, key.ID) if envVar, ok := envVarsByPath[path]; ok { - apiVersion := "env." + envVar - azureConfig.APIVersion = &apiVersion + azureConfig.APIVersion = bifrost.Ptr("env." + envVar) } else { // APIVersion is not sensitive, keep as-is azureConfig.APIVersion = key.AzureKeyConfig.APIVersion @@ -1133,58 +991,58 @@ func (s *ConfigStore) GetProviderConfigRedacted(provider schemas.ModelProvider) redactedConfig.Keys[i].VertexKeyConfig = vertexConfig } - } - // Handle meta config redaction if present (Bedrock only) - if config.MetaConfig != nil { - redactedMetaConfig := s.redactMetaConfig(provider, *config.MetaConfig, envVarsByPath) - redactedConfig.MetaConfig = &redactedMetaConfig - } + // Redact Bedrock key config if present + if key.BedrockKeyConfig != nil { + bedrockConfig := &schemas.BedrockKeyConfig{ + Deployments: key.BedrockKeyConfig.Deployments, + } - return &redactedConfig, nil -} + // Redact AccessKey + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.access_key", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.AccessKey = "env." + envVar + } else { + bedrockConfig.AccessKey = RedactKey(key.BedrockKeyConfig.AccessKey) + } -// redactMetaConfig creates a redacted copy of meta config based on provider type -// Note: Only Bedrock is supported for meta config now, Azure and Vertex moved to key level -func (s *ConfigStore) redactMetaConfig(provider schemas.ModelProvider, metaConfig schemas.MetaConfig, envVarsByPath map[string]string) schemas.MetaConfig { - switch m := metaConfig.(type) { - case *meta.BedrockMetaConfig: - bedrockConfig := *m // Copy the struct - path := fmt.Sprintf("providers.%s.meta_config.secret_access_key", provider) - if envVar, ok := envVarsByPath[path]; ok { - bedrockConfig.SecretAccessKey = "env." + envVar - } else { - bedrockConfig.SecretAccessKey = RedactKey(bedrockConfig.SecretAccessKey) - } - if bedrockConfig.Region != nil { - path = fmt.Sprintf("providers.%s.meta_config.region", provider) + // Redact SecretKey + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.secret_key", provider, key.ID) if envVar, ok := envVarsByPath[path]; ok { - region := "env." + envVar - bedrockConfig.Region = ®ion + bedrockConfig.SecretKey = "env." + envVar + } else { + bedrockConfig.SecretKey = RedactKey(key.BedrockKeyConfig.SecretKey) } - } - if bedrockConfig.SessionToken != nil { - path = fmt.Sprintf("providers.%s.meta_config.session_token", provider) + + // Redact SessionToken + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.session_token", provider, key.ID) if envVar, ok := envVarsByPath[path]; ok { - sessionToken := "env." + envVar - bedrockConfig.SessionToken = &sessionToken + bedrockConfig.SessionToken = bifrost.Ptr("env." + envVar) } else { - sessionToken := RedactKey(*bedrockConfig.SessionToken) - bedrockConfig.SessionToken = &sessionToken + bedrockConfig.SessionToken = key.BedrockKeyConfig.SessionToken } - } - if bedrockConfig.ARN != nil { - path = fmt.Sprintf("providers.%s.meta_config.arn", provider) + + // Redact Region + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.region", provider, key.ID) if envVar, ok := envVarsByPath[path]; ok { - arn := "env." + envVar - bedrockConfig.ARN = &arn + bedrockConfig.Region = bifrost.Ptr("env." + envVar) + } else { + bedrockConfig.Region = key.BedrockKeyConfig.Region + } + + // Redact ARN + path = fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.arn", provider, key.ID) + if envVar, ok := envVarsByPath[path]; ok { + bedrockConfig.ARN = bifrost.Ptr("env." + envVar) + } else { + bedrockConfig.ARN = key.BedrockKeyConfig.ARN } - } - return &bedrockConfig - default: - return metaConfig + redactedConfig.Keys[i].BedrockKeyConfig = bedrockConfig + } } + + return &redactedConfig, nil } // GetAllProviders returns all configured provider names. @@ -1205,7 +1063,7 @@ func (s *ConfigStore) GetAllProviders() ([]schemas.ModelProvider, error) { // // The method: // - Validates that the provider doesn't already exist -// - Processes environment variables in API keys, key-level configs, and meta configurations +// - Processes environment variables in API keys, and key-level configs // - Stores the processed configuration in memory // - Updates metadata and timestamps func (s *ConfigStore) AddProvider(provider schemas.ModelProvider, config ProviderConfig) error { @@ -1219,29 +1077,6 @@ func (s *ConfigStore) AddProvider(provider schemas.ModelProvider, config Provide newEnvKeys := make(map[string]struct{}) - // Process environment variables in meta config if present - if config.MetaConfig != nil { - rawMetaData, err := json.Marshal(*config.MetaConfig) - if err != nil { - return fmt.Errorf("failed to marshal meta config: %w", err) - } - - processedMetaData, envKeys, err := s.processMetaConfigEnvVars(rawMetaData, provider) - - newEnvKeys = envKeys - if err != nil { - s.cleanupEnvKeys(string(provider), "", newEnvKeys) - return fmt.Errorf("failed to process env vars in meta config: %w", err) - } - - metaConfig, err := s.parseMetaConfig(processedMetaData, provider) - if err != nil { - s.cleanupEnvKeys(string(provider), "", newEnvKeys) - return fmt.Errorf("failed to parse processed meta config: %w", err) - } - config.MetaConfig = metaConfig - } - // Process environment variables in keys (including key-level configs) for i, key := range config.Keys { if key.ID == "" { @@ -1283,6 +1118,14 @@ func (s *ConfigStore) AddProvider(provider schemas.ModelProvider, config Provide return fmt.Errorf("failed to process Vertex key config env vars: %w", err) } } + + // Process Bedrock key config if present + if key.BedrockKeyConfig != nil { + if err := s.processBedrockKeyConfigEnvVars(&config.Keys[i], provider, i, newEnvKeys); err != nil { + s.cleanupEnvKeys(string(provider), "", newEnvKeys) + return fmt.Errorf("failed to process Bedrock key config env vars: %w", err) + } + } } s.Providers[provider] = config @@ -1296,7 +1139,7 @@ func (s *ConfigStore) AddProvider(provider schemas.ModelProvider, config Provide // via the HTTP API and ensures all data processing is done upfront. // // The method: -// - Processes environment variables in API keys, key-level configs, and meta configurations +// - Processes environment variables in API keys, and key-level configs // - Stores the processed configuration in memory // - Updates metadata and timestamps // - Thread-safe operation with write locks @@ -1314,32 +1157,6 @@ func (s *ConfigStore) UpdateProviderConfig(provider schemas.ModelProvider, confi // Track new environment variables being added newEnvKeys := make(map[string]struct{}) - // Process environment variables in meta config if present - if config.MetaConfig != nil { - rawMetaData, err := json.Marshal(*config.MetaConfig) - if err != nil { - return fmt.Errorf("failed to marshal meta config: %w", err) - } - - processedMetaData, envKeys, err := s.processMetaConfigEnvVars(rawMetaData, provider) - if err != nil { - s.cleanupEnvKeys(string(provider), "", envKeys) // Clean up only new vars on failure - return fmt.Errorf("failed to process env vars in meta config: %w", err) - } - - metaConfig, err := s.parseMetaConfig(processedMetaData, provider) - if err != nil { - s.cleanupEnvKeys(string(provider), "", envKeys) // Clean up only new vars on failure - return fmt.Errorf("failed to parse processed meta config: %w", err) - } - config.MetaConfig = metaConfig - - // Add the new env vars to tracking - for envVar := range envKeys { - newEnvKeys[envVar] = struct{}{} - } - } - // Process environment variables in keys (including key-level configs) for i, key := range config.Keys { if key.ID == "" { @@ -1381,6 +1198,14 @@ func (s *ConfigStore) UpdateProviderConfig(provider schemas.ModelProvider, confi return fmt.Errorf("failed to process Vertex key config env vars: %w", err) } } + + // Process Bedrock key config if present + if key.BedrockKeyConfig != nil { + if err := s.processBedrockKeyConfigEnvVars(&config.Keys[i], provider, i, newEnvKeys); err != nil { + s.cleanupEnvKeys(string(provider), "", newEnvKeys) + return fmt.Errorf("failed to process Bedrock key config env vars: %w", err) + } + } } s.Providers[provider] = config @@ -1731,7 +1556,7 @@ func (s *ConfigStore) CleanupEnvKeysForKeys(provider string, keysToDelete []sche for _, info := range infos { // Keep entries that either: // 1. Don't belong to this provider, OR - // 2. Don't have a KeyID (meta config, MCP), OR + // 2. Don't have a KeyID (MCP), OR // 3. Have a KeyID that's not being deleted shouldKeep := info.Provider != provider || info.KeyID == "" || @@ -1772,7 +1597,7 @@ func (s *ConfigStore) CleanupEnvKeysForUpdatedKeys(provider string, keysToUpdate for _, info := range infos { // Keep entries that either: // 1. Don't belong to this provider, OR - // 2. Don't have a KeyID (meta config, MCP), OR + // 2. Don't have a KeyID (MCP), OR // 3. Have a KeyID that's not being updated shouldKeep := info.Provider != provider || info.KeyID == "" || @@ -1959,6 +1784,104 @@ func (s *ConfigStore) processVertexKeyConfigEnvVars(key *schemas.Key, provider s return nil } +// processBedrockKeyConfigEnvVars processes environment variables in Bedrock key configuration +func (s *ConfigStore) processBedrockKeyConfigEnvVars(key *schemas.Key, provider schemas.ModelProvider, keyIndex int, newEnvKeys map[string]struct{}) error { + bedrockConfig := key.BedrockKeyConfig + + // Process AccessKey + processedAccessKey, envVar, err := s.processEnvValue(bedrockConfig.AccessKey) + if err != nil { + return err + } + if envVar != "" { + newEnvKeys[envVar] = struct{}{} + s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ + EnvVar: envVar, + Provider: string(provider), + KeyType: "bedrock_config", + ConfigPath: fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.access_key", provider, key.ID), + KeyID: key.ID, + }) + } + bedrockConfig.AccessKey = processedAccessKey + + // Process SecretKey + processedSecretKey, envVar, err := s.processEnvValue(bedrockConfig.SecretKey) + if err != nil { + return err + } + if envVar != "" { + newEnvKeys[envVar] = struct{}{} + s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ + EnvVar: envVar, + Provider: string(provider), + KeyType: "bedrock_config", + ConfigPath: fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.secret_key", provider, key.ID), + KeyID: key.ID, + }) + } + bedrockConfig.SecretKey = processedSecretKey + + // Process SessionToken if present + if bedrockConfig.SessionToken != nil { + processedSessionToken, envVar, err := s.processEnvValue(*bedrockConfig.SessionToken) + if err != nil { + return err + } + if envVar != "" { + newEnvKeys[envVar] = struct{}{} + s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ + EnvVar: envVar, + Provider: string(provider), + KeyType: "bedrock_config", + ConfigPath: fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.session_token", provider, key.ID), + KeyID: key.ID, + }) + } + bedrockConfig.SessionToken = &processedSessionToken + } + + // Process Region if present + if bedrockConfig.Region != nil { + processedRegion, envVar, err := s.processEnvValue(*bedrockConfig.Region) + if err != nil { + return err + } + if envVar != "" { + newEnvKeys[envVar] = struct{}{} + s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ + EnvVar: envVar, + Provider: string(provider), + KeyType: "bedrock_config", + ConfigPath: fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.region", provider, key.ID), + KeyID: key.ID, + }) + } + bedrockConfig.Region = &processedRegion + } + + // Process ARN if present + if bedrockConfig.ARN != nil { + processedARN, envVar, err := s.processEnvValue(*bedrockConfig.ARN) + if err != nil { + return err + } + if envVar != "" { + newEnvKeys[envVar] = struct{}{} + s.EnvKeys[envVar] = append(s.EnvKeys[envVar], EnvKeyInfo{ + EnvVar: envVar, + Provider: string(provider), + KeyType: "bedrock_config", + ConfigPath: fmt.Sprintf("providers.%s.keys[%s].bedrock_key_config.arn", provider, key.ID), + KeyID: key.ID, + }) + } + bedrockConfig.ARN = &processedARN + } + + return nil +} + // LoadConfiguration implements the hybrid file-database configuration loading approach. // It checks for a config.json file on startup and compares its hash with the stored hash in the database. // If the hash matches, it loads from the database (fast path). diff --git a/transports/bifrost-http/main.go b/transports/bifrost-http/main.go index a36fe4b1a4..982eb501c4 100644 --- a/transports/bifrost-http/main.go +++ b/transports/bifrost-http/main.go @@ -17,7 +17,7 @@ // - Environment variable processing for secure configuration management // - Real-time configuration updates via HTTP API // - Explicit persistence control via POST /config/save endpoint -// - Provider-specific meta config support (Azure, Bedrock, Vertex) +// - Provider-specific key config support (Azure, Bedrock, Vertex) // - Thread-safe operations with concurrent request handling // - Statistics and monitoring endpoints for operational insights // diff --git a/transports/config.example.json b/transports/config.example.json index 9ef78aff5b..11426d8b86 100644 --- a/transports/config.example.json +++ b/transports/config.example.json @@ -73,7 +73,12 @@ "mistral.mistral-large-2402-v1:0", "anthropic.claude-3-sonnet-20240229-v1:0" ], - "weight": 1.0 + "weight": 1.0, + "bedrock_key_config": { + "access_key": "env.AWS_ACCESS_KEY_ID", + "secret_key": "env.AWS_SECRET_ACCESS_KEY", + "region": "us-east-1" + } } ], "network_config": { @@ -82,10 +87,6 @@ "retry_backoff_initial_ms": 100, "retry_backoff_max_ms": 2000 }, - "meta_config": { - "secret_access_key": "env.AWS_SECRET_ACCESS_KEY", - "region": "us-east-1" - }, "concurrency_and_buffer_size": { "concurrency": 3, "buffer_size": 10 diff --git a/ui/components/config/meta-config-renderer.tsx b/ui/components/config/meta-config-renderer.tsx deleted file mode 100644 index 4c33e12ebc..0000000000 --- a/ui/components/config/meta-config-renderer.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react' -import { CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Textarea } from '@/components/ui/textarea' -import { Info, PickaxeIcon } from 'lucide-react' -import { MetaConfig } from '@/lib/types/config' -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' - -type FieldType = 'text' | 'textarea' - -interface MetaField { - name: keyof MetaConfig - label: string - type: FieldType - placeholder?: string - isJson?: boolean -} - -const providerMetaFields: Record = { - bedrock: { - title: 'AWS Bedrock Meta Config', - fields: [ - { - name: 'region', - label: 'Region', - type: 'text', - placeholder: 'us-east-1 or env.AWS_REGION', - }, - { - name: 'secret_access_key', - label: 'Secret Access Key (Optional)', - type: 'text', - placeholder: 'Your AWS secret access key or env.AWS_SECRET_ACCESS_KEY', - }, - { - name: 'session_token', - label: 'Session Token (Optional)', - type: 'text', - placeholder: 'Your AWS session token or env.AWS_SESSION_TOKEN', - }, - { - name: 'arn', - label: 'ARN (Optional)', - type: 'text', - placeholder: 'AWS ARN or env.AWS_ARN', - }, - { - name: 'inference_profiles', - label: 'Inference Profiles (JSON format, Optional)', - type: 'textarea', - placeholder: '{ "model-id": "profile-name" }', - isJson: true, - }, - ], - }, -} - -interface MetaConfigRendererProps { - provider: string - metaConfig: MetaConfig - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onMetaConfigChange: (key: keyof MetaConfig, value: any) => void -} - -const MetaConfigRenderer: React.FC = ({ provider, metaConfig, onMetaConfigChange }) => { - const config = providerMetaFields[provider] - - if (!config) { - return null - } - - const renderField = (field: MetaField) => { - const value = metaConfig[field.name] - - if (field.type === 'textarea') { - return ( -