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