perf: 移除未使用的 goose 依赖,优化 Windows CI 构建速度

- 移除 goose git 依赖(从 main 分支拉取,每次都要编译大量代码)
- 删除 goose_agent.rs 和 goose_agent_cmd.rs(未被实际使用)
- 删除 aster_client_tests.rs(goose 相关测试)
- 更新 agent/mod.rs 和 lib.rs 移除相关引用
- 优化 release.yml CI 配置:
  - cache-all-crates: true
  - CARGO_PROFILE_RELEASE_LTO: thin
  - CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16

预计可显著减少 Windows 构建时间(从 ~38 分钟)
This commit is contained in:
coso
2025-12-31 14:13:44 +08:00
parent 2166fe2841
commit c621779c75
10 changed files with 91 additions and 4090 deletions
+10 -1
View File
@@ -14,6 +14,12 @@ on:
permissions:
contents: write
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTUP_MAX_RETRIES: 10
CARGO_TERM_COLOR: always
jobs:
build:
strategy:
@@ -60,8 +66,9 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
shared-key: "rust-cache-${{ matrix.target }}"
shared-key: "rust-${{ matrix.target }}"
cache-on-failure: true
cache-all-crates: true
- name: Install frontend dependencies
run: npm ci
@@ -70,6 +77,8 @@ jobs:
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_PROFILE_RELEASE_LTO: thin
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16
with:
tagName: ${{ github.event.inputs.version || github.ref_name }}
releaseName: 'ProxyCast ${{ github.event.inputs.version || github.ref_name }}'
+79 -3332
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -65,9 +65,6 @@ once_cell = "1"
tokio-util = "0.7"
arboard = "3"
# Goose AI Agent framework
goose = { git = "https://github.com/block/goose", branch = "main" }
# Platform specific dependencies for browser interceptor
# Windows specific dependencies for browser interceptor and machine ID management
-4
View File
@@ -6,10 +6,6 @@
AI Agent 集成模块,提供原生 Rust Agent 功能,支持**连续对话**和**工具调用**。
参考 [goose](https://github.com/block/goose) 项目的 Agent 设计:
- **Conversation History**: 维护完整的消息历史,支持多轮对话
- **Tools/Skills**: 预留工具调用接口(MCP 协议兼容)
### 设计决策
- **原生 Rust 实现**:直接在 Rust 中处理 Agent 功能,复用现有 provider 和流式处理能力
-455
View File
@@ -1,455 +0,0 @@
//! Goose Agent 集成模块
//!
//! 封装 Goose 框架,提供简化的 Agent API
//! 参考: https://github.com/block/goose
use anyhow::Result;
use futures::StreamExt;
use goose::agents::{Agent, AgentEvent, SessionConfig};
use goose::conversation::message::Message;
use goose::providers::create_with_named_model;
use goose::session::session_manager::SessionType;
use goose::session::SessionManager;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::{debug, error, info};
use crate::agent::types::*;
/// Goose Agent 管理器
///
/// 封装 Goose 框架的 Agent,提供简化的 API
pub struct GooseAgentManager {
/// 底层 Goose Agent
agent: Arc<Agent>,
/// Provider 名称
provider_name: String,
/// 模型名称
model_name: String,
/// Session ID 映射
sessions: Arc<RwLock<HashMap<String, String>>>,
}
impl GooseAgentManager {
/// 创建新的 Goose Agent 管理器
///
/// # Arguments
/// * `provider_name` - Provider 名称 (如 "anthropic", "openai", "ollama")
/// * `model_name` - 模型名称 (如 "claude-sonnet-4-20250514", "gpt-4o")
pub async fn new(provider_name: &str, model_name: &str) -> Result<Self> {
info!(
"[GooseAgent] 创建 Agent: provider={}, model={}",
provider_name, model_name
);
// 创建 Provider
let provider = create_with_named_model(provider_name, model_name).await?;
// 创建 Agent
let agent = Agent::new();
// 创建初始 Session
let session = SessionManager::create_session(
PathBuf::default(),
"proxycast-session".to_string(),
SessionType::Hidden,
)
.await?;
// 设置 Provider
agent.update_provider(provider, &session.id).await?;
// 自动加载 ProxyCast Skills
if let Some(skills_prompt) = Self::generate_skills_prompt() {
agent.extend_system_prompt(skills_prompt).await;
info!("[GooseAgent] 已注入 ProxyCast Skills 到 System Prompt");
}
info!("[GooseAgent] Agent 创建成功: session_id={}", session.id);
Ok(Self {
agent: Arc::new(agent),
provider_name: provider_name.to_string(),
model_name: model_name.to_string(),
sessions: Arc::new(RwLock::new(HashMap::new())),
})
}
/// 获取 Skills 目录列表
fn get_skills_directories() -> Vec<PathBuf> {
let mut dirs = Vec::new();
if let Some(home) = dirs::home_dir() {
// ProxyCast Skills 目录
dirs.push(home.join(".proxycast").join("skills"));
// Claude Code 兼容目录
dirs.push(home.join(".claude").join("skills"));
}
dirs
}
/// 解析 SKILL.md 文件的 frontmatter
fn parse_skill_frontmatter(content: &str) -> Option<(String, String)> {
// 解析 YAML frontmatter
if !content.starts_with("---") {
return None;
}
let parts: Vec<&str> = content.splitn(3, "---").collect();
if parts.len() < 3 {
return None;
}
let yaml_content = parts[1].trim();
// 简单解析 name 和 description
let mut name = None;
let mut description = None;
for line in yaml_content.lines() {
let line = line.trim();
if let Some(value) = line.strip_prefix("name:") {
name = Some(value.trim().trim_matches('"').to_string());
} else if let Some(value) = line.strip_prefix("description:") {
description = Some(value.trim().trim_matches('"').to_string());
}
}
match (name, description) {
(Some(n), Some(d)) => Some((n, d)),
_ => None,
}
}
/// 扫描目录中的 Skills
fn discover_skills(directories: &[PathBuf]) -> Vec<(String, String)> {
let mut skills = Vec::new();
for dir in directories {
if !dir.exists() {
continue;
}
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
let skill_file = path.join("SKILL.md");
if skill_file.exists() {
if let Ok(content) = fs::read_to_string(&skill_file) {
if let Some((name, desc)) = Self::parse_skill_frontmatter(&content)
{
skills.push((name, desc));
}
}
}
}
}
}
}
// 按名称排序
skills.sort_by(|a, b| a.0.cmp(&b.0));
skills
}
/// 生成 Skills 提示词
fn generate_skills_prompt() -> Option<String> {
let directories = Self::get_skills_directories();
let skills = Self::discover_skills(&directories);
if skills.is_empty() {
debug!("[GooseAgent] 未发现已安装的 Skills");
return None;
}
let mut prompt =
String::from("\n\n<available_skills>\nYou have these skills at your disposal:\n\n");
for (name, description) in &skills {
prompt.push_str(&format!("- {}: {}\n", name, description));
}
prompt.push_str("</available_skills>");
info!("[GooseAgent] 发现 {} 个 Skills", skills.len());
Some(prompt)
}
/// 发送消息并获取流式响应
pub async fn send_message(
&self,
message: &str,
session_id: &str,
tx: mpsc::Sender<StreamEvent>,
) -> Result<()> {
debug!(
"[GooseAgent] 发送消息: session_id={}, message_len={}",
session_id,
message.len()
);
// 创建用户消息
let user_message = Message::user().with_text(message);
// 创建 SessionConfig
let session_config = SessionConfig {
id: session_id.to_string(),
schedule_id: None,
max_turns: Some(100),
retry_config: None,
};
// 发送消息并获取响应流
let mut stream = self.agent.reply(user_message, session_config, None).await?;
let mut full_content = String::new();
// 处理响应流
while let Some(event) = stream.next().await {
match event {
Ok(AgentEvent::Message(msg)) => {
// 提取文本内容
for content in &msg.content {
if let Some(text) = content.as_text() {
full_content.push_str(&text);
let _ = tx
.send(StreamEvent::TextDelta {
text: text.to_string(),
})
.await;
}
}
}
Ok(AgentEvent::McpNotification(_)) => {
// MCP 通知,可以忽略或记录
debug!("[GooseAgent] MCP 通知");
}
Ok(AgentEvent::ModelChange { model, mode }) => {
debug!("[GooseAgent] 模型切换: model={}, mode={}", model, mode);
}
Ok(AgentEvent::HistoryReplaced(_)) => {
debug!("[GooseAgent] 历史替换");
}
Err(e) => {
error!("[GooseAgent] 流错误: {}", e);
let _ = tx
.send(StreamEvent::Error {
message: format!("流错误: {}", e),
})
.await;
return Err(e);
}
}
}
// 发送完成事件
let _ = tx.send(StreamEvent::Done { usage: None }).await;
info!(
"[GooseAgent] 消息处理完成: content_len={}",
full_content.len()
);
Ok(())
}
/// 创建新会话
pub async fn create_session(&self, name: Option<String>) -> Result<String> {
let session_name = name.unwrap_or_else(|| format!("proxycast-{}", uuid::Uuid::new_v4()));
let session = SessionManager::create_session(
PathBuf::default(),
session_name.clone(),
SessionType::Hidden,
)
.await?;
// 存储会话映射
self.sessions
.write()
.insert(session_name.clone(), session.id.clone());
info!(
"[GooseAgent] 创建会话: name={}, id={}",
session_name, session.id
);
Ok(session.id)
}
/// 获取 Provider 名称
pub fn provider_name(&self) -> &str {
&self.provider_name
}
/// 获取模型名称
pub fn model_name(&self) -> &str {
&self.model_name
}
/// 扩展系统提示词
pub async fn extend_system_prompt(&self, instruction: &str) {
self.agent
.extend_system_prompt(instruction.to_string())
.await;
debug!("[GooseAgent] 扩展系统提示词: len={}", instruction.len());
}
}
/// Goose Agent 状态 (Tauri State)
#[derive(Clone, Default)]
pub struct GooseAgentState {
agent: Arc<RwLock<Option<Arc<GooseAgentManager>>>>,
}
impl GooseAgentState {
pub fn new() -> Self {
Self {
agent: Arc::new(RwLock::new(None)),
}
}
/// 初始化 Goose Agent
pub async fn init(&self, provider_name: &str, model_name: &str) -> Result<(), String> {
let manager = GooseAgentManager::new(provider_name, model_name)
.await
.map_err(|e| format!("初始化 Goose Agent 失败: {}", e))?;
*self.agent.write() = Some(Arc::new(manager));
info!("[GooseAgentState] Goose Agent 初始化成功");
Ok(())
}
/// 检查是否已初始化
pub fn is_initialized(&self) -> bool {
self.agent.read().is_some()
}
/// 重置 Agent
pub fn reset(&self) {
*self.agent.write() = None;
info!("[GooseAgentState] Goose Agent 已重置");
}
/// 发送消息(流式)
pub async fn send_message(
&self,
message: &str,
session_id: &str,
tx: mpsc::Sender<StreamEvent>,
) -> Result<(), String> {
// 先获取 manager 的克隆,然后释放锁
let manager = {
let guard = self.agent.read();
guard
.as_ref()
.ok_or_else(|| "Goose Agent 未初始化".to_string())?
.clone()
};
// 创建用户消息
let user_message = Message::user().with_text(message);
// 创建 SessionConfig
let session_config = SessionConfig {
id: session_id.to_string(),
schedule_id: None,
max_turns: Some(100),
retry_config: None,
};
// 发送消息并获取响应流
let mut stream = manager
.agent
.reply(user_message, session_config, None)
.await
.map_err(|e| format!("发送消息失败: {}", e))?;
// 处理响应流
while let Some(event) = stream.next().await {
match event {
Ok(AgentEvent::Message(msg)) => {
for content in &msg.content {
if let Some(text) = content.as_text() {
let _ = tx
.send(StreamEvent::TextDelta {
text: text.to_string(),
})
.await;
}
}
}
Ok(_) => {}
Err(e) => {
let _ = tx
.send(StreamEvent::Error {
message: format!("流错误: {}", e),
})
.await;
return Err(format!("流错误: {}", e));
}
}
}
let _ = tx.send(StreamEvent::Done { usage: None }).await;
Ok(())
}
/// 创建新会话
pub async fn create_session(&self, name: Option<String>) -> Result<String, String> {
// 先获取 manager 的克隆,然后释放锁
let manager = {
let guard = self.agent.read();
guard
.as_ref()
.ok_or_else(|| "Goose Agent 未初始化".to_string())?
.clone()
};
manager
.create_session(name)
.await
.map_err(|e| format!("创建会话失败: {}", e))
}
/// 扩展系统提示词
pub async fn extend_system_prompt(&self, instruction: &str) -> Result<(), String> {
// 先获取 manager 的克隆,然后释放锁
let manager = {
let guard = self.agent.read();
guard
.as_ref()
.ok_or_else(|| "Goose Agent 未初始化".to_string())?
.clone()
};
manager.extend_system_prompt(instruction).await;
Ok(())
}
/// 获取 Provider 信息
pub fn get_provider_info(&self) -> Option<(String, String)> {
let guard = self.agent.read();
guard
.as_ref()
.map(|m| (m.provider_name.clone(), m.model_name.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_goose_state_default() {
let state = GooseAgentState::new();
assert!(!state.is_initialized());
}
}
+1 -10
View File
@@ -1,18 +1,9 @@
//! AI Agent 集成模块
//!
//! 提供 Agent 功能:
//! - Goose Agent: 基于 Goose 框架的完整 Agent 实现
//! - Native Agent: 基于 OpenAI 兼容 API 的简单实现
//! 提供基于 OpenAI 兼容 API 的 Agent 实现
pub mod goose_agent;
pub mod native_agent;
pub mod types;
// Goose Agent (推荐)
pub use goose_agent::{GooseAgentManager, GooseAgentState};
// Native Agent (简单实现)
pub use native_agent::{NativeAgent, NativeAgentState};
// 公共类型
pub use types::*;
-226
View File
@@ -1,226 +0,0 @@
//! Goose Agent 命令模块
//!
//! 提供基于 Goose 框架的 Agent Tauri 命令
use crate::agent::{GooseAgentState, StreamEvent};
use serde::{Deserialize, Serialize};
use tauri::{Emitter, State};
use tokio::sync::mpsc;
use tracing::{error, info};
/// Goose Agent 状态响应
#[derive(Debug, Serialize)]
pub struct GooseAgentStatus {
pub initialized: bool,
pub provider: Option<String>,
pub model: Option<String>,
}
/// 初始化 Goose Agent
///
/// # Arguments
/// * `provider_name` - Provider 名称 (如 "anthropic", "openai", "ollama")
/// * `model_name` - 模型名称 (如 "claude-sonnet-4-20250514", "gpt-4o")
#[tauri::command]
pub async fn goose_agent_init(
agent_state: State<'_, GooseAgentState>,
provider_name: String,
model_name: String,
) -> Result<GooseAgentStatus, String> {
info!(
"[GooseAgent] 初始化: provider={}, model={}",
provider_name, model_name
);
agent_state.init(&provider_name, &model_name).await?;
Ok(GooseAgentStatus {
initialized: true,
provider: Some(provider_name),
model: Some(model_name),
})
}
/// 获取 Goose Agent 状态
#[tauri::command]
pub async fn goose_agent_status(
agent_state: State<'_, GooseAgentState>,
) -> Result<GooseAgentStatus, String> {
let initialized = agent_state.is_initialized();
let info = agent_state.get_provider_info();
Ok(GooseAgentStatus {
initialized,
provider: info.as_ref().map(|(p, _)| p.clone()),
model: info.map(|(_, m)| m),
})
}
/// 重置 Goose Agent
#[tauri::command]
pub async fn goose_agent_reset(agent_state: State<'_, GooseAgentState>) -> Result<(), String> {
agent_state.reset();
info!("[GooseAgent] Agent 已重置");
Ok(())
}
/// 创建会话响应
#[derive(Debug, Serialize)]
pub struct CreateSessionResponse {
pub session_id: String,
}
/// 创建 Goose Agent 会话
#[tauri::command]
pub async fn goose_agent_create_session(
agent_state: State<'_, GooseAgentState>,
name: Option<String>,
) -> Result<CreateSessionResponse, String> {
let session_id = agent_state.create_session(name).await?;
info!("[GooseAgent] 创建会话: {}", session_id);
Ok(CreateSessionResponse { session_id })
}
/// 发送消息请求参数
#[derive(Debug, Deserialize)]
pub struct SendMessageRequest {
pub session_id: String,
pub message: String,
pub event_name: String,
}
/// 发送消息到 Goose Agent (流式响应)
///
/// 通过 Tauri 事件发送响应流
#[tauri::command]
pub async fn goose_agent_send_message(
app_handle: tauri::AppHandle,
agent_state: State<'_, GooseAgentState>,
request: SendMessageRequest,
) -> Result<(), String> {
info!(
"[GooseAgent] 发送消息: session_id={}, message_len={}",
request.session_id,
request.message.len()
);
if !agent_state.is_initialized() {
return Err("Goose Agent 未初始化,请先调用 goose_agent_init".to_string());
}
let session_id = request.session_id.clone();
let message = request.message.clone();
let event_name = request.event_name.clone();
// 克隆 agent 信息用于后台任务
let agent_guard = agent_state.inner().clone();
// 在后台任务中处理流式响应
tauri::async_runtime::spawn(async move {
let (tx, mut rx) = mpsc::channel::<StreamEvent>(100);
// 启动消息发送任务
let send_task = {
let agent_guard = agent_guard.clone();
let session_id = session_id.clone();
let message = message.clone();
tokio::spawn(async move { agent_guard.send_message(&message, &session_id, tx).await })
};
// 接收并转发事件
while let Some(event) = rx.recv().await {
if let Err(e) = app_handle.emit(&event_name, &event) {
error!("[GooseAgent] 发送事件失败: {}", e);
break;
}
if matches!(event, StreamEvent::Done { .. } | StreamEvent::Error { .. }) {
break;
}
}
// 等待发送任务完成
if let Err(e) = send_task.await {
error!("[GooseAgent] 发送任务失败: {}", e);
}
});
Ok(())
}
/// 扩展系统提示词
#[tauri::command]
pub async fn goose_agent_extend_system_prompt(
agent_state: State<'_, GooseAgentState>,
instruction: String,
) -> Result<(), String> {
info!("[GooseAgent] 扩展系统提示词: len={}", instruction.len());
agent_state.extend_system_prompt(&instruction).await
}
/// 获取可用的 Provider 列表
#[derive(Debug, Serialize)]
pub struct ProviderInfo {
pub name: String,
pub display_name: String,
}
/// 获取 Goose 支持的 Provider 列表
#[tauri::command]
pub async fn goose_agent_list_providers() -> Result<Vec<ProviderInfo>, String> {
// Goose 支持的主要 Provider
let providers = vec![
ProviderInfo {
name: "anthropic".to_string(),
display_name: "Anthropic (Claude)".to_string(),
},
ProviderInfo {
name: "openai".to_string(),
display_name: "OpenAI (GPT)".to_string(),
},
ProviderInfo {
name: "google".to_string(),
display_name: "Google (Gemini)".to_string(),
},
ProviderInfo {
name: "ollama".to_string(),
display_name: "Ollama (Local)".to_string(),
},
ProviderInfo {
name: "openrouter".to_string(),
display_name: "OpenRouter".to_string(),
},
ProviderInfo {
name: "bedrock".to_string(),
display_name: "AWS Bedrock".to_string(),
},
ProviderInfo {
name: "azure".to_string(),
display_name: "Azure OpenAI".to_string(),
},
ProviderInfo {
name: "databricks".to_string(),
display_name: "Databricks".to_string(),
},
];
Ok(providers)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_provider_info_serialize() {
let info = ProviderInfo {
name: "anthropic".to_string(),
display_name: "Anthropic".to_string(),
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("anthropic"));
}
}
-1
View File
@@ -3,7 +3,6 @@ pub mod auto_fix_cmd;
pub mod browser_interceptor_cmd;
pub mod config_cmd;
pub mod flow_monitor_cmd;
pub mod goose_agent_cmd;
pub mod injection_cmd;
pub mod kiro_local;
pub mod machine_id_cmd;
+1 -13
View File
@@ -29,7 +29,7 @@ use std::sync::Arc;
use tauri::{Manager, Runtime};
use tokio::sync::RwLock;
use agent::{GooseAgentState, NativeAgentState};
use agent::NativeAgentState;
use commands::browser_interceptor_cmd::BrowserInterceptorState;
use commands::flow_monitor_cmd::{
BatchOperationsState, BookmarkManagerState, EnhancedStatsServiceState, FlowInterceptorState,
@@ -1695,9 +1695,6 @@ pub fn run() {
// Initialize NativeAgentState
let native_agent_state = NativeAgentState::new();
// Initialize GooseAgentState
let goose_agent_state = GooseAgentState::new();
// FlowQueryService 需要 file_store,如果没有则创建一个临时的
let flow_query_service_state = if let Some(file_store) = flow_file_store {
let query_service = FlowQueryService::new(flow_monitor.memory_store(), file_store);
@@ -1797,7 +1794,6 @@ pub fn run() {
.manage(batch_operations_state)
.manage(browser_interceptor_state)
.manage(native_agent_state)
.manage(goose_agent_state)
.on_window_event(move |window, event| {
// 处理窗口关闭事件
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
@@ -2429,14 +2425,6 @@ pub fn run() {
commands::native_agent_cmd::native_agent_get_session,
commands::native_agent_cmd::native_agent_delete_session,
commands::native_agent_cmd::native_agent_list_sessions,
// Goose Agent commands
commands::goose_agent_cmd::goose_agent_init,
commands::goose_agent_cmd::goose_agent_status,
commands::goose_agent_cmd::goose_agent_reset,
commands::goose_agent_cmd::goose_agent_create_session,
commands::goose_agent_cmd::goose_agent_send_message,
commands::goose_agent_cmd::goose_agent_extend_system_prompt,
commands::goose_agent_cmd::goose_agent_list_providers,
// Network commands
commands::network_cmd::get_network_info,
])
-45
View File
@@ -1,45 +0,0 @@
//! Goose Agent 模块测试
//!
//! 测试 GooseAgentManager 和 GooseAgentState 的基本功能
use proxycast_lib::agent::{GooseAgentState, StreamEvent};
#[test]
fn test_goose_agent_state_creation() {
let state = GooseAgentState::new();
assert!(!state.is_initialized());
}
#[test]
fn test_goose_agent_state_not_initialized() {
let state = GooseAgentState::new();
let info = state.get_provider_info();
assert!(info.is_none());
}
#[test]
fn test_stream_event_serialization() {
let event = StreamEvent::TextDelta {
text: "Hello".to_string(),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("text_delta"));
assert!(json.contains("Hello"));
}
#[test]
fn test_stream_event_done() {
let event = StreamEvent::Done { usage: None };
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("done"));
}
#[test]
fn test_stream_event_error() {
let event = StreamEvent::Error {
message: "Test error".to_string(),
};
let json = serde_json::to_string(&event).unwrap();
assert!(json.contains("error"));
assert!(json.contains("Test error"));
}