Skip to content

Commit 6ab0aa4

Browse files
committed
feat(website): 10 语言文档翻译(zh-tw, ja, ko, ru, fr, pt-br, es, it, de, pl)
为 10 种新语言创建完整文档翻译,每种语言 12 个文件: - index, installation, configuration - guides/provider-setup - features/(translation-proxy, circuit-breaker, smart-routing, context-engine, tui-dashboard, self-update) - reference/(cli, config) 翻译规则:frontmatter/标题/正文翻译,代码块/URL/配置字段保持原样,内部链接更新为对应 locale 前缀
1 parent 17bf2c4 commit 6ab0aa4

128 files changed

Lines changed: 20367 additions & 847 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/oauth/exchange.rs

Lines changed: 421 additions & 0 deletions
Large diffs are not rendered by default.

src/oauth/handler.rs

Lines changed: 118 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,28 @@ use anyhow::Result;
33
use super::{OAuthProvider, OAuthToken};
44

55
/// Trait abstracting per-provider OAuth operations.
6-
///
7-
/// Each provider implements login (obtain initial token) and refresh
8-
/// (re-validate or refresh an expired token). The trait uses dynamic
9-
/// dispatch so providers can be selected at runtime.
106
pub trait OAuthProviderHandler: Send + Sync {
11-
/// Which provider this handler serves.
127
fn provider(&self) -> OAuthProvider;
138

14-
/// Obtain an initial OAuth token (interactive: may open browser, read CLI files, etc.)
159
fn login(
1610
&self,
1711
profile_name: &str,
1812
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>>;
1913

20-
/// Refresh an existing token. Returns the new token.
2114
fn refresh(
2215
&self,
2316
profile_name: &str,
2417
token: &OAuthToken,
2518
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>>;
2619

27-
/// Read token from external CLI files (non-interactive).
28-
/// Falls back to keyring if no external CLI is available.
2920
fn read_external_token(&self) -> Result<OAuthToken>;
3021
}
3122

3223
/// Factory: get the handler for a given provider.
3324
pub fn for_provider(provider: &OAuthProvider) -> Box<dyn OAuthProviderHandler> {
34-
match provider {
25+
match provider.normalize() {
3526
OAuthProvider::Claude => Box::new(ClaudeHandler),
36-
OAuthProvider::Openai => Box::new(OpenaiHandler),
27+
OAuthProvider::Chatgpt | OAuthProvider::Openai => Box::new(ChatgptHandler),
3728
OAuthProvider::Google => Box::new(ExternalCliHandler {
3829
provider: OAuthProvider::Google,
3930
}),
@@ -43,13 +34,11 @@ pub fn for_provider(provider: &OAuthProvider) -> Box<dyn OAuthProviderHandler> {
4334
OAuthProvider::Qwen => Box::new(DeviceCodeHandler {
4435
provider: OAuthProvider::Qwen,
4536
}),
46-
OAuthProvider::Github => Box::new(DeviceCodeHandler {
47-
provider: OAuthProvider::Github,
48-
}),
37+
OAuthProvider::Github => Box::new(GithubHandler),
4938
}
5039
}
5140

52-
// ── Claude: read ~/.claude/.credentials.json ──
41+
// ── Claude ──
5342

5443
struct ClaudeHandler;
5544

@@ -63,11 +52,8 @@ impl OAuthProviderHandler for ClaudeHandler {
6352
_profile_name: &str,
6453
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
6554
Box::pin(async {
66-
println!("Reading Claude credentials from ~/.claude/.credentials.json...");
67-
let token = super::token::read_claude_credentials()
68-
.map_err(|e| anyhow::anyhow!("Failed to read Claude credentials: {e}"))?;
69-
println!("Note: Claude subscription profiles bypass the proxy (Claude Code uses its own OAuth).");
70-
Ok(token)
55+
let cred = super::source::read_claude_credentials()?;
56+
Ok(cred.into_oauth_token())
7157
})
7258
}
7359

@@ -77,57 +63,32 @@ impl OAuthProviderHandler for ClaudeHandler {
7763
_token: &OAuthToken,
7864
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
7965
Box::pin(async {
80-
let token = super::token::read_claude_credentials()?;
81-
println!("Refreshed Claude token from ~/.claude/.credentials.json");
82-
Ok(token)
66+
let cred = super::source::read_claude_credentials()?;
67+
Ok(cred.into_oauth_token())
8368
})
8469
}
8570

8671
fn read_external_token(&self) -> Result<OAuthToken> {
87-
super::token::read_claude_credentials()
72+
super::source::read_claude_credentials().map(|c| c.into_oauth_token())
8873
}
8974
}
9075

91-
// ── OpenAI: read Codex CLI + refresh_token ──
76+
// ── ChatGPT (was OpenAI) ──
9277

93-
struct OpenaiHandler;
78+
struct ChatgptHandler;
9479

95-
impl OAuthProviderHandler for OpenaiHandler {
80+
impl OAuthProviderHandler for ChatgptHandler {
9681
fn provider(&self) -> OAuthProvider {
97-
OAuthProvider::Openai
82+
OAuthProvider::Chatgpt
9883
}
9984

10085
fn login(
10186
&self,
102-
profile_name: &str,
87+
_profile_name: &str,
10388
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
104-
let profile_name = profile_name.to_string();
105-
Box::pin(async move {
106-
match super::token::read_codex_credentials() {
107-
Ok(token) => {
108-
let auth_mode = token
109-
.extra
110-
.as_ref()
111-
.and_then(|e| e.get("auth_mode"))
112-
.and_then(|v| v.as_str())
113-
.unwrap_or("unknown");
114-
println!("Found Codex CLI credentials (auth_mode: {auth_mode})");
115-
println!("Token will be refreshed automatically from ~/.codex/auth.json");
116-
Ok(token)
117-
}
118-
Err(_) => {
119-
println!("No Codex CLI credentials found at ~/.codex/auth.json");
120-
println!();
121-
println!("To use your ChatGPT subscription with Claudex:");
122-
println!(" 1. Install Codex CLI: npm install -g @openai/codex");
123-
println!(" 2. Login: codex --login");
124-
println!(
125-
" 3. Re-run: claudex auth login openai --profile {}",
126-
profile_name
127-
);
128-
anyhow::bail!("no OpenAI credentials available")
129-
}
130-
}
89+
Box::pin(async {
90+
let cred = super::source::read_codex_credentials()?;
91+
Ok(cred.into_oauth_token())
13192
})
13293
}
13394

@@ -138,18 +99,15 @@ impl OAuthProviderHandler for OpenaiHandler {
13899
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
139100
let refresh_tok = token.refresh_token.clone();
140101
Box::pin(async move {
141-
let refresh_tok = refresh_tok.ok_or_else(|| {
142-
anyhow::anyhow!(
143-
"no refresh_token in Codex credentials, please re-login with `codex --login`"
144-
)
145-
})?;
146-
super::providers::refresh_openai_token_pub(&refresh_tok, Some(refresh_tok.clone()))
147-
.await
102+
let refresh_tok = refresh_tok
103+
.ok_or_else(|| anyhow::anyhow!("no refresh_token, please re-login"))?;
104+
let client = reqwest::Client::new();
105+
super::exchange::refresh_chatgpt_token(&client, &refresh_tok).await
148106
})
149107
}
150108

151109
fn read_external_token(&self) -> Result<OAuthToken> {
152-
super::token::read_external_token(&OAuthProvider::Openai)
110+
super::source::read_codex_credentials().map(|c| c.into_oauth_token())
153111
}
154112
}
155113

@@ -170,12 +128,8 @@ impl OAuthProviderHandler for ExternalCliHandler {
170128
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
171129
let provider = self.provider.clone();
172130
Box::pin(async move {
173-
println!(
174-
"Reading {} credentials from external CLI...",
175-
provider.display_name()
176-
);
177-
let token = super::token::read_external_token(&provider)?;
178-
Ok(token)
131+
let cred = super::source::load_credential_chain(&provider)?;
132+
Ok(cred.into_oauth_token())
179133
})
180134
}
181135

@@ -186,21 +140,72 @@ impl OAuthProviderHandler for ExternalCliHandler {
186140
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
187141
let provider = self.provider.clone();
188142
Box::pin(async move {
189-
let token = super::token::read_external_token(&provider)?;
190-
println!(
191-
"Refreshed {} token from external CLI",
192-
provider.display_name()
193-
);
194-
Ok(token)
143+
let cred = super::source::load_credential_chain(&provider)?;
144+
Ok(cred.into_oauth_token())
145+
})
146+
}
147+
148+
fn read_external_token(&self) -> Result<OAuthToken> {
149+
super::source::load_credential_chain(&self.provider).map(|c| c.into_oauth_token())
150+
}
151+
}
152+
153+
// ── GitHub Copilot ──
154+
155+
struct GithubHandler;
156+
157+
impl OAuthProviderHandler for GithubHandler {
158+
fn provider(&self) -> OAuthProvider {
159+
OAuthProvider::Github
160+
}
161+
162+
fn login(
163+
&self,
164+
_profile_name: &str,
165+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
166+
Box::pin(async {
167+
let cred = super::source::load_credential_chain(&OAuthProvider::Github)?;
168+
let client = reqwest::Client::new();
169+
let copilot =
170+
super::exchange::exchange_github_for_copilot(&client, &cred.access_token).await?;
171+
Ok(OAuthToken {
172+
access_token: copilot.token,
173+
refresh_token: None,
174+
expires_at: Some(copilot.expires_at * 1000),
175+
token_type: Some("Bearer".to_string()),
176+
scopes: None,
177+
extra: Some(serde_json::json!({"provider": "copilot"})),
178+
})
179+
})
180+
}
181+
182+
fn refresh(
183+
&self,
184+
_profile_name: &str,
185+
_token: &OAuthToken,
186+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
187+
Box::pin(async {
188+
let cred = super::source::load_credential_chain(&OAuthProvider::Github)?;
189+
let client = reqwest::Client::new();
190+
let copilot =
191+
super::exchange::exchange_github_for_copilot(&client, &cred.access_token).await?;
192+
Ok(OAuthToken {
193+
access_token: copilot.token,
194+
refresh_token: None,
195+
expires_at: Some(copilot.expires_at * 1000),
196+
token_type: Some("Bearer".to_string()),
197+
scopes: None,
198+
extra: Some(serde_json::json!({"provider": "copilot"})),
199+
})
195200
})
196201
}
197202

198203
fn read_external_token(&self) -> Result<OAuthToken> {
199-
super::token::read_external_token(&self.provider)
204+
super::source::read_copilot_config().map(|c| c.into_oauth_token())
200205
}
201206
}
202207

203-
// ── Device Code: GitHub, Qwen ──
208+
// ── Device Code: Qwen ──
204209

205210
struct DeviceCodeHandler {
206211
provider: OAuthProvider,
@@ -217,9 +222,11 @@ impl OAuthProviderHandler for DeviceCodeHandler {
217222
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
218223
let provider = self.provider.clone();
219224
Box::pin(async move {
220-
// Device code login is handled by providers::login_device_code
221-
// which requires interactive I/O. Delegate to the existing impl.
222-
super::providers::login_device_code_pub(&provider).await
225+
// Qwen device code login requires interactive I/O
226+
anyhow::bail!(
227+
"use `claudex auth login {}` for interactive device code flow",
228+
provider.display_name().to_lowercase()
229+
)
223230
})
224231
}
225232

@@ -228,15 +235,32 @@ impl OAuthProviderHandler for DeviceCodeHandler {
228235
profile_name: &str,
229236
_token: &OAuthToken,
230237
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<OAuthToken>> + Send + '_>> {
231-
let provider = self.provider.clone();
232238
let profile_name = profile_name.to_string();
233239
Box::pin(async move {
234-
super::providers::refresh_device_code_pub(&provider, &profile_name).await
240+
let token = super::source::load_keyring(&profile_name)?;
241+
let refresh_token = token
242+
.refresh_token
243+
.as_ref()
244+
.ok_or_else(|| anyhow::anyhow!("no refresh_token, please re-login"))?;
245+
let client = reqwest::Client::new();
246+
let resp = super::server::refresh_access_token(
247+
&client,
248+
"https://chat.qwen.ai/api/oauth/token",
249+
refresh_token,
250+
"claudex-qwen",
251+
)
252+
.await?;
253+
let mut new_token = OAuthToken::from_token_response(&resp)
254+
.ok_or_else(|| anyhow::anyhow!("failed to parse refreshed token"))?;
255+
if new_token.refresh_token.is_none() {
256+
new_token.refresh_token = token.refresh_token;
257+
}
258+
Ok(new_token)
235259
})
236260
}
237261

238262
fn read_external_token(&self) -> Result<OAuthToken> {
239-
super::token::read_external_token(&self.provider)
263+
anyhow::bail!("Qwen has no external CLI credentials")
240264
}
241265
}
242266

@@ -246,38 +270,29 @@ mod tests {
246270

247271
#[test]
248272
fn test_factory_returns_correct_provider() {
249-
let handler = for_provider(&OAuthProvider::Claude);
250-
assert_eq!(handler.provider(), OAuthProvider::Claude);
251-
252-
let handler = for_provider(&OAuthProvider::Openai);
253-
assert_eq!(handler.provider(), OAuthProvider::Openai);
254-
255-
let handler = for_provider(&OAuthProvider::Google);
256-
assert_eq!(handler.provider(), OAuthProvider::Google);
257-
258-
let handler = for_provider(&OAuthProvider::Qwen);
259-
assert_eq!(handler.provider(), OAuthProvider::Qwen);
260-
261-
let handler = for_provider(&OAuthProvider::Kimi);
262-
assert_eq!(handler.provider(), OAuthProvider::Kimi);
263-
264-
let handler = for_provider(&OAuthProvider::Github);
265-
assert_eq!(handler.provider(), OAuthProvider::Github);
273+
assert_eq!(for_provider(&OAuthProvider::Claude).provider(), OAuthProvider::Claude);
274+
assert_eq!(for_provider(&OAuthProvider::Chatgpt).provider(), OAuthProvider::Chatgpt);
275+
// Openai normalizes to Chatgpt handler
276+
assert_eq!(for_provider(&OAuthProvider::Openai).provider(), OAuthProvider::Chatgpt);
277+
assert_eq!(for_provider(&OAuthProvider::Google).provider(), OAuthProvider::Google);
278+
assert_eq!(for_provider(&OAuthProvider::Qwen).provider(), OAuthProvider::Qwen);
279+
assert_eq!(for_provider(&OAuthProvider::Kimi).provider(), OAuthProvider::Kimi);
280+
assert_eq!(for_provider(&OAuthProvider::Github).provider(), OAuthProvider::Github);
266281
}
267282

268283
#[test]
269284
fn test_all_providers_have_handler() {
270285
let providers = [
271286
OAuthProvider::Claude,
287+
OAuthProvider::Chatgpt,
272288
OAuthProvider::Openai,
273289
OAuthProvider::Google,
274290
OAuthProvider::Qwen,
275291
OAuthProvider::Kimi,
276292
OAuthProvider::Github,
277293
];
278294
for p in &providers {
279-
let handler = for_provider(p);
280-
assert_eq!(&handler.provider(), p);
295+
let _handler = for_provider(p);
281296
}
282297
}
283298
}

0 commit comments

Comments
 (0)