use cersei_tools::permissions::{PermissionDecision, PermissionRequest};
use cersei_tools::PermissionLevel;
use cersei_types::*;
use std::time::Duration;
use tokio::sync::mpsc;
use crate::AgentOutput;
#[derive(Debug, Clone)]
pub enum AgentEvent {
TextDelta(String),
ThinkingDelta(String),
ToolStart {
name: String,
id: String,
input: serde_json::Value,
},
ToolEnd {
name: String,
id: String,
result: String,
is_error: bool,
duration: Duration,
},
ToolPermissionCheck {
name: String,
id: String,
level: PermissionLevel,
},
PermissionRequired(PermissionRequest),
TurnStart {
turn: u32,
},
TurnComplete {
turn: u32,
stop_reason: StopReason,
usage: Usage,
},
ModelRequestStart {
turn: u32,
message_count: usize,
token_estimate: u64,
},
ModelResponseStart {
turn: u32,
model: String,
},
TokenWarning {
pct_used: f64,
state: WarningState,
},
CompactStart {
reason: CompactReason,
messages_before: usize,
},
CompactEnd {
messages_after: usize,
tokens_freed: u64,
},
SessionLoaded {
session_id: String,
message_count: usize,
},
SessionSaved {
session_id: String,
},
CostUpdate {
turn_cost: f64,
cumulative_cost: f64,
input_tokens: u64,
output_tokens: u64,
},
SubAgentSpawned {
agent_id: String,
prompt: String,
},
SubAgentComplete {
agent_id: String,
result: AgentOutput,
},
HookFired {
event: cersei_hooks::HookEvent,
hook_name: String,
},
HookBlocked {
event: cersei_hooks::HookEvent,
hook_name: String,
reason: String,
},
Status(String),
Error(String),
Complete(AgentOutput),
}
#[derive(Debug, Clone, Copy)]
pub enum WarningState {
Normal,
Warning,
Critical,
}
#[derive(Debug, Clone, Copy)]
pub enum CompactReason {
ThresholdExceeded,
ManualTrigger,
ContextOverflow,
}
pub struct AgentStream {
rx: mpsc::Receiver<AgentEvent>,
control_tx: mpsc::Sender<AgentControl>,
}
impl AgentStream {
pub(crate) fn new(
rx: mpsc::Receiver<AgentEvent>,
control_tx: mpsc::Sender<AgentControl>,
) -> Self {
Self { rx, control_tx }
}
pub fn respond_permission(&self, request_id: String, decision: PermissionDecision) {
let _ = self.control_tx.try_send(AgentControl::PermissionResponse {
request_id,
decision,
});
}
pub fn cancel(&self) {
let _ = self.control_tx.try_send(AgentControl::Cancel);
}
pub fn inject_message(&self, message: String) {
let _ = self
.control_tx
.try_send(AgentControl::InjectMessage(message));
}
pub async fn next(&mut self) -> Option<AgentEvent> {
self.rx.recv().await
}
pub async fn collect(mut self) -> cersei_types::Result<AgentOutput> {
while let Some(event) = self.rx.recv().await {
match event {
AgentEvent::Complete(output) => return Ok(output),
AgentEvent::Error(e) => return Err(CerseiError::Other(anyhow::anyhow!(e))),
_ => continue,
}
}
Err(CerseiError::Cancelled)
}
pub async fn collect_text(mut self) -> cersei_types::Result<String> {
let mut text = String::new();
while let Some(event) = self.rx.recv().await {
match event {
AgentEvent::TextDelta(t) => text.push_str(&t),
AgentEvent::Complete(_) => return Ok(text),
AgentEvent::Error(e) => return Err(CerseiError::Other(anyhow::anyhow!(e))),
_ => continue,
}
}
Ok(text)
}
}
#[derive(Debug)]
pub(crate) enum AgentControl {
#[allow(dead_code)]
PermissionResponse {
request_id: String,
decision: PermissionDecision,
},
Cancel,
#[allow(dead_code)]
InjectMessage(String),
}