use std::sync::Arc;
use agentyk_core::budget::BudgetChecker;
use agentyk_core::capability::Capability;
use agentyk_core::context::{ContextAssembler, PassthroughContextAssembler};
use agentyk_core::driver::{ChatDriver, DriverRegistry, ModelSpec};
use agentyk_core::error::{Error, Result};
use agentyk_core::event::EventListener;
use agentyk_core::event_log::{EventLog, InMemoryEventLog};
use agentyk_core::executor::{TurnExecutor, TurnResult};
use agentyk_core::extensions::Extensions;
use agentyk_core::hooks::{PostToolExecHook, PreToolUseHook};
use agentyk_core::id::SessionId;
use agentyk_core::tool::Tool;
use async_trait::async_trait;
use crate::in_process::InProcessExecutor;
use crate::session::Session;
pub(crate) struct AgentInner {
pub(crate) name: String,
pub(crate) system_prompt: String,
pub(crate) model: ModelSpec,
pub(crate) capabilities: Vec<Arc<dyn Capability>>,
pub(crate) drivers: DriverRegistry,
pub(crate) listeners: Vec<Arc<dyn EventListener>>,
pub(crate) max_iterations: usize,
pub(crate) executor: Arc<dyn TurnExecutor>,
pub(crate) pre_tool_hooks: Vec<Arc<dyn PreToolUseHook>>,
pub(crate) post_tool_hooks: Vec<Arc<dyn PostToolExecHook>>,
pub(crate) budget_checker: Option<Arc<dyn BudgetChecker>>,
pub(crate) context_assembler: Arc<dyn ContextAssembler>,
pub(crate) extensions: Extensions,
}
#[derive(Clone)]
pub struct Agent {
pub(crate) inner: Arc<AgentInner>,
}
impl std::fmt::Debug for Agent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Agent")
.field("name", &self.inner.name)
.field("model", &self.inner.model)
.field("capabilities", &self.inner.capabilities.len())
.finish_non_exhaustive()
}
}
impl Agent {
pub fn builder() -> AgentBuilder {
AgentBuilder::new()
}
pub fn name(&self) -> &str {
&self.inner.name
}
pub fn system_prompt(&self) -> &str {
&self.inner.system_prompt
}
pub fn model(&self) -> &ModelSpec {
&self.inner.model
}
pub fn capabilities(&self) -> &[Arc<dyn Capability>] {
&self.inner.capabilities
}
pub fn drivers(&self) -> &DriverRegistry {
&self.inner.drivers
}
pub fn driver_for_model(&self) -> Option<Arc<dyn ChatDriver>> {
self.inner.drivers.get(&self.inner.model.driver)
}
pub fn max_iterations(&self) -> usize {
self.inner.max_iterations
}
pub fn pre_tool_hooks(&self) -> &[Arc<dyn PreToolUseHook>] {
&self.inner.pre_tool_hooks
}
pub fn post_tool_hooks(&self) -> &[Arc<dyn PostToolExecHook>] {
&self.inner.post_tool_hooks
}
pub fn budget_checker(&self) -> Option<&Arc<dyn BudgetChecker>> {
self.inner.budget_checker.as_ref()
}
pub fn context_assembler(&self) -> &Arc<dyn ContextAssembler> {
&self.inner.context_assembler
}
pub fn extensions(&self) -> &Extensions {
&self.inner.extensions
}
pub fn session(&self) -> Session {
self.session_with_log(Arc::new(InMemoryEventLog::new()))
}
pub fn session_with_log(&self, log: Arc<dyn EventLog>) -> Session {
Session::new(self.clone(), log)
}
pub async fn resume_session(
&self,
log: Arc<dyn EventLog>,
session_id: SessionId,
) -> Result<Session> {
Session::resume(self.clone(), log, session_id).await
}
pub async fn run(&self, input: impl Into<String>) -> Result<TurnResult> {
self.session().run(input).await
}
}
struct AdHocTools {
tools: Vec<Arc<dyn Tool>>,
}
#[async_trait]
impl Capability for AdHocTools {
fn id(&self) -> &str {
"tools"
}
fn description(&self) -> &str {
"Tools attached directly to the agent."
}
async fn tools(&self) -> Result<Vec<Arc<dyn Tool>>> {
Ok(self.tools.clone())
}
}
pub struct AgentBuilder {
name: String,
system_prompt: String,
model: Option<ModelSpec>,
capabilities: Vec<Arc<dyn Capability>>,
tools: Vec<Arc<dyn Tool>>,
drivers: DriverRegistry,
listeners: Vec<Arc<dyn EventListener>>,
max_iterations: usize,
executor: Arc<dyn TurnExecutor>,
pre_tool_hooks: Vec<Arc<dyn PreToolUseHook>>,
post_tool_hooks: Vec<Arc<dyn PostToolExecHook>>,
budget_checker: Option<Arc<dyn BudgetChecker>>,
context_assembler: Arc<dyn ContextAssembler>,
extensions: Extensions,
}
impl AgentBuilder {
pub fn new() -> Self {
Self {
name: "agent".to_string(),
system_prompt: String::new(),
model: None,
capabilities: Vec::new(),
tools: Vec::new(),
drivers: DriverRegistry::new(),
listeners: Vec::new(),
max_iterations: 16,
executor: Arc::new(InProcessExecutor),
pre_tool_hooks: Vec::new(),
post_tool_hooks: Vec::new(),
budget_checker: None,
context_assembler: Arc::new(PassthroughContextAssembler),
extensions: Extensions::new(),
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = prompt.into();
self
}
pub fn model(mut self, model: ModelSpec) -> Self {
self.model = Some(model);
self
}
pub fn capability(mut self, capability: impl Capability + 'static) -> Self {
self.capabilities.push(Arc::new(capability));
self
}
pub fn capability_arc(mut self, capability: Arc<dyn Capability>) -> Self {
self.capabilities.push(capability);
self
}
pub fn tool(mut self, tool: impl Tool + 'static) -> Self {
self.tools.push(Arc::new(tool));
self
}
pub fn driver(mut self, driver: impl ChatDriver + 'static) -> Self {
self.drivers.register(driver);
self
}
pub fn listener(mut self, listener: impl EventListener + 'static) -> Self {
self.listeners.push(Arc::new(listener));
self
}
pub fn listener_arc(mut self, listener: Arc<dyn EventListener>) -> Self {
self.listeners.push(listener);
self
}
pub fn max_iterations(mut self, max_iterations: usize) -> Self {
self.max_iterations = max_iterations;
self
}
pub fn executor(mut self, executor: impl TurnExecutor + 'static) -> Self {
self.executor = Arc::new(executor);
self
}
pub fn pre_tool_hook(mut self, hook: impl PreToolUseHook + 'static) -> Self {
self.pre_tool_hooks.push(Arc::new(hook));
self
}
pub fn post_tool_hook(mut self, hook: impl PostToolExecHook + 'static) -> Self {
self.post_tool_hooks.push(Arc::new(hook));
self
}
pub fn budget_checker(mut self, checker: impl BudgetChecker + 'static) -> Self {
self.budget_checker = Some(Arc::new(checker));
self
}
pub fn context_assembler(mut self, assembler: impl ContextAssembler + 'static) -> Self {
self.context_assembler = Arc::new(assembler);
self
}
pub fn extension<T: Send + Sync + 'static>(mut self, value: T) -> Self {
self.extensions.insert(value);
self
}
pub fn build(self) -> Result<Agent> {
let model = self
.model
.ok_or_else(|| Error::InvalidAgent("a model is required — call .model(...)".into()))?;
let mut capabilities = self.capabilities;
if !self.tools.is_empty() {
capabilities.push(Arc::new(AdHocTools { tools: self.tools }));
}
#[allow(unused_mut)]
let mut drivers = self.drivers;
#[cfg(feature = "http")]
{
use agentyk_core::driver::DriverId;
if !drivers.contains(&DriverId::openai()) {
drivers.register(crate::drivers::openai::OpenAiDriver::new());
}
if !drivers.contains(&DriverId::anthropic()) {
drivers.register(crate::drivers::anthropic::AnthropicDriver::new());
}
}
if !drivers.contains(&model.driver) {
return Err(Error::UnknownDriver(model.driver.to_string()));
}
Ok(Agent {
inner: Arc::new(AgentInner {
name: self.name,
system_prompt: self.system_prompt,
model,
capabilities,
drivers,
listeners: self.listeners,
max_iterations: self.max_iterations,
executor: self.executor,
pre_tool_hooks: self.pre_tool_hooks,
post_tool_hooks: self.post_tool_hooks,
budget_checker: self.budget_checker,
context_assembler: self.context_assembler,
extensions: self.extensions,
}),
})
}
}
impl Default for AgentBuilder {
fn default() -> Self {
Self::new()
}
}