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    /// Project ID detected by the orchestrator.
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub project_id: Option<uuid::Uuid>,
68    /// Timestamp when execution started.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub started_at: Option<DateTime<Utc>>,
71    /// Timestamp when execution completed.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub completed_at: Option<DateTime<Utc>>,
74    /// Error message if the agent failed.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub error: Option<String>,
77    /// Number of tool call steps completed.
78    #[serde(default)]
79    pub steps_completed: usize,
80    /// Number of total steps (if known).
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub steps_total: Option<usize>,
83    /// Tool calls recorded during execution.
84    #[serde(default, skip_serializing_if = "Vec::is_empty")]
85    pub tool_calls: Vec<ToolCallRecord>,
86    /// Total input tokens consumed.
87    #[serde(default)]
88    pub tokens_input: u64,
89    /// Total output tokens generated.
90    #[serde(default)]
91    pub tokens_output: u64,
92    /// Estimated cost in USD.
93    #[serde(default)]
94    pub cost_usd: f64,
95    /// Model ID used for execution.
96    #[serde(default, skip_serializing_if = "String::is_empty")]
97    pub model_id: String,
98    /// Session ID that spawned this agent (if any).
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub session_id: Option<String>,
101}
102
103/// Record of a single tool call during agent execution.
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ToolCallRecord {
106    /// Tool name (e.g. "read", "bash", "grep").
107    pub tool: String,
108    /// Input parameters or invocation summary.
109    pub input: String,
110    /// Output or result summary.
111    pub output: String,
112    /// Duration of the tool call in milliseconds.
113    pub duration_ms: u64,
114    /// Whether the tool call returned an error.
115    #[serde(default)]
116    pub is_error: bool,
117    /// Provider-specific tool call ID.
118    #[serde(default, skip_serializing_if = "String::is_empty")]
119    pub tool_call_id: String,
120    /// Timestamp when the tool call started (UTC).
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub timestamp: Option<DateTime<Utc>>,
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_agent_status_display_all_variants() {
131        assert_eq!(AgentStatus::Starting.to_string(), "starting");
132        assert_eq!(AgentStatus::Running.to_string(), "running");
133        assert_eq!(AgentStatus::Idle.to_string(), "idle");
134        assert_eq!(AgentStatus::Stopped.to_string(), "stopped");
135        assert_eq!(AgentStatus::Failed.to_string(), "failed");
136    }
137
138    #[test]
139    fn test_agent_status_equality() {
140        assert_eq!(AgentStatus::Running, AgentStatus::Running);
141        assert_ne!(AgentStatus::Running, AgentStatus::Idle);
142    }
143
144    #[test]
145    fn test_agent_status_serialization_roundtrip() {
146        for status in [
147            AgentStatus::Starting,
148            AgentStatus::Running,
149            AgentStatus::Idle,
150            AgentStatus::Stopped,
151            AgentStatus::Failed,
152        ] {
153            let json = serde_json::to_string(&status).unwrap();
154            let restored: AgentStatus = serde_json::from_str(&json).unwrap();
155            assert_eq!(status, restored);
156        }
157    }
158
159    #[test]
160    fn test_agent_info_construction() {
161        let id = AgentId::new_v4();
162
163        let now = Utc::now();
164
165        let info = AgentInfo {
166            id,
167            name: "test-agent".to_string(),
168            status: AgentStatus::Running,
169            created_at: now,
170            project_id: None,
171            started_at: None,
172            completed_at: None,
173            error: None,
174            steps_completed: 0,
175            steps_total: None,
176            tool_calls: vec![],
177            tokens_input: 0,
178            tokens_output: 0,
179            cost_usd: 0.0,
180            model_id: String::new(),
181            session_id: None,
182        };
183
184        assert_eq!(info.id, id);
185        assert_eq!(info.name, "test-agent");
186        assert_eq!(info.status, AgentStatus::Running);
187        assert_eq!(info.created_at, now);
188    }
189
190    #[test]
191    fn test_agent_info_serialization_roundtrip() {
192        let info = AgentInfo {
193            id: AgentId::new_v4(),
194            name: "serializer".to_string(),
195            status: AgentStatus::Idle,
196            created_at: Utc::now(),
197            project_id: None,
198            started_at: None,
199            completed_at: None,
200            error: None,
201            steps_completed: 3,
202            steps_total: Some(5),
203            tool_calls: vec![],
204            tokens_input: 100,
205            tokens_output: 50,
206            cost_usd: 0.002,
207            model_id: String::new(),
208            session_id: None,
209        };
210
211        let json = serde_json::to_string(&info).unwrap();
212        let restored: AgentInfo = serde_json::from_str(&json).unwrap();
213        assert_eq!(restored.id, info.id);
214        assert_eq!(restored.name, info.name);
215        assert_eq!(restored.status, info.status);
216        assert_eq!(restored.steps_completed, 3);
217    }
218
219    #[test]
220    fn test_agent_status_copy() {
221        let status = AgentStatus::Running;
222        let copied = status; // Copy semantics
223        assert_eq!(status, copied); // status is still valid because Copy
224    }
225}