Skip to content

Add optional OpenRouter attribution#20

Merged
ScriptSmith merged 2 commits intomainfrom
openrouter-attribution
Mar 22, 2026
Merged

Add optional OpenRouter attribution#20
ScriptSmith merged 2 commits intomainfrom
openrouter-attribution

Conversation

@ScriptSmith
Copy link
Owner

No description provided.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR adds automatic OpenRouter app attribution headers (HTTP-Referer: https://hadriangateway.com and X-OpenRouter-Title: Hadrian Gateway) for any OpenAI-compatible provider whose base_url contains openrouter.ai, with a clean opt-out mechanism via empty-string overrides in the config.

  • src/providers/open_ai/mod.rs: Uses HashMap::entry().or_insert_with() to inject defaults only when the user hasn't already configured those keys. The build_request and build_multipart_request methods filter out empty-string headers to support opt-out. Logic is correct and well-structured.
  • src/bin/record_fixtures.rs: Aligns fixture-recording headers with the new URL and header name — note that this path detects OpenRouter by provider name ("openrouter") while the production path detects by URL; this divergence is acceptable for a dev tool.
  • src/config/providers.rs: Doc comment updated to explain the new auto-injection behavior.
  • docs/content/docs/configuration/providers.mdx: Correctly documents the automatic headers and the opt-out pattern.
  • One minor concern: the opt-out is case-sensitive — a user who configures http-referer = "" (wrong case) won't successfully opt out, as the entry() lookup won't match, and the default will be inserted silently. A comment or key normalization would make this more robust.

Confidence Score: 5/5

  • Safe to merge — the attribution logic is correct, the opt-out works for properly-cased keys, and all four files are consistent with each other.
  • The core logic using entry().or_insert_with() is sound: user-provided headers always win, defaults are injected only when absent, and empty-string values are filtered before sending. The only issue is a case-sensitivity footgun in the opt-out path (P2 style), which won't affect users following the documented example. Documentation, config struct, fixture tool, and production code are all consistent.
  • No files require special attention.

Important Files Changed

Filename Overview
src/providers/open_ai/mod.rs Injects OpenRouter attribution headers (HTTP-Referer, X-OpenRouter-Title) by default when the base URL contains openrouter.ai, with an opt-out via empty-string override. Logic is correct; case-sensitive key matching is a minor footgun for the opt-out path.
src/config/providers.rs Doc comment updated to reflect automatic OpenRouter header injection. No logic changes.
src/bin/record_fixtures.rs Updated fixture recording headers to match the new names (X-OpenRouter-Title) and URL (hadriangateway.com). Uses provider-name-based detection (def.provider == "openrouter") rather than URL-based, which is consistent with it being a dev tool.
docs/content/docs/configuration/providers.mdx Documentation updated to reflect automatic header injection and correct opt-out instructions. Consistent with the code implementation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[from_config_with_registry] --> B{base_url contains\nopenrouter.ai?}
    B -- No --> D[Use config.headers as-is]
    B -- Yes --> C{HTTP-Referer key\nalready in headers?}
    C -- No --> E[Insert HTTP-Referer:\nhttps://hadriangateway.com]
    C -- Yes --> F[Keep user value]
    E --> G{X-OpenRouter-Title key\nalready in headers?}
    F --> G
    G -- No --> H[Insert X-OpenRouter-Title:\nHadrian Gateway]
    G -- Yes --> I[Keep user value]
    H --> J[build_request]
    I --> J
    D --> J
    J --> K{Filter headers:\nvalue is empty?}
    K -- Empty string --> L[Skip header\nopt-out achieved]
    K -- Non-empty --> M[Add header to request]
Loading

Comments Outside Diff (1)

  1. src/providers/open_ai/mod.rs, line 75-82 (link)

    Case-sensitive opt-out may silently fail

    The opt-out mechanism relies on exact case-sensitive key matching. If a user configures http-referer = "" or Http-Referer = "" (wrong casing) in their TOML config, the entry("HTTP-Referer") call won't find it, so the default Hadrian attribution value gets inserted — and the user's empty-string opt-out is silently ignored.

    In practice, users following the documented example will use the correct casing, but it's a subtle footgun. A more robust approach would be to normalize header keys to lowercase before the entry() check (HTTP headers are case-insensitive at the wire level anyway), or at least call it out with a comment:

    // Note: key matching is case-sensitive. Users must use the exact casing
    // shown in the docs ("HTTP-Referer", "X-OpenRouter-Title") to override.
    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());
    }
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/providers/open_ai/mod.rs
Line: 75-82

Comment:
**Case-sensitive opt-out may silently fail**

The opt-out mechanism relies on exact case-sensitive key matching. If a user configures `http-referer = ""` or `Http-Referer = ""` (wrong casing) in their TOML config, the `entry("HTTP-Referer")` call won't find it, so the default Hadrian attribution value gets inserted — and the user's empty-string opt-out is silently ignored.

In practice, users following the documented example will use the correct casing, but it's a subtle footgun. A more robust approach would be to normalize header keys to lowercase before the `entry()` check (HTTP headers are case-insensitive at the wire level anyway), or at least call it out with a comment:

```rust
// Note: key matching is case-sensitive. Users must use the exact casing
// shown in the docs ("HTTP-Referer", "X-OpenRouter-Title") to override.
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());
}
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Review fixes"

@ScriptSmith
Copy link
Owner Author

@greptile-apps

@ScriptSmith ScriptSmith merged commit e92c8fc into main Mar 22, 2026
20 checks passed
@ScriptSmith ScriptSmith deleted the openrouter-attribution branch March 22, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant