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}