codex_mobile_bridge/app_server/
mod.rs1mod 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}