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