Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:33:04.226Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

codelldb.rs

430 lines · 16.4 KB · rust
1use std::{env::consts, path::PathBuf, sync::OnceLock};
2
3use anyhow::{Context as _, Result};
4use async_trait::async_trait;
5use collections::HashMap;
6use dap::adapters::{DebugTaskDefinition, latest_github_release};
7use futures::StreamExt;
8use gpui::AsyncApp;
9use serde_json::Value;
10use task::{DebugRequest, DebugScenario, ZedDebugConfig};
11use util::fs::remove_matching;
12
13use crate::*;
14
15#[derive(Default)]
16pub(crate) struct CodeLldbDebugAdapter {
17    path_to_codelldb: OnceLock<String>,
18}
19
20impl CodeLldbDebugAdapter {
21    const ADAPTER_NAME: &'static str = "CodeLLDB";
22
23    async fn request_args(
24        &self,
25        delegate: &Arc<dyn DapDelegate>,
26        mut configuration: Value,
27        label: &str,
28    ) -> Result<dap::StartDebuggingRequestArguments> {
29        let obj = configuration
30            .as_object_mut()
31            .context("CodeLLDB is not a valid json object")?;
32
33        // CodeLLDB uses `name` for a terminal label.
34        obj.entry("name")
35            .or_insert(Value::String(String::from(label)));
36
37        obj.entry("cwd")
38            .or_insert(delegate.worktree_root_path().to_string_lossy().into());
39
40        let request = self.request_kind(&configuration).await?;
41
42        Ok(dap::StartDebuggingRequestArguments {
43            request,
44            configuration,
45        })
46    }
47
48    async fn fetch_latest_adapter_version(
49        &self,
50        delegate: &Arc<dyn DapDelegate>,
51    ) -> Result<AdapterVersion> {
52        let release =
53            latest_github_release("vadimcn/codelldb", true, false, delegate.http_client()).await?;
54
55        let arch = match std::env::consts::ARCH {
56            "aarch64" => "arm64",
57            "x86_64" => "x64",
58            unsupported => {
59                anyhow::bail!("unsupported architecture {unsupported}");
60            }
61        };
62        let platform = match std::env::consts::OS {
63            "macos" => "darwin",
64            "linux" => "linux",
65            "windows" => "win32",
66            unsupported => {
67                anyhow::bail!("unsupported operating system {unsupported}");
68            }
69        };
70        let asset_name = format!("codelldb-{platform}-{arch}.vsix");
71        let ret = AdapterVersion {
72            tag_name: release.tag_name,
73            url: release
74                .assets
75                .iter()
76                .find(|asset| asset.name == asset_name)
77                .with_context(|| format!("no asset found matching {asset_name:?}"))?
78                .browser_download_url
79                .clone(),
80        };
81
82        Ok(ret)
83    }
84}
85
86#[async_trait(?Send)]
87impl DebugAdapter for CodeLldbDebugAdapter {
88    fn name(&self) -> DebugAdapterName {
89        DebugAdapterName(Self::ADAPTER_NAME.into())
90    }
91
92    async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
93        let mut configuration = json!({
94            "request": match zed_scenario.request {
95                DebugRequest::Launch(_) => "launch",
96                DebugRequest::Attach(_) => "attach",
97            },
98        });
99        let map = configuration.as_object_mut().unwrap();
100        // CodeLLDB uses `name` for a terminal label.
101        map.insert(
102            "name".into(),
103            Value::String(String::from(zed_scenario.label.as_ref())),
104        );
105        match &zed_scenario.request {
106            DebugRequest::Attach(attach) => {
107                map.insert("pid".into(), attach.process_id.into());
108            }
109            DebugRequest::Launch(launch) => {
110                map.insert("program".into(), launch.program.clone().into());
111
112                if !launch.args.is_empty() {
113                    map.insert("args".into(), launch.args.clone().into());
114                }
115                if !launch.env.is_empty() {
116                    map.insert("env".into(), launch.env_json());
117                }
118                if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
119                    map.insert("stopOnEntry".into(), stop_on_entry.into());
120                }
121                if let Some(cwd) = launch.cwd.as_ref() {
122                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
123                }
124            }
125        }
126
127        Ok(DebugScenario {
128            adapter: zed_scenario.adapter,
129            label: zed_scenario.label,
130            config: configuration,
131            build: None,
132            tcp_connection: None,
133        })
134    }
135
136    fn dap_schema(&self) -> serde_json::Value {
137        json!({
138            "properties": {
139                "request": {
140                    "type": "string",
141                    "enum": ["attach", "launch"],
142                    "description": "Debug adapter request type"
143                },
144                "program": {
145                    "type": "string",
146                    "description": "Path to the program to debug or attach to"
147                },
148                "args": {
149                    "type": ["array", "string"],
150                    "description": "Program arguments"
151                },
152                "cwd": {
153                    "type": "string",
154                    "description": "Program working directory"
155                },
156                "env": {
157                    "type": "object",
158                    "description": "Additional environment variables",
159                    "patternProperties": {
160                        ".*": {
161                            "type": "string"
162                        }
163                    }
164                },
165                "envFile": {
166                    "type": "string",
167                    "description": "File to read the environment variables from"
168                },
169                "stdio": {
170                    "type": ["null", "string", "array", "object"],
171                    "description": "Destination for stdio streams: null = send to debugger console or a terminal, \"<path>\" = attach to a file/tty/fifo"
172                },
173                "terminal": {
174                    "type": "string",
175                    "enum": ["integrated", "console"],
176                    "description": "Terminal type to use",
177                    "default": "integrated"
178                },
179                "console": {
180                    "type": "string",
181                    "enum": ["integratedTerminal", "internalConsole"],
182                    "description": "Terminal type to use (compatibility alias of 'terminal')"
183                },
184                "stopOnEntry": {
185                    "type": "boolean",
186                    "description": "Automatically stop debuggee after launch",
187                    "default": false
188                },
189                "initCommands": {
190                    "type": "array",
191                    "description": "Initialization commands executed upon debugger startup",
192                    "items": {
193                        "type": "string"
194                    }
195                },
196                "targetCreateCommands": {
197                    "type": "array",
198                    "description": "Commands that create the debug target",
199                    "items": {
200                        "type": "string"
201                    }
202                },
203                "preRunCommands": {
204                    "type": "array",
205                    "description": "Commands executed just before the program is launched",
206                    "items": {
207                        "type": "string"
208                    }
209                },
210                "processCreateCommands": {
211                    "type": "array",
212                    "description": "Commands that create the debuggee process",
213                    "items": {
214                        "type": "string"
215                    }
216                },
217                "postRunCommands": {
218                    "type": "array",
219                    "description": "Commands executed just after the program has been launched",
220                    "items": {
221                        "type": "string"
222                    }
223                },
224                "preTerminateCommands": {
225                    "type": "array",
226                    "description": "Commands executed just before the debuggee is terminated or disconnected from",
227                    "items": {
228                        "type": "string"
229                    }
230                },
231                "exitCommands": {
232                    "type": "array",
233                    "description": "Commands executed at the end of debugging session",
234                    "items": {
235                        "type": "string"
236                    }
237                },
238                "expressions": {
239                    "type": "string",
240                    "enum": ["simple", "python", "native"],
241                    "description": "The default evaluator type used for expressions"
242                },
243                "sourceMap": {
244                    "type": "object",
245                    "description": "Source path remapping between the build machine and the local machine",
246                    "patternProperties": {
247                        ".*": {
248                            "type": ["string", "null"]
249                        }
250                    }
251                },
252                "relativePathBase": {
253                    "type": "string",
254                    "description": "Base directory used for resolution of relative source paths. Defaults to the workspace folder"
255                },
256                "sourceLanguages": {
257                    "type": "array",
258                    "description": "A list of source languages to enable language-specific features for",
259                    "items": {
260                        "type": "string"
261                    }
262                },
263                "reverseDebugging": {
264                    "type": "boolean",
265                    "description": "Enable reverse debugging",
266                    "default": false
267                },
268                "breakpointMode": {
269                    "type": "string",
270                    "enum": ["path", "file"],
271                    "description": "Specifies how source breakpoints should be set"
272                },
273                "pid": {
274                    "type": ["integer", "string"],
275                    "description": "Process id to attach to"
276                },
277                "waitFor": {
278                    "type": "boolean",
279                    "description": "Wait for the process to launch (MacOS only)",
280                    "default": false
281                }
282            },
283            "required": ["request"],
284            "allOf": [
285                {
286                    "if": {
287                        "properties": {
288                            "request": {
289                                "enum": ["launch"]
290                            }
291                        }
292                    },
293                    "then": {
294                        "oneOf": [
295                            {
296                                "required": ["program"]
297                            },
298                            {
299                                "required": ["targetCreateCommands"]
300                            }
301                        ]
302                    }
303                },
304                {
305                    "if": {
306                        "properties": {
307                            "request": {
308                                "enum": ["attach"]
309                            }
310                        }
311                    },
312                    "then": {
313                        "oneOf": [
314                            {
315                                "required": ["pid"]
316                            },
317                            {
318                                "required": ["program"]
319                            }
320                        ]
321                    }
322                }
323            ]
324        })
325    }
326
327    async fn get_binary(
328        &self,
329        delegate: &Arc<dyn DapDelegate>,
330        config: &DebugTaskDefinition,
331        user_installed_path: Option<PathBuf>,
332        user_args: Option<Vec<String>>,
333        user_env: Option<HashMap<String, String>>,
334        _: &mut AsyncApp,
335    ) -> Result<DebugAdapterBinary> {
336        let mut command = user_installed_path
337            .map(|p| p.to_string_lossy().into_owned())
338            .or(self.path_to_codelldb.get().cloned());
339
340        if command.is_none() {
341            delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
342            let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
343            let version_path = match self.fetch_latest_adapter_version(delegate).await {
344                Ok(version) => {
345                    adapters::download_adapter_from_github(
346                        self.name(),
347                        version.clone(),
348                        adapters::DownloadedFileType::Vsix,
349                        delegate.as_ref(),
350                    )
351                    .await?;
352                    let version_path =
353                        adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version.tag_name));
354                    remove_matching(&adapter_path, |entry| entry != version_path).await;
355                    version_path
356                }
357                Err(e) => {
358                    delegate.output_to_console("Unable to fetch latest version".to_string());
359                    log::error!("Error fetching latest version of {}: {}", self.name(), e);
360                    delegate.output_to_console(format!(
361                        "Searching for adapters in: {}",
362                        adapter_path.display()
363                    ));
364                    let mut paths = delegate
365                        .fs()
366                        .read_dir(&adapter_path)
367                        .await
368                        .context("No cached adapter directory")?;
369                    paths
370                        .next()
371                        .await
372                        .context("No cached adapter found")?
373                        .context("No cached adapter found")?
374                }
375            };
376            let adapter_dir = version_path.join("extension").join("adapter");
377            let path = adapter_dir
378                .join(format!("codelldb{}", consts::EXE_SUFFIX))
379                .to_string_lossy()
380                .into_owned();
381            self.path_to_codelldb.set(path.clone()).ok();
382            command = Some(path);
383        };
384        let mut json_config = config.config.clone();
385
386        // Auto-detect Rust projects and add sourceLanguages if not present.
387        // This enables panic breakpoints to work correctly with CodeLLDB.
388        if let Some(config_obj) = json_config.as_object_mut() {
389            if !config_obj.contains_key("sourceLanguages") {
390                // Check if this looks like a Rust binary (Cargo build output)
391                if let Some(program) = config_obj.get("program").and_then(|p| p.as_str()) {
392                    let path_str = program.replace('\\', "/");
393                    if path_str.contains("/target/debug/") || path_str.contains("/target/release/")
394                    {
395                        config_obj.insert("sourceLanguages".to_owned(), json!(["rust"]));
396                    }
397                }
398            }
399        }
400
401        Ok(DebugAdapterBinary {
402            command: Some(command.unwrap()),
403            cwd: Some(delegate.worktree_root_path().to_path_buf()),
404            arguments: user_args.unwrap_or_else(|| {
405                if let Some(config) = json_config.as_object_mut()
406                    && let Some(source_languages) = config.get("sourceLanguages").filter(|value| {
407                        value
408                            .as_array()
409                            .is_some_and(|array| array.iter().all(Value::is_string))
410                    })
411                {
412                    let ret = vec![
413                        "--settings".into(),
414                        json!({"sourceLanguages": source_languages}).to_string(),
415                    ];
416                    config.remove("sourceLanguages");
417                    ret
418                } else {
419                    vec![]
420                }
421            }),
422            request_args: self
423                .request_args(delegate, json_config, &config.label)
424                .await?,
425            envs: user_env.unwrap_or_default(),
426            connection: None,
427        })
428    }
429}
430
Served at tenant.openagents/omega Member data and write actions are omitted.