Skip to content

Commit e92c8fc

Browse files
authored
Add optional OpenRouter attribution (#20)
* Add optional OpenRouter attribution * Review fixes
1 parent db1badf commit e92c8fc

4 files changed

Lines changed: 41 additions & 15 deletions

File tree

docs/content/docs/configuration/providers.mdx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ type = "open_ai"
4545
api_key = "${OPENROUTER_API_KEY}"
4646
base_url = "https://openrouter.ai/api/v1"
4747

48-
# OpenRouter-specific headers
49-
[providers.openrouter.headers]
50-
HTTP-Referer = "https://myapp.example.com"
51-
X-Title = "My Application"
48+
# App attribution headers are sent automatically:
49+
# HTTP-Referer: https://hadriangateway.com
50+
# X-OpenRouter-Title: Hadrian Gateway
51+
# Override to customize, or set to "" to opt out:
52+
# [providers.openrouter.headers]
53+
# HTTP-Referer = "https://myapp.example.com"
54+
# X-OpenRouter-Title = "My Application"
5255
```
5356

5457
**Ollama** (local, no API key needed):

src/bin/record_fixtures.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,8 +1919,8 @@ async fn record_fixture(
19191919
// Add OpenRouter-specific headers
19201920
if def.provider == "openrouter" {
19211921
request = request
1922-
.header("HTTP-Referer", "https://github.com/ScriptSmith/hadrian")
1923-
.header("X-Title", "Hadrian Gateway Fixture Recording");
1922+
.header("HTTP-Referer", "https://hadriangateway.com")
1923+
.header("X-OpenRouter-Title", "Hadrian Gateway");
19241924
}
19251925
}
19261926

src/config/providers.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,8 @@ pub struct OpenAiProviderConfig {
606606
pub model_aliases: HashMap<String, String>,
607607

608608
/// Custom headers to include in requests.
609-
/// Useful for provider-specific headers like OpenRouter's HTTP-Referer.
609+
/// For OpenRouter providers, `HTTP-Referer` and `X-OpenRouter-Title` are set
610+
/// automatically for app attribution. Override here to customize or opt out.
610611
#[serde(default)]
611612
pub headers: HashMap<String, String>,
612613

src/providers/open_ai/mod.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,25 @@ impl OpenAICompatibleProvider {
6666
registry: &CircuitBreakerRegistry,
6767
) -> Self {
6868
let circuit_breaker = registry.get_or_create(provider_name, &config.circuit_breaker);
69+
let base_url = config.base_url.trim_end_matches('/').to_string();
70+
71+
let mut headers = config.headers.clone();
72+
73+
// OpenRouter app attribution: send Hadrian metadata by default unless
74+
// the user has explicitly set these headers (opt-out by overriding).
75+
if base_url.contains("openrouter.ai") {
76+
headers
77+
.entry("HTTP-Referer".to_string())
78+
.or_insert_with(|| "https://hadriangateway.com".to_string());
79+
headers
80+
.entry("X-OpenRouter-Title".to_string())
81+
.or_insert_with(|| "Hadrian Gateway".to_string());
82+
}
6983

7084
Self {
7185
api_key: config.api_key.clone(),
72-
base_url: config.base_url.trim_end_matches('/').to_string(),
73-
headers: config.headers.clone(),
86+
base_url,
87+
headers,
7488
timeout: Duration::from_secs(config.timeout_secs),
7589
retry: config.retry.clone(),
7690
circuit_breaker_config: config.circuit_breaker.clone(),
@@ -86,9 +100,13 @@ impl OpenAICompatibleProvider {
86100
request
87101
};
88102

89-
let request = self.headers.iter().fold(request, |req, (key, value)| {
90-
req.header(key.as_str(), value.as_str())
91-
});
103+
let request = self
104+
.headers
105+
.iter()
106+
.filter(|(_, value)| !value.is_empty())
107+
.fold(request, |req, (key, value)| {
108+
req.header(key.as_str(), value.as_str())
109+
});
92110

93111
request.timeout(self.timeout)
94112
}
@@ -108,9 +126,13 @@ impl OpenAICompatibleProvider {
108126
request
109127
};
110128

111-
let request = self.headers.iter().fold(request, |req, (key, value)| {
112-
req.header(key.as_str(), value.as_str())
113-
});
129+
let request = self
130+
.headers
131+
.iter()
132+
.filter(|(_, value)| !value.is_empty())
133+
.fold(request, |req, (key, value)| {
134+
req.header(key.as_str(), value.as_str())
135+
});
114136

115137
request.timeout(self.timeout)
116138
}

0 commit comments

Comments
 (0)