فهرست منبع

feat(api): match API auth headers and layofflabs request format

Trace the local Claw Code TS request path and align the Rust client with its
non-OAuth direct-request behavior. The Rust client now resolves the message base
URL from ANTHROPIC_BASE_URL, uses ANTHROPIC_API_KEY for x-api-key, and sends
ANTHROPIC_AUTH_TOKEN as a Bearer Authorization header when present.

Constraint: Must match the local Claw Code source request/auth split, not inferred behavior
Rejected: Treat ANTHROPIC_AUTH_TOKEN as the x-api-key source | diverges from local TS client path
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep direct /v1/messages auth handling aligned with src/services/api/client.ts and src/utils/auth.ts when changing env precedence
Tested: cargo test -p api; cargo run -p claw-cli -- prompt "say hello"
Not-tested: Non-default proxy transport features beyond ANTHROPIC_BASE_URL override
Yeachan-Heo 2 ماه پیش
والد
کامیت
07f80f879d
1فایلهای تغییر یافته به همراه34 افزوده شده و 15 حذف شده
  1. 34 15
      rust/crates/api/src/client.rs

+ 34 - 15
rust/crates/api/src/client.rs

@@ -41,12 +41,9 @@ impl AnthropicClient {
     }
 
     pub fn from_env() -> Result<Self, ApiError> {
-        Ok(Self::new(read_api_key()?).with_base_url(
-            std::env::var("ANTHROPIC_BASE_URL")
-                .ok()
-                .or_else(|| std::env::var("CLAUDE_CODE_API_BASE_URL").ok())
-                .unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
-        ))
+        Ok(Self::new(read_api_key()?)
+            .with_auth_token(read_auth_token())
+            .with_base_url(read_base_url()))
     }
 
     #[must_use]
@@ -150,16 +147,20 @@ impl AnthropicClient {
         &self,
         request: &MessageRequest,
     ) -> Result<reqwest::Response, ApiError> {
+        let request_url = format!("{}/v1/messages", self.base_url.trim_end_matches('/'));
+        let resolved_base_url = self.base_url.trim_end_matches('/');
+        eprintln!("[anthropic-client] resolved_base_url={resolved_base_url}");
+        eprintln!("[anthropic-client] request_url={request_url}");
         let mut request_builder = self
             .http
-            .post(format!(
-                "{}/v1/messages",
-                self.base_url.trim_end_matches('/')
-            ))
+            .post(&request_url)
             .header("x-api-key", &self.api_key)
             .header("anthropic-version", ANTHROPIC_VERSION)
             .header("content-type", "application/json");
 
+        let auth_header = self.auth_token.as_ref().map(|_| "Bearer [REDACTED]").unwrap_or("<absent>");
+        eprintln!("[anthropic-client] headers x-api-key=[REDACTED] authorization={auth_header} anthropic-version={ANTHROPIC_VERSION} content-type=application/json");
+
         if let Some(auth_token) = &self.auth_token {
             request_builder = request_builder.bearer_auth(auth_token);
         }
@@ -186,10 +187,10 @@ impl AnthropicClient {
 }
 
 fn read_api_key() -> Result<String, ApiError> {
-    match std::env::var("ANTHROPIC_AUTH_TOKEN") {
+    match std::env::var("ANTHROPIC_API_KEY") {
         Ok(api_key) if !api_key.is_empty() => Ok(api_key),
         Ok(_) => Err(ApiError::MissingApiKey),
-        Err(std::env::VarError::NotPresent) => match std::env::var("ANTHROPIC_API_KEY") {
+        Err(std::env::VarError::NotPresent) => match std::env::var("ANTHROPIC_AUTH_TOKEN") {
             Ok(api_key) if !api_key.is_empty() => Ok(api_key),
             Ok(_) => Err(ApiError::MissingApiKey),
             Err(std::env::VarError::NotPresent) => Err(ApiError::MissingApiKey),
@@ -199,6 +200,17 @@ fn read_api_key() -> Result<String, ApiError> {
     }
 }
 
+fn read_auth_token() -> Option<String> {
+    match std::env::var("ANTHROPIC_AUTH_TOKEN") {
+        Ok(token) if !token.is_empty() => Some(token),
+        _ => None,
+    }
+}
+
+fn read_base_url() -> String {
+    std::env::var("ANTHROPIC_BASE_URL").unwrap_or_else(|_| DEFAULT_BASE_URL.to_string())
+}
+
 fn request_id_from_headers(headers: &reqwest::header::HeaderMap) -> Option<String> {
     headers
         .get(REQUEST_ID_HEADER)
@@ -312,17 +324,24 @@ mod tests {
     }
 
     #[test]
-    fn read_api_key_prefers_auth_token() {
+    fn read_api_key_prefers_api_key_env() {
         std::env::set_var("ANTHROPIC_AUTH_TOKEN", "auth-token");
         std::env::set_var("ANTHROPIC_API_KEY", "legacy-key");
         assert_eq!(
-            super::read_api_key().expect("token should load"),
-            "auth-token"
+            super::read_api_key().expect("api key should load"),
+            "legacy-key"
         );
         std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
         std::env::remove_var("ANTHROPIC_API_KEY");
     }
 
+    #[test]
+    fn read_auth_token_reads_auth_token_env() {
+        std::env::set_var("ANTHROPIC_AUTH_TOKEN", "auth-token");
+        assert_eq!(super::read_auth_token().as_deref(), Some("auth-token"));
+        std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
+    }
+
     #[test]
     fn message_request_stream_helper_sets_stream_true() {
         let request = MessageRequest {