How to Configure Custom HTTP Headers for LLM Requests in ChocolateLMLite

Extend the OpenRouterHttpHandler class to read a dictionary of custom headers from the global settings and inject them into every outgoing HTTP request to your LLM provider.

ChocolateLMLite routes all LLM API traffic through a centralized HTTP wrapper called OpenRouterHttpHandler (located in src/OpenRouterHttpHandler.cs). By tapping into the existing configuration pipeline—specifically the GeneralSettings model and FileManager accessor—you can attach arbitrary headers such as custom User-Agent strings, API version markers, or authentication tokens to every request without touching the core generation logic in gpsnmeajp/chocolatelmlite.

How the OpenRouterHttpHandler Manages LLM Requests

All LLM requests in ChocolateLMLite—including calls from SummaryLlm, SearchLlm, and ImageGenerater—flow through a single chokepoint: the OpenRouterHttpHandler class in src/OpenRouterHttpHandler.cs.

The handler currently injects two static identification headers into every HttpRequestMessage:

request.Headers.Add("X-Title", "Chocolate LM Lite");
request.Headers.Add("HTTP-Referer", "https://github.com/gpsnmeajp/chocolatelmlite");

Because LLM.GenerateResponseAsync instantiates this handler (see src/LLM.cs, lines 31–38), adding custom header logic here automatically applies to all LLM traffic. The following steps extend this handler to read user-defined headers from settings.json.

Step-by-Step Implementation

Add Custom Headers to settings.json

Locate settings.json in your runtime directory (next to the executable). Add a CustomHeaders dictionary inside the existing GeneralSettings object:

{
  "GeneralSettings": {
    "CustomHeaders": {
      "User-Agent": "ChocolateLMLite/1.0",
      "X-Api-Version": "2024-03",
      "Authorization": "Bearer custom-token"
    }
  }
}

Keys must be valid HTTP header names; values are inserted verbatim into the request.

Update the GeneralSettings Model

Open src/GeneralSettings.cs (or the equivalent settings POCO) and add a property to capture the dictionary:

public Dictionary<string, string>? CustomHeaders { get; set; }

The existing JsonSerializer configuration automatically populates this property when settings.json is loaded.

Expose Headers via FileManager

In src/FileManager.cs, add a public accessor method to surface the dictionary to the HTTP handler:

public IReadOnlyDictionary<string, string>? GetCustomHeaders()
{
    return generalSettings?.CustomHeaders;
}

This method leverages the already-deserialized generalSettings instance used throughout the project.

Inject Headers in OpenRouterHttpHandler.cs

Edit src/OpenRouterHttpHandler.cs and modify the SendAsync method. After the two fixed headers, iterate over the custom dictionary and add each entry:

protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    // Fixed headers required by the project
    request.Headers.Add("X-Title", "Chocolate LM Lite");
    request.Headers.Add("HTTP-Referer", "https://github.com/gpsnmeajp/chocolatelmlite");

    // Inject user-defined headers from settings.json
    var customHeaders = _fileManager.GetCustomHeaders();
    if (customHeaders != null)
    {
        foreach (var kvp in customHeaders)
        {
            // Prevent duplicate header exceptions by removing existing values
            if (request.Headers.Contains(kvp.Key))
                request.Headers.Remove(kvp.Key);
            
            request.Headers.Add(kvp.Key, kvp.Value);
        }
    }

    // Continue with existing logic (logging, debug files, etc.)
    return await base.SendAsync(request, cancellationToken);
}

Ensure the handler has access to the FileManager instance (typically passed via constructor injection or a static accessor depending on your fork).

Testing and Verification

Run the application and capture an outgoing request using a local proxy such as mitmproxy, Fiddler, or the built-in debug logs. The request headers should now include your custom entries alongside the default identifiers:

X-Title: Chocolate LM Lite
HTTP-Referer: https://github.com/gpsnmeajp/chocolatelmlite
User-Agent: ChocolateLMLite/1.0
X-Api-Version: 2024-03

If headers are missing, verify that:

  • settings.json contains valid JSON with no trailing commas
  • FileManager.GetCustomHeaders() returns a non-null dictionary at runtime
  • The modified assembly is compiled and deployed

Advanced Configuration: Per-LLM-Type Headers

If you require distinct headers for specific LLM types—such as different Authorization tokens for summarization versus image generation—you can extend the pattern:

  1. Add separate dictionaries to GeneralSettings (e.g., SummaryHeaders, SearchHeaders, ImageHeaders)
  2. In src/SummaryLlm.cs, src/SearchLlm.cs, or src/ImageGenerater.cs, read the appropriate dictionary from FileManager
  3. Pass the specific headers into a modified OpenRouterHttpHandler constructor or use a factory pattern to instantiate handler variants

This approach maintains clean separation while supporting provider-specific requirements for each LLM workflow.

Summary

  • Centralized Handler: All LLM requests pass through src/OpenRouterHttpHandler.cs, making it the ideal injection point for global headers.
  • Configuration Flow: Add headers to settings.json → deserialize into GeneralSettings → expose via FileManager.GetCustomHeaders() → consume in the HTTP handler.
  • Duplicate Safety: The implementation removes existing headers before adding custom ones to avoid ArgumentException collisions.
  • Universal Coverage: Because LLM.GenerateResponseAsync instantiates the handler, custom headers automatically apply to text generation, search, and image endpoints.

Frequently Asked Questions

Where is the HTTP handler instantiated in ChocolateLMLite?

The OpenRouterHttpHandler is instantiated inside LLM.GenerateResponseAsync (see src/LLM.cs, lines 31–38). This centralized instantiation ensures that every LLM request—whether for summarization, search, or image generation—routes through the same handler pipeline.

Can I override the default X-Title and HTTP-Referer headers?

Yes. The custom header loop in OpenRouterHttpHandler.SendAsync removes existing headers before adding new ones (request.Headers.Remove(kvp.Key)). If you include "X-Title" or "HTTP-Referer" in your settings.json CustomHeaders dictionary, your values will replace the default identifiers.

What happens if I specify an invalid header name in settings.json?

The HttpHeaders.Add method in .NET validates header names at runtime. If settings.json contains an illegal character (such as a space or non-ASCII symbol in the key name), the application will throw a FormatException when attempting to send the first LLM request. Always use RFC-compliant header names.

Do custom headers apply to image generation requests?

Yes. The ImageGenerater class (located in src/ImageGenerater.cs) uses the same LLM infrastructure to communicate with the provider. Because OpenRouterHttpHandler is the underlying transport layer for all LLM-derived operations, custom headers configured in settings.json automatically propagate to image generation calls.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →