#3598·headroom

[FEATURE]

Author: skittlejohnCreated Sep 15, 2026Updated Sep 15, 2026
Labelsenhancement

Problem Statement

Claude Code v2.1.129+ supports running non-Claude models via gateway model discovery (CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 + a GET /v1/models endpoint). LiteLLM documents this pattern in their Claude Code tutorial. Headroom's backend is LiteLLM and already does 90% of the Anthropic↔OpenAI API translation needed to serve non-Claude models — but three hard-coded filters and a missing endpoint prevent it from working.

Current State

LiteLLMBackend already:

  • Converts Anthropic content blocks (text, tool_use, tool_result) ↔ OpenAI format
  • Converts tool schemas, tool_choice, stop reasons, and usage stats
  • Streams Anthropic SSE events from OpenAI-format upstream responses
  • Handles API key routing (_caller_key_travels_to strips sk-ant- keys for non-Anthropic providers)
  • Has pass-through providers in the registry (OpenRouter, Databricks) that accept any model name

The PROVIDER_REGISTRY, map_model_id(), _convert_messages_for_litellm(), and _to_anthropic_response() / stream_message() paths are provider-agnostic. The only Claude-specific assumptions are in discovery and validation.

What Blocks It

1. Bedrock discovery filters out non-Claude models

_fetch_bedrock_inference_profiles() skips every non-Anthropic profile:

# backends/litellm.py line ~227
if "anthropic" not in profile_id.lower():
    continue

_normalize_bedrock_profile_id() gates on claude:

# backends/litellm.py line ~330
if not profile_id.startswith("claude"):
    return None

supports_model() rejects non-Claude names for non-pass-through providers:

# backends/litellm.py line ~747
return "claude" in model.lower() or model in self._model_map

2. No GET /v1/models endpoint

Claude Code discovers proxy-hosted models by querying GET /v1/models on ANTHROPIC_BASE_URL. Headroom has no such endpoint — the OpenAI handler's /v1/models is a passthrough to an upstream, not a self-served list. Without this, non-Claude models can't appear in Claude Code's /model picker.

This was previously requested in #77 ([FEATURE] Dynamic model registry for headroom wrap — auto-discover available models), which proposes the same OpenAI-compatible GET /v1/models endpoint derived from the backend's model map. The Copilot path is getting live catalog discovery via #2935, but the Anthropic/Bedrock proxy path has no equivalent. This feature request subsumes #77's /v1/models ask and adds the backend-level gating changes that #77 doesn't cover.

3. Extended thinking headers aren't stripped for non-Claude targets

Claude Code sends anthropic-beta: extended-thinking-* headers and may send "thinking" in the request body. Non-Claude models don't understand these. The proxy should strip them when the target model isn't Claude, rather than forwarding and hoping the upstream ignores them.

Proposed Solution

Minimum viable: widen discovery + add /v1/models

  1. Make Bedrock discovery configurable. Add an opt-in flag (env var or CLI flag, e.g. --discover-all-models / HEADROOM_DISCOVER_ALL_MODELS=1) that removes the "anthropic" not in profile_id filter and generalizes _normalize_bedrock_profile_id() to handle Meta/Mistral/Amazon model ID formats. Default remains Claude-only so nothing changes for existing users.

  2. Extend HEADROOM_BEDROCK_MODEL_MAP to bypass supports_model(). Models registered via the override map are already operator-approved — supports_model() should accept them regardless of whether "claude" is in the name.

  3. Add a GET /v1/models endpoint that returns an OpenAI-compatible model list derived from the backend's resolved model map (discovery + overrides). This is what Claude Code queries when CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 is set. This would also close #77.

  4. Strip extended-thinking from non-Claude requests. When the resolved model target doesn't contain "claude"/"anthropic", drop the anthropic-beta header and thinking body field before forwarding.

Stretch: explicit multi-model config

A HEADROOM_EXTRA_MODELS env var or config section that defines non-Claude models with their provider, LiteLLM model string, context window, and display name — similar to LiteLLM's config.yaml model_list. This would cleanly separate "what models are available" from "how Bedrock discovery works."

Use Case

  • Using Claude Code through Headroom for context compression and cost tracking
  • Want to experiment with non-Claude models (GPT, Gemini, Llama on Bedrock, OpenRouter models) for cost comparison or capability testing, without giving up Headroom's compression pipeline
  • LiteLLM's standalone proxy supports this today, but requires running a second process and loses Headroom's compression, dashboard, and savings tracking
  • The translation layer is already in Headroom — only the discovery/validation gates need widening

Alternatives Considered

  • Running a standalone LiteLLM proxy chained behind Headroom. Works today, no Headroom changes needed. Costs a second process and duplicates model routing logic. Headroom's compression still applies but savings tracking may double-count.
  • Using OpenRouter as the backend (--backend openrouter). Works for pass-through providers since supports_model() returns True for pass_through=True. Loses Bedrock billing, AWS auth, and compliance. Not viable for orgs that require Bedrock.
  • Using HEADROOM_BEDROCK_MODEL_MAP to manually register non-Claude models. Almost works today — the map resolves correctly, but supports_model() rejects the model name before map_model_id() is ever called. Fixing this one gate (supports_model should check the override map) would unblock manual registration with zero other changes.

Related Issues

  • #77 — [FEATURE] Dynamic model registry for headroom wrap — auto-discover available models. Requests the GET /v1/models endpoint this feature also needs. This request subsumes that ask and adds the backend-level discovery/validation changes #77 doesn't cover.
  • #2935 — feat(copilot): drive model routing and discovery from the live model catalog. Implements live model discovery for the Copilot path. The Anthropic/Bedrock proxy path has no equivalent; this request fills that gap.
  • #1264 — [FEATURE] Anthropic-compatible third-party upstream support (DashScope, XF-Yun, SiliconFlow, etc.). Adjacent but different — that issue is about third-party providers that mimic the Anthropic API upstream. This request is about routing Anthropic API requests from Claude Code to non-Claude models downstream.
  • #3272 — Stacking Headroom with a single OpenAI-compatible gateway. Describes chaining Headroom with a separate gateway for multi-model routing — the "Option D" workaround this feature request would make unnecessary.

Additional Context

  • Headroom v0.37.0, using headroom proxy --backend bedrock
  • Claude Code's gateway discovery docs: the client sends GET /v1/models and expects {"object": "list", "data": [{"id": "model-name", "object": "model", ...}]}
  • The model router (HEADROOM_MODEL_ROUTER_ENABLED) and route advice extension system (request.state.headroom_route) already support per-request model+provider switching — this feature request is about making the backend accept and translate for non-Claude models, which the routing infrastructure already assumes it can
  • _caller_key_travels_to() already handles the credential mismatch case (Anthropic key → non-Anthropic provider), so auth routing is solved
  • Are you willing to contribute this feature? Yes.

Source: headroomlabs-ai/headroom