gitai/
common.rs

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