use std::path::PathBuf;
use std::time::Duration;
/// Low-level events emitted by TurnRunner during execution.
/// Does not contain approval events — approval is handled internally via PermissionDecider.
#[derive(Debug, Clone)]
pub enum TurnEvent {
/// LLM streaming text output
TextDelta(String),
/// LLM reasoning/thinking content (e.g., DeepSeek-R1, MiniMax-M2.7, o1-series).
/// Emitted when the model produces thinking content separately from the final response.
/// UI can optionally display this in verbose mode (Ctrl+O).
ReasoningDelta(String),
/// LLM has started emitting a tool call — name is known, arguments still streaming.
/// Fires once per tool call, BEFORE the full args have arrived. Lets the UI surface
/// the tool name immediately so users see "⠋ Write File…" instead of an opaque
/// "Generating…" while the model spends seconds streaming args.
ToolCallStreaming { name: String, hint: String },
/// Tool call fully assembled, about to execute.
/// `id` is the provider-supplied call id — pairs with the matching `ToolCallResult.call_id`.
ToolCallStarted {
id: String,
name: String,
arguments: String,
},
/// Multiple tool calls fan out from one assistant message. Emitted
/// BEFORE the per-call `ToolCallStarted` events, only when the
/// runner is about to dispatch ≥ 2 non-duplicate calls. Lets the
/// UI render a single grouped block instead of N independent rows.
/// Per-call `ToolCallStarted` events STILL fire — UIs that don't
/// care about batches can ignore the batch events and render each
/// call as today.
ToolBatchStarted {
batch_id: String,
calls: Vec<ToolBatchCall>,
},
/// Closes the batch opened by `ToolBatchStarted`. UI uses it to
/// finalize the group header with `ok / total / elapsed` summary.
ToolBatchCompleted {
batch_id: String,
ok: usize,
total: usize,
elapsed_ms: u64,
},
/// Real-time output chunk from a running tool (e.g., bash command).
/// Sent during tool execution before ToolCallResult.
ToolOutputChunk {
call_id: String,
chunk: String,
},
/// Tool call completed.
/// `call_id` must equal the `id` emitted with the corresponding `ToolCallStarted`.
ToolCallResult {
call_id: String,
name: String,
output: String,
success: bool,
duration: Duration,
},
/// Non-fatal error during execution
Error(String),
/// Non-fatal advisory surfaced from a provider or other subsystem.
/// TUI renders this as a one-line yellow banner; no turn failure.
/// Currently used for "provider may be truncating input" detection.
Warning(String),
/// Token usage update
TokenUsage {
prompt_tokens: usize,
completion_tokens: usize,
total_tokens: usize,
cached_tokens: usize,
},
/// Context budget stats for logging
ContextStats {
system_tokens: usize,
sent_tokens: usize,
dropped_tokens: usize,
working_set_tokens: usize,
total_messages: usize,
},
/// Emitted when a tool mutated `ctx.working_dir` (e.g. `change_dir`
/// or a `bash` call starting with `cd`). Lets the TUI footer track
/// the current cwd without polling the shared `Arc<RwLock<PathBuf>>`.
WorkingDirChanged(PathBuf),
/// A tool requires user approval. Carries a snapshot of
/// `conversation.messages` so the TUI can persist mid-turn
/// session state (e.g. for `/bg`). The approval itself is
/// handled by `PermissionDecider`; this event is purely
/// informational.
ApprovalRequested {
tool_name: String,
reason: String,
call: crate::tool::ToolCall,
messages: Vec<crate::conversation::message::Message>,
},
}
/// One call inside a `ToolBatchStarted` payload. Carries everything the UI
/// needs to render a child row in the group block (name + abbreviated detail).
#[derive(Debug, Clone, serde::Serialize)]
pub struct ToolBatchCall {
pub id: String,
pub name: String,
pub arguments: String,
}
/// Result of a single turn execution
#[derive(Debug)]
pub enum TurnResult {
/// LLM produced text only, no tool calls.
/// `truncated` = true means finish_reason was "length" (model hit max_tokens).
Responded {
text: String,
tokens: usize,
truncated: bool,
},
/// LLM called tools, results added to conversation — ready for next turn
UsedTools {
text: Option<String>,
tool_count: usize,
tokens: usize,
},
/// Unrecoverable error
Failed(String),
/// Cancelled by caller
Cancelled,
}
impl TurnResult {
/// Returns true if this result represents an error condition (used by telemetry).
pub fn is_failed(&self) -> bool {
matches!(self, TurnResult::Failed(_))
}
}