Skip to main content

everruns_core/
execution_context.rs

1//! Transport-neutral context shared by execution phases and emitted events.
2
3use everruns_provider::typed_id::{ExecId, MessageId, SessionId, TurnId, WorkspaceId};
4use serde::{Deserialize, Serialize};
5
6/// Correlation and resource identity for one execution phase within a turn.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ExecutionContext {
9    /// Session that owns the turn.
10    pub session_id: SessionId,
11    /// Turn containing this execution phase.
12    pub turn_id: TurnId,
13    /// Input message that triggered the turn.
14    pub input_message_id: MessageId,
15    /// Unique identifier for this execution phase.
16    pub exec_id: ExecId,
17    /// Workspace used for virtual file operations, when explicitly attached.
18    #[serde(default)]
19    pub workspace_id: Option<WorkspaceId>,
20}
21
22impl ExecutionContext {
23    /// Create a context for the first execution phase in a turn.
24    pub fn new(session_id: SessionId, turn_id: TurnId, input_message_id: MessageId) -> Self {
25        Self {
26            session_id,
27            turn_id,
28            input_message_id,
29            exec_id: ExecId::new(),
30            workspace_id: None,
31        }
32    }
33
34    /// Attach the workspace addressed by this execution.
35    pub fn with_workspace_id(mut self, workspace_id: WorkspaceId) -> Self {
36        self.workspace_id = Some(workspace_id);
37        self
38    }
39
40    /// Create a context for the next phase while preserving turn lineage.
41    pub fn next_exec(&self) -> Self {
42        Self {
43            session_id: self.session_id,
44            turn_id: self.turn_id,
45            input_message_id: self.input_message_id,
46            exec_id: ExecId::new(),
47            workspace_id: self.workspace_id,
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn next_exec_preserves_lineage_and_changes_execution_id() {
58        let context = ExecutionContext::new(SessionId::new(), TurnId::new(), MessageId::new());
59        let next = context.next_exec();
60
61        assert_eq!(next.session_id, context.session_id);
62        assert_eq!(next.turn_id, context.turn_id);
63        assert_eq!(next.input_message_id, context.input_message_id);
64        assert_ne!(next.exec_id, context.exec_id);
65    }
66
67    #[test]
68    fn serialization_round_trips() {
69        let context = ExecutionContext::new(SessionId::new(), TurnId::new(), MessageId::new());
70        let json = serde_json::to_string(&context).unwrap();
71        let parsed: ExecutionContext = serde_json::from_str(&json).unwrap();
72
73        assert_eq!(parsed.session_id, context.session_id);
74        assert_eq!(parsed.turn_id, context.turn_id);
75        assert_eq!(parsed.input_message_id, context.input_message_id);
76        assert_eq!(parsed.exec_id, context.exec_id);
77    }
78}