use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WorkflowEventType {
Started,
Phase,
Log,
AgentStarted,
AgentEvent,
AgentCompleted,
AgentFailed,
Result,
Error,
Other(String),
}
impl WorkflowEventType {
pub fn as_str(&self) -> &str {
match self {
Self::Started => "workflow.started",
Self::Phase => "workflow.phase",
Self::Log => "workflow.log",
Self::AgentStarted => "workflow.agent_started",
Self::AgentEvent => "workflow.agent_event",
Self::AgentCompleted => "workflow.agent_completed",
Self::AgentFailed => "workflow.agent_failed",
Self::Result => "workflow.result",
Self::Error => "workflow.error",
Self::Other(event_type) => event_type.as_str(),
}
}
}
impl From<&str> for WorkflowEventType {
fn from(value: &str) -> Self {
match value {
"workflow.started" => Self::Started,
"workflow.phase" => Self::Phase,
"workflow.log" => Self::Log,
"workflow.agent_started" => Self::AgentStarted,
"workflow.agent_event" => Self::AgentEvent,
"workflow.agent_completed" => Self::AgentCompleted,
"workflow.agent_failed" => Self::AgentFailed,
"workflow.result" => Self::Result,
"workflow.error" => Self::Error,
value => Self::Other(value.to_string()),
}
}
}
impl From<String> for WorkflowEventType {
fn from(value: String) -> Self {
WorkflowEventType::from(value.as_str())
}
}
impl std::fmt::Display for WorkflowEventType {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Serialize for WorkflowEventType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for WorkflowEventType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let event_type = String::deserialize(deserializer)?;
Ok(Self::from(event_type))
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct WorkflowEventMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub run_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub step_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workflow_depth: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_step_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct WorkflowEvent {
#[serde(rename = "type")]
pub event_type: WorkflowEventType,
#[serde(skip_serializing_if = "Option::is_none")]
pub elapsed_nanos: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<WorkflowEventMetadata>,
pub data: Value,
}
impl WorkflowEvent {
pub fn started(start_time: String) -> Self {
Self {
event_type: WorkflowEventType::Started,
elapsed_nanos: None,
metadata: None,
data: serde_json::json!({ "startTime": start_time }),
}
}
pub fn log(message: String) -> Self {
Self {
event_type: WorkflowEventType::Log,
elapsed_nanos: None,
metadata: None,
data: serde_json::json!({ "message": message }),
}
}
pub fn phase(name: String, options: Option<Value>) -> Self {
let mut data = serde_json::Map::new();
data.insert("name".to_string(), Value::String(name));
if let Some(options) = options {
data.insert("options".to_string(), options);
}
Self {
event_type: WorkflowEventType::Phase,
elapsed_nanos: None,
metadata: None,
data: Value::Object(data),
}
}
pub fn result(
input_tokens: u64,
output_tokens: u64,
total_tokens: u64,
results: Value,
) -> Self {
Self {
event_type: WorkflowEventType::Result,
elapsed_nanos: None,
metadata: None,
data: serde_json::json!({
"tokenUsage": {
"inputTokens": input_tokens,
"outputTokens": output_tokens,
"totalTokens": total_tokens,
},
"results": results,
}),
}
}
pub fn error(message: String, details: Option<String>) -> Self {
let mut data = serde_json::Map::new();
data.insert("message".to_string(), Value::String(message));
if let Some(details) = details {
data.insert("details".to_string(), Value::String(details));
}
Self {
event_type: WorkflowEventType::Error,
elapsed_nanos: None,
metadata: None,
data: Value::Object(data),
}
}
pub fn agent_started(data: Value, metadata: WorkflowEventMetadata) -> Self {
Self {
event_type: WorkflowEventType::AgentStarted,
elapsed_nanos: None,
metadata: Some(metadata),
data,
}
}
pub fn agent_event(data: Value, metadata: WorkflowEventMetadata) -> Self {
Self {
event_type: WorkflowEventType::AgentEvent,
elapsed_nanos: None,
metadata: Some(metadata),
data,
}
}
pub fn agent_completed(data: Value, metadata: WorkflowEventMetadata) -> Self {
Self {
event_type: WorkflowEventType::AgentCompleted,
elapsed_nanos: None,
metadata: Some(metadata),
data,
}
}
pub fn agent_failed(data: Value, metadata: WorkflowEventMetadata) -> Self {
Self {
event_type: WorkflowEventType::AgentFailed,
elapsed_nanos: None,
metadata: Some(metadata),
data,
}
}
}
#[async_trait::async_trait]
pub trait WorkflowEventSink: Send + Sync {
async fn emit(&self, event: WorkflowEvent) -> anyhow::Result<()>;
}