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}