Skip to content

Custom provider headers not propagated to model objects (buildInlineProviderModels missing headers) #15682

@julius2016

Description

@julius2016

Summary

Custom provider headers configured in models.providers are not passed through to model objects during inline model resolution. This causes custom HTTP headers (e.g., User-Agent) to be silently ignored for all API requests.

This is the same class of bug as #2903 (which was fixed for baseUrl in #2740), but headers was missed in that fix.

Environment

  • OpenClaw Version: 2026.2.12
  • Platform: macOS (Darwin 25.2.0)
  • pi-ai: @mariozechner/pi-ai (bundled)

Steps to Reproduce

  1. Configure a custom provider with headers in openclaw.json:

    {
      "models": {
        "providers": {
          "my-proxy": {
            "baseUrl": "https://proxy.example.com/api",
            "apiKey": "...",
            "api": "anthropic-messages",
            "headers": {
              "User-Agent": "claude-code/2.1.0"
            },
            "models": [...]
          }
        }
      }
    }
  2. Start the gateway and trigger an API request

  3. Observe that the proxy receives the Anthropic SDK default User-Agent: Anthropic/JS <version> instead of the configured claude-code/2.1.0

Expected Behavior

The headers from provider config should be included in the model object and merged into the Anthropic SDK's defaultHeaders via pi-ai.

Actual Behavior

headers is silently dropped. The Anthropic SDK sends its default User-Agent, which may be rejected by proxies that validate specific User-Agent patterns.

Root Cause

In buildInlineProviderModels() (reply entry, line ~3994), the function reads entry?.baseUrl and entry?.api but omits entry?.headers:

function buildInlineProviderModels(providers) {
    return Object.entries(providers).flatMap(([providerId, entry]) => {
        const trimmed = providerId.trim();
        if (!trimmed) return [];
        return (entry?.models ?? []).map((model) => ({
            ...model,
            provider: trimmed,
            baseUrl: entry?.baseUrl,
            api: model.api ?? entry?.api
            // ← headers missing!
        }));
    });
}

Meanwhile, pi-ai's Anthropic provider (anthropic.js:394-398) already supports model.headers:

defaultHeaders: mergeHeaders({
    accept: "application/json",
    ...
}, model.headers, dynamicHeaders, optionsHeaders),

And the runner entry (runner-DWOMWPyI.js:1509) does read providerConfig?.headers — confirming it's only the main chat/reply path that's missing it.

Fix

Add headers: entry?.headers to buildInlineProviderModels():

return (entry?.models ?? []).map((model) => ({
    ...model,
    provider: trimmed,
    baseUrl: entry?.baseUrl,
    api: model.api ?? entry?.api,
    headers: entry?.headers
}));

Workaround

Manually patch dist/reply-*.js to add the missing headers field. Note: patch is overwritten on updates.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions