use std::io::{self, IsTerminal};
use crossterm::execute;
use crossterm::style::{Print, ResetColor, SetForegroundColor};
use newt_core::agentic::{print_newt, warmup_if_cold, NEWT_ORANGE_CT};
use crate::probe;
use crate::{
active_backend_name, backends_list_items, fetch_models_for, is_ephemeral_session,
keep_alive_str, run_newt_subcmd, today_date, with_interrupt_watch, BackendChoice,
};
fn choice_or_print(
resolved: &newt_core::ResolvedConfig,
color: bool,
verbose: bool,
) -> Option<BackendChoice> {
match crate::resolve_backend_choice(resolved) {
Ok(choice) => Some(choice),
Err(refusal) => {
print_newt(&refusal, color, verbose);
None
}
}
}
pub(crate) fn dispatch(
cmd: &str,
arg1: &str,
arg2: &str,
color: bool,
verbose: bool,
) -> anyhow::Result<bool> {
match cmd {
"models" => {
let cfg = crate::resolve_runtime_or_default();
let Some(choice) = choice_or_print(&cfg, color, verbose) else {
return Ok(true);
};
let url = choice.url;
let current = choice.active_model.clone().unwrap_or_default();
if arg1 == "capabilities" {
match probe::fetch_ollama_models(&url) {
Err(e) => print_newt(&format!("error: {e}"), color, verbose),
Ok(models) => {
let cache = probe::load_cache();
probe::print_capabilities_table(&models, &cache, ¤t, &url, color);
}
}
} else {
let fetched = fetch_models_for(&url, choice.kind, choice.api_key.as_deref());
match fetched {
Ok(names) if names.is_empty() => {
print_newt(&format!("No models found on {url}"), color, verbose);
}
Ok(names) => {
let cache = probe::load_cache();
print_newt(&format!("Models on {url}:"), color, verbose);
for name in &names {
let conformance_tag = cache
.get(&probe::cap_key(newt_core::Serving::Multiplexer, "", name))
.map(|e| format!(" {}", e.conformance.symbol()))
.unwrap_or_default();
if *name == current {
if color {
execute!(
io::stdout(),
Print(format!(" {name}{conformance_tag}")),
SetForegroundColor(NEWT_ORANGE_CT),
Print(" ◀ active"),
ResetColor,
Print("\n"),
)
.ok();
} else {
println!(" {name}{conformance_tag} ◀ active");
}
} else {
println!(" {name}{conformance_tag}");
}
}
let tested = names
.iter()
.filter(|n| {
cache.contains_key(&probe::cap_key(
newt_core::Serving::Multiplexer,
"",
n,
))
})
.count();
if tested < names.len() {
println!(
"\n {}/{} tested — /models capabilities for the full matrix",
tested,
names.len()
);
}
}
Err(e) => print_newt(&format!("error: {e}"), color, verbose),
}
}
}
"probe" => {
let cfg = crate::resolve_runtime_or_default();
let Some(choice) = choice_or_print(&cfg, color, verbose) else {
return Ok(true);
};
if arg1 == "reset" {
probe::save_cache(&probe::CapabilityCache::default());
print_newt(
"probe cache reset — conformance, context windows, and calibration cleared. \
Re-test with /probe all (Esc to cancel).",
color,
verbose,
);
} else if choice.kind != newt_core::BackendKind::Ollama {
print_newt(
"/probe only works with Ollama endpoints (vLLM/OpenAI keep models resident)",
color,
verbose,
);
} else {
let endpoint = &choice.url;
let mut cache = probe::load_cache();
let do_window = arg1 == "window";
let model_arg = if do_window { arg2 } else { arg1 };
let targets: Vec<String> = if !do_window && model_arg == "all" {
match probe::fetch_ollama_models(endpoint) {
Ok(models) => models.into_iter().map(|m| m.name).collect(),
Err(e) => {
print_newt(&format!("error fetching model list: {e}"), color, verbose);
vec![]
}
}
} else if model_arg.is_empty() {
vec![choice.active_model.clone().unwrap_or_default()]
} else {
vec![model_arg.to_string()]
};
if targets.is_empty() {
print_newt("No models to probe.", color, verbose);
} else if model_arg == "all" {
print_newt(
&format!("Probing {} models — press Esc to cancel.", targets.len()),
color,
verbose,
);
}
let probe_cancel = std::sync::atomic::AtomicBool::new(false);
let probe_interruptible = io::stdin().is_terminal() && io::stdout().is_terminal();
let mut probed = 0usize;
with_interrupt_watch(probe_interruptible, &probe_cancel, || {
for model in &targets {
if probe_cancel.load(std::sync::atomic::Ordering::Relaxed) {
print_newt(
&format!("⊘ interrupted — probed {probed}/{}", targets.len()),
color,
verbose,
);
break;
}
if do_window {
print_newt(
&format!("Probing {model} (window search)…"),
color,
verbose,
);
} else {
print_newt(&format!("Probing {model}…"), color, verbose);
}
warmup_if_cold(
endpoint,
model,
&keep_alive_str(&cfg),
choice.api_key.as_deref(),
color,
verbose,
);
let today = today_date();
let key =
probe::cap_key(newt_core::Serving::Multiplexer, &choice.name, model);
let mut entry = cache.remove(&key).unwrap_or_default();
let report = probe::full_probe(
endpoint,
model,
&mut entry,
do_window,
&today,
|line: &str| print_newt(line, color, verbose),
cfg.context
.as_ref()
.map(|c| c.estimation)
.unwrap_or_default(),
choice.kind,
choice.api_key.as_deref(),
);
cache.insert(key, entry);
probe::save_cache(&cache);
print_newt(
&format!(
"{model} → {} (tested {today})",
report.conformance.symbol()
),
color,
verbose,
);
if let Some(w) = report.context_window {
print_newt(&format!(" context window: {w}"), color, verbose);
}
if report.emits_thinking {
print_newt(" quirk: emits thinking-only responses", color, verbose);
}
if let Some(r) = report.estimate_ratio {
print_newt(
&format!(" estimate calibration: x{r:.2} (chars/4 → real)"),
color,
verbose,
);
}
if let Some(outcome) = &report.boundary {
match outcome.highest_accepted {
Some(max) => print_newt(
&format!(
" max input (empirical): {max} — High confidence \
({} steps)",
outcome.steps
),
color,
verbose,
),
None => print_newt(
&format!(
" no input accepted in {} steps (bounds {:?})",
outcome.steps, outcome.final_bounds
),
color,
verbose,
),
}
if let Some(err) = &outcome.error {
print_newt(&format!(" note: {err}"), color, verbose);
}
}
for note in &report.notes {
print_newt(&format!(" note: {note}"), color, verbose);
}
probed += 1;
}
});
}
}
"model" => {
if arg1.is_empty() {
let cfg = crate::resolve_runtime_or_default();
let Some(choice) = choice_or_print(&cfg, color, verbose) else {
return Ok(true);
};
print_newt(
&format!(
"active model: {} (/models to see the choices, /model <name> to switch)",
choice.display_model()
),
color,
verbose,
);
} else {
apply_model_choice(arg1, color, verbose);
}
}
"backend" | "backends" => {
let cfg = crate::resolve_runtime_or_default();
if arg1.is_empty() {
let active = active_backend_name(&cfg);
print_newt("configured backends:", color, verbose);
if cfg.backends.is_empty() {
print_newt(
" (none — add [[backends]] entries to ~/.newt/config.toml)",
color,
verbose,
);
} else {
for (label, is_active) in backends_list_items(&cfg, active.as_deref()) {
newt_core::agentic::print_list_item(&label, is_active, color);
}
print_newt(
"usage: /backends <name> to switch (e.g. /backends dgx1)",
color,
verbose,
);
}
} else {
apply_backend_choice(arg1, color, verbose);
}
}
"summarizer" => {
let mut args = vec!["summarizer"];
if !arg1.is_empty() {
args.push(arg1);
}
if !arg2.is_empty() {
args.push(arg2);
}
run_newt_subcmd(&args, color, verbose)?;
}
"dgx" => {
if arg1.is_empty() {
print_newt(
"usage: /dgx <status|models|ps|warm [model]|pull <model>|rm <model>|route <task>|doctor>",
color,
verbose,
);
} else {
let mut dgx_args = vec!["dgx", arg1];
if !arg2.is_empty() {
dgx_args.push(arg2);
}
run_newt_subcmd(&dgx_args, color, verbose)?;
}
}
other => unreachable!("commands::model::dispatch routed a non-model command: {other:?}"),
}
Ok(true)
}
#[cfg(feature = "rich-tui")]
pub(crate) fn served_choices(
cfg: &newt_core::ResolvedConfig,
) -> Option<Vec<crate::config_panel::ModelChoice>> {
let choice = crate::resolve_backend_choice(cfg).ok()?;
let names = fetch_models_for(&choice.url, choice.kind, choice.api_key.as_deref()).ok()?;
let cache = probe::load_cache();
Some(
names
.into_iter()
.map(|name| {
let tag = cache
.get(&probe::cap_key(newt_core::Serving::Multiplexer, "", &name))
.map(|e| e.conformance.symbol().to_string())
.unwrap_or_default();
crate::config_panel::ModelChoice { name, tag }
})
.collect(),
)
}
#[cfg(feature = "rich-tui")]
pub(crate) fn apply_backend_kind(kind: &str, model: &str, color: bool, verbose: bool) {
{
let _env = newt_core::process_env::lock();
newt_core::process_env::set_var("NEWT_BACKEND", kind);
if kind == "ollama" && !model.is_empty() {
newt_core::process_env::set_var("NEWT_DGX_MODEL", model);
newt_core::runtime::mark_model_pick(model);
}
}
match crate::resolve_backend_choice(&crate::resolve_runtime_or_default()) {
Ok(choice) => print_newt(
&format!(
"switched to {} · {} @ {} — next message.",
choice.kind.label(),
choice.display_model(),
choice.url
),
color,
verbose,
),
Err(refusal) => print_newt(&refusal, color, verbose),
}
}
pub(crate) fn apply_backend_choice(name: &str, color: bool, verbose: bool) -> bool {
let cfg = crate::resolve_runtime_or_default();
if let Some(target) = cfg.backends.iter().find(|b| b.name == name) {
if target.endpoint.is_empty() || target.kind == Some(newt_core::BackendKind::Embedded) {
print_newt(
&format!(
"backend '{name}' is an embedded (model_path) backend — chat drives \
HTTP backends only; nothing was switched or saved"
),
color,
verbose,
);
return false;
}
{
let _env = newt_core::process_env::lock();
newt_core::process_env::set_var("NEWT_PROVIDER", name);
newt_core::process_env::remove_var("NEWT_DGX_MODEL");
}
newt_core::runtime::mark_backend_pick(name);
if newt_core::settings::should_persist(is_ephemeral_session()) {
newt_core::settings::record_provider(
name,
crate::resolve_runtime_or_default()
.default_backend
.as_deref(),
);
}
match crate::resolve_backend_choice(&crate::resolve_runtime_or_default()) {
Ok(choice) => print_newt(
&format!(
"switched to backend '{}' · {} @ {} — next message.",
name,
choice.display_model(),
choice.url
),
color,
verbose,
),
Err(refusal) => print_newt(&refusal, color, verbose),
}
true
} else {
let names: Vec<&str> = cfg.backends.iter().map(|b| b.name.as_str()).collect();
print_newt(
&format!(
"no backend named '{}'. configured: {}",
name,
if names.is_empty() {
"(none)".to_string()
} else {
names.join(", ")
}
),
color,
verbose,
);
false
}
}
pub(crate) fn apply_model_choice(name: &str, color: bool, verbose: bool) {
let gate_cfg = crate::resolve_runtime_or_default();
let Some(gate_choice) = choice_or_print(&gate_cfg, color, verbose) else {
return;
};
let served: Option<Vec<String>> = fetch_models_for(
&gate_choice.url,
gate_choice.kind,
gate_choice.api_key.as_deref(),
)
.ok();
if !model_choice_ok(name, served.as_deref()) {
let served = served.unwrap_or_default();
match suggest_model(name, &served) {
Some(s) => print_newt(
&format!(
"no model `{name}` on {} — did you mean `{s}`? (not applied)",
gate_choice.url
),
color,
verbose,
),
None => print_newt(
&format!(
"no model `{name}` on {} — run /models to list (not applied)",
gate_choice.url
),
color,
verbose,
),
}
return;
}
newt_core::process_env::set_var("NEWT_DGX_MODEL", name);
newt_core::runtime::mark_model_pick(name);
if newt_core::settings::should_persist(is_ephemeral_session()) {
newt_core::settings::record_model(
name,
crate::resolve_runtime_or_default()
.default_backend
.as_deref(),
);
}
let cfg = crate::resolve_runtime_or_default();
let Some(choice) = choice_or_print(&cfg, color, verbose) else {
return;
};
if choice.kind == newt_core::BackendKind::Ollama {
warmup_if_cold(
&choice.url,
&choice.active_model.clone().unwrap_or_default(),
&keep_alive_str(&cfg),
choice.api_key.as_deref(),
color,
verbose,
);
} else {
print_newt(
&format!(
"Switched to {} — takes effect on next message.",
choice.display_model()
),
color,
verbose,
);
}
}
fn model_choice_ok(name: &str, served: Option<&[String]>) -> bool {
served.is_none_or(|models| models.iter().any(|m| m == name))
}
fn suggest_model(typo: &str, served: &[String]) -> Option<String> {
served
.iter()
.map(|m| (levenshtein(typo, m), m))
.filter(|(d, _)| *d >= 1 && *d <= 3)
.min_by_key(|(d, _)| *d)
.map(|(_, m)| m.clone())
}
fn levenshtein(a: &str, b: &str) -> usize {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().collect();
let mut prev: Vec<usize> = (0..=b.len()).collect();
let mut cur = vec![0usize; b.len() + 1];
for (i, &ca) in a.iter().enumerate() {
cur[0] = i + 1;
for (j, &cb) in b.iter().enumerate() {
let cost = usize::from(ca != cb);
cur[j + 1] = (prev[j + 1] + 1).min(cur[j] + 1).min(prev[j] + cost);
}
std::mem::swap(&mut prev, &mut cur);
}
prev[b.len()]
}
#[cfg(test)]
mod validate_tests {
use super::{levenshtein, model_choice_ok, suggest_model};
#[test]
fn model_choice_gate() {
let served = ["qwen2.5-coder:7b".to_string(), "llama3.1:8b".to_string()];
assert!(model_choice_ok("qwen2.5-coder:7b", Some(&served)));
assert!(!model_choice_ok("quen2.5-coder:7b", Some(&served)));
assert!(model_choice_ok("anything", None));
}
#[test]
fn suggest_catches_a_small_typo_only() {
let served = vec![
"qwen2.5-coder:7b".to_string(),
"nomic-embed-text:latest".to_string(),
];
assert_eq!(
suggest_model("quen2.5-coder:7b", &served).as_deref(),
Some("qwen2.5-coder:7b")
);
assert_eq!(suggest_model("totally-different", &served), None);
}
#[test]
fn levenshtein_basics() {
assert_eq!(levenshtein("qwen", "quen"), 1);
assert_eq!(levenshtein("abc", "abc"), 0);
assert_eq!(levenshtein("", "abc"), 3);
}
}
#[cfg(test)]
mod mark_tests {
use super::*;
use newt_core::runtime::drain_preference_actions;
use newt_core::test_guard::GlobalSettingsGuard;
#[test]
fn browsing_backends_marks_no_preference_action() {
let _g = GlobalSettingsGuard::acquire();
let _ = drain_preference_actions();
dispatch("backends", "", "", false, false).expect("listing must not error");
assert!(
drain_preference_actions().is_empty(),
"a bare /backends LISTING must pin nothing — browsing is not choosing"
);
dispatch(
"backends",
"definitely-not-a-configured-backend",
"",
false,
false,
)
.expect("an unknown name must not error");
assert!(
drain_preference_actions().is_empty(),
"a REFUSED /backends <unknown> must pin nothing — a failed switch is not a choice"
);
}
}