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