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("context_providers", &self.context_providers.len())
25 .field("confirmation_manager", &self.confirmation_manager.is_some())
26 .field("permission_checker", &self.permission_checker.is_some())
27 .field("permission_policy", &self.permission_policy.is_some())
28 .field("planning_mode", &self.planning_mode)
29 .field("goal_tracking", &self.goal_tracking)
30 .field(
31 "skill_registry",
32 &self
33 .skill_registry
34 .as_ref()
35 .map(|r| format!("{} skills", r.len())),
36 )
37 .field("memory_store", &self.memory_store.is_some())
38 .field("session_store", &self.session_store.is_some())
39 .field("session_id", &self.session_id)
40 .field("auto_save", &self.auto_save)
41 .field("artifact_store_limits", &self.artifact_store_limits)
42 .field("max_parse_retries", &self.max_parse_retries)
43 .field("tool_timeout_ms", &self.tool_timeout_ms)
44 .field("circuit_breaker_threshold", &self.circuit_breaker_threshold)
45 .field("sandbox_handle", &self.sandbox_handle.is_some())
46 .field("workspace_services", &self.workspace_services.is_some())
47 .field("auto_compact", &self.auto_compact)
48 .field("auto_compact_threshold", &self.auto_compact_threshold)
49 .field("continuation_enabled", &self.continuation_enabled)
50 .field("max_continuation_turns", &self.max_continuation_turns)
51 .field("mcp_manager", &self.mcp_manager.is_some())
52 .field("temperature", &self.temperature)
53 .field("thinking_budget", &self.thinking_budget)
54 .field("max_tool_rounds", &self.max_tool_rounds)
55 .field("max_parallel_tasks", &self.max_parallel_tasks)
56 .field("auto_delegation", &self.auto_delegation)
57 .field("auto_parallel_delegation", &self.auto_parallel_delegation)
58 .field("prompt_slots", &self.prompt_slots.is_some())
59 .finish()
60 }
61}
62
63impl SessionOptions {
64 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn with_model(mut self, model: impl Into<String>) -> Self {
69 self.model = Some(model.into());
70 self
71 }
72
73 pub fn with_agent_dir(mut self, dir: impl Into<PathBuf>) -> Self {
74 self.agent_dirs.push(dir.into());
75 self
76 }
77
78 pub fn with_worker_agent(mut self, spec: WorkerAgentSpec) -> Self {
80 self.worker_agents.push(spec);
81 self
82 }
83
84 pub fn with_worker_agents<I>(mut self, specs: I) -> Self
86 where
87 I: IntoIterator<Item = WorkerAgentSpec>,
88 {
89 self.worker_agents.extend(specs);
90 self
91 }
92
93 pub fn with_queue_config(mut self, config: SessionQueueConfig) -> Self {
94 self.queue_config = Some(config);
95 self
96 }
97
98 pub fn with_default_security(mut self) -> Self {
100 self.security_provider = Some(Arc::new(crate::security::DefaultSecurityProvider::new()));
101 self
102 }
103
104 pub fn with_security_provider(
106 mut self,
107 provider: Arc<dyn crate::security::SecurityProvider>,
108 ) -> Self {
109 self.security_provider = Some(provider);
110 self
111 }
112
113 pub fn with_fs_context(mut self, root_path: impl Into<PathBuf>) -> Self {
115 let config = crate::context::FileSystemContextConfig::new(root_path);
116 self.context_providers
117 .push(Arc::new(crate::context::FileSystemContextProvider::new(
118 config,
119 )));
120 self
121 }
122
123 pub fn with_context_provider(
125 mut self,
126 provider: Arc<dyn crate::context::ContextProvider>,
127 ) -> Self {
128 self.context_providers.push(provider);
129 self
130 }
131
132 pub fn with_confirmation_manager(
134 mut self,
135 manager: Arc<dyn crate::hitl::ConfirmationProvider>,
136 ) -> Self {
137 self.confirmation_manager = Some(manager);
138 self
139 }
140
141 pub fn with_confirmation_policy(mut self, policy: crate::hitl::ConfirmationPolicy) -> Self {
146 self.confirmation_policy = Some(policy);
147 self
148 }
149
150 pub fn with_permission_policy(mut self, policy: crate::permissions::PermissionPolicy) -> Self {
152 self.permission_checker = Some(Arc::new(policy.clone()));
153 self.permission_policy = Some(policy);
154 self
155 }
156
157 pub fn with_permission_checker(
159 mut self,
160 checker: Arc<dyn crate::permissions::PermissionChecker>,
161 ) -> Self {
162 self.permission_checker = Some(checker);
163 self
164 }
165
166 pub fn with_planning_mode(mut self, mode: PlanningMode) -> Self {
168 self.planning_mode = mode;
169 self
170 }
171
172 pub fn with_planning(mut self, enabled: bool) -> Self {
174 self.planning_mode = if enabled {
175 PlanningMode::Enabled
176 } else {
177 PlanningMode::Disabled
178 };
179 self
180 }
181
182 pub fn with_goal_tracking(mut self, enabled: bool) -> Self {
184 self.goal_tracking = enabled;
185 self
186 }
187
188 pub fn with_builtin_skills(mut self) -> Self {
190 self.skill_registry = Some(Arc::new(crate::skills::SkillRegistry::with_builtins()));
191 self
192 }
193
194 pub fn with_skill_registry(mut self, registry: Arc<crate::skills::SkillRegistry>) -> Self {
196 self.skill_registry = Some(registry);
197 self
198 }
199
200 pub fn with_skill_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
203 self.skill_dirs.extend(dirs.into_iter().map(Into::into));
204 self
205 }
206
207 pub fn with_skills_from_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
209 let registry = self
210 .skill_registry
211 .unwrap_or_else(|| Arc::new(crate::skills::SkillRegistry::new()));
212 if let Err(e) = registry.load_from_dir(&dir) {
213 tracing::warn!(
214 dir = %dir.as_ref().display(),
215 error = %e,
216 "Failed to load skills from directory — continuing without them"
217 );
218 }
219 self.skill_registry = Some(registry);
220 self
221 }
222
223 pub fn with_memory(mut self, store: Arc<dyn MemoryStore>) -> Self {
225 self.memory_store = Some(store);
226 self
227 }
228
229 pub fn with_file_memory(mut self, dir: impl Into<PathBuf>) -> Self {
235 self.file_memory_dir = Some(dir.into());
236 self
237 }
238
239 pub fn with_session_store(mut self, store: Arc<dyn crate::store::SessionStore>) -> Self {
241 self.session_store = Some(store);
242 self
243 }
244
245 pub fn with_file_session_store(mut self, dir: impl Into<PathBuf>) -> Self {
247 let dir = dir.into();
248 match tokio::runtime::Handle::try_current() {
249 Ok(handle) => {
250 match tokio::task::block_in_place(|| {
251 handle.block_on(crate::store::FileSessionStore::new(dir))
252 }) {
253 Ok(store) => {
254 self.session_store =
255 Some(Arc::new(store) as Arc<dyn crate::store::SessionStore>);
256 }
257 Err(e) => {
258 tracing::warn!("Failed to create file session store: {}", e);
259 }
260 }
261 }
262 Err(_) => {
263 tracing::warn!(
264 "No async runtime available for file session store — persistence disabled"
265 );
266 }
267 }
268 self
269 }
270
271 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
273 self.session_id = Some(id.into());
274 self
275 }
276
277 pub fn with_auto_save(mut self, enabled: bool) -> Self {
279 self.auto_save = enabled;
280 self
281 }
282
283 pub fn with_artifact_store_limits(mut self, limits: crate::tools::ArtifactStoreLimits) -> Self {
285 self.artifact_store_limits = Some(limits);
286 self
287 }
288
289 pub fn with_parse_retries(mut self, max: u32) -> Self {
295 self.max_parse_retries = Some(max);
296 self
297 }
298
299 pub fn with_tool_timeout(mut self, timeout_ms: u64) -> Self {
305 self.tool_timeout_ms = Some(timeout_ms);
306 self
307 }
308
309 pub fn with_circuit_breaker(mut self, threshold: u32) -> Self {
315 self.circuit_breaker_threshold = Some(threshold);
316 self
317 }
318
319 pub fn with_resilience_defaults(self) -> Self {
325 self.with_parse_retries(2)
326 .with_tool_timeout(120_000)
327 .with_circuit_breaker(3)
328 }
329
330 pub fn with_sandbox_handle(mut self, handle: Arc<dyn crate::sandbox::BashSandbox>) -> Self {
338 self.sandbox_handle = Some(handle);
339 self
340 }
341
342 pub fn with_workspace_backend(
348 mut self,
349 services: Arc<crate::workspace::WorkspaceServices>,
350 ) -> Self {
351 self.workspace_services = Some(services);
352 self
353 }
354
355 pub fn with_auto_compact(mut self, enabled: bool) -> Self {
360 self.auto_compact = enabled;
361 self
362 }
363
364 pub fn with_auto_compact_threshold(mut self, threshold: f32) -> Self {
366 self.auto_compact_threshold = Some(threshold.clamp(0.0, 1.0));
367 self
368 }
369
370 pub fn with_continuation(mut self, enabled: bool) -> Self {
375 self.continuation_enabled = Some(enabled);
376 self
377 }
378
379 pub fn with_max_continuation_turns(mut self, turns: u32) -> Self {
381 self.max_continuation_turns = Some(turns);
382 self
383 }
384
385 pub fn with_mcp(mut self, manager: Arc<crate::mcp::manager::McpManager>) -> Self {
390 self.mcp_manager = Some(manager);
391 self
392 }
393
394 pub fn with_temperature(mut self, temperature: f32) -> Self {
395 self.temperature = Some(temperature);
396 self
397 }
398
399 pub fn with_thinking_budget(mut self, budget: usize) -> Self {
400 self.thinking_budget = Some(budget);
401 self
402 }
403
404 pub fn with_max_tool_rounds(mut self, rounds: usize) -> Self {
409 self.max_tool_rounds = Some(rounds);
410 self
411 }
412
413 pub fn with_max_parallel_tasks(mut self, tasks: usize) -> Self {
415 self.max_parallel_tasks = Some(tasks.max(1));
416 self
417 }
418
419 pub fn with_auto_delegation(mut self, config: crate::config::AutoDelegationConfig) -> Self {
421 self.auto_delegation = Some(config);
422 self
423 }
424
425 pub fn with_auto_delegation_enabled(mut self, enabled: bool) -> Self {
427 let mut config = self.auto_delegation.take().unwrap_or_default();
428 config.enabled = enabled;
429 self.auto_delegation = Some(config);
430 self
431 }
432
433 pub fn with_auto_parallel_delegation(mut self, enabled: bool) -> Self {
437 if let Some(config) = &mut self.auto_delegation {
438 config.auto_parallel = enabled;
439 }
440 self.auto_parallel_delegation = Some(enabled);
441 self
442 }
443
444 pub fn with_prompt_slots(mut self, slots: SystemPromptSlots) -> Self {
449 self.prompt_slots = Some(slots);
450 self
451 }
452
453 pub fn with_hook_executor(mut self, executor: Arc<dyn crate::hooks::HookExecutor>) -> Self {
459 self.hook_executor = Some(executor);
460 self
461 }
462}