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")]
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}