1use tokio::{io::AsyncWriteExt, process::Command};
2
3use crate::{
4 process::spawn_with_retry, CodexClient, CodexError, ResponsesApiProxyHandle,
5 ResponsesApiProxyRequest,
6};
7
8impl CodexClient {
9 pub async fn start_responses_api_proxy(
15 &self,
16 request: ResponsesApiProxyRequest,
17 ) -> Result<ResponsesApiProxyHandle, CodexError> {
18 let ResponsesApiProxyRequest {
19 api_key,
20 port,
21 server_info_path,
22 http_shutdown,
23 upstream_url,
24 } = request;
25
26 let api_key = api_key.trim().to_string();
27 if api_key.is_empty() {
28 return Err(CodexError::EmptyApiKey);
29 }
30
31 let working_dir = self.sandbox_working_dir(None)?;
32
33 let mut command = Command::new(self.command_env.binary_path());
34 command
35 .arg("responses-api-proxy")
36 .stdin(std::process::Stdio::piped())
37 .stdout(std::process::Stdio::piped())
38 .stderr(std::process::Stdio::piped())
39 .kill_on_drop(true)
40 .current_dir(&working_dir);
41
42 if let Some(port) = port {
43 command.arg("--port").arg(port.to_string());
44 }
45
46 if let Some(path) = server_info_path.as_ref() {
47 command.arg("--server-info").arg(path);
48 }
49
50 if http_shutdown {
51 command.arg("--http-shutdown");
52 }
53
54 if let Some(url) = upstream_url.as_ref() {
55 if !url.trim().is_empty() {
56 command.arg("--upstream-url").arg(url);
57 }
58 }
59
60 self.command_env.apply(&mut command)?;
61
62 let mut child = spawn_with_retry(&mut command, self.command_env.binary_path())?;
63
64 let mut stdin = child.stdin.take().ok_or(CodexError::StdinUnavailable)?;
65 stdin
66 .write_all(api_key.as_bytes())
67 .await
68 .map_err(CodexError::StdinWrite)?;
69 stdin
70 .write_all(b"\n")
71 .await
72 .map_err(CodexError::StdinWrite)?;
73 stdin.shutdown().await.map_err(CodexError::StdinWrite)?;
74
75 Ok(ResponsesApiProxyHandle {
76 child,
77 server_info_path,
78 })
79 }
80}