From 99453292a9ec33b6363194f4ffdaef526470e1b6 Mon Sep 17 00:00:00 2001 From: coso Date: Fri, 16 Jan 2026 00:52:36 +0800 Subject: [PATCH] chore: prepare workspace migration and bump version to 0.47.3 --- .github/workflows/release.yml | 26 ++++++- package.json | 2 +- src-tauri/Cargo.lock | 78 ++++++++++++++++++- src-tauri/Cargo.toml | 25 ++++-- src-tauri/crates/agent/Cargo.toml | 17 ++++ src-tauri/crates/agent/src/lib.rs | 7 ++ src-tauri/crates/app/Cargo.toml | 16 ++++ src-tauri/crates/app/src/lib.rs | 7 ++ src-tauri/crates/core/Cargo.toml | 13 ++++ src-tauri/crates/core/src/lib.rs | 7 ++ src-tauri/crates/providers/Cargo.toml | 15 ++++ src-tauri/crates/providers/src/lib.rs | 7 ++ src-tauri/crates/server/Cargo.toml | 17 ++++ src-tauri/crates/server/src/lib.rs | 7 ++ src-tauri/src/app/runner.rs | 2 + .../src/commands/api_key_provider_cmd.rs | 9 ++- .../src/services/api_key_provider_service.rs | 44 ++++++++--- src-tauri/tauri.conf.json | 2 +- .../api-key/ApiKeyProviderSection.tsx | 30 +++---- src/lib/api/apiKeyProvider.ts | 44 +++++++++++ 20 files changed, 338 insertions(+), 37 deletions(-) create mode 100644 src-tauri/crates/agent/Cargo.toml create mode 100644 src-tauri/crates/agent/src/lib.rs create mode 100644 src-tauri/crates/app/Cargo.toml create mode 100644 src-tauri/crates/app/src/lib.rs create mode 100644 src-tauri/crates/core/Cargo.toml create mode 100644 src-tauri/crates/core/src/lib.rs create mode 100644 src-tauri/crates/providers/Cargo.toml create mode 100644 src-tauri/crates/providers/src/lib.rs create mode 100644 src-tauri/crates/server/Cargo.toml create mode 100644 src-tauri/crates/server/src/lib.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b81191512..414e1da9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,11 @@ on: push: tags: - 'v*' + paths-ignore: + - 'docs/**' + - '*.md' + - '.github/workflows/deploy-docs.yml' + - '.github/workflows/update-homebrew.yml' workflow_dispatch: inputs: version: @@ -17,7 +22,6 @@ permissions: actions: read env: - CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 RUSTUP_MAX_RETRIES: 10 CARGO_TERM_COLOR: always @@ -68,6 +72,9 @@ jobs: with: targets: ${{ matrix.target }} + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.9 + - name: Setup Rust cache uses: Swatinem/rust-cache@v2 with: @@ -75,6 +82,7 @@ jobs: shared-key: "rust-${{ matrix.target }}" cache-on-failure: true cache-all-crates: true + save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} - name: Install frontend dependencies run: pnpm install --no-frozen-lockfile @@ -109,6 +117,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_PROFILE_RELEASE_LTO: thin CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16 + CARGO_INCREMENTAL: 0 + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: sccache # Apple 签名环境变量 APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} @@ -144,6 +155,19 @@ jobs: prerelease: false args: --target ${{ matrix.target }} + # 构建后清理,节省缓存空间 + - name: Post-build cleanup + run: | + rm -rf src-tauri/target/release/deps || true + rm -rf src-tauri/target/release/build || true + rm -rf src-tauri/target/release/incremental || true + rm -rf src-tauri/target/${{ matrix.target }}/release/deps || true + rm -rf src-tauri/target/${{ matrix.target }}/release/build || true + rm -rf src-tauri/target/${{ matrix.target }}/release/incremental || true + + - name: Show sccache stats + run: sccache --show-stats + # 清理 keychain - name: Cleanup Keychain if: matrix.platform == 'macos-latest' && always() diff --git a/package.json b/package.json index 66fb1e0a3..c99cb0260 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "proxycast", "private": true, - "version": "0.47.2", + "version": "0.47.3", "type": "module", "repository": { "type": "git", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 401b50e52..3b4918439 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "agent" +version = "0.1.0" +dependencies = [ + "anyhow", + "aster", + "core", + "futures", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "ahash" version = "0.8.12" @@ -137,6 +152,22 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "agent", + "anyhow", + "core", + "providers", + "serde", + "serde_json", + "server", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "arboard" version = "3.6.1" @@ -1781,6 +1812,19 @@ dependencies = [ "url", ] +[[package]] +name = "core" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "uuid", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -6057,9 +6101,24 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "providers" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "core", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "proxycast" -version = "0.47.2" +version = "0.47.3" dependencies = [ "anyhow", "arboard", @@ -7302,6 +7361,23 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "server" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.9", + "core", + "providers", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tower 0.4.13", + "tower-http 0.5.2", + "tracing", +] + [[package]] name = "servo_arc" version = "0.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b9a27cfef..f8697a777 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,12 +1,23 @@ -[package] -name = "proxycast" -version = "0.47.2" -description = "AI API Proxy Desktop App" -authors = ["you"] +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.package] +version = "0.47.3" edition = "2021" +authors = ["you"] repository = "https://github.com/aiclientproxy/proxycast" homepage = "https://github.com/aiclientproxy/proxycast" +[package] +name = "proxycast" +version.workspace = true +description = "AI API Proxy Desktop App" +authors.workspace = true +edition.workspace = true +repository.workspace = true +homepage.workspace = true + [lib] name = "proxycast_lib" crate-type = ["lib", "cdylib", "staticlib"] @@ -77,8 +88,8 @@ mouse_position = "0.1.4" window-vibrancy = "0.7.1" if-addrs = "0.13" -# Aster Agent Framework -aster = { git = "https://github.com/astercloud/aster-rust", branch = "main" } +# Aster Agent Framework - 使用固定 tag 避免每次重新拉取 +aster = { git = "https://github.com/astercloud/aster-rust", tag = "v0.3.0" } # Platform specific dependencies for browser interceptor diff --git a/src-tauri/crates/agent/Cargo.toml b/src-tauri/crates/agent/Cargo.toml new file mode 100644 index 000000000..c6c8b493f --- /dev/null +++ b/src-tauri/crates/agent/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "agent" +version = "0.1.0" +edition = "2021" + +[dependencies] +core = { path = "../core" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +tokio = { version = "1", features = ["full"] } +futures = "0.3" + +# Aster Agent Framework +aster = { git = "https://github.com/astercloud/aster-rust", tag = "v0.3.0" } diff --git a/src-tauri/crates/agent/src/lib.rs b/src-tauri/crates/agent/src/lib.rs new file mode 100644 index 000000000..ca73cd0aa --- /dev/null +++ b/src-tauri/crates/agent/src/lib.rs @@ -0,0 +1,7 @@ +//! Aster Agent 集成模块 +//! +//! 包含 agent 相关功能,依赖 aster 框架 + +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/src-tauri/crates/app/Cargo.toml b/src-tauri/crates/app/Cargo.toml new file mode 100644 index 000000000..2cf3d1548 --- /dev/null +++ b/src-tauri/crates/app/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "app" +version = "0.1.0" +edition = "2021" + +[dependencies] +core = { path = "../core" } +providers = { path = "../providers" } +server = { path = "../server" } +agent = { path = "../agent" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +tokio = { version = "1", features = ["full"] } diff --git a/src-tauri/crates/app/src/lib.rs b/src-tauri/crates/app/src/lib.rs new file mode 100644 index 000000000..116cc1858 --- /dev/null +++ b/src-tauri/crates/app/src/lib.rs @@ -0,0 +1,7 @@ +//! Tauri 应用入口模块 +//! +//! 包含 app, commands, tray, services 等功能 + +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/src-tauri/crates/core/Cargo.toml b/src-tauri/crates/core/Cargo.toml new file mode 100644 index 000000000..0a74f42d2 --- /dev/null +++ b/src-tauri/crates/core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "core" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1", features = ["v4"] } diff --git a/src-tauri/crates/core/src/lib.rs b/src-tauri/crates/core/src/lib.rs new file mode 100644 index 000000000..df932d6f7 --- /dev/null +++ b/src-tauri/crates/core/src/lib.rs @@ -0,0 +1,7 @@ +//! 核心类型和工具模块 +//! +//! 包含 models, config, database, logger 等基础功能 + +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/src-tauri/crates/providers/Cargo.toml b/src-tauri/crates/providers/Cargo.toml new file mode 100644 index 000000000..80390d44f --- /dev/null +++ b/src-tauri/crates/providers/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "providers" +version = "0.1.0" +edition = "2021" + +[dependencies] +core = { path = "../core" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +async-trait = "0.1" +tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.12", features = ["json", "stream"] } diff --git a/src-tauri/crates/providers/src/lib.rs b/src-tauri/crates/providers/src/lib.rs new file mode 100644 index 000000000..c2d83b196 --- /dev/null +++ b/src-tauri/crates/providers/src/lib.rs @@ -0,0 +1,7 @@ +//! Provider 系统模块 +//! +//! 包含 providers, credential, converter 等功能 + +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/src-tauri/crates/server/Cargo.toml b/src-tauri/crates/server/Cargo.toml new file mode 100644 index 000000000..949ee8b40 --- /dev/null +++ b/src-tauri/crates/server/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[dependencies] +core = { path = "../core" } +providers = { path = "../providers" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +tokio = { version = "1", features = ["full"] } +axum = { version = "0.7", features = ["ws"] } +tower = "0.4" +tower-http = { version = "0.5", features = ["limit", "cors"] } diff --git a/src-tauri/crates/server/src/lib.rs b/src-tauri/crates/server/src/lib.rs new file mode 100644 index 000000000..6071ab71e --- /dev/null +++ b/src-tauri/crates/server/src/lib.rs @@ -0,0 +1,7 @@ +//! API 服务器模块 +//! +//! 包含 server, streaming, middleware, router 等功能 + +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/src-tauri/src/app/runner.rs b/src-tauri/src/app/runner.rs index e8df50e07..c499baf34 100644 --- a/src-tauri/src/app/runner.rs +++ b/src-tauri/src/app/runner.rs @@ -810,6 +810,8 @@ pub fn run() { commands::api_key_provider_cmd::get_legacy_api_key_credentials, commands::api_key_provider_cmd::migrate_legacy_api_key_credentials, commands::api_key_provider_cmd::delete_legacy_api_key_credential, + // API Key Provider connection test command + commands::api_key_provider_cmd::test_api_key_provider_connection, // Route commands commands::route_cmd::get_available_routes, commands::route_cmd::get_route_curl_examples, diff --git a/src-tauri/src/commands/api_key_provider_cmd.rs b/src-tauri/src/commands/api_key_provider_cmd.rs index 4ab5f4fb4..9abb8663c 100644 --- a/src-tauri/src/commands/api_key_provider_cmd.rs +++ b/src-tauri/src/commands/api_key_provider_cmd.rs @@ -9,7 +9,9 @@ use crate::database::dao::api_key_provider::{ ApiKeyEntry, ApiKeyProvider, ApiProviderType, ProviderWithKeys, }; use crate::database::DbConnection; -use crate::services::api_key_provider_service::{ApiKeyProviderService, ConnectionTestResult, ImportResult}; +use crate::services::api_key_provider_service::{ + ApiKeyProviderService, ConnectionTestResult, ImportResult, +}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tauri::State; @@ -605,5 +607,8 @@ pub async fn test_api_key_provider_connection( provider_id: String, model_name: Option, ) -> Result { - service.0.test_connection(&db, &provider_id, model_name).await + service + .0 + .test_connection(&db, &provider_id, model_name) + .await } diff --git a/src-tauri/src/services/api_key_provider_service.rs b/src-tauri/src/services/api_key_provider_service.rs index bb9833a40..e400010dd 100644 --- a/src-tauri/src/services/api_key_provider_service.rs +++ b/src-tauri/src/services/api_key_provider_service.rs @@ -11,13 +11,32 @@ use crate::database::dao::api_key_provider::{ }; use crate::database::system_providers::{get_system_providers, to_api_key_provider}; use crate::database::DbConnection; +use crate::models::{CredentialData, CredentialSource, PoolProviderType, ProviderCredential}; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; use chrono::Utc; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::RwLock; +// ============================================================================ +// 连接测试结果 +// ============================================================================ + +/// 连接测试结果 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConnectionTestResult { + /// 是否成功 + pub success: bool, + /// 延迟(毫秒) + pub latency_ms: Option, + /// 错误信息 + pub error: Option, + /// 模型列表(如果使用 models 端点测试) + pub models: Option>, +} + // ============================================================================ // 加密服务 // ============================================================================ @@ -1265,7 +1284,8 @@ impl ApiKeyProviderService { ) -> Result, String> { use crate::providers::openai_custom::OpenAICustomProvider; - let provider = OpenAICustomProvider::with_config(api_key.to_string(), Some(api_host.to_string())); + let provider = + OpenAICustomProvider::with_config(api_key.to_string(), Some(api_host.to_string())); let response = provider .list_models() @@ -1273,7 +1293,7 @@ impl ApiKeyProviderService { .map_err(|e| format!("获取模型列表失败: {}", e))?; // 解析模型列表 - let models = response["data"] + let models: Vec = response["data"] .as_array() .map(|arr| { arr.iter() @@ -1296,23 +1316,28 @@ impl ApiKeyProviderService { api_host: &str, model: &str, ) -> Result, String> { - use crate::models::openai::{ChatCompletionRequest, Message, MessageContent}; + use crate::models::openai::{ChatCompletionRequest, ChatMessage, MessageContent}; use crate::providers::openai_custom::OpenAICustomProvider; - let provider = OpenAICustomProvider::with_config(api_key.to_string(), Some(api_host.to_string())); + let provider = + OpenAICustomProvider::with_config(api_key.to_string(), Some(api_host.to_string())); let request = ChatCompletionRequest { model: model.to_string(), - messages: vec![Message { + messages: vec![ChatMessage { role: "user".to_string(), content: Some(MessageContent::Text("hi".to_string())), - name: None, tool_calls: None, tool_call_id: None, + reasoning_content: None, }], + temperature: None, max_tokens: Some(1), + top_p: None, stream: false, - ..Default::default() + tools: None, + tool_choice: None, + reasoning_effort: None, }; let response = provider @@ -1338,7 +1363,8 @@ impl ApiKeyProviderService { ) -> Result, String> { use crate::providers::claude_custom::ClaudeCustomProvider; - let provider = ClaudeCustomProvider::with_config(api_key.to_string(), Some(api_host.to_string())); + let provider = + ClaudeCustomProvider::with_config(api_key.to_string(), Some(api_host.to_string())); // 发送一个简单的测试请求 let request = serde_json::json!({ @@ -1397,7 +1423,7 @@ impl ApiKeyProviderService { .await .map_err(|e| format!("解析响应失败: {}", e))?; - let models = data["models"] + let models: Vec = data["models"] .as_array() .map(|arr| { arr.iter() diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0cf3b9b9f..e20922e0b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ProxyCast", - "version": "0.47.2", + "version": "0.47.3", "identifier": "com.proxycast.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/components/provider-pool/api-key/ApiKeyProviderSection.tsx b/src/components/provider-pool/api-key/ApiKeyProviderSection.tsx index 9a25a5250..22cb33c42 100644 --- a/src/components/provider-pool/api-key/ApiKeyProviderSection.tsx +++ b/src/components/provider-pool/api-key/ApiKeyProviderSection.tsx @@ -130,9 +130,6 @@ export const ApiKeyProviderSection = forwardRef< const handleTestConnection = useCallback( async (providerId: string): Promise => { try { - // 调用后端测试连接 API - // 注意:这里需要后端实现 test_api_key_provider_connection 命令 - // 暂时使用模拟实现 const provider = selectedProvider; if (!provider || provider.api_keys.length === 0) { return { @@ -141,20 +138,23 @@ export const ApiKeyProviderSection = forwardRef< }; } - // 尝试获取下一个 API Key 来验证连接 - const apiKey = await apiKeyProviderApi.getNextApiKey(providerId); - if (!apiKey) { - return { - success: false, - error: "没有启用的 API Key", - }; - } + // 如果 Provider 配置了自定义模型,使用第一个模型进行测试 + const modelName = + provider.custom_models && provider.custom_models.length > 0 + ? provider.custom_models[0] + : undefined; - // TODO: 实现真正的连接测试 - // 目前返回成功,后续需要调用后端的连接测试 API + // 调用后端连接测试 API + const result = await apiKeyProviderApi.testConnection( + providerId, + modelName, + ); + + // 转换后端返回的 latency_ms 为前端期望的 latencyMs return { - success: true, - latencyMs: Math.floor(Math.random() * 200) + 50, + success: result.success, + latencyMs: result.latency_ms, + error: result.error, }; } catch (e) { return { diff --git a/src/lib/api/apiKeyProvider.ts b/src/lib/api/apiKeyProvider.ts index 077c8772a..959c4ea68 100644 --- a/src/lib/api/apiKeyProvider.ts +++ b/src/lib/api/apiKeyProvider.ts @@ -279,8 +279,52 @@ export const apiKeyProviderApi = { async deleteLegacyCredential(uuid: string): Promise { return safeInvoke("delete_legacy_api_key_credential", { uuid }); }, + + // ============================================================================ + // 连接测试 API + // ============================================================================ + + /** + * 测试 API Key Provider 连接 + * + * 方案 C 实现: + * 1. 默认使用 /v1/models 端点测试 + * 2. 如果提供了 modelName,用该模型发送简单请求 + * + * @param providerId Provider ID + * @param modelName 可选的模型名称,用于发送测试请求 + */ + async testConnection( + providerId: string, + modelName?: string, + ): Promise { + return safeInvoke("test_api_key_provider_connection", { + providerId, + modelName, + }); + }, }; +// ============================================================================ +// 连接测试类型 +// ============================================================================ + +/** + * 连接测试结果 + */ +export interface ConnectionTestResult { + /** 是否成功 */ + success: boolean; + /** 延迟(毫秒) */ + latency_ms?: number; + /** 错误信息 */ + error?: string; + /** 测试方法 */ + method?: string; + /** 模型列表(如果使用 models 端点测试) */ + models?: string[]; +} + /** * 旧的 API Key 凭证信息 */