use crate::reply::agent::{Agent, Offer};
use crate::reply::transcript::{Block, Entry, EntryKind, Transcript};
use super::parts::{brief, clause, line, line_over, listing, when};
use super::walls::{parked, tail};
pub(super) fn transcript(transcript: &Transcript) -> String {
let entries = transcript.entries.iter().map(entry).collect();
listing("transcript", entries, "nothing has been said yet")
}
fn entry(entry: &Entry) -> String {
let (clause, body) = reading(&entry.kind);
line_over(&line(vec![Some(entry.name.clone()), Some(clause)]), body)
}
fn reading(kind: &EntryKind) -> (String, Option<String>) {
match kind {
EntryKind::Delivered {
sender,
sender_name,
epitaph,
body,
} => (
line(vec![
Some(format!(
"from {}",
crate::reply::transcript::said_by(sender, sender_name.as_deref())
)),
clause("epitaph:", epitaph.as_deref()),
]),
Some(body.clone()),
),
EntryKind::Model {
model_id,
blocks,
usage,
} => (
line(vec![
Some(model_id.clone()),
when(!usage.is_empty(), &counts(usage)),
]),
Some(blocks.iter().map(block).collect::<Vec<String>>().join("\n")),
),
EntryKind::ToolResult {
tool_use_id,
content,
is_error,
} => (
line(vec![
Some(format!("result of {tool_use_id}")),
when(*is_error, "ERROR"),
]),
Some(content.clone()),
),
EntryKind::Streaming { thinking, text } => {
("in flight".to_owned(), Some(tail(thinking, text)))
}
EntryKind::Compacted {
first,
last,
summary,
} => (
format!("compacted {first}\u{2013}{last}"),
Some(summary.clone()),
),
EntryKind::Raw => ("raw".to_owned(), None),
EntryKind::Unknown(word) => (
format!("{word} (this seat has no reading of that entry)"),
None,
),
}
}
fn block(block: &Block) -> String {
match block {
Block::Text(text) => text.clone(),
Block::Thinking(text) => line_over("thinking", Some(text.clone())),
Block::ToolUse { id, name, input } => {
line_over(&format!("{name} ({id})"), Some(input.clone()))
}
Block::Unknown(word) => format!("[{word}]"),
}
}
fn counts(usage: &std::collections::BTreeMap<String, u64>) -> String {
usage
.iter()
.map(|(name, count)| format!("{name} {count}"))
.collect::<Vec<String>>()
.join(" ")
}
pub(super) fn agent(agent: &Agent) -> String {
let facts = line(vec![
Some(agent.display.clone()),
Some(agent.state.label()),
when(agent.display_only, "(display only)"),
when(agent.refused, "refused"),
when(agent.present, "present"),
clause("tip", Some(&agent.tip)),
clause("flight:", agent.flight.as_deref()),
clause("failed:", agent.failure.as_deref().map(brief).as_deref()),
when(!agent.marks.is_empty(), &agent.marks.join(", ")),
clause("spend", Some(&agent.spend.tokens.total.to_string())),
clause(
"context",
agent
.context
.as_ref()
.map(|c| format!("{}% of {}", c.percent, c.model))
.as_deref(),
),
]);
let more = [
agent.held.as_ref().map(parked),
agent
.strip
.as_ref()
.map(|strip| format!("{}: {}", strip.class, strip.facts)),
when(
!agent.seats.is_empty(),
&agent
.seats
.iter()
.map(|seat| format!("{} — {}", seat.name, seat.doing))
.collect::<Vec<String>>()
.join("\n"),
),
when(!agent.offers.is_empty(), &offered(&agent.offers)),
];
listing(&facts, more.into_iter().flatten().collect(), "")
}
fn offered(offers: &[Offer]) -> String {
format!(
"offers: {}",
offers
.iter()
.map(|offer| match offer {
Offer::Nudge => "nudge",
Offer::Stop => "stop",
Offer::Children => "stop children",
})
.collect::<Vec<&str>>()
.join(", ")
)
}