use std::sync::Arc;
use anyhow::Result;
use serde_json::{Value, json};
use super::handshake::{default_opt_out_notification_methods, parse_initialize_info};
use super::{
APP_SERVER_EXPERIMENTAL_API_ENABLED, AppServerInbound, AppServerManager, RunningAppServer,
};
impl AppServerManager {
pub fn new(
launch_config: super::AppServerLaunchConfig,
inbound_tx: tokio::sync::mpsc::UnboundedSender<AppServerInbound>,
) -> Self {
Self {
launch_config,
inbound_tx,
inner: tokio::sync::Mutex::new(None),
}
}
pub fn runtime_id(&self) -> &str {
&self.launch_config.runtime_id
}
pub async fn start(&self) -> Result<()> {
self.ensure_started().await.map(|_| ())
}
pub async fn stop(&self) -> Result<()> {
let existing = {
let mut guard = self.inner.lock().await;
guard.take()
};
if let Some(existing) = existing {
existing.stop().await?;
for _ in 0..30 {
if !existing.is_alive() {
break;
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
Ok(())
}
pub async fn restart(&self) -> Result<()> {
self.stop().await?;
self.start().await
}
pub async fn request(&self, method: &str, params: Value) -> Result<Value> {
let running = self.ensure_started().await?;
running.request(method, params).await
}
pub async fn respond(&self, id: Value, result: Value) -> Result<()> {
let running = self.ensure_started().await?;
running.respond(id, result).await
}
pub async fn respond_error(&self, id: Value, code: i64, message: &str) -> Result<()> {
let running = self.ensure_started().await?;
running.respond_error(id, code, message).await
}
async fn ensure_started(&self) -> Result<Arc<RunningAppServer>> {
{
let guard = self.inner.lock().await;
if let Some(existing) = guard.as_ref().filter(|existing| existing.is_alive()) {
return Ok(Arc::clone(existing));
}
}
let opt_out_notification_methods = default_opt_out_notification_methods();
let _ = self.inbound_tx.send(AppServerInbound::Starting {
runtime_id: self.launch_config.runtime_id.clone(),
});
let running =
RunningAppServer::spawn(self.launch_config.clone(), self.inbound_tx.clone()).await?;
{
let mut guard = self.inner.lock().await;
*guard = Some(Arc::clone(&running));
}
let _ = self.inbound_tx.send(AppServerInbound::Initializing {
runtime_id: self.launch_config.runtime_id.clone(),
experimental_api_enabled: APP_SERVER_EXPERIMENTAL_API_ENABLED,
opt_out_notification_methods: opt_out_notification_methods.clone(),
});
let init_result = match running
.request(
"initialize",
json!({
"clientInfo": {
"name": "codex-mobile-bridge",
"title": "Codex Mobile Bridge",
"version": env!("CARGO_PKG_VERSION"),
},
"capabilities": {
"experimentalApi": APP_SERVER_EXPERIMENTAL_API_ENABLED,
"optOutNotificationMethods": opt_out_notification_methods,
}
}),
)
.await
{
Ok(result) => result,
Err(error) => {
self.abort_startup(
&running,
APP_SERVER_EXPERIMENTAL_API_ENABLED,
&default_opt_out_notification_methods(),
format!("initialize 失败: {error}"),
)
.await;
return Err(error);
}
};
let info = match parse_initialize_info(&init_result) {
Ok(info) => info,
Err(error) => {
self.abort_startup(
&running,
APP_SERVER_EXPERIMENTAL_API_ENABLED,
&default_opt_out_notification_methods(),
format!("initialize 响应解析失败: {error}"),
)
.await;
return Err(error);
}
};
if let Err(error) = running.notify_initialized().await {
self.abort_startup(
&running,
APP_SERVER_EXPERIMENTAL_API_ENABLED,
&default_opt_out_notification_methods(),
format!("initialized 发送失败: {error}"),
)
.await;
return Err(error);
}
let _ = self.inbound_tx.send(AppServerInbound::Initialized {
runtime_id: self.launch_config.runtime_id.clone(),
info,
experimental_api_enabled: APP_SERVER_EXPERIMENTAL_API_ENABLED,
opt_out_notification_methods: default_opt_out_notification_methods(),
});
Ok(running)
}
async fn abort_startup(
&self,
running: &Arc<RunningAppServer>,
experimental_api_enabled: bool,
opt_out_notification_methods: &[String],
message: String,
) {
let _ = self.inbound_tx.send(AppServerInbound::HandshakeFailed {
runtime_id: self.launch_config.runtime_id.clone(),
message,
experimental_api_enabled,
opt_out_notification_methods: opt_out_notification_methods.to_vec(),
});
let _ = running.abort().await;
let mut guard = self.inner.lock().await;
if guard
.as_ref()
.map(|existing| Arc::ptr_eq(existing, running))
.unwrap_or(false)
{
*guard = None;
}
}
}