mod handshake;
mod manager;
mod running;
mod streams;
#[cfg(test)]
mod tests;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU64},
};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tokio::io::BufWriter;
use tokio::process::{Child, ChildStdin};
use tokio::sync::{Mutex, mpsc, oneshot};
const APP_SERVER_EXPERIMENTAL_API_ENABLED: bool = true;
const APP_SERVER_OPT_OUT_NOTIFICATION_METHODS: &[&str] = &[
"account/login/completed",
"account/rateLimits/updated",
"account/updated",
"app/list/updated",
"fs/changed",
"fuzzyFileSearch/sessionCompleted",
"fuzzyFileSearch/sessionUpdated",
"hook/completed",
"hook/started",
"item/autoApprovalReview/completed",
"item/autoApprovalReview/started",
"item/commandExecution/terminalInteraction",
"mcpServer/oauthLogin/completed",
"mcpServer/startupStatus/updated",
"skills/changed",
"thread/compacted",
"thread/realtime/closed",
"thread/realtime/error",
"thread/realtime/itemAdded",
"thread/realtime/outputAudio/delta",
"thread/realtime/sdp",
"thread/realtime/started",
"thread/realtime/transcriptUpdated",
"windows/worldWritableWarning",
"windowsSandbox/setupCompleted",
];
#[derive(Debug, Clone)]
pub struct AppServerLaunchConfig {
pub runtime_id: String,
pub codex_binary: String,
pub codex_home: Option<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct InitializeInfo {
pub user_agent: String,
pub codex_home: String,
pub platform_family: String,
pub platform_os: String,
}
#[derive(Debug, Clone)]
pub enum AppServerInbound {
Starting {
runtime_id: String,
},
ProcessChanged {
runtime_id: String,
pid: Option<u32>,
running: bool,
},
Initializing {
runtime_id: String,
experimental_api_enabled: bool,
opt_out_notification_methods: Vec<String>,
},
Initialized {
runtime_id: String,
info: InitializeInfo,
experimental_api_enabled: bool,
opt_out_notification_methods: Vec<String>,
},
HandshakeFailed {
runtime_id: String,
message: String,
experimental_api_enabled: bool,
opt_out_notification_methods: Vec<String>,
},
Notification {
runtime_id: String,
method: String,
params: Value,
},
ServerRequest {
runtime_id: String,
id: Value,
method: String,
params: Value,
},
Exited {
runtime_id: String,
message: String,
expected: bool,
},
LogChunk {
runtime_id: String,
stream: String,
level: String,
source: String,
message: String,
detail: Option<Value>,
occurred_at_ms: i64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcErrorPayload {
pub code: i64,
pub message: String,
#[serde(default)]
pub data: Option<Value>,
}
impl std::fmt::Display for RpcErrorPayload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
pub struct AppServerManager {
launch_config: AppServerLaunchConfig,
inbound_tx: mpsc::UnboundedSender<AppServerInbound>,
inner: Mutex<Option<Arc<RunningAppServer>>>,
}
struct RunningAppServer {
runtime_id: String,
stdin: Arc<Mutex<BufWriter<ChildStdin>>>,
child: Arc<Mutex<Option<Child>>>,
pending: Arc<Mutex<HashMap<String, oneshot::Sender<Result<Value, RpcErrorPayload>>>>>,
next_id: AtomicU64,
alive: Arc<AtomicBool>,
stopping: Arc<AtomicBool>,
}