use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
use crate::{
agent::AgentLoopExit,
cancel::CANCELED_STATUS_MESSAGE,
errors::{RuntimeError, E_RUN_033, E_RUN_034, E_RUN_035, E_RUN_036},
streaming::{
emit_chunk_sse, emit_chunk_sse_final, emit_sse, emit_thinking_chunk_sse, SseBroadcast,
SseEventBuffer, StreamArtifact, StreamStatus, TaskArtifactUpdateEvent,
TaskStatusUpdateEvent,
},
};
struct A2aTarget {
sse: Option<(SseBroadcast, Arc<Mutex<SseEventBuffer>>)>,
task_id: String,
context_id: Option<String>,
}
#[derive(Default)]
struct Segment {
open: bool,
streamed_text: bool,
cursor_removal_owed: bool,
streamed_thinking: bool,
}
struct Wire<'a> {
tx: &'a SseBroadcast,
buf: &'a Arc<Mutex<SseEventBuffer>>,
task_id: &'a str,
}
pub(super) struct A2aStream {
target: Option<A2aTarget>,
chunks_emitted: Arc<AtomicBool>,
segment: Segment,
result: String,
interrupted: bool,
harness_killed: bool,
spend_refusal: Option<String>,
}
impl A2aStream {
pub(super) fn new(
sse: Option<(SseBroadcast, Arc<Mutex<SseEventBuffer>>)>,
task_id: Option<String>,
context_id: Option<String>,
chunks_emitted: Arc<AtomicBool>,
) -> Self {
Self {
target: task_id.map(|task_id| A2aTarget {
sse,
task_id,
context_id,
}),
chunks_emitted,
segment: Segment::default(),
result: String::new(),
interrupted: false,
harness_killed: false,
spend_refusal: None,
}
}
pub(super) fn mark_interrupted(&mut self) {
self.interrupted = true;
}
pub(super) fn mark_harness_killed(&mut self) {
self.harness_killed = true;
}
pub(super) fn mark_spend_refused(&mut self, refusal: &crate::spend::SpendRefusal) {
self.spend_refusal = Some(refusal.to_string());
}
pub(super) async fn open_segment(&mut self, turn: u32) {
if self.segment.open {
return;
}
self.segment = Segment {
open: true,
..Segment::default()
};
self.chunks_emitted.store(false, Ordering::Relaxed);
self.status("working", &format!("inference turn {turn}"), None, false)
.await;
}
pub(super) fn text_delta(&mut self, text: &str) {
self.segment.streamed_text = true;
self.segment.cursor_removal_owed = true;
self.chunks_emitted.store(true, Ordering::Relaxed);
if let Some(wire) = self.wire() {
emit_chunk_sse(wire.tx, wire.buf, wire.task_id, text);
}
}
pub(super) fn complete_text(&mut self) {
self.pay_cursor_removal();
}
pub(super) fn thinking_delta(&mut self, text: &str) {
self.segment.streamed_thinking = true;
if let Some(wire) = self.wire() {
emit_thinking_chunk_sse(wire.tx, wire.buf, wire.task_id, text);
}
}
pub(super) fn complete_thinking(&mut self, text: &str) {
if self.segment.streamed_thinking {
return;
}
if let Some(wire) = self.wire() {
emit_thinking_chunk_sse(wire.tx, wire.buf, wire.task_id, text);
}
}
pub(super) async fn tool_result(&mut self, artifact: StreamArtifact) {
self.pay_cursor_removal();
self.segment.open = false;
let Some(target) = self.target.as_ref() else {
return;
};
emit_sse(
&target.sse,
"artifact",
&TaskArtifactUpdateEvent {
id: target.task_id.clone(),
artifact,
},
)
.await;
}
pub(super) fn turn_end(&mut self, result: &str) {
self.pay_cursor_removal();
self.segment.open = false;
self.result = result.to_string();
if self.interrupted || self.segment.streamed_text || result.is_empty() {
return;
}
if let Some(wire) = self.wire() {
emit_chunk_sse_final(wire.tx, wire.buf, wire.task_id, result);
}
}
pub(super) fn turn_failed(&mut self) {
self.pay_cursor_removal();
self.segment.open = false;
}
pub(super) async fn finish(&mut self, outcome: &Result<AgentLoopExit, RuntimeError>) {
match outcome {
Ok(AgentLoopExit::Canceled) => {
let message = canceled_message(self.harness_killed).to_string();
self.status(AgentLoopExit::Canceled.as_str(), &message, None, true)
.await;
}
Ok(AgentLoopExit::SpendCeilingReached) => {
let message = self.spend_refusal.clone().unwrap_or_default();
self.status("failed", &message, None, true).await;
}
Ok(_) => {
let response = self.result.clone();
self.status("completed", "session ended", Some(response), true)
.await;
}
Err(error) => {
self.status("failed", &failure_message(error), None, true)
.await;
}
}
}
fn pay_cursor_removal(&mut self) {
if !self.segment.cursor_removal_owed {
return;
}
self.segment.cursor_removal_owed = false;
if let Some(wire) = self.wire() {
emit_chunk_sse_final(wire.tx, wire.buf, wire.task_id, "");
}
}
async fn status(&self, state: &str, message: &str, response: Option<String>, last: bool) {
let Some(target) = self.target.as_ref() else {
return;
};
emit_sse(
&target.sse,
"status",
&TaskStatusUpdateEvent {
id: target.task_id.clone(),
context_id: target.context_id.clone(),
status: StreamStatus {
state: state.to_string(),
message: message.to_string(),
response,
},
r#final: last,
},
)
.await;
}
fn wire(&self) -> Option<Wire<'_>> {
let target = self.target.as_ref()?;
let (tx, buf) = target.sse.as_ref()?;
Some(Wire {
tx,
buf,
task_id: &target.task_id,
})
}
}
fn canceled_message(harness_killed: bool) -> &'static str {
if harness_killed {
CANCELED_KILLED_STATUS_MESSAGE
} else {
CANCELED_STATUS_MESSAGE
}
}
const CANCELED_KILLED_STATUS_MESSAGE: &str =
"task canceled; the harness was killed and its session may not resume cleanly";
fn failure_message(error: &RuntimeError) -> String {
match diagnostic_code(error) {
Some(code) => format!("error[{code}]: {error}"),
None => error.to_string(),
}
}
fn diagnostic_code(error: &RuntimeError) -> Option<&'static str> {
match error {
RuntimeError::HarnessTurnFailed { .. } => Some(E_RUN_033),
RuntimeError::ProcessDriverCallFailed { .. } => Some(E_RUN_034),
RuntimeError::ProcessHarnessInactive { .. } => Some(E_RUN_035),
RuntimeError::HarnessSessionGone { .. } => Some(E_RUN_036),
_ => None,
}
}