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;
}
}