Skip to main content

agent_base/engine/
builder.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use crate::llm::{LlmClient, ReasoningConfig};
5use crate::tool::{Tool, ToolPolicy, ToolRegistry};
6use crate::types::{
7    AgentConfig, AtomicU64SessionIdGenerator, ResponseFormat, RetryConfig, SessionIdGenerator,
8};
9
10use super::AgentRuntime;
11use super::approval::ApprovalHandler;
12use super::context::ContextWindowManager;
13use super::middleware::{Middleware, MiddlewareRef};
14use super::recovery::{StopOnError, ToolErrorRecovery};
15use super::session_store::{InMemorySessionStore, SessionStore};
16
17pub struct AgentBuilder {
18    client: Arc<dyn LlmClient>,
19    config: AgentConfig,
20    tools: ToolRegistry,
21    tool_names: HashSet<String>,
22    approval_handler: Option<Arc<dyn ApprovalHandler>>,
23    tool_policy: Option<Arc<dyn ToolPolicy>>,
24    middlewares: Vec<MiddlewareRef>,
25    context_manager: Option<ContextWindowManager>,
26    session_store: Option<Arc<dyn SessionStore>>,
27    error_recovery: Option<Arc<dyn ToolErrorRecovery>>,
28    event_bus_capacity: usize,
29    session_id_generator: Option<Arc<dyn SessionIdGenerator>>,
30}
31
32impl AgentBuilder {
33    pub fn new(client: Arc<dyn LlmClient>) -> Self {
34        Self {
35            client,
36            config: AgentConfig::default(),
37            tools: ToolRegistry::default(),
38            tool_names: HashSet::new(),
39            approval_handler: None,
40            tool_policy: None,
41            middlewares: Vec::new(),
42            context_manager: None,
43            session_store: None,
44            error_recovery: None,
45            event_bus_capacity: 2048,
46            session_id_generator: None,
47        }
48    }
49
50    pub fn event_bus_capacity(mut self, capacity: usize) -> Self {
51        self.event_bus_capacity = capacity;
52        self
53    }
54
55    pub fn session_id_generator(mut self, generator: Arc<dyn SessionIdGenerator>) -> Self {
56        self.session_id_generator = Some(generator);
57        self
58    }
59
60    pub fn system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
61        self.config.system_prompt = Some(system_prompt.into());
62        self
63    }
64
65    /// Set whether to include the reasoning content in LLM responses.
66    ///
67    /// Controls whether the `reasoning_content` field from the LLM is forwarded
68    /// to consumers (i.e., "show the thinking process").
69    /// See [`AgentConfig::enable_thought`] for the distinction from `enable_thinking()`.
70    pub fn enable_thought(mut self, enable: bool) -> Self {
71        self.config.enable_thought = enable;
72        self
73    }
74
75    pub fn reasoning(mut self, config: ReasoningConfig) -> Self {
76        self.config.reasoning = Some(config);
77        self
78    }
79
80    /// Set whether to enable the model's extended thinking / reasoning mode.
81    ///
82    /// Controls whether the model performs deep reasoning (i.e., "enable thinking mode").
83    /// See [`AgentConfig::enable_thought`] for the distinction from `enable_thought()`.
84    pub fn enable_thinking(mut self, enable: bool) -> Self {
85        let mut config = self.config.reasoning.take().unwrap_or_default();
86        config.enabled = Some(enable);
87        self.config.reasoning = Some(config);
88        self
89    }
90
91    pub fn thinking_budget(mut self, budget: u64) -> Self {
92        let mut config = self.config.reasoning.take().unwrap_or_default();
93        config.budget_tokens = Some(budget);
94        self.config.reasoning = Some(config);
95        self
96    }
97
98    pub fn tool_timeout(mut self, timeout_ms: u64) -> Self {
99        self.config.tool.tool_timeout_ms = Some(timeout_ms);
100        self
101    }
102
103    pub fn max_tool_output_chars(mut self, max_chars: usize) -> Self {
104        self.config.tool.max_tool_output_chars = Some(max_chars);
105        self
106    }
107
108    pub fn register_tool(mut self, tool: impl Tool + 'static) -> Self {
109        self.tools.register(tool);
110        self
111    }
112
113    pub fn register_tool_arc(mut self, tool: Arc<dyn Tool>) -> Self {
114        self.tools.register_arc(tool);
115        self
116    }
117
118    pub fn approval_handler(mut self, handler: Arc<dyn ApprovalHandler>) -> Self {
119        self.approval_handler = Some(handler);
120        self
121    }
122
123    pub fn tool_policy(mut self, policy: Arc<dyn ToolPolicy>) -> Self {
124        self.tool_policy = Some(policy);
125        self
126    }
127
128    pub fn middleware(mut self, mw: impl Middleware + 'static) -> Self {
129        self.middlewares.push(Arc::new(mw));
130        self
131    }
132
133    pub fn context_window(mut self, max_tokens: usize) -> Self {
134        self.context_manager = Some(ContextWindowManager::new(max_tokens));
135        self
136    }
137
138    pub fn context_window_manager(mut self, manager: ContextWindowManager) -> Self {
139        self.context_manager = Some(manager);
140        self
141    }
142
143    pub fn response_format(mut self, format: ResponseFormat) -> Self {
144        self.config.llm.response_format = Some(format);
145        self
146    }
147
148    pub fn llm_retry(mut self, retry: RetryConfig) -> Self {
149        self.config.llm.llm_retry = Some(retry);
150        self
151    }
152
153    pub fn session_store(mut self, store: Arc<dyn SessionStore>) -> Self {
154        self.session_store = Some(store);
155        self
156    }
157
158    pub fn error_recovery(mut self, recovery: Arc<dyn ToolErrorRecovery>) -> Self {
159        self.error_recovery = Some(recovery);
160        self
161    }
162
163    pub fn max_sessions(mut self, max: usize) -> Self {
164        self.config.session.max_sessions = Some(max);
165        self
166    }
167
168    pub fn max_turns_per_session(mut self, max: usize) -> Self {
169        self.config.session.max_turns_per_session = Some(max);
170        self
171    }
172
173    pub fn max_message_tokens(mut self, max: usize) -> Self {
174        self.config.session.max_message_tokens = Some(max);
175        self
176    }
177
178    pub fn tool_error_retry_prompt(mut self, prompt: impl Into<String>) -> Self {
179        self.config.tool.tool_error_retry_prompt = Some(prompt.into());
180        self
181    }
182
183    pub fn language(mut self, language: crate::types::Language) -> Self {
184        self.config.language = language;
185        self
186    }
187
188    /// Conditionally chain a builder call: apply `f` only when `value` is `Some`.
189    ///
190    /// # Example
191    /// ```ignore
192    /// let builder = AgentBuilder::new(client)
193    ///     .apply_if(config.timeout, |b, t| b.tool_timeout(t))
194    ///     .apply_if(config.max_chars, |b, c| b.max_tool_output_chars(c));
195    /// ```
196    pub fn apply_if<T>(self, value: Option<T>, f: impl FnOnce(Self, T) -> Self) -> Self {
197        match value {
198            Some(v) => f(self, v),
199            None => self,
200        }
201    }
202
203    pub fn build(self) -> crate::types::AgentResult<AgentRuntime> {
204        self.config.validate()?;
205
206        tracing::info!(
207            tool_count = self.tools.len(),
208            middleware_count = self.middlewares.len(),
209            has_approval = self.approval_handler.is_some(),
210            has_context_window = self.context_manager.is_some(),
211            "building agent runtime"
212        );
213
214        let event_bus = super::runtime::EventBus::new(self.event_bus_capacity);
215
216        // Inject EventBus into framework-provided tools that need it
217        // (e.g., UpdatePlanTool via FrameworkTool::set_event_bus)
218        self.tools.inject_event_bus(&event_bus);
219
220        let session_store = self
221            .session_store
222            .unwrap_or_else(|| Arc::new(InMemorySessionStore::new()));
223        let error_recovery = self.error_recovery.unwrap_or_else(|| Arc::new(StopOnError));
224        let session_id_generator = self
225            .session_id_generator
226            .unwrap_or_else(|| Arc::new(AtomicU64SessionIdGenerator::default()));
227
228        let session_manager = super::runtime::SessionManager::new(
229            session_id_generator,
230            session_store,
231            self.config.session.clone(),
232        );
233
234        let llm_engine = super::runtime::LlmEngine::new(self.client.clone(), event_bus.clone());
235
236        let tool_engine = super::runtime::ToolEngine::new(
237            self.tools,
238            self.approval_handler,
239            self.tool_policy,
240            error_recovery,
241            event_bus.clone(),
242        );
243
244        let runner = Arc::new(super::runtime::RuntimeCore::new(
245            self.config,
246            llm_engine,
247            tool_engine,
248            session_manager,
249            event_bus,
250            self.context_manager,
251            self.middlewares,
252        ));
253
254        Ok(AgentRuntime { runner })
255    }
256}