use std::path::{Path, PathBuf};
use std::sync::Arc;
use ::lernie::cmd::{InjectedTool, RoutedCall, RoutedCapture, ToolInjection};
use crate::ui_state::{Clock, iso8601_extended};
pub mod ask;
pub mod clients;
pub mod loaded;
pub mod remote;
pub mod render;
pub struct Site {
pub state_root: PathBuf,
pub workspace: String,
pub agent: String,
pub budget: ask::Budget,
pub patience: ask::Budget,
pub clock: Arc<dyn Clock>,
}
impl Site {
pub fn observed(&self) -> String {
iso8601_extended(self.clock.stamp().parse().unwrap_or(0))
}
}
pub struct Injection {
state_root: PathBuf,
budget: ask::Budget,
patience: ask::Budget,
clock: Arc<dyn Clock>,
driving: Option<(String, String)>,
}
impl Injection {
pub fn new(
state_root: PathBuf,
budget: ask::Budget,
patience: ask::Budget,
clock: Arc<dyn Clock>,
driving: Option<(String, String)>,
) -> Self {
Self {
state_root,
budget,
patience,
clock,
driving,
}
}
fn site(&self, workspace: &Path, agent: &str) -> Site {
Site {
state_root: self.state_root.clone(),
workspace: crate::naming::leaf(workspace),
agent: agent.to_owned(),
budget: self.budget,
patience: self.patience,
clock: Arc::clone(&self.clock),
}
}
}
fn declaration() -> InjectedTool {
InjectedTool {
name: clients::NAME.to_owned(),
input_schema: clients::schema(),
description: Some(clients::DESCRIPTION.to_owned()),
}
}
fn capture(name: &str, answered: Result<String, String>) -> RoutedCapture {
match answered {
Ok(product) => RoutedCapture {
stdout: product.into_bytes(),
stderr: Vec::new(),
exit_code: 0,
},
Err(reason) => RoutedCapture {
stdout: Vec::new(),
stderr: format!("{name}: {reason}\n").into_bytes(),
exit_code: 1,
},
}
}
fn routed(site: &Site, entry: &loaded::Entry, call: &RoutedCall<'_>) -> RoutedCapture {
match remote::invoke(site, entry, call.input, call.stop) {
Ok(got) => RoutedCapture {
stdout: got.stdout.into_bytes(),
stderr: got.stderr.into_bytes(),
exit_code: got.exit_code,
},
Err(reason) => capture(call.name, Err(reason)),
}
}
impl ToolInjection for Injection {
fn tools(&self) -> Vec<InjectedTool> {
let mut out = vec![declaration()];
if let Some((workspace, agent)) = &self.driving {
out.extend(
loaded::read(&self.state_root, workspace, agent)
.into_iter()
.map(|entry| InjectedTool {
name: entry.presented(),
input_schema: entry.tool.input_schema,
description: Some(entry.tool.description),
}),
);
}
out
}
fn route(&self, call: RoutedCall<'_>) -> Option<RoutedCapture> {
let site = self.site(call.workspace, call.agent);
if call.name == clients::NAME {
return Some(capture(
call.name,
clients::answer(&site, call.input, call.stop),
));
}
let entry = loaded::read(&site.state_root, &site.workspace, &site.agent)
.into_iter()
.find(|entry| entry.presented() == call.name)?;
Some(routed(&site, &entry, &call))
}
}
#[cfg(test)]
mod tests;