use crate::constants::reasoning;
use crate::core::PromptCachingConfig;
use hashbrown::HashMap;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum ReasoningEffortLevel {
None,
Minimal,
Low,
#[default]
Medium,
High,
XHigh,
}
impl ReasoningEffortLevel {
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Minimal => "minimal",
Self::Low => reasoning::LOW,
Self::Medium => reasoning::MEDIUM,
Self::High => reasoning::HIGH,
Self::XHigh => "xhigh",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("none") {
Some(Self::None)
} else if normalized.eq_ignore_ascii_case("minimal") {
Some(Self::Minimal)
} else if normalized.eq_ignore_ascii_case(reasoning::LOW) {
Some(Self::Low)
} else if normalized.eq_ignore_ascii_case(reasoning::MEDIUM) {
Some(Self::Medium)
} else if normalized.eq_ignore_ascii_case(reasoning::HIGH) {
Some(Self::High)
} else if normalized.eq_ignore_ascii_case("xhigh") {
Some(Self::XHigh)
} else {
None
}
}
pub fn allowed_values() -> &'static [&'static str] {
reasoning::ALLOWED_LEVELS
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum SystemPromptMode {
Minimal,
Lightweight,
#[default]
Default,
Specialized,
}
impl SystemPromptMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Minimal => "minimal",
Self::Lightweight => "lightweight",
Self::Default => "default",
Self::Specialized => "specialized",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("minimal") {
Some(Self::Minimal)
} else if normalized.eq_ignore_ascii_case("lightweight") {
Some(Self::Lightweight)
} else if normalized.eq_ignore_ascii_case("default") {
Some(Self::Default)
} else if normalized.eq_ignore_ascii_case("specialized") {
Some(Self::Specialized)
} else {
None
}
}
pub fn allowed_values() -> &'static [&'static str] {
&["minimal", "lightweight", "default", "specialized"]
}
}
impl fmt::Display for SystemPromptMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for SystemPromptMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if let Some(parsed) = Self::parse(&raw) {
Ok(parsed)
} else {
Ok(Self::default())
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum ToolDocumentationMode {
Minimal,
#[default]
Progressive,
Full,
}
impl ToolDocumentationMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Minimal => "minimal",
Self::Progressive => "progressive",
Self::Full => "full",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("minimal") {
Some(Self::Minimal)
} else if normalized.eq_ignore_ascii_case("progressive") {
Some(Self::Progressive)
} else if normalized.eq_ignore_ascii_case("full") {
Some(Self::Full)
} else {
None
}
}
pub fn allowed_values() -> &'static [&'static str] {
&["minimal", "progressive", "full"]
}
}
impl fmt::Display for ToolDocumentationMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ToolDocumentationMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if let Some(parsed) = Self::parse(&raw) {
Ok(parsed)
} else {
Ok(Self::default())
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum VerbosityLevel {
Low,
#[default]
Medium,
High,
}
impl VerbosityLevel {
pub fn as_str(self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("low") {
Some(Self::Low)
} else if normalized.eq_ignore_ascii_case("medium") {
Some(Self::Medium)
} else if normalized.eq_ignore_ascii_case("high") {
Some(Self::High)
} else {
None
}
}
pub fn allowed_values() -> &'static [&'static str] {
&["low", "medium", "high"]
}
}
impl fmt::Display for VerbosityLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for VerbosityLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if let Some(parsed) = Self::parse(&raw) {
Ok(parsed)
} else {
Ok(Self::default())
}
}
}
impl fmt::Display for ReasoningEffortLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ReasoningEffortLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if let Some(parsed) = Self::parse(&raw) {
Ok(parsed)
} else {
Ok(Self::default())
}
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum UiSurfacePreference {
#[default]
Auto,
Alternate,
Inline,
}
impl UiSurfacePreference {
pub fn as_str(self) -> &'static str {
match self {
Self::Auto => "auto",
Self::Alternate => "alternate",
Self::Inline => "inline",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("auto") {
Some(Self::Auto)
} else if normalized.eq_ignore_ascii_case("alternate")
|| normalized.eq_ignore_ascii_case("alt")
{
Some(Self::Alternate)
} else if normalized.eq_ignore_ascii_case("inline") {
Some(Self::Inline)
} else {
None
}
}
pub fn allowed_values() -> &'static [&'static str] {
&["auto", "alternate", "inline"]
}
}
impl fmt::Display for UiSurfacePreference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for UiSurfacePreference {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if let Some(parsed) = Self::parse(&raw) {
Ok(parsed)
} else {
Ok(Self::default())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ModelSelectionSource {
#[default]
WorkspaceConfig,
CliOverride,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum EditingMode {
#[default]
Edit,
Plan,
}
impl EditingMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Edit => "edit",
Self::Plan => "plan",
}
}
pub fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("edit") {
Some(Self::Edit)
} else if normalized.eq_ignore_ascii_case("plan") {
Some(Self::Plan)
} else {
None
}
}
pub fn allowed_values() -> &'static [&'static str] {
&["edit", "plan"]
}
pub fn can_modify_files(self) -> bool {
matches!(self, Self::Edit)
}
pub fn can_execute_commands(self) -> bool {
matches!(self, Self::Edit)
}
pub fn is_read_only(self) -> bool {
matches!(self, Self::Plan)
}
}
impl fmt::Display for EditingMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for EditingMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if let Some(parsed) = Self::parse(&raw) {
Ok(parsed)
} else {
Ok(Self::default())
}
}
}
#[derive(Debug, Clone)]
pub struct AgentConfig {
pub model: String,
pub api_key: String,
pub provider: String,
pub openai_chatgpt_auth: Option<crate::auth::OpenAIChatGptAuthHandle>,
pub api_key_env: String,
pub workspace: PathBuf,
pub verbose: bool,
pub quiet: bool,
pub theme: String,
pub reasoning_effort: ReasoningEffortLevel,
pub ui_surface: UiSurfacePreference,
pub prompt_cache: PromptCachingConfig,
pub model_source: ModelSelectionSource,
pub custom_api_keys: BTreeMap<String, String>,
pub checkpointing_enabled: bool,
pub checkpointing_storage_dir: Option<PathBuf>,
pub checkpointing_max_snapshots: usize,
pub checkpointing_max_age_days: Option<u64>,
pub max_conversation_turns: usize,
pub model_behavior: Option<crate::core::ModelConfig>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CapabilityLevel {
Basic,
FileReading,
FileListing,
Bash,
Editing,
CodeSearch,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
pub session_id: String,
pub start_time: u64,
pub total_turns: usize,
pub total_decisions: usize,
pub error_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationTurn {
pub turn_number: usize,
pub timestamp: u64,
pub user_input: Option<String>,
pub agent_response: Option<String>,
pub tool_calls: Vec<ToolCallInfo>,
pub decision: Option<DecisionInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallInfo {
pub name: String,
pub args: Value,
pub result: Option<Value>,
pub error: Option<String>,
pub execution_time_ms: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecisionInfo {
pub turn_number: usize,
pub action_type: String,
pub description: String,
pub reasoning: String,
pub outcome: Option<String>,
pub confidence_score: Option<f64>,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorInfo {
pub error_type: String,
pub message: String,
pub turn_number: usize,
pub recoverable: bool,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskInfo {
pub task_type: String,
pub description: String,
pub completed: bool,
pub success: bool,
pub duration_seconds: Option<u64>,
pub tools_used: Vec<String>,
pub dependencies: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectSpec {
pub name: String,
pub features: Vec<String>,
pub template: Option<String>,
pub dependencies: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceAnalysis {
pub root_path: String,
pub project_type: Option<String>,
pub languages: Vec<String>,
pub frameworks: Vec<String>,
pub config_files: Vec<String>,
pub source_files: Vec<String>,
pub test_files: Vec<String>,
pub documentation_files: Vec<String>,
pub total_files: usize,
pub total_size_bytes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandResult {
pub command: String,
pub success: bool,
pub stdout: String,
pub stderr: String,
pub exit_code: Option<i32>,
pub execution_time_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileOperationResult {
pub operation: String,
pub path: String,
pub success: bool,
pub details: HashMap<String, Value>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
pub session_duration_seconds: u64,
pub total_api_calls: usize,
pub total_tokens_used: Option<usize>,
pub average_response_time_ms: f64,
pub tool_execution_count: usize,
pub error_count: usize,
pub recovery_success_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityMetrics {
pub decision_confidence_avg: f64,
pub tool_success_rate: f64,
pub error_recovery_rate: f64,
pub context_preservation_rate: f64,
pub user_satisfaction_score: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolConfig {
pub enable_validation: bool,
pub max_execution_time_seconds: u64,
pub allow_file_creation: bool,
pub allow_file_deletion: bool,
pub working_directory: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextConfig {
pub max_context_length: usize,
pub compression_threshold: usize,
pub summarization_interval: usize,
pub preservation_priority: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub level: String,
pub file_logging: bool,
pub log_directory: Option<String>,
pub max_log_files: usize,
pub max_log_size_mb: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AnalysisDepth {
Basic,
Standard,
Deep,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OutputFormat {
Text,
Json,
Html,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CompressionLevel {
Light,
Medium,
Aggressive,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_editing_mode_parse() {
assert_eq!(EditingMode::parse("edit"), Some(EditingMode::Edit));
assert_eq!(EditingMode::parse("EDIT"), Some(EditingMode::Edit));
assert_eq!(EditingMode::parse("Edit"), Some(EditingMode::Edit));
assert_eq!(EditingMode::parse("plan"), Some(EditingMode::Plan));
assert_eq!(EditingMode::parse("PLAN"), Some(EditingMode::Plan));
assert_eq!(EditingMode::parse("Plan"), Some(EditingMode::Plan));
assert_eq!(EditingMode::parse("agent"), None);
assert_eq!(EditingMode::parse("invalid"), None);
assert_eq!(EditingMode::parse(""), None);
}
#[test]
fn test_editing_mode_as_str() {
assert_eq!(EditingMode::Edit.as_str(), "edit");
assert_eq!(EditingMode::Plan.as_str(), "plan");
}
#[test]
fn test_editing_mode_capabilities() {
assert!(EditingMode::Edit.can_modify_files());
assert!(EditingMode::Edit.can_execute_commands());
assert!(!EditingMode::Edit.is_read_only());
assert!(!EditingMode::Plan.can_modify_files());
assert!(!EditingMode::Plan.can_execute_commands());
assert!(EditingMode::Plan.is_read_only());
}
#[test]
fn test_editing_mode_default() {
assert_eq!(EditingMode::default(), EditingMode::Edit);
}
#[test]
fn test_editing_mode_display() {
assert_eq!(format!("{}", EditingMode::Edit), "edit");
assert_eq!(format!("{}", EditingMode::Plan), "plan");
}
#[test]
fn test_editing_mode_allowed_values() {
let values = EditingMode::allowed_values();
assert_eq!(values, &["edit", "plan"]);
}
}