use std::{env, process::Stdio};
use anyhow::{ensure, Context};
use crate::{
cli::{Cli, ModelPromptCommand},
external_editor::{editor_command, resolve_editor},
prompt::model_prompt_edit::{EditOutcome, ModelPromptEdit},
};
pub(super) async fn run(command: &ModelPromptCommand, cli: &Cli) -> anyhow::Result<()> {
let ModelPromptCommand::Edit { provider, model } = command;
ensure!(
provider.is_none() || cli.provider.is_none(),
"pass --provider either before the command or after edit, not both"
);
ensure!(
model.is_none() || cli.model.is_none(),
"pass --model either before the command or after edit, not both"
);
let config = super::config_repository::ConfigRepository::new(cli.config.clone()).load()?;
let explicit_provider = provider.as_deref().or(cli.provider.as_deref());
let reference = model
.as_deref()
.or(cli.model.as_deref())
.unwrap_or(&config.model);
let resolved = config.model_aliases.resolve(reference)?;
if let (Some(explicit), Some(alias_provider)) =
(explicit_provider, resolved.provider.as_deref())
{
ensure!(canonical_provider(explicit) == canonical_provider(alias_provider),
"model alias '{reference}' selects provider '{alias_provider}', which conflicts with --provider '{explicit}'");
}
let provider = canonical_provider(
resolved
.provider
.as_deref()
.or(explicit_provider)
.unwrap_or(&config.provider),
);
let model = resolved.model;
let editor = resolve_editor(env::var_os("VISUAL"), env::var_os("EDITOR"))
.context("set VISUAL or EDITOR to edit a model prompt")?;
let mut command = editor_command(&editor)?;
let home = crate::paths::home_dir().context("could not locate the home directory")?;
let edit = ModelPromptEdit::prepare(&home, provider, &model)?;
command
.arg(edit.path())
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
eprintln!("editing model prompt for {provider}/{model}");
let result: anyhow::Result<()> = async {
#[cfg(unix)]
let _signals = crate::external_editor::unix_suspended_child_signals::SuspendedChildSignalGuard::install(&mut command)
.context("could not prepare editor signal handling")?;
let status = command.status().await.context("could not start editor")?;
ensure!(status.success(), "editor exited with {status}");
Ok(())
}.await;
if let Err(error) = result {
let path = edit
.recover()
.context("could not preserve the editor draft")?;
return Err(error.context(format!("model prompt draft kept at {}", path.display())));
}
match edit.finish()? {
EditOutcome::Saved(path) => {
println!("saved model prompt: {}", path.display());
println!("running sessions pick up changes on model switch, /new, or resume");
}
EditOutcome::Unchanged => println!("model prompt unchanged"),
}
Ok(())
}
fn canonical_provider(provider: &str) -> &str {
rho_providers::provider::legacy_provider_alias(provider)
.map_or(provider, |(canonical, _)| canonical)
}