mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-26 14:35:22 +08:00
8ccfbd36d6
* refactor(toolsearch): replace binary patch with ENABLE_TOOL_SEARCH env var toggle
- Remove toolsearch_patch.rs binary patching mechanism (~590 lines)
- Delete `toolsearch_patch.rs` and `commands/toolsearch.rs`
- Remove auto-patch startup logic and command registration from lib.rs
- Remove `tool_search_bypass` field from settings.rs
- Remove frontend settings ToggleRow, useSettings hook sync logic, and API methods
- Clean up zh/en/ja i18n keys (notifications + settings)
- Add ENABLE_TOOL_SEARCH toggle to Claude provider form
- Add checkbox in CommonConfigEditor.tsx (alongside teammates toggle)
- When enabled, writes `"env": { "ENABLE_TOOL_SEARCH": "true" }`
- When disabled, removes the key; takes effect on provider switch
- Add zh/en/ja i18n key: `claudeConfig.enableToolSearch`
Claude Code 2.1.76+ natively supports this env var, eliminating the need for binary patching.
* feat(claude): add effortLevel high toggle to provider form
- Add "high-effort thinking" checkbox to Claude provider config form
- When checked, writes `"effortLevel": "high"`; when unchecked, removes the field
- Add zh/en/ja i18n translations
* refactor(claude): remove deprecated alwaysThinking toggle
- Claude Code now enables extended thinking by default; alwaysThinkingEnabled is a no-op
- Thinking control is now handled via effortLevel (added in prior commit)
- Remove state, switch case, and checkbox UI from CommonConfigEditor
- Clean up alwaysThinking i18n keys across zh/en/ja locales
* feat(opencode): add setCacheKey: true to all provider presets
- Add setCacheKey: true to options in all 33 regular presets
- Add setCacheKey: true to OPENCODE_DEFAULT_CONFIG for custom providers
- Exclude 2 OMO presets (Oh My OpenCode / Slim) which have their own config mechanism
Closes #1523
* fix(codex): resolve 1M context window toggle causing MCP editor flicker
- Add localValueRef to short-circuit duplicate CodeMirror updateListener callbacks,
breaking the React state → CodeMirror → stale onChange → React state feedback loop
- Use localValueRef.current in handleContextWindowToggle and handleCompactLimitChange
to avoid stale closure reads
- Change compact limit input from type="number" to type="text" with inputMode="numeric"
to remove unnecessary spinner buttons
* feat(codex): add 1M context window toggle utilities and i18n keys
- Add extractCodexTopLevelInt, setCodexTopLevelInt, removeCodexTopLevelField
TOML helpers in providerConfigUtils.ts
- Add i18n keys for contextWindow1M, autoCompactLimit in zh/en/ja locales
* feat(claude): collapse model mapping fields by default
- Wrap 5 model mapping inputs in a Collapsible, collapsed by default
- Auto-expand when any model value is present (including preset-filled)
- Show hint text when collapsed explaining most users need no config
- Add zh/en/ja i18n keys for toggle label and collapsed hint
- Use variant={null} to avoid ghost button hover style clash in dark mode
* feat(claude): merge advanced fields into single collapsible section
- Merge API format, auth field, and model mapping into a unified "Advanced Options" collapsible
- Extend smart-expand logic to detect non-default values across all advanced fields
- Preserve model mapping sub-header and hint with a separator line
- Update zh/en/ja i18n keys (advancedOptionsToggle, advancedOptionsHint, modelMappingLabel, modelMappingHint)
* feat(copilot): add GitHub Copilot reverse proxy support
Add GitHub Copilot as a Claude provider variant with OAuth device code
authentication and Anthropic ↔ OpenAI format transformation.
Backend:
- Add CopilotAuthManager for GitHub OAuth device code flow
- Implement Copilot token auto-refresh (60s before expiry)
- Persist GitHub token to ~/.cc-switch/copilot_auth.json
- Add ProviderType::GitHubCopilot and AuthStrategy::GitHubCopilot
- Modify forwarder to use /chat/completions for Copilot
- Add Copilot-specific headers (Editor-Version, Editor-Plugin-Version)
Frontend:
- Add CopilotAuthSection component for OAuth UI
- Add useCopilotAuth hook for OAuth state management
- Auto-copy user code to clipboard and open browser
- Use 8-second polling interval to avoid GitHub rate limits
- Skip API Key validation for Copilot providers
- Add GitHub Copilot preset with claude-sonnet-4 model
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* fix(copilot): remove is_expired() calls from tests
Remove references to deleted is_expired() method in test code.
Only is_expiring_soon() is needed for token refresh logic.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* feat(copilot): add real-time model listing from Copilot API
- Add fetch_models() to CopilotAuthManager calling GET /models endpoint
- Add copilot_get_models Tauri command
- Add copilotGetModels() frontend API wrapper
- Modify ClaudeFormFields to show model dropdown for Copilot providers
- Fetches available models on component mount when isCopilotPreset
- Groups models by vendor (Anthropic, OpenAI, Google, etc.)
- Input + dropdown button combo allows both manual entry and selection
- Non-Copilot providers keep original plain Input behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(copilot): add usage query integration
- Add Copilot usage API integration (fetch_usage method)
- Add copilot_get_usage Tauri command
- Add GitHub Copilot template in usage query modal
- Unify naming: copilot → github_copilot
- Add constants management (TEMPLATE_TYPES, PROVIDER_TYPES)
- Improve error handling with detailed error messages
- Add database migration (v5 → v6) for template type update
- Add i18n translations (zh, en, ja)
- Improve type safety with TemplateType
- Apply code formatting (cargo fmt, prettier)
* 修复github 登录和注销问题 ,模型选择问题
* feat(copilot): add multi-account support for GitHub Copilot
- Add multi-account storage structure with v1 to v2 migration
- Add per-account token caching and auto-refresh
- Add new Tauri commands for account management
- Integrate account selection in Proxy forwarder
- Add account selection UI in CopilotAuthSection
- Save githubAccountId to ProviderMeta
- Add i18n translations for multi-account features (zh/en/ja)
* 修复用量查询Reset字段出现多余字符
* refactor(auth-binding): introduce generic provider auth binding primitives
- add shared authBinding types in Rust and TypeScript while keeping githubAccountId as a compatibility field\n- resolve Copilot token, models, and usage through provider-bound account lookup instead of only the implicit default account\n- fix the Unix build regression in settings.rs by restoring std::io::Write for write_all()\n- remove the accidental .github ignore entry and drop leftover Copilot form debug logs\n- keep the first migration step non-breaking by writing both authBinding and the legacy githubAccountId field from the form
* refactor(auth-service): add managed auth command surface and explicit default account state
- introduce generic managed auth commands and frontend auth API wrappers for provider-scoped login, status, account listing, removal, logout, and default-account selection\n- store an explicit Copilot default_account_id instead of relying on HashMap iteration order, and use it consistently for fallback token/model/usage resolution\n- sort managed accounts deterministically and surface default-account state to the UI\n- refactor the Copilot form hook to wrap a generic useManagedAuth implementation while preserving the existing component contract\n- add default-account controls to the Copilot auth section and extend Copilot auth status serialization/tests for the new state
* feat(auth-center): add a dedicated settings entrypoint for managed OAuth accounts
- add an Auth Center tab to Settings so managed OAuth accounts are no longer hidden inside individual provider forms\n- introduce a first AuthCenterPanel that hosts GitHub Copilot account management as the initial managed auth provider\n- keep the provider form experience intact while establishing a global account-management surface for future providers such as OpenAI\n- validate that the new settings tab works cleanly with the generic managed auth hook and existing Copilot account controls
* feat(add-provider): expose managed OAuth sources alongside universal providers
- add an OAuth tab to the Add Provider flow so managed auth sources sit beside app-specific and universal providers\n- reuse the new Auth Center panel inside the dialog, keeping account management discoverable during provider creation\n- make the dialog footer adapt to the OAuth tab so account setup does not pretend to create a provider directly\n- align the add-provider UX with the new architecture where OAuth accounts are global assets and providers bind to them later
* fix(auth-reliability): harden managed auth persistence and refresh behavior
- replace direct Copilot auth store writes with private temp-file writes and atomic rename semantics, and document the local token storage limitation\n- add per-account refresh locks plus a double-check path so concurrent requests do not stampede GitHub token refresh\n- surface legacy migration failures through auth status, expose them in the UI, and add translated copy for the new account-state labels\n- stop writing the legacy githubAccountId field from the provider form while keeping compatibility reads in place\n- add logout error recovery and Copilot model-load toasts so auth failures are no longer silently swallowed
* refactor(copilot-detection): prefer provider type before URL fallbacks
- update forwarder endpoint rewriting to treat providerType as the primary GitHub Copilot signal\n- keep githubcopilot.com string matching only as a compatibility fallback for older provider records without providerType\n- reduce one more path where Copilot behavior depended purely on URL heuristics
* fix(copilot-auth): add cancel button to error state in CopilotAuthSection
- 错误状态下仅有"重试"按钮,用户无法退出(如不可恢复的 403 未订阅错误)
- 新增"取消"按钮,复用已有的 cancelAuth 逻辑重置为 idle 状态
* 修复打包后github账号头像显示异常
* 修复github copilot 来源的模型测试报错
* feat(copilot-preset): add default model presets for GitHub Copilot
- 补充 Copilot 预设的默认模型配置,用户选完预设即可直接使用
- ANTHROPIC_MODEL: claude-opus-4.6
- ANTHROPIC_DEFAULT_HAIKU_MODEL: claude-haiku-4.5
- ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4.6
- ANTHROPIC_DEFAULT_OPUS_MODEL: claude-opus-4.6
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
490 lines
17 KiB
Rust
490 lines
17 KiB
Rust
//! Provider Adapters Module
|
||
//!
|
||
//! 供应商适配器模块,提供统一的接口抽象不同上游供应商的处理逻辑。
|
||
//!
|
||
//! ## 模块结构
|
||
//! - `adapter`: 定义 `ProviderAdapter` trait
|
||
//! - `auth`: 认证类型和策略
|
||
//! - `claude`: Claude (Anthropic) 适配器
|
||
//! - `codex`: Codex (OpenAI) 适配器
|
||
//! - `gemini`: Gemini (Google) 适配器
|
||
//! - `models`: API 数据模型
|
||
//! - `transform`: 格式转换
|
||
|
||
mod adapter;
|
||
mod auth;
|
||
mod claude;
|
||
mod codex;
|
||
pub mod copilot_auth;
|
||
mod gemini;
|
||
pub mod models;
|
||
pub mod streaming;
|
||
pub mod streaming_responses;
|
||
pub mod transform;
|
||
pub mod transform_responses;
|
||
|
||
use crate::app_config::AppType;
|
||
use crate::provider::Provider;
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
// 公开导出
|
||
pub use adapter::ProviderAdapter;
|
||
pub use auth::{AuthInfo, AuthStrategy};
|
||
pub use claude::{get_claude_api_format, ClaudeAdapter};
|
||
pub use codex::CodexAdapter;
|
||
pub use gemini::GeminiAdapter;
|
||
|
||
/// 供应商类型枚举
|
||
///
|
||
/// 区分不同供应商的具体实现方式,决定认证和请求处理逻辑。
|
||
/// 比 AppType 更细粒度,支持同一 AppType 下的多种变体。
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "snake_case")]
|
||
pub enum ProviderType {
|
||
/// Anthropic 官方 API (x-api-key + anthropic-version)
|
||
Claude,
|
||
/// Claude 中转服务 (仅 Bearer 认证,无 x-api-key)
|
||
ClaudeAuth,
|
||
/// OpenAI Codex Response API
|
||
Codex,
|
||
/// Google Gemini API (x-goog-api-key)
|
||
Gemini,
|
||
/// Google Gemini CLI (OAuth Bearer)
|
||
GeminiCli,
|
||
/// OpenRouter(已支持 Claude Code 兼容接口,默认透传;保留旧转换逻辑备用)
|
||
OpenRouter,
|
||
/// GitHub Copilot (OAuth + Copilot Token,需要 Anthropic ↔ OpenAI 转换)
|
||
GitHubCopilot,
|
||
}
|
||
|
||
impl ProviderType {
|
||
/// 是否需要格式转换
|
||
///
|
||
/// 过去 OpenRouter 需要将 Anthropic 格式转换为 OpenAI 格式;
|
||
/// 现在默认关闭转换(因为 OpenRouter 已支持 Claude Code 兼容接口)。
|
||
/// GitHub Copilot 需要转换(Anthropic → OpenAI 格式)。
|
||
#[allow(dead_code)]
|
||
pub fn needs_transform(&self) -> bool {
|
||
match self {
|
||
ProviderType::GitHubCopilot => true,
|
||
ProviderType::OpenRouter => false,
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
/// 获取默认端点
|
||
#[allow(dead_code)]
|
||
pub fn default_endpoint(&self) -> &'static str {
|
||
match self {
|
||
ProviderType::Claude | ProviderType::ClaudeAuth => "https://api.anthropic.com",
|
||
ProviderType::Codex => "https://api.openai.com",
|
||
ProviderType::Gemini | ProviderType::GeminiCli => {
|
||
"https://generativelanguage.googleapis.com"
|
||
}
|
||
ProviderType::OpenRouter => "https://openrouter.ai/api",
|
||
ProviderType::GitHubCopilot => "https://api.githubcopilot.com",
|
||
}
|
||
}
|
||
|
||
/// 从 AppType 和 Provider 配置推断供应商类型
|
||
///
|
||
/// 根据配置中的 base_url、auth_mode、api_key 格式等信息推断具体的供应商类型
|
||
#[allow(dead_code)]
|
||
pub fn from_app_type_and_config(app_type: &AppType, provider: &Provider) -> Self {
|
||
match app_type {
|
||
AppType::Claude => {
|
||
// 检测是否为 GitHub Copilot
|
||
if let Some(meta) = provider.meta.as_ref() {
|
||
if meta.provider_type.as_deref() == Some("github_copilot") {
|
||
return ProviderType::GitHubCopilot;
|
||
}
|
||
}
|
||
|
||
// 检测 base_url 是否为 GitHub Copilot
|
||
let adapter = ClaudeAdapter::new();
|
||
if let Ok(base_url) = adapter.extract_base_url(provider) {
|
||
if base_url.contains("githubcopilot.com") {
|
||
return ProviderType::GitHubCopilot;
|
||
}
|
||
// 检测是否为 OpenRouter
|
||
if base_url.contains("openrouter.ai") {
|
||
return ProviderType::OpenRouter;
|
||
}
|
||
}
|
||
// 检测是否为中转服务(仅 Bearer 认证)
|
||
// 注意:ProviderMeta 没有直接的 auth_mode 字段,
|
||
// 我们通过检查 settings_config 中的配置来判断
|
||
// 检查 settings_config 中的 auth_mode
|
||
if let Some(auth_mode) = provider
|
||
.settings_config
|
||
.get("auth_mode")
|
||
.and_then(|v| v.as_str())
|
||
{
|
||
if auth_mode == "bearer_only" {
|
||
return ProviderType::ClaudeAuth;
|
||
}
|
||
}
|
||
// 检查 env 中的 auth_mode
|
||
if let Some(env) = provider.settings_config.get("env") {
|
||
if let Some(auth_mode) = env.get("AUTH_MODE").and_then(|v| v.as_str()) {
|
||
if auth_mode == "bearer_only" {
|
||
return ProviderType::ClaudeAuth;
|
||
}
|
||
}
|
||
}
|
||
ProviderType::Claude
|
||
}
|
||
AppType::Codex => ProviderType::Codex,
|
||
AppType::Gemini => {
|
||
// 检测是否为 CLI 模式(OAuth)
|
||
let adapter = GeminiAdapter::new();
|
||
if let Some(auth) = adapter.extract_auth(provider) {
|
||
let key = &auth.api_key;
|
||
// OAuth access_token 以 ya29. 开头
|
||
if key.starts_with("ya29.") {
|
||
return ProviderType::GeminiCli;
|
||
}
|
||
// JSON 格式的 OAuth 凭证
|
||
if key.starts_with('{') {
|
||
return ProviderType::GeminiCli;
|
||
}
|
||
}
|
||
ProviderType::Gemini
|
||
}
|
||
AppType::OpenCode => {
|
||
// OpenCode doesn't support proxy, but return a default type for completeness
|
||
ProviderType::Codex // Fallback to Codex-like type
|
||
}
|
||
AppType::OpenClaw => {
|
||
// OpenClaw doesn't support proxy, but return a default type for completeness
|
||
ProviderType::Codex // Fallback to Codex-like type
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 转换为字符串表示
|
||
pub fn as_str(&self) -> &'static str {
|
||
match self {
|
||
ProviderType::Claude => "claude",
|
||
ProviderType::ClaudeAuth => "claude_auth",
|
||
ProviderType::Codex => "codex",
|
||
ProviderType::Gemini => "gemini",
|
||
ProviderType::GeminiCli => "gemini_cli",
|
||
ProviderType::OpenRouter => "openrouter",
|
||
ProviderType::GitHubCopilot => "github_copilot",
|
||
}
|
||
}
|
||
}
|
||
|
||
impl std::fmt::Display for ProviderType {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
write!(f, "{}", self.as_str())
|
||
}
|
||
}
|
||
|
||
impl std::str::FromStr for ProviderType {
|
||
type Err = String;
|
||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||
match s.to_lowercase().as_str() {
|
||
"claude" => Ok(ProviderType::Claude),
|
||
"claude_auth" | "claude-auth" => Ok(ProviderType::ClaudeAuth),
|
||
"codex" => Ok(ProviderType::Codex),
|
||
"gemini" => Ok(ProviderType::Gemini),
|
||
"gemini_cli" | "gemini-cli" => Ok(ProviderType::GeminiCli),
|
||
"openrouter" => Ok(ProviderType::OpenRouter),
|
||
"github_copilot" | "github-copilot" | "githubcopilot" => {
|
||
Ok(ProviderType::GitHubCopilot)
|
||
}
|
||
_ => Err(format!("Invalid provider type: {s}")),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 根据 AppType 获取对应的适配器
|
||
pub fn get_adapter(app_type: &AppType) -> Box<dyn ProviderAdapter> {
|
||
match app_type {
|
||
AppType::Claude => Box::new(ClaudeAdapter::new()),
|
||
AppType::Codex => Box::new(CodexAdapter::new()),
|
||
AppType::Gemini => Box::new(GeminiAdapter::new()),
|
||
AppType::OpenCode => {
|
||
// OpenCode doesn't support proxy, fallback to Codex adapter
|
||
Box::new(CodexAdapter::new())
|
||
}
|
||
AppType::OpenClaw => {
|
||
// OpenClaw doesn't support proxy, fallback to Codex adapter
|
||
Box::new(CodexAdapter::new())
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 根据 ProviderType 获取对应的适配器
|
||
#[allow(dead_code)]
|
||
pub fn get_adapter_for_provider_type(provider_type: &ProviderType) -> Box<dyn ProviderAdapter> {
|
||
match provider_type {
|
||
ProviderType::Claude
|
||
| ProviderType::ClaudeAuth
|
||
| ProviderType::OpenRouter
|
||
| ProviderType::GitHubCopilot => Box::new(ClaudeAdapter::new()),
|
||
ProviderType::Codex => Box::new(CodexAdapter::new()),
|
||
ProviderType::Gemini | ProviderType::GeminiCli => Box::new(GeminiAdapter::new()),
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use serde_json::json;
|
||
|
||
fn create_provider(config: serde_json::Value) -> Provider {
|
||
Provider {
|
||
id: "test".to_string(),
|
||
name: "Test Provider".to_string(),
|
||
settings_config: config,
|
||
website_url: None,
|
||
category: None,
|
||
created_at: None,
|
||
sort_index: None,
|
||
notes: None,
|
||
meta: None,
|
||
icon: None,
|
||
icon_color: None,
|
||
in_failover_queue: false,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_provider_type_needs_transform() {
|
||
assert!(!ProviderType::Claude.needs_transform());
|
||
assert!(!ProviderType::ClaudeAuth.needs_transform());
|
||
assert!(!ProviderType::Codex.needs_transform());
|
||
assert!(!ProviderType::Gemini.needs_transform());
|
||
assert!(!ProviderType::GeminiCli.needs_transform());
|
||
assert!(!ProviderType::OpenRouter.needs_transform());
|
||
assert!(ProviderType::GitHubCopilot.needs_transform());
|
||
}
|
||
|
||
#[test]
|
||
fn test_provider_type_default_endpoint() {
|
||
assert_eq!(
|
||
ProviderType::Claude.default_endpoint(),
|
||
"https://api.anthropic.com"
|
||
);
|
||
assert_eq!(
|
||
ProviderType::ClaudeAuth.default_endpoint(),
|
||
"https://api.anthropic.com"
|
||
);
|
||
assert_eq!(
|
||
ProviderType::Codex.default_endpoint(),
|
||
"https://api.openai.com"
|
||
);
|
||
assert_eq!(
|
||
ProviderType::Gemini.default_endpoint(),
|
||
"https://generativelanguage.googleapis.com"
|
||
);
|
||
assert_eq!(
|
||
ProviderType::GeminiCli.default_endpoint(),
|
||
"https://generativelanguage.googleapis.com"
|
||
);
|
||
assert_eq!(
|
||
ProviderType::OpenRouter.default_endpoint(),
|
||
"https://openrouter.ai/api"
|
||
);
|
||
assert_eq!(
|
||
ProviderType::GitHubCopilot.default_endpoint(),
|
||
"https://api.githubcopilot.com"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_provider_type_from_str() {
|
||
assert_eq!(
|
||
"claude".parse::<ProviderType>().unwrap(),
|
||
ProviderType::Claude
|
||
);
|
||
assert_eq!(
|
||
"claude_auth".parse::<ProviderType>().unwrap(),
|
||
ProviderType::ClaudeAuth
|
||
);
|
||
assert_eq!(
|
||
"claude-auth".parse::<ProviderType>().unwrap(),
|
||
ProviderType::ClaudeAuth
|
||
);
|
||
assert_eq!(
|
||
"codex".parse::<ProviderType>().unwrap(),
|
||
ProviderType::Codex
|
||
);
|
||
assert_eq!(
|
||
"gemini".parse::<ProviderType>().unwrap(),
|
||
ProviderType::Gemini
|
||
);
|
||
assert_eq!(
|
||
"gemini_cli".parse::<ProviderType>().unwrap(),
|
||
ProviderType::GeminiCli
|
||
);
|
||
assert_eq!(
|
||
"gemini-cli".parse::<ProviderType>().unwrap(),
|
||
ProviderType::GeminiCli
|
||
);
|
||
assert_eq!(
|
||
"openrouter".parse::<ProviderType>().unwrap(),
|
||
ProviderType::OpenRouter
|
||
);
|
||
assert_eq!(
|
||
"github_copilot".parse::<ProviderType>().unwrap(),
|
||
ProviderType::GitHubCopilot
|
||
);
|
||
assert_eq!(
|
||
"github-copilot".parse::<ProviderType>().unwrap(),
|
||
ProviderType::GitHubCopilot
|
||
);
|
||
assert_eq!(
|
||
"githubcopilot".parse::<ProviderType>().unwrap(),
|
||
ProviderType::GitHubCopilot
|
||
);
|
||
assert!("invalid".parse::<ProviderType>().is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_provider_type_as_str() {
|
||
assert_eq!(ProviderType::Claude.as_str(), "claude");
|
||
assert_eq!(ProviderType::ClaudeAuth.as_str(), "claude_auth");
|
||
assert_eq!(ProviderType::Codex.as_str(), "codex");
|
||
assert_eq!(ProviderType::Gemini.as_str(), "gemini");
|
||
assert_eq!(ProviderType::GeminiCli.as_str(), "gemini_cli");
|
||
assert_eq!(ProviderType::OpenRouter.as_str(), "openrouter");
|
||
assert_eq!(ProviderType::GitHubCopilot.as_str(), "github_copilot");
|
||
}
|
||
|
||
#[test]
|
||
fn test_provider_type_serde() {
|
||
// Test serialization
|
||
let claude = ProviderType::Claude;
|
||
let serialized = serde_json::to_string(&claude).unwrap();
|
||
assert_eq!(serialized, "\"claude\"");
|
||
|
||
let claude_auth = ProviderType::ClaudeAuth;
|
||
let serialized = serde_json::to_string(&claude_auth).unwrap();
|
||
assert_eq!(serialized, "\"claude_auth\"");
|
||
|
||
// Test deserialization
|
||
let deserialized: ProviderType = serde_json::from_str("\"claude\"").unwrap();
|
||
assert_eq!(deserialized, ProviderType::Claude);
|
||
|
||
let deserialized: ProviderType = serde_json::from_str("\"gemini_cli\"").unwrap();
|
||
assert_eq!(deserialized, ProviderType::GeminiCli);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_claude_direct() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
|
||
"ANTHROPIC_AUTH_TOKEN": "sk-ant-test"
|
||
}
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Claude, &provider);
|
||
assert_eq!(provider_type, ProviderType::Claude);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_claude_openrouter() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api",
|
||
"OPENROUTER_API_KEY": "sk-or-test"
|
||
}
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Claude, &provider);
|
||
assert_eq!(provider_type, ProviderType::OpenRouter);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_claude_auth() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"ANTHROPIC_BASE_URL": "https://some-proxy.com",
|
||
"ANTHROPIC_AUTH_TOKEN": "sk-test"
|
||
},
|
||
"auth_mode": "bearer_only"
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Claude, &provider);
|
||
assert_eq!(provider_type, ProviderType::ClaudeAuth);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_codex() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"OPENAI_API_KEY": "sk-test"
|
||
}
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Codex, &provider);
|
||
assert_eq!(provider_type, ProviderType::Codex);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_gemini_api_key() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"GEMINI_API_KEY": "AIza-test-key"
|
||
}
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Gemini, &provider);
|
||
assert_eq!(provider_type, ProviderType::Gemini);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_gemini_cli_oauth() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"GEMINI_API_KEY": "ya29.test-access-token"
|
||
}
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Gemini, &provider);
|
||
assert_eq!(provider_type, ProviderType::GeminiCli);
|
||
}
|
||
|
||
#[test]
|
||
fn test_from_app_type_gemini_cli_json() {
|
||
let provider = create_provider(json!({
|
||
"env": {
|
||
"GEMINI_API_KEY": "{\"access_token\":\"ya29.test\",\"refresh_token\":\"1//test\"}"
|
||
}
|
||
}));
|
||
|
||
let provider_type = ProviderType::from_app_type_and_config(&AppType::Gemini, &provider);
|
||
assert_eq!(provider_type, ProviderType::GeminiCli);
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_adapter_for_provider_type() {
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::Claude);
|
||
assert_eq!(adapter.name(), "Claude");
|
||
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::ClaudeAuth);
|
||
assert_eq!(adapter.name(), "Claude");
|
||
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::OpenRouter);
|
||
assert_eq!(adapter.name(), "Claude");
|
||
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::GitHubCopilot);
|
||
assert_eq!(adapter.name(), "Claude");
|
||
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::Codex);
|
||
assert_eq!(adapter.name(), "Codex");
|
||
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::Gemini);
|
||
assert_eq!(adapter.name(), "Gemini");
|
||
|
||
let adapter = get_adapter_for_provider_type(&ProviderType::GeminiCli);
|
||
assert_eq!(adapter.name(), "Gemini");
|
||
}
|
||
}
|