pub use memory::{InMemory, Memory, NoEmbedder};
pub use wcore::{
AgentConfig, Handler, Hook, ToolRegistry,
model::{Message, Request, Response, Role, StreamChunk, Tool},
};
use anyhow::Result;
use async_stream::stream;
use compact_str::CompactString;
use futures_core::Stream;
use futures_util::StreamExt;
use std::{collections::BTreeMap, sync::Arc};
use tokio::sync::{Mutex, RwLock, mpsc};
use wcore::AgentEvent;
pub mod prelude {
pub use crate::{
AgentConfig, Handler, Hook, InMemory, Message, Request, Response, Role, Runtime,
StreamChunk, Tool, ToolRegistry,
};
}
pub struct Runtime<M: wcore::model::Model, H: Hook> {
pub model: M,
pub hook: H,
agents: BTreeMap<CompactString, Arc<Mutex<wcore::Agent<M>>>>,
tools: Arc<RwLock<ToolRegistry>>,
}
impl<M: wcore::model::Model + Send + Sync + Clone + 'static, H: Hook + 'static> Runtime<M, H> {
pub async fn new(model: M, hook: H) -> Self {
let mut registry = ToolRegistry::new();
hook.on_register_tools(&mut registry).await;
Self {
model,
hook,
agents: BTreeMap::new(),
tools: Arc::new(RwLock::new(registry)),
}
}
pub async fn register_tool(&self, tool: Tool, handler: Handler) {
self.tools.write().await.insert(tool, handler);
}
pub async fn unregister_tool(&self, name: &str) -> bool {
self.tools.write().await.remove(name)
}
pub async fn replace_tools(
&self,
old_names: &[CompactString],
new_tools: Vec<(Tool, Handler)>,
) {
let mut registry = self.tools.write().await;
for name in old_names {
registry.remove(name);
}
for (tool, handler) in new_tools {
registry.insert(tool, handler);
}
}
async fn dispatcher_for(&self, agent: &str) -> ToolRegistry {
let registry = self.tools.read().await;
let filter: Vec<CompactString> = self
.agents
.get(agent)
.and_then(|m| m.try_lock().ok())
.map(|g| g.config.tools.to_vec())
.unwrap_or_default();
registry.filtered_snapshot(&filter)
}
pub fn add_agent(&mut self, config: AgentConfig) {
let config = self.hook.on_build_agent(config);
let name = config.name.clone();
let agent = wcore::AgentBuilder::new(self.model.clone())
.config(config)
.build();
self.agents.insert(name, Arc::new(Mutex::new(agent)));
}
pub async fn agent(&self, name: &str) -> Option<AgentConfig> {
let mutex = self.agents.get(name)?;
Some(mutex.lock().await.config.clone())
}
pub async fn agents(&self) -> Vec<AgentConfig> {
let mut configs = Vec::with_capacity(self.agents.len());
for mutex in self.agents.values() {
configs.push(mutex.lock().await.config.clone());
}
configs
}
pub fn agent_mutex(&self, name: &str) -> Option<Arc<Mutex<wcore::Agent<M>>>> {
self.agents.get(name).cloned()
}
pub async fn clear_session(&self, agent: &str) {
if let Some(mutex) = self.agents.get(agent) {
mutex.lock().await.clear_history();
}
}
pub async fn send_to(&self, agent: &str, content: &str) -> Result<wcore::AgentResponse> {
let mutex = self
.agents
.get(agent)
.ok_or_else(|| anyhow::anyhow!("agent '{agent}' not registered"))?;
let dispatcher = self.dispatcher_for(agent).await;
let mut guard = mutex.lock().await;
guard.push_message(Message::user(content));
let (tx, mut rx) = mpsc::unbounded_channel();
let response = guard.run(&dispatcher, tx).await;
while let Ok(event) = rx.try_recv() {
self.hook.on_event(agent, &event);
}
Ok(response)
}
pub fn stream_to<'a>(
&'a self,
agent: &'a str,
content: &'a str,
) -> impl Stream<Item = AgentEvent> + 'a {
stream! {
let mutex = match self.agents.get(agent) {
Some(m) => m,
None => {
let resp = wcore::AgentResponse {
final_response: None,
iterations: 0,
stop_reason: wcore::AgentStopReason::Error(
format!("agent '{agent}' not registered"),
),
steps: vec![],
};
yield AgentEvent::Done(resp);
return;
}
};
let dispatcher = self.dispatcher_for(agent).await;
let mut guard = mutex.lock().await;
guard.push_message(Message::user(content));
let mut event_stream = std::pin::pin!(guard.run_stream(&dispatcher));
while let Some(event) = event_stream.next().await {
self.hook.on_event(agent, &event);
yield event;
}
}
}
}