oxicode_agent/runtime/
dap.rs1use super::DebugService;
17use async_trait::async_trait;
18use parking_lot::Mutex;
19use std::collections::HashMap;
20use std::sync::Arc;
21use std::sync::atomic::{AtomicU64, Ordering};
22use std::time::{Duration, Instant};
23use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
24use tokio::process::{Child, ChildStdin, ChildStdout};
25
26const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
27
28pub struct DapClient {
30 child: Child,
31 stdin: ChildStdin,
32 stdout: BufReader<ChildStdout>,
33 seq: AtomicU64,
34 pub events: Arc<Mutex<Vec<serde_json::Value>>>,
37}
38
39impl std::fmt::Debug for DapClient {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("DapClient")
42 .field("alive", &self.child.id().is_some())
43 .field("events", &self.events.lock().len())
44 .finish()
45 }
46}
47
48impl DapClient {
49 pub fn spawn(adapter: &[String]) -> Result<Self, String> {
51 use tokio::process::Command;
52 let (program, args) = adapter.split_first().ok_or("adapter command is empty")?;
53 let mut cmd = Command::new(program);
54 cmd.args(args)
55 .stdin(std::process::Stdio::piped())
56 .stdout(std::process::Stdio::piped())
57 .stderr(std::process::Stdio::piped())
58 .kill_on_drop(true);
59 let mut child = cmd.spawn().map_err(|e| format!("spawn adapter: {e}"))?;
60 let stdin = child.stdin.take().ok_or("adapter: no stdin")?;
61 let stdout = child.stdout.take().ok_or("adapter: no stdout")?;
62 Ok(Self {
63 child,
64 stdin,
65 stdout: BufReader::new(stdout),
66 seq: AtomicU64::new(0),
67 events: Arc::new(Mutex::new(Vec::new())),
68 })
69 }
70
71 fn next_seq(&self) -> u64 {
72 self.seq.fetch_add(1, Ordering::SeqCst) + 1
73 }
74
75 async fn write_frame(&mut self, value: &serde_json::Value) -> Result<(), String> {
76 let body = serde_json::to_vec(value).map_err(|e| format!("serialize frame: {e}"))?;
77 self.stdin
78 .write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes())
79 .await
80 .map_err(|e| format!("adapter stdin write: {e}"))?;
81 self.stdin
82 .write_all(&body)
83 .await
84 .map_err(|e| format!("adapter stdin write: {e}"))?;
85 self.stdin
86 .flush()
87 .await
88 .map_err(|e| format!("adapter stdin flush: {e}"))
89 }
90
91 async fn read_frame(&mut self, deadline: Instant) -> Result<serde_json::Value, String> {
93 let mut content_length: Option<usize> = None;
94 loop {
95 let mut line = String::new();
96 let read = tokio::time::timeout_at(
97 tokio::time::Instant::from(deadline),
98 self.stdout.read_line(&mut line),
99 )
100 .await
101 .map_err(|_| "timed out reading adapter frame headers".to_string())?
102 .map_err(|e| format!("adapter stdout read: {e}"))?;
103 if read == 0 {
104 return Err("adapter closed the connection".to_string());
105 }
106 let trimmed = line.trim_end();
107 if trimmed.is_empty() {
108 break; }
110 if let Some(v) = trimmed.strip_prefix("Content-Length:") {
111 content_length = Some(
112 v.trim()
113 .parse()
114 .map_err(|_| "bad Content-Length header".to_string())?,
115 );
116 }
117 }
118 let length = content_length.ok_or("adapter frame missing Content-Length")?;
119 let mut body = vec![0u8; length];
120 tokio::time::timeout_at(
121 tokio::time::Instant::from(deadline),
122 self.stdout.read_exact(&mut body),
123 )
124 .await
125 .map_err(|_| "timed out reading adapter frame body".to_string())?
126 .map_err(|e| format!("adapter stdout read: {e}"))?;
127 serde_json::from_slice(&body).map_err(|e| format!("bad adapter frame JSON: {e}"))
128 }
129
130 pub async fn request(
133 &mut self,
134 command: &str,
135 arguments: &serde_json::Value,
136 ) -> Result<serde_json::Value, String> {
137 let deadline = Instant::now() + REQUEST_TIMEOUT;
138 let seq = self.next_seq();
139 let frame = serde_json::json!({
140 "seq": seq,
141 "type": "request",
142 "command": command,
143 "arguments": arguments,
144 });
145 self.write_frame(&frame).await?;
146 loop {
147 if Instant::now() >= deadline {
148 return Err(format!("DAP request '{command}' timed out"));
149 }
150 let msg = self.read_frame(deadline).await?;
151 match msg.get("type").and_then(|t| t.as_str()) {
152 Some("response") => {
153 let req_seq = msg.get("request_seq").and_then(|s| s.as_u64());
154 if req_seq == Some(seq) {
155 let success = msg
156 .get("success")
157 .and_then(|s| s.as_bool())
158 .unwrap_or(false);
159 return if success {
160 Ok(msg.get("body").cloned().unwrap_or(serde_json::Value::Null))
161 } else {
162 Err(format!(
163 "DAP '{command}' failed: {}",
164 msg.get("message")
165 .and_then(|m| m.as_str())
166 .unwrap_or("unknown")
167 ))
168 };
169 }
170 }
172 Some("event") => self.events.lock().push(msg),
173 _ => {}
174 }
175 }
176 }
177
178 pub async fn wait_for_event(
180 &mut self,
181 event: &str,
182 wait: Duration,
183 ) -> Option<serde_json::Value> {
184 let deadline = Instant::now() + wait;
185 loop {
186 {
187 let events = self.events.lock();
188 if let Some(found) = events
189 .iter()
190 .find(|e| e.get("event").and_then(|v| v.as_str()) == Some(event))
191 {
192 return Some(found.clone());
193 }
194 }
195 if Instant::now() >= deadline {
196 return None;
197 }
198 match self.read_frame(deadline).await {
199 Ok(msg) => {
200 if msg.get("type").and_then(|t| t.as_str()) == Some("event") {
201 let is_match = msg.get("event").and_then(|v| v.as_str()) == Some(event);
202 self.events.lock().push(msg);
203 if is_match {
204 return self
205 .events
206 .lock()
207 .iter()
208 .find(|e| e.get("event").and_then(|v| v.as_str()) == Some(event))
209 .cloned();
210 }
211 }
212 }
213 Err(_) => return None,
214 }
215 }
216 }
217
218 async fn shutdown(&mut self) {
219 let _ = self
220 .request(
221 "disconnect",
222 &serde_json::json!({"terminateDebuggee": true}),
223 )
224 .await;
225 let _ = self.child.kill().await;
226 }
227}
228
229#[derive(Debug, Default)]
231pub struct DapDebugService {
232 sessions: Mutex<HashMap<String, DapClient>>,
233}
234
235impl DapDebugService {
236 pub fn new() -> Self {
239 Self::default()
240 }
241}
242
243#[async_trait]
244impl DebugService for DapDebugService {
245 async fn start(&self, config: &serde_json::Value) -> Result<String, String> {
246 let adapter: Vec<String> = config
247 .get("adapter")
248 .and_then(|a| a.as_array())
249 .map(|items| {
250 items
251 .iter()
252 .filter_map(|v| v.as_str().map(String::from))
253 .collect()
254 })
255 .ok_or("config must carry an \"adapter\" command array")?;
256 let request = config
257 .get("request")
258 .and_then(|r| r.as_str())
259 .unwrap_or("launch");
260 let mut launch_args = config.clone();
261 if let Some(obj) = launch_args.as_object_mut() {
262 obj.remove("adapter");
263 obj.insert(
264 "request".to_string(),
265 serde_json::Value::String(request.to_string()),
266 );
267 }
268
269 let mut client = DapClient::spawn(&adapter)?;
270 client
271 .request(
272 "initialize",
273 &serde_json::json!({"adapterID": config.get("type").and_then(|t| t.as_str()).unwrap_or("oxi"), "clientID": "oxicode"}),
274 )
275 .await?;
276 client.request(request, &launch_args).await?;
277 let _ = client
280 .wait_for_event("stopped", Duration::from_secs(10))
281 .await;
282
283 let session = uuid::Uuid::new_v4().to_string();
284 self.sessions.lock().insert(session.clone(), client);
285 Ok(session)
286 }
287
288 async fn request(
289 &self,
290 session: &str,
291 command: &str,
292 args: &serde_json::Value,
293 ) -> Result<serde_json::Value, String> {
294 let mut client = self
297 .sessions
298 .lock()
299 .remove(session)
300 .ok_or("unknown debug session")?;
301 let result = client.request(command, args).await;
302 self.sessions.lock().insert(session.to_string(), client);
303 result
304 }
305
306 async fn terminate(&self, session: &str) -> Result<(), String> {
307 let mut client = self
308 .sessions
309 .lock()
310 .remove(session)
311 .ok_or("unknown debug session")?;
312 let _ = client.request("terminate", &serde_json::json!({})).await;
313 client.shutdown().await;
314 Ok(())
315 }
316}