Make model inspection and switching feel more like a real CLI surface

Replace terse /model strings with sectioned model reports that show the active model and preserved session context, and use a structured switch report when the model changes. This keeps the behavior honest while making model management feel more intentional and Claude-like.

Constraint: Model switching must preserve the current session and avoid adding any fake model catalog or validation layer
Rejected: Add a hardcoded model list or aliases | would create drift with actual backend-supported model names
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep /model output informational and backend-agnostic unless the runtime gains authoritative model discovery
Tested: cargo fmt --manifest-path ./rust/Cargo.toml --all; cargo clippy --manifest-path ./rust/Cargo.toml --workspace --all-targets -- -D warnings; cargo test --manifest-path ./rust/Cargo.toml --workspace
Not-tested: Manual interactive switching across multiple real Anthropic model names
This commit is contained in:
Yeachan-Heo
2026-03-31 20:43:56 +00:00
parent 0ac188caad
commit 3db3dfa60d

View File

@@ -269,6 +269,28 @@ struct StatusUsage {
estimated_tokens: usize,
}
fn format_model_report(model: &str, message_count: usize, turns: u32) -> String {
format!(
"Model
Current model {model}
Session messages {message_count}
Session turns {turns}
Usage
Inspect current model with /model
Switch models with /model <name>"
)
}
fn format_model_switch_report(previous: &str, next: &str, message_count: usize) -> String {
format!(
"Model updated
Previous {previous}
Current {next}
Preserved msgs {message_count}"
)
}
fn run_resume_command(
session_path: &Path,
session: &Session,
@@ -489,19 +511,38 @@ impl LiveCli {
fn set_model(&mut self, model: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
let Some(model) = model else {
println!("Current model: {}", self.model);
println!(
"{}",
format_model_report(
&self.model,
self.runtime.session().messages.len(),
self.runtime.usage().turns(),
)
);
return Ok(());
};
if model == self.model {
println!("Model already set to {model}.");
println!(
"{}",
format_model_report(
&self.model,
self.runtime.session().messages.len(),
self.runtime.usage().turns(),
)
);
return Ok(());
}
let previous = self.model.clone();
let session = self.runtime.session().clone();
let message_count = session.messages.len();
self.runtime = build_runtime(session, model.clone(), self.system_prompt.clone(), true)?;
self.model.clone_from(&model);
println!("Switched model to {model}.");
println!(
"{}",
format_model_switch_report(&previous, &model, message_count)
);
Ok(())
}
@@ -1193,9 +1234,10 @@ fn print_help() {
#[cfg(test)]
mod tests {
use super::{
format_status_report, normalize_permission_mode, parse_args, render_init_claude_md,
render_repl_help, resume_supported_slash_commands, status_context, CliAction, SlashCommand,
StatusUsage, DEFAULT_MODEL,
format_model_report, format_model_switch_report, format_status_report,
normalize_permission_mode, parse_args, render_init_claude_md, render_repl_help,
resume_supported_slash_commands, status_context, CliAction, SlashCommand, StatusUsage,
DEFAULT_MODEL,
};
use runtime::{ContentBlock, ConversationMessage, MessageRole};
use std::path::{Path, PathBuf};
@@ -1310,6 +1352,24 @@ mod tests {
);
}
#[test]
fn model_report_uses_sectioned_layout() {
let report = format_model_report("claude-sonnet", 12, 4);
assert!(report.contains("Model"));
assert!(report.contains("Current model claude-sonnet"));
assert!(report.contains("Session messages 12"));
assert!(report.contains("Switch models with /model <name>"));
}
#[test]
fn model_switch_report_preserves_context_summary() {
let report = format_model_switch_report("claude-sonnet", "claude-opus", 9);
assert!(report.contains("Model updated"));
assert!(report.contains("Previous claude-sonnet"));
assert!(report.contains("Current claude-opus"));
assert!(report.contains("Preserved msgs 9"));
}
#[test]
fn status_line_reports_model_and_token_totals() {
let status = format_status_report(