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};
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        // Only create new Arc if aliases changed
161        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}