use crate::{
config::{AuthState, EffectiveConfig, McPaths, ProviderCredential},
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 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(())
}
pub(crate) fn fast_mode_status(
enabled: bool,
provider: &str,
model: &str,
effective: bool,
) -> String {
if !enabled {
return "Fast mode off. Magi-code will not request Fast processing.".to_string();
}
if effective {
format!(
"Fast mode on. {provider}/{model} will request Fast processing.\nEligible requests may have higher provider pricing."
)
} else {
format!("Fast remains on, but {provider}/{model} has no configured Fast support.")
}
}
#[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(),
}
})
}
fn auth_after_local_logout(provider_id: &str) -> Option<ProviderCredential> {
if provider_id != crate::providers::ANTHROPIC_PROVIDER {
return None;
}
std::env::var("ANTHROPIC_API_KEY")
.ok()
.filter(|key| !key.is_empty())
.map(|key| ProviderCredential::ApiKey { key })
}
fn set_effective_config_auth(
config: &mut EffectiveConfig,
provider_id: &str,
credential: Option<&ProviderCredential>,
) {
if provider_id == crate::providers::ANTHROPIC_PROVIDER {
config.auth = credential.cloned();
} else {
config.auth = None;
}
}
fn sync_effective_config_after_logout(
config: &mut EffectiveConfig,
removal: &LogoutRemoval,
credential: Option<&ProviderCredential>,
) {
if let Some(provider_id) = removal.removed_custom_provider() {
config.custom_providers.remove(provider_id);
}
if removal.removed() && config.provider_id() == removal.provider_id() {
set_effective_config_auth(config, removal.provider_id(), credential);
}
}
pub(crate) fn reconcile_runtime_after_logout(
state: &mut ShellState,
app_config: Option<&mut EffectiveConfig>,
removal: &LogoutRemoval,
) {
if !removal.removed() {
return;
}
let credential = auth_after_local_logout(removal.provider_id());
let state_auth_state = state.config.as_mut().and_then(|config| {
sync_effective_config_after_logout(config, removal, credential.as_ref());
(config.provider_id() == removal.provider_id()).then(|| config.auth_state())
});
if let Some(config) = app_config {
sync_effective_config_after_logout(config, removal, credential.as_ref());
}
if state.auth_state.provider() == removal.provider_id() {
state.auth_state = state_auth_state.unwrap_or_else(|| AuthState::Missing {
provider: removal.provider_id().to_string(),
});
}
}