use agtrace_types::*;
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Copy)]
pub enum SemanticSuffix {
User,
Reasoning,
Message,
ToolCall,
ToolResult,
TokenUsage,
Notification,
SlashCommand,
QueueOperation,
Summary,
}
impl SemanticSuffix {
pub fn as_str(&self) -> &'static str {
match self {
Self::User => "user",
Self::Reasoning => "reasoning",
Self::Message => "message",
Self::ToolCall => "call",
Self::ToolResult => "result",
Self::TokenUsage => "usage",
Self::Notification => "notify",
Self::SlashCommand => "slashcmd",
Self::QueueOperation => "queue",
Self::Summary => "summary",
}
}
}
pub struct EventBuilder {
pub session_id: Uuid,
stream_tips: HashMap<StreamId, Uuid>,
tool_map: HashMap<String, Uuid>,
}
impl EventBuilder {
pub fn new(session_id: Uuid) -> Self {
Self {
session_id,
stream_tips: HashMap::new(),
tool_map: HashMap::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_and_push(
&mut self,
events: &mut Vec<AgentEvent>,
base_id: &str,
suffix: SemanticSuffix,
timestamp: DateTime<Utc>,
payload: EventPayload,
metadata: Option<serde_json::Value>,
stream_id: StreamId,
) -> Uuid {
let name = format!("{}:{}", base_id, suffix.as_str());
let id = Uuid::new_v5(&self.session_id, name.as_bytes());
let parent_id = self.stream_tips.get(&stream_id).copied();
let event = AgentEvent {
id,
session_id: self.session_id,
parent_id,
timestamp,
stream_id: stream_id.clone(),
payload,
metadata,
};
let event_id = event.id;
events.push(event);
self.stream_tips.insert(stream_id, event_id);
event_id
}
pub fn register_tool_call(&mut self, provider_id: String, uuid: Uuid) {
self.tool_map.insert(provider_id, uuid);
}
pub fn get_tool_call_uuid(&self, provider_id: &str) -> Option<Uuid> {
self.tool_map.get(provider_id).copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::claude::ClaudeToolMapper;
use crate::traits::ToolMapper;
#[test]
fn test_event_builder_chain() {
let session_id = Uuid::new_v4();
let mut builder = EventBuilder::new(session_id);
let mut events = Vec::new();
let event1_id = builder.build_and_push(
&mut events,
"test-id-1",
SemanticSuffix::User,
Utc::now(),
EventPayload::User(UserPayload {
text: "Hello".to_string(),
}),
None,
StreamId::Main,
);
assert_eq!(events[0].parent_id, None);
assert_eq!(events[0].session_id, session_id);
assert_eq!(events[0].stream_id, StreamId::Main);
let event2_id = builder.build_and_push(
&mut events,
"test-id-2",
SemanticSuffix::Message,
Utc::now(),
EventPayload::Message(MessagePayload {
text: "Hi".to_string(),
}),
None,
StreamId::Main,
);
assert_eq!(events[1].parent_id, Some(event1_id));
let mapper = ClaudeToolMapper;
builder.build_and_push(
&mut events,
"test-id-3",
SemanticSuffix::ToolCall,
Utc::now(),
EventPayload::ToolCall(mapper.normalize_call(
"Bash",
serde_json::json!({"command": "ls"}),
Some("call_123".to_string()),
)),
None,
StreamId::Main,
);
assert_eq!(events[2].parent_id, Some(event2_id));
}
#[test]
fn test_multi_stream_chains() {
let session_id = Uuid::new_v4();
let mut builder = EventBuilder::new(session_id);
let mut events = Vec::new();
let main1_id = builder.build_and_push(
&mut events,
"main-1",
SemanticSuffix::User,
Utc::now(),
EventPayload::User(UserPayload {
text: "Main".to_string(),
}),
None,
StreamId::Main,
);
let _side1_id = builder.build_and_push(
&mut events,
"side-1",
SemanticSuffix::User,
Utc::now(),
EventPayload::User(UserPayload {
text: "Sidechain".to_string(),
}),
None,
StreamId::Sidechain {
agent_id: "test123".to_string(),
},
);
let _main2_id = builder.build_and_push(
&mut events,
"main-2",
SemanticSuffix::Message,
Utc::now(),
EventPayload::Message(MessagePayload {
text: "Main 2".to_string(),
}),
None,
StreamId::Main,
);
assert_eq!(events[0].parent_id, None); assert_eq!(events[2].parent_id, Some(main1_id));
assert_eq!(events[1].parent_id, None); }
#[test]
fn test_tool_map() {
let mut builder = EventBuilder::new(Uuid::new_v4());
let tool_uuid = Uuid::new_v4();
builder.register_tool_call("gemini-tool-123".to_string(), tool_uuid);
assert_eq!(
builder.get_tool_call_uuid("gemini-tool-123"),
Some(tool_uuid)
);
assert_eq!(builder.get_tool_call_uuid("nonexistent"), None);
}
}