app
Codex
Public Made by Adomby adom
Codex in Adom Hydrogen: ecosystem setup, dock dashboard and live engineering demos.
main
John Lauer
Add Rust Codex Desktop bridge with native app tools, auth reuse and plugin lifecycle
7aae933
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
#[cfg(any(windows, test))]
use serde_json::json;
use serde_json::Value;
#[cfg(any(windows, test))]
const MAX_FRAME: usize = 8 * 1024 * 1024;
#[cfg(any(windows, test))]
fn frame(value: &Value) -> Result<Vec<u8>, String> {
let body = serde_json::to_vec(value).map_err(|e| e.to_string())?;
if body.len() > MAX_FRAME {
return Err("app_tools_frame_too_large".into());
}
let mut frame = (body.len() as u32).to_le_bytes().to_vec();
frame.extend(body);
Ok(frame)
}
#[cfg(not(windows))]
pub fn request(_method: &str, _params: Value, _endpoint: Option<&str>) -> Result<Value, String> {
Err("desktop_tools_require_windows".into())
}
#[cfg(windows)]
pub fn request(method: &str, params: Value, endpoint: Option<&str>) -> Result<Value, String> {
use std::{
fs::OpenOptions,
io::{Read, Write},
os::windows::io::AsRawHandle,
time::{Duration, Instant},
};
use windows_sys::Win32::System::Pipes::{GetNamedPipeServerProcessId, PeekNamedPipe};
let inv = crate::desktop::inventory()?;
let owners: Vec<u32> = inv["windows"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|w| w["pid"].as_u64().map(|p| p as u32))
.collect();
if owners.is_empty() {
return Err("desktop_window_not_running".into());
}
let listing = crate::process::powershell(
r#"@(Get-ChildItem \\.\pipe\ | Where-Object {$_.Name -like 'codex-browser-use-*'} | ForEach-Object {$_.Name}) | ConvertTo-Json -Compress"#,
Duration::from_secs(10),
)?;
let value: Value = serde_json::from_str(listing["stdout"].as_str().unwrap_or("").trim())
.map_err(|_| "desktop_tools_pipe_not_found")?;
let names = if value.is_array() {
value.as_array().unwrap().clone()
} else {
vec![value]
};
let mut verified = Vec::new();
for name in names {
let Some(name) = name.as_str() else {
continue;
};
if !name.starts_with("codex-browser-use-")
|| name.contains(['\\', '/'])
|| endpoint.is_some_and(|e| e != name)
{
continue;
}
let path = format!(r"\\.\pipe\{name}");
if let Ok(pipe) = OpenOptions::new().read(true).write(true).open(&path) {
let mut pid = 0;
if unsafe { GetNamedPipeServerProcessId(pipe.as_raw_handle(), &mut pid) } != 0
&& owners.contains(&pid)
{
verified.push((pipe, pid, name.to_string()));
}
}
}
if verified.len() != 1 {
return Err(format!(
"desktop_tools_pipe_ambiguous_or_missing: specify desktopPipe from verified endpoints {:?}",
verified.iter().map(|(_,pid,name)|(pid,name)).collect::<Vec<_>>()
));
}
let (mut pipe, pid, endpoint) = verified.pop().unwrap();
let id = uuid::Uuid::new_v4().to_string();
pipe.write_all(&frame(
&json!({"jsonrpc":"2.0","id":id,"method":method,"params":params}),
)?)
.map_err(|_| "desktop_tools_write_failed")?;
let deadline = Instant::now() + Duration::from_secs(120);
fn read_until(
pipe: &mut std::fs::File,
buf: &mut [u8],
deadline: Instant,
) -> Result<(), String> {
let mut offset = 0;
while offset < buf.len() {
let mut available = 0;
if unsafe {
PeekNamedPipe(
pipe.as_raw_handle(),
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
&mut available,
std::ptr::null_mut(),
)
} == 0
{
return Err("desktop_tools_disconnected".into());
}
if available == 0 {
if Instant::now() >= deadline {
return Err("desktop_tools_timeout: native request may still need a user response; inspect desktop state".into());
}
std::thread::sleep(Duration::from_millis(20));
continue;
}
let end = buf.len().min(offset + available as usize);
let n = pipe
.read(&mut buf[offset..end])
.map_err(|_| "desktop_tools_read_failed")?;
if n == 0 {
return Err("desktop_tools_disconnected".into());
}
offset += n;
}
Ok(())
}
let mut size = [0; 4];
read_until(&mut pipe, &mut size, deadline)?;
let size = u32::from_le_bytes(size) as usize;
if size > MAX_FRAME {
return Err("desktop_tools_frame_too_large".into());
}
let mut body = vec![0; size];
read_until(&mut pipe, &mut body, deadline)?;
let response: Value =
serde_json::from_slice(&body).map_err(|_| "desktop_tools_invalid_response")?;
if response["id"] != id {
return Err("desktop_tools_response_id_mismatch".into());
}
if let Some(error) = response.get("error") {
return Err(format!("desktop_tools_error: {error}"));
}
Ok(
json!({"result":response["result"],"desktopPid":pid,"desktopPipe":endpoint,"transport":"desktop-native-pipe"}),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn framing_matches_native_little_endian_protocol() {
let v = json!({"method":"tools/list","params":{"threadStartKind":"all"}});
let f = frame(&v).unwrap();
assert_eq!(
u32::from_le_bytes(f[..4].try_into().unwrap()) as usize,
f.len() - 4
);
assert_eq!(serde_json::from_slice::<Value>(&f[4..]).unwrap(), v);
}
}