magi-code 0.64.0

Repository-aware CLI coding agent for terminal work
Documentation
use crate::{
    config::{AuthState, EffectiveConfig, McPaths},
    shell::ShellState,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ModelSwitchResult {
    pub(crate) provider: String,
    pub(crate) model: String,
    pub(crate) notice: Option<String>,
}

pub(crate) fn switch_model_selection(
    paths: &McPaths,
    auth_state: &AuthState,
    model_id: &str,
) -> anyhow::Result<ModelSwitchResult> {
    let (provider, model, notice) =
        crate::model_catalog::switch_model(paths, auth_state, model_id)?;
    Ok(ModelSwitchResult {
        provider,
        model,
        notice,
    })
}

pub(crate) fn switch_shell_model(
    state: &mut ShellState,
    model_id: &str,
) -> anyhow::Result<ModelSwitchResult> {
    let config = state
        .config
        .as_ref()
        .ok_or_else(|| anyhow::anyhow!("/setmodel requires loaded runtime config"))?;
    let disabled = crate::config::disabled_model_ids_from_settings(&crate::config::read_settings(
        &config.paths,
    )?);
    if disabled.contains(model_id) {
        anyhow::bail!("cannot select disabled model: {model_id}");
    }
    let result = switch_model_selection(&config.paths, &state.auth_state, model_id)?;
    refresh_runtime_provider_selection(state, &result.provider, &result.model)?;
    Ok(result)
}

pub(crate) fn refresh_runtime_provider_selection(
    state: &mut ShellState,
    provider: &str,
    model: &str,
) -> anyhow::Result<()> {
    let Some(config) = state.config.as_ref() else {
        state.model = model.to_string();
        return Ok(());
    };
    let refreshed =
        crate::config::load_effective_provider_selection(&config.paths, provider, model)?;
    state.model = model.to_string();
    state.auth_state = refreshed.auth_state();
    state.config = Some(refreshed);
    Ok(())
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum LogoutRemoval {
    CustomRemoved { provider_id: String },
    CustomMissing { provider_id: String },
    ProviderRemoved { label: String, id: String },
    ProviderMissing { label: String, id: String },
}

impl LogoutRemoval {
    pub(crate) fn message(&self) -> String {
        match self {
            Self::CustomRemoved { provider_id } => format!("removed custom provider {provider_id}"),
            Self::CustomMissing { provider_id } => {
                format!("custom provider {provider_id} is not configured")
            }
            Self::ProviderRemoved { label, id } => {
                format!("removed local auth for {label} ({id})")
            }
            Self::ProviderMissing { label, id } => {
                format!("{label} ({id}) is not configured; credentials unchanged")
            }
        }
    }

    pub(crate) fn provider_id(&self) -> &str {
        match self {
            Self::CustomRemoved { provider_id } | Self::CustomMissing { provider_id } => {
                provider_id
            }
            Self::ProviderRemoved { id, .. } | Self::ProviderMissing { id, .. } => id,
        }
    }

    pub(crate) fn removed(&self) -> bool {
        matches!(
            self,
            Self::CustomRemoved { .. } | Self::ProviderRemoved { .. }
        )
    }

    pub(crate) fn removed_custom_provider(&self) -> Option<&str> {
        match self {
            Self::CustomRemoved { provider_id } => Some(provider_id),
            Self::CustomMissing { .. }
            | Self::ProviderRemoved { .. }
            | Self::ProviderMissing { .. } => None,
        }
    }
}

pub(crate) fn remove_logout_target(
    paths: &McPaths,
    provider_id: &str,
) -> anyhow::Result<LogoutRemoval> {
    if crate::config::read_settings(paths)?
        .custom_providers
        .contains_key(provider_id)
    {
        return remove_custom_logout_target(paths, provider_id);
    }
    remove_provider_logout_target(paths, provider_id)
}

fn remove_custom_logout_target(
    paths: &McPaths,
    provider_id: &str,
) -> anyhow::Result<LogoutRemoval> {
    Ok(
        if crate::config::remove_custom_provider(paths, provider_id)? {
            LogoutRemoval::CustomRemoved {
                provider_id: provider_id.to_string(),
            }
        } else {
            LogoutRemoval::CustomMissing {
                provider_id: provider_id.to_string(),
            }
        },
    )
}

fn remove_provider_logout_target(
    paths: &McPaths,
    provider_id: &str,
) -> anyhow::Result<LogoutRemoval> {
    let provider = crate::login::validate_logout_provider(provider_id)?;
    let removal = crate::config::remove_provider_auth(paths, provider.id)?;
    Ok(if removal.removed {
        LogoutRemoval::ProviderRemoved {
            label: provider.label.to_string(),
            id: provider.id.to_string(),
        }
    } else {
        LogoutRemoval::ProviderMissing {
            label: provider.label.to_string(),
            id: provider.id.to_string(),
        }
    })
}

pub(crate) fn clear_runtime_auth_for_provider(state: &mut ShellState, provider_id: &str) {
    if state.auth_state.provider() == provider_id {
        state.auth_state = AuthState::Missing {
            provider: provider_id.to_string(),
        };
    }
    if let Some(config) = &mut state.config
        && config.provider_id() == provider_id
    {
        config.auth = None;
    }
}

pub(crate) fn sync_effective_config_after_logout(
    config: &mut EffectiveConfig,
    removal: &LogoutRemoval,
) {
    if let Some(provider_id) = removal.removed_custom_provider() {
        config.custom_providers.remove(provider_id);
    }
    if removal.removed() && config.provider_id() == removal.provider_id() {
        config.auth = None;
    }
}