use serde::{Deserialize, Serialize};
use std::fmt;
use svix_ksuid::{Ksuid, KsuidLike};
fn new_ksuid() -> String {
Ksuid::new(None, None).to_string()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ExecutionId(String);
impl ExecutionId {
pub fn new() -> Self {
Self(format!("exec_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for ExecutionId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ExecutionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for ExecutionId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ExecutionId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
pub type RunId = ExecutionId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct StepId(String);
impl StepId {
pub fn new() -> Self {
Self(format!("step_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for StepId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for StepId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for StepId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for StepId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
pub type NodeId = StepId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GraphId(String);
impl GraphId {
pub fn new() -> Self {
Self(format!("graph_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for GraphId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for GraphId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ArtifactId(String);
impl ArtifactId {
pub fn new() -> Self {
Self(format!("artifact_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for ArtifactId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ArtifactId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TenantId(String);
impl TenantId {
pub fn new() -> Self {
Self(format!("tenant_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for TenantId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for TenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for TenantId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for TenantId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UserId(String);
impl UserId {
pub fn new() -> Self {
Self(format!("user_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for UserId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for UserId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for UserId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum StepType {
LlmNode,
GraphNode,
ToolNode,
FunctionNode,
RouterNode,
BranchNode,
LoopNode,
}
impl fmt::Display for StepType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StepType::LlmNode => write!(f, "LlmNode"),
StepType::GraphNode => write!(f, "GraphNode"),
StepType::ToolNode => write!(f, "ToolNode"),
StepType::FunctionNode => write!(f, "FunctionNode"),
StepType::RouterNode => write!(f, "RouterNode"),
StepType::BranchNode => write!(f, "BranchNode"),
StepType::LoopNode => write!(f, "LoopNode"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum CallableType {
Completion,
Chat,
#[default]
Agent,
Workflow,
Background,
Composite,
Tool,
Custom,
}
impl fmt::Display for CallableType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CallableType::Completion => write!(f, "completion"),
CallableType::Chat => write!(f, "chat"),
CallableType::Agent => write!(f, "agent"),
CallableType::Workflow => write!(f, "workflow"),
CallableType::Background => write!(f, "background"),
CallableType::Composite => write!(f, "composite"),
CallableType::Tool => write!(f, "tool"),
CallableType::Custom => write!(f, "custom"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ParentType {
UserMessage,
ScheduleEvent,
StepExecution,
Webhook,
A2aRequest,
System,
AssistantMessage,
ThreadStart,
ToolResult,
}
impl fmt::Display for ParentType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParentType::UserMessage => write!(f, "user_message"),
ParentType::ScheduleEvent => write!(f, "schedule_event"),
ParentType::StepExecution => write!(f, "step_execution"),
ParentType::Webhook => write!(f, "webhook"),
ParentType::A2aRequest => write!(f, "a2a_request"),
ParentType::System => write!(f, "system"),
ParentType::AssistantMessage => write!(f, "assistant_message"),
ParentType::ThreadStart => write!(f, "thread_start"),
ParentType::ToolResult => write!(f, "tool_result"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParentLink {
pub parent_id: String,
pub parent_type: ParentType,
}
impl ParentLink {
pub fn new(parent_id: impl Into<String>, parent_type: ParentType) -> Self {
Self {
parent_id: parent_id.into(),
parent_type,
}
}
pub fn from_user_message(message_id: impl Into<String>) -> Self {
Self::new(message_id, ParentType::UserMessage)
}
pub fn from_step(step_id: &StepId) -> Self {
Self::new(step_id.as_str(), ParentType::StepExecution)
}
pub fn execution(execution_id: ExecutionId) -> Self {
Self::new(execution_id.as_str(), ParentType::StepExecution)
}
pub fn system() -> Self {
Self::new("system", ParentType::System)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum StepSourceType {
#[default]
InitialPlan,
Discovered,
Retry,
UserGuidance,
ToolResult,
LlmOutput,
A2aMessage,
}
impl fmt::Display for StepSourceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StepSourceType::InitialPlan => write!(f, "initial_plan"),
StepSourceType::Discovered => write!(f, "discovered"),
StepSourceType::Retry => write!(f, "retry"),
StepSourceType::UserGuidance => write!(f, "user_guidance"),
StepSourceType::ToolResult => write!(f, "tool_result"),
StepSourceType::LlmOutput => write!(f, "llm_output"),
StepSourceType::A2aMessage => write!(f, "a2a_message"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StepSource {
pub source_type: StepSourceType,
#[serde(skip_serializing_if = "Option::is_none")]
pub triggered_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub depth: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spawn_mode: Option<SpawnMode>,
}
impl StepSource {
pub fn new(source_type: StepSourceType) -> Self {
Self {
source_type,
triggered_by: None,
reason: None,
depth: None,
spawn_mode: None,
}
}
pub fn initial_plan() -> Self {
Self::new(StepSourceType::InitialPlan)
}
pub fn discovered(triggered_by: &StepId, reason: impl Into<String>, depth: u32) -> Self {
Self {
source_type: StepSourceType::Discovered,
triggered_by: Some(triggered_by.as_str().to_string()),
reason: Some(reason.into()),
depth: Some(depth),
spawn_mode: None,
}
}
pub fn retry(original_step_id: &StepId) -> Self {
Self {
source_type: StepSourceType::Retry,
triggered_by: Some(original_step_id.as_str().to_string()),
reason: None,
depth: None,
spawn_mode: None,
}
}
pub fn user_guidance(message_id: impl Into<String>, reason: impl Into<String>) -> Self {
Self {
source_type: StepSourceType::UserGuidance,
triggered_by: Some(message_id.into()),
reason: Some(reason.into()),
depth: None,
spawn_mode: None,
}
}
pub fn llm_output(step_id: &StepId, reason: impl Into<String>, depth: u32) -> Self {
Self {
source_type: StepSourceType::LlmOutput,
triggered_by: Some(step_id.as_str().to_string()),
reason: Some(reason.into()),
depth: Some(depth),
spawn_mode: None,
}
}
pub fn tool_result(step_id: &StepId, reason: impl Into<String>, depth: u32) -> Self {
Self {
source_type: StepSourceType::ToolResult,
triggered_by: Some(step_id.as_str().to_string()),
reason: Some(reason.into()),
depth: Some(depth),
spawn_mode: None,
}
}
pub fn with_spawn_mode(mut self, spawn_mode: SpawnMode) -> Self {
self.spawn_mode = Some(spawn_mode);
self
}
pub fn with_triggered_by(mut self, triggered_by: impl Into<String>) -> Self {
self.triggered_by = Some(triggered_by.into());
self
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
pub fn with_depth(mut self, depth: u32) -> Self {
self.depth = Some(depth);
self
}
}
impl Default for StepSource {
fn default() -> Self {
Self::initial_plan()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(tag = "mode", rename_all = "snake_case")]
pub enum SpawnMode {
#[default]
Inline,
Child {
#[serde(default)]
background: bool,
#[serde(default)]
inherit_inbox: bool,
#[serde(skip_serializing_if = "Option::is_none")]
policies: Option<serde_json::Value>,
},
}
impl SpawnMode {
pub fn inline() -> Self {
SpawnMode::Inline
}
pub fn child(background: bool, inherit_inbox: bool) -> Self {
SpawnMode::Child {
background,
inherit_inbox,
policies: None,
}
}
pub fn child_with_policies(
background: bool,
inherit_inbox: bool,
policies: serde_json::Value,
) -> Self {
SpawnMode::Child {
background,
inherit_inbox,
policies: Some(policies),
}
}
pub fn is_inline(&self) -> bool {
matches!(self, SpawnMode::Inline)
}
pub fn is_child(&self) -> bool {
matches!(self, SpawnMode::Child { .. })
}
pub fn is_background(&self) -> bool {
matches!(
self,
SpawnMode::Child {
background: true,
..
}
)
}
pub fn inherits_inbox(&self) -> bool {
matches!(
self,
SpawnMode::Child {
inherit_inbox: true,
..
}
)
}
}
impl fmt::Display for SpawnMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SpawnMode::Inline => write!(f, "inline"),
SpawnMode::Child {
background,
inherit_inbox,
..
} => {
write!(
f,
"child(background={}, inherit_inbox={})",
background, inherit_inbox
)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum CancellationPolicy {
#[default]
CascadeCancel,
WaitForChildren,
Detach,
}
impl fmt::Display for CancellationPolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CancellationPolicy::CascadeCancel => write!(f, "cascade_cancel"),
CancellationPolicy::WaitForChildren => write!(f, "wait_for_children"),
CancellationPolicy::Detach => write!(f, "detach"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ThreadId(String);
impl ThreadId {
pub fn new() -> Self {
Self(format!("thread_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for ThreadId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ThreadId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for ThreadId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for ThreadId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MessageId(String);
impl MessageId {
pub fn new() -> Self {
Self(format!("msg_{}", new_ksuid()))
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for MessageId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for MessageId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for MessageId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for MessageId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
pub mod prefixes {
pub const EXECUTION: &str = "exec_";
pub const STEP: &str = "step_";
pub const ARTIFACT: &str = "artifact_";
pub const GRAPH: &str = "graph_";
pub const TENANT: &str = "tenant_";
pub const USER: &str = "user_";
pub const EVENT: &str = "evt_";
pub const DECISION: &str = "dec_";
pub const CONTROL: &str = "ctrl_";
pub const THREAD: &str = "thread_";
pub const MESSAGE: &str = "msg_";
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_execution_id_new_has_correct_prefix() {
let id = ExecutionId::new();
assert!(
id.as_str().starts_with("exec_"),
"ExecutionId should start with 'exec_'"
);
}
#[test]
fn test_execution_id_new_unique() {
let id1 = ExecutionId::new();
let id2 = ExecutionId::new();
assert_ne!(id1, id2, "Each new ExecutionId should be unique");
}
#[test]
fn test_execution_id_from_string() {
let id = ExecutionId::from_string("exec_custom123");
assert_eq!(id.as_str(), "exec_custom123");
}
#[test]
fn test_execution_id_from_string_owned() {
let id = ExecutionId::from_string(String::from("exec_owned"));
assert_eq!(id.as_str(), "exec_owned");
}
#[test]
fn test_execution_id_display() {
let id = ExecutionId::from_string("exec_display_test");
assert_eq!(format!("{}", id), "exec_display_test");
}
#[test]
fn test_execution_id_default() {
let id = ExecutionId::default();
assert!(id.as_str().starts_with("exec_"));
}
#[test]
fn test_execution_id_from_string_trait() {
let id: ExecutionId = String::from("exec_from_string").into();
assert_eq!(id.as_str(), "exec_from_string");
}
#[test]
fn test_execution_id_from_str_trait() {
let id: ExecutionId = "exec_from_str".into();
assert_eq!(id.as_str(), "exec_from_str");
}
#[test]
fn test_execution_id_clone() {
let id1 = ExecutionId::new();
let id2 = id1.clone();
assert_eq!(id1, id2);
}
#[test]
fn test_execution_id_hash() {
let id1 = ExecutionId::from_string("exec_hash_test");
let id2 = ExecutionId::from_string("exec_hash_test");
let mut set = HashSet::new();
set.insert(id1);
assert!(set.contains(&id2));
}
#[test]
fn test_execution_id_debug() {
let id = ExecutionId::from_string("exec_debug");
let debug_str = format!("{:?}", id);
assert!(debug_str.contains("exec_debug"));
}
#[test]
fn test_execution_id_serde() {
let id = ExecutionId::from_string("exec_serde_test");
let serialized = serde_json::to_string(&id).unwrap();
let deserialized: ExecutionId = serde_json::from_str(&serialized).unwrap();
assert_eq!(id, deserialized);
}
#[test]
fn test_step_id_new_has_correct_prefix() {
let id = StepId::new();
assert!(
id.as_str().starts_with("step_"),
"StepId should start with 'step_'"
);
}
#[test]
fn test_step_id_new_unique() {
let id1 = StepId::new();
let id2 = StepId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_step_id_from_string() {
let id = StepId::from_string("step_custom");
assert_eq!(id.as_str(), "step_custom");
}
#[test]
fn test_step_id_display() {
let id = StepId::from_string("step_display");
assert_eq!(format!("{}", id), "step_display");
}
#[test]
fn test_step_id_default() {
let id = StepId::default();
assert!(id.as_str().starts_with("step_"));
}
#[test]
fn test_step_id_from_traits() {
let id1: StepId = String::from("step_string").into();
let id2: StepId = "step_str".into();
assert_eq!(id1.as_str(), "step_string");
assert_eq!(id2.as_str(), "step_str");
}
#[test]
fn test_step_id_hash() {
let id = StepId::from_string("step_hash");
let mut set = HashSet::new();
set.insert(id.clone());
assert!(set.contains(&id));
}
#[test]
fn test_step_id_serde() {
let id = StepId::from_string("step_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: StepId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_graph_id_new_has_correct_prefix() {
let id = GraphId::new();
assert!(id.as_str().starts_with("graph_"));
}
#[test]
fn test_graph_id_new_unique() {
let id1 = GraphId::new();
let id2 = GraphId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_graph_id_from_string() {
let id = GraphId::from_string("graph_custom");
assert_eq!(id.as_str(), "graph_custom");
}
#[test]
fn test_graph_id_display() {
let id = GraphId::from_string("graph_display");
assert_eq!(format!("{}", id), "graph_display");
}
#[test]
fn test_graph_id_default() {
let id = GraphId::default();
assert!(id.as_str().starts_with("graph_"));
}
#[test]
fn test_graph_id_serde() {
let id = GraphId::from_string("graph_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: GraphId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_artifact_id_new_has_correct_prefix() {
let id = ArtifactId::new();
assert!(id.as_str().starts_with("artifact_"));
}
#[test]
fn test_artifact_id_new_unique() {
let id1 = ArtifactId::new();
let id2 = ArtifactId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_artifact_id_from_string() {
let id = ArtifactId::from_string("artifact_custom");
assert_eq!(id.as_str(), "artifact_custom");
}
#[test]
fn test_artifact_id_display() {
let id = ArtifactId::from_string("artifact_display");
assert_eq!(format!("{}", id), "artifact_display");
}
#[test]
fn test_artifact_id_default() {
let id = ArtifactId::default();
assert!(id.as_str().starts_with("artifact_"));
}
#[test]
fn test_artifact_id_serde() {
let id = ArtifactId::from_string("artifact_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: ArtifactId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_tenant_id_new_has_correct_prefix() {
let id = TenantId::new();
assert!(id.as_str().starts_with("tenant_"));
}
#[test]
fn test_tenant_id_new_unique() {
let id1 = TenantId::new();
let id2 = TenantId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_tenant_id_from_string() {
let id = TenantId::from_string("tenant_custom");
assert_eq!(id.as_str(), "tenant_custom");
}
#[test]
fn test_tenant_id_display() {
let id = TenantId::from_string("tenant_display");
assert_eq!(format!("{}", id), "tenant_display");
}
#[test]
fn test_tenant_id_default() {
let id = TenantId::default();
assert!(id.as_str().starts_with("tenant_"));
}
#[test]
fn test_tenant_id_from_traits() {
let id1: TenantId = String::from("tenant_string").into();
let id2: TenantId = "tenant_str".into();
assert_eq!(id1.as_str(), "tenant_string");
assert_eq!(id2.as_str(), "tenant_str");
}
#[test]
fn test_tenant_id_serde() {
let id = TenantId::from_string("tenant_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: TenantId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_user_id_new_has_correct_prefix() {
let id = UserId::new();
assert!(id.as_str().starts_with("user_"));
}
#[test]
fn test_user_id_new_unique() {
let id1 = UserId::new();
let id2 = UserId::new();
assert_ne!(id1, id2);
}
#[test]
fn test_user_id_from_string() {
let id = UserId::from_string("user_custom");
assert_eq!(id.as_str(), "user_custom");
}
#[test]
fn test_user_id_display() {
let id = UserId::from_string("user_display");
assert_eq!(format!("{}", id), "user_display");
}
#[test]
fn test_user_id_default() {
let id = UserId::default();
assert!(id.as_str().starts_with("user_"));
}
#[test]
fn test_user_id_from_traits() {
let id1: UserId = String::from("user_string").into();
let id2: UserId = "user_str".into();
assert_eq!(id1.as_str(), "user_string");
assert_eq!(id2.as_str(), "user_str");
}
#[test]
fn test_user_id_serde() {
let id = UserId::from_string("user_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: UserId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_step_type_display_llm_node() {
assert_eq!(format!("{}", StepType::LlmNode), "LlmNode");
}
#[test]
fn test_step_type_display_graph_node() {
assert_eq!(format!("{}", StepType::GraphNode), "GraphNode");
}
#[test]
fn test_step_type_display_tool_node() {
assert_eq!(format!("{}", StepType::ToolNode), "ToolNode");
}
#[test]
fn test_step_type_display_function_node() {
assert_eq!(format!("{}", StepType::FunctionNode), "FunctionNode");
}
#[test]
fn test_step_type_display_router_node() {
assert_eq!(format!("{}", StepType::RouterNode), "RouterNode");
}
#[test]
fn test_step_type_display_branch_node() {
assert_eq!(format!("{}", StepType::BranchNode), "BranchNode");
}
#[test]
fn test_step_type_display_loop_node() {
assert_eq!(format!("{}", StepType::LoopNode), "LoopNode");
}
#[test]
fn test_step_type_serde_llm_node() {
let step = StepType::LlmNode;
let json = serde_json::to_string(&step).unwrap();
assert_eq!(json, "\"LlmNode\"");
let parsed: StepType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, StepType::LlmNode);
}
#[test]
fn test_step_type_serde_all_variants() {
let variants = vec![
StepType::LlmNode,
StepType::GraphNode,
StepType::ToolNode,
StepType::FunctionNode,
StepType::RouterNode,
StepType::BranchNode,
StepType::LoopNode,
];
for variant in variants {
let json = serde_json::to_string(&variant).unwrap();
let parsed: StepType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, variant);
}
}
#[test]
fn test_step_type_equality() {
assert_eq!(StepType::LlmNode, StepType::LlmNode);
assert_ne!(StepType::LlmNode, StepType::ToolNode);
}
#[test]
fn test_step_type_clone() {
let step = StepType::GraphNode;
let cloned = step.clone();
assert_eq!(step, cloned);
}
#[test]
fn test_callable_type_display_all() {
assert_eq!(format!("{}", CallableType::Completion), "completion");
assert_eq!(format!("{}", CallableType::Chat), "chat");
assert_eq!(format!("{}", CallableType::Agent), "agent");
assert_eq!(format!("{}", CallableType::Workflow), "workflow");
assert_eq!(format!("{}", CallableType::Background), "background");
assert_eq!(format!("{}", CallableType::Composite), "composite");
assert_eq!(format!("{}", CallableType::Tool), "tool");
assert_eq!(format!("{}", CallableType::Custom), "custom");
}
#[test]
fn test_callable_type_default() {
let default_type = CallableType::default();
assert_eq!(default_type, CallableType::Agent);
}
#[test]
fn test_callable_type_serde_all_variants() {
let variants = vec![
CallableType::Completion,
CallableType::Chat,
CallableType::Agent,
CallableType::Workflow,
CallableType::Background,
CallableType::Composite,
CallableType::Tool,
CallableType::Custom,
];
for variant in variants {
let json = serde_json::to_string(&variant).unwrap();
let parsed: CallableType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, variant);
}
}
#[test]
fn test_callable_type_serde_snake_case() {
let json = serde_json::to_string(&CallableType::Background).unwrap();
assert_eq!(json, "\"background\"");
let json = serde_json::to_string(&CallableType::Workflow).unwrap();
assert_eq!(json, "\"workflow\"");
}
#[test]
fn test_callable_type_equality() {
assert_eq!(CallableType::Agent, CallableType::Agent);
assert_ne!(CallableType::Agent, CallableType::Chat);
}
#[test]
fn test_callable_type_clone() {
let callable = CallableType::Workflow;
let cloned = callable.clone();
assert_eq!(callable, cloned);
}
#[test]
fn test_callable_type_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(CallableType::Agent);
set.insert(CallableType::Chat);
set.insert(CallableType::Agent); assert_eq!(set.len(), 2);
}
#[test]
fn test_parent_type_display_user_message() {
assert_eq!(format!("{}", ParentType::UserMessage), "user_message");
}
#[test]
fn test_parent_type_display_schedule_event() {
assert_eq!(format!("{}", ParentType::ScheduleEvent), "schedule_event");
}
#[test]
fn test_parent_type_display_step_execution() {
assert_eq!(format!("{}", ParentType::StepExecution), "step_execution");
}
#[test]
fn test_parent_type_display_webhook() {
assert_eq!(format!("{}", ParentType::Webhook), "webhook");
}
#[test]
fn test_parent_type_display_a2a_request() {
assert_eq!(format!("{}", ParentType::A2aRequest), "a2a_request");
}
#[test]
fn test_parent_type_display_system() {
assert_eq!(format!("{}", ParentType::System), "system");
}
#[test]
fn test_parent_type_serde_all_variants() {
let variants = vec![
ParentType::UserMessage,
ParentType::ScheduleEvent,
ParentType::StepExecution,
ParentType::Webhook,
ParentType::A2aRequest,
ParentType::System,
];
for variant in variants {
let json = serde_json::to_string(&variant).unwrap();
let parsed: ParentType = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, variant);
}
}
#[test]
fn test_parent_type_equality() {
assert_eq!(ParentType::System, ParentType::System);
assert_ne!(ParentType::System, ParentType::Webhook);
}
#[test]
fn test_parent_link_new() {
let link = ParentLink::new("msg_123", ParentType::UserMessage);
assert_eq!(link.parent_id, "msg_123");
assert_eq!(link.parent_type, ParentType::UserMessage);
}
#[test]
fn test_parent_link_new_with_string() {
let link = ParentLink::new(String::from("wh_456"), ParentType::Webhook);
assert_eq!(link.parent_id, "wh_456");
assert_eq!(link.parent_type, ParentType::Webhook);
}
#[test]
fn test_parent_link_from_user_message() {
let link = ParentLink::from_user_message("msg_user_789");
assert_eq!(link.parent_id, "msg_user_789");
assert_eq!(link.parent_type, ParentType::UserMessage);
}
#[test]
fn test_parent_link_from_step() {
let step_id = StepId::from_string("step_abc123");
let link = ParentLink::from_step(&step_id);
assert_eq!(link.parent_id, "step_abc123");
assert_eq!(link.parent_type, ParentType::StepExecution);
}
#[test]
fn test_parent_link_execution() {
let exec_id = ExecutionId::from_string("exec_parent");
let link = ParentLink::execution(exec_id);
assert_eq!(link.parent_id, "exec_parent");
assert_eq!(link.parent_type, ParentType::StepExecution);
}
#[test]
fn test_parent_link_system() {
let link = ParentLink::system();
assert_eq!(link.parent_id, "system");
assert_eq!(link.parent_type, ParentType::System);
}
#[test]
fn test_parent_link_serde() {
let link = ParentLink::new("test_id", ParentType::A2aRequest);
let json = serde_json::to_string(&link).unwrap();
let parsed: ParentLink = serde_json::from_str(&json).unwrap();
assert_eq!(link, parsed);
}
#[test]
fn test_parent_link_equality() {
let link1 = ParentLink::new("same_id", ParentType::System);
let link2 = ParentLink::new("same_id", ParentType::System);
let link3 = ParentLink::new("different_id", ParentType::System);
assert_eq!(link1, link2);
assert_ne!(link1, link3);
}
#[test]
fn test_parent_link_clone() {
let link = ParentLink::from_user_message("msg_clone");
let cloned = link.clone();
assert_eq!(link, cloned);
}
#[test]
fn test_prefix_execution() {
assert_eq!(prefixes::EXECUTION, "exec_");
}
#[test]
fn test_prefix_step() {
assert_eq!(prefixes::STEP, "step_");
}
#[test]
fn test_prefix_artifact() {
assert_eq!(prefixes::ARTIFACT, "artifact_");
}
#[test]
fn test_prefix_graph() {
assert_eq!(prefixes::GRAPH, "graph_");
}
#[test]
fn test_prefix_tenant() {
assert_eq!(prefixes::TENANT, "tenant_");
}
#[test]
fn test_prefix_user() {
assert_eq!(prefixes::USER, "user_");
}
#[test]
fn test_prefix_event() {
assert_eq!(prefixes::EVENT, "evt_");
}
#[test]
fn test_prefix_decision() {
assert_eq!(prefixes::DECISION, "dec_");
}
#[test]
fn test_prefix_control() {
assert_eq!(prefixes::CONTROL, "ctrl_");
}
#[test]
fn test_run_id_alias() {
let exec_id = ExecutionId::from_string("exec_alias");
let run_id: RunId = exec_id.clone();
assert_eq!(exec_id.as_str(), run_id.as_str());
}
#[test]
fn test_node_id_alias() {
let step_id = StepId::from_string("step_alias");
let node_id: NodeId = step_id.clone();
assert_eq!(step_id.as_str(), node_id.as_str());
}
#[test]
fn test_execution_id_ksuid_length() {
let id = ExecutionId::new();
assert_eq!(id.as_str().len(), 32);
}
#[test]
fn test_step_id_ksuid_length() {
let id = StepId::new();
assert_eq!(id.as_str().len(), 32);
}
#[test]
fn test_graph_id_ksuid_length() {
let id = GraphId::new();
assert_eq!(id.as_str().len(), 33);
}
#[test]
fn test_artifact_id_ksuid_length() {
let id = ArtifactId::new();
assert_eq!(id.as_str().len(), 36);
}
#[test]
fn test_tenant_id_ksuid_length() {
let id = TenantId::new();
assert_eq!(id.as_str().len(), 34);
}
#[test]
fn test_user_id_ksuid_length() {
let id = UserId::new();
assert_eq!(id.as_str().len(), 32);
}
#[test]
fn test_execution_ids_sortable() {
let mut ids: Vec<ExecutionId> = (0..10).map(|_| ExecutionId::new()).collect();
let original_count = ids.len();
ids.sort_by(|a, b| a.as_str().cmp(b.as_str()));
ids.dedup_by(|a, b| a.as_str() == b.as_str());
assert_eq!(
ids.len(),
original_count,
"All generated IDs should be unique"
);
}
#[test]
fn test_ksuid_contains_alphanumeric() {
let id = ExecutionId::new();
let ksuid_part = &id.as_str()[5..]; assert!(ksuid_part.chars().all(|c| c.is_ascii_alphanumeric()));
}
#[test]
fn test_thread_id_new_has_correct_prefix() {
let id = ThreadId::new();
assert!(
id.as_str().starts_with("thread_"),
"ThreadId should start with 'thread_'"
);
}
#[test]
fn test_thread_id_new_unique() {
let id1 = ThreadId::new();
let id2 = ThreadId::new();
assert_ne!(id1, id2, "Each new ThreadId should be unique");
}
#[test]
fn test_thread_id_from_string() {
let id = ThreadId::from_string("thread_custom123");
assert_eq!(id.as_str(), "thread_custom123");
}
#[test]
fn test_thread_id_display() {
let id = ThreadId::from_string("thread_display_test");
assert_eq!(format!("{}", id), "thread_display_test");
}
#[test]
fn test_thread_id_default() {
let id = ThreadId::default();
assert!(id.as_str().starts_with("thread_"));
}
#[test]
fn test_thread_id_from_traits() {
let id1: ThreadId = String::from("thread_string").into();
let id2: ThreadId = "thread_str".into();
assert_eq!(id1.as_str(), "thread_string");
assert_eq!(id2.as_str(), "thread_str");
}
#[test]
fn test_thread_id_hash() {
let id = ThreadId::from_string("thread_hash");
let mut set = HashSet::new();
set.insert(id.clone());
assert!(set.contains(&id));
}
#[test]
fn test_thread_id_serde() {
let id = ThreadId::from_string("thread_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: ThreadId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_thread_id_ksuid_length() {
let id = ThreadId::new();
assert_eq!(id.as_str().len(), 34);
}
#[test]
fn test_message_id_new_has_correct_prefix() {
let id = MessageId::new();
assert!(
id.as_str().starts_with("msg_"),
"MessageId should start with 'msg_'"
);
}
#[test]
fn test_message_id_new_unique() {
let id1 = MessageId::new();
let id2 = MessageId::new();
assert_ne!(id1, id2, "Each new MessageId should be unique");
}
#[test]
fn test_message_id_from_string() {
let id = MessageId::from_string("msg_custom123");
assert_eq!(id.as_str(), "msg_custom123");
}
#[test]
fn test_message_id_display() {
let id = MessageId::from_string("msg_display_test");
assert_eq!(format!("{}", id), "msg_display_test");
}
#[test]
fn test_message_id_default() {
let id = MessageId::default();
assert!(id.as_str().starts_with("msg_"));
}
#[test]
fn test_message_id_from_traits() {
let id1: MessageId = String::from("msg_string").into();
let id2: MessageId = "msg_str".into();
assert_eq!(id1.as_str(), "msg_string");
assert_eq!(id2.as_str(), "msg_str");
}
#[test]
fn test_message_id_hash() {
let id = MessageId::from_string("msg_hash");
let mut set = HashSet::new();
set.insert(id.clone());
assert!(set.contains(&id));
}
#[test]
fn test_message_id_serde() {
let id = MessageId::from_string("msg_serde");
let json = serde_json::to_string(&id).unwrap();
let parsed: MessageId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
#[test]
fn test_message_id_ksuid_length() {
let id = MessageId::new();
assert_eq!(id.as_str().len(), 31);
}
#[test]
fn test_prefix_thread() {
assert_eq!(prefixes::THREAD, "thread_");
}
#[test]
fn test_prefix_message() {
assert_eq!(prefixes::MESSAGE, "msg_");
}
#[test]
fn test_spawn_mode_inline_default() {
let mode = SpawnMode::Inline;
assert_eq!(mode, SpawnMode::Inline);
}
#[test]
fn test_spawn_mode_child_default_fields() {
let mode = SpawnMode::Child {
background: false,
inherit_inbox: false,
policies: None,
};
if let SpawnMode::Child {
background,
inherit_inbox,
policies,
} = mode
{
assert!(!background, "default background should be false");
assert!(!inherit_inbox, "default inherit_inbox should be false");
assert!(policies.is_none(), "default policies should be None");
} else {
panic!("Expected SpawnMode::Child");
}
}
#[test]
fn test_spawn_mode_child_with_inherit_inbox() {
let mode = SpawnMode::Child {
background: false,
inherit_inbox: true,
policies: None,
};
if let SpawnMode::Child { inherit_inbox, .. } = mode {
assert!(inherit_inbox, "inherit_inbox should be true");
} else {
panic!("Expected SpawnMode::Child");
}
}
#[test]
fn test_spawn_mode_child_background() {
let mode = SpawnMode::Child {
background: true,
inherit_inbox: false,
policies: None,
};
if let SpawnMode::Child { background, .. } = mode {
assert!(background, "background should be true");
} else {
panic!("Expected SpawnMode::Child");
}
}
#[test]
fn test_spawn_mode_serde_inline() {
let mode = SpawnMode::Inline;
let json = serde_json::to_string(&mode).unwrap();
assert!(
json.contains("\"mode\":\"inline\""),
"Inline should serialize with mode tag"
);
let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, parsed);
}
#[test]
fn test_spawn_mode_serde_child() {
let mode = SpawnMode::Child {
background: true,
inherit_inbox: true,
policies: None,
};
let json = serde_json::to_string(&mode).unwrap();
assert!(
json.contains("\"mode\":\"child\""),
"Child should serialize with mode tag"
);
assert!(
json.contains("\"background\":true"),
"background should be serialized"
);
assert!(
json.contains("\"inherit_inbox\":true"),
"inherit_inbox should be serialized"
);
let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, parsed);
}
#[test]
fn test_spawn_mode_serde_child_with_policies() {
let policies = serde_json::json!({
"max_steps": 100,
"max_tokens": 10000
});
let mode = SpawnMode::Child {
background: false,
inherit_inbox: true,
policies: Some(policies.clone()),
};
let json = serde_json::to_string(&mode).unwrap();
let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, parsed);
if let SpawnMode::Child {
policies: Some(p), ..
} = parsed
{
assert_eq!(p["max_steps"], 100);
} else {
panic!("Expected SpawnMode::Child with policies");
}
}
#[test]
fn test_spawn_mode_equality() {
let mode1 = SpawnMode::Inline;
let mode2 = SpawnMode::Inline;
let mode3 = SpawnMode::Child {
background: false,
inherit_inbox: false,
policies: None,
};
assert_eq!(mode1, mode2);
assert_ne!(mode1, mode3);
}
#[test]
fn test_spawn_mode_clone() {
let mode = SpawnMode::Child {
background: true,
inherit_inbox: true,
policies: Some(serde_json::json!({"key": "value"})),
};
let cloned = mode.clone();
assert_eq!(mode, cloned);
}
#[test]
fn test_cancellation_policy_default() {
let policy = CancellationPolicy::default();
assert_eq!(policy, CancellationPolicy::CascadeCancel);
}
#[test]
fn test_cancellation_policy_variants() {
let cascade = CancellationPolicy::CascadeCancel;
let wait = CancellationPolicy::WaitForChildren;
let detach = CancellationPolicy::Detach;
assert_ne!(cascade, wait);
assert_ne!(wait, detach);
assert_ne!(cascade, detach);
}
#[test]
fn test_cancellation_policy_serde_cascade() {
let policy = CancellationPolicy::CascadeCancel;
let json = serde_json::to_string(&policy).unwrap();
assert_eq!(json, "\"cascade_cancel\"");
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(policy, parsed);
}
#[test]
fn test_cancellation_policy_serde_wait() {
let policy = CancellationPolicy::WaitForChildren;
let json = serde_json::to_string(&policy).unwrap();
assert_eq!(json, "\"wait_for_children\"");
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(policy, parsed);
}
#[test]
fn test_cancellation_policy_serde_detach() {
let policy = CancellationPolicy::Detach;
let json = serde_json::to_string(&policy).unwrap();
assert_eq!(json, "\"detach\"");
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(policy, parsed);
}
#[test]
fn test_cancellation_policy_all_variants_serde() {
let variants = vec![
CancellationPolicy::CascadeCancel,
CancellationPolicy::WaitForChildren,
CancellationPolicy::Detach,
];
for variant in variants {
let json = serde_json::to_string(&variant).unwrap();
let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, variant);
}
}
#[test]
fn test_cancellation_policy_clone() {
let policy = CancellationPolicy::WaitForChildren;
let cloned = policy.clone();
assert_eq!(policy, cloned);
}
#[test]
fn test_cancellation_policy_hash() {
let policy = CancellationPolicy::Detach;
let mut set = HashSet::new();
set.insert(policy.clone());
assert!(set.contains(&policy));
}
#[test]
fn test_step_source_with_spawn_mode() {
let source = StepSource {
source_type: StepSourceType::Discovered,
triggered_by: Some("step_parent".to_string()),
reason: Some("test spawn".to_string()),
depth: Some(1),
spawn_mode: Some(SpawnMode::Child {
background: false,
inherit_inbox: true,
policies: None,
}),
};
assert!(source.spawn_mode.is_some());
if let Some(SpawnMode::Child { inherit_inbox, .. }) = source.spawn_mode {
assert!(inherit_inbox);
}
}
#[test]
fn test_step_source_discovered_default_spawn_mode() {
let spawner = StepId::new();
let source = StepSource::discovered(&spawner, "test reason", 1);
assert!(
source.spawn_mode.is_none(),
"discovered() should default to None spawn_mode"
);
}
#[test]
fn test_step_source_serde_with_spawn_mode() {
let source = StepSource {
source_type: StepSourceType::Discovered,
triggered_by: Some("step_123".to_string()),
reason: Some("test reason".to_string()),
depth: Some(2),
spawn_mode: Some(SpawnMode::Inline),
};
let json = serde_json::to_string(&source).unwrap();
let parsed: StepSource = serde_json::from_str(&json).unwrap();
assert_eq!(source, parsed);
}
}