Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:37:02.012Z 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

go.rs

628 lines · 22.0 KB · rust
1use anyhow::{Context as _, bail};
2use collections::HashMap;
3use dap::{
4    StartDebuggingRequestArguments,
5    adapters::{
6        DebugTaskDefinition, DownloadedFileType, TcpArguments, download_adapter_from_github,
7        latest_github_release,
8    },
9};
10use fs::Fs;
11use futures::StreamExt;
12use gpui::{AsyncApp, SharedString};
13use language::LanguageName;
14use log::warn;
15use serde_json::{Map, Value};
16use task::TcpArgumentsTemplate;
17use util;
18
19use std::{
20    env::consts,
21    ffi::OsStr,
22    path::{Path, PathBuf},
23    str::FromStr,
24    sync::OnceLock,
25};
26
27use crate::*;
28
29#[derive(Default, Debug)]
30pub(crate) struct GoDebugAdapter {
31    shim_path: OnceLock<PathBuf>,
32}
33
34impl GoDebugAdapter {
35    const ADAPTER_NAME: &'static str = "Delve";
36    async fn fetch_latest_adapter_version(
37        delegate: &Arc<dyn DapDelegate>,
38    ) -> Result<AdapterVersion> {
39        let release = latest_github_release(
40            "zed-industries/delve-shim-dap",
41            true,
42            false,
43            delegate.http_client(),
44        )
45        .await?;
46
47        let os = match consts::OS {
48            "macos" => "apple-darwin",
49            "linux" => "unknown-linux-gnu",
50            "windows" => "pc-windows-msvc",
51            other => bail!("Running on unsupported os: {other}"),
52        };
53        let suffix = if consts::OS == "windows" {
54            ".zip"
55        } else {
56            ".tar.gz"
57        };
58        let asset_name = format!("delve-shim-dap-{}-{os}{suffix}", consts::ARCH);
59        let asset = release
60            .assets
61            .iter()
62            .find(|asset| asset.name == asset_name)
63            .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
64
65        Ok(AdapterVersion {
66            tag_name: release.tag_name,
67            url: asset.browser_download_url.clone(),
68        })
69    }
70    async fn install_shim(&self, delegate: &Arc<dyn DapDelegate>) -> anyhow::Result<PathBuf> {
71        if let Some(path) = self.shim_path.get().cloned() {
72            return Ok(path);
73        }
74
75        let adapter_dir = paths::debug_adapters_dir().join("delve-shim-dap");
76
77        match Self::fetch_latest_adapter_version(delegate).await {
78            Ok(asset) => {
79                let ty = if consts::OS == "windows" {
80                    DownloadedFileType::Zip
81                } else {
82                    DownloadedFileType::GzipTar
83                };
84                download_adapter_from_github(
85                    "delve-shim-dap".into(),
86                    asset.clone(),
87                    ty,
88                    delegate.as_ref(),
89                )
90                .await?;
91
92                let path = adapter_dir
93                    .join(format!("delve-shim-dap_{}", asset.tag_name))
94                    .join(format!("delve-shim-dap{}", consts::EXE_SUFFIX));
95                self.shim_path.set(path.clone()).ok();
96
97                Ok(path)
98            }
99            Err(error) => {
100                let binary_name = format!("delve-shim-dap{}", consts::EXE_SUFFIX);
101                let mut cached = None;
102                if let Ok(mut entries) = delegate.fs().read_dir(&adapter_dir).await {
103                    while let Some(entry) = entries.next().await {
104                        if let Ok(version_dir) = entry {
105                            let candidate = version_dir.join(&binary_name);
106                            if delegate
107                                .fs()
108                                .metadata(&candidate)
109                                .await
110                                .is_ok_and(|m| m.is_some())
111                            {
112                                cached = Some(candidate);
113                                break;
114                            }
115                        }
116                    }
117                }
118
119                if let Some(path) = cached {
120                    warn!("Failed to fetch latest delve-shim-dap, using cached version: {error:#}");
121                    self.shim_path.set(path.clone()).ok();
122                    Ok(path)
123                } else {
124                    Err(error)
125                }
126            }
127        }
128    }
129}
130
131#[async_trait(?Send)]
132impl DebugAdapter for GoDebugAdapter {
133    fn name(&self) -> DebugAdapterName {
134        DebugAdapterName(Self::ADAPTER_NAME.into())
135    }
136
137    fn adapter_language_name(&self) -> Option<LanguageName> {
138        Some(SharedString::new_static("Go").into())
139    }
140
141    fn dap_schema(&self) -> serde_json::Value {
142        // Create common properties shared between launch and attach
143        let common_properties = json!({
144            "debugAdapter": {
145                "enum": ["legacy", "dlv-dap"],
146                "description": "Select which debug adapter to use with this configuration.",
147                "default": "dlv-dap"
148            },
149            "stopOnEntry": {
150                "type": "boolean",
151                "description": "Automatically stop program after launch or attach.",
152                "default": false
153            },
154            "showLog": {
155                "type": "boolean",
156                "description": "Show log output from the delve debugger. Maps to dlv's `--log` flag.",
157                "default": false
158            },
159            "cwd": {
160                "type": "string",
161                "description": "Workspace relative or absolute path to the working directory of the program being debugged.",
162                "default": "${ZED_WORKTREE_ROOT}"
163            },
164            "dlvFlags": {
165                "type": "array",
166                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported flags.",
167                "items": {
168                    "type": "string"
169                },
170                "default": []
171            },
172            "port": {
173                "type": "number",
174                "description": "Debug server port. For remote configurations, this is where to connect.",
175                "default": 2345
176            },
177            "host": {
178                "type": "string",
179                "description": "Debug server host. For remote configurations, this is where to connect.",
180                "default": "127.0.0.1"
181            },
182            "substitutePath": {
183                "type": "array",
184                "items": {
185                    "type": "object",
186                    "properties": {
187                        "from": {
188                            "type": "string",
189                            "description": "The absolute local path to be replaced."
190                        },
191                        "to": {
192                            "type": "string",
193                            "description": "The absolute remote path to replace with."
194                        }
195                    }
196                },
197                "description": "Mappings from local to remote paths for debugging.",
198                "default": []
199            },
200            "trace": {
201                "type": "string",
202                "enum": ["verbose", "trace", "log", "info", "warn", "error"],
203                "default": "error",
204                "description": "Debug logging level."
205            },
206            "backend": {
207                "type": "string",
208                "enum": ["default", "native", "lldb", "rr"],
209                "description": "Backend used by delve. Maps to `dlv`'s `--backend` flag."
210            },
211            "logOutput": {
212                "type": "string",
213                "enum": ["debugger", "gdbwire", "lldbout", "debuglineerr", "rpc", "dap"],
214                "description": "Components that should produce debug output.",
215                "default": "debugger"
216            },
217            "logDest": {
218                "type": "string",
219                "description": "Log destination for delve."
220            },
221            "stackTraceDepth": {
222                "type": "number",
223                "description": "Maximum depth of stack traces.",
224                "default": 50
225            },
226            "showGlobalVariables": {
227                "type": "boolean",
228                "default": false,
229                "description": "Show global package variables in variables pane."
230            },
231            "showRegisters": {
232                "type": "boolean",
233                "default": false,
234                "description": "Show register variables in variables pane."
235            },
236            "hideSystemGoroutines": {
237                "type": "boolean",
238                "default": false,
239                "description": "Hide system goroutines from call stack view."
240            },
241            "console": {
242                "default": "internalConsole",
243                "description": "Where to launch the debugger.",
244                "enum": ["internalConsole", "integratedTerminal"]
245            },
246            "asRoot": {
247                "default": false,
248                "description": "Debug with elevated permissions (on Unix).",
249                "type": "boolean"
250            }
251        });
252
253        // Create launch-specific properties
254        let launch_properties = json!({
255            "program": {
256                "type": "string",
257                "description": "Path to the program folder or file to debug.",
258                "default": "${ZED_WORKTREE_ROOT}"
259            },
260            "args": {
261                "type": ["array", "string"],
262                "description": "Command line arguments for the program.",
263                "items": {
264                    "type": "string"
265                },
266                "default": []
267            },
268            "env": {
269                "type": "object",
270                "description": "Environment variables for the debugged program.",
271                "default": {}
272            },
273            "envFile": {
274                "type": ["string", "array"],
275                "items": {
276                    "type": "string"
277                },
278                "description": "Path(s) to files with environment variables.",
279                "default": ""
280            },
281            "buildFlags": {
282                "type": ["string", "array"],
283                "items": {
284                    "type": "string"
285                },
286                "description": "Flags for the Go compiler.",
287                "default": []
288            },
289            "output": {
290                "type": "string",
291                "description": "Output path for the binary.",
292                "default": "debug"
293            },
294            "mode": {
295                "enum": [ "debug", "test", "exec", "replay", "core"],
296                "description": "Debug mode for launch configuration.",
297            },
298            "traceDirPath": {
299                "type": "string",
300                "description": "Directory for record trace (for 'replay' mode).",
301                "default": ""
302            },
303            "coreFilePath": {
304                "type": "string",
305                "description": "Path to core dump file (for 'core' mode).",
306                "default": ""
307            }
308        });
309
310        // Create attach-specific properties
311        let attach_properties = json!({
312            "processId": {
313                "anyOf": [
314                    {
315                        "enum": ["${command:pickProcess}", "${command:pickGoProcess}"],
316                        "description": "Use process picker to select a process."
317                    },
318                    {
319                        "type": "string",
320                        "description": "Process name to attach to."
321                    },
322                    {
323                        "type": "number",
324                        "description": "Process ID to attach to."
325                    }
326                ],
327                "default": 0
328            },
329            "mode": {
330                "enum": ["local", "remote"],
331                "description": "Local or remote debugging.",
332                "default": "local"
333            },
334            "remotePath": {
335                "type": "string",
336                "description": "Path to source on remote machine.",
337                "markdownDeprecationMessage": "Use `substitutePath` instead.",
338                "default": ""
339            }
340        });
341
342        // Create the final schema
343        json!({
344            "oneOf": [
345                {
346                    "allOf": [
347                        {
348                            "type": "object",
349                            "required": ["request"],
350                            "properties": {
351                                "request": {
352                                    "type": "string",
353                                    "enum": ["launch"],
354                                    "description": "Request to launch a new process"
355                                }
356                            }
357                        },
358                        {
359                            "type": "object",
360                            "properties": common_properties
361                        },
362                        {
363                            "type": "object",
364                            "required": ["program", "mode"],
365                            "properties": launch_properties
366                        }
367                    ]
368                },
369                {
370                    "allOf": [
371                        {
372                            "type": "object",
373                            "required": ["request"],
374                            "properties": {
375                                "request": {
376                                    "type": "string",
377                                    "enum": ["attach"],
378                                    "description": "Request to attach to an existing process"
379                                }
380                            }
381                        },
382                        {
383                            "type": "object",
384                            "properties": common_properties
385                        },
386                        {
387                            "type": "object",
388                            "required": ["mode"],
389                            "properties": attach_properties
390                        }
391                    ]
392                }
393            ]
394        })
395    }
396
397    async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
398        let mut args = match &zed_scenario.request {
399            dap::DebugRequest::Attach(attach_config) => {
400                json!({
401                    "request": "attach",
402                    "mode": "local",
403                    "processId": attach_config.process_id,
404                })
405            }
406            dap::DebugRequest::Launch(launch_config) => {
407                let mode = if launch_config.program != "." {
408                    "exec"
409                } else {
410                    "debug"
411                };
412
413                json!({
414                    "request": "launch",
415                    "mode": mode,
416                    "program": launch_config.program,
417                    "cwd": launch_config.cwd,
418                    "args": launch_config.args,
419                    "env": launch_config.env_json()
420                })
421            }
422        };
423
424        let map = args.as_object_mut().unwrap();
425
426        if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
427            map.insert("stopOnEntry".into(), stop_on_entry.into());
428        }
429
430        Ok(DebugScenario {
431            adapter: zed_scenario.adapter,
432            label: zed_scenario.label,
433            build: None,
434            config: args,
435            tcp_connection: None,
436        })
437    }
438
439    async fn get_binary(
440        &self,
441        delegate: &Arc<dyn DapDelegate>,
442        task_definition: &DebugTaskDefinition,
443        user_installed_path: Option<PathBuf>,
444        user_args: Option<Vec<String>>,
445        user_env: Option<HashMap<String, String>>,
446        _cx: &mut AsyncApp,
447    ) -> Result<DebugAdapterBinary> {
448        let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
449        let dlv_binary = format!("dlv{}", consts::EXE_SUFFIX);
450        let dlv_path = adapter_path.join(&dlv_binary);
451
452        let delve_path = if let Some(path) = user_installed_path {
453            path.to_string_lossy().into_owned()
454        } else if let Some(path) = delegate.which(OsStr::new("dlv")).await {
455            path.to_string_lossy().into_owned()
456        } else if delegate.fs().is_file(&dlv_path).await {
457            dlv_path.to_string_lossy().into_owned()
458        } else {
459            let go = delegate
460                .which(OsStr::new("go"))
461                .await
462                .context("Go not found in path. Please install Go first, then Dlv will be installed automatically.")?;
463
464            let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
465
466            let install_output = util::command::new_command(&go)
467                .env("GO111MODULE", "on")
468                .env("GOBIN", &adapter_path)
469                .args(&["install", "github.com/go-delve/delve/cmd/dlv@latest"])
470                .output()
471                .await?;
472
473            if !install_output.status.success() {
474                bail!(
475                    "failed to install dlv via `go install`. stdout: {:?}, stderr: {:?}\n Please try installing it manually using 'go install github.com/go-delve/delve/cmd/dlv@latest'",
476                    String::from_utf8_lossy(&install_output.stdout),
477                    String::from_utf8_lossy(&install_output.stderr)
478                );
479            }
480
481            adapter_path
482                .join(&dlv_binary)
483                .to_string_lossy()
484                .into_owned()
485        };
486
487        let cwd = Some(
488            task_definition
489                .config
490                .get("cwd")
491                .and_then(|s| s.as_str())
492                .map(PathBuf::from)
493                .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()),
494        );
495
496        let arguments;
497        let command;
498        let connection;
499
500        let mut configuration = task_definition.config.clone();
501        let mut envs = user_env.unwrap_or_default();
502
503        if let Some(configuration) = configuration.as_object_mut() {
504            configuration
505                .entry("cwd")
506                .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
507
508            handle_envs(
509                configuration,
510                &mut envs,
511                cwd.as_deref(),
512                delegate.fs().clone(),
513            )
514            .await;
515        }
516
517        if let Some(connection_options) = &task_definition.tcp_connection {
518            command = None;
519            arguments = vec![];
520            let (host, port, timeout) =
521                crate::configure_tcp_connection(connection_options.clone()).await?;
522            connection = Some(TcpArguments {
523                host,
524                port,
525                timeout,
526            });
527        } else {
528            let minidelve_path = self.install_shim(delegate).await?;
529            let (host, port, _) =
530                crate::configure_tcp_connection(TcpArgumentsTemplate::default()).await?;
531            command = Some(minidelve_path.to_string_lossy().into_owned());
532            connection = None;
533            arguments = if let Some(mut args) = user_args {
534                args.insert(0, delve_path);
535                args
536            } else if cfg!(windows) {
537                vec![
538                    delve_path,
539                    "dap".into(),
540                    "--listen".into(),
541                    format!("{}:{}", host, port),
542                    "--headless".into(),
543                ]
544            } else {
545                vec![
546                    delve_path,
547                    "dap".into(),
548                    "--listen".into(),
549                    format!("{}:{}", host, port),
550                ]
551            };
552        }
553        Ok(DebugAdapterBinary {
554            command,
555            arguments,
556            cwd,
557            envs,
558            connection,
559            request_args: StartDebuggingRequestArguments {
560                configuration,
561                request: self.request_kind(&task_definition.config).await?,
562            },
563        })
564    }
565}
566
567// delve doesn't do anything with the envFile setting, so we intercept it
568async fn handle_envs(
569    config: &mut Map<String, Value>,
570    envs: &mut HashMap<String, String>,
571    cwd: Option<&Path>,
572    fs: Arc<dyn Fs>,
573) -> Option<()> {
574    let env_files = match config.get("envFile")? {
575        Value::Array(arr) => arr.iter().map(|v| v.as_str()).collect::<Vec<_>>(),
576        Value::String(s) => vec![Some(s.as_str())],
577        _ => return None,
578    };
579
580    let rebase_path = |path: PathBuf| {
581        if path.is_absolute() {
582            Some(path)
583        } else {
584            cwd.map(|p| p.join(path))
585        }
586    };
587
588    let mut env_vars = HashMap::default();
589    for path in env_files {
590        let Some(path) = path
591            .and_then(|s| PathBuf::from_str(s).ok())
592            .and_then(rebase_path)
593        else {
594            continue;
595        };
596
597        if let Ok(file) = fs.open_sync(&path).await {
598            let file_envs: HashMap<String, String> = dotenvy::from_read_iter(file)
599                .filter_map(Result::ok)
600                .collect();
601            envs.extend(file_envs.iter().map(|(k, v)| (k.clone(), v.clone())));
602            env_vars.extend(file_envs);
603        } else {
604            warn!("While starting Go debug session: failed to read env file {path:?}");
605        };
606    }
607
608    let mut env_obj: serde_json::Map<String, Value> = serde_json::Map::new();
609
610    for (k, v) in env_vars {
611        env_obj.insert(k, Value::String(v));
612    }
613
614    if let Some(existing_env) = config.get("env").and_then(|v| v.as_object()) {
615        for (k, v) in existing_env {
616            env_obj.insert(k.clone(), v.clone());
617        }
618    }
619
620    if !env_obj.is_empty() {
621        config.insert("env".to_string(), Value::Object(env_obj));
622    }
623
624    // remove envFile now that it's been handled
625    config.remove("envFile");
626    Some(())
627}
628
Served at tenant.openagents/omega Member data and write actions are omitted.