diff --git a/docs/content/docs/configuration/providers.mdx b/docs/content/docs/configuration/providers.mdx index 59f7030..7c3ecdf 100644 --- a/docs/content/docs/configuration/providers.mdx +++ b/docs/content/docs/configuration/providers.mdx @@ -45,10 +45,13 @@ type = "open_ai" api_key = "${OPENROUTER_API_KEY}" base_url = "https://openrouter.ai/api/v1" -# OpenRouter-specific headers -[providers.openrouter.headers] -HTTP-Referer = "https://myapp.example.com" -X-Title = "My Application" +# App attribution headers are sent automatically: +# HTTP-Referer: https://hadriangateway.com +# X-OpenRouter-Title: Hadrian Gateway +# Override to customize, or set to "" to opt out: +# [providers.openrouter.headers] +# HTTP-Referer = "https://myapp.example.com" +# X-OpenRouter-Title = "My Application" ``` **Ollama** (local, no API key needed): diff --git a/src/bin/record_fixtures.rs b/src/bin/record_fixtures.rs index a61a9bb..bf2c130 100644 --- a/src/bin/record_fixtures.rs +++ b/src/bin/record_fixtures.rs @@ -1919,8 +1919,8 @@ async fn record_fixture( // Add OpenRouter-specific headers if def.provider == "openrouter" { request = request - .header("HTTP-Referer", "https://github.com/ScriptSmith/hadrian") - .header("X-Title", "Hadrian Gateway Fixture Recording"); + .header("HTTP-Referer", "https://hadriangateway.com") + .header("X-OpenRouter-Title", "Hadrian Gateway"); } } diff --git a/src/config/providers.rs b/src/config/providers.rs index 6f835d2..9c11b27 100644 --- a/src/config/providers.rs +++ b/src/config/providers.rs @@ -606,7 +606,8 @@ pub struct OpenAiProviderConfig { pub model_aliases: HashMap, /// Custom headers to include in requests. - /// Useful for provider-specific headers like OpenRouter's HTTP-Referer. + /// For OpenRouter providers, `HTTP-Referer` and `X-OpenRouter-Title` are set + /// automatically for app attribution. Override here to customize or opt out. #[serde(default)] pub headers: HashMap, diff --git a/src/providers/open_ai/mod.rs b/src/providers/open_ai/mod.rs index 3a51914..0697c96 100644 --- a/src/providers/open_ai/mod.rs +++ b/src/providers/open_ai/mod.rs @@ -66,11 +66,25 @@ impl OpenAICompatibleProvider { registry: &CircuitBreakerRegistry, ) -> Self { let circuit_breaker = registry.get_or_create(provider_name, &config.circuit_breaker); + let base_url = config.base_url.trim_end_matches('/').to_string(); + + let mut headers = config.headers.clone(); + + // OpenRouter app attribution: send Hadrian metadata by default unless + // the user has explicitly set these headers (opt-out by overriding). + if base_url.contains("openrouter.ai") { + headers + .entry("HTTP-Referer".to_string()) + .or_insert_with(|| "https://hadriangateway.com".to_string()); + headers + .entry("X-OpenRouter-Title".to_string()) + .or_insert_with(|| "Hadrian Gateway".to_string()); + } Self { api_key: config.api_key.clone(), - base_url: config.base_url.trim_end_matches('/').to_string(), - headers: config.headers.clone(), + base_url, + headers, timeout: Duration::from_secs(config.timeout_secs), retry: config.retry.clone(), circuit_breaker_config: config.circuit_breaker.clone(), @@ -86,9 +100,13 @@ impl OpenAICompatibleProvider { request }; - let request = self.headers.iter().fold(request, |req, (key, value)| { - req.header(key.as_str(), value.as_str()) - }); + let request = self + .headers + .iter() + .filter(|(_, value)| !value.is_empty()) + .fold(request, |req, (key, value)| { + req.header(key.as_str(), value.as_str()) + }); request.timeout(self.timeout) } @@ -108,9 +126,13 @@ impl OpenAICompatibleProvider { request }; - let request = self.headers.iter().fold(request, |req, (key, value)| { - req.header(key.as_str(), value.as_str()) - }); + let request = self + .headers + .iter() + .filter(|(_, value)| !value.is_empty()) + .fold(request, |req, (key, value)| { + req.header(key.as_str(), value.as_str()) + }); request.timeout(self.timeout) }