use arcan_core::protocol::AgentEvent;
use lago_api::sse::format::{SseFormat, SseFrame};
use lago_core::{BranchId, SessionId};
use crate::event_map;
pub struct SseBridge {
format: Box<dyn SseFormat>,
session_id: SessionId,
branch_id: BranchId,
seq: u64,
}
impl SseBridge {
pub fn new(format: Box<dyn SseFormat>, session_id: &str, branch_id: &str) -> Self {
Self {
format,
session_id: SessionId::from(session_id.to_string()),
branch_id: BranchId::from(branch_id.to_string()),
seq: 0,
}
}
pub fn format_event(&mut self, event: &AgentEvent, run_id: &str) -> Vec<SseFrame> {
self.seq += 1;
let event_id = uuid::Uuid::new_v4().to_string();
let envelope = event_map::arcan_to_lago(
&self.session_id,
&self.branch_id,
self.seq,
run_id,
event,
&event_id,
);
self.format.format(&envelope)
}
pub fn done_frame(&self) -> Option<SseFrame> {
self.format.done_frame()
}
pub fn extra_headers(&self) -> Vec<(String, String)> {
self.format.extra_headers()
}
pub fn format_name(&self) -> &str {
self.format.name()
}
}
pub fn select_format(name: &str) -> Option<Box<dyn SseFormat>> {
match name {
"openai" => Some(Box::new(lago_api::sse::openai::OpenAiFormat)),
"anthropic" => Some(Box::new(lago_api::sse::anthropic::AnthropicFormat)),
"vercel" => Some(Box::new(lago_api::sse::vercel::VercelFormat)),
"lago" => Some(Box::new(lago_api::sse::lago::LagoFormat)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use arcan_core::protocol::{RunStopReason, ToolCall};
#[test]
fn text_delta_produces_openai_frame() {
let mut bridge = SseBridge::new(select_format("openai").unwrap(), "test-session", "main");
let event = AgentEvent::TextDelta {
run_id: "r1".into(),
session_id: "s1".into(),
iteration: 1,
delta: "Hello!".into(),
};
let frames = bridge.format_event(&event, "r1");
assert_eq!(frames.len(), 1);
let data: serde_json::Value = serde_json::from_str(&frames[0].data).unwrap();
assert_eq!(data["object"], "chat.completion.chunk");
assert_eq!(data["choices"][0]["delta"]["content"], "Hello!");
}
#[test]
fn run_finished_with_answer_produces_openai_frame() {
let mut bridge = SseBridge::new(select_format("openai").unwrap(), "test-session", "main");
let event = AgentEvent::RunFinished {
run_id: "r1".into(),
session_id: "s1".into(),
reason: RunStopReason::Completed,
total_iterations: 1,
final_answer: Some("The answer is 42.".into()),
usage: None,
};
let frames = bridge.format_event(&event, "r1");
assert!(!frames.is_empty());
}
#[test]
fn tool_events_filtered_in_openai_format() {
let mut bridge = SseBridge::new(select_format("openai").unwrap(), "test-session", "main");
let event = AgentEvent::ToolCallRequested {
run_id: "r1".into(),
session_id: "s1".into(),
iteration: 1,
call: ToolCall {
call_id: "c1".into(),
tool_name: "bash".into(),
input: serde_json::json!({}),
},
};
let frames = bridge.format_event(&event, "r1");
assert!(frames.is_empty());
}
#[test]
fn lago_format_emits_all_events() {
let mut bridge = SseBridge::new(select_format("lago").unwrap(), "test-session", "main");
let event = AgentEvent::ToolCallRequested {
run_id: "r1".into(),
session_id: "s1".into(),
iteration: 1,
call: ToolCall {
call_id: "c1".into(),
tool_name: "bash".into(),
input: serde_json::json!({"cmd": "ls"}),
},
};
let frames = bridge.format_event(&event, "r1");
assert!(!frames.is_empty());
}
#[test]
fn done_frame_for_openai() {
let bridge = SseBridge::new(select_format("openai").unwrap(), "test-session", "main");
let done = bridge.done_frame().unwrap();
assert_eq!(done.data, "[DONE]");
}
#[test]
fn select_format_unknown_returns_none() {
assert!(select_format("unknown").is_none());
}
#[test]
fn select_format_all_known() {
for name in &["openai", "anthropic", "vercel", "lago"] {
assert!(select_format(name).is_some(), "format {name} not found");
}
}
#[test]
fn sequence_numbers_increment() {
let mut bridge = SseBridge::new(select_format("lago").unwrap(), "test-session", "main");
let event = AgentEvent::TextDelta {
run_id: "r1".into(),
session_id: "s1".into(),
iteration: 1,
delta: "a".into(),
};
bridge.format_event(&event, "r1");
bridge.format_event(&event, "r1");
assert_eq!(bridge.seq, 2);
}
}