1extern crate alloc;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
14pub enum AgentState {
15 Spawned,
16 Running,
17 Suspended,
18 Terminal,
19}
20
21impl AgentState {
22 pub const fn as_str(self) -> &'static str {
24 match self {
25 Self::Spawned => "spawned",
26 Self::Running => "running",
27 Self::Suspended => "suspended",
28 Self::Terminal => "terminal",
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
37pub enum TerminalReason {
38 Completed,
39 Failed,
40 Killed,
41 Abandoned,
42 HostRestart,
43}
44
45impl TerminalReason {
46 pub const fn as_str(self) -> &'static str {
48 match self {
49 Self::Completed => "completed",
50 Self::Failed => "failed",
51 Self::Killed => "killed",
52 Self::Abandoned => "abandoned",
53 Self::HostRestart => "host_restart",
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct AgentRecord {
62 pub agent_id: String,
63 pub state: AgentState,
64 pub terminal_reason: Option<TerminalReason>,
65 pub provider: String,
66 pub provider_session_id: Option<String>,
67 pub checkpoint_session_id: Option<String>,
68 pub checkpoint_cursor: Option<i64>,
69 pub owner_actor: String,
70 pub owner_peer_class: String,
71 pub owner_write_namespace: String,
72 pub owner_visible_namespaces: Vec<String>,
73 pub spawn_fingerprint: String,
74 pub spawned_at: i64,
75 pub state_changed_at: i64,
76 pub idempotency_key: Option<String>,
77}
78
79#[cfg(all(test, feature = "serde"))]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn agent_state_as_str_matches_serde_wire_spelling() {
85 let cases = [
86 (AgentState::Spawned, "spawned"),
87 (AgentState::Running, "running"),
88 (AgentState::Suspended, "suspended"),
89 (AgentState::Terminal, "terminal"),
90 ];
91 for (state, spelling) in cases {
92 let wire = serde_json::to_value(state).unwrap();
93 assert_eq!(wire.as_str(), Some(state.as_str()), "{state:?}");
94 assert_eq!(state.as_str(), spelling);
95 }
96 }
97
98 #[test]
99 fn terminal_reason_as_str_matches_serde_wire_spelling() {
100 let cases = [
101 (TerminalReason::Completed, "completed"),
102 (TerminalReason::Failed, "failed"),
103 (TerminalReason::Killed, "killed"),
104 (TerminalReason::Abandoned, "abandoned"),
105 (TerminalReason::HostRestart, "host_restart"),
106 ];
107 for (reason, spelling) in cases {
108 let wire = serde_json::to_value(reason).unwrap();
109 assert_eq!(wire.as_str(), Some(reason.as_str()), "{reason:?}");
110 assert_eq!(reason.as_str(), spelling);
111 }
112 }
113}