1use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10
11use crate::async_context_transformer::AsyncContextTransformer;
12use crate::checkpoint::CheckpointStore;
13use crate::loop_::AgentEvent;
14use crate::message_provider::MessageProvider;
15use crate::retry::{DefaultRetryStrategy, RetryStrategy};
16use crate::stream::{StreamFn, StreamOptions};
17use crate::tool::{AgentTool, ApprovalMode, ToolApproval, ToolApprovalRequest};
18use crate::types::{AgentMessage, CustomMessageRegistry, LlmMessage, ModelSpec};
19
20pub(crate) type ConvertToLlmFn = Arc<dyn Fn(&AgentMessage) -> Option<LlmMessage> + Send + Sync>;
23pub(crate) type TransformContextArc = Arc<dyn crate::context_transformer::ContextTransformer>;
24pub(crate) type AsyncTransformContextArc = Arc<dyn AsyncContextTransformer>;
25pub(crate) type CheckpointStoreArc = Arc<dyn CheckpointStore>;
26pub(crate) type GetApiKeyFuture = Pin<Box<dyn Future<Output = Option<String>> + Send>>;
27pub(crate) type GetApiKeyFn = dyn Fn(&str) -> GetApiKeyFuture + Send + Sync;
28pub(crate) type GetApiKeyArc = Arc<GetApiKeyFn>;
29pub(crate) type ApproveToolFuture = Pin<Box<dyn Future<Output = ToolApproval> + Send>>;
30pub(crate) type ApproveToolFn = dyn Fn(ToolApprovalRequest) -> ApproveToolFuture + Send + Sync;
31pub(crate) type ApproveToolArc = Arc<ApproveToolFn>;
32
33pub const DEFAULT_PLAN_MODE_ADDENDUM: &str = "\n\nYou are in planning mode. Analyze the request and produce a step-by-step plan. Do not make any modifications or execute any write operations.";
37
38pub struct AgentOptions {
42 pub system_prompt: String,
44 pub static_system_prompt: Option<String>,
48 pub dynamic_system_prompt: Option<Box<dyn Fn() -> String + Send + Sync>>,
54 pub model: ModelSpec,
56 pub tools: Vec<Arc<dyn AgentTool>>,
58 pub stream_fn: Arc<dyn StreamFn>,
60 pub convert_to_llm: ConvertToLlmFn,
62 pub transform_context: Option<TransformContextArc>,
64 pub get_api_key: Option<GetApiKeyArc>,
66 pub retry_strategy: Box<dyn RetryStrategy>,
68 pub stream_options: StreamOptions,
70 pub steering_mode: crate::agent::SteeringMode,
72 pub follow_up_mode: crate::agent::FollowUpMode,
74 pub structured_output_max_retries: usize,
76 pub approve_tool: Option<ApproveToolArc>,
78 pub approval_mode: ApprovalMode,
80 pub available_models: Vec<(ModelSpec, Arc<dyn StreamFn>)>,
82 pub pre_turn_policies: Vec<Arc<dyn crate::policy::PreTurnPolicy>>,
84 pub pre_dispatch_policies: Vec<Arc<dyn crate::policy::PreDispatchPolicy>>,
86 pub post_turn_policies: Vec<Arc<dyn crate::policy::PostTurnPolicy>>,
88 pub post_loop_policies: Vec<Arc<dyn crate::policy::PostLoopPolicy>>,
90 pub event_forwarders: Vec<crate::event_forwarder::EventForwarderFn>,
92 pub async_transform_context: Option<AsyncTransformContextArc>,
94 pub checkpoint_store: Option<CheckpointStoreArc>,
96 pub custom_message_registry: Option<Arc<CustomMessageRegistry>>,
106 pub metrics_collector: Option<Arc<dyn crate::metrics::MetricsCollector>>,
108 pub token_counter: Option<Arc<dyn crate::context::TokenCounter>>,
116 pub fallback: Option<crate::fallback::ModelFallback>,
119 pub external_message_provider: Option<Arc<dyn MessageProvider>>,
124 pub tool_execution_policy: crate::tool_execution_policy::ToolExecutionPolicy,
128 pub plan_mode_addendum: Option<String>,
132 pub session_state: Option<crate::SessionState>,
134 pub credential_resolver: Option<Arc<dyn crate::credential::CredentialResolver>>,
139 pub cache_config: Option<crate::context_cache::CacheConfig>,
145 #[cfg(feature = "plugins")]
151 pub plugins: Vec<Arc<dyn crate::plugin::Plugin>>,
152 pub agent_name: Option<String>,
157 pub transfer_chain: Option<crate::transfer::TransferChain>,
161}
162
163impl AgentOptions {
164 #[must_use]
166 pub fn new(
167 system_prompt: impl Into<String>,
168 model: ModelSpec,
169 stream_fn: Arc<dyn StreamFn>,
170 convert_to_llm: impl Fn(&AgentMessage) -> Option<LlmMessage> + Send + Sync + 'static,
171 ) -> Self {
172 Self {
173 system_prompt: system_prompt.into(),
174 static_system_prompt: None,
175 dynamic_system_prompt: None,
176 model,
177 tools: Vec::new(),
178 stream_fn,
179 convert_to_llm: Arc::new(convert_to_llm),
180 transform_context: Some(Arc::new(
181 crate::context_transformer::SlidingWindowTransformer::new(100_000, 50_000, 2),
182 )),
183 get_api_key: None,
184 retry_strategy: Box::new(DefaultRetryStrategy::default()),
185 stream_options: StreamOptions::default(),
186 steering_mode: crate::agent::SteeringMode::default(),
187 follow_up_mode: crate::agent::FollowUpMode::default(),
188 structured_output_max_retries: 3,
189 approve_tool: None,
190 approval_mode: ApprovalMode::default(),
191 available_models: Vec::new(),
192 pre_turn_policies: Vec::new(),
193 pre_dispatch_policies: Vec::new(),
194 post_turn_policies: Vec::new(),
195 post_loop_policies: Vec::new(),
196 event_forwarders: Vec::new(),
197 async_transform_context: None,
198 checkpoint_store: None,
199 custom_message_registry: None,
200 metrics_collector: None,
201 token_counter: None,
202 fallback: None,
203 external_message_provider: None,
204 tool_execution_policy: crate::tool_execution_policy::ToolExecutionPolicy::default(),
205 plan_mode_addendum: None,
206 session_state: None,
207 credential_resolver: None,
208 cache_config: None,
209 #[cfg(feature = "plugins")]
210 plugins: Vec::new(),
211 agent_name: None,
212 transfer_chain: None,
213 }
214 }
215
216 #[must_use]
220 pub fn new_simple(
221 system_prompt: impl Into<String>,
222 model: ModelSpec,
223 stream_fn: Arc<dyn StreamFn>,
224 ) -> Self {
225 Self::new(
226 system_prompt,
227 model,
228 stream_fn,
229 crate::agent::default_convert,
230 )
231 }
232
233 #[must_use]
239 pub fn from_connections(
240 system_prompt: impl Into<String>,
241 connections: crate::model_presets::ModelConnections,
242 ) -> Self {
243 let (model, stream_fn, extra_models) = connections.into_parts();
244 Self::new_simple(system_prompt, model, stream_fn).with_available_models(extra_models)
245 }
246
247 #[must_use]
249 pub fn with_tools(mut self, tools: Vec<Arc<dyn AgentTool>>) -> Self {
250 self.tools = tools;
251 self
252 }
253
254 #[cfg(feature = "builtin-tools")]
256 #[must_use]
257 pub fn with_default_tools(self) -> Self {
258 self.with_tools(crate::tools::builtin_tools())
259 }
260
261 #[must_use]
263 pub fn with_retry_strategy(mut self, strategy: Box<dyn RetryStrategy>) -> Self {
264 self.retry_strategy = strategy;
265 self
266 }
267
268 #[must_use]
270 pub fn with_stream_options(mut self, options: StreamOptions) -> Self {
271 self.stream_options = options;
272 self
273 }
274
275 #[must_use]
277 pub fn with_transform_context(
278 mut self,
279 transformer: impl crate::context_transformer::ContextTransformer + 'static,
280 ) -> Self {
281 self.transform_context = Some(Arc::new(transformer));
282 self
283 }
284
285 #[must_use]
290 pub fn with_transform_context_fn(
291 mut self,
292 f: impl Fn(&mut Vec<AgentMessage>, bool) + Send + Sync + 'static,
293 ) -> Self {
294 self.transform_context = Some(Arc::new(f));
295 self
296 }
297
298 #[must_use]
300 pub fn with_get_api_key(
301 mut self,
302 f: impl Fn(&str) -> GetApiKeyFuture + Send + Sync + 'static,
303 ) -> Self {
304 self.get_api_key = Some(Arc::new(f));
305 self
306 }
307
308 #[must_use]
310 pub const fn with_steering_mode(mut self, mode: crate::agent::SteeringMode) -> Self {
311 self.steering_mode = mode;
312 self
313 }
314
315 #[must_use]
317 pub const fn with_follow_up_mode(mut self, mode: crate::agent::FollowUpMode) -> Self {
318 self.follow_up_mode = mode;
319 self
320 }
321
322 #[must_use]
324 pub const fn with_structured_output_max_retries(mut self, n: usize) -> Self {
325 self.structured_output_max_retries = n;
326 self
327 }
328
329 #[must_use]
331 pub fn with_approve_tool(
332 mut self,
333 f: impl Fn(ToolApprovalRequest) -> ApproveToolFuture + Send + Sync + 'static,
334 ) -> Self {
335 self.approve_tool = Some(Arc::new(f));
336 self
337 }
338
339 #[must_use]
344 pub fn with_approve_tool_async<F, Fut>(mut self, f: F) -> Self
345 where
346 F: Fn(ToolApprovalRequest) -> Fut + Send + Sync + 'static,
347 Fut: std::future::Future<Output = ToolApproval> + Send + 'static,
348 {
349 let f = std::sync::Arc::new(f);
350 self.approve_tool = Some(std::sync::Arc::new(move |req| {
351 let f = std::sync::Arc::clone(&f);
352 Box::pin(async move { f(req).await })
353 }));
354 self
355 }
356
357 #[must_use]
359 pub const fn with_approval_mode(mut self, mode: ApprovalMode) -> Self {
360 self.approval_mode = mode;
361 self
362 }
363
364 #[must_use]
366 pub fn with_available_models(mut self, models: Vec<(ModelSpec, Arc<dyn StreamFn>)>) -> Self {
367 self.available_models = models;
368 self
369 }
370
371 #[must_use]
373 pub fn with_pre_turn_policy(
374 mut self,
375 policy: impl crate::policy::PreTurnPolicy + 'static,
376 ) -> Self {
377 self.pre_turn_policies.push(Arc::new(policy));
378 self
379 }
380
381 #[must_use]
383 pub fn with_pre_dispatch_policy(
384 mut self,
385 policy: impl crate::policy::PreDispatchPolicy + 'static,
386 ) -> Self {
387 self.pre_dispatch_policies.push(Arc::new(policy));
388 self
389 }
390
391 #[must_use]
393 pub fn with_post_turn_policy(
394 mut self,
395 policy: impl crate::policy::PostTurnPolicy + 'static,
396 ) -> Self {
397 self.post_turn_policies.push(Arc::new(policy));
398 self
399 }
400
401 #[must_use]
403 pub fn with_post_loop_policy(
404 mut self,
405 policy: impl crate::policy::PostLoopPolicy + 'static,
406 ) -> Self {
407 self.post_loop_policies.push(Arc::new(policy));
408 self
409 }
410
411 #[must_use]
413 pub fn with_event_forwarder(mut self, f: impl Fn(AgentEvent) + Send + Sync + 'static) -> Self {
414 self.event_forwarders.push(Arc::new(f));
415 self
416 }
417
418 #[must_use]
420 pub fn with_async_transform_context(
421 mut self,
422 transformer: impl AsyncContextTransformer + 'static,
423 ) -> Self {
424 self.async_transform_context = Some(Arc::new(transformer));
425 self
426 }
427
428 #[must_use]
430 pub fn with_checkpoint_store(mut self, store: impl CheckpointStore + 'static) -> Self {
431 self.checkpoint_store = Some(Arc::new(store));
432 self
433 }
434
435 #[must_use]
444 pub fn with_custom_message_registry(mut self, registry: CustomMessageRegistry) -> Self {
445 self.custom_message_registry = Some(Arc::new(registry));
446 self
447 }
448
449 #[must_use]
452 pub fn with_custom_message_registry_arc(
453 mut self,
454 registry: Arc<CustomMessageRegistry>,
455 ) -> Self {
456 self.custom_message_registry = Some(registry);
457 self
458 }
459
460 #[must_use]
462 pub fn with_metrics_collector(
463 mut self,
464 collector: impl crate::metrics::MetricsCollector + 'static,
465 ) -> Self {
466 self.metrics_collector = Some(Arc::new(collector));
467 self
468 }
469
470 #[must_use]
476 pub fn with_token_counter(
477 mut self,
478 counter: impl crate::context::TokenCounter + 'static,
479 ) -> Self {
480 self.token_counter = Some(Arc::new(counter));
481 self
482 }
483
484 #[must_use]
490 pub fn with_model_fallback(mut self, fallback: crate::fallback::ModelFallback) -> Self {
491 self.fallback = Some(fallback);
492 self
493 }
494
495 pub fn with_message_channel(&mut self) -> crate::message_provider::MessageSender {
510 let (provider, sender) = crate::message_provider::message_channel();
511 self.external_message_provider = Some(Arc::new(provider));
512 sender
513 }
514
515 #[must_use]
519 pub fn with_external_message_provider(
520 mut self,
521 provider: impl MessageProvider + 'static,
522 ) -> Self {
523 self.external_message_provider = Some(Arc::new(provider));
524 self
525 }
526
527 #[must_use]
532 pub fn with_tool_execution_policy(
533 mut self,
534 policy: crate::tool_execution_policy::ToolExecutionPolicy,
535 ) -> Self {
536 self.tool_execution_policy = policy;
537 self
538 }
539
540 #[must_use]
544 pub fn with_plan_mode_addendum(mut self, addendum: impl Into<String>) -> Self {
545 self.plan_mode_addendum = Some(addendum.into());
546 self
547 }
548
549 #[must_use]
551 pub fn with_initial_state(mut self, state: crate::SessionState) -> Self {
552 self.session_state = Some(state);
553 self
554 }
555
556 #[must_use]
558 pub fn with_state_entry(
559 mut self,
560 key: impl Into<String>,
561 value: impl serde::Serialize,
562 ) -> Self {
563 let state = self
564 .session_state
565 .get_or_insert_with(crate::SessionState::new);
566 state
567 .set(&key.into(), value)
568 .expect("with_state_entry: value must be serializable to JSON");
569 state.flush_delta();
571 self
572 }
573
574 #[must_use]
579 pub fn with_credential_resolver(
580 mut self,
581 resolver: Arc<dyn crate::credential::CredentialResolver>,
582 ) -> Self {
583 self.credential_resolver = Some(resolver);
584 self
585 }
586
587 #[must_use]
589 pub const fn with_cache_config(mut self, config: crate::context_cache::CacheConfig) -> Self {
590 self.cache_config = Some(config);
591 self
592 }
593
594 #[must_use]
598 pub fn with_static_system_prompt(mut self, prompt: String) -> Self {
599 self.static_system_prompt = Some(prompt);
600 self
601 }
602
603 #[must_use]
608 pub fn with_dynamic_system_prompt(
609 mut self,
610 f: impl Fn() -> String + Send + Sync + 'static,
611 ) -> Self {
612 self.dynamic_system_prompt = Some(Box::new(f));
613 self
614 }
615
616 #[cfg(feature = "plugins")]
622 #[must_use]
623 pub fn with_plugin(mut self, plugin: Arc<dyn crate::plugin::Plugin>) -> Self {
624 let name = plugin.name();
625 if let Some(pos) = self.plugins.iter().position(|p| p.name() == name) {
626 tracing::warn!(plugin = %name, "replacing duplicate plugin in AgentOptions");
627 self.plugins[pos] = plugin;
628 } else {
629 self.plugins.push(plugin);
630 }
631 self
632 }
633
634 #[cfg(feature = "plugins")]
639 #[must_use]
640 pub fn with_plugins(mut self, plugins: Vec<Arc<dyn crate::plugin::Plugin>>) -> Self {
641 for plugin in plugins {
642 self = self.with_plugin(plugin);
643 }
644 self
645 }
646
647 #[must_use]
653 pub fn with_agent_name(mut self, name: impl Into<String>) -> Self {
654 self.agent_name = Some(name.into());
655 self
656 }
657
658 #[must_use]
663 pub fn with_transfer_chain(mut self, chain: crate::transfer::TransferChain) -> Self {
664 self.transfer_chain = Some(chain);
665 self
666 }
667
668 pub fn effective_system_prompt(&self) -> &str {
673 self.static_system_prompt
674 .as_deref()
675 .unwrap_or(&self.system_prompt)
676 }
677}
678
679#[cfg(test)]
680#[cfg(feature = "plugins")]
681mod tests {
682 use super::*;
683 use crate::testing::{MockPlugin, SimpleMockStreamFn};
684 use crate::types::ModelSpec;
685
686 fn test_options() -> AgentOptions {
687 AgentOptions::new_simple(
688 "test",
689 ModelSpec::new("test-model", "test-model"),
690 Arc::new(SimpleMockStreamFn::from_text("hello")),
691 )
692 }
693
694 #[test]
695 fn with_plugin_deduplicates_by_name() {
696 let opts = test_options()
697 .with_plugin(Arc::new(MockPlugin::new("alpha").with_priority(1)))
698 .with_plugin(Arc::new(MockPlugin::new("alpha").with_priority(5)));
699
700 assert_eq!(opts.plugins.len(), 1);
701 assert_eq!(opts.plugins[0].priority(), 5);
702 }
703
704 #[test]
705 fn with_plugin_keeps_distinct_names() {
706 let opts = test_options()
707 .with_plugin(Arc::new(MockPlugin::new("alpha")))
708 .with_plugin(Arc::new(MockPlugin::new("beta")));
709
710 assert_eq!(opts.plugins.len(), 2);
711 }
712
713 #[test]
714 fn with_plugins_deduplicates_within_batch() {
715 let opts = test_options().with_plugins(vec![
716 Arc::new(MockPlugin::new("alpha").with_priority(1)),
717 Arc::new(MockPlugin::new("beta")),
718 Arc::new(MockPlugin::new("alpha").with_priority(9)),
719 ]);
720
721 assert_eq!(opts.plugins.len(), 2);
722 let alpha = opts.plugins.iter().find(|p| p.name() == "alpha").unwrap();
724 assert_eq!(alpha.priority(), 9);
725 }
726
727 #[test]
728 fn with_plugins_deduplicates_against_existing() {
729 let opts = test_options()
730 .with_plugin(Arc::new(MockPlugin::new("alpha").with_priority(1)))
731 .with_plugins(vec![
732 Arc::new(MockPlugin::new("alpha").with_priority(7)),
733 Arc::new(MockPlugin::new("beta")),
734 ]);
735
736 assert_eq!(opts.plugins.len(), 2);
737 let alpha = opts.plugins.iter().find(|p| p.name() == "alpha").unwrap();
738 assert_eq!(alpha.priority(), 7);
739 }
740}