Skip to main content

codex_mobile_contracts/
runtime.rs

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}