refactor: 创建 proxycast-services crate,迁移 ~37k 行业务服务

- 新建 proxycast-services crate(依赖 core + providers)
- 迁移 28 个 service 模块 + 2 个子模块(general_chat, content_creator)
- ClientType 从 server/client_detector 迁移到 core/models/client_type
- 主 crate services/mod.rs 改为 re-export 层
- 保留 file_browser_service、sysinfo_service、update_window 在主 crate(依赖 Tauri)
- 所有 crate::xxx 引用已替换为对应 crate 路径
- workspace 全量编译通过,测试编译通过
This commit is contained in:
coso
2026-02-08 15:15:39 +08:00
parent 49f0a49b41
commit 46b2303acc
48 changed files with 568 additions and 585 deletions
+39
View File
@@ -6672,6 +6672,7 @@ dependencies = [
"proxycast-core",
"proxycast-infra",
"proxycast-providers",
"proxycast-services",
"rand 0.8.5",
"regex",
"reqwest 0.12.28",
@@ -6808,6 +6809,44 @@ dependencies = [
"uuid",
]
[[package]]
name = "proxycast-services"
version = "0.60.0"
dependencies = [
"anyhow",
"aster",
"async-trait",
"base64 0.22.1",
"chrono",
"dashmap 5.5.3",
"dirs 5.0.1",
"glob",
"indexmap 2.13.0",
"md5",
"once_cell",
"parking_lot",
"proptest",
"proxycast-core",
"proxycast-providers",
"regex",
"reqwest 0.12.28",
"rusqlite",
"serde",
"serde_json",
"serde_yaml",
"sha2",
"sysinfo",
"tempfile",
"thiserror 1.0.69",
"tokio",
"tracing",
"url",
"urlencoding",
"uuid",
"whoami",
"zip",
]
[[package]]
name = "psl-types"
version = "2.0.11"
+2
View File
@@ -14,6 +14,7 @@ homepage = "https://github.com/aiclientproxy/proxycast"
proxycast-core = { path = "crates/core" }
proxycast-infra = { path = "crates/infra" }
proxycast-providers = { path = "crates/providers" }
proxycast-services = { path = "crates/services" }
voice-core = { path = "crates/voice-core" }
# 序列化
@@ -187,6 +188,7 @@ tauri-build.workspace = true
proxycast-core.workspace = true
proxycast-infra.workspace = true
proxycast-providers.workspace = true
proxycast-services.workspace = true
voice-core.workspace = true
# Tauri
@@ -0,0 +1,164 @@
//! 客户端类型检测模块
//!
//! 通过解析 HTTP 请求的 User-Agent 头来识别客户端类型。
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
/// 客户端类型枚举
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientType {
/// Cursor 编辑器
Cursor,
/// Claude Code 客户端
ClaudeCode,
/// OpenAI Codex CLI
Codex,
/// Windsurf 编辑器
Windsurf,
/// Kiro IDE
Kiro,
/// 未识别的客户端
Other,
}
impl ClientType {
/// 从 User-Agent 字符串检测客户端类型
pub fn from_user_agent(user_agent: &str) -> Self {
let ua_lower = user_agent.to_lowercase();
if ua_lower.contains("cursor") {
ClientType::Cursor
} else if ua_lower.contains("claude-code") || ua_lower.contains("claude_code") {
ClientType::ClaudeCode
} else if ua_lower.contains("codex") {
ClientType::Codex
} else if ua_lower.contains("windsurf") {
ClientType::Windsurf
} else if ua_lower.contains("kiro") {
ClientType::Kiro
} else {
ClientType::Other
}
}
/// 获取配置键名
pub fn config_key(&self) -> &'static str {
match self {
ClientType::Cursor => "cursor",
ClientType::ClaudeCode => "claude_code",
ClientType::Codex => "codex",
ClientType::Windsurf => "windsurf",
ClientType::Kiro => "kiro",
ClientType::Other => "other",
}
}
/// 获取所有客户端类型
pub fn all() -> &'static [ClientType] {
&[
ClientType::Cursor,
ClientType::ClaudeCode,
ClientType::Codex,
ClientType::Windsurf,
ClientType::Kiro,
ClientType::Other,
]
}
/// 从配置键名解析客户端类型
pub fn from_config_key(key: &str) -> Option<Self> {
match key {
"cursor" => Some(ClientType::Cursor),
"claude_code" => Some(ClientType::ClaudeCode),
"codex" => Some(ClientType::Codex),
"windsurf" => Some(ClientType::Windsurf),
"kiro" => Some(ClientType::Kiro),
"other" => Some(ClientType::Other),
_ => None,
}
}
}
impl std::fmt::Display for ClientType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.config_key())
}
}
/// 根据客户端类型和端点配置选择 Provider
pub fn select_provider(
_client_type: ClientType,
endpoint_provider: Option<&String>,
default_provider: &str,
) -> String {
match endpoint_provider {
Some(provider) => provider.clone(),
None => default_provider.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_user_agent_cursor() {
assert_eq!(
ClientType::from_user_agent("Cursor/1.0"),
ClientType::Cursor
);
assert_eq!(ClientType::from_user_agent("cursor"), ClientType::Cursor);
assert_eq!(ClientType::from_user_agent("CURSOR"), ClientType::Cursor);
}
#[test]
fn test_from_user_agent_claude_code() {
assert_eq!(
ClientType::from_user_agent("Claude-Code/2.0"),
ClientType::ClaudeCode
);
assert_eq!(
ClientType::from_user_agent("claude-code"),
ClientType::ClaudeCode
);
assert_eq!(
ClientType::from_user_agent("claude_code"),
ClientType::ClaudeCode
);
}
#[test]
fn test_from_user_agent_other() {
assert_eq!(ClientType::from_user_agent("Unknown"), ClientType::Other);
assert_eq!(ClientType::from_user_agent(""), ClientType::Other);
}
#[test]
fn test_config_key() {
assert_eq!(ClientType::Cursor.config_key(), "cursor");
assert_eq!(ClientType::ClaudeCode.config_key(), "claude_code");
}
#[test]
fn test_from_config_key() {
assert_eq!(
ClientType::from_config_key("cursor"),
Some(ClientType::Cursor)
);
assert_eq!(ClientType::from_config_key("invalid"), None);
}
#[test]
fn test_all_client_types() {
assert_eq!(ClientType::all().len(), 6);
}
#[test]
fn test_serialization() {
let json = serde_json::to_string(&ClientType::Cursor).unwrap();
assert_eq!(json, "\"cursor\"");
}
}
+2
View File
@@ -4,6 +4,7 @@
pub mod anthropic;
pub mod app_type;
pub mod client_type;
pub mod codewhisperer;
pub mod injection_types;
pub mod kiro_fingerprint;
@@ -23,6 +24,7 @@ pub mod vertex_model;
#[allow(unused_imports)]
pub use anthropic::*;
pub use app_type::AppType;
pub use client_type::{select_provider, ClientType};
#[allow(unused_imports)]
pub use codewhisperer::*;
pub use injection_types::{InjectionMode, InjectionRule};
+61
View File
@@ -0,0 +1,61 @@
[package]
name = "proxycast-services"
version.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
# 项目内 crate
proxycast-core.workspace = true
proxycast-providers.workspace = true
# 序列化
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
# 异步运行时
tokio.workspace = true
async-trait.workspace = true
# 错误处理
thiserror.workspace = true
anyhow.workspace = true
# 日志
tracing.workspace = true
# HTTP 客户端
reqwest.workspace = true
# 时间和 UUID
chrono.workspace = true
uuid.workspace = true
# 工具库
dirs.workspace = true
parking_lot.workspace = true
dashmap.workspace = true
indexmap.workspace = true
glob.workspace = true
sysinfo.workspace = true
whoami.workspace = true
regex.workspace = true
sha2.workspace = true
base64.workspace = true
md5.workspace = true
once_cell.workspace = true
url.workspace = true
urlencoding.workspace = true
zip.workspace = true
# Aster Agent Framework
aster.workspace = true
# 数据库
rusqlite.workspace = true
[dev-dependencies]
tempfile.workspace = true
proptest.workspace = true
@@ -5,15 +5,17 @@
//! **Feature: provider-ui-refactor**
//! **Validates: Requirements 7.3, 9.1, 9.2, 9.3**
use crate::database::dao::api_key_provider::{
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use chrono::Utc;
use proxycast_core::database::dao::api_key_provider::{
ApiKeyEntry, ApiKeyProvider, ApiKeyProviderDao, ApiProviderType, ProviderGroup,
ProviderWithKeys,
};
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 proxycast_core::database::system_providers::{get_system_providers, to_api_key_provider};
use proxycast_core::database::DbConnection;
use proxycast_core::models::{
CredentialData, CredentialSource, PoolProviderType, ProviderCredential,
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
@@ -247,8 +249,8 @@ impl ApiKeyProviderService {
model: &str,
prompt: &str,
) -> Result<(String, String), String> {
use crate::models::openai::{ChatCompletionRequest, ChatMessage, MessageContent};
use crate::providers::openai_custom::OpenAICustomProvider;
use proxycast_core::models::openai::{ChatCompletionRequest, ChatMessage, MessageContent};
use proxycast_providers::providers::openai_custom::OpenAICustomProvider;
let provider =
OpenAICustomProvider::with_config(api_key.to_string(), Some(api_host.to_string()));
@@ -457,7 +459,7 @@ impl ApiKeyProviderService {
/// 检查数据库中是否存在系统 Provider,如果不存在则插入
/// **Validates: Requirements 9.3**
pub fn initialize_system_providers(&self, db: &DbConnection) -> Result<usize, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let system_providers = get_system_providers();
let mut inserted_count = 0;
@@ -487,7 +489,7 @@ impl ApiKeyProviderService {
// 首先确保系统 Provider 已初始化
self.initialize_system_providers(db)?;
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let providers =
ApiKeyProviderDao::get_all_providers_with_keys(&conn).map_err(|e| e.to_string())?;
@@ -514,7 +516,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
id: &str,
) -> Result<Option<ProviderWithKeys>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let provider =
ApiKeyProviderDao::get_provider_by_id(&conn, id).map_err(|e| e.to_string())?;
@@ -564,7 +566,7 @@ impl ApiKeyProviderService {
updated_at: now,
};
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ApiKeyProviderDao::insert_provider(&conn, &provider).map_err(|e| e.to_string())?;
Ok(provider)
@@ -586,7 +588,7 @@ impl ApiKeyProviderService {
region: Option<String>,
custom_models: Option<Vec<String>>,
) -> Result<ApiKeyProvider, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let mut provider = ApiKeyProviderDao::get_provider_by_id(&conn, id)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Provider not found: {id}"))?;
@@ -636,7 +638,7 @@ impl ApiKeyProviderService {
/// 删除自定义 Provider
/// 系统 Provider 不允许删除
pub fn delete_custom_provider(&self, db: &DbConnection, id: &str) -> Result<bool, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 检查是否为系统 Provider
let provider = ApiKeyProviderDao::get_provider_by_id(&conn, id)
@@ -668,7 +670,7 @@ impl ApiKeyProviderService {
provider_id
);
let mut conn = crate::database::lock_db(db)?;
let mut conn = proxycast_core::database::lock_db(db)?;
// 使用事务确保操作的原子性
let tx = conn
@@ -754,7 +756,7 @@ impl ApiKeyProviderService {
/// 删除 API Key
pub fn delete_api_key(&self, db: &DbConnection, key_id: &str) -> Result<bool, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ApiKeyProviderDao::delete_api_key(&conn, key_id).map_err(|e| e.to_string())
}
@@ -765,7 +767,7 @@ impl ApiKeyProviderService {
key_id: &str,
enabled: bool,
) -> Result<ApiKeyEntry, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let mut key = ApiKeyProviderDao::get_api_key_by_id(&conn, key_id)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("API Key not found: {key_id}"))?;
@@ -783,7 +785,7 @@ impl ApiKeyProviderService {
key_id: &str,
alias: Option<String>,
) -> Result<ApiKeyEntry, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let mut key = ApiKeyProviderDao::get_api_key_by_id(&conn, key_id)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("API Key not found: {key_id}"))?;
@@ -803,7 +805,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
provider_id: &str,
) -> Result<Option<String>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 获取所有启用的 API Keys
let keys = ApiKeyProviderDao::get_enabled_api_keys_by_provider(&conn, provider_id)
@@ -836,7 +838,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
provider_id: &str,
) -> Result<Option<(String, String)>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 获取所有启用的 API Keys
let keys = ApiKeyProviderDao::get_enabled_api_keys_by_provider(&conn, provider_id)
@@ -865,7 +867,7 @@ impl ApiKeyProviderService {
/// 记录 API Key 使用
pub fn record_usage(&self, db: &DbConnection, key_id: &str) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let key = ApiKeyProviderDao::get_api_key_by_id(&conn, key_id)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("API Key not found: {key_id}"))?;
@@ -881,7 +883,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
provider_id: &str,
) -> Result<Option<(String, ApiKeyProvider)>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 获取 Provider 信息
let provider = match ApiKeyProviderDao::get_provider_by_id(&conn, provider_id)
@@ -928,7 +930,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
provider_type: ApiProviderType,
) -> Result<Option<(String, String, ApiKeyProvider)>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 获取所有启用的 API Keys(按类型)
let keys = ApiKeyProviderDao::get_enabled_api_keys_by_type(&conn, provider_type)
@@ -958,7 +960,7 @@ impl ApiKeyProviderService {
/// 记录 API Key 错误
pub fn record_error(&self, db: &DbConnection, key_id: &str) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ApiKeyProviderDao::increment_api_key_error(&conn, key_id).map_err(|e| e.to_string())
}
@@ -983,13 +985,13 @@ impl ApiKeyProviderService {
/// 获取 UI 状态
pub fn get_ui_state(&self, db: &DbConnection, key: &str) -> Result<Option<String>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ApiKeyProviderDao::get_ui_state(&conn, key).map_err(|e| e.to_string())
}
/// 设置 UI 状态
pub fn set_ui_state(&self, db: &DbConnection, key: &str, value: &str) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ApiKeyProviderDao::set_ui_state(&conn, key, value).map_err(|e| e.to_string())
}
@@ -1000,7 +1002,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
sort_orders: Vec<(String, i32)>,
) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ApiKeyProviderDao::update_provider_sort_orders(&conn, &sort_orders)
.map_err(|e| e.to_string())
}
@@ -1013,7 +1015,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
include_keys: bool,
) -> Result<serde_json::Value, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let providers =
ApiKeyProviderDao::get_all_providers_with_keys(&conn).map_err(|e| e.to_string())?;
@@ -1073,7 +1075,7 @@ impl ApiKeyProviderService {
.as_array()
.ok_or_else(|| "配置格式错误: 缺少 providers 数组".to_string())?;
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let mut imported_providers = 0;
let mut skipped_providers = 0;
let mut errors = Vec::new();
@@ -1145,7 +1147,7 @@ impl ApiKeyProviderService {
db: &DbConnection,
pool_type: &PoolProviderType,
provider_id_hint: Option<&str>,
client_type: Option<&crate::server::client_detector::ClientType>,
client_type: Option<&proxycast_core::models::client_type::ClientType>,
) -> Result<Option<ProviderCredential>, String> {
eprintln!(
"[get_fallback_credential] 开始查找: pool_type={pool_type:?}, provider_id_hint={provider_id_hint:?}"
@@ -1222,7 +1224,7 @@ impl ApiKeyProviderService {
pool_type: &PoolProviderType,
api_type: &ApiProviderType,
) -> Result<Option<ProviderCredential>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 查找该类型的启用的 Provider(按 sort_order 排序)
let providers = ApiKeyProviderDao::get_all_providers(&conn).map_err(|e| e.to_string())?;
@@ -1288,11 +1290,11 @@ impl ApiKeyProviderService {
&self,
db: &DbConnection,
provider_id: &str,
client_type: Option<&crate::server::client_detector::ClientType>,
client_type: Option<&proxycast_core::models::client_type::ClientType>,
) -> Result<Option<ProviderCredential>, String> {
// First, get all data we need while holding the lock
let (provider, keys) = {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 直接按 provider_id 查找
let provider = ApiKeyProviderDao::get_provider_by_id(&conn, provider_id)
@@ -1359,7 +1361,7 @@ impl ApiKeyProviderService {
// 对于 Claude Code 客户端,可以使用任何 Claude 凭证
if matches!(
client,
crate::server::client_detector::ClientType::ClaudeCode
proxycast_core::models::client_type::ClientType::ClaudeCode
) {
selected_key = Some(candidate_key);
break;
@@ -1680,7 +1682,7 @@ impl ApiKeyProviderService {
api_key: &str,
api_host: &str,
) -> Result<Vec<String>, String> {
use crate::providers::openai_custom::OpenAICustomProvider;
use proxycast_providers::providers::openai_custom::OpenAICustomProvider;
let provider =
OpenAICustomProvider::with_config(api_key.to_string(), Some(api_host.to_string()));
@@ -1725,7 +1727,7 @@ impl ApiKeyProviderService {
api_key: &str,
api_host: &str,
) -> Result<(), String> {
use crate::providers::claude_custom::ClaudeCustomProvider;
use proxycast_providers::providers::claude_custom::ClaudeCustomProvider;
let provider =
ClaudeCustomProvider::with_config(api_key.to_string(), Some(api_host.to_string()));
@@ -1764,7 +1766,7 @@ impl ApiKeyProviderService {
api_host: &str,
model: &str,
) -> Result<Vec<String>, String> {
use crate::providers::claude_custom::ClaudeCustomProvider;
use proxycast_providers::providers::claude_custom::ClaudeCustomProvider;
let provider =
ClaudeCustomProvider::with_config(api_key.to_string(), Some(api_host.to_string()));
@@ -5,7 +5,6 @@
//!
//! 这是应用层接管框架层存储的关键桥接模块。
use crate::database::DbConnection;
use anyhow::{anyhow, Result};
use aster::conversation::message::{Message, MessageContent};
use aster::conversation::Conversation;
@@ -17,6 +16,7 @@ use aster::session::{
};
use async_trait::async_trait;
use chrono::Utc;
use proxycast_core::database::DbConnection;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
@@ -4,8 +4,8 @@
#![allow(dead_code)]
use crate::database::{get_db_path, DbConnection};
use chrono::{DateTime, Duration, Utc};
use proxycast_core::database::{get_db_path, DbConnection};
use rusqlite::DatabaseName;
use std::path::{Path, PathBuf};
@@ -186,7 +186,7 @@ impl SessionService {
#[cfg(test)]
mod tests {
use super::*;
use crate::services::general_chat::MessageRole;
use crate::general_chat::MessageRole;
#[test]
fn test_create_session_with_name() {
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio::sync::{broadcast, RwLock};
use crate::websocket::{KiroTokenInfo, WsKiroEvent};
use proxycast_core::websocket::{KiroTokenInfo, WsKiroEvent};
/// Kiro 事件服务
#[derive(Debug)]
+76
View File
@@ -0,0 +1,76 @@
//! ProxyCast Services Crate
//!
//! 业务服务层,包含所有不依赖 Tauri 的业务逻辑。
//!
//! ## 模块结构
//! - `context_memory_service` - 上下文记忆服务
//! - `file_browser_service` - 文件浏览服务
//! - `sysinfo_service` - 系统信息服务
//! - `update_check_service` - 更新检查服务
//! - `update_window` - 更新窗口
//! - `usage_service` - 使用统计服务
//! - `machine_id_service` - 机器 ID 服务
//! - `live_sync` - 实时同步
//! - `mcp_sync` - MCP 同步
//! - `prompt_sync` - Prompt 同步
//! - `skill_service` - 技能服务
//! - `backup_service` - 备份服务
//! - `material_service` - 素材服务
//! - `persona_service` - 人设服务
//! - `template_service` - 模板服务
//! - `model_registry_service` - 模型注册服务
//! - `model_service` - 模型服务
//! - `prompt_service` - Prompt 服务
//! - `mcp_service` - MCP 服务
//! - `switch` - Provider 切换
//! - `aster_session_store` - Aster 会话存储
//! - `general_chat` - 通用聊天
//! - `content_creator` - 内容创作
//! - `session_context_service` - 会话上下文服务
//! - `project_context_builder` - 项目上下文构建器
//! - `tool_hooks_service` - 工具钩子服务
//! - `kiro_event_service` - Kiro 事件服务
//! - `api_key_provider_service` - API Key Provider 服务
//! - `provider_pool_service` - Provider 池服务
//! - `token_cache_service` - Token 缓存服务
// 无外部依赖的服务
pub mod context_memory_service;
pub mod update_check_service;
pub mod usage_service;
// 依赖 models 的服务
pub mod live_sync;
pub mod machine_id_service;
pub mod mcp_sync;
pub mod prompt_sync;
pub mod skill_service;
// 依赖 database + models 的服务
pub mod aster_session_store;
pub mod backup_service;
pub mod material_service;
pub mod mcp_service;
pub mod model_registry_service;
pub mod model_service;
pub mod persona_service;
pub mod prompt_service;
pub mod switch;
pub mod template_service;
// 子模块
pub mod content_creator;
pub mod general_chat;
// 依赖其他 services 的服务
pub mod project_context_builder;
pub mod session_context_service;
pub mod tool_hooks_service;
// 事件服务
pub mod kiro_event_service;
// 依赖 providers 的服务
pub mod api_key_provider_service;
pub mod provider_pool_service;
pub mod token_cache_service;
@@ -1,4 +1,4 @@
use crate::models::{AppType, Provider};
use proxycast_core::models::{AppType, Provider};
use serde_json::{json, Value};
use std::fs;
use std::io::Write;
@@ -1,7 +1,7 @@
#![allow(dead_code)]
use crate::models::machine_id::*;
use dirs;
use proxycast_core::models::machine_id::*;
use serde_json;
use std::fs;
use std::path::PathBuf;
@@ -20,9 +20,9 @@ use rusqlite::Connection;
use tracing::{debug, error, info, warn};
use uuid::Uuid;
use crate::database::dao::material_dao::MaterialDao;
use crate::errors::project_error::MaterialError;
use crate::models::project_model::{
use proxycast_core::database::dao::material_dao::MaterialDao;
use proxycast_core::errors::project_error::MaterialError;
use proxycast_core::models::project_model::{
Material, MaterialFilter, MaterialUpdate, UploadMaterialRequest,
};
@@ -619,7 +619,7 @@ impl MaterialService {
#[cfg(test)]
mod tests {
use super::*;
use crate::database::schema::create_tables;
use proxycast_core::database::schema::create_tables;
use std::io::Write;
use tempfile::TempDir;
@@ -1,8 +1,8 @@
use crate::database::dao::mcp::McpDao;
use crate::database::DbConnection;
use crate::models::mcp_model::ConfigValidationError;
use crate::models::{AppType, McpServer};
use crate::services::mcp_sync;
use crate::mcp_sync;
use proxycast_core::database::dao::mcp::McpDao;
use proxycast_core::database::DbConnection;
use proxycast_core::models::mcp_model::ConfigValidationError;
use proxycast_core::models::{AppType, McpServer};
pub struct McpService;
@@ -1,4 +1,4 @@
use crate::models::{AppType, McpServer};
use proxycast_core::models::{AppType, McpServer};
use serde_json::{json, Map, Value};
use std::path::PathBuf;
@@ -361,7 +361,7 @@ pub fn remove_mcp_from_all_apps(
/// Import MCP servers from Claude's ~/.claude/settings.json
pub fn import_mcp_from_claude(
) -> Result<Vec<crate::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
) -> Result<Vec<proxycast_core::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
let home = dirs::home_dir().ok_or("Cannot find home directory")?;
let config_path = home.join(".claude").join("settings.json");
@@ -376,7 +376,7 @@ pub fn import_mcp_from_claude(
if let Some(mcp_servers) = settings.get("mcpServers").and_then(|v| v.as_object()) {
for (id, config) in mcp_servers {
let server = crate::models::McpServer {
let server = proxycast_core::models::McpServer {
id: id.clone(),
name: id.clone(),
server_config: config.clone(),
@@ -396,7 +396,7 @@ pub fn import_mcp_from_claude(
/// Import MCP servers from Codex's config.toml
pub fn import_mcp_from_codex(
) -> Result<Vec<crate::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
) -> Result<Vec<proxycast_core::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
let home = dirs::home_dir().ok_or("Cannot find home directory")?;
let config_path = home.join(".codex").join("config.toml");
@@ -421,7 +421,7 @@ pub fn import_mcp_from_codex(
if !current_env.is_empty() {
current_config.insert("env".to_string(), Value::Object(current_env.clone()));
}
let server = crate::models::McpServer {
let server = proxycast_core::models::McpServer {
id: id.clone(),
name: id.clone(),
server_config: Value::Object(current_config.clone()),
@@ -455,7 +455,7 @@ pub fn import_mcp_from_codex(
if !current_env.is_empty() {
current_config.insert("env".to_string(), Value::Object(current_env.clone()));
}
let server = crate::models::McpServer {
let server = proxycast_core::models::McpServer {
id: id.clone(),
name: id.clone(),
server_config: Value::Object(current_config.clone()),
@@ -503,7 +503,7 @@ pub fn import_mcp_from_codex(
if !current_env.is_empty() {
current_config.insert("env".to_string(), Value::Object(current_env));
}
let server = crate::models::McpServer {
let server = proxycast_core::models::McpServer {
id: id.clone(),
name: id.clone(),
server_config: Value::Object(current_config),
@@ -522,7 +522,7 @@ pub fn import_mcp_from_codex(
/// Import MCP servers from Gemini's settings.json
pub fn import_mcp_from_gemini(
) -> Result<Vec<crate::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
) -> Result<Vec<proxycast_core::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
let home = dirs::home_dir().ok_or("Cannot find home directory")?;
let settings_path = home.join(".gemini").join("settings.json");
@@ -537,7 +537,7 @@ pub fn import_mcp_from_gemini(
if let Some(mcp_servers) = settings.get("mcpServers").and_then(|v| v.as_object()) {
for (id, config) in mcp_servers {
let server = crate::models::McpServer {
let server = proxycast_core::models::McpServer {
id: id.clone(),
name: id.clone(),
server_config: config.clone(),
@@ -558,7 +558,7 @@ pub fn import_mcp_from_gemini(
/// Import MCP servers from a specific app
pub fn import_mcp_from_app(
app_type: &AppType,
) -> Result<Vec<crate::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
) -> Result<Vec<proxycast_core::models::McpServer>, Box<dyn std::error::Error + Send + Sync>> {
match app_type {
AppType::Claude => import_mcp_from_claude(),
AppType::Codex => import_mcp_from_codex(),
@@ -3,8 +3,8 @@
//! 从内嵌资源加载模型数据,管理本地缓存,提供模型搜索等功能
//! 模型数据在构建时从 aiclientproxy/models 仓库打包进应用
use crate::database::DbConnection;
use crate::models::model_registry::{
use proxycast_core::database::DbConnection;
use proxycast_core::models::model_registry::{
EnhancedModelMetadata, ModelCapabilities, ModelLimits, ModelPricing, ModelSource, ModelStatus,
ModelSyncState, ModelTier, ProviderAliasConfig, UserModelPreference,
};
@@ -2,9 +2,11 @@
//!
//! 提供统一的模型获取、缓存和查询接口,支持从不同 Provider 获取模型列表。
use crate::database::dao::provider_pool::ProviderPoolDao;
use crate::database::DbConnection;
use crate::models::provider_pool_model::{CredentialData, PoolProviderType, ProviderCredential};
use proxycast_core::database::dao::provider_pool::ProviderPoolDao;
use proxycast_core::database::DbConnection;
use proxycast_core::models::provider_pool_model::{
CredentialData, PoolProviderType, ProviderCredential,
};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -16,10 +16,10 @@
use rusqlite::Connection;
use crate::database::dao::brand_persona_dao::BrandPersonaDao;
use crate::database::dao::persona_dao::PersonaDao;
use crate::errors::project_error::PersonaError;
use crate::models::project_model::{
use proxycast_core::database::dao::brand_persona_dao::BrandPersonaDao;
use proxycast_core::database::dao::persona_dao::PersonaDao;
use proxycast_core::errors::project_error::PersonaError;
use proxycast_core::models::project_model::{
BrandPersona, BrandPersonaExtension, BrandPersonaTemplate, CreateBrandExtensionRequest,
CreatePersonaRequest, Persona, PersonaTemplate, PersonaUpdate, UpdateBrandExtensionRequest,
};
@@ -401,7 +401,7 @@ impl PersonaService {
#[cfg(test)]
mod tests {
use super::*;
use crate::database::schema::create_tables;
use proxycast_core::database::schema::create_tables;
/// 创建测试数据库连接
fn setup_test_db() -> Connection {
@@ -693,7 +693,7 @@ mod tests {
#[test]
fn test_get_brand_persona() {
use crate::models::project_model::{BrandTone, DesignConfig};
use proxycast_core::models::project_model::{BrandTone, DesignConfig};
let conn = setup_test_db();
create_test_project(&conn, "project-1");
@@ -742,7 +742,7 @@ mod tests {
#[test]
fn test_save_brand_extension_creates_new() {
use crate::models::project_model::BrandTone;
use proxycast_core::models::project_model::BrandTone;
let conn = setup_test_db();
create_test_project(&conn, "project-1");
@@ -782,7 +782,7 @@ mod tests {
#[test]
fn test_save_brand_extension_updates_existing() {
use crate::models::project_model::BrandTone;
use proxycast_core::models::project_model::BrandTone;
let conn = setup_test_db();
create_test_project(&conn, "project-1");
@@ -19,12 +19,12 @@ use chrono::Utc;
use rusqlite::Connection;
use tracing::{debug, warn};
use crate::errors::project_error::ProjectError;
use crate::models::project_model::{Material, Persona, ProjectContext, Template};
use crate::services::material_service::MaterialService;
use crate::services::persona_service::PersonaService;
use crate::services::template_service::TemplateService;
use crate::workspace::{Workspace, WorkspaceSettings, WorkspaceType};
use crate::material_service::MaterialService;
use crate::persona_service::PersonaService;
use crate::template_service::TemplateService;
use proxycast_core::errors::project_error::ProjectError;
use proxycast_core::models::project_model::{Material, Persona, ProjectContext, Template};
use proxycast_core::workspace::{Workspace, WorkspaceSettings, WorkspaceType};
// ============================================================================
// 项目上下文构建器
@@ -453,8 +453,8 @@ impl ProjectContextBuilder {
#[cfg(test)]
mod tests {
use super::*;
use crate::database::schema::create_tables;
use crate::models::project_model::CreatePersonaRequest;
use proxycast_core::database::schema::create_tables;
use proxycast_core::models::project_model::CreatePersonaRequest;
/// 创建测试数据库连接
fn setup_test_db() -> Connection {
@@ -584,7 +584,7 @@ mod tests {
create_test_project(&conn, "project-1", "测试项目");
// 创建素材
use crate::models::project_model::UploadMaterialRequest;
use proxycast_core::models::project_model::UploadMaterialRequest;
let req = UploadMaterialRequest {
project_id: "project-1".to_string(),
name: "参考文档".to_string(),
@@ -614,7 +614,7 @@ mod tests {
create_test_project(&conn, "project-1", "测试项目");
// 创建模板
use crate::models::project_model::CreateTemplateRequest;
use proxycast_core::models::project_model::CreateTemplateRequest;
let req = CreateTemplateRequest {
project_id: "project-1".to_string(),
name: "小红书模板".to_string(),
@@ -665,7 +665,7 @@ mod tests {
PersonaService::set_default_persona(&conn, "project-1", &persona.id).unwrap();
// 创建素材
use crate::models::project_model::UploadMaterialRequest;
use proxycast_core::models::project_model::UploadMaterialRequest;
let material_req = UploadMaterialRequest {
project_id: "project-1".to_string(),
name: "素材1".to_string(),
@@ -678,7 +678,7 @@ mod tests {
MaterialService::upload_material(&conn, material_req).unwrap();
// 创建模板
use crate::models::project_model::CreateTemplateRequest;
use proxycast_core::models::project_model::CreateTemplateRequest;
let template_req = CreateTemplateRequest {
project_id: "project-1".to_string(),
name: "测试模板".to_string(),
@@ -1,7 +1,7 @@
use crate::database::dao::prompts::PromptDao;
use crate::database::DbConnection;
use crate::models::{AppType, Prompt};
use crate::services::prompt_sync;
use crate::prompt_sync;
use proxycast_core::database::dao::prompts::PromptDao;
use proxycast_core::database::DbConnection;
use proxycast_core::models::{AppType, Prompt};
use std::collections::HashMap;
pub struct PromptService;
@@ -1,4 +1,4 @@
use crate::models::AppType;
use proxycast_core::models::AppType;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
@@ -4,19 +4,19 @@
#![allow(dead_code)]
use crate::database::dao::provider_pool::ProviderPoolDao;
use crate::database::DbConnection;
use crate::models::provider_pool_model::{
use crate::api_key_provider_service::ApiKeyProviderService;
use chrono::Utc;
use proxycast_core::database::dao::provider_pool::ProviderPoolDao;
use proxycast_core::database::DbConnection;
use proxycast_core::models::client_type::ClientType;
use proxycast_core::models::provider_pool_model::{
get_default_check_model, get_oauth_creds_path, CredentialData, CredentialDisplay,
HealthCheckResult, OAuthStatus, PoolProviderType, PoolStats, ProviderCredential,
ProviderPoolOverview,
};
use crate::models::route_model::RouteInfo;
use crate::providers::antigravity::TokenRefreshError;
use crate::providers::kiro::KiroProvider;
use crate::server::client_detector::ClientType;
use crate::services::api_key_provider_service::ApiKeyProviderService;
use chrono::Utc;
use proxycast_core::models::route_model::RouteInfo;
use proxycast_providers::providers::antigravity::TokenRefreshError;
use proxycast_providers::providers::kiro::KiroProvider;
use reqwest::Client;
use serde::{Deserialize, Serialize};
@@ -107,7 +107,7 @@ impl ProviderPoolService {
/// 获取所有凭证概览
pub fn get_overview(&self, db: &DbConnection) -> Result<Vec<ProviderPoolOverview>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let grouped = ProviderPoolDao::get_grouped(&conn).map_err(|e| e.to_string())?;
let mut overview = Vec::new();
@@ -141,7 +141,7 @@ impl ProviderPoolService {
provider_type: &str,
) -> Result<Vec<CredentialDisplay>, String> {
let pt: PoolProviderType = provider_type.parse().map_err(|e: String| e)?;
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let mut credentials =
ProviderPoolDao::get_by_type(&conn, &pt).map_err(|e| e.to_string())?;
@@ -172,7 +172,7 @@ impl ProviderPoolService {
cred.check_health = check_health.unwrap_or(true);
cred.check_model_name = check_model_name;
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::insert(&conn, &cred).map_err(|e| e.to_string())?;
Ok(cred)
@@ -190,7 +190,7 @@ impl ProviderPoolService {
not_supported_models: Option<Vec<String>>,
proxy_url: Option<String>,
) -> Result<ProviderCredential, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let mut cred = ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?;
@@ -224,7 +224,7 @@ impl ProviderPoolService {
/// 删除凭证
pub fn delete_credential(&self, db: &DbConnection, uuid: &str) -> Result<bool, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::delete(&conn, uuid).map_err(|e| e.to_string())
}
@@ -252,7 +252,7 @@ impl ProviderPoolService {
db: &DbConnection,
provider_type: &str,
model: Option<&str>,
client_type: Option<&crate::server::client_detector::ClientType>,
client_type: Option<&proxycast_core::models::client_type::ClientType>,
) -> Result<Option<ProviderCredential>, String> {
// 对于未知的 provider_type,直接返回 None(不是错误)
// 这样可以让 select_credential_with_fallback 继续尝试智能降级
@@ -265,7 +265,7 @@ impl ProviderPoolService {
return Ok(None);
}
};
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
// 获取凭证,对于 AI Provider 类型,也查找 Assistant 类型的凭证
let mut credentials =
@@ -408,7 +408,7 @@ impl ProviderPoolService {
provider_type: &str,
model: Option<&str>,
provider_id_hint: Option<&str>,
client_type: Option<&crate::server::client_detector::ClientType>,
client_type: Option<&proxycast_core::models::client_type::ClientType>,
) -> Result<Option<ProviderCredential>, String> {
eprintln!(
"[select_credential_with_fallback] 开始: provider_type={provider_type}, model={model:?}, provider_id_hint={provider_id_hint:?}"
@@ -554,7 +554,7 @@ impl ProviderPoolService {
/// 记录凭证使用
pub fn record_usage(&self, db: &DbConnection, uuid: &str) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let cred = ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?;
@@ -570,7 +570,7 @@ impl ProviderPoolService {
uuid: &str,
check_model: Option<&str>,
) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::update_health_status(
&conn,
uuid,
@@ -591,7 +591,7 @@ impl ProviderPoolService {
uuid: &str,
error_message: Option<&str>,
) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let cred = ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?;
@@ -614,7 +614,7 @@ impl ProviderPoolService {
/// 重置凭证计数器
pub fn reset_counters(&self, db: &DbConnection, uuid: &str) -> Result<(), String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::reset_counters(&conn, uuid).map_err(|e| e.to_string())
}
@@ -625,7 +625,7 @@ impl ProviderPoolService {
provider_type: &str,
) -> Result<usize, String> {
let pt: PoolProviderType = provider_type.parse().map_err(|e: String| e)?;
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::reset_health_by_type(&conn, &pt).map_err(|e| e.to_string())
}
@@ -636,7 +636,7 @@ impl ProviderPoolService {
db: &DbConnection,
uuid: &str,
) -> Result<Option<CredentialHealthInfo>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let cred = ProviderPoolDao::get_by_uuid(&conn, uuid).map_err(|e| e.to_string())?;
Ok(cred.map(|c| CredentialHealthInfo {
@@ -661,7 +661,7 @@ impl ProviderPoolService {
&self,
db: &DbConnection,
) -> Result<Vec<CredentialHealthInfo>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let credentials = ProviderPoolDao::get_all(&conn).map_err(|e| e.to_string())?;
Ok(credentials
@@ -694,7 +694,7 @@ impl ProviderPoolService {
let error_message = error.user_message();
let requires_reauth = error.requires_reauth();
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let cred = ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?;
@@ -737,7 +737,8 @@ impl ProviderPoolService {
let pt: PoolProviderType = provider_type
.parse()
.map_err(|_| SelectionError::NoCredentials)?;
let conn = crate::database::lock_db(db).map_err(|_| SelectionError::NoCredentials)?;
let conn =
proxycast_core::database::lock_db(db).map_err(|_| SelectionError::NoCredentials)?;
let credentials =
ProviderPoolDao::get_by_type(&conn, &pt).map_err(|_| SelectionError::NoCredentials)?;
drop(conn);
@@ -847,7 +848,7 @@ impl ProviderPoolService {
uuid: &str,
) -> Result<HealthCheckResult, String> {
let cred = {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?
@@ -887,7 +888,7 @@ impl ProviderPoolService {
// 重新获取凭证(token 已更新)
let updated_cred = {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?
@@ -961,7 +962,7 @@ impl ProviderPoolService {
) -> Result<Vec<HealthCheckResult>, String> {
let pt: PoolProviderType = provider_type.parse().map_err(|e: String| e)?;
let credentials = {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_type(&conn, &pt).map_err(|e| e.to_string())?
};
@@ -1419,7 +1420,7 @@ impl ProviderPoolService {
override_base_url: Option<&str>,
model: &str,
) -> Result<(), String> {
use crate::providers::codex::CodexProvider;
use proxycast_providers::providers::codex::CodexProvider;
let mut provider = CodexProvider::new();
provider
@@ -1519,7 +1520,7 @@ impl ProviderPoolService {
// Claude OAuth 健康检查
async fn check_claude_oauth_health(&self, creds_path: &str, model: &str) -> Result<(), String> {
use crate::providers::claude_oauth::ClaudeOAuthProvider;
use proxycast_providers::providers::claude_oauth::ClaudeOAuthProvider;
let mut provider = ClaudeOAuthProvider::new();
provider
@@ -1564,7 +1565,7 @@ impl ProviderPoolService {
db: &DbConnection,
name: &str,
) -> Result<Option<ProviderCredential>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_name(&conn, name).map_err(|e| e.to_string())
}
@@ -1574,7 +1575,7 @@ impl ProviderPoolService {
db: &DbConnection,
uuid: &str,
) -> Result<Option<ProviderCredential>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_uuid(&conn, uuid).map_err(|e| e.to_string())
}
@@ -1584,7 +1585,7 @@ impl ProviderPoolService {
db: &DbConnection,
base_url: &str,
) -> Result<Vec<RouteInfo>, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let grouped = ProviderPoolDao::get_grouped(&conn).map_err(|e| e.to_string())?;
drop(conn);
@@ -1701,7 +1702,7 @@ impl ProviderPoolService {
/// 使用副本文件中的凭证进行刷新,副本文件应包含完整的 client_id/client_secret。
/// 支持多账号场景,每个副本文件完全独立。
pub async fn refresh_kiro_token(&self, creds_path: &str) -> Result<String, String> {
let mut provider = crate::providers::kiro::KiroProvider::new();
let mut provider = proxycast_providers::providers::kiro::KiroProvider::new();
provider
.load_credentials_from_path(creds_path)
.await
@@ -1716,7 +1717,7 @@ impl ProviderPoolService {
/// 刷新 OAuth Token (Gemini)
pub async fn refresh_gemini_token(&self, creds_path: &str) -> Result<String, String> {
let mut provider = crate::providers::gemini::GeminiProvider::new();
let mut provider = proxycast_providers::providers::gemini::GeminiProvider::new();
provider
.load_credentials_from_path(creds_path)
.await
@@ -1729,7 +1730,7 @@ impl ProviderPoolService {
/// 刷新 OAuth Token (Antigravity)
pub async fn refresh_antigravity_token(&self, creds_path: &str) -> Result<String, String> {
let mut provider = crate::providers::antigravity::AntigravityProvider::new();
let mut provider = proxycast_providers::providers::antigravity::AntigravityProvider::new();
provider
.load_credentials_from_path(creds_path)
.await
@@ -1747,7 +1748,7 @@ impl ProviderPoolService {
uuid: &str,
) -> Result<String, String> {
let cred = {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?
@@ -1774,7 +1775,7 @@ impl ProviderPoolService {
uuid: &str,
) -> Result<OAuthStatus, String> {
let cred = {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::get_by_uuid(&conn, uuid)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("Credential not found: {uuid}"))?
@@ -1795,7 +1796,7 @@ impl ProviderPoolService {
name: Option<String>,
check_health: Option<bool>,
check_model_name: Option<String>,
source: crate::models::provider_pool_model::CredentialSource,
source: proxycast_core::models::provider_pool_model::CredentialSource,
) -> Result<ProviderCredential, String> {
let pt: PoolProviderType = provider_type.parse().map_err(|e: String| e)?;
@@ -1804,7 +1805,7 @@ impl ProviderPoolService {
cred.check_health = check_health.unwrap_or(true);
cred.check_model_name = check_model_name;
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
ProviderPoolDao::insert(&conn, &cred).map_err(|e| e.to_string())?;
Ok(cred)
@@ -1816,10 +1817,10 @@ impl ProviderPoolService {
pub fn migrate_private_config(
&self,
db: &DbConnection,
config: &crate::config::Config,
config: &proxycast_core::config::Config,
) -> Result<MigrationResult, String> {
use crate::config::expand_tilde;
use crate::models::provider_pool_model::CredentialSource;
use proxycast_core::config::expand_tilde;
use proxycast_core::models::provider_pool_model::CredentialSource;
let mut result = MigrationResult::default();
@@ -1936,7 +1937,7 @@ impl ProviderPoolService {
/// 检查是否存在相同路径的凭证
fn credential_exists_by_path(&self, db: &DbConnection, path: &str) -> Result<bool, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let all_creds = ProviderPoolDao::get_all(&conn).map_err(|e| e.to_string())?;
for cred in all_creds {
@@ -1955,7 +1956,7 @@ impl ProviderPoolService {
db: &DbConnection,
api_key: &str,
) -> Result<bool, String> {
let conn = crate::database::lock_db(db)?;
let conn = proxycast_core::database::lock_db(db)?;
let all_creds = ProviderPoolDao::get_all(&conn).map_err(|e| e.to_string())?;
for cred in all_creds {
@@ -2,8 +2,8 @@
//!
//! 提供会话上下文的持久化、恢复和智能管理功能,解决 AI 对话中的上下文丢失问题
use crate::database::dao::general_chat::GeneralChatDao;
use crate::services::general_chat::{ChatMessage, MessageRole};
use crate::general_chat::{ChatMessage, MessageRole};
use proxycast_core::database::dao::general_chat::GeneralChatDao;
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -410,7 +410,7 @@ pub struct SessionStats {
#[cfg(test)]
mod tests {
use super::*;
use crate::services::general_chat::ChatSession;
use crate::general_chat::ChatSession;
use rusqlite::Connection;
fn setup_test_db() -> Connection {
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use std::time::Duration;
use tokio::time::timeout;
use crate::models::{AppType, Skill, SkillMetadata, SkillRepo, SkillState};
use proxycast_core::models::{AppType, Skill, SkillMetadata, SkillRepo, SkillState};
const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(60);
@@ -1,8 +1,8 @@
use crate::database::dao::providers::ProviderDao;
use crate::database::DbConnection;
use crate::models::{AppType, Provider};
use crate::services::live_sync;
use crate::live_sync;
use once_cell::sync::Lazy;
use proxycast_core::database::dao::providers::ProviderDao;
use proxycast_core::database::DbConnection;
use proxycast_core::models::{AppType, Provider};
use tokio::sync::Mutex;
pub struct SwitchService;
@@ -13,9 +13,9 @@
use rusqlite::Connection;
use crate::database::dao::template_dao::TemplateDao;
use crate::errors::project_error::TemplateError;
use crate::models::project_model::{CreateTemplateRequest, Template, TemplateUpdate};
use proxycast_core::database::dao::template_dao::TemplateDao;
use proxycast_core::errors::project_error::TemplateError;
use proxycast_core::models::project_model::{CreateTemplateRequest, Template, TemplateUpdate};
// ============================================================================
// 排版模板服务
@@ -211,7 +211,7 @@ impl TemplateService {
#[cfg(test)]
mod tests {
use super::*;
use crate::database::schema::create_tables;
use proxycast_core::database::schema::create_tables;
/// 创建测试数据库连接
fn setup_test_db() -> Connection {
@@ -8,16 +8,16 @@
//! - 按需刷新即将过期的 Token
//! - 处理 401/403 错误时的强制刷新
use crate::database::dao::provider_pool::ProviderPoolDao;
use crate::database::DbConnection;
use crate::models::provider_pool_model::{
CachedTokenInfo, CredentialData, PoolProviderType, ProviderCredential,
};
use crate::providers::gemini::GeminiProvider;
use crate::providers::kiro::KiroProvider;
use crate::services::kiro_event_service::KiroEventService;
use crate::kiro_event_service::KiroEventService;
use chrono::Utc;
use dashmap::DashMap;
use proxycast_core::database::dao::provider_pool::ProviderPoolDao;
use proxycast_core::database::DbConnection;
use proxycast_core::models::provider_pool_model::{
CachedTokenInfo, CredentialData, PoolProviderType, ProviderCredential,
};
use proxycast_providers::providers::gemini::GeminiProvider;
use proxycast_providers::providers::kiro::KiroProvider;
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -556,7 +556,7 @@ impl TokenCacheService {
/// 刷新 Antigravity Token
async fn refresh_antigravity(&self, creds_path: &str) -> Result<CachedTokenInfo, String> {
use crate::providers::antigravity::AntigravityProvider;
use proxycast_providers::providers::antigravity::AntigravityProvider;
let mut provider = AntigravityProvider::new();
provider
@@ -588,7 +588,7 @@ impl TokenCacheService {
/// 刷新 Codex Token
async fn refresh_codex(&self, creds_path: &str) -> Result<CachedTokenInfo, String> {
use crate::providers::codex::CodexProvider;
use proxycast_providers::providers::codex::CodexProvider;
let mut provider = CodexProvider::new();
provider
@@ -622,7 +622,7 @@ impl TokenCacheService {
/// 刷新 Claude OAuth Token
async fn refresh_claude_oauth(&self, creds_path: &str) -> Result<CachedTokenInfo, String> {
use crate::providers::claude_oauth::ClaudeOAuthProvider;
use proxycast_providers::providers::claude_oauth::ClaudeOAuthProvider;
let mut provider = ClaudeOAuthProvider::new();
provider
@@ -1029,8 +1029,10 @@ impl TokenCacheService {
// 首先检查缓存
let cached = {
let conn = db.lock().map_err(|e| e.to_string())?;
crate::database::dao::provider_pool::ProviderPoolDao::get_token_cache(&conn, uuid)
.map_err(|e| e.to_string())?
proxycast_core::database::dao::provider_pool::ProviderPoolDao::get_token_cache(
&conn, uuid,
)
.map_err(|e| e.to_string())?
};
// 检查是否需要提前刷新(使用指定的分钟数阈值)
@@ -2,7 +2,7 @@
//!
//! 提供工具执行前后的钩子机制,用于自动化上下文记忆管理
use crate::services::context_memory_service::{ContextMemoryService, MemoryEntry, MemoryFileType};
use crate::context_memory_service::{ContextMemoryService, MemoryEntry, MemoryFileType};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
@@ -566,7 +566,7 @@ impl ToolHooksService {
#[cfg(test)]
mod tests {
use super::*;
use crate::services::context_memory_service::ContextMemoryConfig;
use crate::context_memory_service::ContextMemoryConfig;
use tempfile::TempDir;
fn create_test_services() -> (Arc<ContextMemoryService>, ToolHooksService, TempDir) {
+1 -5
View File
@@ -20,8 +20,6 @@ pub use proxycast_core::models::prompt_model;
pub use proxycast_core::models::provider_model;
pub use proxycast_core::models::provider_pool_model;
pub use proxycast_core::models::route_model;
pub use proxycast_core::models::skill_model;
// project_model 已迁移到 core
pub use proxycast_core::models::project_model;
@@ -38,6 +36,4 @@ pub use proxycast_core::models::prompt_model::Prompt;
pub use proxycast_core::models::provider_model::Provider;
#[allow(unused_imports)]
pub use proxycast_core::models::provider_pool_model::*;
pub use proxycast_core::models::skill_model::{
Skill, SkillMetadata, SkillRepo, SkillState, SkillStates,
};
pub use proxycast_core::models::skill_model::{Skill, SkillMetadata, SkillRepo, SkillState};
+4 -375
View File
@@ -1,307 +1,8 @@
//! 客户端类型检测模块
//!
//! 通过解析 HTTP 请求的 User-Agent 头来识别客户端类型。
//! 已迁移到 proxycast-core crate,本文件仅做重新导出。
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
/// 客户端类型枚举
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientType {
/// Cursor 编辑器
Cursor,
/// Claude Code 客户端
ClaudeCode,
/// OpenAI Codex CLI
Codex,
/// Windsurf 编辑器
Windsurf,
/// Kiro IDE
Kiro,
/// 未识别的客户端
Other,
}
impl ClientType {
/// 从 User-Agent 字符串检测客户端类型
///
/// 支持大小写不敏感匹配。
///
/// # 参数
/// - `user_agent`: HTTP 请求的 User-Agent 头值
///
/// # 返回
/// 检测到的客户端类型
///
/// # 示例
/// ```ignore
/// use proxycast_lib::server::client_detector::ClientType;
///
/// assert_eq!(ClientType::from_user_agent("Cursor/1.0"), ClientType::Cursor);
/// assert_eq!(ClientType::from_user_agent("claude-code/2.0"), ClientType::ClaudeCode);
/// assert_eq!(ClientType::from_user_agent("Unknown"), ClientType::Other);
/// ```
pub fn from_user_agent(user_agent: &str) -> Self {
let ua_lower = user_agent.to_lowercase();
if ua_lower.contains("cursor") {
ClientType::Cursor
} else if ua_lower.contains("claude-code") || ua_lower.contains("claude_code") {
ClientType::ClaudeCode
} else if ua_lower.contains("codex") {
ClientType::Codex
} else if ua_lower.contains("windsurf") {
ClientType::Windsurf
} else if ua_lower.contains("kiro") {
ClientType::Kiro
} else {
ClientType::Other
}
}
/// 获取配置键名
///
/// 返回用于配置文件中的键名。
///
/// # 返回
/// 配置键名字符串
pub fn config_key(&self) -> &'static str {
match self {
ClientType::Cursor => "cursor",
ClientType::ClaudeCode => "claude_code",
ClientType::Codex => "codex",
ClientType::Windsurf => "windsurf",
ClientType::Kiro => "kiro",
ClientType::Other => "other",
}
}
/// 获取所有客户端类型
///
/// 返回所有支持的客户端类型列表。
pub fn all() -> &'static [ClientType] {
&[
ClientType::Cursor,
ClientType::ClaudeCode,
ClientType::Codex,
ClientType::Windsurf,
ClientType::Kiro,
ClientType::Other,
]
}
/// 从配置键名解析客户端类型
///
/// # 参数
/// - `key`: 配置键名
///
/// # 返回
/// 如果键名有效,返回对应的客户端类型;否则返回 None
pub fn from_config_key(key: &str) -> Option<Self> {
match key {
"cursor" => Some(ClientType::Cursor),
"claude_code" => Some(ClientType::ClaudeCode),
"codex" => Some(ClientType::Codex),
"windsurf" => Some(ClientType::Windsurf),
"kiro" => Some(ClientType::Kiro),
"other" => Some(ClientType::Other),
_ => None,
}
}
}
impl std::fmt::Display for ClientType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.config_key())
}
}
/// 根据客户端类型和端点配置选择 Provider
///
/// **Validates: Requirements 1.3, 1.4, 3.4**
///
/// 优先级:端点 Provider 配置 > 默认 Provider
///
/// # 参数
/// - `client_type`: 检测到的客户端类型
/// - `endpoint_provider`: 端点配置中该客户端类型对应的 Provider(可选)
/// - `default_provider`: 默认 Provider
///
/// # 返回
/// 选择的 Provider 名称
pub fn select_provider(
_client_type: ClientType,
endpoint_provider: Option<&String>,
default_provider: &str,
) -> String {
match endpoint_provider {
Some(provider) => provider.clone(),
None => default_provider.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_user_agent_cursor() {
assert_eq!(
ClientType::from_user_agent("Cursor/1.0"),
ClientType::Cursor
);
assert_eq!(ClientType::from_user_agent("cursor"), ClientType::Cursor);
assert_eq!(ClientType::from_user_agent("CURSOR"), ClientType::Cursor);
assert_eq!(
ClientType::from_user_agent("Mozilla/5.0 Cursor"),
ClientType::Cursor
);
}
#[test]
fn test_from_user_agent_claude_code() {
assert_eq!(
ClientType::from_user_agent("Claude-Code/2.0"),
ClientType::ClaudeCode
);
assert_eq!(
ClientType::from_user_agent("claude-code"),
ClientType::ClaudeCode
);
assert_eq!(
ClientType::from_user_agent("CLAUDE-CODE"),
ClientType::ClaudeCode
);
assert_eq!(
ClientType::from_user_agent("claude_code"),
ClientType::ClaudeCode
);
assert_eq!(
ClientType::from_user_agent("CLAUDE_CODE"),
ClientType::ClaudeCode
);
}
#[test]
fn test_from_user_agent_codex() {
assert_eq!(ClientType::from_user_agent("Codex/1.0"), ClientType::Codex);
assert_eq!(ClientType::from_user_agent("codex"), ClientType::Codex);
assert_eq!(ClientType::from_user_agent("CODEX"), ClientType::Codex);
}
#[test]
fn test_from_user_agent_windsurf() {
assert_eq!(
ClientType::from_user_agent("Windsurf/1.0"),
ClientType::Windsurf
);
assert_eq!(
ClientType::from_user_agent("windsurf"),
ClientType::Windsurf
);
assert_eq!(
ClientType::from_user_agent("WINDSURF"),
ClientType::Windsurf
);
}
#[test]
fn test_from_user_agent_kiro() {
assert_eq!(ClientType::from_user_agent("Kiro/1.0"), ClientType::Kiro);
assert_eq!(ClientType::from_user_agent("kiro"), ClientType::Kiro);
assert_eq!(ClientType::from_user_agent("KIRO"), ClientType::Kiro);
}
#[test]
fn test_from_user_agent_other() {
assert_eq!(ClientType::from_user_agent("Unknown"), ClientType::Other);
assert_eq!(ClientType::from_user_agent(""), ClientType::Other);
assert_eq!(
ClientType::from_user_agent("Mozilla/5.0"),
ClientType::Other
);
}
#[test]
fn test_config_key() {
assert_eq!(ClientType::Cursor.config_key(), "cursor");
assert_eq!(ClientType::ClaudeCode.config_key(), "claude_code");
assert_eq!(ClientType::Codex.config_key(), "codex");
assert_eq!(ClientType::Windsurf.config_key(), "windsurf");
assert_eq!(ClientType::Kiro.config_key(), "kiro");
assert_eq!(ClientType::Other.config_key(), "other");
}
#[test]
fn test_from_config_key() {
assert_eq!(
ClientType::from_config_key("cursor"),
Some(ClientType::Cursor)
);
assert_eq!(
ClientType::from_config_key("claude_code"),
Some(ClientType::ClaudeCode)
);
assert_eq!(
ClientType::from_config_key("codex"),
Some(ClientType::Codex)
);
assert_eq!(
ClientType::from_config_key("windsurf"),
Some(ClientType::Windsurf)
);
assert_eq!(ClientType::from_config_key("kiro"), Some(ClientType::Kiro));
assert_eq!(
ClientType::from_config_key("other"),
Some(ClientType::Other)
);
assert_eq!(ClientType::from_config_key("invalid"), None);
}
#[test]
fn test_all_client_types() {
let all = ClientType::all();
assert_eq!(all.len(), 6);
assert!(all.contains(&ClientType::Cursor));
assert!(all.contains(&ClientType::ClaudeCode));
assert!(all.contains(&ClientType::Codex));
assert!(all.contains(&ClientType::Windsurf));
assert!(all.contains(&ClientType::Kiro));
assert!(all.contains(&ClientType::Other));
}
#[test]
fn test_display() {
assert_eq!(format!("{}", ClientType::Cursor), "cursor");
assert_eq!(format!("{}", ClientType::ClaudeCode), "claude_code");
}
#[test]
fn test_serialization() {
let cursor = ClientType::Cursor;
let json = serde_json::to_string(&cursor).unwrap();
assert_eq!(json, "\"cursor\"");
let claude_code = ClientType::ClaudeCode;
let json = serde_json::to_string(&claude_code).unwrap();
assert_eq!(json, "\"claude_code\"");
}
#[test]
fn test_deserialization() {
let cursor: ClientType = serde_json::from_str("\"cursor\"").unwrap();
assert_eq!(cursor, ClientType::Cursor);
let claude_code: ClientType = serde_json::from_str("\"claude_code\"").unwrap();
assert_eq!(claude_code, ClientType::ClaudeCode);
}
}
// ============================================================================
// Property 2: Provider 选择优先级属性测试
// ============================================================================
pub use proxycast_core::models::client_type::*;
#[cfg(test)]
mod property_tests {
@@ -309,7 +10,6 @@ mod property_tests {
use crate::config::EndpointProvidersConfig;
use proptest::prelude::*;
/// 生成随机的客户端类型
fn arb_client_type() -> impl Strategy<Value = ClientType> {
prop_oneof![
Just(ClientType::Cursor),
@@ -321,7 +21,6 @@ mod property_tests {
]
}
/// 生成随机的 Provider 名称
fn arb_provider_name() -> impl Strategy<Value = String> {
prop_oneof![
Just("kiro".to_string()),
@@ -333,12 +32,10 @@ mod property_tests {
]
}
/// 生成可选的 Provider 名称
fn arb_optional_provider() -> impl Strategy<Value = Option<String>> {
prop_oneof![Just(None), arb_provider_name().prop_map(Some),]
}
/// 生成随机的 EndpointProvidersConfig
fn arb_endpoint_providers_config() -> impl Strategy<Value = EndpointProvidersConfig> {
(
arb_optional_provider(),
@@ -363,90 +60,22 @@ mod property_tests {
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
/// **Feature: endpoint-provider-config, Property 2: Provider 选择优先级**
/// *对于任意* 客户端类型和配置:
/// - 当 endpoint_providers[client_type] 有值时,应使用该 Provider
/// - 当 endpoint_providers[client_type] 为空时,应使用 default_provider
/// **Validates: Requirements 1.3, 1.4, 3.4**
#[test]
fn prop_provider_selection_priority(
client_type in arb_client_type(),
endpoint_config in arb_endpoint_providers_config(),
default_provider in arb_provider_name()
) {
// 获取端点配置中该客户端类型对应的 Provider
let endpoint_provider = endpoint_config.get_provider(client_type.config_key());
// 调用 select_provider 函数
let selected = select_provider(client_type, endpoint_provider, &default_provider);
// 验证选择逻辑
match endpoint_provider {
Some(provider) => {
// 当端点配置有值时,应使用端点配置的 Provider
prop_assert_eq!(
selected,
provider.clone(),
"当端点配置有值时,应使用端点配置的 Provider"
);
prop_assert_eq!(selected, provider.clone());
}
None => {
// 当端点配置为空时,应使用默认 Provider
prop_assert_eq!(
selected,
default_provider,
"当端点配置为空时,应使用默认 Provider"
);
prop_assert_eq!(selected, default_provider);
}
}
}
/// **Feature: endpoint-provider-config, Property 2: Provider 选择优先级(端点配置优先)**
/// *对于任意* 客户端类型,当端点配置有值时,应始终使用端点配置的 Provider,
/// 而不是默认 Provider。
/// **Validates: Requirements 1.3, 3.4**
#[test]
fn prop_endpoint_config_takes_priority(
client_type in arb_client_type(),
endpoint_provider in arb_provider_name(),
default_provider in arb_provider_name()
) {
// 调用 select_provider 函数,端点配置有值
let selected = select_provider(
client_type,
Some(&endpoint_provider),
&default_provider
);
// 验证:端点配置优先于默认配置
prop_assert_eq!(
selected,
endpoint_provider,
"端点配置应优先于默认配置"
);
}
/// **Feature: endpoint-provider-config, Property 2: Provider 选择优先级(回退到默认)**
/// *对于任意* 客户端类型,当端点配置为空时,应使用默认 Provider。
/// **Validates: Requirements 1.4**
#[test]
fn prop_fallback_to_default_provider(
client_type in arb_client_type(),
default_provider in arb_provider_name()
) {
// 调用 select_provider 函数,端点配置为空
let selected = select_provider(
client_type,
None,
&default_provider
);
// 验证:回退到默认 Provider
prop_assert_eq!(
selected,
default_provider,
"当端点配置为空时,应回退到默认 Provider"
);
}
}
}
+7 -8
View File
@@ -22,7 +22,6 @@ use axum::{
response::{IntoResponse, Response},
Json,
};
use chrono::Utc;
use serde_json::json;
use std::collections::HashMap;
@@ -494,13 +493,13 @@ pub async fn chat_completions(
// 尝试将 selected_provider 解析为 ProviderType
// 构建 Flow Metadata,同时保存 provider_type 和实际的 provider_id
let provider_type = selected_provider
let _provider_type = selected_provider
.parse::<ProviderType>()
.unwrap_or(ProviderType::OpenAI);
// 从凭证名称中提取 Provider 显示名称
// 凭证名称格式:Some("[降级] DeepSeek") 或 Some("DeepSeek")
let provider_display_name = cred.name.as_ref().map(|name| {
let _provider_display_name = cred.name.as_ref().map(|name| {
// 去掉 "[降级] " 前缀
if name.starts_with("[降级] ") {
&name[9..] // "[降级] " 是 9 个字节
@@ -521,7 +520,7 @@ pub async fn chat_completions(
// 记录请求统计
let is_success = response.status().is_success();
let status_code = response.status().as_u16();
let _status_code = response.status().as_u16();
let status = if is_success {
crate::telemetry::RequestStatus::Success
} else {
@@ -564,7 +563,7 @@ pub async fn chat_completions(
// 启动 Flow 捕获(legacy mode)
// 使用实际的 provider ID 构建 Flow Metadata
let provider_type = selected_provider
let _provider_type = selected_provider
.parse::<ProviderType>()
.unwrap_or(ProviderType::OpenAI);
@@ -1113,11 +1112,11 @@ pub async fn anthropic_messages(
// 使用凭证的实际 provider_type(支持自定义 Provider)
// 对于自定义 Provider ID,凭证的 provider_type 已通过数据库查询正确设置
let provider_type = cred.provider_type;
let _provider_type = cred.provider_type;
// 从凭证名称中提取 Provider 显示名称
// 凭证名称格式:Some("[降级] DeepSeek") 或 Some("DeepSeek")
let provider_display_name = cred.name.as_ref().map(|name| {
let _provider_display_name = cred.name.as_ref().map(|name| {
// 去掉 "[降级] " 前缀
if name.starts_with("[降级] ") {
&name[9..] // "[降级] " 是 9 个字节
@@ -1205,7 +1204,7 @@ pub async fn anthropic_messages(
// 启动 Flow 捕获(legacy mode)
// 使用实际的 provider ID 构建 Flow Metadata
let provider_type = selected_provider
let _provider_type = selected_provider
.parse::<ProviderType>()
.unwrap_or(ProviderType::OpenAI);
@@ -2156,7 +2156,7 @@ pub fn get_stream_format_for_credential(credential: &ProviderCredential) -> Stre
/// - 需求 4.2: 调用 process_chunk 更新流重建器
/// - 需求 5.1: 在收到 chunk 后立即转发给客户端
pub async fn handle_streaming_response(
state: &AppState,
_state: &AppState,
flow_id: Option<&str>,
source_stream: StreamResponse,
source_format: StreamingFormat,
@@ -2229,7 +2229,7 @@ pub async fn handle_streaming_response(
/// - 需求 6.2: 超时错误处理
/// - 需求 6.5: 可配置的流式响应超时
pub async fn handle_streaming_response_with_timeout(
state: &AppState,
_state: &AppState,
flow_id: Option<&str>,
source_stream: StreamResponse,
source_format: StreamingFormat,
@@ -2328,7 +2328,7 @@ pub fn response_to_stream(response: reqwest::Response) -> StreamResponse {
/// # 需求覆盖
/// - 需求 5.4: 客户端断开时取消上游请求
pub async fn handle_streaming_with_disconnect_detection(
state: &AppState,
_state: &AppState,
flow_id: Option<&str>,
source_stream: StreamResponse,
source_format: StreamingFormat,
+1
View File
@@ -114,6 +114,7 @@ impl CWParsedResponse {
///
/// # 返回
/// (input_tokens, output_tokens) 元组
#[allow(dead_code)]
pub fn estimate_tokens(&self) -> (u32, u32) {
// 估算 output tokens: 基于响应内容长度 (约 4 字符 = 1 token)
let mut output_tokens: u32 = (self.content.len() / 4) as u32;
+35 -26
View File
@@ -1,29 +1,38 @@
pub mod api_key_provider_service;
pub mod aster_session_store;
pub mod backup_service;
pub mod context_memory_service;
//! 业务服务模块
//!
//! 核心业务逻辑已迁移到 proxycast-services crate。
//! 本模块保留 Tauri 相关服务和重新导出。
// 从 proxycast-services crate 重新导出
pub use proxycast_services::api_key_provider_service;
pub use proxycast_services::aster_session_store;
pub use proxycast_services::backup_service;
pub use proxycast_services::content_creator;
pub use proxycast_services::context_memory_service;
pub use proxycast_services::general_chat;
pub use proxycast_services::kiro_event_service;
pub use proxycast_services::live_sync;
pub use proxycast_services::machine_id_service;
pub use proxycast_services::material_service;
pub use proxycast_services::mcp_service;
pub use proxycast_services::mcp_sync;
pub use proxycast_services::model_registry_service;
pub use proxycast_services::model_service;
pub use proxycast_services::persona_service;
pub use proxycast_services::project_context_builder;
pub use proxycast_services::prompt_service;
pub use proxycast_services::prompt_sync;
pub use proxycast_services::provider_pool_service;
pub use proxycast_services::session_context_service;
pub use proxycast_services::skill_service;
pub use proxycast_services::switch;
pub use proxycast_services::template_service;
pub use proxycast_services::token_cache_service;
pub use proxycast_services::tool_hooks_service;
pub use proxycast_services::update_check_service;
pub use proxycast_services::usage_service;
// 保留在主 crate 的 Tauri 相关服务
pub mod file_browser_service;
pub mod general_chat;
pub mod kiro_event_service;
pub mod live_sync;
pub mod machine_id_service;
pub mod material_service;
pub mod mcp_service;
pub mod mcp_sync;
pub mod model_registry_service;
pub mod model_service;
pub mod persona_service;
pub mod project_context_builder;
pub mod prompt_service;
pub mod prompt_sync;
pub mod provider_pool_service;
pub mod session_context_service;
pub mod skill_service;
pub mod switch;
pub mod sysinfo_service;
pub mod template_service;
pub mod token_cache_service;
pub mod tool_hooks_service;
pub mod update_check_service;
pub mod update_window;
pub mod usage_service;
+1 -3
View File
@@ -13,10 +13,8 @@ mod lifecycle;
mod processor;
mod stream;
// types 已迁移到 proxycast-core
pub use proxycast_core::websocket::types;
pub use processor::MessageProcessor;
pub use proxycast_core::websocket::types;
pub use proxycast_core::websocket::{
KiroTokenInfo, WsApiRequest, WsApiResponse, WsConfig, WsConnection, WsEndpoint, WsError,
WsKiroEvent, WsMessage, WsStats, WsStatsSnapshot, WsStreamChunk, WsStreamEnd,