debugger/dap/
types.rs

1//! DAP message types
2//!
3//! These types represent the Debug Adapter Protocol messages.
4//! See: https://microsoft.github.io/debug-adapter-protocol/specification
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9// === Base Protocol Messages ===
10
11/// Base message type for DAP protocol
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "type", rename_all = "lowercase")]
14pub enum ProtocolMessage {
15    Request(RequestMessage),
16    Response(ResponseMessage),
17    Event(EventMessage),
18}
19
20/// DAP request message
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct RequestMessage {
23    pub seq: i64,
24    #[serde(rename = "type")]
25    pub message_type: String,
26    pub command: String,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub arguments: Option<Value>,
29}
30
31/// DAP response message
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ResponseMessage {
34    pub seq: i64,
35    #[serde(rename = "type")]
36    pub message_type: String,
37    pub request_seq: i64,
38    pub success: bool,
39    pub command: String,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub message: Option<String>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub body: Option<Value>,
44}
45
46/// DAP event message
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct EventMessage {
49    pub seq: i64,
50    #[serde(rename = "type")]
51    pub message_type: String,
52    pub event: String,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub body: Option<Value>,
55}
56
57// === Request Arguments ===
58
59/// Initialize request arguments
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct InitializeArguments {
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub client_id: Option<String>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub client_name: Option<String>,
67    #[serde(rename = "adapterID")]
68    pub adapter_id: String,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub locale: Option<String>,
71    #[serde(default = "default_true")]
72    pub lines_start_at1: bool,
73    #[serde(default = "default_true")]
74    pub columns_start_at1: bool,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub path_format: Option<String>,
77    #[serde(default)]
78    pub supports_variable_type: bool,
79    #[serde(default)]
80    pub supports_variable_paging: bool,
81    #[serde(default)]
82    pub supports_run_in_terminal_request: bool,
83    #[serde(default)]
84    pub supports_memory_references: bool,
85    #[serde(default)]
86    pub supports_progress_reporting: bool,
87}
88
89fn default_true() -> bool {
90    true
91}
92
93impl Default for InitializeArguments {
94    fn default() -> Self {
95        Self {
96            client_id: Some("debugger-cli".to_string()),
97            client_name: Some("LLM Debugger CLI".to_string()),
98            adapter_id: "lldb-dap".to_string(),
99            locale: None,
100            lines_start_at1: true,
101            columns_start_at1: true,
102            path_format: Some("path".to_string()),
103            supports_variable_type: true,
104            supports_variable_paging: true,
105            supports_run_in_terminal_request: false,
106            supports_memory_references: true,
107            supports_progress_reporting: false,
108        }
109    }
110}
111
112/// Launch request arguments
113///
114/// This structure contains fields for multiple DAP adapters.
115/// Unused fields are skipped during serialization.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct LaunchArguments {
119    pub program: String,
120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
121    pub args: Vec<String>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub cwd: Option<String>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub env: Option<std::collections::HashMap<String, String>>,
126    #[serde(default)]
127    pub stop_on_entry: bool,
128    
129    // === lldb-dap specific ===
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub init_commands: Option<Vec<String>>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub pre_run_commands: Option<Vec<String>>,
134    
135    // === debugpy (Python) specific ===
136    /// Request type: "launch" or "attach" (required by debugpy)
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub request: Option<String>,
139    /// Console type: "integratedTerminal", "internalConsole", or "externalTerminal"
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub console: Option<String>,
142    /// Python executable path (debugpy)
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub python: Option<String>,
145    /// Only debug user code, skip library frames (debugpy)
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub just_my_code: Option<bool>,
148}
149
150/// Attach request arguments
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct AttachArguments {
154    pub pid: u32,
155    // lldb-dap specific
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub wait_for: Option<bool>,
158}
159
160/// SetBreakpoints request arguments
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct SetBreakpointsArguments {
164    pub source: Source,
165    #[serde(default, skip_serializing_if = "Vec::is_empty")]
166    pub breakpoints: Vec<SourceBreakpoint>,
167}
168
169/// SetFunctionBreakpoints request arguments
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct SetFunctionBreakpointsArguments {
173    pub breakpoints: Vec<FunctionBreakpoint>,
174}
175
176/// Continue request arguments
177#[derive(Debug, Clone, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct ContinueArguments {
180    pub thread_id: i64,
181    #[serde(default)]
182    pub single_thread: bool,
183}
184
185/// Step request arguments (next, stepIn, stepOut)
186#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct StepArguments {
189    pub thread_id: i64,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub granularity: Option<String>,
192}
193
194/// Pause request arguments
195#[derive(Debug, Clone, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct PauseArguments {
198    pub thread_id: i64,
199}
200
201/// StackTrace request arguments
202#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct StackTraceArguments {
205    pub thread_id: i64,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub start_frame: Option<i64>,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub levels: Option<i64>,
210}
211
212/// Scopes request arguments
213#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(rename_all = "camelCase")]
215pub struct ScopesArguments {
216    pub frame_id: i64,
217}
218
219/// Variables request arguments
220#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct VariablesArguments {
223    pub variables_reference: i64,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub start: Option<i64>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub count: Option<i64>,
228}
229
230/// Evaluate request arguments
231#[derive(Debug, Clone, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct EvaluateArguments {
234    pub expression: String,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub frame_id: Option<i64>,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub context: Option<String>,
239}
240
241/// Disconnect request arguments
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct DisconnectArguments {
245    #[serde(default)]
246    pub restart: bool,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub terminate_debuggee: Option<bool>,
249}
250
251// === Response Bodies ===
252
253/// Capabilities returned by initialize response
254#[derive(Debug, Clone, Default, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct Capabilities {
257    #[serde(default)]
258    pub supports_configuration_done_request: bool,
259    #[serde(default)]
260    pub supports_function_breakpoints: bool,
261    #[serde(default)]
262    pub supports_conditional_breakpoints: bool,
263    #[serde(default)]
264    pub supports_hit_conditional_breakpoints: bool,
265    #[serde(default)]
266    pub supports_evaluate_for_hovers: bool,
267    #[serde(default)]
268    pub supports_step_back: bool,
269    #[serde(default)]
270    pub supports_set_variable: bool,
271    #[serde(default)]
272    pub supports_restart_frame: bool,
273    #[serde(default)]
274    pub supports_restart_request: bool,
275    #[serde(default)]
276    pub supports_goto_targets_request: bool,
277    #[serde(default)]
278    pub supports_step_in_targets_request: bool,
279    #[serde(default)]
280    pub supports_completions_request: bool,
281    #[serde(default)]
282    pub supports_modules_request: bool,
283    #[serde(default)]
284    pub supports_data_breakpoints: bool,
285    #[serde(default)]
286    pub supports_read_memory_request: bool,
287    #[serde(default)]
288    pub supports_disassemble_request: bool,
289    #[serde(default)]
290    pub supports_terminate_request: bool,
291}
292
293/// SetBreakpoints response body
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct SetBreakpointsResponseBody {
296    pub breakpoints: Vec<Breakpoint>,
297}
298
299/// StackTrace response body
300#[derive(Debug, Clone, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct StackTraceResponseBody {
303    pub stack_frames: Vec<StackFrame>,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub total_frames: Option<i64>,
306}
307
308/// Threads response body
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct ThreadsResponseBody {
311    pub threads: Vec<Thread>,
312}
313
314/// Scopes response body
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct ScopesResponseBody {
317    pub scopes: Vec<Scope>,
318}
319
320/// Variables response body
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct VariablesResponseBody {
323    pub variables: Vec<Variable>,
324}
325
326/// Evaluate response body
327#[derive(Debug, Clone, Serialize, Deserialize)]
328#[serde(rename_all = "camelCase")]
329pub struct EvaluateResponseBody {
330    pub result: String,
331    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
332    pub type_name: Option<String>,
333    #[serde(default)]
334    pub variables_reference: i64,
335}
336
337/// Continue response body
338#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct ContinueResponseBody {
341    #[serde(default = "default_true")]
342    pub all_threads_continued: bool,
343}
344
345// === Common Types ===
346
347/// Source location
348#[derive(Debug, Clone, Default, Serialize, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct Source {
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub name: Option<String>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub path: Option<String>,
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub source_reference: Option<i64>,
357}
358
359/// Breakpoint to set at a source location
360#[derive(Debug, Clone, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct SourceBreakpoint {
363    pub line: u32,
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub column: Option<u32>,
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub condition: Option<String>,
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub hit_condition: Option<String>,
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub log_message: Option<String>,
372}
373
374/// Function breakpoint
375#[derive(Debug, Clone, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct FunctionBreakpoint {
378    pub name: String,
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub condition: Option<String>,
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub hit_condition: Option<String>,
383}
384
385/// Breakpoint information
386#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct Breakpoint {
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub id: Option<u32>,
391    pub verified: bool,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub message: Option<String>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub source: Option<Source>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub line: Option<u32>,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub column: Option<u32>,
400}
401
402/// Stack frame
403#[derive(Debug, Clone, Serialize, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct StackFrame {
406    pub id: i64,
407    pub name: String,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub source: Option<Source>,
410    pub line: u32,
411    pub column: u32,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub module_id: Option<Value>,
414}
415
416/// Thread
417#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct Thread {
419    pub id: i64,
420    pub name: String,
421}
422
423/// Scope
424#[derive(Debug, Clone, Serialize, Deserialize)]
425#[serde(rename_all = "camelCase")]
426pub struct Scope {
427    pub name: String,
428    pub variables_reference: i64,
429    #[serde(default)]
430    pub expensive: bool,
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub source: Option<Source>,
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub line: Option<u32>,
435}
436
437/// Variable
438#[derive(Debug, Clone, Serialize, Deserialize)]
439#[serde(rename_all = "camelCase")]
440pub struct Variable {
441    pub name: String,
442    pub value: String,
443    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
444    pub type_name: Option<String>,
445    #[serde(default)]
446    pub variables_reference: i64,
447}
448
449// === Event Bodies ===
450
451/// Stopped event body
452#[derive(Debug, Clone, Serialize, Deserialize)]
453#[serde(rename_all = "camelCase")]
454pub struct StoppedEventBody {
455    pub reason: String,
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub description: Option<String>,
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub thread_id: Option<i64>,
460    #[serde(default)]
461    pub all_threads_stopped: bool,
462    #[serde(default, skip_serializing_if = "Vec::is_empty")]
463    pub hit_breakpoint_ids: Vec<u32>,
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub text: Option<String>,
466}
467
468/// Output event body
469#[derive(Debug, Clone, Serialize, Deserialize)]
470pub struct OutputEventBody {
471    pub category: Option<String>,
472    pub output: String,
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub source: Option<Source>,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub line: Option<u32>,
477}
478
479/// Thread event body
480#[derive(Debug, Clone, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct ThreadEventBody {
483    pub reason: String,
484    pub thread_id: i64,
485}
486
487/// Exited event body
488#[derive(Debug, Clone, Serialize, Deserialize)]
489#[serde(rename_all = "camelCase")]
490pub struct ExitedEventBody {
491    pub exit_code: i32,
492}
493
494/// Terminated event body
495#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct TerminatedEventBody {
497    #[serde(default)]
498    pub restart: bool,
499}
500
501// === Parsed Events ===
502
503/// Parsed DAP event
504#[derive(Debug, Clone)]
505pub enum Event {
506    Initialized,
507    Stopped(StoppedEventBody),
508    Continued { thread_id: i64, all_threads_continued: bool },
509    Exited(ExitedEventBody),
510    Terminated(Option<TerminatedEventBody>),
511    Thread(ThreadEventBody),
512    Output(OutputEventBody),
513    Breakpoint { reason: String, breakpoint: Breakpoint },
514    Unknown { event: String, body: Option<Value> },
515}
516
517impl Event {
518    /// Parse an event from an EventMessage
519    pub fn from_message(msg: &EventMessage) -> Self {
520        match msg.event.as_str() {
521            "initialized" => Event::Initialized,
522            "stopped" => {
523                if let Some(body) = &msg.body {
524                    if let Ok(stopped) = serde_json::from_value(body.clone()) {
525                        return Event::Stopped(stopped);
526                    }
527                }
528                Event::Unknown {
529                    event: msg.event.clone(),
530                    body: msg.body.clone(),
531                }
532            }
533            "continued" => {
534                let thread_id = msg.body.as_ref()
535                    .and_then(|b| b.get("threadId"))
536                    .and_then(|v| v.as_i64())
537                    .unwrap_or(0);
538                let all_threads_continued = msg.body.as_ref()
539                    .and_then(|b| b.get("allThreadsContinued"))
540                    .and_then(|v| v.as_bool())
541                    .unwrap_or(true);
542                Event::Continued { thread_id, all_threads_continued }
543            }
544            "exited" => {
545                if let Some(body) = &msg.body {
546                    if let Ok(exited) = serde_json::from_value(body.clone()) {
547                        return Event::Exited(exited);
548                    }
549                }
550                Event::Exited(ExitedEventBody { exit_code: 0 })
551            }
552            "terminated" => {
553                let body = msg.body.as_ref()
554                    .and_then(|b| serde_json::from_value(b.clone()).ok());
555                Event::Terminated(body)
556            }
557            "thread" => {
558                if let Some(body) = &msg.body {
559                    if let Ok(thread) = serde_json::from_value(body.clone()) {
560                        return Event::Thread(thread);
561                    }
562                }
563                Event::Unknown {
564                    event: msg.event.clone(),
565                    body: msg.body.clone(),
566                }
567            }
568            "output" => {
569                if let Some(body) = &msg.body {
570                    if let Ok(output) = serde_json::from_value(body.clone()) {
571                        return Event::Output(output);
572                    }
573                }
574                Event::Unknown {
575                    event: msg.event.clone(),
576                    body: msg.body.clone(),
577                }
578            }
579            "breakpoint" => {
580                if let Some(body) = &msg.body {
581                    let reason = body.get("reason")
582                        .and_then(|v| v.as_str())
583                        .unwrap_or("unknown")
584                        .to_string();
585                    if let Some(bp) = body.get("breakpoint") {
586                        if let Ok(breakpoint) = serde_json::from_value(bp.clone()) {
587                            return Event::Breakpoint { reason, breakpoint };
588                        }
589                    }
590                }
591                Event::Unknown {
592                    event: msg.event.clone(),
593                    body: msg.body.clone(),
594                }
595            }
596            _ => Event::Unknown {
597                event: msg.event.clone(),
598                body: msg.body.clone(),
599            },
600        }
601    }
602}