1use std::time::{SystemTime, UNIX_EPOCH};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "camelCase")]
7#[serde(default)]
8pub struct ApiError {
9 pub code: String,
10 pub message: String,
11}
12
13impl ApiError {
14 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
15 Self {
16 code: code.into(),
17 message: message.into(),
18 }
19 }
20}
21
22#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
23#[serde(rename_all = "camelCase")]
24#[serde(default)]
25pub struct RuntimeRecord {
26 pub runtime_id: String,
27 pub display_name: String,
28 pub codex_home: Option<String>,
29 pub codex_binary: String,
30 pub is_primary: bool,
31 pub auto_start: bool,
32 pub created_at_ms: i64,
33 pub updated_at_ms: i64,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(rename_all = "camelCase")]
38#[serde(default)]
39pub struct AppServerHandshakeSummary {
40 pub state: String,
41 pub protocol: String,
42 pub transport: String,
43 pub experimental_api_enabled: bool,
44 pub notification_mode: String,
45 #[serde(default)]
46 pub opt_out_notification_methods: Vec<String>,
47 pub detail: Option<String>,
48 pub updated_at_ms: i64,
49}
50
51impl Default for AppServerHandshakeSummary {
52 fn default() -> Self {
53 Self::inactive()
54 }
55}
56
57impl AppServerHandshakeSummary {
58 pub fn new(
59 state: impl Into<String>,
60 experimental_api_enabled: bool,
61 opt_out_notification_methods: Vec<String>,
62 detail: Option<String>,
63 ) -> Self {
64 let notification_mode = if opt_out_notification_methods.is_empty() {
65 "full".to_string()
66 } else {
67 "optimized".to_string()
68 };
69 Self {
70 state: state.into(),
71 protocol: "jsonrpc2.0".to_string(),
72 transport: "stdio".to_string(),
73 experimental_api_enabled,
74 notification_mode,
75 opt_out_notification_methods,
76 detail,
77 updated_at_ms: now_millis(),
78 }
79 }
80
81 pub fn inactive() -> Self {
82 Self::new("inactive", false, Vec::new(), None)
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "camelCase")]
88#[serde(default)]
89pub struct RuntimeStatus {
90 pub runtime_id: String,
91 pub status: String,
92 pub codex_home: Option<String>,
93 pub user_agent: Option<String>,
94 pub platform_family: Option<String>,
95 pub platform_os: Option<String>,
96 pub last_error: Option<String>,
97 pub pid: Option<i32>,
98 #[serde(default)]
99 pub app_server_handshake: AppServerHandshakeSummary,
100 pub updated_at_ms: i64,
101}
102
103impl Default for RuntimeStatus {
104 fn default() -> Self {
105 Self::stopped(String::new())
106 }
107}
108
109impl RuntimeStatus {
110 pub fn stopped(runtime_id: impl Into<String>) -> Self {
111 Self {
112 runtime_id: runtime_id.into(),
113 status: "stopped".to_string(),
114 codex_home: None,
115 user_agent: None,
116 platform_family: None,
117 platform_os: None,
118 last_error: None,
119 pid: None,
120 app_server_handshake: AppServerHandshakeSummary::inactive(),
121 updated_at_ms: now_millis(),
122 }
123 }
124}
125
126pub type RuntimeStatusSnapshot = RuntimeStatus;
127
128#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
129#[serde(rename_all = "camelCase")]
130#[serde(default)]
131pub struct RuntimeSummary {
132 pub runtime_id: String,
133 pub display_name: String,
134 pub codex_home: Option<String>,
135 pub codex_binary: String,
136 pub is_primary: bool,
137 pub auto_start: bool,
138 pub created_at_ms: i64,
139 pub updated_at_ms: i64,
140 pub status: RuntimeStatus,
141}
142
143impl RuntimeSummary {
144 pub fn from_parts(record: &RuntimeRecord, status: RuntimeStatus) -> Self {
145 Self {
146 runtime_id: record.runtime_id.clone(),
147 display_name: record.display_name.clone(),
148 codex_home: record.codex_home.clone(),
149 codex_binary: record.codex_binary.clone(),
150 is_primary: record.is_primary,
151 auto_start: record.auto_start,
152 created_at_ms: record.created_at_ms,
153 updated_at_ms: record.updated_at_ms,
154 status,
155 }
156 }
157}
158
159fn now_millis() -> i64 {
160 SystemTime::now()
161 .duration_since(UNIX_EPOCH)
162 .map(|value| value.as_millis() as i64)
163 .unwrap_or_default()
164}