diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-06-10 22:01:45 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-06-10 22:01:45 +0200 |
| commit | 5e31a81cd2207f053b8cd8ad84ebe2a2f691b29d (patch) | |
| tree | 5d76811ab0d693c01fa472d41aa2ceaf3bd0b415 /crates/atuin-ai/src/tui/view/turn.rs | |
| parent | chore: Remove unneeded files (diff) | |
| download | atuin-5e31a81cd2207f053b8cd8ad84ebe2a2f691b29d.zip | |
chore: Remove some unused rust code
Diffstat (limited to 'crates/atuin-ai/src/tui/view/turn.rs')
| -rw-r--r-- | crates/atuin-ai/src/tui/view/turn.rs | 606 |
1 files changed, 0 insertions, 606 deletions
diff --git a/crates/atuin-ai/src/tui/view/turn.rs b/crates/atuin-ai/src/tui/view/turn.rs deleted file mode 100644 index aa1f55fa..00000000 --- a/crates/atuin-ai/src/tui/view/turn.rs +++ /dev/null @@ -1,606 +0,0 @@ -use std::path::PathBuf; - -use crate::fsm::tools::ToolManager; -use crate::tools::descriptor; -use crate::tools::{ClientToolCall, HistorySearchFilterMode, ToolPreview}; -use crate::tui::ConversationEvent; - -/// Server-sent danger level for a suggested command -#[derive(Debug)] -pub(crate) enum DangerLevel { - Low(Option<String>), - Medium(Option<String>), - High(Option<String>), - Unknown(Option<String>), -} - -impl DangerLevel { - pub(crate) fn notes(&self) -> Option<&String> { - match self { - DangerLevel::Low(notes) => notes.as_ref(), - DangerLevel::Medium(notes) => notes.as_ref(), - DangerLevel::High(notes) => notes.as_ref(), - DangerLevel::Unknown(notes) => notes.as_ref(), - } - } -} - -impl From<(&String, &String)> for DangerLevel { - fn from((danger_level, danger_notes): (&String, &String)) -> Self { - let notes = if danger_notes.is_empty() { - None - } else { - Some(danger_notes.to_string()) - }; - - match danger_level.as_str() { - "low" => DangerLevel::Low(notes), - "medium" => DangerLevel::Medium(notes), - "med" => DangerLevel::Medium(notes), - "high" => DangerLevel::High(notes), - _ => DangerLevel::Unknown(notes), - } - } -} - -/// Server-sent confidence level for a suggested command -#[derive(Debug)] -pub(crate) enum ConfidenceLevel { - Low(Option<String>), - Medium(Option<String>), - High(Option<String>), - Unknown(Option<String>), -} - -impl ConfidenceLevel { - pub(crate) fn notes(&self) -> Option<&String> { - match self { - ConfidenceLevel::Low(notes) => notes.as_ref(), - ConfidenceLevel::Medium(notes) => notes.as_ref(), - ConfidenceLevel::High(notes) => notes.as_ref(), - ConfidenceLevel::Unknown(notes) => notes.as_ref(), - } - } -} - -impl From<(&String, &String)> for ConfidenceLevel { - fn from((confidence_level, confidence_notes): (&String, &String)) -> Self { - let notes = if confidence_notes.is_empty() { - None - } else { - Some(confidence_notes.to_string()) - }; - - match confidence_level.as_str() { - "low" => ConfidenceLevel::Low(notes), - "medium" => ConfidenceLevel::Medium(notes), - "med" => ConfidenceLevel::Medium(notes), - "high" => ConfidenceLevel::High(notes), - _ => ConfidenceLevel::Unknown(notes), - } - } -} - -#[derive(Debug)] -pub(crate) enum UiEvent { - Text { - content: String, - }, - ToolCall(ToolCallDetails), - /// Consecutive client-side tool calls of the same groupable kind, collapsed - /// into one unit so the view can render a shared status line + a list of - /// individual entries. - ToolGroup(ToolGroup), - ToolSummary(ToolSummary), - SuggestedCommand(SuggestedCommandDetails), - OutOfBandOutput(OutOfBandOutputDetails), -} - -/// A run of consecutive client-side tool calls of the same groupable kind. -#[derive(Debug)] -pub(crate) struct ToolGroup { - pub(crate) kind: ToolGroupKind, - pub(crate) calls: Vec<ToolCallDetails>, -} - -impl ToolGroup { - /// True if any call in the group is still pending. - pub(crate) fn any_pending(&self) -> bool { - self.calls - .iter() - .any(|c| c.status == ToolResultStatus::Pending) - } -} - -/// Which kind of client-side tools this group holds. -/// -/// Only tool types that benefit from grouped presentation appear here. -/// Shell (needs its own viewport) and FileWrite (wants diffs/contents) are -/// intentionally absent — those render as individual `UiEvent::ToolCall`s. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) enum ToolGroupKind { - FileRead, - HistorySearch, -} - -/// Tool-type-specific data for rendering in the view layer. -/// -/// Each variant carries the data a per-tool renderer component needs. -/// Built by TurnBuilder from ToolTracker + ConversationEvent data. -#[derive(Debug)] -pub(crate) enum ToolRenderData { - /// Shell command with live/cached VT100 output preview. - Shell { - command: String, - preview: Option<ToolPreview>, - }, - /// File read operation. - FileRead { path: PathBuf }, - /// File edit (str_replace) operation. - FileEdit { - path: PathBuf, - preview: Option<crate::diff::EditPreview>, - }, - /// File write/create operation. - FileWrite { - path: PathBuf, - preview: Option<crate::diff::WritePreview>, - }, - /// Atuin history search. - HistorySearch { - query: String, - filter_modes: Vec<HistorySearchFilterMode>, - }, - /// Skill loading — read-only, auto-approved. - SkillLoad { _name: String }, - /// Server-side tool — no client rendering data available. - Remote, -} - -impl ToolRenderData { - pub(crate) fn is_remote(&self) -> bool { - matches!(self, ToolRenderData::Remote) - } - - /// The group kind this tool should collapse into, if any. - /// - /// Returns `None` for tools that render as individual `UiEvent::ToolCall`s - /// (shell, file writes, remote). - pub(crate) fn group_kind(&self) -> Option<ToolGroupKind> { - match self { - ToolRenderData::FileRead { .. } => Some(ToolGroupKind::FileRead), - ToolRenderData::HistorySearch { .. } => Some(ToolGroupKind::HistorySearch), - _ => None, - } - } -} - -#[derive(Debug)] -pub(crate) struct ToolCallDetails { - pub(crate) tool_use_id: String, - pub(crate) name: String, - pub(crate) status: ToolResultStatus, - pub(crate) render_data: ToolRenderData, -} - -#[derive(Debug)] -pub(crate) struct SuggestedCommandDetails { - pub(crate) command: String, - pub(crate) danger_level: DangerLevel, - pub(crate) confidence_level: ConfidenceLevel, -} - -#[derive(Debug)] -pub(crate) struct OutOfBandOutputDetails { - pub(crate) command: Option<String>, - pub(crate) content: String, -} - -#[derive(Debug, PartialEq, Eq)] -pub(crate) enum ToolResultStatus { - Pending, - Success, - Error, -} - -#[derive(Debug)] -pub(crate) struct UiTurn { - pub(crate) id: usize, - pub(crate) kind: UiTurnKind, -} - -#[derive(Debug)] -pub(crate) enum UiTurnKind { - User { events: Vec<UiEvent> }, - Agent { events: Vec<UiEvent> }, - OutOfBand { events: Vec<UiEvent> }, -} - -pub(crate) struct TurnBuilder<'a> { - turns: Vec<UiTurnKind>, - current_turn: Option<UiTurnKind>, - tracker: &'a ToolManager, - next_id: usize, -} - -/// A struct to iteratively build [UiTurn] events from [ConversationEvent]s. -impl<'a> TurnBuilder<'a> { - pub(crate) fn new(tracker: &'a ToolManager) -> Self { - Self { - turns: Vec::new(), - current_turn: None, - tracker, - next_id: 0, - } - } - - pub(crate) fn new_starting_at(tracker: &'a ToolManager, start_id: usize) -> Self { - Self { - turns: Vec::new(), - current_turn: None, - tracker, - next_id: start_id, - } - } - - pub(crate) fn add_event(&mut self, event: &ConversationEvent) { - match event { - ConversationEvent::UserMessage { content } => { - self.add_user_message(content); - } - ConversationEvent::Text { content } => { - self.add_agent_text(content); - } - ConversationEvent::ToolCall { id, name, input } => { - if name == "suggest_command" { - self.add_suggested_command(input); - } else { - self.add_tool_call(id, name, input); - } - } - ConversationEvent::ToolResult { - tool_use_id, - content, - is_error, - .. - } => { - self.add_tool_result(tool_use_id, content, *is_error); - } - ConversationEvent::OutOfBandOutput { - name, - command, - content, - } => { - self.add_out_of_band_output(name, command.as_deref(), content); - } - ConversationEvent::SystemContext { .. } => { - // Not rendered in the TUI — only sent to the API - } - ConversationEvent::SkillInvocation { - name, arguments, .. - } => { - let display = match arguments { - Some(args) => format!("/{name} {args}"), - None => format!("/{name}"), - }; - self.add_user_message(&display); - } - } - } - - pub(crate) fn build(&mut self) -> Vec<UiTurn> { - self.commit_turn(); - - // Within each agent turn: - // - Consecutive remote tool calls collapse into a ToolSummary - // - Consecutive client-side tool calls of the same group kind collapse - // into a ToolGroup (e.g. N file reads → one group) - // - All other events pass through unchanged - for turn in &mut self.turns { - if let UiTurnKind::Agent { events } = turn { - let mut new_events: Vec<UiEvent> = Vec::new(); - let mut pending_remote: Vec<ToolCallDetails> = Vec::new(); - let mut pending_group: Option<(ToolGroupKind, Vec<ToolCallDetails>)> = None; - - for event in events.drain(..) { - match event { - UiEvent::ToolCall(details) if details.render_data.is_remote() => { - flush_group(&mut pending_group, &mut new_events); - pending_remote.push(details); - } - UiEvent::ToolCall(details) - if details.render_data.group_kind().is_some() => - { - flush_remote(&mut pending_remote, &mut new_events); - - let kind = details.render_data.group_kind().unwrap(); - match pending_group.as_mut() { - Some((current_kind, calls)) if *current_kind == kind => { - calls.push(details); - } - _ => { - flush_group(&mut pending_group, &mut new_events); - pending_group = Some((kind, vec![details])); - } - } - } - other => { - flush_remote(&mut pending_remote, &mut new_events); - flush_group(&mut pending_group, &mut new_events); - new_events.push(other); - } - } - } - - flush_remote(&mut pending_remote, &mut new_events); - flush_group(&mut pending_group, &mut new_events); - - *events = new_events; - } - } - - let kinds = std::mem::take(&mut self.turns); - kinds - .into_iter() - .enumerate() - .map(|(i, kind)| UiTurn { - id: self.next_id + i, - kind, - }) - .collect() - } - - fn commit_turn(&mut self) { - if let Some(turn) = self.current_turn.take() { - self.turns.push(turn); - } - } - - fn start_user_turn(&mut self) { - if !matches!(self.current_turn, Some(UiTurnKind::User { .. })) { - self.commit_turn(); - self.current_turn = Some(UiTurnKind::User { events: vec![] }); - } - } - - fn start_agent_turn(&mut self) { - if !matches!(self.current_turn, Some(UiTurnKind::Agent { .. })) { - self.commit_turn(); - self.current_turn = Some(UiTurnKind::Agent { events: vec![] }); - } - } - - fn start_out_of_band_turn(&mut self) { - if !matches!(self.current_turn, Some(UiTurnKind::OutOfBand { .. })) { - self.commit_turn(); - self.current_turn = Some(UiTurnKind::OutOfBand { events: vec![] }); - } - } - - fn current_events_mut(&mut self) -> &mut Vec<UiEvent> { - match self.current_turn.as_mut().unwrap() { - UiTurnKind::User { events } - | UiTurnKind::Agent { events } - | UiTurnKind::OutOfBand { events } => events, - } - } - - fn add_user_message(&mut self, content: &str) { - self.start_user_turn(); - self.current_events_mut().push(UiEvent::Text { - content: content.to_string(), - }); - } - - fn add_agent_text(&mut self, content: &str) { - if content.trim().is_empty() { - return; - } - self.start_agent_turn(); - self.current_events_mut().push(UiEvent::Text { - content: content.to_string(), - }); - } - - fn add_suggested_command(&mut self, input: &serde_json::Value) { - let command = input - .get("command") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - if command.is_empty() { - return; - } - - self.start_agent_turn(); - { - let events = self.current_events_mut(); - let danger_level = input - .get("danger") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let confidence_level = input - .get("confidence") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let danger_notes = input - .get("danger_notes") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let confidence_notes = input - .get("confidence_notes") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let danger = DangerLevel::from((&danger_level, &danger_notes)); - let confidence = ConfidenceLevel::from((&confidence_level, &confidence_notes)); - - events.push(UiEvent::SuggestedCommand(SuggestedCommandDetails { - command: input - .get("command") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(), - danger_level: danger, - confidence_level: confidence, - })); - } - } - - fn add_tool_call(&mut self, id: &str, name: &str, _input: &serde_json::Value) { - let render_data = self.build_render_data(id, name); - - self.start_agent_turn(); - self.current_events_mut() - .push(UiEvent::ToolCall(ToolCallDetails { - tool_use_id: id.to_string(), - name: name.to_string(), - status: ToolResultStatus::Pending, - render_data, - })); - } - - /// Build tool-type-specific render data from the ToolTracker. - /// - /// For client-side tools, the tracker holds the typed `ClientToolCall` and - /// any live/cached preview data. For server-side (or unknown) tools, we - /// fall back to `ToolRenderData::Remote`. - fn build_render_data(&self, id: &str, _name: &str) -> ToolRenderData { - if let Some(tracked) = self.tracker.get(id) { - match &tracked.tool { - ClientToolCall::Shell(shell) => ToolRenderData::Shell { - command: shell.command.clone(), - preview: tracked.shell_preview(), - }, - ClientToolCall::Read(read) => ToolRenderData::FileRead { - path: read.path.clone(), - }, - ClientToolCall::Edit(edit) => ToolRenderData::FileEdit { - path: edit.path.clone(), - preview: tracked.edit_preview().cloned(), - }, - ClientToolCall::Write(write) => ToolRenderData::FileWrite { - path: write.path.clone(), - preview: tracked.write_preview().cloned(), - }, - ClientToolCall::AtuinHistory(history) => ToolRenderData::HistorySearch { - query: history.query.clone(), - filter_modes: history.filter_modes.clone(), - }, - ClientToolCall::AtuinOutput(_) => ToolRenderData::Remote, - ClientToolCall::LoadSkill(skill) => ToolRenderData::SkillLoad { - _name: skill.name.clone(), - }, - } - } else { - // Not in tracker → server-side tool - ToolRenderData::Remote - } - } - - fn add_tool_result(&mut self, tool_use_id: &str, _content: &str, is_error: bool) { - self.start_agent_turn(); - let events = self.current_events_mut(); - let event = events.iter_mut().find(|e| match e { - UiEvent::ToolCall(ToolCallDetails { - tool_use_id: id, .. - }) => id == tool_use_id, - _ => false, - }); - if let Some(UiEvent::ToolCall(ToolCallDetails { status, .. })) = event { - *status = if is_error { - ToolResultStatus::Error - } else { - ToolResultStatus::Success - }; - } - } - - fn add_out_of_band_output(&mut self, _name: &str, command: Option<&str>, content: &str) { - self.start_out_of_band_turn(); - self.current_events_mut() - .push(UiEvent::OutOfBandOutput(OutOfBandOutputDetails { - command: command.map(|c| c.to_string()), - content: content.to_string(), - })); - } -} - -/// Drain pending remote tool calls into a `ToolSummary`. -fn flush_remote(pending: &mut Vec<ToolCallDetails>, out: &mut Vec<UiEvent>) { - if !pending.is_empty() { - out.push(UiEvent::ToolSummary(ToolSummary { - tool_calls: std::mem::take(pending), - })); - } -} - -/// Drain a pending client-side tool group into a `ToolGroup`. -fn flush_group( - pending: &mut Option<(ToolGroupKind, Vec<ToolCallDetails>)>, - out: &mut Vec<UiEvent>, -) { - if let Some((kind, calls)) = pending.take() { - out.push(UiEvent::ToolGroup(ToolGroup { kind, calls })); - } -} - -#[derive(Debug)] -pub(crate) struct ToolSummary { - tool_calls: Vec<ToolCallDetails>, -} - -impl ToolSummary { - /// Determines the summary line: - /// - If any call is pending, use present tense verb with `-ing` - /// - If multiple calls are complete, say "Used n tools" - /// - If a single call is complete, use past tense verb - pub(crate) fn summary(&self) -> String { - if self.any_pending() { - // Find the last pending tool for the active verb - if let Some(pending) = self - .tool_calls - .iter() - .rev() - .find(|t| t.status == ToolResultStatus::Pending) - { - return Self::progressive_verb(&pending.name); - } - } - - if self.tool_calls.len() == 1 { - return Self::past_verb(&self.tool_calls[0].name); - } - - format!("Used {} tools", self.tool_calls.len()) - } - - /// Determines if the spinner should be spinning - pub(crate) fn any_pending(&self) -> bool { - self.tool_calls - .iter() - .any(|tool_call| tool_call.status == ToolResultStatus::Pending) - } - - /// Present-tense progressive verb for a tool name (e.g. "Searching...") - fn progressive_verb(name: &str) -> String { - descriptor::by_name(name) - .map(|d| d.progressive_verb.to_string()) - .unwrap_or_else(|| format!("Running {}...", name.replace('_', " "))) - } - - /// Past-tense verb for a tool name (e.g. "Searched") - fn past_verb(name: &str) -> String { - descriptor::by_name(name) - .map(|d| d.past_verb.to_string()) - .unwrap_or_else(|| format!("Ran {}", name.replace('_', " "))) - } -} |
