1use crate::constants::reasoning;
4use crate::core::PromptCachingConfig;
5use hashbrown::HashMap;
6use serde::{Deserialize, Deserializer, Serialize};
7use serde_json::Value;
8use std::collections::BTreeMap;
9use std::fmt;
10use std::path::PathBuf;
11
12#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
18#[serde(rename_all = "lowercase")]
19#[derive(Default)]
20pub enum ReasoningEffortLevel {
21 None,
23 Minimal,
25 Low,
27 #[default]
29 Medium,
30 High,
32 XHigh,
34}
35
36impl ReasoningEffortLevel {
37 pub fn as_str(self) -> &'static str {
39 match self {
40 Self::None => "none",
41 Self::Minimal => "minimal",
42 Self::Low => reasoning::LOW,
43 Self::Medium => reasoning::MEDIUM,
44 Self::High => reasoning::HIGH,
45 Self::XHigh => "xhigh",
46 }
47 }
48
49 pub fn parse(value: &str) -> Option<Self> {
51 let normalized = value.trim();
52 if normalized.eq_ignore_ascii_case("none") {
53 Some(Self::None)
54 } else if normalized.eq_ignore_ascii_case("minimal") {
55 Some(Self::Minimal)
56 } else if normalized.eq_ignore_ascii_case(reasoning::LOW) {
57 Some(Self::Low)
58 } else if normalized.eq_ignore_ascii_case(reasoning::MEDIUM) {
59 Some(Self::Medium)
60 } else if normalized.eq_ignore_ascii_case(reasoning::HIGH) {
61 Some(Self::High)
62 } else if normalized.eq_ignore_ascii_case("xhigh") {
63 Some(Self::XHigh)
64 } else {
65 None
66 }
67 }
68
69 pub fn allowed_values() -> &'static [&'static str] {
71 reasoning::ALLOWED_LEVELS
72 }
73}
74
75#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
79#[serde(rename_all = "lowercase")]
80#[derive(Default)]
81pub enum SystemPromptMode {
82 Minimal,
85 Lightweight,
88 #[default]
91 Default,
92 Specialized,
95}
96
97impl SystemPromptMode {
98 pub fn as_str(self) -> &'static str {
100 match self {
101 Self::Minimal => "minimal",
102 Self::Lightweight => "lightweight",
103 Self::Default => "default",
104 Self::Specialized => "specialized",
105 }
106 }
107
108 pub fn parse(value: &str) -> Option<Self> {
110 let normalized = value.trim();
111 if normalized.eq_ignore_ascii_case("minimal") {
112 Some(Self::Minimal)
113 } else if normalized.eq_ignore_ascii_case("lightweight") {
114 Some(Self::Lightweight)
115 } else if normalized.eq_ignore_ascii_case("default") {
116 Some(Self::Default)
117 } else if normalized.eq_ignore_ascii_case("specialized") {
118 Some(Self::Specialized)
119 } else {
120 None
121 }
122 }
123
124 pub fn allowed_values() -> &'static [&'static str] {
126 &["minimal", "lightweight", "default", "specialized"]
127 }
128}
129
130impl fmt::Display for SystemPromptMode {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 f.write_str(self.as_str())
133 }
134}
135
136impl<'de> Deserialize<'de> for SystemPromptMode {
137 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138 where
139 D: Deserializer<'de>,
140 {
141 let raw = String::deserialize(deserializer)?;
142 if let Some(parsed) = Self::parse(&raw) {
143 Ok(parsed)
144 } else {
145 Ok(Self::default())
146 }
147 }
148}
149
150#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
154#[serde(rename_all = "lowercase")]
155#[derive(Default)]
156pub enum ToolDocumentationMode {
157 Minimal,
160 #[default]
163 Progressive,
164 Full,
167}
168
169impl ToolDocumentationMode {
170 pub fn as_str(self) -> &'static str {
172 match self {
173 Self::Minimal => "minimal",
174 Self::Progressive => "progressive",
175 Self::Full => "full",
176 }
177 }
178
179 pub fn parse(value: &str) -> Option<Self> {
181 let normalized = value.trim();
182 if normalized.eq_ignore_ascii_case("minimal") {
183 Some(Self::Minimal)
184 } else if normalized.eq_ignore_ascii_case("progressive") {
185 Some(Self::Progressive)
186 } else if normalized.eq_ignore_ascii_case("full") {
187 Some(Self::Full)
188 } else {
189 None
190 }
191 }
192
193 pub fn allowed_values() -> &'static [&'static str] {
195 &["minimal", "progressive", "full"]
196 }
197}
198
199impl fmt::Display for ToolDocumentationMode {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.write_str(self.as_str())
202 }
203}
204
205impl<'de> Deserialize<'de> for ToolDocumentationMode {
206 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
207 where
208 D: Deserializer<'de>,
209 {
210 let raw = String::deserialize(deserializer)?;
211 if let Some(parsed) = Self::parse(&raw) {
212 Ok(parsed)
213 } else {
214 Ok(Self::default())
215 }
216 }
217}
218
219#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
222#[serde(rename_all = "lowercase")]
223#[derive(Default)]
224pub enum VerbosityLevel {
225 Low,
226 #[default]
227 Medium,
228 High,
229}
230
231impl VerbosityLevel {
232 pub fn as_str(self) -> &'static str {
234 match self {
235 Self::Low => "low",
236 Self::Medium => "medium",
237 Self::High => "high",
238 }
239 }
240
241 pub fn parse(value: &str) -> Option<Self> {
243 let normalized = value.trim();
244 if normalized.eq_ignore_ascii_case("low") {
245 Some(Self::Low)
246 } else if normalized.eq_ignore_ascii_case("medium") {
247 Some(Self::Medium)
248 } else if normalized.eq_ignore_ascii_case("high") {
249 Some(Self::High)
250 } else {
251 None
252 }
253 }
254
255 pub fn allowed_values() -> &'static [&'static str] {
257 &["low", "medium", "high"]
258 }
259}
260
261impl fmt::Display for VerbosityLevel {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 f.write_str(self.as_str())
264 }
265}
266
267impl<'de> Deserialize<'de> for VerbosityLevel {
268 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
269 where
270 D: Deserializer<'de>,
271 {
272 let raw = String::deserialize(deserializer)?;
273 if let Some(parsed) = Self::parse(&raw) {
274 Ok(parsed)
275 } else {
276 Ok(Self::default())
277 }
278 }
279}
280
281impl fmt::Display for ReasoningEffortLevel {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 f.write_str(self.as_str())
284 }
285}
286
287impl<'de> Deserialize<'de> for ReasoningEffortLevel {
288 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
289 where
290 D: Deserializer<'de>,
291 {
292 let raw = String::deserialize(deserializer)?;
293 if let Some(parsed) = Self::parse(&raw) {
294 Ok(parsed)
295 } else {
296 Ok(Self::default())
297 }
298 }
299}
300
301#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
304#[serde(rename_all = "lowercase")]
305#[derive(Default)]
306pub enum UiSurfacePreference {
307 #[default]
308 Auto,
309 Alternate,
310 Inline,
311}
312
313impl UiSurfacePreference {
314 pub fn as_str(self) -> &'static str {
316 match self {
317 Self::Auto => "auto",
318 Self::Alternate => "alternate",
319 Self::Inline => "inline",
320 }
321 }
322
323 pub fn parse(value: &str) -> Option<Self> {
325 let normalized = value.trim();
326 if normalized.eq_ignore_ascii_case("auto") {
327 Some(Self::Auto)
328 } else if normalized.eq_ignore_ascii_case("alternate")
329 || normalized.eq_ignore_ascii_case("alt")
330 {
331 Some(Self::Alternate)
332 } else if normalized.eq_ignore_ascii_case("inline") {
333 Some(Self::Inline)
334 } else {
335 None
336 }
337 }
338
339 pub fn allowed_values() -> &'static [&'static str] {
341 &["auto", "alternate", "inline"]
342 }
343}
344
345impl fmt::Display for UiSurfacePreference {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 f.write_str(self.as_str())
348 }
349}
350
351impl<'de> Deserialize<'de> for UiSurfacePreference {
352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
353 where
354 D: Deserializer<'de>,
355 {
356 let raw = String::deserialize(deserializer)?;
357 if let Some(parsed) = Self::parse(&raw) {
358 Ok(parsed)
359 } else {
360 Ok(Self::default())
361 }
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
367pub enum ModelSelectionSource {
368 #[default]
370 WorkspaceConfig,
371 CliOverride,
373}
374
375#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
385#[serde(rename_all = "lowercase")]
386#[derive(Default)]
387pub enum EditingMode {
388 #[default]
391 Edit,
392 Plan,
396}
397
398impl EditingMode {
399 pub fn as_str(self) -> &'static str {
401 match self {
402 Self::Edit => "edit",
403 Self::Plan => "plan",
404 }
405 }
406
407 pub fn parse(value: &str) -> Option<Self> {
409 let normalized = value.trim();
410 if normalized.eq_ignore_ascii_case("edit") {
411 Some(Self::Edit)
412 } else if normalized.eq_ignore_ascii_case("plan") {
413 Some(Self::Plan)
414 } else {
415 None
416 }
417 }
418
419 pub fn allowed_values() -> &'static [&'static str] {
421 &["edit", "plan"]
422 }
423
424 pub fn can_modify_files(self) -> bool {
426 matches!(self, Self::Edit)
427 }
428
429 pub fn can_execute_commands(self) -> bool {
431 matches!(self, Self::Edit)
432 }
433
434 pub fn is_read_only(self) -> bool {
436 matches!(self, Self::Plan)
437 }
438}
439
440impl fmt::Display for EditingMode {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 f.write_str(self.as_str())
443 }
444}
445
446impl<'de> Deserialize<'de> for EditingMode {
447 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
448 where
449 D: Deserializer<'de>,
450 {
451 let raw = String::deserialize(deserializer)?;
452 if let Some(parsed) = Self::parse(&raw) {
453 Ok(parsed)
454 } else {
455 Ok(Self::default())
456 }
457 }
458}
459
460#[derive(Debug, Clone)]
462pub struct AgentConfig {
463 pub model: String,
464 pub api_key: String,
465 pub provider: String,
466 pub api_key_env: String,
467 pub workspace: PathBuf,
468 pub verbose: bool,
469 pub quiet: bool,
470 pub theme: String,
471 pub reasoning_effort: ReasoningEffortLevel,
472 pub ui_surface: UiSurfacePreference,
473 pub prompt_cache: PromptCachingConfig,
474 pub model_source: ModelSelectionSource,
475 pub custom_api_keys: BTreeMap<String, String>,
476 pub checkpointing_enabled: bool,
477 pub checkpointing_storage_dir: Option<PathBuf>,
478 pub checkpointing_max_snapshots: usize,
479 pub checkpointing_max_age_days: Option<u64>,
480 pub max_conversation_turns: usize,
481 pub model_behavior: Option<crate::core::ModelConfig>,
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
486pub enum CapabilityLevel {
487 Basic,
489 FileReading,
491 FileListing,
493 Bash,
495 Editing,
497 CodeSearch,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct SessionInfo {
504 pub session_id: String,
505 pub start_time: u64,
506 pub total_turns: usize,
507 pub total_decisions: usize,
508 pub error_count: usize,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct ConversationTurn {
514 pub turn_number: usize,
515 pub timestamp: u64,
516 pub user_input: Option<String>,
517 pub agent_response: Option<String>,
518 pub tool_calls: Vec<ToolCallInfo>,
519 pub decision: Option<DecisionInfo>,
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct ToolCallInfo {
525 pub name: String,
526 pub args: Value,
527 pub result: Option<Value>,
528 pub error: Option<String>,
529 pub execution_time_ms: Option<u64>,
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
534pub struct DecisionInfo {
535 pub turn_number: usize,
536 pub action_type: String,
537 pub description: String,
538 pub reasoning: String,
539 pub outcome: Option<String>,
540 pub confidence_score: Option<f64>,
541 pub timestamp: u64,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct ErrorInfo {
547 pub error_type: String,
548 pub message: String,
549 pub turn_number: usize,
550 pub recoverable: bool,
551 pub timestamp: u64,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct TaskInfo {
557 pub task_type: String,
558 pub description: String,
559 pub completed: bool,
560 pub success: bool,
561 pub duration_seconds: Option<u64>,
562 pub tools_used: Vec<String>,
563 pub dependencies: Vec<String>,
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct ProjectSpec {
569 pub name: String,
570 pub features: Vec<String>,
571 pub template: Option<String>,
572 pub dependencies: HashMap<String, String>,
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct WorkspaceAnalysis {
578 pub root_path: String,
579 pub project_type: Option<String>,
580 pub languages: Vec<String>,
581 pub frameworks: Vec<String>,
582 pub config_files: Vec<String>,
583 pub source_files: Vec<String>,
584 pub test_files: Vec<String>,
585 pub documentation_files: Vec<String>,
586 pub total_files: usize,
587 pub total_size_bytes: u64,
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize)]
592pub struct CommandResult {
593 pub command: String,
594 pub success: bool,
595 pub stdout: String,
596 pub stderr: String,
597 pub exit_code: Option<i32>,
598 pub execution_time_ms: u64,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct FileOperationResult {
604 pub operation: String,
605 pub path: String,
606 pub success: bool,
607 pub details: HashMap<String, Value>,
608 pub error: Option<String>,
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct PerformanceMetrics {
614 pub session_duration_seconds: u64,
615 pub total_api_calls: usize,
616 pub total_tokens_used: Option<usize>,
617 pub average_response_time_ms: f64,
618 pub tool_execution_count: usize,
619 pub error_count: usize,
620 pub recovery_success_rate: f64,
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct QualityMetrics {
626 pub decision_confidence_avg: f64,
627 pub tool_success_rate: f64,
628 pub error_recovery_rate: f64,
629 pub context_preservation_rate: f64,
630 pub user_satisfaction_score: Option<f64>,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize)]
635pub struct ToolConfig {
636 pub enable_validation: bool,
637 pub max_execution_time_seconds: u64,
638 pub allow_file_creation: bool,
639 pub allow_file_deletion: bool,
640 pub working_directory: Option<String>,
641}
642
643#[derive(Debug, Clone, Serialize, Deserialize)]
645pub struct ContextConfig {
646 pub max_context_length: usize,
647 pub compression_threshold: usize,
648 pub summarization_interval: usize,
649 pub preservation_priority: Vec<String>,
650}
651
652#[derive(Debug, Clone, Serialize, Deserialize)]
654pub struct LoggingConfig {
655 pub level: String,
656 pub file_logging: bool,
657 pub log_directory: Option<String>,
658 pub max_log_files: usize,
659 pub max_log_size_mb: usize,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize)]
664pub enum AnalysisDepth {
665 Basic,
666 Standard,
667 Deep,
668}
669
670#[derive(Debug, Clone, Serialize, Deserialize)]
672pub enum OutputFormat {
673 Text,
674 Json,
675 Html,
676}
677
678#[derive(Debug, Clone, Serialize, Deserialize)]
680pub enum CompressionLevel {
681 Light,
682 Medium,
683 Aggressive,
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689
690 #[test]
691 fn test_editing_mode_parse() {
692 assert_eq!(EditingMode::parse("edit"), Some(EditingMode::Edit));
693 assert_eq!(EditingMode::parse("EDIT"), Some(EditingMode::Edit));
694 assert_eq!(EditingMode::parse("Edit"), Some(EditingMode::Edit));
695 assert_eq!(EditingMode::parse("plan"), Some(EditingMode::Plan));
696 assert_eq!(EditingMode::parse("PLAN"), Some(EditingMode::Plan));
697 assert_eq!(EditingMode::parse("Plan"), Some(EditingMode::Plan));
698 assert_eq!(EditingMode::parse("agent"), None);
699 assert_eq!(EditingMode::parse("invalid"), None);
700 assert_eq!(EditingMode::parse(""), None);
701 }
702
703 #[test]
704 fn test_editing_mode_as_str() {
705 assert_eq!(EditingMode::Edit.as_str(), "edit");
706 assert_eq!(EditingMode::Plan.as_str(), "plan");
707 }
708
709 #[test]
710 fn test_editing_mode_capabilities() {
711 assert!(EditingMode::Edit.can_modify_files());
713 assert!(EditingMode::Edit.can_execute_commands());
714 assert!(!EditingMode::Edit.is_read_only());
715
716 assert!(!EditingMode::Plan.can_modify_files());
718 assert!(!EditingMode::Plan.can_execute_commands());
719 assert!(EditingMode::Plan.is_read_only());
720 }
721
722 #[test]
723 fn test_editing_mode_default() {
724 assert_eq!(EditingMode::default(), EditingMode::Edit);
725 }
726
727 #[test]
728 fn test_editing_mode_display() {
729 assert_eq!(format!("{}", EditingMode::Edit), "edit");
730 assert_eq!(format!("{}", EditingMode::Plan), "plan");
731 }
732
733 #[test]
734 fn test_editing_mode_allowed_values() {
735 let values = EditingMode::allowed_values();
736 assert_eq!(values, &["edit", "plan"]);
737 }
738}