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("prompt_slots", &self.prompt_slots.is_some())
56 .finish()
57 }
58}
59
60impl SessionOptions {
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn with_model(mut self, model: impl Into<String>) -> Self {
66 self.model = Some(model.into());
67 self
68 }
69
70 pub fn with_agent_dir(mut self, dir: impl Into<PathBuf>) -> Self {
71 self.agent_dirs.push(dir.into());
72 self
73 }
74
75 pub fn with_worker_agent(mut self, spec: WorkerAgentSpec) -> Self {
77 self.worker_agents.push(spec);
78 self
79 }
80
81 pub fn with_worker_agents<I>(mut self, specs: I) -> Self
83 where
84 I: IntoIterator<Item = WorkerAgentSpec>,
85 {
86 self.worker_agents.extend(specs);
87 self
88 }
89
90 pub fn with_queue_config(mut self, config: SessionQueueConfig) -> Self {
91 self.queue_config = Some(config);
92 self
93 }
94
95 pub fn with_default_security(mut self) -> Self {
97 self.security_provider = Some(Arc::new(crate::security::DefaultSecurityProvider::new()));
98 self
99 }
100
101 pub fn with_security_provider(
103 mut self,
104 provider: Arc<dyn crate::security::SecurityProvider>,
105 ) -> Self {
106 self.security_provider = Some(provider);
107 self
108 }
109
110 pub fn with_fs_context(mut self, root_path: impl Into<PathBuf>) -> Self {
112 let config = crate::context::FileSystemContextConfig::new(root_path);
113 self.context_providers
114 .push(Arc::new(crate::context::FileSystemContextProvider::new(
115 config,
116 )));
117 self
118 }
119
120 pub fn with_context_provider(
122 mut self,
123 provider: Arc<dyn crate::context::ContextProvider>,
124 ) -> Self {
125 self.context_providers.push(provider);
126 self
127 }
128
129 pub fn with_confirmation_manager(
131 mut self,
132 manager: Arc<dyn crate::hitl::ConfirmationProvider>,
133 ) -> Self {
134 self.confirmation_manager = Some(manager);
135 self
136 }
137
138 pub fn with_confirmation_policy(mut self, policy: crate::hitl::ConfirmationPolicy) -> Self {
143 self.confirmation_policy = Some(policy);
144 self
145 }
146
147 pub fn with_permission_policy(mut self, policy: crate::permissions::PermissionPolicy) -> Self {
149 self.permission_checker = Some(Arc::new(policy.clone()));
150 self.permission_policy = Some(policy);
151 self
152 }
153
154 pub fn with_permission_checker(
156 mut self,
157 checker: Arc<dyn crate::permissions::PermissionChecker>,
158 ) -> Self {
159 self.permission_checker = Some(checker);
160 self
161 }
162
163 pub fn with_planning_mode(mut self, mode: PlanningMode) -> Self {
165 self.planning_mode = mode;
166 self
167 }
168
169 pub fn with_planning(mut self, enabled: bool) -> Self {
171 self.planning_mode = if enabled {
172 PlanningMode::Enabled
173 } else {
174 PlanningMode::Disabled
175 };
176 self
177 }
178
179 pub fn with_goal_tracking(mut self, enabled: bool) -> Self {
181 self.goal_tracking = enabled;
182 self
183 }
184
185 pub fn with_builtin_skills(mut self) -> Self {
187 self.skill_registry = Some(Arc::new(crate::skills::SkillRegistry::with_builtins()));
188 self
189 }
190
191 pub fn with_skill_registry(mut self, registry: Arc<crate::skills::SkillRegistry>) -> Self {
193 self.skill_registry = Some(registry);
194 self
195 }
196
197 pub fn with_skill_dirs(mut self, dirs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
200 self.skill_dirs.extend(dirs.into_iter().map(Into::into));
201 self
202 }
203
204 pub fn with_skills_from_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
206 let registry = self
207 .skill_registry
208 .unwrap_or_else(|| Arc::new(crate::skills::SkillRegistry::new()));
209 if let Err(e) = registry.load_from_dir(&dir) {
210 tracing::warn!(
211 dir = %dir.as_ref().display(),
212 error = %e,
213 "Failed to load skills from directory — continuing without them"
214 );
215 }
216 self.skill_registry = Some(registry);
217 self
218 }
219
220 pub fn with_memory(mut self, store: Arc<dyn MemoryStore>) -> Self {
222 self.memory_store = Some(store);
223 self
224 }
225
226 pub fn with_file_memory(mut self, dir: impl Into<PathBuf>) -> Self {
232 self.file_memory_dir = Some(dir.into());
233 self
234 }
235
236 pub fn with_session_store(mut self, store: Arc<dyn crate::store::SessionStore>) -> Self {
238 self.session_store = Some(store);
239 self
240 }
241
242 pub fn with_file_session_store(mut self, dir: impl Into<PathBuf>) -> Self {
244 let dir = dir.into();
245 match tokio::runtime::Handle::try_current() {
246 Ok(handle) => {
247 match tokio::task::block_in_place(|| {
248 handle.block_on(crate::store::FileSessionStore::new(dir))
249 }) {
250 Ok(store) => {
251 self.session_store =
252 Some(Arc::new(store) as Arc<dyn crate::store::SessionStore>);
253 }
254 Err(e) => {
255 tracing::warn!("Failed to create file session store: {}", e);
256 }
257 }
258 }
259 Err(_) => {
260 tracing::warn!(
261 "No async runtime available for file session store — persistence disabled"
262 );
263 }
264 }
265 self
266 }
267
268 pub fn with_session_id(mut self, id: impl Into<String>) -> Self {
270 self.session_id = Some(id.into());
271 self
272 }
273
274 pub fn with_auto_save(mut self, enabled: bool) -> Self {
276 self.auto_save = enabled;
277 self
278 }
279
280 pub fn with_artifact_store_limits(mut self, limits: crate::tools::ArtifactStoreLimits) -> Self {
282 self.artifact_store_limits = Some(limits);
283 self
284 }
285
286 pub fn with_parse_retries(mut self, max: u32) -> Self {
292 self.max_parse_retries = Some(max);
293 self
294 }
295
296 pub fn with_tool_timeout(mut self, timeout_ms: u64) -> Self {
302 self.tool_timeout_ms = Some(timeout_ms);
303 self
304 }
305
306 pub fn with_circuit_breaker(mut self, threshold: u32) -> Self {
312 self.circuit_breaker_threshold = Some(threshold);
313 self
314 }
315
316 pub fn with_resilience_defaults(self) -> Self {
322 self.with_parse_retries(2)
323 .with_tool_timeout(120_000)
324 .with_circuit_breaker(3)
325 }
326
327 pub fn with_sandbox_handle(mut self, handle: Arc<dyn crate::sandbox::BashSandbox>) -> Self {
335 self.sandbox_handle = Some(handle);
336 self
337 }
338
339 pub fn with_workspace_backend(
345 mut self,
346 services: Arc<crate::workspace::WorkspaceServices>,
347 ) -> Self {
348 self.workspace_services = Some(services);
349 self
350 }
351
352 pub fn with_auto_compact(mut self, enabled: bool) -> Self {
357 self.auto_compact = enabled;
358 self
359 }
360
361 pub fn with_auto_compact_threshold(mut self, threshold: f32) -> Self {
363 self.auto_compact_threshold = Some(threshold.clamp(0.0, 1.0));
364 self
365 }
366
367 pub fn with_continuation(mut self, enabled: bool) -> Self {
372 self.continuation_enabled = Some(enabled);
373 self
374 }
375
376 pub fn with_max_continuation_turns(mut self, turns: u32) -> Self {
378 self.max_continuation_turns = Some(turns);
379 self
380 }
381
382 pub fn with_mcp(mut self, manager: Arc<crate::mcp::manager::McpManager>) -> Self {
387 self.mcp_manager = Some(manager);
388 self
389 }
390
391 pub fn with_temperature(mut self, temperature: f32) -> Self {
392 self.temperature = Some(temperature);
393 self
394 }
395
396 pub fn with_thinking_budget(mut self, budget: usize) -> Self {
397 self.thinking_budget = Some(budget);
398 self
399 }
400
401 pub fn with_max_tool_rounds(mut self, rounds: usize) -> Self {
406 self.max_tool_rounds = Some(rounds);
407 self
408 }
409
410 pub fn with_prompt_slots(mut self, slots: SystemPromptSlots) -> Self {
415 self.prompt_slots = Some(slots);
416 self
417 }
418
419 pub fn with_hook_executor(mut self, executor: Arc<dyn crate::hooks::HookExecutor>) -> Self {
425 self.hook_executor = Some(executor);
426 self
427 }
428}