Skip to main content

oxios_kernel/
types.rs

1//! Core types for the Oxios kernel.
2//!
3//! Defines agent identity, status, and metadata.
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8/// Unique identifier for an agent instance.
9pub type AgentId = uuid::Uuid;
10
11/// Current status of an agent instance.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum AgentStatus {
14    /// Agent is being initialized.
15    Starting,
16    /// Agent is actively executing tasks.
17    Running,
18    /// Agent is alive but not currently working.
19    Idle,
20    /// Agent has been stopped.
21    Stopped,
22    /// Agent has encountered an error.
23    Failed,
24    /// Agent finished execution successfully.
25    Completed,
26}
27
28impl std::fmt::Display for AgentStatus {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            AgentStatus::Starting => write!(f, "starting"),
32            AgentStatus::Running => write!(f, "running"),
33            AgentStatus::Idle => write!(f, "idle"),
34            AgentStatus::Stopped => write!(f, "stopped"),
35            AgentStatus::Failed => write!(f, "failed"),
36            AgentStatus::Completed => write!(f, "completed"),
37        }
38    }
39}
40/// Priority levels for tasks.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
42pub enum Priority {
43    /// Low priority, good for background work.
44    Low = 0,
45    /// Normal priority, default for most tasks.
46    #[default]
47    Normal = 1,
48    /// High priority, important tasks.
49    High = 2,
50    /// Critical priority, must execute immediately.
51    Critical = 3,
52}
53
54/// Metadata about an agent instance.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct AgentInfo {
57    /// Unique identifier for this agent.
58    pub id: AgentId,
59    /// Human-readable name of the agent.
60    pub name: String,
61    /// Current status of the agent.
62    pub status: AgentStatus,
63    /// Timestamp when the agent was created.
64    pub created_at: DateTime<Utc>,
65    /// The seed this agent was forked from, if any.
66    pub seed_id: Option<uuid::Uuid>,
67    /// Project ID detected by the orchestrator.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub project_id: Option<uuid::Uuid>,
70    /// Timestamp when execution started.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub started_at: Option<DateTime<Utc>>,
73    /// Timestamp when execution completed.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub completed_at: Option<DateTime<Utc>>,
76    /// Error message if the agent failed.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub error: Option<String>,
79    /// Number of tool call steps completed.
80    #[serde(default)]
81    pub steps_completed: usize,
82    /// Number of total steps (if known).
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub steps_total: Option<usize>,
85    /// Tool calls recorded during execution.
86    #[serde(default, skip_serializing_if = "Vec::is_empty")]
87    pub tool_calls: Vec<ToolCallRecord>,
88    /// Total input tokens consumed.
89    #[serde(default)]
90    pub tokens_input: u64,
91    /// Total output tokens generated.
92    #[serde(default)]
93    pub tokens_output: u64,
94    /// Estimated cost in USD.
95    #[serde(default)]
96    pub cost_usd: f64,
97    /// Model ID used for execution.
98    #[serde(default, skip_serializing_if = "String::is_empty")]
99    pub model_id: String,
100    /// Session ID that spawned this agent (if any).
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub session_id: Option<String>,
103}
104
105/// Record of a single tool call during agent execution.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ToolCallRecord {
108    /// Tool name (e.g. "read", "bash", "grep").
109    pub tool: String,
110    /// Input parameters or invocation summary.
111    pub input: String,
112    /// Output or result summary.
113    pub output: String,
114    /// Duration of the tool call in milliseconds.
115    pub duration_ms: u64,
116    /// Whether the tool call returned an error.
117    #[serde(default)]
118    pub is_error: bool,
119    /// Provider-specific tool call ID.
120    #[serde(default, skip_serializing_if = "String::is_empty")]
121    pub tool_call_id: String,
122    /// Timestamp when the tool call started (UTC).
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub timestamp: Option<DateTime<Utc>>,
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_agent_status_display_all_variants() {
133        assert_eq!(AgentStatus::Starting.to_string(), "starting");
134        assert_eq!(AgentStatus::Running.to_string(), "running");
135        assert_eq!(AgentStatus::Idle.to_string(), "idle");
136        assert_eq!(AgentStatus::Stopped.to_string(), "stopped");
137        assert_eq!(AgentStatus::Failed.to_string(), "failed");
138    }
139
140    #[test]
141    fn test_agent_status_equality() {
142        assert_eq!(AgentStatus::Running, AgentStatus::Running);
143        assert_ne!(AgentStatus::Running, AgentStatus::Idle);
144    }
145
146    #[test]
147    fn test_agent_status_serialization_roundtrip() {
148        for status in [
149            AgentStatus::Starting,
150            AgentStatus::Running,
151            AgentStatus::Idle,
152            AgentStatus::Stopped,
153            AgentStatus::Failed,
154        ] {
155            let json = serde_json::to_string(&status).unwrap();
156            let restored: AgentStatus = serde_json::from_str(&json).unwrap();
157            assert_eq!(status, restored);
158        }
159    }
160
161    #[test]
162    fn test_agent_info_construction() {
163        let id = AgentId::new_v4();
164        let seed_id = uuid::Uuid::new_v4();
165        let now = Utc::now();
166
167        let info = AgentInfo {
168            id,
169            name: "test-agent".to_string(),
170            status: AgentStatus::Running,
171            created_at: now,
172            seed_id: Some(seed_id),
173            project_id: None,
174            started_at: None,
175            completed_at: None,
176            error: None,
177            steps_completed: 0,
178            steps_total: None,
179            tool_calls: vec![],
180            tokens_input: 0,
181            tokens_output: 0,
182            cost_usd: 0.0,
183            model_id: String::new(),
184            session_id: None,
185        };
186
187        assert_eq!(info.id, id);
188        assert_eq!(info.name, "test-agent");
189        assert_eq!(info.status, AgentStatus::Running);
190        assert_eq!(info.created_at, now);
191        assert_eq!(info.seed_id, Some(seed_id));
192    }
193
194    #[test]
195    fn test_agent_info_serialization_roundtrip() {
196        let info = AgentInfo {
197            id: AgentId::new_v4(),
198            name: "serializer".to_string(),
199            status: AgentStatus::Idle,
200            created_at: Utc::now(),
201            seed_id: None,
202            project_id: None,
203            started_at: None,
204            completed_at: None,
205            error: None,
206            steps_completed: 3,
207            steps_total: Some(5),
208            tool_calls: vec![],
209            tokens_input: 100,
210            tokens_output: 50,
211            cost_usd: 0.002,
212            model_id: String::new(),
213            session_id: None,
214        };
215
216        let json = serde_json::to_string(&info).unwrap();
217        let restored: AgentInfo = serde_json::from_str(&json).unwrap();
218        assert_eq!(restored.id, info.id);
219        assert_eq!(restored.name, info.name);
220        assert_eq!(restored.status, info.status);
221        assert_eq!(restored.seed_id, None);
222        assert_eq!(restored.steps_completed, 3);
223    }
224
225    #[test]
226    fn test_agent_status_copy() {
227        let status = AgentStatus::Running;
228        let copied = status; // Copy semantics
229        assert_eq!(status, copied); // status is still valid because Copy
230    }
231}