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
4use crate::constants::env::ai;
5use std::collections::HashMap;
6
7/// Get the user agent string for API requests
8pub fn get_user_agent() -> String {
9    let version = std::env::var(ai::VERSION).unwrap_or_else(|_| "unknown".to_string());
10    let user_type = std::env::var(ai::USER_TYPE).unwrap_or_else(|_| "external".to_string());
11    let entrypoint = std::env::var(ai::CODE_ENTRYPOINT).unwrap_or_else(|_| "cli".to_string());
12    let agent_sdk_version = std::env::var(ai::AGENT_SDK_VERSION);
13    let client_app = std::env::var(ai::AGENT_SDK_CLIENT_APP);
14
15    let mut ua = format!("claude-cli/{} ({}, {}", version, user_type, entrypoint);
16
17    if let Ok(v) = agent_sdk_version {
18        ua.push_str(&format!(", agent-sdk/{}", v));
19    }
20
21    if let Ok(app) = client_app {
22        ua.push_str(&format!(", client-app/{}", app));
23    }
24
25    // TODO: Add workload suffix from getWorkload()
26
27    ua.push(')');
28    ua
29}
30
31/// Get the user agent string for MCP requests
32pub fn get_mcp_user_agent() -> String {
33    let version = std::env::var(ai::VERSION).unwrap_or_else(|_| "unknown".to_string());
34
35    let mut parts: Vec<String> = vec![];
36
37    if let Ok(v) = std::env::var(ai::CODE_ENTRYPOINT) {
38        parts.push(v);
39    }
40    if let Ok(v) = std::env::var(ai::AGENT_SDK_VERSION) {
41        parts.push(format!("agent-sdk/{}", v));
42    }
43    if let Ok(v) = std::env::var(ai::AGENT_SDK_CLIENT_APP) {
44        parts.push(format!("client-app/{}", v));
45    }
46
47    if parts.is_empty() {
48        format!("claude-code/{}", version)
49    } else {
50        format!("claude-code/{} ({})", version, parts.join(", "))
51    }
52}
53
54/// Get the user agent string for WebFetch requests
55pub fn get_web_fetch_user_agent() -> String {
56    // Claude-User is Anthropic's publicly documented agent for user-initiated fetches
57    // The claude-code suffix lets site operators distinguish local CLI traffic
58    format!(
59        "Claude-User ({}; +https://support.anthropic.com/)",
60        get_user_agent()
61    )
62}
63
64/// Authentication headers for API requests
65#[derive(Debug, Clone)]
66pub struct AuthHeaders {
67    /// Headers map
68    pub headers: HashMap<String, String>,
69    /// Error message if auth unavailable
70    pub error: Option<String>,
71}
72
73/// Get authentication headers for API requests
74/// Returns either OAuth headers for Max/Pro users or API key headers for regular users
75pub fn get_auth_headers() -> AuthHeaders {
76    // TODO: Implement proper OAuth and API key authentication
77    // For now, return empty headers
78    AuthHeaders {
79        headers: HashMap::new(),
80        error: Some("Not implemented - requires auth integration".to_string()),
81    }
82}
83
84/// Wrapper that handles OAuth 401 errors by force-refreshing the token and
85/// retrying once. Addresses clock drift scenarios where the local expiration
86/// check disagrees with the server.
87///
88/// # Arguments
89/// * `request` - The async request closure to execute
90/// * `also_403_revoked` - Also retry on 403 with "OAuth token has been revoked" body
91///
92/// # Returns
93/// The result of the wrapped request
94pub async fn with_oauth_401_retry<T, R>(
95    request: impl FnOnce() -> R,
96    _also_403_revoked: Option<bool>,
97) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
98where
99    R: Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
100{
101    // TODO: Implement proper OAuth retry logic
102    request().await
103}