use crate::tools::ToolResult;
use bamboo_domain::{TaskItemStatus, TaskList};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentEvent {
Token {
content: String,
},
ReasoningToken {
content: String,
},
ToolToken {
tool_call_id: String,
content: String,
},
ToolStart {
tool_call_id: String,
tool_name: String,
arguments: serde_json::Value,
},
ToolComplete {
tool_call_id: String,
result: ToolResult,
},
ToolError {
tool_call_id: String,
error: String,
},
ToolLifecycle {
tool_call_id: String,
tool_name: String,
phase: String,
#[serde(skip_serializing_if = "Option::is_none")]
elapsed_ms: Option<u64>,
is_mutating: bool,
auto_approved: bool,
#[serde(skip_serializing_if = "Option::is_none")]
summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
NeedClarification {
question: String,
options: Option<Vec<String>>,
},
TaskListUpdated {
task_list: TaskList,
},
TaskListItemProgress {
session_id: String,
item_id: String,
status: TaskItemStatus,
tool_calls_count: usize,
version: u64,
},
TaskListCompleted {
session_id: String,
completed_at: DateTime<Utc>,
total_rounds: u32,
total_tool_calls: usize,
},
TaskEvaluationStarted {
session_id: String,
items_count: usize,
},
TaskEvaluationCompleted {
session_id: String,
updates_count: usize,
reasoning: String,
},
TokenBudgetUpdated {
usage: TokenBudgetUsage,
},
ContextCompressionStatus {
phase: String,
status: String,
},
ContextSummarized {
summary: String,
messages_summarized: usize,
tokens_saved: u32,
#[serde(default)]
usage_before_percent: f64,
#[serde(default)]
usage_after_percent: f64,
#[serde(default)]
trigger_type: String,
},
ContextPressureNotification {
percent: f64,
level: String,
message: String,
},
SubSessionStarted {
parent_session_id: String,
child_session_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
},
SubSessionEvent {
parent_session_id: String,
child_session_id: String,
event: Box<AgentEvent>,
},
SubSessionHeartbeat {
parent_session_id: String,
child_session_id: String,
timestamp: DateTime<Utc>,
},
SubSessionCompleted {
parent_session_id: String,
child_session_id: String,
status: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
Complete {
usage: TokenUsage,
},
Error {
message: String,
},
}
pub use bamboo_domain::TokenUsage;
pub use bamboo_domain::budget_types::TokenBudgetUsage;
#[cfg(test)]
mod tests {
use super::*;
use bamboo_domain::{TaskItem, TaskItemStatus, TaskList};
fn sample_task_list() -> TaskList {
TaskList {
session_id: "session-1".to_string(),
title: "Task List".to_string(),
items: vec![TaskItem {
id: "task_1".to_string(),
description: "Implement event rename".to_string(),
status: TaskItemStatus::InProgress,
depends_on: Vec::new(),
notes: "Implementing".to_string(),
..TaskItem::default()
}],
created_at: Utc::now(),
updated_at: Utc::now(),
}
}
#[test]
fn task_list_updated_serializes_with_task_names() {
let event = AgentEvent::TaskListUpdated {
task_list: sample_task_list(),
};
let value = serde_json::to_value(event).expect("event should serialize");
assert_eq!(value["type"], "task_list_updated");
assert!(value.get("task_list").is_some());
assert!(value.get("todo_list").is_none());
}
#[test]
fn task_evaluation_completed_serializes_with_task_type() {
let event = AgentEvent::TaskEvaluationCompleted {
session_id: "session-1".to_string(),
updates_count: 2,
reasoning: "Updated statuses".to_string(),
};
let value = serde_json::to_value(event).expect("event should serialize");
assert_eq!(value["type"], "task_evaluation_completed");
}
#[test]
fn context_compression_status_serializes_with_phase_and_status() {
let event = AgentEvent::ContextCompressionStatus {
phase: "mid-turn".to_string(),
status: "started".to_string(),
};
let value = serde_json::to_value(event).expect("event should serialize");
assert_eq!(value["type"], "context_compression_status");
assert_eq!(value["phase"], "mid-turn");
assert_eq!(value["status"], "started");
}
}