language_models: Support custom HTTP headers for provider requests (#55246)

Add a `custom_headers` setting to each HTTP-based language model
provider
(Anthropic, Bedrock, DeepSeek, Google, LM Studio, Mistral, Ollama,
OpenAI,
OpenAI-compatible, OpenCode, OpenRouter, Vercel AI Gateway, and xAI) so
users
can attach extra headers to every outgoing request. Headers managed by
Zed
(authentication, content-type, etc.) cannot be overridden and are
skipped with
a warning.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Added support for configuring custom HTTP headers on language model
  providers via `language_models.<provider>.custom_headers`.

Signed-off-by: Aurabindo Pillai <mail@aurabindo.in>
This commit is contained in:
Aurabindo Pillai authored and GitHub committed 2026-06-02 13:29:23 +00:00
1 parent 27db63f0e6
commit d524bc9a78
34 files changed
+678 -157

No files matched your search

+39 -8
View File
@@ -6,7 +6,10 @@ use anyhow::{Context as _, Result};
use chrono::{DateTime, Utc};
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
use http_client::http::{self, HeaderMap, HeaderValue};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, StatusCode};
use http_client::{
AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt,
StatusCode,
};
use serde::{Deserialize, Serialize};
use strum::EnumString;
use thiserror::Error;
@@ -202,10 +205,18 @@ pub async fn stream_completion(
api_key: &str,
request: Request,
beta_headers: Option<String>,
extra_headers: &CustomHeaders,
) -> Result<BoxStream<'static, Result<Event, AnthropicError>>, AnthropicError> {
stream_completion_with_rate_limit_info(client, api_url, api_key, request, beta_headers)
.await
.map(|output| output.0)
stream_completion_with_rate_limit_info(
client,
api_url,
api_key,
request,
beta_headers,
extra_headers,
)
.await
.map(|output| output.0)
}
/// A raw model entry returned by the Anthropic models listing endpoint.
@@ -233,6 +244,7 @@ pub async fn list_models(
client: &dyn HttpClient,
api_url: &str,
api_key: &str,
extra_headers: &CustomHeaders,
) -> Result<Vec<Model>> {
let uri = format!("{api_url}/v1/models?limit=1000");
@@ -242,6 +254,7 @@ pub async fn list_models(
.header("Anthropic-Version", "2023-06-01")
.header("X-Api-Key", api_key.trim())
.header("Accept", "application/json")
.extra_headers(extra_headers)
.body(AsyncBody::default())
.context("failed to build Anthropic models list request")?;
@@ -282,9 +295,17 @@ pub async fn non_streaming_completion(
api_key: &str,
request: Request,
beta_headers: Option<String>,
extra_headers: &CustomHeaders,
) -> Result<Response, AnthropicError> {
let (mut response, rate_limits) =
send_request(client, api_url, api_key, &request, beta_headers).await?;
let (mut response, rate_limits) = send_request(
client,
api_url,
api_key,
&request,
beta_headers,
extra_headers,
)
.await?;
if response.status().is_success() {
let mut body = String::new();
@@ -306,6 +327,7 @@ async fn send_request(
api_key: &str,
request: impl Serialize,
beta_headers: Option<String>,
extra_headers: &CustomHeaders,
) -> Result<(http::Response<AsyncBody>, RateLimitInfo), AnthropicError> {
let uri = format!("{api_url}/v1/messages");
@@ -323,6 +345,7 @@ async fn send_request(
let serialized_request =
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
let request = request_builder
.extra_headers(extra_headers)
.body(AsyncBody::from(serialized_request))
.map_err(AnthropicError::BuildRequestBody)?;
@@ -462,6 +485,7 @@ pub async fn stream_completion_with_rate_limit_info(
api_key: &str,
request: Request,
beta_headers: Option<String>,
extra_headers: &CustomHeaders,
) -> Result<
(
BoxStream<'static, Result<Event, AnthropicError>>,
@@ -474,8 +498,15 @@ pub async fn stream_completion_with_rate_limit_info(
stream: true,
};
let (response, rate_limits) =
send_request(client, api_url, api_key, &request, beta_headers).await?;
let (response, rate_limits) = send_request(
client,
api_url,
api_key,
&request,
beta_headers,
extra_headers,
)
.await?;
if response.status().is_success() {
let reader = BufReader::new(response.into_body());