monoloop_connector_codex/
session.rs1use crate::config::{CodexAgentConfig, CodexSessionConfig};
4use crate::error::CodexConnectorError;
5use crate::process::ProcessInner;
6use crate::raw_dump::CodexRawDump;
7use monoloop_contracts::{DialectBinding, DialectDescriptor, ExternalSessionId};
8use std::sync::Arc;
9use tokio::sync::mpsc;
10
11pub struct CodexAgentHandle {
13 inner: Arc<ProcessInner>,
14 updates: Option<mpsc::Receiver<bytes::Bytes>>,
15 dump: Arc<CodexRawDump>,
16}
17
18impl CodexAgentHandle {
19 pub async fn connect(config: CodexAgentConfig) -> Result<Self, CodexConnectorError> {
21 let dump = Arc::new(CodexRawDump::new(config.raw_dump_path.clone(), 10_000));
22 let (update_tx, updates) = mpsc::channel(config.max_output_queue);
23 let inner = ProcessInner::spawn(config.clone(), update_tx, Arc::clone(&dump)).await?;
24
25 let fs = if config.advertise_fs {
26 serde_json::json!({ "readTextFile": true, "writeTextFile": true })
27 } else {
28 serde_json::json!({ "readTextFile": false, "writeTextFile": false })
29 };
30
31 inner
32 .request(
33 "initialize",
34 serde_json::json!({
35 "protocolVersion": 1,
36 "clientCapabilities": {
37 "fs": fs,
38 "terminal": false
39 },
40 "clientInfo": {
41 "name": config.client_name,
42 "version": config.client_version
43 }
44 }),
45 )
46 .await?;
47
48 if config.authenticate {
49 let _ = inner
50 .request(
51 "authenticate",
52 serde_json::json!({ "methodId": config.auth_method_id }),
53 )
54 .await?;
55 }
56
57 Ok(Self {
58 inner,
59 updates: Some(updates),
60 dump,
61 })
62 }
63
64 pub fn take_updates(&mut self) -> mpsc::Receiver<bytes::Bytes> {
66 self.updates
67 .take()
68 .expect("CodexAgentHandle updates already taken")
69 }
70
71 pub async fn session_new(
73 &self,
74 config: CodexSessionConfig,
75 ) -> Result<CodexSession, CodexConnectorError> {
76 let result = self
77 .inner
78 .request(
79 "session/new",
80 serde_json::json!({
81 "cwd": config.cwd.to_string_lossy(),
82 "mcpServers": config.mcp_servers,
83 }),
84 )
85 .await?;
86 let session_id = result
87 .get("sessionId")
88 .and_then(|s| s.as_str())
89 .ok_or_else(|| CodexConnectorError::session("session/new missing sessionId"))?
90 .to_string();
91 let session = CodexSession {
92 session_id,
93 inner: Arc::clone(&self.inner),
94 };
95 if let Some(mode) = &config.mode_id {
96 let _ = session.set_mode(mode).await;
98 }
99 Ok(session)
100 }
101
102 pub async fn session_load(
104 &self,
105 session_id: impl Into<String>,
106 cwd: impl AsRef<std::path::Path>,
107 ) -> Result<CodexSession, CodexConnectorError> {
108 let session_id = session_id.into();
109 let result = self
110 .inner
111 .request(
112 "session/load",
113 serde_json::json!({
114 "sessionId": session_id,
115 "cwd": cwd.as_ref().to_string_lossy(),
116 "mcpServers": [],
117 }),
118 )
119 .await?;
120 let sid = result
121 .get("sessionId")
122 .and_then(|s| s.as_str())
123 .unwrap_or(&session_id)
124 .to_string();
125 Ok(CodexSession {
126 session_id: sid,
127 inner: Arc::clone(&self.inner),
128 })
129 }
130
131 pub fn dialect(&self) -> DialectBinding {
133 DialectBinding::negotiated(DialectDescriptor::codex_acp("1"))
134 }
135
136 pub fn raw_dump_text(&self) -> String {
138 self.dump.as_text()
139 }
140
141 pub fn raw_dump(&self) -> Arc<CodexRawDump> {
143 Arc::clone(&self.dump)
144 }
145
146 pub async fn shutdown(self) {
148 self.inner.shutdown().await;
149 }
150}
151
152pub struct CodexSession {
154 pub session_id: String,
156 inner: Arc<ProcessInner>,
157}
158
159impl CodexSession {
160 pub fn external_session_id(&self) -> ExternalSessionId {
162 ExternalSessionId::new(self.session_id.clone())
163 }
164
165 pub async fn set_mode(&self, mode_id: impl AsRef<str>) -> Result<(), CodexConnectorError> {
167 self.inner
168 .request(
169 "session/set_mode",
170 serde_json::json!({
171 "sessionId": self.session_id,
172 "modeId": mode_id.as_ref(),
173 }),
174 )
175 .await?;
176 Ok(())
177 }
178
179 pub async fn prompt_text(
181 &self,
182 text: impl Into<String>,
183 ) -> Result<serde_json::Value, CodexConnectorError> {
184 let text = text.into();
185 self.inner
186 .request(
187 "session/prompt",
188 serde_json::json!({
189 "sessionId": self.session_id,
190 "prompt": [{ "type": "text", "text": text }]
191 }),
192 )
193 .await
194 }
195
196 pub async fn cancel(&self) -> Result<(), CodexConnectorError> {
198 let _ = self
199 .inner
200 .request(
201 "session/cancel",
202 serde_json::json!({ "sessionId": self.session_id }),
203 )
204 .await;
205 Ok(())
206 }
207}