use crate::types::Task;
pub fn format_route_fit(task: &Task, max_width: usize) -> String {
if max_width == 0 {
return String::new();
}
let full = task.display_route();
if char_len(&full) <= max_width {
return full;
}
let cli = task.agent_display_name();
let mut parts = full.splitn(3, '/');
let _cli_seg = parts.next().unwrap_or(cli);
let provider = parts.next().unwrap_or("unknown");
let model = parts.next().unwrap_or("unknown");
let fixed = char_len(cli) + 2;
if fixed >= max_width {
return truncate_chars(cli, max_width);
}
let remaining = max_width - fixed;
let prov_budget = (remaining / 2).max(4).min(remaining.saturating_sub(1));
let model_budget = remaining.saturating_sub(prov_budget);
let prov = truncate_chars(provider, prov_budget);
let modl = truncate_chars(model, model_budget.max(1));
let fitted = format!("{cli}/{prov}/{modl}");
if char_len(&fitted) <= max_width {
return fitted;
}
truncate_chars(cli, max_width)
}
fn char_len(s: &str) -> usize {
s.chars().count()
}
fn truncate_chars(s: &str, max: usize) -> String {
if max == 0 {
return String::new();
}
if char_len(s) <= max {
return s.to_string();
}
if max <= 3 {
return s.chars().take(max).collect();
}
let keep = max - 3;
let mut out: String = s.chars().take(keep).collect();
out.push_str("...");
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{AgentKind, AttributionSource, TaskId, TaskStatus, VerifyStatus};
use chrono::Local;
fn task_with(
agent: AgentKind,
custom: Option<&str>,
requested: Option<&str>,
observed: Option<&str>,
source: Option<AttributionSource>,
) -> Task {
Task {
id: TaskId("t-route".to_string()),
agent,
custom_agent_name: custom.map(str::to_string),
prompt: "p".to_string(),
resolved_prompt: None,
category: None,
status: TaskStatus::Done,
parent_task_id: None,
workgroup_id: None,
caller_kind: None,
caller_session_id: None,
agent_session_id: None,
repo_path: None, project_id: crate::project::current_project_id(),
worktree_path: None, effective_dir: None,
worktree_branch: None,
final_head_sha: None,
final_branch: None,
start_sha: None,
log_path: None,
output_path: None,
tokens: None,
prompt_tokens: None,
duration_ms: None,
requested_model: requested.map(str::to_string),
observed_model: observed.map(str::to_string),
attribution_source: source,
cost_usd: None,
exit_code: None,
created_at: Local::now(),
completed_at: None,
verify: None,
verify_status: VerifyStatus::Skipped,
pending_reason: None,
read_only: false,
budget: false,
audit_verdict: None,
audit_report_path: None,
delivery_assessment: None,
}
}
#[test]
fn full_route_shows_cli_provider_and_echoed_model() {
let t = task_with(
AgentKind::Codex,
None,
Some("gpt-5.6"),
Some("gpt-5.6"),
Some(AttributionSource::Echoed),
);
assert_eq!(
format_route_fit(&t, 80),
"codex/openai-chatgpt-plan/gpt-5.6"
);
assert_eq!(t.route().provider.as_str(), "openai-chatgpt-plan");
}
#[test]
fn unknown_model_stays_unknown() {
let t = task_with(AgentKind::Codex, None, None, None, None);
assert_eq!(
format_route_fit(&t, 80),
"codex/openai-chatgpt-plan/unknown"
);
assert!(t.route().model.is_none());
}
#[test]
fn inferred_attribution_is_visible() {
let t = task_with(
AgentKind::Codex,
None,
Some("gpt-5.6-luna"),
Some("gpt-5.6-luna"),
Some(AttributionSource::ConfirmedBySuccess),
);
assert_eq!(
format_route_fit(&t, 80),
"codex/openai-chatgpt-plan/gpt-5.6-luna (inferred)"
);
}
#[test]
fn unconfirmed_request_is_marked() {
let t = task_with(
AgentKind::Cursor,
None,
Some("composer-2"),
None,
None,
);
let shown = format_route_fit(&t, 80);
assert!(shown.ends_with("/composer-2?"), "{shown}");
assert!(shown.starts_with("cursor/cursor-subscription/"), "{shown}");
}
#[test]
fn custom_cli_name_is_used() {
let t = task_with(AgentKind::Custom, Some("glm5"), None, None, None);
assert_eq!(format_route_fit(&t, 80), "glm5/unknown/unknown");
}
#[test]
fn narrow_width_keeps_cli_and_does_not_wrap() {
let t = task_with(
AgentKind::Qwen,
None,
Some("qwen3.8-max"),
Some("qwen3.8-max"),
Some(AttributionSource::Echoed),
);
let shown = format_route_fit(&t, 24);
assert!(char_len(&shown) <= 24, "{shown}");
assert!(shown.starts_with("qwen/"), "{shown}");
assert!(!shown.contains('\n'));
}
#[test]
fn substitution_shows_both_models() {
let t = task_with(
AgentKind::Cursor,
None,
Some("auto"),
Some("composer-2"),
Some(AttributionSource::Echoed),
);
let shown = format_route_fit(&t, 80);
assert!(shown.contains("composer-2 (asked auto)"), "{shown}");
}
}