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