mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 19:12:04 +08:00
a35209a6e7
Add an xAI OAuth manager using the OAuth 2.0 Device Authorization Grant with endpoints resolved from xAI's OIDC discovery document. All HTTP goes through the app-managed proxy client. - Managed provider kind xai_oauth: forced openai_responses wire format, pinned api.x.ai base URL, bearer injection gated to the xAI origin, tokens registered for log redaction, single-auth-key takeover policy. - Token cache cannot bypass account state: cache hits re-validate account usability, refresh commits run under the mutation lock with a refresh-token CAS check, and pending logins are re-checked before an account is persisted. - Refresh classification: 401/403 with any body and 400 with a non-JSON body mark the account for re-auth; 429/5xx stay transient. - Shared auth_* commands dispatch to xAI with guard types mirroring the Copilot/Codex branches.
73 lines
2.1 KiB
Rust
73 lines
2.1 KiB
Rust
//! xAI OAuth state and xAI-specific commands.
|
|
|
|
use crate::proxy::providers::xai_oauth_auth::XaiOAuthManager;
|
|
use crate::proxy::providers::XAI_API_BASE_URL;
|
|
use crate::services::model_fetch::FetchedModel;
|
|
use serde::Deserialize;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use tauri::State;
|
|
use tokio::sync::RwLock;
|
|
|
|
pub struct XaiOAuthState(pub Arc<RwLock<XaiOAuthManager>>);
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ModelsResponse {
|
|
#[serde(default)]
|
|
data: Vec<ModelEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct ModelEntry {
|
|
id: String,
|
|
#[serde(default)]
|
|
owned_by: Option<String>,
|
|
}
|
|
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn get_xai_oauth_models(
|
|
account_id: Option<String>,
|
|
state: State<'_, XaiOAuthState>,
|
|
) -> Result<Vec<FetchedModel>, String> {
|
|
let manager = state.0.read().await;
|
|
let resolved = match account_id
|
|
.as_deref()
|
|
.map(str::trim)
|
|
.filter(|id| !id.is_empty())
|
|
{
|
|
Some(id) => Some(id.to_string()),
|
|
None => manager.default_account_id().await,
|
|
};
|
|
let account_id = resolved.ok_or_else(|| "No usable xAI account available".to_string())?;
|
|
let token = manager
|
|
.get_valid_token_for_account(&account_id)
|
|
.await
|
|
.map_err(|error| format!("xAI OAuth token unavailable: {error}"))?;
|
|
|
|
let response = crate::proxy::http_client::get()
|
|
.get(format!("{XAI_API_BASE_URL}/models"))
|
|
.bearer_auth(token)
|
|
.timeout(Duration::from_secs(15))
|
|
.send()
|
|
.await
|
|
.map_err(|error| format!("xAI models request failed: {error}"))?;
|
|
let status = response.status();
|
|
if !status.is_success() {
|
|
return Err(format!("xAI models request failed: HTTP {status}"));
|
|
}
|
|
let payload: ModelsResponse = response
|
|
.json()
|
|
.await
|
|
.map_err(|_| "xAI models response was not valid JSON".to_string())?;
|
|
let mut models: Vec<FetchedModel> = payload
|
|
.data
|
|
.into_iter()
|
|
.map(|model| FetchedModel {
|
|
id: model.id,
|
|
owned_by: model.owned_by,
|
|
})
|
|
.collect();
|
|
models.sort_by(|a, b| a.id.cmp(&b.id));
|
|
Ok(models)
|
|
}
|