gitai/
common.rs

1use crate::config::Config;
2use crate::core::llm::get_available_provider_names;
3use crate::instruction_presets::{PresetType, get_instruction_preset_library};
4use anyhow::Result;
5use clap::Args;
6use std::fmt::Write;
7use std::str::FromStr;
8
9#[derive(Clone, Copy, PartialEq, Eq, Debug)]
10pub enum DetailLevel {
11    Minimal,
12    Standard,
13    Detailed,
14}
15
16impl FromStr for DetailLevel {
17    type Err = anyhow::Error;
18
19    fn from_str(s: &str) -> Result<Self, Self::Err> {
20        match s.to_lowercase().as_str() {
21            "minimal" => Ok(Self::Minimal),
22            "standard" => Ok(Self::Standard),
23            "detailed" => Ok(Self::Detailed),
24            _ => Err(anyhow::anyhow!("Invalid detail level: {s}")),
25        }
26    }
27}
28
29impl DetailLevel {
30    pub fn as_str(&self) -> &'static str {
31        match self {
32            Self::Minimal => "minimal",
33            Self::Standard => "standard",
34            Self::Detailed => "detailed",
35        }
36    }
37}
38
39#[derive(Args, Clone, Default, Debug)]
40pub struct CommonParams {
41    /// Override default LLM provider
42    #[arg(long, help = "Override default LLM provider", value_parser = available_providers_parser)]
43    pub provider: Option<String>,
44
45    /// Custom instructions for this operation
46    #[arg(short, long, help = "Custom instructions for this operation")]
47    pub instructions: Option<String>,
48
49    /// Select an instruction preset
50    #[arg(
51        long,
52        help = "Select an instruction preset (use 'git presets' to see available presets for commits and reviews)"
53    )]
54    pub preset: Option<String>,
55
56    /// Enable or disable emoji
57    #[arg(long, help = "Enable or disable emoji")]
58    pub emoji: Option<bool>,
59
60    /// Set the detail level
61    #[arg(
62        long,
63        help = "Set the detail level (minimal, standard, detailed)",
64        default_value = "standard"
65    )]
66    pub detail_level: String,
67
68    /// Repository URL to use instead of local repository
69    #[arg(
70        short = 'r',
71        long = "repo",
72        help = "Repository URL to use instead of local repository"
73    )]
74    pub repository_url: Option<String>,
75}
76
77impl CommonParams {
78    pub fn apply_to_config(&self, config: &mut Config) -> Result<bool> {
79        let mut changes_made = false;
80
81        if let Some(provider) = &self.provider {
82            // Convert "claude" to "anthropic" for backward compatibility
83            let provider_name = if provider.to_lowercase() == "claude" {
84                "anthropic".to_string()
85            } else if provider.to_lowercase() == "openrouter" {
86                "openrouter".to_string()
87            } else {
88                provider.clone()
89            };
90
91            // Check if we need to update the default provider
92            if config.default_provider != provider_name {
93                // Ensure the provider exists in the providers HashMap
94                if !config.providers.contains_key(&provider_name) {
95                    // Import ProviderConfig here
96                    use crate::config::ProviderConfig;
97                    config.providers.insert(
98                        provider_name.clone(),
99                        ProviderConfig::default_for(&provider_name),
100                    );
101                }
102
103                config.default_provider.clone_from(&provider_name);
104                changes_made = true;
105            }
106        }
107
108        if let Some(instructions) = &self.instructions {
109            config.set_temp_instructions(Some(instructions.clone()));
110            // Note: temp instructions don't count as permanent changes
111        }
112
113        if let Some(preset) = &self.preset {
114            config.set_temp_preset(Some(preset.clone()));
115            // Note: temp preset doesn't count as permanent changes
116        }
117
118        if let Some(use_emoji) = self.emoji
119            && config.use_emoji != use_emoji
120        {
121            config.use_emoji = use_emoji;
122            changes_made = true;
123        }
124
125        Ok(changes_made)
126    }
127
128    /// Check if the provided preset is valid for the specified preset type
129    pub fn is_valid_preset_for_type(&self, preset_type: PresetType) -> bool {
130        if let Some(preset_name) = &self.preset {
131            let library = get_instruction_preset_library();
132            let valid_presets = library.list_valid_presets_for_command(preset_type);
133            return valid_presets.iter().any(|(key, _)| *key == preset_name);
134        }
135        true // No preset specified is always valid
136    }
137}
138
139/// Validates that a provider name is available in the system
140pub fn available_providers_parser(s: &str) -> Result<String, String> {
141    let mut provider_name = s.to_lowercase();
142
143    // Handle legacy "claude" provider name by mapping it to "anthropic"
144    if provider_name == "claude" {
145        provider_name = "anthropic".to_string();
146    }
147
148    let available_providers = get_available_provider_names();
149
150    if available_providers
151        .iter()
152        .any(|p| p.to_lowercase() == provider_name)
153    {
154        Ok(provider_name)
155    } else {
156        Err(format!(
157            "Invalid provider '{}'. Available providers: {}",
158            s,
159            available_providers.join(", ")
160        ))
161    }
162}
163
164pub fn get_combined_instructions(config: &Config) -> String {
165    let mut prompt = String::from("\n\n");
166
167    if !config.instructions.is_empty() {
168        write!(
169            &mut prompt,
170            "\n\nAdditional instructions for the request:\n{}\n\n",
171            config.instructions
172        )
173        .expect("write to string should not fail");
174    }
175
176    let preset_library = get_instruction_preset_library();
177    if let Some(preset_instructions) = preset_library.get_preset(config.instruction_preset.as_str())
178    {
179        write!(
180            &mut prompt,
181            "\n\nIMPORTANT: Use this style for your output:\n{}\n\n",
182            preset_instructions.instructions
183        )
184        .expect("write to string should not fail");
185    }
186
187    prompt
188}