1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Interaction - One intervention
//!
//! Interaction represents a single trigger → response attempt within a Thread.
//! It replaces the concept of "Turn" with a more general abstraction.
//!
//! Key points:
//! - Usually triggered by an Event
//! - Multiple Interactions can exist in the same Thread concurrently
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::thread::ThreadId;
use orchestral_core::types::TaskId;
pub use orchestral_core::store::InteractionId;
/// Interaction state
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InteractionState {
/// Interaction is active and processing
Active,
/// Interaction is waiting for user input
WaitingUser,
/// Interaction is waiting for external event
WaitingEvent,
/// Interaction is paused
Paused,
/// Interaction completed successfully
Completed,
/// Interaction failed
Failed,
/// Interaction was cancelled
Cancelled,
}
impl InteractionState {
/// Check if the interaction is in a terminal state
pub fn is_terminal(&self) -> bool {
matches!(
self,
InteractionState::Completed | InteractionState::Failed | InteractionState::Cancelled
)
}
/// Check if the interaction is active
pub fn is_active(&self) -> bool {
matches!(self, InteractionState::Active)
}
/// Check if the interaction is waiting
pub fn is_waiting(&self) -> bool {
matches!(
self,
InteractionState::WaitingUser | InteractionState::WaitingEvent
)
}
}
/// Interaction - a single trigger → response attempt
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Interaction {
/// Unique identifier
pub id: InteractionId,
/// Thread this interaction belongs to
pub thread_id: ThreadId,
/// Current state
pub state: InteractionState,
/// Task IDs associated with this interaction
pub task_ids: Vec<TaskId>,
/// Start timestamp
pub started_at: DateTime<Utc>,
/// End timestamp (if completed)
pub ended_at: Option<DateTime<Utc>>,
}
impl Interaction {
/// Create a new interaction
pub fn new(thread_id: impl Into<ThreadId>) -> Self {
Self {
id: InteractionId::from(uuid::Uuid::new_v4().to_string()),
thread_id: thread_id.into(),
state: InteractionState::Active,
task_ids: Vec::new(),
started_at: Utc::now(),
ended_at: None,
}
}
/// Create a new interaction with specific ID
pub fn with_id(id: impl Into<InteractionId>, thread_id: impl Into<ThreadId>) -> Self {
Self {
id: id.into(),
thread_id: thread_id.into(),
state: InteractionState::Active,
task_ids: Vec::new(),
started_at: Utc::now(),
ended_at: None,
}
}
/// Add a task to this interaction
pub fn add_task(&mut self, task_id: TaskId) {
self.task_ids.push(task_id);
}
/// Set the interaction state
pub fn set_state(&mut self, state: InteractionState) {
let is_terminal = state.is_terminal();
self.state = state;
if is_terminal && self.ended_at.is_none() {
self.ended_at = Some(Utc::now());
}
}
/// Mark the interaction as completed
pub fn complete(&mut self) {
self.set_state(InteractionState::Completed);
}
/// Mark the interaction as failed
pub fn fail(&mut self) {
self.set_state(InteractionState::Failed);
}
/// Mark the interaction as cancelled
pub fn cancel(&mut self) {
self.set_state(InteractionState::Cancelled);
}
/// Mark the interaction as waiting for user
pub fn wait_for_user(&mut self) {
self.set_state(InteractionState::WaitingUser);
}
/// Mark the interaction as waiting for event
pub fn wait_for_event(&mut self) {
self.set_state(InteractionState::WaitingEvent);
}
/// Resume the interaction (set back to active)
pub fn resume(&mut self) {
if !self.state.is_terminal() {
self.state = InteractionState::Active;
}
}
/// Get the duration of this interaction (if ended)
pub fn duration(&self) -> Option<chrono::Duration> {
self.ended_at.map(|end| end - self.started_at)
}
}