use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use async_trait::async_trait;
use tokio::sync::{Mutex, mpsc};
use tokio_util::sync::CancellationToken;
use crate::agent::context::ConversationContext;
use crate::agent::r#loop::AgentEvent;
use crate::api::provider::OpenAiCompatibleProvider;
use crate::config::Config;
use crate::evolution::trial::Evolvable;
use crate::evolution::types::{Task, Trajectory};
use crate::evolution::workspace::AgentWorkspace;
pub struct ColletEvolvable {
client: OpenAiCompatibleProvider,
config: Config,
workspace: AgentWorkspace,
working_dir: String,
system_prompt: Arc<Mutex<String>>,
}
impl ColletEvolvable {
pub fn new(
client: OpenAiCompatibleProvider,
config: Config,
workspace_root: PathBuf,
working_dir: String,
) -> Self {
let workspace = AgentWorkspace::new(&workspace_root);
let initial_prompt = workspace.read_prompt().unwrap_or_default();
Self {
client,
config,
workspace,
working_dir,
system_prompt: Arc::new(Mutex::new(initial_prompt)),
}
}
}
#[async_trait]
impl Evolvable for ColletEvolvable {
async fn solve(&self, task: &Task) -> Result<Trajectory> {
let system_prompt = self.system_prompt.lock().await.clone();
let context = ConversationContext::new(system_prompt);
let (event_tx, mut event_rx) = mpsc::unbounded_channel::<AgentEvent>();
let cancel = CancellationToken::new();
let lsp = crate::lsp::manager::LspManager::new(self.working_dir.clone());
let client = self.client.clone();
let config = self.config.clone();
let working_dir = self.working_dir.clone();
let task_input = task.input.clone();
tokio::spawn(async move {
crate::agent::r#loop::run(crate::agent::r#loop::AgentParams {
client,
config,
context,
user_msg: task_input,
working_dir,
event_tx,
cancel,
lsp_manager: lsp,
trust_level: crate::trust::TrustLevel::Full,
approval_gate: crate::agent::approval::ApprovalGate::yolo(),
images: Vec::new(),
})
.await;
});
let mut output = String::new();
let mut steps: Vec<serde_json::Value> = Vec::new();
while let Some(event) = event_rx.recv().await {
match event {
AgentEvent::Token(t) => output.push_str(&t),
AgentEvent::Response(r) => output = r,
AgentEvent::ToolCall {
name,
args,
call_id,
} => {
steps.push(serde_json::json!({
"type": "tool_call",
"name": name,
"args": args,
"call_id": call_id,
}));
}
AgentEvent::ToolResult {
name,
result,
success,
call_id,
} => {
steps.push(serde_json::json!({
"type": "tool_result",
"name": name,
"result": result,
"success": success,
"call_id": call_id,
}));
}
AgentEvent::Done { .. } | AgentEvent::GuardStop(_) | AgentEvent::Error(_) => break,
_ => {}
}
}
Ok(Trajectory {
task_id: task.id.clone(),
output,
steps,
conversation: Vec::new(),
})
}
async fn export_to_fs(&self) -> Result<()> {
let prompt = self.system_prompt.lock().await.clone();
if !prompt.is_empty() {
self.workspace.write_prompt(&prompt)?;
}
Ok(())
}
async fn reload_from_fs(&self) -> Result<()> {
let new_prompt = self.workspace.read_prompt().unwrap_or_default();
if !new_prompt.is_empty() {
let mut prompt = self.system_prompt.lock().await;
*prompt = new_prompt;
tracing::info!("Agent reloaded system prompt from workspace");
}
Ok(())
}
}