1use std::sync::Arc;
4
5use tokio::sync::RwLock;
6
7use super::config::AgentConfig;
8use crate::Client;
9use crate::budget::{BudgetTracker, TenantBudget};
10use crate::context::PromptOrchestrator;
11use crate::hooks::HookManager;
12use crate::session::ToolState;
13use crate::tools::{ToolRegistry, ToolRegistryBuilder, ToolSearchManager};
14use crate::types::Message;
15
16pub struct Agent {
17 pub(crate) client: Arc<Client>,
18 pub(crate) config: Arc<AgentConfig>,
19 pub(crate) tools: Arc<ToolRegistry>,
20 pub(crate) hooks: Arc<HookManager>,
21 pub(crate) session_id: Arc<str>,
22 pub(crate) state: ToolState,
23 pub(crate) orchestrator: Option<Arc<RwLock<PromptOrchestrator>>>,
24 pub(crate) initial_messages: Option<Vec<Message>>,
25 pub(crate) budget_tracker: Arc<BudgetTracker>,
26 pub(crate) tenant_budget: Option<Arc<TenantBudget>>,
27 pub(crate) mcp_manager: Option<Arc<crate::mcp::McpManager>>,
28 pub(crate) tool_search_manager: Option<Arc<ToolSearchManager>>,
29}
30
31impl Agent {
32 pub fn new(client: Client, config: AgentConfig) -> Self {
33 let tools = ToolRegistry::default_tools(
34 &config.security.tool_access,
35 config.working_dir.clone(),
36 Some(config.security.permission_policy.clone()),
37 );
38 Self::from_parts(
39 Arc::new(client),
40 Arc::new(config),
41 Arc::new(tools),
42 Arc::new(HookManager::new()),
43 None,
44 )
45 }
46
47 pub fn with_skills(
48 client: Client,
49 config: AgentConfig,
50 skill_executor: crate::skills::SkillExecutor,
51 ) -> Self {
52 let mut builder = ToolRegistryBuilder::new()
53 .access(&config.security.tool_access)
54 .skill_executor(skill_executor)
55 .policy(config.security.permission_policy.clone());
56
57 if let Some(dir) = config.working_dir.clone() {
58 builder = builder.working_dir(dir);
59 }
60
61 let tools = builder.build();
62 Self::from_parts(
63 Arc::new(client),
64 Arc::new(config),
65 Arc::new(tools),
66 Arc::new(HookManager::new()),
67 None,
68 )
69 }
70
71 pub fn with_components(
72 client: Client,
73 config: AgentConfig,
74 tools: ToolRegistry,
75 hooks: HookManager,
76 ) -> Self {
77 Self::from_parts(
78 Arc::new(client),
79 Arc::new(config),
80 Arc::new(tools),
81 Arc::new(hooks),
82 None,
83 )
84 }
85
86 pub fn with_orchestrator(
87 client: Client,
88 config: AgentConfig,
89 tools: Arc<ToolRegistry>,
90 hooks: HookManager,
91 orchestrator: PromptOrchestrator,
92 ) -> Self {
93 Self::from_parts(
94 Arc::new(client),
95 Arc::new(config),
96 tools,
97 Arc::new(hooks),
98 Some(Arc::new(RwLock::new(orchestrator))),
99 )
100 }
101
102 pub fn with_shared_tools(
103 client: Client,
104 config: AgentConfig,
105 tools: Arc<ToolRegistry>,
106 hooks: HookManager,
107 ) -> Self {
108 Self::from_parts(
109 Arc::new(client),
110 Arc::new(config),
111 tools,
112 Arc::new(hooks),
113 None,
114 )
115 }
116
117 fn from_parts(
118 client: Arc<Client>,
119 config: Arc<AgentConfig>,
120 tools: Arc<ToolRegistry>,
121 hooks: Arc<HookManager>,
122 orchestrator: Option<Arc<RwLock<PromptOrchestrator>>>,
123 ) -> Self {
124 let budget_tracker = match config.budget.max_cost_usd {
125 Some(max) => BudgetTracker::new(max),
126 None => BudgetTracker::unlimited(),
127 };
128
129 let config = Self::resolve_model_aliases(config, &client);
130
131 let state = tools
132 .tool_state()
133 .cloned()
134 .unwrap_or_else(|| ToolState::new(crate::session::SessionId::new()));
135 let session_id: Arc<str> = state.session_id().to_string().into();
136
137 Self {
138 client,
139 config,
140 tools,
141 hooks,
142 session_id,
143 state,
144 orchestrator,
145 initial_messages: None,
146 budget_tracker: Arc::new(budget_tracker),
147 tenant_budget: None,
148 mcp_manager: None,
149 tool_search_manager: None,
150 }
151 }
152
153 fn resolve_model_aliases(config: Arc<AgentConfig>, client: &Client) -> Arc<AgentConfig> {
154 let model_config = &client.config().models;
155
156 let primary = &config.model.primary;
157 let resolved_primary = model_config.resolve_alias(primary);
158
159 let small = &config.model.small;
160 let resolved_small = model_config.resolve_alias(small);
161
162 if resolved_primary != primary || resolved_small != small {
164 let mut new_config = (*config).clone();
165 if resolved_primary != primary {
166 tracing::debug!(
167 alias = %primary,
168 resolved = %resolved_primary,
169 "Resolved primary model alias"
170 );
171 new_config.model.primary = resolved_primary.to_string();
172 }
173 if resolved_small != small {
174 tracing::debug!(
175 alias = %small,
176 resolved = %resolved_small,
177 "Resolved small model alias"
178 );
179 new_config.model.small = resolved_small.to_string();
180 }
181 Arc::new(new_config)
182 } else {
183 config
184 }
185 }
186
187 pub fn with_tenant_budget(mut self, budget: Arc<TenantBudget>) -> Self {
188 self.tenant_budget = Some(budget);
189 self
190 }
191
192 pub fn with_mcp_manager(mut self, manager: Arc<crate::mcp::McpManager>) -> Self {
193 self.mcp_manager = Some(manager);
194 self
195 }
196
197 pub fn mcp_manager(&self) -> Option<&Arc<crate::mcp::McpManager>> {
198 self.mcp_manager.as_ref()
199 }
200
201 pub fn with_tool_search_manager(mut self, manager: Arc<ToolSearchManager>) -> Self {
202 self.tool_search_manager = Some(manager);
203 self
204 }
205
206 pub fn tool_search_manager(&self) -> Option<&Arc<ToolSearchManager>> {
207 self.tool_search_manager.as_ref()
208 }
209
210 pub fn with_initial_messages(mut self, messages: Vec<Message>) -> Self {
211 self.initial_messages = Some(messages);
212 self
213 }
214
215 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
216 self.session_id = id.into().into();
217 self
218 }
219
220 #[must_use]
221 pub fn builder() -> super::AgentBuilder {
222 super::AgentBuilder::new()
223 }
224
225 pub fn with_model(model: impl Into<String>) -> super::AgentBuilder {
226 super::AgentBuilder::new().model(model)
227 }
228
229 pub async fn default_agent() -> crate::Result<Self> {
230 Self::builder().build().await
231 }
232
233 #[must_use]
234 pub fn hooks(&self) -> &Arc<HookManager> {
235 &self.hooks
236 }
237
238 #[must_use]
239 pub fn session_id(&self) -> &str {
240 &self.session_id
241 }
242
243 #[must_use]
244 pub fn client(&self) -> &Arc<Client> {
245 &self.client
246 }
247
248 pub fn orchestrator(&self) -> Option<&Arc<RwLock<PromptOrchestrator>>> {
249 self.orchestrator.as_ref()
250 }
251
252 #[must_use]
253 pub fn config(&self) -> &AgentConfig {
254 &self.config
255 }
256
257 #[must_use]
258 pub fn tools(&self) -> &Arc<ToolRegistry> {
259 &self.tools
260 }
261
262 #[must_use]
263 pub fn state(&self) -> &ToolState {
264 &self.state
265 }
266}