Skip to main content

atomcode_core/turn/
event.rs

1use std::path::PathBuf;
2use std::time::Duration;
3
4/// Low-level events emitted by TurnRunner during execution.
5/// Does not contain approval events — approval is handled internally via PermissionDecider.
6#[derive(Debug, Clone)]
7pub enum TurnEvent {
8    /// LLM streaming text output
9    TextDelta(String),
10    /// LLM reasoning/thinking content (e.g., DeepSeek-R1, MiniMax-M2.7, o1-series).
11    /// Emitted when the model produces thinking content separately from the final response.
12    /// UI can optionally display this in verbose mode (Ctrl+O).
13    ReasoningDelta(String),
14    /// LLM has started emitting a tool call — name is known, arguments still streaming.
15    /// Fires once per tool call, BEFORE the full args have arrived. Lets the UI surface
16    /// the tool name immediately so users see "⠋ Write File…" instead of an opaque
17    /// "Generating…" while the model spends seconds streaming args.
18    ToolCallStreaming { name: String, hint: String },
19    /// Tool call fully assembled, about to execute.
20    /// `id` is the provider-supplied call id — pairs with the matching `ToolCallResult.call_id`.
21    ToolCallStarted {
22        id: String,
23        name: String,
24        arguments: String,
25    },
26    /// Multiple tool calls fan out from one assistant message. Emitted
27    /// BEFORE the per-call `ToolCallStarted` events, only when the
28    /// runner is about to dispatch ≥ 2 non-duplicate calls. Lets the
29    /// UI render a single grouped block instead of N independent rows.
30    /// Per-call `ToolCallStarted` events STILL fire — UIs that don't
31    /// care about batches can ignore the batch events and render each
32    /// call as today.
33    ToolBatchStarted {
34        batch_id: String,
35        calls: Vec<ToolBatchCall>,
36    },
37    /// Closes the batch opened by `ToolBatchStarted`. UI uses it to
38    /// finalize the group header with `ok / total / elapsed` summary.
39    ToolBatchCompleted {
40        batch_id: String,
41        ok: usize,
42        total: usize,
43        elapsed_ms: u64,
44    },
45    /// Real-time output chunk from a running tool (e.g., bash command).
46    /// Sent during tool execution before ToolCallResult.
47    ToolOutputChunk {
48        call_id: String,
49        chunk: String,
50    },
51    /// Tool call completed.
52    /// `call_id` must equal the `id` emitted with the corresponding `ToolCallStarted`.
53    ToolCallResult {
54        call_id: String,
55        name: String,
56        output: String,
57        success: bool,
58        duration: Duration,
59    },
60    /// Non-fatal error during execution
61    Error(String),
62    /// Non-fatal advisory surfaced from a provider or other subsystem.
63    /// TUI renders this as a one-line yellow banner; no turn failure.
64    /// Currently used for "provider may be truncating input" detection.
65    Warning(String),
66    /// Token usage update
67    TokenUsage {
68        prompt_tokens: usize,
69        completion_tokens: usize,
70        total_tokens: usize,
71        cached_tokens: usize,
72    },
73    /// Context budget stats for logging
74    ContextStats {
75        system_tokens: usize,
76        sent_tokens: usize,
77        dropped_tokens: usize,
78        working_set_tokens: usize,
79        total_messages: usize,
80    },
81    /// Emitted when a tool mutated `ctx.working_dir` (e.g. `change_dir`
82    /// or a `bash` call starting with `cd`). Lets the TUI footer track
83    /// the current cwd without polling the shared `Arc<RwLock<PathBuf>>`.
84    WorkingDirChanged(PathBuf),
85    /// A tool requires user approval. Carries a snapshot of
86    /// `conversation.messages` so the TUI can persist mid-turn
87    /// session state (e.g. for `/bg`). The approval itself is
88    /// handled by `PermissionDecider`; this event is purely
89    /// informational.
90    ApprovalRequested {
91        tool_name: String,
92        reason: String,
93        call: crate::tool::ToolCall,
94        messages: Vec<crate::conversation::message::Message>,
95    },
96}
97
98/// One call inside a `ToolBatchStarted` payload. Carries everything the UI
99/// needs to render a child row in the group block (name + abbreviated detail).
100#[derive(Debug, Clone, serde::Serialize)]
101pub struct ToolBatchCall {
102    pub id: String,
103    pub name: String,
104    pub arguments: String,
105}
106
107/// Result of a single turn execution
108#[derive(Debug)]
109pub enum TurnResult {
110    /// LLM produced text only, no tool calls.
111    /// `truncated` = true means finish_reason was "length" (model hit max_tokens).
112    Responded {
113        text: String,
114        tokens: usize,
115        truncated: bool,
116    },
117    /// LLM called tools, results added to conversation — ready for next turn
118    UsedTools {
119        text: Option<String>,
120        tool_count: usize,
121        tokens: usize,
122    },
123    /// Unrecoverable error
124    Failed(String),
125    /// Cancelled by caller
126    Cancelled,
127}
128
129impl TurnResult {
130    /// Returns true if this result represents an error condition (used by telemetry).
131    pub fn is_failed(&self) -> bool {
132        matches!(self, TurnResult::Failed(_))
133    }
134}