Skip to main content

codex_mobile_bridge/app_server/
mod.rs

1mod handshake;
2mod manager;
3mod running;
4mod streams;
5
6#[cfg(test)]
7mod tests;
8
9use std::collections::HashMap;
10use std::path::PathBuf;
11use std::sync::{
12    Arc,
13    atomic::{AtomicBool, AtomicU64},
14};
15
16use anyhow::Result;
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use tokio::io::BufWriter;
20use tokio::process::{Child, ChildStdin};
21use tokio::sync::{Mutex, mpsc, oneshot};
22
23const APP_SERVER_EXPERIMENTAL_API_ENABLED: bool = true;
24const APP_SERVER_OPT_OUT_NOTIFICATION_METHODS: &[&str] = &[
25    "account/login/completed",
26    "account/rateLimits/updated",
27    "account/updated",
28    "app/list/updated",
29    "fs/changed",
30    "fuzzyFileSearch/sessionCompleted",
31    "fuzzyFileSearch/sessionUpdated",
32    "mcpServer/oauthLogin/completed",
33    "mcpServer/startupStatus/updated",
34    "skills/changed",
35    "thread/realtime/closed",
36    "thread/realtime/error",
37    "thread/realtime/itemAdded",
38    "thread/realtime/outputAudio/delta",
39    "thread/realtime/sdp",
40    "thread/realtime/started",
41    "thread/realtime/transcriptUpdated",
42    "windows/worldWritableWarning",
43    "windowsSandbox/setupCompleted",
44];
45
46#[derive(Debug, Clone)]
47pub struct AppServerLaunchConfig {
48    pub runtime_id: String,
49    pub codex_binary: String,
50    pub codex_home: Option<PathBuf>,
51}
52
53#[derive(Debug, Clone)]
54pub struct InitializeInfo {
55    pub user_agent: String,
56    pub codex_home: String,
57    pub platform_family: String,
58    pub platform_os: String,
59}
60
61#[derive(Debug, Clone)]
62pub enum AppServerInbound {
63    Starting {
64        runtime_id: String,
65    },
66    ProcessChanged {
67        runtime_id: String,
68        pid: Option<u32>,
69        running: bool,
70    },
71    Initializing {
72        runtime_id: String,
73        experimental_api_enabled: bool,
74        opt_out_notification_methods: Vec<String>,
75    },
76    Initialized {
77        runtime_id: String,
78        info: InitializeInfo,
79        experimental_api_enabled: bool,
80        opt_out_notification_methods: Vec<String>,
81    },
82    HandshakeFailed {
83        runtime_id: String,
84        message: String,
85        experimental_api_enabled: bool,
86        opt_out_notification_methods: Vec<String>,
87    },
88    Notification {
89        runtime_id: String,
90        method: String,
91        params: Value,
92    },
93    ServerRequest {
94        runtime_id: String,
95        id: Value,
96        method: String,
97        params: Value,
98    },
99    Exited {
100        runtime_id: String,
101        message: String,
102        expected: bool,
103    },
104    LogChunk {
105        runtime_id: String,
106        stream: String,
107        level: String,
108        source: String,
109        message: String,
110        detail: Option<Value>,
111        occurred_at_ms: i64,
112    },
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct RpcErrorPayload {
117    pub code: i64,
118    pub message: String,
119    #[serde(default)]
120    pub data: Option<Value>,
121}
122
123impl std::fmt::Display for RpcErrorPayload {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(f, "[{}] {}", self.code, self.message)
126    }
127}
128
129pub struct AppServerManager {
130    launch_config: AppServerLaunchConfig,
131    inbound_tx: mpsc::UnboundedSender<AppServerInbound>,
132    inner: Mutex<Option<Arc<RunningAppServer>>>,
133}
134
135type PendingRpcSender = oneshot::Sender<Result<Value, RpcErrorPayload>>;
136type PendingRpcMap = HashMap<String, PendingRpcSender>;
137
138struct RunningAppServer {
139    runtime_id: String,
140    stdin: Arc<Mutex<BufWriter<ChildStdin>>>,
141    child: Arc<Mutex<Option<Child>>>,
142    pending: Arc<Mutex<PendingRpcMap>>,
143    next_id: AtomicU64,
144    alive: Arc<AtomicBool>,
145    stopping: Arc<AtomicBool>,
146}