use std::path::PathBuf;
use std::sync::Arc;
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemParam;
use rig_core::serve::ErasedHandler;
use rig_core::serve::adapters::ToolAdapter;
use rig_core::tool::{Touches, tool_definition};
use rig_ecs::effects::Handler;
use rig_ecs::prelude::{
Admitted, AgentPass, CallHandler, Footprint, ForAgent, PrepareSystems, Tool, ToolCalls,
ToolDef, ToolOptions, ToolRequest, Wake, WorldToolsExt,
};
use rig_tools::workspace::Workspace;
use rig_tools::{Live, Spill};
use crate::host::session::Scratch;
use crate::progress::CallOutput;
use crate::services::{ProjectRoot, WorkDir};
use crate::workspace::ProjectWorkspace;
pub struct CallPlace {
pub workspace: Arc<dyn Workspace>,
pub base: PathBuf,
pub confine: bool,
pub live: Option<Live>,
pub spill: Option<Spill>,
}
pub trait AppPerCallExt {
fn add_tool_per_call<T: Tool + 'static>(
&mut self,
tool: T,
options: ToolOptions,
make: impl Fn(CallPlace) -> T + Send + Sync + 'static,
) -> &mut Self;
fn add_live_tool_per_call<T: Tool + 'static>(
&mut self,
tool: T,
options: ToolOptions,
make: impl Fn(CallPlace) -> T + Send + Sync + 'static,
) -> &mut Self;
}
impl AppPerCallExt for App {
fn add_tool_per_call<T: Tool + 'static>(
&mut self,
tool: T,
options: ToolOptions,
make: impl Fn(CallPlace) -> T + Send + Sync + 'static,
) -> &mut Self {
add_per_call(self, tool, options, false, make)
}
fn add_live_tool_per_call<T: Tool + 'static>(
&mut self,
tool: T,
options: ToolOptions,
make: impl Fn(CallPlace) -> T + Send + Sync + 'static,
) -> &mut Self {
add_per_call(self, tool, options, true, make)
}
}
fn add_per_call<T: Tool + 'static>(
app: &mut App,
tool: T,
options: ToolOptions,
live: bool,
make: impl Fn(CallPlace) -> T + Send + Sync + 'static,
) -> &mut App {
if !app.is_plugin_added::<PerCallPlugin>() {
app.add_plugins(PerCallPlugin);
}
let definition = tool_definition(&tool);
let own = Touches::from_annotations(&definition.annotations);
let own = options.footprint.is_none().then_some(Footprint(own));
let handler = ErasedHandler::new(ToolAdapter::new(tool));
let make = move |place: CallPlace| {
CallHandler(Handler(ErasedHandler::new(ToolAdapter::new(make(place)))))
};
let world = app.world_mut();
if let Some(tool) = world.spawn_tool(definition, handler, options) {
let make = Box::new(make);
let mut tool = world.entity_mut(tool);
tool.insert(PerCall { live, make });
if let Some(own) = own {
tool.insert(own);
}
}
app
}
struct PerCallPlugin;
impl Plugin for PerCallPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ProjectRoot>()
.init_resource::<ProjectWorkspace>()
.add_systems(
AgentPass,
build_per_call_handlers.in_set(PrepareSystems::Compose),
);
}
}
#[derive(Component)]
struct PerCall {
live: bool,
make: Box<dyn Fn(CallPlace) -> CallHandler + Send + Sync>,
}
#[derive(SystemParam)]
pub struct PerCallTools<'w, 's> {
tools: Query<'w, 's, (&'static ToolDef, &'static PerCall)>,
dirs: Query<'w, 's, &'static WorkDir>,
root: Res<'w, ProjectRoot>,
}
impl PerCallTools<'_, '_> {
pub fn place(&self, agent: Option<Entity>, workspace: Arc<dyn Workspace>) -> CallPlace {
let dir = agent.and_then(|agent| self.dirs.get(agent).ok());
CallPlace {
workspace,
base: dir.map_or(&self.root.0, |dir| &dir.0).clone(),
confine: dir.is_some(),
live: None,
spill: None,
}
}
pub fn build(&self, name: &str, place: CallPlace) -> Option<CallHandler> {
self.find(name).map(|tool| (tool.make)(place))
}
fn find(&self, name: &str) -> Option<&PerCall> {
self.tools
.iter()
.find(|(tool, _)| tool.0.name == name)
.map(|(_, per_call)| per_call)
}
}
fn build_per_call_handlers(
mut calls: Query<
(Entity, &ToolRequest, Option<&ForAgent>, &mut CallHandler),
(ToolCalls, Added<Admitted>),
>,
tools: PerCallTools,
(workspace, scratch): (Res<ProjectWorkspace>, Option<Res<Scratch>>),
wake: Res<Wake>,
mut commands: Commands,
) {
for (call, request, agent, mut handler) in &mut calls {
let Some(tool) = tools.find(request.call.function.name.as_str()) else {
continue;
};
let live = tool.live.then(|| {
let (output, live) = CallOutput::new(&wake);
commands.entity(call).insert(output);
live
});
let place = CallPlace {
live,
spill: scratch.as_ref().and_then(|scratch| scratch.spill()),
..tools.place(agent.map(|agent| agent.0), workspace.0.clone())
};
*handler = (tool.make)(place);
}
}
#[cfg(test)]
mod tests;