Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions agent-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@
"examples": [
"CUSTOM_PROVIDER_API_KEY"
]
},
"headers": {
"type": "object",
"description": "Custom HTTP headers to include in requests. Header values can reference environment variables using ${VAR_NAME} syntax.",
"additionalProperties": {
"type": "string"
},
"examples": [
{
"cf-aig-authorization": "Bearer ${CLOUDFLARE_AI_GATEWAY_TOKEN}",
"x-custom-header": "value"
}
]
}
},
"required": [
Expand Down Expand Up @@ -525,6 +538,19 @@
"type": "string",
"description": "Token key for authentication"
},
"headers": {
"type": "object",
"description": "Custom HTTP headers to include in requests to this model's provider. Header values can reference environment variables using ${VAR_NAME} syntax.",
"additionalProperties": {
"type": "string"
},
"examples": [
{
"cf-aig-authorization": "Bearer ${CLOUDFLARE_AI_GATEWAY_TOKEN}",
"x-custom-header": "value"
}
]
},
"provider_opts": {
"type": "object",
"description": "Provider-specific options. dmr: runtime_flags. anthropic/amazon-bedrock (Claude): interleaved_thinking (boolean, default true). openai/anthropic/google: rerank_prompt (string) to fully override the system prompt used for RAG reranking (advanced - prefer using results.reranking.criteria for domain-specific guidance).",
Expand Down
44 changes: 15 additions & 29 deletions examples/custom_provider.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,31 @@

# Define custom providers with reusable configuration
providers:
# Example: A custom OpenAI Chat Completions compatible API gateway
my_gateway:
api_type: openai_chatcompletions # Use the Chat Completions API schema
base_url: https://api.example.com/
token_key: API_KEY_ENV_VAR_NAME # Environment variable containing the API token

# Example: A custom OpenAI Responses compatible API gateway
responses_provider:
api_type: openai_responses
base_url: https://responses.example.com/
token_key: API_KEY_ENV_VAR_NAME
# Example: Cloudflare AI Gateway with custom headers
cloudflare_gateway:
api_type: openai_chatcompletions
base_url: https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat
token_key: GOOGLE_API_KEY # Standard Authorization header for provider auth
headers:
# Custom header for gateway authentication with environment variable expansion
cf-aig-authorization: Bearer ${CLOUDFLARE_AI_GATEWAY_TOKEN}

# Define models that use the custom providers
models:
# Model using the custom gateway provider
gateway_gpt4o:
provider: my_gateway
model: gpt-4o
max_tokens: 32768
temperature: 0.7

# Model using the responses provider
responses_model:
provider: responses_provider
model: gpt-5
max_tokens: 16000
# Model using Cloudflare AI Gateway with custom headers
gemini_via_cloudflare:
provider: cloudflare_gateway
model: google-ai-studio/gemini-3-flash-preview
max_tokens: 8000
temperature: 0.7

# Define agents that use the models
agents:
root:
model: responses_model
model: gemini_via_cloudflare
description: Main assistant using the custom gateway
instruction: |
You are a helpful AI assistant. Be concise and helpful in your responses.

# Example using shorthand syntax: provider_name/model_name
# The provider defaults (base_url, token_key, api_type) are automatically applied
subagent:
model: my_gateway/gpt-4o-mini
description: Sub-agent for specialized tasks
instruction: |
You are a specialized assistant for specific tasks.
32 changes: 32 additions & 0 deletions pkg/config/gather.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"maps"
"os"
"regexp"
"slices"
"strings"

Expand Down Expand Up @@ -122,6 +123,37 @@ func addEnvVarsForModelConfig(model *latest.ModelConfig, customProviders map[str
}
}
}

// Gather env vars from headers (model-level and provider-level) and base URLs
gatherEnvVarsFromHeaders(model.Headers, requiredEnv)
gatherEnvVarsFromString(model.BaseURL, requiredEnv)
if customProviders != nil {
if provCfg, exists := customProviders[model.Provider]; exists {
gatherEnvVarsFromHeaders(provCfg.Headers, requiredEnv)
gatherEnvVarsFromString(provCfg.BaseURL, requiredEnv)
}
}
}

// envVarPattern matches ${VAR} and $VAR references in strings.
var envVarPattern = regexp.MustCompile(`\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)`)

// gatherEnvVarsFromHeaders extracts environment variable names referenced in header values.
func gatherEnvVarsFromHeaders(headers map[string]string, requiredEnv map[string]bool) {
for _, value := range headers {
gatherEnvVarsFromString(value, requiredEnv)
}
}

// gatherEnvVarsFromString extracts environment variable names from a string containing $VAR or ${VAR}.
func gatherEnvVarsFromString(s string, requiredEnv map[string]bool) {
for _, match := range envVarPattern.FindAllStringSubmatch(s, -1) {
if match[1] != "" {
requiredEnv[match[1]] = true
} else if match[2] != "" {
requiredEnv[match[2]] = true
}
}
}

func GatherEnvVarsForTools(ctx context.Context, cfg *latest.Config) ([]string, error) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/latest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ type ProviderConfig struct {
BaseURL string `json:"base_url"`
// TokenKey is the environment variable name containing the API token
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
// Header values can reference environment variables using ${VAR_NAME} syntax.
Headers map[string]string `json:"headers,omitempty"`
}

// FallbackConfig represents fallback model configuration for an agent.
Expand Down Expand Up @@ -392,6 +395,9 @@ type ModelConfig struct {
BaseURL string `json:"base_url,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests to this model's provider.
// Header values can reference environment variables using ${VAR_NAME} syntax.
Headers map[string]string `json:"headers,omitempty"`
// ProviderOpts allows provider-specific options.
ProviderOpts map[string]any `json:"provider_opts,omitempty"`
TrackUsage *bool `json:"track_usage,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/v3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type ProviderConfig struct {
BaseURL string `json:"base_url"`
// TokenKey is the environment variable name containing the API token
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
// Header values can reference environment variables using ${VAR_NAME} syntax.
Headers map[string]string `json:"headers,omitempty"`
}

// AgentConfig represents a single agent configuration
Expand Down Expand Up @@ -70,6 +73,8 @@ type ModelConfig struct {
BaseURL string `json:"base_url,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
Headers map[string]string `json:"headers,omitempty"`
// ProviderOpts allows provider-specific options. Currently used for "dmr" provider only.
ProviderOpts map[string]any `json:"provider_opts,omitempty"`
TrackUsage *bool `json:"track_usage,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/v4/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ type ProviderConfig struct {
BaseURL string `json:"base_url"`
// TokenKey is the environment variable name containing the API token
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
// Header values can reference environment variables using ${VAR_NAME} syntax.
Headers map[string]string `json:"headers,omitempty"`
}

// FallbackConfig represents fallback model configuration for an agent.
Expand Down Expand Up @@ -270,6 +273,8 @@ type ModelConfig struct {
BaseURL string `json:"base_url,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
Headers map[string]string `json:"headers,omitempty"`
// ProviderOpts allows provider-specific options.
ProviderOpts map[string]any `json:"provider_opts,omitempty"`
TrackUsage *bool `json:"track_usage,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/v5/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ type ProviderConfig struct {
BaseURL string `json:"base_url"`
// TokenKey is the environment variable name containing the API token
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
// Header values can reference environment variables using ${VAR_NAME} syntax.
Headers map[string]string `json:"headers,omitempty"`
}

// FallbackConfig represents fallback model configuration for an agent.
Expand Down Expand Up @@ -369,6 +372,8 @@ type ModelConfig struct {
BaseURL string `json:"base_url,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
Headers map[string]string `json:"headers,omitempty"`
// ProviderOpts allows provider-specific options.
ProviderOpts map[string]any `json:"provider_opts,omitempty"`
TrackUsage *bool `json:"track_usage,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/v6/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ type ProviderConfig struct {
BaseURL string `json:"base_url"`
// TokenKey is the environment variable name containing the API token
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
// Header values can reference environment variables using ${VAR_NAME} syntax.
Headers map[string]string `json:"headers,omitempty"`
}

// FallbackConfig represents fallback model configuration for an agent.
Expand Down Expand Up @@ -392,6 +395,8 @@ type ModelConfig struct {
BaseURL string `json:"base_url,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
TokenKey string `json:"token_key,omitempty"`
// Headers allows custom HTTP headers to be included in requests.
Headers map[string]string `json:"headers,omitempty"`
// ProviderOpts allows provider-specific options.
ProviderOpts map[string]any `json:"provider_opts,omitempty"`
TrackUsage *bool `json:"track_usage,omitempty"`
Expand Down
37 changes: 36 additions & 1 deletion pkg/model/provider/anthropic/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,43 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, env environment.Pro
option.WithHTTPClient(httpclient.NewHTTPClient()),
}
if cfg.BaseURL != "" {
requestOptions = append(requestOptions, option.WithBaseURL(cfg.BaseURL))
expandedBaseURL, err := environment.Expand(ctx, cfg.BaseURL, env)
if err != nil {
return nil, fmt.Errorf("expanding base_url: %w", err)
}
requestOptions = append(requestOptions, option.WithBaseURL(expandedBaseURL))
}

// Apply custom headers from provider config if present
if cfg.ProviderOpts != nil {
if headers, exists := cfg.ProviderOpts["headers"]; exists {
headersMap := make(map[string]string)
switch h := headers.(type) {
case map[string]string:
headersMap = h
case map[interface{}]interface{}:
for k, v := range h {
keyStr, okKey := k.(string)
valStr, okVal := v.(string)
if !okKey || !okVal {
return nil, fmt.Errorf("invalid header key/value type: key=%T, value=%T", k, v)
}
headersMap[keyStr] = valStr
}
default:
return nil, fmt.Errorf("invalid headers configuration: expected map[string]string, got %T", headers)
}
for key, value := range headersMap {
expandedValue, err := environment.Expand(ctx, value, env)
if err != nil {
return nil, fmt.Errorf("expanding header %s: %w", key, err)
}
requestOptions = append(requestOptions, option.WithHeader(key, expandedValue))
slog.Debug("Applied custom header", "header", key, "provider", cfg.Provider)
}
}
}

client := anthropic.NewClient(requestOptions...)
anthropicClient.clientFn = func(context.Context) (anthropic.Client, error) {
return client, nil
Expand Down
Loading