claude_agent/agent/options/
builder.rs1use std::path::PathBuf;
27use std::sync::Arc;
28use std::time::Duration;
29
30use crate::auth::{Credential, OAuthConfig};
31use crate::budget::TenantBudgetManager;
32use crate::client::{CloudProvider, FallbackConfig, ModelConfig, ProviderConfig};
33use crate::common::IndexRegistry;
34use crate::context::{LeveledMemoryProvider, RuleIndex};
35use crate::hooks::{Hook, HookManager};
36use crate::output_style::OutputStyle;
37use crate::permissions::{PermissionMode, PermissionPolicy, PermissionRule};
38use crate::skills::SkillIndex;
39use crate::subagents::{SubagentIndex, builtin_subagents};
40use crate::tools::{Tool, ToolAccess};
41
42use crate::agent::config::{AgentConfig, SystemPromptMode};
43
44pub const DEFAULT_COMPACT_KEEP_MESSAGES: usize = 4;
46
47#[derive(Default)]
51pub struct AgentBuilder {
52 pub(super) config: AgentConfig,
53 pub(super) credential: Option<Credential>,
54 pub(super) auth_type: Option<crate::auth::Auth>,
55 pub(super) oauth_config: Option<OAuthConfig>,
56 pub(super) cloud_provider: Option<CloudProvider>,
57 pub(super) model_config: Option<ModelConfig>,
58 pub(super) provider_config: Option<ProviderConfig>,
59 pub(super) skill_registry: Option<IndexRegistry<SkillIndex>>,
60 pub(super) subagent_registry: Option<IndexRegistry<SubagentIndex>>,
61 pub(super) rule_indices: Vec<RuleIndex>,
62 pub(super) hooks: HookManager,
63 pub(super) custom_tools: Vec<Arc<dyn Tool>>,
64 pub(super) memory_provider: Option<LeveledMemoryProvider>,
65 pub(super) sandbox_settings: Option<crate::config::SandboxSettings>,
66 pub(super) initial_messages: Option<Vec<crate::types::Message>>,
67 pub(super) resume_session_id: Option<String>,
68 pub(super) resumed_session: Option<crate::session::Session>,
69 pub(super) tenant_budget_manager: Option<TenantBudgetManager>,
70 pub(super) fallback_config: Option<FallbackConfig>,
71 pub(super) output_style_name: Option<String>,
72 pub(super) mcp_configs: std::collections::HashMap<String, crate::mcp::McpServerConfig>,
73 pub(super) mcp_manager: Option<std::sync::Arc<crate::mcp::McpManager>>,
74 pub(super) mcp_toolset_registry: Option<crate::mcp::McpToolsetRegistry>,
75 pub(super) tool_search_config: Option<crate::tools::ToolSearchConfig>,
76 pub(super) tool_search_manager: Option<std::sync::Arc<crate::tools::ToolSearchManager>>,
77 pub(super) session_manager: Option<crate::session::SessionManager>,
78
79 pub(super) load_enterprise: bool,
82 pub(super) load_user: bool,
83 pub(super) load_project: bool,
84 pub(super) load_local: bool,
85
86 #[cfg(feature = "aws")]
87 pub(super) aws_region: Option<String>,
88 #[cfg(feature = "gcp")]
89 pub(super) gcp_project: Option<String>,
90 #[cfg(feature = "gcp")]
91 pub(super) gcp_region: Option<String>,
92 #[cfg(feature = "azure")]
93 pub(super) azure_resource: Option<String>,
94
95 #[cfg(feature = "plugins")]
96 pub(super) plugin_dirs: Vec<PathBuf>,
97}
98
99impl AgentBuilder {
100 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn agent_config(mut self, config: AgentConfig) -> Self {
111 self.config = config;
112 self
113 }
114
115 pub fn provider_config(mut self, config: ProviderConfig) -> Self {
117 self.provider_config = Some(config);
118 self
119 }
120
121 pub async fn auth(mut self, auth: impl Into<crate::auth::Auth>) -> crate::Result<Self> {
145 let auth = auth.into();
146
147 #[allow(unreachable_patterns)]
148 match &auth {
149 #[cfg(feature = "aws")]
150 crate::auth::Auth::Bedrock { region } => {
151 self.cloud_provider = Some(CloudProvider::Bedrock);
152 self.aws_region = Some(region.clone());
153 self.model_config = Some(ModelConfig::bedrock());
154 self = self.apply_provider_models();
155 }
156 #[cfg(feature = "gcp")]
157 crate::auth::Auth::Vertex { project, region } => {
158 self.cloud_provider = Some(CloudProvider::Vertex);
159 self.gcp_project = Some(project.clone());
160 self.gcp_region = Some(region.clone());
161 self.model_config = Some(ModelConfig::vertex());
162 self = self.apply_provider_models();
163 }
164 #[cfg(feature = "azure")]
165 crate::auth::Auth::Foundry { resource } => {
166 self.cloud_provider = Some(CloudProvider::Foundry);
167 self.azure_resource = Some(resource.clone());
168 self.model_config = Some(ModelConfig::foundry());
169 self = self.apply_provider_models();
170 }
171 _ => {}
172 }
173
174 let credential = auth.resolve().await?;
175 if !credential.is_default() {
176 self.credential = Some(credential);
177 }
178
179 self.auth_type = Some(auth);
180
181 if self.supports_server_tools() {
182 self.config.server_tools = crate::agent::config::ServerToolsConfig::all();
183 }
184
185 Ok(self)
186 }
187
188 pub fn oauth_config(mut self, config: OAuthConfig) -> Self {
190 self.oauth_config = Some(config);
191 self
192 }
193
194 pub fn supports_server_tools(&self) -> bool {
196 self.auth_type
197 .as_ref()
198 .map(|a| a.supports_server_tools())
199 .unwrap_or(true)
200 }
201
202 pub fn models(mut self, config: ModelConfig) -> Self {
208 self.model_config = Some(config.clone());
209 self.config.model.primary = config.primary;
210 self.config.model.small = config.small;
211 self
212 }
213
214 #[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
215 fn apply_provider_models(mut self) -> Self {
216 if let Some(ref config) = self.model_config {
217 if self.config.model.primary
218 == crate::agent::config::AgentModelConfig::default().primary
219 {
220 self.config.model.primary = config.primary.clone();
221 }
222 if self.config.model.small == crate::agent::config::AgentModelConfig::default().small {
223 self.config.model.small = config.small.clone();
224 }
225 }
226 self
227 }
228
229 pub fn model(mut self, model: impl Into<String>) -> Self {
233 self.config.model.primary = model.into();
234 self
235 }
236
237 pub fn small_model(mut self, model: impl Into<String>) -> Self {
241 self.config.model.small = model.into();
242 self
243 }
244
245 pub fn max_tokens(mut self, tokens: u32) -> Self {
250 self.config.model.max_tokens = tokens;
251 self
252 }
253
254 pub fn extended_context(mut self, enabled: bool) -> Self {
259 self.config.model.extended_context = enabled;
260 self
261 }
262
263 pub fn tools(mut self, access: ToolAccess) -> Self {
275 self.config.security.tool_access = access;
276 self
277 }
278
279 pub fn tool<T: Tool + 'static>(mut self, tool: T) -> Self {
281 self.custom_tools.push(Arc::new(tool));
282 self
283 }
284
285 pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
291 self.config.working_dir = Some(path.into());
292 self
293 }
294
295 pub fn max_iterations(mut self, max: usize) -> Self {
299 self.config.execution.max_iterations = max;
300 self
301 }
302
303 pub fn timeout(mut self, timeout: Duration) -> Self {
307 self.config.execution.timeout = Some(timeout);
308 self
309 }
310
311 pub fn chunk_timeout(mut self, timeout: Duration) -> Self {
321 self.config.execution.chunk_timeout = timeout;
322 self
323 }
324
325 pub fn auto_compact(mut self, enabled: bool) -> Self {
329 self.config.execution.auto_compact = enabled;
330 self
331 }
332
333 pub fn compact_keep_messages(mut self, count: usize) -> Self {
337 self.config.execution.compact_keep_messages = count;
338 self
339 }
340
341 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
347 self.config.prompt.system_prompt = Some(prompt.into());
348 self
349 }
350
351 pub fn system_prompt_mode(mut self, mode: SystemPromptMode) -> Self {
353 self.config.prompt.system_prompt_mode = mode;
354 self
355 }
356
357 pub fn append_system_prompt(mut self, prompt: impl Into<String>) -> Self {
359 self.config.prompt.system_prompt_mode = SystemPromptMode::Append;
360 self.config.prompt.system_prompt = Some(prompt.into());
361 self
362 }
363
364 pub fn output_style(mut self, style: OutputStyle) -> Self {
366 self.config.prompt.output_style = Some(style);
367 self
368 }
369
370 pub fn output_style_name(mut self, name: impl Into<String>) -> Self {
372 self.output_style_name = Some(name.into());
373 self
374 }
375
376 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
378 self.config.prompt.output_schema = Some(schema);
379 self
380 }
381
382 pub fn structured_output<T: schemars::JsonSchema>(mut self) -> Self {
384 let schema = schemars::schema_for!(T);
385 self.config.prompt.output_schema = serde_json::to_value(schema).ok();
386 self
387 }
388
389 pub fn permission_policy(mut self, policy: PermissionPolicy) -> Self {
395 self.config.security.permission_policy = policy;
396 self
397 }
398
399 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
401 self.config.security.permission_policy.mode = mode;
402 self
403 }
404
405 pub fn allow_tool(mut self, pattern: impl Into<String>) -> Self {
407 let pattern = pattern.into();
408 let rule = if pattern.contains('(') && pattern.contains(')') {
409 PermissionRule::allow_scoped(&pattern)
410 } else {
411 PermissionRule::allow(&pattern)
412 };
413 self.config.security.permission_policy.rules.push(rule);
414 self
415 }
416
417 pub fn deny_tool(mut self, pattern: impl Into<String>) -> Self {
419 let pattern = pattern.into();
420 let rule = if pattern.contains('(') && pattern.contains(')') {
421 PermissionRule::deny_scoped(&pattern)
422 } else {
423 PermissionRule::deny(&pattern)
424 };
425 self.config.security.permission_policy.rules.push(rule);
426 self
427 }
428
429 pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
435 self.config.security.env.insert(key.into(), value.into());
436 self
437 }
438
439 pub fn envs(
441 mut self,
442 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
443 ) -> Self {
444 for (k, v) in vars {
445 self.config.security.env.insert(k.into(), v.into());
446 }
447 self
448 }
449
450 pub fn allow_domain(mut self, domain: impl Into<String>) -> Self {
456 self.sandbox_settings
457 .get_or_insert_with(crate::config::SandboxSettings::default)
458 .network
459 .allowed_domains
460 .insert(domain.into());
461 self
462 }
463
464 pub fn deny_domain(mut self, domain: impl Into<String>) -> Self {
466 self.sandbox_settings
467 .get_or_insert_with(crate::config::SandboxSettings::default)
468 .network
469 .blocked_domains
470 .insert(domain.into());
471 self
472 }
473
474 pub fn sandbox_enabled(mut self, enabled: bool) -> Self {
476 self.sandbox_settings
477 .get_or_insert_with(crate::config::SandboxSettings::default)
478 .enabled = enabled;
479 self
480 }
481
482 pub fn exclude_command(mut self, command: impl Into<String>) -> Self {
484 self.sandbox_settings
485 .get_or_insert_with(crate::config::SandboxSettings::default)
486 .excluded_commands
487 .push(command.into());
488 self
489 }
490
491 pub fn max_budget_usd(mut self, amount: f64) -> Self {
497 self.config.budget.max_cost_usd = Some(amount);
498 self
499 }
500
501 pub fn tenant_id(mut self, id: impl Into<String>) -> Self {
503 self.config.budget.tenant_id = Some(id.into());
504 self
505 }
506
507 pub fn tenant_budget_manager(mut self, manager: TenantBudgetManager) -> Self {
509 self.tenant_budget_manager = Some(manager);
510 self
511 }
512
513 pub fn fallback_model(mut self, model: impl Into<String>) -> Self {
515 self.config.budget.fallback_model = Some(model.into());
516 self
517 }
518
519 pub fn fallback(mut self, config: FallbackConfig) -> Self {
521 self.fallback_config = Some(config);
522 self
523 }
524
525 pub fn session_manager(mut self, manager: crate::session::SessionManager) -> Self {
531 self.session_manager = Some(manager);
532 self
533 }
534
535 pub async fn fork_session(mut self, session_id: impl Into<String>) -> crate::Result<Self> {
537 let manager = self.session_manager.take().unwrap_or_default();
538 let session_id_str: String = session_id.into();
539 let original_id = crate::session::SessionId::from(session_id_str);
540 let forked = manager
541 .fork(&original_id)
542 .await
543 .map_err(|e| crate::Error::Session(e.to_string()))?;
544
545 self.initial_messages = Some(forked.to_api_messages());
546 self.resume_session_id = Some(forked.id.to_string());
547 self.session_manager = Some(manager);
548 Ok(self)
549 }
550
551 pub async fn resume_session(mut self, session_id: impl Into<String>) -> crate::Result<Self> {
553 let session_id_str: String = session_id.into();
554 let id = crate::session::SessionId::from(session_id_str);
555 let manager = self.session_manager.take().unwrap_or_default();
556 let session = manager.get(&id).await?;
557
558 let messages: Vec<crate::types::Message> = session
559 .messages
560 .iter()
561 .map(|m| crate::types::Message {
562 role: m.role,
563 content: m.content.clone(),
564 })
565 .collect();
566
567 self.initial_messages = Some(messages);
568 self.resume_session_id = Some(id.to_string());
569 self.resumed_session = Some(session);
570 self.session_manager = Some(manager);
571 Ok(self)
572 }
573
574 pub fn messages(mut self, messages: Vec<crate::types::Message>) -> Self {
576 self.initial_messages = Some(messages);
577 self
578 }
579
580 pub fn mcp_server(
586 mut self,
587 name: impl Into<String>,
588 config: crate::mcp::McpServerConfig,
589 ) -> Self {
590 self.mcp_configs.insert(name.into(), config);
591 self
592 }
593
594 pub fn mcp_stdio(
596 mut self,
597 name: impl Into<String>,
598 command: impl Into<String>,
599 args: Vec<String>,
600 ) -> Self {
601 self.mcp_configs.insert(
602 name.into(),
603 crate::mcp::McpServerConfig::Stdio {
604 command: command.into(),
605 args,
606 env: std::collections::HashMap::new(),
607 cwd: None,
608 },
609 );
610 self
611 }
612
613 pub fn mcp_manager(mut self, manager: crate::mcp::McpManager) -> Self {
615 self.mcp_manager = Some(std::sync::Arc::new(manager));
616 self
617 }
618
619 pub fn shared_mcp_manager(mut self, manager: std::sync::Arc<crate::mcp::McpManager>) -> Self {
621 self.mcp_manager = Some(manager);
622 self
623 }
624
625 pub fn mcp_toolset(mut self, toolset: crate::mcp::McpToolset) -> Self {
627 self.mcp_toolset_registry
628 .get_or_insert_with(crate::mcp::McpToolsetRegistry::new)
629 .register(toolset);
630 self
631 }
632
633 pub fn with_tool_search(mut self) -> Self {
639 self.tool_search_config = Some(crate::tools::ToolSearchConfig::default());
640 self
641 }
642
643 pub fn tool_search_config(mut self, config: crate::tools::ToolSearchConfig) -> Self {
645 self.tool_search_config = Some(config);
646 self
647 }
648
649 pub fn tool_search_threshold(mut self, threshold: f64) -> Self {
651 let config = self
652 .tool_search_config
653 .get_or_insert_with(crate::tools::ToolSearchConfig::default);
654 config.threshold = threshold.clamp(0.0, 1.0);
655 self
656 }
657
658 pub fn tool_search_mode(mut self, mode: crate::tools::SearchMode) -> Self {
660 let config = self
661 .tool_search_config
662 .get_or_insert_with(crate::tools::ToolSearchConfig::default);
663 config.search_mode = mode;
664 self
665 }
666
667 pub fn always_load_tools(mut self, tools: impl IntoIterator<Item = impl Into<String>>) -> Self {
669 let config = self
670 .tool_search_config
671 .get_or_insert_with(crate::tools::ToolSearchConfig::default);
672 config.always_load = tools.into_iter().map(Into::into).collect();
673 self
674 }
675
676 pub fn shared_tool_search_manager(
678 mut self,
679 manager: std::sync::Arc<crate::tools::ToolSearchManager>,
680 ) -> Self {
681 self.tool_search_manager = Some(manager);
682 self
683 }
684
685 pub fn skill_registry(mut self, registry: IndexRegistry<SkillIndex>) -> Self {
691 self.skill_registry = Some(registry);
692 self
693 }
694
695 pub fn skill(mut self, skill: SkillIndex) -> Self {
697 self.skill_registry
698 .get_or_insert_with(IndexRegistry::new)
699 .register(skill);
700 self
701 }
702
703 pub fn rule_index(mut self, index: RuleIndex) -> Self {
705 self.rule_indices.push(index);
706 self
707 }
708
709 pub fn memory_content(mut self, content: impl Into<String>) -> Self {
711 self.memory_provider
712 .get_or_insert_with(LeveledMemoryProvider::new)
713 .add_content(content);
714 self
715 }
716
717 pub fn local_memory_content(mut self, content: impl Into<String>) -> Self {
719 self.memory_provider
720 .get_or_insert_with(LeveledMemoryProvider::new)
721 .add_local_content(content);
722 self
723 }
724
725 pub fn subagent_registry(mut self, registry: IndexRegistry<SubagentIndex>) -> Self {
731 self.subagent_registry = Some(registry);
732 self
733 }
734
735 pub fn subagent(mut self, subagent: SubagentIndex) -> Self {
737 self.subagent_registry
738 .get_or_insert_with(|| {
739 let mut registry = IndexRegistry::new();
740 registry.register_all(builtin_subagents());
741 registry
742 })
743 .register(subagent);
744 self
745 }
746
747 #[cfg(feature = "plugins")]
757 pub fn plugin_dir(mut self, dir: impl Into<PathBuf>) -> Self {
758 self.plugin_dirs.push(dir.into());
759 self
760 }
761
762 #[cfg(feature = "plugins")]
764 pub fn plugin_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
765 self.plugin_dirs.extend(dirs.into_iter().map(Into::into));
766 self
767 }
768
769 pub fn hook<H: Hook + 'static>(mut self, hook: H) -> Self {
775 self.hooks.register(hook);
776 self
777 }
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783 use crate::client::DEFAULT_MAX_TOKENS;
784
785 #[test]
786 fn test_tool_access() {
787 assert!(ToolAccess::all().is_allowed("Read"));
788 assert!(!ToolAccess::none().is_allowed("Read"));
789 assert!(ToolAccess::only(["Read", "Write"]).is_allowed("Read"));
790 assert!(!ToolAccess::only(["Read", "Write"]).is_allowed("Bash"));
791 assert!(!ToolAccess::except(["Bash"]).is_allowed("Bash"));
792 assert!(ToolAccess::except(["Bash"]).is_allowed("Read"));
793 }
794
795 #[test]
796 fn test_max_tokens_default() {
797 let builder = AgentBuilder::new();
798 assert_eq!(builder.config.model.max_tokens, DEFAULT_MAX_TOKENS);
799 }
800
801 #[test]
802 fn test_max_tokens_custom() {
803 let builder = AgentBuilder::new().max_tokens(16384);
804 assert_eq!(builder.config.model.max_tokens, 16384);
805 }
806}