mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
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:
@@ -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 }}'
|
||||
|
||||
Generated
+79
-3332
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
|
||||
AI Agent 集成模块,提供原生 Rust Agent 功能,支持**连续对话**和**工具调用**。
|
||||
|
||||
参考 [goose](https://github.com/block/goose) 项目的 Agent 设计:
|
||||
- **Conversation History**: 维护完整的消息历史,支持多轮对话
|
||||
- **Tools/Skills**: 预留工具调用接口(MCP 协议兼容)
|
||||
|
||||
### 设计决策
|
||||
|
||||
- **原生 Rust 实现**:直接在 Rust 中处理 Agent 功能,复用现有 provider 和流式处理能力
|
||||
|
||||
@@ -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,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::*;
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
])
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
Reference in New Issue
Block a user