Trim API key when submitting requests to LLM providers (#37082)

Antonio Scandurra created

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.

Change summary

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(-)

Detailed changes

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)?;

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?;

crates/google_ai/src/google_ai.rs 🔗

@@ -13,6 +13,7 @@ pub async fn stream_generate_content(
     api_key: &str,
     mut request: GenerateContentRequest,
 ) -> Result<BoxStream<'static, Result<GenerateContentResponse>>> {
+    let api_key = api_key.trim();
     validate_generate_content_request(&request)?;
 
     // The `model` field is emptied as it is provided as a path parameter.

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?;

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));
 

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");