claude_agent/agent/options/
builder.rs1use std::path::PathBuf;
27use std::sync::Arc;
28use std::time::Duration;
29
30use rust_decimal::Decimal;
31
32use crate::auth::{Credential, OAuthConfig};
33use crate::budget::TenantBudgetManager;
34use crate::client::{CloudProvider, FallbackConfig, ModelConfig, ProviderConfig};
35use crate::common::IndexRegistry;
36use crate::context::{LeveledMemoryProvider, RuleIndex};
37use crate::hooks::{Hook, HookManager};
38use crate::output_style::OutputStyle;
39use crate::permissions::{PermissionMode, PermissionPolicy, PermissionRule};
40use crate::skills::SkillIndex;
41use crate::subagents::{SubagentIndex, builtin_subagents};
42use crate::tools::{Tool, ToolAccess};
43
44use crate::agent::config::{AgentConfig, CacheConfig, SystemPromptMode};
45
46pub const DEFAULT_COMPACT_KEEP_MESSAGES: usize = 4;
48
49#[derive(Default)]
53pub struct AgentBuilder {
54 pub(super) config: AgentConfig,
55 pub(super) credential: Option<Credential>,
56 pub(super) auth_type: Option<crate::auth::Auth>,
57 pub(super) oauth_config: Option<OAuthConfig>,
58 pub(super) cloud_provider: Option<CloudProvider>,
59 pub(super) model_config: Option<ModelConfig>,
60 pub(super) provider_config: Option<ProviderConfig>,
61 pub(super) skill_registry: Option<IndexRegistry<SkillIndex>>,
62 pub(super) subagent_registry: Option<IndexRegistry<SubagentIndex>>,
63 pub(super) rule_indices: Vec<RuleIndex>,
64 pub(super) hooks: HookManager,
65 pub(super) custom_tools: Vec<Arc<dyn Tool>>,
66 pub(super) memory_provider: Option<LeveledMemoryProvider>,
67 pub(super) sandbox_settings: Option<crate::config::SandboxSettings>,
68 pub(super) initial_messages: Option<Vec<crate::types::Message>>,
69 pub(super) resume_session_id: Option<String>,
70 pub(super) resumed_session: Option<crate::session::Session>,
71 pub(super) tenant_budget_manager: Option<TenantBudgetManager>,
72 pub(super) fallback_config: Option<FallbackConfig>,
73 pub(super) output_style_name: Option<String>,
74 pub(super) mcp_configs: std::collections::HashMap<String, crate::mcp::McpServerConfig>,
75 pub(super) mcp_manager: Option<std::sync::Arc<crate::mcp::McpManager>>,
76 pub(super) mcp_toolset_registry: Option<crate::mcp::McpToolsetRegistry>,
77 pub(super) tool_search_config: Option<crate::tools::ToolSearchConfig>,
78 pub(super) tool_search_manager: Option<std::sync::Arc<crate::tools::ToolSearchManager>>,
79 pub(super) session_manager: Option<crate::session::SessionManager>,
80
81 pub(super) load_enterprise: bool,
84 pub(super) load_user: bool,
85 pub(super) load_project: bool,
86 pub(super) load_local: bool,
87
88 #[cfg(feature = "aws")]
89 pub(super) aws_region: Option<String>,
90 #[cfg(feature = "gcp")]
91 pub(super) gcp_project: Option<String>,
92 #[cfg(feature = "gcp")]
93 pub(super) gcp_region: Option<String>,
94 #[cfg(feature = "azure")]
95 pub(super) azure_resource: Option<String>,
96
97 #[cfg(feature = "plugins")]
98 pub(super) plugin_dirs: Vec<PathBuf>,
99}
100
101impl AgentBuilder {
102 pub fn new() -> Self {
104 Self::default()
105 }
106
107 pub fn agent_config(mut self, config: AgentConfig) -> Self {
113 self.config = config;
114 self
115 }
116
117 pub fn provider_config(mut self, config: ProviderConfig) -> Self {
119 self.provider_config = Some(config);
120 self
121 }
122
123 pub async fn auth(mut self, auth: impl Into<crate::auth::Auth>) -> crate::Result<Self> {
147 let auth = auth.into();
148
149 #[allow(unreachable_patterns)]
150 match &auth {
151 #[cfg(feature = "aws")]
152 crate::auth::Auth::Bedrock { region } => {
153 self.cloud_provider = Some(CloudProvider::Bedrock);
154 self.aws_region = Some(region.clone());
155 self.model_config = Some(ModelConfig::bedrock());
156 self = self.apply_provider_models();
157 }
158 #[cfg(feature = "gcp")]
159 crate::auth::Auth::Vertex { project, region } => {
160 self.cloud_provider = Some(CloudProvider::Vertex);
161 self.gcp_project = Some(project.clone());
162 self.gcp_region = Some(region.clone());
163 self.model_config = Some(ModelConfig::vertex());
164 self = self.apply_provider_models();
165 }
166 #[cfg(feature = "azure")]
167 crate::auth::Auth::Foundry { resource } => {
168 self.cloud_provider = Some(CloudProvider::Foundry);
169 self.azure_resource = Some(resource.clone());
170 self.model_config = Some(ModelConfig::foundry());
171 self = self.apply_provider_models();
172 }
173 _ => {}
174 }
175
176 let credential = auth.resolve().await?;
177 if !credential.is_placeholder() {
178 self.credential = Some(credential);
179 }
180
181 self.auth_type = Some(auth);
182
183 if self.supports_server_tools() {
184 self.config.server_tools = crate::agent::config::ServerToolsConfig::all();
185 }
186
187 Ok(self)
188 }
189
190 pub fn oauth_config(mut self, config: OAuthConfig) -> Self {
192 self.oauth_config = Some(config);
193 self
194 }
195
196 pub fn supports_server_tools(&self) -> bool {
200 let auth_supports = self
201 .auth_type
202 .as_ref()
203 .map(|a| a.supports_server_tools())
204 .unwrap_or(true);
205
206 let access_allows = self.config.security.tool_access.is_allowed("WebSearch")
207 || self.config.security.tool_access.is_allowed("WebFetch");
208
209 auth_supports && access_allows
210 }
211
212 pub fn models(mut self, config: ModelConfig) -> Self {
218 self.model_config = Some(config.clone());
219 self.config.model.primary = config.primary;
220 self.config.model.small = config.small;
221 self
222 }
223
224 #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
225 fn apply_provider_models(mut self) -> Self {
226 if let Some(ref config) = self.model_config {
227 if self.config.model.primary
228 == crate::agent::config::AgentModelConfig::default().primary
229 {
230 self.config.model.primary = config.primary.clone();
231 }
232 if self.config.model.small == crate::agent::config::AgentModelConfig::default().small {
233 self.config.model.small = config.small.clone();
234 }
235 }
236 self
237 }
238
239 pub fn model(mut self, model: impl Into<String>) -> Self {
243 self.config.model.primary = model.into();
244 self
245 }
246
247 pub fn small_model(mut self, model: impl Into<String>) -> Self {
251 self.config.model.small = model.into();
252 self
253 }
254
255 pub fn max_tokens(mut self, tokens: u32) -> Self {
260 self.config.model.max_tokens = tokens;
261 self
262 }
263
264 pub fn extended_context(mut self, enabled: bool) -> Self {
269 self.config.model.extended_context = enabled;
270 self
271 }
272
273 pub fn tools(mut self, access: ToolAccess) -> Self {
285 self.config.security.tool_access = access;
286 self
287 }
288
289 pub fn tool<T: Tool + 'static>(mut self, tool: T) -> Self {
291 self.custom_tools.push(Arc::new(tool));
292 self
293 }
294
295 pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
301 self.config.working_dir = Some(path.into());
302 self
303 }
304
305 pub fn max_iterations(mut self, max: usize) -> Self {
309 self.config.execution.max_iterations = max;
310 self
311 }
312
313 pub fn timeout(mut self, timeout: Duration) -> Self {
317 self.config.execution.timeout = Some(timeout);
318 self
319 }
320
321 pub fn chunk_timeout(mut self, timeout: Duration) -> Self {
331 self.config.execution.chunk_timeout = timeout;
332 self
333 }
334
335 pub fn auto_compact(mut self, enabled: bool) -> Self {
339 self.config.execution.auto_compact = enabled;
340 self
341 }
342
343 pub fn compact_keep_messages(mut self, count: usize) -> Self {
347 self.config.execution.compact_keep_messages = count;
348 self
349 }
350
351 pub fn cache(mut self, config: CacheConfig) -> Self {
363 self.config.cache = config;
364 self
365 }
366
367 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
373 self.config.prompt.system_prompt = Some(prompt.into());
374 self
375 }
376
377 pub fn system_prompt_mode(mut self, mode: SystemPromptMode) -> Self {
379 self.config.prompt.system_prompt_mode = mode;
380 self
381 }
382
383 pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
385 self.config.prompt.system_prompt_mode = SystemPromptMode::Append;
386 self.config.prompt.system_prompt = Some(prompt.into());
387 self
388 }
389
390 pub fn output_style(mut self, style: OutputStyle) -> Self {
392 self.config.prompt.output_style = Some(style);
393 self
394 }
395
396 pub fn output_style_name(mut self, name: impl Into<String>) -> Self {
398 self.output_style_name = Some(name.into());
399 self
400 }
401
402 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
404 self.config.prompt.output_schema = Some(schema);
405 self
406 }
407
408 pub fn structured_output<T: schemars::JsonSchema>(mut self) -> Self {
410 let schema = schemars::schema_for!(T);
411 self.config.prompt.output_schema = serde_json::to_value(schema).ok();
412 self
413 }
414
415 pub fn permission_policy(mut self, policy: PermissionPolicy) -> Self {
421 self.config.security.permission_policy = policy;
422 self
423 }
424
425 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
427 self.config.security.permission_policy.mode = mode;
428 self
429 }
430
431 pub fn allow_tool(mut self, pattern: impl Into<String>) -> Self {
433 self.config
434 .security
435 .permission_policy
436 .rules
437 .push(PermissionRule::allow_pattern(pattern));
438 self
439 }
440
441 pub fn deny_tool(mut self, pattern: impl Into<String>) -> Self {
443 self.config
444 .security
445 .permission_policy
446 .rules
447 .push(PermissionRule::deny_pattern(pattern));
448 self
449 }
450
451 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
457 self.config.security.env.insert(key.into(), value.into());
458 self
459 }
460
461 pub fn envs(
463 mut self,
464 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
465 ) -> Self {
466 for (k, v) in vars {
467 self.config.security.env.insert(k.into(), v.into());
468 }
469 self
470 }
471
472 pub fn allow_domain(mut self, domain: impl Into<String>) -> Self {
478 self.sandbox_settings
479 .get_or_insert_with(crate::config::SandboxSettings::default)
480 .network
481 .allowed_domains
482 .insert(domain.into());
483 self
484 }
485
486 pub fn deny_domain(mut self, domain: impl Into<String>) -> Self {
488 self.sandbox_settings
489 .get_or_insert_with(crate::config::SandboxSettings::default)
490 .network
491 .blocked_domains
492 .insert(domain.into());
493 self
494 }
495
496 pub fn sandbox_enabled(mut self, enabled: bool) -> Self {
498 self.sandbox_settings
499 .get_or_insert_with(crate::config::SandboxSettings::default)
500 .enabled = enabled;
501 self
502 }
503
504 pub fn exclude_command(mut self, command: impl Into<String>) -> Self {
506 self.sandbox_settings
507 .get_or_insert_with(crate::config::SandboxSettings::default)
508 .excluded_commands
509 .push(command.into());
510 self
511 }
512
513 pub fn max_budget_usd(mut self, amount: Decimal) -> Self {
519 self.config.budget.max_cost_usd = Some(amount);
520 self
521 }
522
523 pub fn tenant_id(mut self, id: impl Into<String>) -> Self {
525 self.config.budget.tenant_id = Some(id.into());
526 self
527 }
528
529 pub fn tenant_budget_manager(mut self, manager: TenantBudgetManager) -> Self {
531 self.tenant_budget_manager = Some(manager);
532 self
533 }
534
535 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
537 self.config.budget.fallback_model = Some(model.into());
538 self
539 }
540
541 pub fn fallback(mut self, config: FallbackConfig) -> Self {
543 self.fallback_config = Some(config);
544 self
545 }
546
547 pub fn session_manager(mut self, manager: crate::session::SessionManager) -> Self {
553 self.session_manager = Some(manager);
554 self
555 }
556
557 pub async fn fork_session(mut self, session_id: impl Into<String>) -> crate::Result<Self> {
559 let manager = self.session_manager.take().unwrap_or_default();
560 let session_id_str: String = session_id.into();
561 let original_id = crate::session::SessionId::from(session_id_str);
562 let forked = manager
563 .fork(&original_id)
564 .await
565 .map_err(|e| crate::Error::Session(e.to_string()))?;
566
567 self.initial_messages = Some(forked.to_api_messages());
568 self.resume_session_id = Some(forked.id.to_string());
569 self.session_manager = Some(manager);
570 Ok(self)
571 }
572
573 pub async fn resume_session(mut self, session_id: impl Into<String>) -> crate::Result<Self> {
575 let session_id_str: String = session_id.into();
576 let id = crate::session::SessionId::from(session_id_str);
577 let manager = self.session_manager.take().unwrap_or_default();
578 let session = manager.get(&id).await?;
579
580 let messages: Vec<crate::types::Message> = session
581 .messages
582 .iter()
583 .map(|m| crate::types::Message {
584 role: m.role,
585 content: m.content.clone(),
586 })
587 .collect();
588
589 self.initial_messages = Some(messages);
590 self.resume_session_id = Some(id.to_string());
591 self.resumed_session = Some(session);
592 self.session_manager = Some(manager);
593 Ok(self)
594 }
595
596 pub fn messages(mut self, messages: Vec<crate::types::Message>) -> Self {
598 self.initial_messages = Some(messages);
599 self
600 }
601
602 pub fn mcp_server(
608 mut self,
609 name: impl Into<String>,
610 config: crate::mcp::McpServerConfig,
611 ) -> Self {
612 self.mcp_configs.insert(name.into(), config);
613 self
614 }
615
616 pub fn mcp_stdio(
618 mut self,
619 name: impl Into<String>,
620 command: impl Into<String>,
621 args: Vec<String>,
622 ) -> Self {
623 self.mcp_configs.insert(
624 name.into(),
625 crate::mcp::McpServerConfig::Stdio {
626 command: command.into(),
627 args,
628 env: std::collections::HashMap::new(),
629 cwd: None,
630 },
631 );
632 self
633 }
634
635 pub fn mcp_manager(mut self, manager: crate::mcp::McpManager) -> Self {
637 self.mcp_manager = Some(std::sync::Arc::new(manager));
638 self
639 }
640
641 pub fn shared_mcp_manager(mut self, manager: std::sync::Arc<crate::mcp::McpManager>) -> Self {
643 self.mcp_manager = Some(manager);
644 self
645 }
646
647 pub fn mcp_toolset(mut self, toolset: crate::mcp::McpToolset) -> Self {
649 self.mcp_toolset_registry
650 .get_or_insert_with(crate::mcp::McpToolsetRegistry::new)
651 .register(toolset);
652 self
653 }
654
655 pub fn tool_search(mut self) -> Self {
661 self.tool_search_config = Some(crate::tools::ToolSearchConfig::default());
662 self
663 }
664
665 pub fn tool_search_config(mut self, config: crate::tools::ToolSearchConfig) -> Self {
667 self.tool_search_config = Some(config);
668 self
669 }
670
671 pub fn tool_search_threshold(mut self, threshold: f64) -> Self {
673 let config = self
674 .tool_search_config
675 .get_or_insert_with(crate::tools::ToolSearchConfig::default);
676 config.threshold = threshold.clamp(0.0, 1.0);
677 self
678 }
679
680 pub fn tool_search_mode(mut self, mode: crate::tools::SearchMode) -> Self {
682 let config = self
683 .tool_search_config
684 .get_or_insert_with(crate::tools::ToolSearchConfig::default);
685 config.search_mode = mode;
686 self
687 }
688
689 pub fn always_load_tools(mut self, tools: impl IntoIterator<Item = impl Into<String>>) -> Self {
691 let config = self
692 .tool_search_config
693 .get_or_insert_with(crate::tools::ToolSearchConfig::default);
694 config.always_load = tools.into_iter().map(Into::into).collect();
695 self
696 }
697
698 pub fn shared_tool_search_manager(
700 mut self,
701 manager: std::sync::Arc<crate::tools::ToolSearchManager>,
702 ) -> Self {
703 self.tool_search_manager = Some(manager);
704 self
705 }
706
707 pub fn skill_registry(mut self, registry: IndexRegistry<SkillIndex>) -> Self {
713 self.skill_registry = Some(registry);
714 self
715 }
716
717 pub fn skill(mut self, skill: SkillIndex) -> Self {
719 self.skill_registry
720 .get_or_insert_with(IndexRegistry::new)
721 .register(skill);
722 self
723 }
724
725 pub fn rule_index(mut self, index: RuleIndex) -> Self {
727 self.rule_indices.push(index);
728 self
729 }
730
731 pub fn memory_content(mut self, content: impl Into<String>) -> Self {
733 self.memory_provider
734 .get_or_insert_with(LeveledMemoryProvider::new)
735 .add_content(content);
736 self
737 }
738
739 pub fn local_memory_content(mut self, content: impl Into<String>) -> Self {
741 self.memory_provider
742 .get_or_insert_with(LeveledMemoryProvider::new)
743 .add_local_content(content);
744 self
745 }
746
747 pub fn subagent_registry(mut self, registry: IndexRegistry<SubagentIndex>) -> Self {
753 self.subagent_registry = Some(registry);
754 self
755 }
756
757 pub fn subagent(mut self, subagent: SubagentIndex) -> Self {
759 self.subagent_registry
760 .get_or_insert_with(|| {
761 let mut registry = IndexRegistry::new();
762 registry.register_all(builtin_subagents());
763 registry
764 })
765 .register(subagent);
766 self
767 }
768
769 #[cfg(feature = "plugins")]
779 pub fn plugin_dir(mut self, dir: impl Into<PathBuf>) -> Self {
780 self.plugin_dirs.push(dir.into());
781 self
782 }
783
784 #[cfg(feature = "plugins")]
786 pub fn plugin_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
787 self.plugin_dirs.extend(dirs.into_iter().map(Into::into));
788 self
789 }
790
791 pub fn hook<H: Hook + 'static>(mut self, hook: H) -> Self {
797 self.hooks.register(hook);
798 self
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805 use crate::client::DEFAULT_MAX_TOKENS;
806
807 #[test]
808 fn test_tool_access() {
809 assert!(ToolAccess::all().is_allowed("Read"));
810 assert!(!ToolAccess::none().is_allowed("Read"));
811 assert!(ToolAccess::only(["Read", "Write"]).is_allowed("Read"));
812 assert!(!ToolAccess::only(["Read", "Write"]).is_allowed("Bash"));
813 assert!(!ToolAccess::except(["Bash"]).is_allowed("Bash"));
814 assert!(ToolAccess::except(["Bash"]).is_allowed("Read"));
815 }
816
817 #[test]
818 fn test_max_tokens_default() {
819 let builder = AgentBuilder::new();
820 assert_eq!(builder.config.model.max_tokens, DEFAULT_MAX_TOKENS);
821 }
822
823 #[test]
824 fn test_max_tokens_custom() {
825 let builder = AgentBuilder::new().max_tokens(16384);
826 assert_eq!(builder.config.model.max_tokens, 16384);
827 }
828}