Skip to main content

ai_agent/utils/
http.rs

1// Source: /data/home/swei/claudecode/openclaudecode/src/utils/http.ts
2//! HTTP utility constants and helpers
3
4pub use crate::utils::user_agent::get_user_agent;
5
6use std::collections::HashMap;
7
8/// Get the user agent string for MCP requests
9pub fn get_mcp_user_agent() -> String {
10    let version = env!("CARGO_PKG_VERSION");
11
12    let mut parts: Vec<String> = vec![];
13
14    if let Ok(v) = std::env::var("AI_CODE_ENTRYPOINT") {
15        parts.push(v);
16    }
17    if let Ok(v) = std::env::var("AI_AGENT_SDK_VERSION") {
18        parts.push(format!("agent-sdk/{}", v));
19    }
20    if let Ok(v) = std::env::var("AI_AGENT_SDK_CLIENT_APP") {
21        parts.push(format!("client-app/{}", v));
22    }
23
24    if parts.is_empty() {
25        format!("claude-code/{}", version)
26    } else {
27        format!("claude-code/{} ({})", version, parts.join(", "))
28    }
29}
30
31/// Get the user agent string for WebFetch requests
32pub fn get_web_fetch_user_agent() -> String {
33    // Claude-User is Anthropic's publicly documented agent for user-initiated fetches
34    // The claude-code suffix lets site operators distinguish local CLI traffic
35    format!(
36        "Claude-User ({}; +https://support.anthropic.com/)",
37        get_user_agent()
38    )
39}
40
41/// Authentication headers for API requests
42#[derive(Debug, Clone)]
43pub struct AuthHeaders {
44    /// Headers map
45    pub headers: HashMap<String, String>,
46    /// Error message if auth unavailable
47    pub error: Option<String>,
48}
49
50/// Get authentication headers for API requests
51/// Returns either OAuth headers for Max/Pro users or API key headers for regular users
52pub fn get_auth_headers() -> AuthHeaders {
53    // Check for OAuth token via env var (Max/Pro subscribers)
54    if let Ok(token) = std::env::var("AI_CODE_OAUTH_TOKEN") {
55        if !token.is_empty() {
56            let mut headers = HashMap::new();
57            headers.insert("Authorization".to_string(), format!("Bearer {}", token));
58            headers.insert(
59                "anthropic-beta".to_string(),
60                "oauth-mcp-servers-2025-01-16".to_string(),
61            );
62            return AuthHeaders {
63                headers,
64                error: None,
65            };
66        }
67    }
68
69    // Fall back to API key env var for regular users
70    if let Ok(api_key) = std::env::var("AI_AUTH_TOKEN") {
71        if !api_key.is_empty() {
72            let mut headers = HashMap::new();
73            headers.insert("x-api-key".to_string(), api_key);
74            return AuthHeaders {
75                headers,
76                error: None,
77            };
78        }
79    }
80
81    AuthHeaders {
82        headers: HashMap::new(),
83        error: Some("No API key available".to_string()),
84    }
85}
86
87/// Wrapper that handles OAuth 401 errors by force-refreshing the token and
88/// retrying once. Addresses clock drift scenarios where the local expiration
89/// check disagrees with the server.
90///
91/// The request closure is called again on retry, so it should re-read auth
92/// (e.g., via get_auth_headers()) to pick up the refreshed token.
93///
94/// Note: Full implementation requires handleOAuth401Error from auth module.
95/// SDK implementation forwards the request as-is (caller handles auth refresh).
96///
97/// # Arguments
98/// * `request` - The async request closure to execute
99/// * `_also_403_revoked` - Also retry on 403 with "OAuth token has been revoked" body (unused in SDK)
100///
101/// # Returns
102/// The result of the wrapped request
103pub async fn with_oauth_401_retry<T, R>(
104    request: impl FnOnce() -> R,
105    _also_403_revoked: Option<bool>,
106) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
107where
108    R: Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
109{
110    // SDK: Forward request as-is. Full retry-on-401 logic requires handleOAuth401Error
111    // from auth module which has heavy dependencies. Callers should implement their own
112    // retry logic using get_auth_headers() to pick up refreshed tokens.
113    request().await
114}