mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-27 08:14:33 +08:00
87635e7fc6
* feat(copilot): add GitHub Enterprise Server support * fix(copilot): address GHES PR review findings (P1 + 2×P2) - P1: Use composite account ID (domain:user_id) for GHES to prevent cross-instance ID collisions; github.com keeps plain numeric ID for backward compatibilit - P2-a: Use get_api_endpoint() for model list URL with automatic fallback to static URL when dynamic endpoint resolution fails - P2-b: Add normalize_github_domain() as backend SSOT for domain normalization (lowercase, strip protocol/path/query, reject userinfo) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
222 lines
6.7 KiB
Rust
222 lines
6.7 KiB
Rust
//! GitHub Copilot Tauri Commands
|
|
//!
|
|
//! 提供 Copilot OAuth 认证相关的 Tauri 命令,支持多账号管理。
|
|
|
|
use crate::proxy::providers::copilot_auth::{
|
|
CopilotAuthManager, CopilotAuthStatus, CopilotModel, CopilotUsageResponse, GitHubAccount,
|
|
GitHubDeviceCodeResponse,
|
|
};
|
|
use std::sync::Arc;
|
|
use tauri::State;
|
|
use tokio::sync::RwLock;
|
|
|
|
/// Copilot 认证状态
|
|
pub struct CopilotAuthState(pub Arc<RwLock<CopilotAuthManager>>);
|
|
|
|
// ==================== 设备码流程 ====================
|
|
|
|
/// 启动设备码流程
|
|
///
|
|
/// 返回设备码和用户码,用于 OAuth 认证
|
|
#[tauri::command]
|
|
pub async fn copilot_start_device_flow(
|
|
github_domain: Option<String>,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<GitHubDeviceCodeResponse, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager
|
|
.start_device_flow(github_domain.as_deref())
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 轮询 OAuth Token(向后兼容)
|
|
///
|
|
/// 使用设备码轮询 GitHub,等待用户完成授权
|
|
/// 返回 true 表示授权成功,false 表示等待中
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_poll_for_auth(
|
|
device_code: String,
|
|
github_domain: Option<String>,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<bool, String> {
|
|
let auth_manager = state.0.write().await;
|
|
match auth_manager
|
|
.poll_for_token(&device_code, github_domain.as_deref())
|
|
.await
|
|
{
|
|
Ok(Some(_account)) => {
|
|
log::info!("[CopilotAuth] 用户已授权");
|
|
Ok(true)
|
|
}
|
|
Ok(None) => Ok(false),
|
|
Err(crate::proxy::providers::copilot_auth::CopilotAuthError::AuthorizationPending) => {
|
|
Ok(false)
|
|
}
|
|
Err(e) => {
|
|
log::error!("[CopilotAuth] 轮询失败: {e}");
|
|
Err(e.to_string())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 轮询 OAuth Token(多账号版本)
|
|
///
|
|
/// 返回新添加的账号信息,如果授权成功
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_poll_for_account(
|
|
device_code: String,
|
|
github_domain: Option<String>,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<Option<GitHubAccount>, String> {
|
|
let auth_manager = state.0.write().await;
|
|
match auth_manager
|
|
.poll_for_token(&device_code, github_domain.as_deref())
|
|
.await
|
|
{
|
|
Ok(account) => Ok(account),
|
|
Err(crate::proxy::providers::copilot_auth::CopilotAuthError::AuthorizationPending) => {
|
|
Ok(None)
|
|
}
|
|
Err(e) => {
|
|
log::error!("[CopilotAuth] 轮询失败: {e}");
|
|
Err(e.to_string())
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==================== 多账号管理 ====================
|
|
|
|
/// 列出所有已认证的账号
|
|
#[tauri::command]
|
|
pub async fn copilot_list_accounts(
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<Vec<GitHubAccount>, String> {
|
|
let auth_manager = state.0.read().await;
|
|
Ok(auth_manager.list_accounts().await)
|
|
}
|
|
|
|
/// 移除指定账号
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_remove_account(
|
|
account_id: String,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<(), String> {
|
|
let auth_manager = state.0.write().await;
|
|
auth_manager
|
|
.remove_account(&account_id)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 设置默认账号
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_set_default_account(
|
|
account_id: String,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<(), String> {
|
|
let auth_manager = state.0.write().await;
|
|
auth_manager
|
|
.set_default_account(&account_id)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
// ==================== 状态查询 ====================
|
|
|
|
/// 获取认证状态(包含所有账号)
|
|
#[tauri::command]
|
|
pub async fn copilot_get_auth_status(
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<CopilotAuthStatus, String> {
|
|
let auth_manager = state.0.read().await;
|
|
Ok(auth_manager.get_status().await)
|
|
}
|
|
|
|
/// 检查是否已认证(有任意账号)
|
|
#[tauri::command]
|
|
pub async fn copilot_is_authenticated(state: State<'_, CopilotAuthState>) -> Result<bool, String> {
|
|
let auth_manager = state.0.read().await;
|
|
Ok(auth_manager.is_authenticated().await)
|
|
}
|
|
|
|
/// 注销所有 Copilot 认证
|
|
#[tauri::command]
|
|
pub async fn copilot_logout(state: State<'_, CopilotAuthState>) -> Result<(), String> {
|
|
let auth_manager = state.0.write().await;
|
|
auth_manager.clear_auth().await.map_err(|e| e.to_string())
|
|
}
|
|
|
|
// ==================== Token 获取 ====================
|
|
|
|
/// 获取有效的 Copilot Token(向后兼容:使用第一个账号)
|
|
///
|
|
/// 内部使用,用于代理请求
|
|
#[tauri::command]
|
|
pub async fn copilot_get_token(state: State<'_, CopilotAuthState>) -> Result<String, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager
|
|
.get_valid_token()
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 获取指定账号的有效 Copilot Token
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_get_token_for_account(
|
|
account_id: String,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<String, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager
|
|
.get_valid_token_for_account(&account_id)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
// ==================== 模型和使用量 ====================
|
|
|
|
/// 获取 Copilot 可用模型列表(向后兼容:使用第一个账号)
|
|
#[tauri::command]
|
|
pub async fn copilot_get_models(
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<Vec<CopilotModel>, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager.fetch_models().await.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 获取指定账号的 Copilot 可用模型列表
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_get_models_for_account(
|
|
account_id: String,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<Vec<CopilotModel>, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager
|
|
.fetch_models_for_account(&account_id)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 获取 Copilot 使用量信息(向后兼容:使用第一个账号)
|
|
#[tauri::command]
|
|
pub async fn copilot_get_usage(
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<CopilotUsageResponse, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager.fetch_usage().await.map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 获取指定账号的 Copilot 使用量信息
|
|
#[tauri::command(rename_all = "camelCase")]
|
|
pub async fn copilot_get_usage_for_account(
|
|
account_id: String,
|
|
state: State<'_, CopilotAuthState>,
|
|
) -> Result<CopilotUsageResponse, String> {
|
|
let auth_manager = state.0.read().await;
|
|
auth_manager
|
|
.fetch_usage_for_account(&account_id)
|
|
.await
|
|
.map_err(|e| e.to_string())
|
|
}
|