Skip to main content

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    // === Delve (Go) specific ===
150    /// Launch mode: "exec" (precompiled), "debug" (build and run), "test", "replay", "core"
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub mode: Option<String>,
153    /// Stop at entry point (Delve uses stopAtEntry instead of stopOnEntry)
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub stop_at_entry: Option<bool>,
156
157    // === GDB-based adapters (GDB, CUDA-GDB) ===
158    /// Stop at beginning of main (GDB uses stopAtBeginningOfMainSubprogram instead of stopOnEntry)
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub stop_at_beginning_of_main_subprogram: Option<bool>,
161
162    // === js-debug (JavaScript/TypeScript) specific ===
163    /// Debugger type for js-debug (e.g., "pwa-node" for Node.js, "pwa-chrome" for Chrome)
164    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
165    pub type_attr: Option<String>,
166    /// Enable source maps for TypeScript debugging
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub source_maps: Option<bool>,
169    /// Glob patterns for output files (compiled JavaScript)
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub out_files: Option<Vec<String>>,
172    /// Node.js runtime executable path
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub runtime_executable: Option<String>,
175    /// Additional runtime arguments
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub runtime_args: Option<Vec<String>>,
178    /// Patterns for files to skip during debugging
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub skip_files: Option<Vec<String>>,
181}
182
183/// Attach request arguments
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct AttachArguments {
187    pub pid: u32,
188    // lldb-dap specific
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub wait_for: Option<bool>,
191}
192
193/// SetBreakpoints request arguments
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(rename_all = "camelCase")]
196pub struct SetBreakpointsArguments {
197    pub source: Source,
198    #[serde(default, skip_serializing_if = "Vec::is_empty")]
199    pub breakpoints: Vec<SourceBreakpoint>,
200}
201
202/// SetFunctionBreakpoints request arguments
203#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct SetFunctionBreakpointsArguments {
206    pub breakpoints: Vec<FunctionBreakpoint>,
207}
208
209/// Continue request arguments
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct ContinueArguments {
213    pub thread_id: i64,
214    #[serde(default)]
215    pub single_thread: bool,
216}
217
218/// Step request arguments (next, stepIn, stepOut)
219#[derive(Debug, Clone, Serialize, Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct StepArguments {
222    pub thread_id: i64,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub granularity: Option<String>,
225}
226
227/// Pause request arguments
228#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct PauseArguments {
231    pub thread_id: i64,
232}
233
234/// StackTrace request arguments
235#[derive(Debug, Clone, Serialize, Deserialize)]
236#[serde(rename_all = "camelCase")]
237pub struct StackTraceArguments {
238    pub thread_id: i64,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub start_frame: Option<i64>,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub levels: Option<i64>,
243}
244
245/// Scopes request arguments
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct ScopesArguments {
249    pub frame_id: i64,
250}
251
252/// Variables request arguments
253#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct VariablesArguments {
256    pub variables_reference: i64,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub start: Option<i64>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub count: Option<i64>,
261}
262
263/// Evaluate request arguments
264#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct EvaluateArguments {
267    pub expression: String,
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub frame_id: Option<i64>,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub context: Option<String>,
272}
273
274/// Disconnect request arguments
275#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct DisconnectArguments {
278    #[serde(default)]
279    pub restart: bool,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub terminate_debuggee: Option<bool>,
282}
283
284// === Response Bodies ===
285
286/// Capabilities returned by initialize response
287#[derive(Debug, Clone, Default, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct Capabilities {
290    #[serde(default)]
291    pub supports_configuration_done_request: bool,
292    #[serde(default)]
293    pub supports_function_breakpoints: bool,
294    #[serde(default)]
295    pub supports_conditional_breakpoints: bool,
296    #[serde(default)]
297    pub supports_hit_conditional_breakpoints: bool,
298    #[serde(default)]
299    pub supports_evaluate_for_hovers: bool,
300    #[serde(default)]
301    pub supports_step_back: bool,
302    #[serde(default)]
303    pub supports_set_variable: bool,
304    #[serde(default)]
305    pub supports_restart_frame: bool,
306    #[serde(default)]
307    pub supports_restart_request: bool,
308    #[serde(default)]
309    pub supports_goto_targets_request: bool,
310    #[serde(default)]
311    pub supports_step_in_targets_request: bool,
312    #[serde(default)]
313    pub supports_completions_request: bool,
314    #[serde(default)]
315    pub supports_modules_request: bool,
316    #[serde(default)]
317    pub supports_data_breakpoints: bool,
318    #[serde(default)]
319    pub supports_read_memory_request: bool,
320    #[serde(default)]
321    pub supports_disassemble_request: bool,
322    #[serde(default)]
323    pub supports_terminate_request: bool,
324}
325
326/// SetBreakpoints response body
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct SetBreakpointsResponseBody {
329    pub breakpoints: Vec<Breakpoint>,
330}
331
332/// StackTrace response body
333#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct StackTraceResponseBody {
336    pub stack_frames: Vec<StackFrame>,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub total_frames: Option<i64>,
339}
340
341/// Threads response body
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ThreadsResponseBody {
344    pub threads: Vec<Thread>,
345}
346
347/// Scopes response body
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct ScopesResponseBody {
350    pub scopes: Vec<Scope>,
351}
352
353/// Variables response body
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct VariablesResponseBody {
356    pub variables: Vec<Variable>,
357}
358
359/// Evaluate response body
360#[derive(Debug, Clone, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct EvaluateResponseBody {
363    pub result: String,
364    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
365    pub type_name: Option<String>,
366    #[serde(default)]
367    pub variables_reference: i64,
368}
369
370/// Continue response body
371#[derive(Debug, Clone, Serialize, Deserialize)]
372#[serde(rename_all = "camelCase")]
373pub struct ContinueResponseBody {
374    #[serde(default = "default_true")]
375    pub all_threads_continued: bool,
376}
377
378// === Common Types ===
379
380/// Source location
381#[derive(Debug, Clone, Default, Serialize, Deserialize)]
382#[serde(rename_all = "camelCase")]
383pub struct Source {
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub name: Option<String>,
386    #[serde(skip_serializing_if = "Option::is_none")]
387    pub path: Option<String>,
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub source_reference: Option<i64>,
390}
391
392/// Breakpoint to set at a source location
393#[derive(Debug, Clone, Serialize, Deserialize)]
394#[serde(rename_all = "camelCase")]
395pub struct SourceBreakpoint {
396    pub line: u32,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub column: Option<u32>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub condition: Option<String>,
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub hit_condition: Option<String>,
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub log_message: Option<String>,
405}
406
407/// Function breakpoint
408#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct FunctionBreakpoint {
411    pub name: String,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub condition: Option<String>,
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub hit_condition: Option<String>,
416}
417
418/// Breakpoint information
419#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct Breakpoint {
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub id: Option<u32>,
424    pub verified: bool,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub message: Option<String>,
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub source: Option<Source>,
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub line: Option<u32>,
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub column: Option<u32>,
433}
434
435/// Stack frame
436#[derive(Debug, Clone, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct StackFrame {
439    pub id: i64,
440    pub name: String,
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub source: Option<Source>,
443    pub line: u32,
444    pub column: u32,
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub module_id: Option<Value>,
447}
448
449/// Thread
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct Thread {
452    pub id: i64,
453    pub name: String,
454}
455
456/// Scope
457#[derive(Debug, Clone, Serialize, Deserialize)]
458#[serde(rename_all = "camelCase")]
459pub struct Scope {
460    pub name: String,
461    pub variables_reference: i64,
462    #[serde(default)]
463    pub expensive: bool,
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub source: Option<Source>,
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub line: Option<u32>,
468}
469
470/// Variable
471#[derive(Debug, Clone, Serialize, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct Variable {
474    pub name: String,
475    pub value: String,
476    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
477    pub type_name: Option<String>,
478    #[serde(default)]
479    pub variables_reference: i64,
480}
481
482// === Event Bodies ===
483
484/// Stopped event body
485#[derive(Debug, Clone, Serialize, Deserialize)]
486#[serde(rename_all = "camelCase")]
487pub struct StoppedEventBody {
488    pub reason: String,
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub description: Option<String>,
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub thread_id: Option<i64>,
493    #[serde(default)]
494    pub all_threads_stopped: bool,
495    #[serde(default, skip_serializing_if = "Vec::is_empty")]
496    pub hit_breakpoint_ids: Vec<u32>,
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub text: Option<String>,
499}
500
501/// Output event body
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct OutputEventBody {
504    pub category: Option<String>,
505    pub output: String,
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub source: Option<Source>,
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub line: Option<u32>,
510}
511
512/// Thread event body
513#[derive(Debug, Clone, Serialize, Deserialize)]
514#[serde(rename_all = "camelCase")]
515pub struct ThreadEventBody {
516    pub reason: String,
517    pub thread_id: i64,
518}
519
520/// Exited event body
521#[derive(Debug, Clone, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct ExitedEventBody {
524    pub exit_code: i32,
525}
526
527/// Terminated event body
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct TerminatedEventBody {
530    #[serde(default)]
531    pub restart: bool,
532}
533
534// === Parsed Events ===
535
536/// Parsed DAP event
537#[derive(Debug, Clone)]
538pub enum Event {
539    Initialized,
540    Stopped(StoppedEventBody),
541    Continued { thread_id: i64, all_threads_continued: bool },
542    Exited(ExitedEventBody),
543    Terminated(Option<TerminatedEventBody>),
544    Thread(ThreadEventBody),
545    Output(OutputEventBody),
546    Breakpoint { reason: String, breakpoint: Breakpoint },
547    Unknown { event: String, body: Option<Value> },
548}
549
550impl Event {
551    /// Parse an event from an EventMessage
552    pub fn from_message(msg: &EventMessage) -> Self {
553        match msg.event.as_str() {
554            "initialized" => Event::Initialized,
555            "stopped" => {
556                if let Some(body) = &msg.body {
557                    if let Ok(stopped) = serde_json::from_value(body.clone()) {
558                        return Event::Stopped(stopped);
559                    }
560                }
561                Event::Unknown {
562                    event: msg.event.clone(),
563                    body: msg.body.clone(),
564                }
565            }
566            "continued" => {
567                let thread_id = msg.body.as_ref()
568                    .and_then(|b| b.get("threadId"))
569                    .and_then(|v| v.as_i64())
570                    .unwrap_or(0);
571                let all_threads_continued = msg.body.as_ref()
572                    .and_then(|b| b.get("allThreadsContinued"))
573                    .and_then(|v| v.as_bool())
574                    .unwrap_or(true);
575                Event::Continued { thread_id, all_threads_continued }
576            }
577            "exited" => {
578                if let Some(body) = &msg.body {
579                    if let Ok(exited) = serde_json::from_value(body.clone()) {
580                        return Event::Exited(exited);
581                    }
582                }
583                Event::Exited(ExitedEventBody { exit_code: 0 })
584            }
585            "terminated" => {
586                let body = msg.body.as_ref()
587                    .and_then(|b| serde_json::from_value(b.clone()).ok());
588                Event::Terminated(body)
589            }
590            "thread" => {
591                if let Some(body) = &msg.body {
592                    if let Ok(thread) = serde_json::from_value(body.clone()) {
593                        return Event::Thread(thread);
594                    }
595                }
596                Event::Unknown {
597                    event: msg.event.clone(),
598                    body: msg.body.clone(),
599                }
600            }
601            "output" => {
602                if let Some(body) = &msg.body {
603                    if let Ok(output) = serde_json::from_value(body.clone()) {
604                        return Event::Output(output);
605                    }
606                }
607                Event::Unknown {
608                    event: msg.event.clone(),
609                    body: msg.body.clone(),
610                }
611            }
612            "breakpoint" => {
613                if let Some(body) = &msg.body {
614                    let reason = body.get("reason")
615                        .and_then(|v| v.as_str())
616                        .unwrap_or("unknown")
617                        .to_string();
618                    if let Some(bp) = body.get("breakpoint") {
619                        if let Ok(breakpoint) = serde_json::from_value(bp.clone()) {
620                            return Event::Breakpoint { reason, breakpoint };
621                        }
622                    }
623                }
624                Event::Unknown {
625                    event: msg.event.clone(),
626                    body: msg.body.clone(),
627                }
628            }
629            _ => Event::Unknown {
630                event: msg.event.clone(),
631                body: msg.body.clone(),
632            },
633        }
634    }
635}