claude_agent/agent/
executor.rs

1//! Agent core structure and construction.
2
3use 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        // Only create new Arc if aliases changed
163        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}