claude_agent/auth/
config.rs1use std::collections::HashMap;
4
5use crate::client::BetaConfig;
6
7pub const DEFAULT_SYSTEM_PROMPT: &str = "You are Claude Code, Anthropic's official CLI for Claude.";
8pub const DEFAULT_USER_AGENT: &str = "claude-cli/2.0.76 (external, cli)";
9pub const DEFAULT_APP_IDENTIFIER: &str = "cli";
10pub const CLAUDE_CODE_BETA: &str = "claude-code-20250219";
11
12#[derive(Debug, Clone)]
13pub struct OAuthConfig {
14 pub system_prompt: String,
15 pub user_agent: String,
16 pub app_identifier: String,
17 pub url_params: HashMap<String, String>,
18 pub extra_headers: HashMap<String, String>,
19}
20
21impl Default for OAuthConfig {
22 fn default() -> Self {
23 Self {
24 system_prompt: DEFAULT_SYSTEM_PROMPT.to_string(),
25 user_agent: DEFAULT_USER_AGENT.to_string(),
26 app_identifier: DEFAULT_APP_IDENTIFIER.to_string(),
27 url_params: [("beta".to_string(), "true".to_string())]
28 .into_iter()
29 .collect(),
30 extra_headers: [(
31 "anthropic-dangerous-direct-browser-access".to_string(),
32 "true".to_string(),
33 )]
34 .into_iter()
35 .collect(),
36 }
37 }
38}
39
40impl OAuthConfig {
41 pub fn from_env() -> Self {
42 let mut config = Self::default();
43
44 if let Ok(prompt) = std::env::var("CLAUDE_AGENT_SYSTEM_PROMPT") {
45 config.system_prompt = prompt;
46 }
47 if let Ok(ua) = std::env::var("CLAUDE_AGENT_USER_AGENT") {
48 config.user_agent = ua;
49 }
50 if let Ok(app) = std::env::var("CLAUDE_AGENT_APP_IDENTIFIER") {
51 config.app_identifier = app;
52 }
53
54 config
55 }
56
57 pub fn builder() -> OAuthConfigBuilder {
58 OAuthConfigBuilder::default()
59 }
60
61 pub fn build_beta_header(&self, base: &BetaConfig) -> String {
62 let mut beta = base.clone();
63 beta.add(crate::client::BetaFeature::OAuth);
64 beta.add_custom(CLAUDE_CODE_BETA);
65 beta.header_value().unwrap_or_default()
66 }
67
68 pub fn build_url(&self, base_url: &str, endpoint: &str) -> String {
69 let url = format!("{}{}", base_url, endpoint);
70 if self.url_params.is_empty() {
71 url
72 } else {
73 let params: Vec<String> = self
74 .url_params
75 .iter()
76 .map(|(k, v)| format!("{}={}", k, v))
77 .collect();
78 format!("{}?{}", url, params.join("&"))
79 }
80 }
81
82 pub fn apply_headers(
83 &self,
84 req: reqwest::RequestBuilder,
85 token: &str,
86 api_version: &str,
87 beta: &BetaConfig,
88 ) -> reqwest::RequestBuilder {
89 let mut r = req
90 .header("Authorization", format!("Bearer {}", token))
91 .header("anthropic-version", api_version)
92 .header("content-type", "application/json")
93 .header("user-agent", &self.user_agent)
94 .header("x-app", &self.app_identifier);
95
96 for (k, v) in &self.extra_headers {
97 r = r.header(k.as_str(), v.as_str());
98 }
99
100 let beta_header = self.build_beta_header(beta);
101 if !beta_header.is_empty() {
102 r = r.header("anthropic-beta", beta_header);
103 }
104
105 r
106 }
107}
108
109pub struct OAuthConfigBuilder {
110 config: OAuthConfig,
111}
112
113impl Default for OAuthConfigBuilder {
114 fn default() -> Self {
115 Self {
116 config: OAuthConfig::from_env(),
117 }
118 }
119}
120
121impl OAuthConfigBuilder {
122 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
123 self.config.system_prompt = prompt.into();
124 self
125 }
126
127 pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
128 self.config.user_agent = ua.into();
129 self
130 }
131
132 pub fn app_identifier(mut self, app: impl Into<String>) -> Self {
133 self.config.app_identifier = app.into();
134 self
135 }
136
137 pub fn url_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
138 self.config.url_params.insert(key.into(), value.into());
139 self
140 }
141
142 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
143 self.config.extra_headers.insert(key.into(), value.into());
144 self
145 }
146
147 pub fn build(self) -> OAuthConfig {
148 self.config
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_default_config() {
158 let config = OAuthConfig::default();
159 assert_eq!(config.system_prompt, DEFAULT_SYSTEM_PROMPT);
160 assert_eq!(config.user_agent, DEFAULT_USER_AGENT);
161 assert_eq!(config.app_identifier, DEFAULT_APP_IDENTIFIER);
162 }
163
164 #[test]
165 fn test_builder() {
166 let config = OAuthConfig::builder()
167 .system_prompt("Custom prompt")
168 .user_agent("my-app/1.0")
169 .build();
170
171 assert_eq!(config.system_prompt, "Custom prompt");
172 assert_eq!(config.user_agent, "my-app/1.0");
173 }
174
175 #[test]
176 fn test_url_params() {
177 let config = OAuthConfig::default();
178 assert_eq!(config.url_params.get("beta"), Some(&"true".to_string()));
179 }
180}