From 39d86eeb7f93001ba526eb97f58102034d522f8c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 28 Aug 2025 14:00:44 +0200 Subject: [PATCH] Trim API key when submitting requests to LLM providers (#37082) This prevents the common footgun of copy/pasting an API key starting/ending with extra newlines, which would lead to a "bad request" error. Closes #37038 Release Notes: - agent: Support pasting language model API keys that contain newlines. --- crates/anthropic/src/anthropic.rs | 4 ++-- crates/deepseek/src/deepseek.rs | 2 +- crates/google_ai/src/google_ai.rs | 1 + crates/mistral/src/mistral.rs | 2 +- crates/open_ai/src/open_ai.rs | 4 ++-- crates/open_router/src/open_router.rs | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/anthropic/src/anthropic.rs b/crates/anthropic/src/anthropic.rs index 3ff1666755d439cf52a14ea635a06a7c3414d9f6..773bb557de1895e57bdeb5612e01e2839af3244b 100644 --- a/crates/anthropic/src/anthropic.rs +++ b/crates/anthropic/src/anthropic.rs @@ -373,7 +373,7 @@ pub async fn complete( .uri(uri) .header("Anthropic-Version", "2023-06-01") .header("Anthropic-Beta", beta_headers) - .header("X-Api-Key", api_key) + .header("X-Api-Key", api_key.trim()) .header("Content-Type", "application/json"); let serialized_request = @@ -526,7 +526,7 @@ pub async fn stream_completion_with_rate_limit_info( .uri(uri) .header("Anthropic-Version", "2023-06-01") .header("Anthropic-Beta", beta_headers) - .header("X-Api-Key", api_key) + .header("X-Api-Key", api_key.trim()) .header("Content-Type", "application/json"); let serialized_request = serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?; diff --git a/crates/deepseek/src/deepseek.rs b/crates/deepseek/src/deepseek.rs index c49270febe3b2b3702b808e2219f6e45d7252267..c2554c67e93b4c1d3772e60a62063fdae0511f05 100644 --- a/crates/deepseek/src/deepseek.rs +++ b/crates/deepseek/src/deepseek.rs @@ -268,7 +268,7 @@ pub async fn stream_completion( .method(Method::POST) .uri(uri) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", api_key)); + .header("Authorization", format!("Bearer {}", api_key.trim())); let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; diff --git a/crates/google_ai/src/google_ai.rs b/crates/google_ai/src/google_ai.rs index ca0aa309b1296e021402d921b7a7809f1e593e2b..92fd53189327fabccdc1472ac0fa2a20dc665646 100644 --- a/crates/google_ai/src/google_ai.rs +++ b/crates/google_ai/src/google_ai.rs @@ -13,6 +13,7 @@ pub async fn stream_generate_content( api_key: &str, mut request: GenerateContentRequest, ) -> Result>> { + let api_key = api_key.trim(); validate_generate_content_request(&request)?; // The `model` field is emptied as it is provided as a path parameter. diff --git a/crates/mistral/src/mistral.rs b/crates/mistral/src/mistral.rs index 5b4d05377c7132f47828aa6afafbb5c850e940a8..55986e7e5bfd69ec91f11089753562e9e1984fcc 100644 --- a/crates/mistral/src/mistral.rs +++ b/crates/mistral/src/mistral.rs @@ -482,7 +482,7 @@ pub async fn stream_completion( .method(Method::POST) .uri(uri) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", api_key)); + .header("Authorization", format!("Bearer {}", api_key.trim())); let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; diff --git a/crates/open_ai/src/open_ai.rs b/crates/open_ai/src/open_ai.rs index 08be82b8303291f9fa7795b2f04cbe8392d6d581..f9a983b433b9d918424f9696269dd0bbd72adefd 100644 --- a/crates/open_ai/src/open_ai.rs +++ b/crates/open_ai/src/open_ai.rs @@ -461,7 +461,7 @@ pub async fn stream_completion( .method(Method::POST) .uri(uri) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", api_key)); + .header("Authorization", format!("Bearer {}", api_key.trim())); let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?; let mut response = client.send(request).await?; @@ -565,7 +565,7 @@ pub fn embed<'a>( .method(Method::POST) .uri(uri) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", api_key)) + .header("Authorization", format!("Bearer {}", api_key.trim())) .body(body) .map(|request| client.send(request)); diff --git a/crates/open_router/src/open_router.rs b/crates/open_router/src/open_router.rs index b7e6d69d8fbe7c342e833cd13ad069399fb44a26..65ef519d2c887e57e67d68ca6fcaea64ad67ee3e 100644 --- a/crates/open_router/src/open_router.rs +++ b/crates/open_router/src/open_router.rs @@ -424,7 +424,7 @@ pub async fn complete( .method(Method::POST) .uri(uri) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", api_key)) + .header("Authorization", format!("Bearer {}", api_key.trim())) .header("HTTP-Referer", "https://zed.dev") .header("X-Title", "Zed Editor");