use std::future::Future;
use std::pin::Pin;
use schemars::JsonSchema;
use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
use crate::context::Context;
use crate::tools::base::ErasedTool;
use crate::tools::{ToolBox, ToolDefinition, ToolError};
pub trait Agent: JsonSchema + Serialize + DeserializeOwned + Send + Sync + 'static {
type Output: JsonSchema + Serialize + DeserializeOwned + Send + Sync + 'static;
fn node_id() -> String {
Self::schema_name()
}
fn preamble() -> String;
fn model_url() -> String {
"gemini://gemini-2.5-flash-lite".to_string()
}
fn tool_box() -> ToolBox {
ToolBox::builder().build()
}
}
struct AgentExitTool {
name: String,
def: ToolDefinition,
}
impl ErasedTool for AgentExitTool {
fn name(&self) -> &str {
&self.name
}
fn definition(&self) -> ToolDefinition {
self.def.clone()
}
fn call_raw<'a>(
&'a self,
_ctx: Context,
args: Value,
) -> Pin<Box<dyn Future<Output = Result<Value, ToolError>> + Send + 'a>> {
Box::pin(async move { Err(ToolError::Exit(args)) })
}
}
impl ToolBox {
pub(crate) fn with_agent<A: Agent>(mut self) -> Self {
if self.is_empty() {
return self;
}
let name = self.exit_name().to_owned();
let mut schema_gen = schemars::r#gen::SchemaGenerator::default();
let parameters = serde_json::to_value(schema_gen.root_schema_for::<A::Output>())
.unwrap_or_else(|_| Value::Object(Default::default()));
let def = ToolDefinition {
name: name.clone(),
description: "Submit your final result. Call this only when the task is complete."
.to_owned(),
parameters,
};
self.push_erased(Box::new(AgentExitTool { name, def }));
self
}
}