mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
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:
Generated
+39
@@ -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"
|
||||
|
||||
@@ -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\"");
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
+39
-37
@@ -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()));
|
||||
+1
-1
@@ -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;
|
||||
+1
-1
@@ -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};
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
+1
-1
@@ -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)]
|
||||
@@ -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
-1
@@ -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;
|
||||
+4
-4
@@ -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;
|
||||
|
||||
+5
-5
@@ -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(),
|
||||
+2
-2
@@ -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,
|
||||
};
|
||||
+5
-3
@@ -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;
|
||||
+8
-8
@@ -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");
|
||||
+12
-12
@@ -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(),
|
||||
+4
-4
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
use crate::models::AppType;
|
||||
use proxycast_core::models::AppType;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
+47
-46
@@ -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 {
|
||||
+3
-3
@@ -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 {
|
||||
+1
-1
@@ -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;
|
||||
+4
-4
@@ -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 {
|
||||
+15
-13
@@ -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
-2
@@ -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) {
|
||||
@@ -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};
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user