use std::path::PathBuf;
use std::time::Duration;
use atomcode_kernel::event::ToolBatchCall;
use atomcode_kernel::message::{ImageContent, SessionSnapshot};
use atomcode_kernel::stream::TokenUsage;
use atomcode_kernel::tool::ToolCall;
#[derive(Debug, Clone)]
pub enum UiEvent {
TextDelta(String),
ReasoningDelta(String),
ToolCallStreaming {
name: String,
hint: String,
arg_chars: usize,
},
ToolCallStarted {
id: String,
name: String,
arguments: String,
},
ToolBatchStarted {
batch_id: String,
calls: Vec<ToolBatchCall>,
},
ToolBatchCompleted {
batch_id: String,
ok: usize,
total: usize,
elapsed_ms: u64,
},
ToolOutputChunk {
call_id: String,
chunk: String,
},
ToolCallResult {
call_id: String,
name: String,
output: String,
success: bool,
duration: Duration,
},
PolicyIntervention(atomcode_kernel::event::PolicyIntervention),
ApprovalNeeded {
tool_name: String,
reason: Option<String>,
call: ToolCall,
snapshot: SessionSnapshot,
},
TokenUsage {
tokens: TokenUsage,
used_tokens: usize,
ctx_window: usize,
},
PhaseChange(UiAgentPhase),
TurnComplete {
duration: Duration,
total_tokens: usize,
turn_count: usize,
tool_call_count: usize,
stop_reason: UiTurnStopReason,
snapshot: SessionSnapshot,
},
TurnCancelled {
snapshot: SessionSnapshot,
},
ConversationTruncated {
snapshot: SessionSnapshot,
restored_prompt: String,
target_n: usize,
prompts_before: usize,
},
UndoFailed {
requested: usize,
available: usize,
},
MessagesSync {
snapshot: SessionSnapshot,
},
ConversationRestored {
restore_id: u64,
snapshot: SessionSnapshot,
},
ConversationRestoreFailed {
restore_id: u64,
error: String,
},
Error {
error: String,
snapshot: SessionSnapshot,
},
Warning(String),
StreamRecovery {
attempt: u32,
max_attempts: u32,
recovered: bool,
},
OutputTruncationRecovery {
attempt: u32,
max_attempts: u32,
},
RateLimited {
reset_at_display: String,
reset_label: String,
secs_until_reset: Option<u64>,
auto_resuming: bool,
server_message: Option<String>,
},
HookWarningHint(String),
RestorePendingImages {
images: Vec<ImageContent>,
markers: Vec<usize>,
},
VisionPreprocessSuccess {
vl_model: String,
char_count: usize,
},
SubAgentDispatchStart {
tasks: Vec<SubAgentTaskInfo>,
},
SubAgentDispatchEnd,
SubAgentTaskStarted {
index: usize,
},
SubAgentTaskDone {
index: usize,
elapsed_ms: u64,
turns: usize,
summary: String,
},
SubAgentTaskFailed {
index: usize,
elapsed_ms: u64,
turns: usize,
reason: String,
},
GoalUpdate {
active: bool,
terminal: Option<atomcode_coding::GoalTerminal>,
phase: atomcode_coding::GoalPhase,
round: u32,
elapsed_secs: u64,
condition: String,
last_reason: Option<String>,
},
LoopUpdate {
active: bool,
round: u32,
elapsed_secs: u64,
label: String,
last_reason: Option<String>,
},
WorkingDirChanged(PathBuf),
ProjectSwitched(PathBuf),
RemoteSlashCommand(String),
ContextStats {
system_tokens: usize,
sent_tokens: usize,
dropped_tokens: usize,
working_set_tokens: usize,
total_messages: usize,
tool_defs_tokens: usize,
cold_zone_tokens: usize,
ctx_window: usize,
ctx_name: String,
system_prompt: String,
},
UserEcho(String),
SharedRequestResolved {
request_id: atomcode_kernel::event::RequestId,
kind: String,
},
PeerBusy(bool),
ProviderChanged(String),
ModeChanged {
plan: bool,
bypass: bool,
},
SessionRenamed {
name: String,
},
Steered {
count: usize,
inputs: Vec<atomcode_kernel::event::SteeredInput>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum UiAgentPhase {
Idle,
Thinking,
CallingTool(String),
WaitingApproval,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UiTurnStopReason {
Natural,
TurnLimit,
StepLimit,
Cancelled,
PolicyDenied,
Error,
}
impl UiTurnStopReason {
pub fn as_tag(&self) -> &'static str {
match self {
Self::Natural => "natural",
Self::TurnLimit => "turn_limit",
Self::StepLimit => "step_limit",
Self::Cancelled => "cancelled",
Self::PolicyDenied => "policy_denied",
Self::Error => "error",
}
}
}
#[derive(Debug, Clone)]
pub struct SubAgentTaskInfo {
pub path: String,
pub dedup_suffix: String,
}
#[cfg(test)]
mod tests {
use super::UiEvent;
#[test]
fn tool_batch_payload_is_kernel_native() {
let calls: Vec<atomcode_kernel::event::ToolBatchCall> =
vec![atomcode_kernel::event::ToolBatchCall {
id: "call-1".into(),
name: "read_file".into(),
arguments: "{}".into(),
parallel_safe: true,
}];
let event = UiEvent::ToolBatchStarted {
batch_id: "batch-1".into(),
calls,
};
assert!(matches!(event, UiEvent::ToolBatchStarted { calls, .. } if calls.len() == 1));
}
}