use serde::Serialize;
use crate::agent::r#loop::AgentEvent;
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WebEvent {
Token {
text: String,
},
Response {
text: String,
},
ToolCall {
name: String,
args: String,
},
ToolResult {
name: String,
result: String,
success: bool,
},
FileModified {
path: String,
},
Error {
message: String,
},
GuardStop {
message: String,
},
StreamRetry {
attempt: u32,
max: u32,
},
Status {
iteration: u32,
elapsed_secs: u64,
prompt_tokens: u32,
completion_tokens: u32,
cached_tokens: u32,
context_tokens: usize,
},
PhaseChange {
label: String,
},
PlanReady {
plan: String,
},
SwarmAgentStarted {
agent_id: String,
agent_name: String,
task_preview: String,
},
SwarmAgentProgress {
agent_id: String,
agent_name: String,
iteration: u32,
status: String,
},
SwarmAgentToolCall {
agent_id: String,
name: String,
args: String,
},
SwarmAgentToolResult {
agent_id: String,
name: String,
result: String,
success: bool,
},
SwarmAgentToken {
agent_id: String,
text: String,
},
SwarmAgentResponse {
agent_id: String,
text: String,
},
SwarmAgentDone {
agent_id: String,
agent_name: String,
success: bool,
modified_files: Vec<String>,
tool_calls: u32,
input_tokens: u64,
output_tokens: u64,
response: String,
},
SwarmWorkerApproaching {
agent_id: String,
task_preview: String,
remaining: u32,
},
SwarmConflict {
conflicts: Vec<(String, Vec<String>)>,
},
SwarmDone {
merged_response: String,
agent_count: usize,
total_tool_calls: u32,
conflicts_resolved: usize,
},
SwarmWorkersDispatched,
SwarmWorkerPaused {
agent_id: String,
},
SwarmWorkerResumed {
agent_id: String,
},
PerformanceUpdate {
tool_latency_avg_ms: f64,
api_latency_avg_ms: f64,
total_iterations: u32,
total_tokens_used: u64,
},
Done,
}
impl WebEvent {
pub fn from_agent_event(ev: &AgentEvent) -> Self {
match ev {
AgentEvent::Token(t) => Self::Token { text: t.clone() },
AgentEvent::Response(t) => Self::Response { text: t.clone() },
AgentEvent::ToolCall { name, args, .. } => Self::ToolCall {
name: name.clone(),
args: args.clone(),
},
AgentEvent::ToolResult {
name,
result,
success,
..
} => Self::ToolResult {
name: name.clone(),
result: result.clone(),
success: *success,
},
AgentEvent::FileModified { path } => Self::FileModified { path: path.clone() },
AgentEvent::Done { .. } => Self::Done,
AgentEvent::Error(msg) => Self::Error {
message: msg.clone(),
},
AgentEvent::GuardStop(msg) => Self::GuardStop {
message: msg.clone(),
},
AgentEvent::StreamRetry { attempt, max, .. } => Self::StreamRetry {
attempt: *attempt,
max: *max,
},
AgentEvent::Status {
iteration,
elapsed_secs,
prompt_tokens,
completion_tokens,
cached_tokens,
context_tokens,
} => Self::Status {
iteration: *iteration,
elapsed_secs: *elapsed_secs,
prompt_tokens: *prompt_tokens,
completion_tokens: *completion_tokens,
cached_tokens: *cached_tokens,
context_tokens: *context_tokens,
},
AgentEvent::PhaseChange { label } => Self::PhaseChange {
label: label.clone(),
},
AgentEvent::PlanReady { plan, .. } => Self::PlanReady { plan: plan.clone() },
AgentEvent::SwarmAgentStarted {
agent_id,
agent_name,
task_preview,
} => Self::SwarmAgentStarted {
agent_id: agent_id.clone(),
agent_name: agent_name.clone(),
task_preview: task_preview.clone(),
},
AgentEvent::SwarmAgentProgress {
agent_id,
agent_name,
iteration,
status,
} => Self::SwarmAgentProgress {
agent_id: agent_id.clone(),
agent_name: agent_name.clone(),
iteration: *iteration,
status: status.clone(),
},
AgentEvent::SwarmAgentToolCall {
agent_id,
name,
args,
} => Self::SwarmAgentToolCall {
agent_id: agent_id.clone(),
name: name.clone(),
args: args.clone(),
},
AgentEvent::SwarmAgentToolResult {
agent_id,
name,
result,
success,
} => Self::SwarmAgentToolResult {
agent_id: agent_id.clone(),
name: name.clone(),
result: result.clone(),
success: *success,
},
AgentEvent::SwarmAgentToken { agent_id, text } => Self::SwarmAgentToken {
agent_id: agent_id.clone(),
text: text.clone(),
},
AgentEvent::SwarmAgentResponse { agent_id, text } => Self::SwarmAgentResponse {
agent_id: agent_id.clone(),
text: text.clone(),
},
AgentEvent::SwarmAgentDone {
agent_id,
agent_name,
success,
modified_files,
tool_calls,
input_tokens,
output_tokens,
response,
} => Self::SwarmAgentDone {
agent_id: agent_id.clone(),
agent_name: agent_name.clone(),
success: *success,
modified_files: modified_files.clone(),
tool_calls: *tool_calls,
input_tokens: *input_tokens,
output_tokens: *output_tokens,
response: response.clone(),
},
AgentEvent::SwarmModeSwitch { .. } => Self::PhaseChange {
label: String::new(),
},
AgentEvent::SwarmResolvedToSingle { .. } => Self::PhaseChange {
label: String::new(),
},
AgentEvent::SwarmWorkerApproaching {
agent_id,
task_preview,
remaining,
} => Self::SwarmWorkerApproaching {
agent_id: agent_id.clone(),
task_preview: task_preview.clone(),
remaining: *remaining,
},
AgentEvent::SwarmConflict { conflicts } => Self::SwarmConflict {
conflicts: conflicts.clone(),
},
AgentEvent::SwarmDone {
merged_response,
agent_count,
total_tool_calls,
conflicts_resolved,
..
} => Self::SwarmDone {
merged_response: merged_response.clone(),
agent_count: *agent_count,
total_tool_calls: *total_tool_calls,
conflicts_resolved: *conflicts_resolved,
},
AgentEvent::SwarmWorkersDispatched => Self::SwarmWorkersDispatched,
AgentEvent::SwarmWorkerPaused { agent_id } => Self::SwarmWorkerPaused {
agent_id: agent_id.clone(),
},
AgentEvent::SwarmWorkerResumed { agent_id } => Self::SwarmWorkerResumed {
agent_id: agent_id.clone(),
},
AgentEvent::PerformanceUpdate {
tool_latency_avg_ms,
api_latency_avg_ms,
total_iterations,
total_tokens_used,
..
} => Self::PerformanceUpdate {
tool_latency_avg_ms: *tool_latency_avg_ms,
api_latency_avg_ms: *api_latency_avg_ms,
total_iterations: *total_iterations,
total_tokens_used: *total_tokens_used,
},
AgentEvent::LspInstalled { .. }
| AgentEvent::McpPids { .. }
| AgentEvent::SoulReflecting { .. }
| AgentEvent::ImageNotice { .. }
| AgentEvent::ApprovalRequired { .. }
| AgentEvent::ApprovalDenied { .. }
| AgentEvent::Evolution(_)
| AgentEvent::ShellOutput { .. } => Self::PhaseChange {
label: String::new(),
},
AgentEvent::ToolBatchProgress { running, total } => Self::PhaseChange {
label: format!("tools:{running}/{total}"),
},
AgentEvent::StreamWaiting { elapsed_secs } => Self::PhaseChange {
label: format!("stream_waiting:{elapsed_secs}s"),
},
AgentEvent::CompactionStarted { .. } => Self::PhaseChange {
label: "compaction_started".to_string(),
},
AgentEvent::CompactionDone {
before_tokens,
after_tokens,
} => Self::PhaseChange {
label: format!("compacted:{before_tokens}->{after_tokens}"),
},
AgentEvent::ToolResultTruncated {
tool_name,
original_bytes,
truncated_bytes,
} => Self::PhaseChange {
label: format!("truncated:{tool_name}:{original_bytes}->{truncated_bytes}"),
},
}
}
}