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