1use super::SessionOptions;
8use crate::prompts::{PlanningMode, SystemPromptSlots};
9use crate::queue::SessionQueueConfig;
10use crate::subagent::WorkerAgentSpec;
11use a3s_memory::MemoryStore;
12use std::path::PathBuf;
13use std::sync::Arc;
14
15impl std::fmt::Debug for SessionOptions {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 f.debug_struct("SessionOptions")
18 .field("model", &self.model)
19 .field("agent_dirs", &self.agent_dirs)
20 .field("worker_agents", &self.worker_agents.len())
21 .field("skill_dirs", &self.skill_dirs)
22 .field("queue_config", &self.queue_config)
23 .field("security_provider", &self.security_provider.is_some())
24 .field("llm_client", &self.llm_client.is_some())
25 .field("context_providers", &self.context_providers.len())
26 .field("confirmation_manager", &self.confirmation_manager.is_some())
27 .field("permission_checker", &self.permission_checker.is_some())
28 .field("permission_policy", &self.permission_policy.is_some())
29 .field("planning_mode", &self.planning_mode)
30 .field("goal_tracking", &self.goal_tracking)
31 .field(
32 "skill_registry",
33 &self
34 .skill_registry
35 .as_ref()
36 .map(|r| format!("{} skills", r.len())),
37 )
38 .field("memory_store", &self.memory_store.is_some())
39 .field("session_store", &self.session_store.is_some())
40 .field("session_id", &self.session_id)
41 .field("auto_save", &self.auto_save)
42 .field("artifact_store_limits", &self.artifact_store_limits)
43 .field("max_parse_retries", &self.max_parse_retries)
44 .field("tool_timeout_ms", &self.tool_timeout_ms)
45 .field("circuit_breaker_threshold", &self.circuit_breaker_threshold)
46 .field("sandbox_handle", &self.sandbox_handle.is_some())
47 .field("workspace_services", &self.workspace_services.is_some())
48 .field("auto_compact", &self.auto_compact)
49 .field("auto_compact_threshold", &self.auto_compact_threshold)
50 .field("continuation_enabled", &self.continuation_enabled)
51 .field("max_continuation_turns", &self.max_continuation_turns)
52 .field("mcp_manager", &self.mcp_manager.is_some())
53 .field("temperature", &self.temperature)
54 .field("thinking_budget", &self.thinking_budget)
55 .field("max_tool_rounds", &self.max_tool_rounds)
56 .field("max_parallel_tasks", &self.max_parallel_tasks)
57 .field("auto_delegation", &self.auto_delegation)
58 .field("auto_parallel_delegation", &self.auto_parallel_delegation)
59 .field("prompt_slots", &self.prompt_slots.is_some())
60 .finish()
61 }
62}
63
64impl SessionOptions {
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 pub fn with_model(mut self, model: impl Into<String>) -> Self {
70 self.model = Some(model.into());
71 self
72 }
73
74 pub fn with_agent_dir(mut self, dir: impl Into<PathBuf>) -> Self {
75 self.agent_dirs.push(dir.into());
76 self
77 }
78
79 pub fn with_worker_agent(mut self, spec: WorkerAgentSpec) -> Self {
81 self.worker_agents.push(spec);
82 self
83 }
84
85 pub fn with_worker_agents<I>(mut self, specs: I) -> Self
87 where
88 I: IntoIterator<Item = WorkerAgentSpec>,
89 {
90 self.worker_agents.extend(specs);
91 self
92 }
93
94 pub fn with_queue_config(mut self, config: SessionQueueConfig) -> Self {
95 self.queue_config = Some(config);
96 self
97 }
98
99 pub fn with_default_security(mut self) -> Self {
101 self.security_provider = Some(Arc::new(crate::security::DefaultSecurityProvider::new()));
102 self
103 }
104
105 pub fn with_security_provider(
107 mut self,
108 provider: Arc<dyn crate::security::SecurityProvider>,
109 ) -> Self {
110 self.security_provider = Some(provider);
111 self
112 }
113
114 pub fn with_llm_client(mut self, client: Arc<dyn crate::llm::LlmClient>) -> Self {
122 self.llm_client = Some(client);
123 self
124 }
125
126 pub fn with_fs_context(mut self, root_path: impl Into<PathBuf>) -> Self {
128 let config = crate::context::FileSystemContextConfig::new(root_path);
129 self.context_providers
130 .push(Arc::new(crate::context::FileSystemContextProvider::new(
131 config,
132 )));
133 self
134 }
135
136 pub fn with_context_provider(
138 mut self,
139 provider: Arc<dyn crate::context::ContextProvider>,
140 ) -> Self {
141 self.context_providers.push(provider);
142 self
143 }
144
145 pub fn with_confirmation_manager(
147 mut self,
148 manager: Arc<dyn crate::hitl::ConfirmationProvider>,
149 ) -> Self {
150 self.confirmation_manager = Some(manager);
151 self
152 }
153
154 pub fn with_confirmation_policy(mut self, policy: crate::hitl::ConfirmationPolicy) -> Self {
159 self.confirmation_policy = Some(policy);
160 self
161 }
162
163 pub fn with_permission_policy(mut self, policy: crate::permissions::PermissionPolicy) -> Self {
165 self.permission_checker = Some(Arc::new(policy.clone()));
166 self.permission_policy = Some(policy);
167 self
168 }
169
170 pub fn with_permission_checker(
172 mut self,
173 checker: Arc<dyn crate::permissions::PermissionChecker>,
174 ) -> Self {
175 self.permission_checker = Some(checker);
176 self
177 }
178
179 pub fn with_planning_mode(mut self, mode: PlanningMode) -> Self {
181 self.planning_mode = mode;
182 self
183 }
184
185 pub fn with_planning(mut self, enabled: bool) -> Self {
187 self.planning_mode = if enabled {
188 PlanningMode::Enabled
189 } else {
190 PlanningMode::Disabled
191 };
192 self
193 }
194
195 pub fn with_goal_tracking(mut self, enabled: bool) -> Self {
197 self.goal_tracking = enabled;
198 self
199 }
200
201 pub fn with_builtin_skills(mut self) -> Self {
203 self.skill_registry = Some(Arc::new(crate::skills::SkillRegistry::with_builtins()));
204 self
205 }
206
207 pub fn with_skill_registry(mut self, registry: Arc<crate::skills::SkillRegistry>) -> Self {
209 self.skill_registry = Some(registry);
210 self
211 }
212
213 pub fn with_skill_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
216 self.skill_dirs.extend(dirs.into_iter().map(Into::into));
217 self
218 }
219
220 pub fn with_skills_from_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
222 let registry = self
223 .skill_registry
224 .unwrap_or_else(|| Arc::new(crate::skills::SkillRegistry::new()));
225 if let Err(e) = registry.load_from_dir(&dir) {
226 tracing::warn!(
227 dir = %dir.as_ref().display(),
228 error = %e,
229 "Failed to load skills from directory — continuing without them"
230 );
231 }
232 self.skill_registry = Some(registry);
233 self
234 }
235
236 pub fn with_memory(mut self, store: Arc<dyn MemoryStore>) -> Self {
238 self.memory_store = Some(store);
239 self
240 }
241
242 pub fn with_file_memory(mut self, dir: impl Into<PathBuf>) -> Self {
248 self.file_memory_dir = Some(dir.into());
249 self
250 }
251
252 pub fn with_session_store(mut self, store: Arc<dyn crate::store::SessionStore>) -> Self {
254 self.session_store = Some(store);
255 self
256 }
257
258 pub fn with_file_session_store(mut self, dir: impl Into<PathBuf>) -> Self {
260 let dir = dir.into();
261 match tokio::runtime::Handle::try_current() {
262 Ok(handle) => {
263 match tokio::task::block_in_place(|| {
264 handle.block_on(crate::store::FileSessionStore::new(dir))
265 }) {
266 Ok(store) => {
267 self.session_store =
268 Some(Arc::new(store) as Arc<dyn crate::store::SessionStore>);
269 }
270 Err(e) => {
271 tracing::warn!("Failed to create file session store: {}", e);
272 }
273 }
274 }
275 Err(_) => {
276 tracing::warn!(
277 "No async runtime available for file session store — persistence disabled"
278 );
279 }
280 }
281 self
282 }
283
284 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
286 self.session_id = Some(id.into());
287 self
288 }
289
290 pub fn with_tenant_id(mut self, tenant: impl Into<String>) -> Self {
293 self.tenant_id = Some(tenant.into());
294 self
295 }
296
297 pub fn with_principal(mut self, principal: impl Into<String>) -> Self {
300 self.principal = Some(principal.into());
301 self
302 }
303
304 pub fn with_agent_template_id(mut self, template_id: impl Into<String>) -> Self {
307 self.agent_template_id = Some(template_id.into());
308 self
309 }
310
311 pub fn with_correlation_id(mut self, corr: impl Into<String>) -> Self {
314 self.correlation_id = Some(corr.into());
315 self
316 }
317
318 pub fn with_budget_guard(mut self, guard: Arc<dyn crate::budget::BudgetGuard>) -> Self {
323 self.budget_guard = Some(guard);
324 self
325 }
326
327 pub fn with_host_env(mut self, env: Arc<crate::host_env::HostEnv>) -> Self {
333 self.host_env = Some(env);
334 self
335 }
336
337 pub fn with_retention_limits(
347 mut self,
348 limits: crate::retention::SessionRetentionLimits,
349 ) -> Self {
350 self.retention_limits = Some(limits);
351 self
352 }
353
354 pub fn with_auto_save(mut self, enabled: bool) -> Self {
356 self.auto_save = enabled;
357 self
358 }
359
360 pub fn with_artifact_store_limits(mut self, limits: crate::tools::ArtifactStoreLimits) -> Self {
362 self.artifact_store_limits = Some(limits);
363 self
364 }
365
366 pub fn with_parse_retries(mut self, max: u32) -> Self {
372 self.max_parse_retries = Some(max);
373 self
374 }
375
376 pub fn with_tool_timeout(mut self, timeout_ms: u64) -> Self {
382 self.tool_timeout_ms = Some(timeout_ms);
383 self
384 }
385
386 pub fn with_circuit_breaker(mut self, threshold: u32) -> Self {
392 self.circuit_breaker_threshold = Some(threshold);
393 self
394 }
395
396 pub fn with_resilience_defaults(self) -> Self {
402 self.with_parse_retries(2)
403 .with_tool_timeout(120_000)
404 .with_circuit_breaker(3)
405 }
406
407 pub fn with_sandbox_handle(mut self, handle: Arc<dyn crate::sandbox::BashSandbox>) -> Self {
415 self.sandbox_handle = Some(handle);
416 self
417 }
418
419 pub fn with_workspace_backend(
425 mut self,
426 services: Arc<crate::workspace::WorkspaceServices>,
427 ) -> Self {
428 self.workspace_services = Some(services);
429 self
430 }
431
432 pub fn with_auto_compact(mut self, enabled: bool) -> Self {
437 self.auto_compact = enabled;
438 self
439 }
440
441 pub fn with_auto_compact_threshold(mut self, threshold: f32) -> Self {
443 self.auto_compact_threshold = Some(threshold.clamp(0.0, 1.0));
444 self
445 }
446
447 pub fn with_continuation(mut self, enabled: bool) -> Self {
452 self.continuation_enabled = Some(enabled);
453 self
454 }
455
456 pub fn with_max_continuation_turns(mut self, turns: u32) -> Self {
458 self.max_continuation_turns = Some(turns);
459 self
460 }
461
462 pub fn with_mcp(mut self, manager: Arc<crate::mcp::manager::McpManager>) -> Self {
467 self.mcp_manager = Some(manager);
468 self
469 }
470
471 pub fn with_temperature(mut self, temperature: f32) -> Self {
472 self.temperature = Some(temperature);
473 self
474 }
475
476 pub fn with_thinking_budget(mut self, budget: usize) -> Self {
477 self.thinking_budget = Some(budget);
478 self
479 }
480
481 pub fn with_max_tool_rounds(mut self, rounds: usize) -> Self {
486 self.max_tool_rounds = Some(rounds);
487 self
488 }
489
490 pub fn with_max_parallel_tasks(mut self, tasks: usize) -> Self {
492 self.max_parallel_tasks = Some(tasks.max(1));
493 self
494 }
495
496 pub fn with_auto_delegation(mut self, config: crate::config::AutoDelegationConfig) -> Self {
498 self.auto_delegation = Some(config);
499 self
500 }
501
502 pub fn with_auto_delegation_enabled(mut self, enabled: bool) -> Self {
504 let mut config = self.auto_delegation.take().unwrap_or_default();
505 config.enabled = enabled;
506 self.auto_delegation = Some(config);
507 self
508 }
509
510 pub fn with_auto_parallel_delegation(mut self, enabled: bool) -> Self {
514 if let Some(config) = &mut self.auto_delegation {
515 config.auto_parallel = enabled;
516 }
517 self.auto_parallel_delegation = Some(enabled);
518 self
519 }
520
521 pub fn with_prompt_slots(mut self, slots: SystemPromptSlots) -> Self {
526 self.prompt_slots = Some(slots);
527 self
528 }
529
530 pub fn with_hook_executor(mut self, executor: Arc<dyn crate::hooks::HookExecutor>) -> Self {
536 self.hook_executor = Some(executor);
537 self
538 }
539}