feat(debugger): add BAML DAP, Cursor integration, and stepping/hover fixes#3164
feat(debugger): add BAML DAP, Cursor integration, and stepping/hover fixes#3164antoniosarosi wants to merge 2 commits intocanaryfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughImplements DAP-based debugging: adds a DAP server to the CLI, VM debugger primitives and stops, preserves debug sequence points in bytecode, VSCode extension wiring to launch the adapter, LSP/playground and project-discovery updates, tests, and supporting configuration and smoke fixtures. Changes
Sequence Diagram(s)sequenceDiagram
participant User as VSCode User
participant Ext as VSCode Extension
participant DAP as DAP Server (baml_cli)
participant DB as ProjectDB
participant VM as BexVm
User->>Ext: Start debug (launch config)
Ext->>DAP: Spawn CLI with "dap" arg
Ext->>DAP: send initialize
DAP->>Ext: initialized event
Ext->>DAP: send launch
DAP->>DB: load project files & get_debug_bytecode
DAP->>VM: create VM & load program
Ext->>DAP: setBreakpoints
DAP->>VM: debug_set_breakpoints(...)
Ext->>DAP: configurationDone / continue
DAP->>VM: start exec()
VM->>VM: execute instructions (check debug_try_stop)
alt stop triggered (breakpoint/step/pause)
VM->>DAP: emit DebugStop
DAP->>Ext: stopped event
Ext->>User: show stopped location
Ext->>DAP: stackTrace / scopes / variables
DAP->>VM: debug_stack_frames / debug_frame_locals
Ext->>User: user issues continue/step
Ext->>DAP: next/stepIn/continue
DAP->>VM: debug_step_* or debug_continue
DAP->>VM: resume exec()
end
VM->>DAP: execution complete
DAP->>Ext: terminated event
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merging this PR will not alter performance
|
Binary size checks passed✅ 7 passed
Generated by |
There was a problem hiding this comment.
Actionable comments posted: 21
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
baml_language/crates/baml_lsp_server/src/lib.rs (1)
125-135: 🧹 Nitpick | 🔵 TrivialPort is bound even when the playground is disabled.
playground_enabledis computed at Line 125, but the port-picking at Line 129 runs unconditionally. When the playground is disabled, the boundTcpListeneris handed toplayground_server::run, which presumably returns an error mentioning the env vars (caught at Lines 170-173 and logged atinfo). This works but unnecessarily binds a TCP port.Consider short-circuiting:
♻️ Optional: skip port binding when playground is disabled
let playground_enabled = std::env::var("BAML_PLAYGROUND_DEV_PORT").is_ok() || std::env::var("BAML_PLAYGROUND_DIR").is_ok(); - let (playground_listener, playground_port): (Option<TcpListener>, u16) = - match tokio_runtime.block_on(playground_server::pick_port(3700, 100)) { - Ok((listener, port)) => (Some(listener), port), - Err(e) => { - tracing::error!("Could not find playground port: {e}"); - (None, 0) - } - }; + let (playground_listener, playground_port): (Option<TcpListener>, u16) = if playground_enabled { + match tokio_runtime.block_on(playground_server::pick_port(3700, 100)) { + Ok((listener, port)) => (Some(listener), port), + Err(e) => { + tracing::error!("Could not find playground port: {e}"); + (None, 0) + } + } + } else { + tracing::info!("Playground disabled (BAML_PLAYGROUND_DEV_PORT / BAML_PLAYGROUND_DIR not set)"); + (None, 0) + };Also applies to: 163-178
baml_language/crates/baml_cli/src/commands.rs (1)
115-139: 🧹 Nitpick | 🔵 TrivialMinor: duplicated error-handling arm.
The
DebugAdapterandLanguageServermatch arms are identical. Consider extracting a small helper to reduce duplication as more server-like subcommands are added.Optional refactor
impl RuntimeCli { + fn run_server_command(run: impl FnOnce() -> Result<()>) -> Result<crate::ExitCode> { + match run() { + Ok(()) => Ok(crate::ExitCode::Success), + Err(e) => { + #[allow(clippy::print_stderr)] + { + eprintln!("Error: {e}"); + } + Ok(crate::ExitCode::Other) + } + } + } + pub fn run(&self) -> Result<crate::ExitCode> { match &self.command { - Commands::LanguageServer(args) => match args.run() { - Ok(()) => Ok(crate::ExitCode::Success), - Err(e) => { - #[allow(clippy::print_stderr)] - { - eprintln!("Error: {e}"); - } - Ok(crate::ExitCode::Other) - } - }, - Commands::DebugAdapter(args) => match args.run() { - Ok(()) => Ok(crate::ExitCode::Success), - Err(e) => { - #[allow(clippy::print_stderr)] - { - eprintln!("Error: {e}"); - } - Ok(crate::ExitCode::Other) - } - }, + Commands::LanguageServer(args) => Self::run_server_command(|| args.run()), + Commands::DebugAdapter(args) => Self::run_server_command(|| args.run()), Commands::Format(args) => args.run(), } }
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (26)
.vscode/launch.jsonbaml_language/crates/baml_cli/Cargo.tomlbaml_language/crates/baml_cli/src/commands.rsbaml_language/crates/baml_cli/src/dap.rsbaml_language/crates/baml_cli/src/lib.rsbaml_language/crates/baml_compiler_emit/src/emit.rsbaml_language/crates/baml_compiler_emit/tests/while_loops.rsbaml_language/crates/baml_lsp_server/src/lib.rsbaml_language/crates/baml_lsp_server/src/playground_env.rsbaml_language/crates/baml_lsp_server/src/playground_sender.rsbaml_language/crates/baml_project/src/db.rsbaml_language/crates/baml_tests/src/vm.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rsbaml_language/crates/bex_vm/src/debugger.rsbaml_language/crates/bex_vm/src/lib.rsbaml_language/crates/bex_vm/src/vm.rsbaml_language/crates/bex_vm/tests/debugger.rsbaml_language/crates/tools_onionskin/src/compiler.rsbaml_language/stow.tomldebugger_smoke/baml_src/main.bamltypescript2/app-vscode-ext/package.jsontypescript2/app-vscode-ext/src/__tests__/debugger.test.tstypescript2/app-vscode-ext/src/debugger.tstypescript2/app-vscode-ext/src/extension.tstypescript2/app-vscode-ext/tsup.config.ts
| pub fn run() -> Result<()> { | ||
| let stdin = io::stdin(); | ||
| let stdout = io::stdout(); | ||
| let mut reader = BufReader::new(stdin.lock()); | ||
| let writer = BufWriter::new(stdout.lock()); | ||
| let mut server = DapServer::new(writer); | ||
|
|
||
| while let Some(message) = read_dap_message(&mut reader)? { | ||
| let value: JsonValue = | ||
| serde_json::from_str(&message).context("failed to parse incoming DAP message")?; | ||
|
|
||
| if value.get("type").and_then(JsonValue::as_str) != Some("request") { | ||
| continue; | ||
| } | ||
|
|
||
| let keep_running = server.handle_request(&value)?; | ||
| if !keep_running { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Main loop silently ignores non-request DAP messages.
This is correct per the DAP protocol for this server-role implementation (the adapter only needs to respond to requests), but consider logging ignored messages at a trace/debug level for easier protocol-level troubleshooting.
| fn handle_resume_command( | ||
| &mut self, | ||
| request_seq: i64, | ||
| command: &str, | ||
| action: ResumeAction, | ||
| ) -> Result<()> { | ||
| if self.session.is_none() { | ||
| bail!("no active debug session"); | ||
| } | ||
|
|
||
| let body = if command == "continue" { | ||
| Some(json!({ "allThreadsContinued": true })) | ||
| } else { | ||
| Some(json!({})) | ||
| }; | ||
| self.respond_success(request_seq, command, body)?; | ||
| self.send_event( | ||
| "continued", | ||
| json!({ | ||
| "threadId": THREAD_ID, | ||
| "allThreadsContinued": true, | ||
| }), | ||
| )?; | ||
| self.run_and_emit_or_terminate(action) | ||
| } |
There was a problem hiding this comment.
Sending continued event for pause request is incorrect per DAP spec.
The pause request means "interrupt a running debuggee." It should produce a stopped event, never a continued event. In this synchronous, single-threaded model the pause request only arrives when the VM is already stopped, making the continued → stopped sequence misleading for DAP clients.
Consider excluding Pause from this shared handler or skipping the continued event for it:
Proposed fix
fn handle_resume_command(
&mut self,
request_seq: i64,
command: &str,
action: ResumeAction,
) -> Result<()> {
if self.session.is_none() {
bail!("no active debug session");
}
let body = if command == "continue" {
Some(json!({ "allThreadsContinued": true }))
} else {
Some(json!({}))
};
self.respond_success(request_seq, command, body)?;
- self.send_event(
- "continued",
- json!({
- "threadId": THREAD_ID,
- "allThreadsContinued": true,
- }),
- )?;
+ if !matches!(action, ResumeAction::Pause) {
+ self.send_event(
+ "continued",
+ json!({
+ "threadId": THREAD_ID,
+ "allThreadsContinued": true,
+ }),
+ )?;
+ }
self.run_and_emit_or_terminate(action)
}| struct DebugSession { | ||
| db: ProjectDatabase, | ||
| vm: BexVm, | ||
| stop_on_entry: bool, | ||
| file_id_to_path: HashMap<u32, PathBuf>, | ||
| path_to_file_id: HashMap<PathBuf, u32>, | ||
| sequence_points_by_file: HashMap<u32, Vec<usize>>, | ||
| breakpoints_by_file: HashMap<u32, HashSet<usize>>, | ||
| handles: HashMap<i64, HandleValue>, | ||
| next_handle: i64, | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
db field in DebugSession is unused after rebuild_file_maps().
The ProjectDatabase is only queried during construction (via rebuild_file_maps and get_debug_bytecode). After launch, it sits idle in memory holding all project data. If there's no plan to reload/recompile during a debug session, consider dropping it after launch to reduce memory pressure, or document why it's retained.
| fn load_project_files(db: &mut ProjectDatabase, root: &Path) -> Result<()> { | ||
| for entry in WalkDir::new(root) | ||
| .follow_links(true) | ||
| .into_iter() | ||
| .filter_map(Result::ok) | ||
| { | ||
| if !entry.file_type().is_file() { | ||
| continue; | ||
| } | ||
|
|
||
| let path = entry.into_path(); | ||
| let is_baml = path.extension().and_then(|ext| ext.to_str()) == Some("baml"); | ||
| let is_baml_jinja = path | ||
| .to_string_lossy() | ||
| .to_ascii_lowercase() | ||
| .ends_with(".baml.jinja"); | ||
| if !is_baml && !is_baml_jinja { | ||
| continue; | ||
| } | ||
|
|
||
| let content = fs::read_to_string(&path) | ||
| .with_context(|| format!("failed to read source file {}", path.display()))?; | ||
| db.add_or_update_file(&path, &content); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
.baml extension check is case-sensitive, .baml.jinja check is case-insensitive.
Line 982 checks ext == Some("baml") (case-sensitive), while line 985 uses to_ascii_lowercase().ends_with(".baml.jinja") (case-insensitive). This is likely fine since .baml is the canonical extension, but the inconsistency could surprise a maintainer.
| fn json_to_vm_value(vm: &mut BexVm, value: &JsonValue) -> Result<Value> { | ||
| match value { | ||
| JsonValue::Null => Ok(Value::Null), | ||
| JsonValue::Bool(value) => Ok(Value::Bool(*value)), | ||
| JsonValue::Number(number) => { | ||
| if let Some(value) = number.as_i64() { | ||
| return Ok(Value::Int(value)); | ||
| } | ||
| if let Some(value) = number.as_u64() { | ||
| let value = i64::try_from(value).context("integer is too large for i64")?; | ||
| return Ok(Value::Int(value)); | ||
| } | ||
| if let Some(value) = number.as_f64() { | ||
| return Ok(Value::Float(value)); | ||
| } | ||
| bail!("unsupported JSON number representation") | ||
| } | ||
| JsonValue::String(value) => Ok(vm.alloc_string(value.clone())), | ||
| JsonValue::Array(values) => { | ||
| let values = values | ||
| .iter() | ||
| .map(|value| json_to_vm_value(vm, value)) | ||
| .collect::<Result<Vec<_>>>()?; | ||
| Ok(vm.alloc_array(values)) | ||
| } | ||
| JsonValue::Object(values) => { | ||
| let mut map = indexmap::IndexMap::new(); | ||
| for (key, value) in values { | ||
| map.insert(key.clone(), json_to_vm_value(vm, value)?); | ||
| } | ||
| Ok(vm.alloc_map(map)) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
No recursion depth limit in json_to_vm_value.
Deeply nested JSON payloads in launch args could cause a stack overflow. Since this processes user-supplied launch configuration, consider adding a depth guard.
| allowed_crates = ["bridge_cffi", "baml_cli"] | ||
| reason = "baml_* crates should not depend on bex_* crates (except baml_cli for DAP runtime integration)." | ||
|
|
||
| [[namespaces.dependency_rules]] | ||
| pattern.select = "bex_vm_types" | ||
| allowed_crates = ["baml_compiler_emit", "baml_project"] | ||
| reason = "Only compiler_emit and baml_project crates should depend on bex_vm_types (pure data layer)." | ||
| allowed_crates = ["baml_compiler_emit", "baml_project", "baml_cli"] | ||
| reason = "Only compiler_emit, baml_project, and baml_cli (DAP) should depend on bex_vm_types (pure data layer)." |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the stow tool and understand the codebase structure
find . -name "*stow*" -type f | head -20Repository: BoundaryML/baml
Length of output: 141
🏁 Script executed:
# Search for the stow tool source code
find . -path "*stow*" -name "*.rs" -type f | head -30Repository: BoundaryML/baml
Length of output: 104
🏁 Script executed:
# Look for the stow.toml file and understand its location relative to the tool
git ls-files | grep -E "(stow|stow\.toml)"Repository: BoundaryML/baml
Length of output: 266
🏁 Script executed:
# Examine the stow tool main.rs to understand how allowed_crates is processed
wc -l baml_language/crates/tools_stow/src/main.rsRepository: BoundaryML/baml
Length of output: 107
🏁 Script executed:
# Read the stow main.rs file
cat -n baml_language/crates/tools_stow/src/main.rs | head -200Repository: BoundaryML/baml
Length of output: 9834
🏁 Script executed:
# Search for allowed_crates and the validation logic
rg -n "allowed_crates" baml_language/crates/tools_stow/src/main.rs -A 5 -B 5Repository: BoundaryML/baml
Length of output: 804
🏁 Script executed:
# Find the DependencyRule struct definition and validation logic
rg -n "struct DependencyRule\|fn check.*depend\|fn validate.*depend" baml_language/crates/tools_stow/src/main.rs -A 10Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for DependencyRule definition
rg -n "DependencyRule" baml_language/crates/tools_stow/src/main.rs | head -20Repository: BoundaryML/baml
Length of output: 317
🏁 Script executed:
# Read lines around the DependencyRule struct definition
sed -n '270,330p' baml_language/crates/tools_stow/src/main.rsRepository: BoundaryML/baml
Length of output: 1898
🏁 Script executed:
# Find where namespace-scoped rules are applied vs global rules
rg -n "applicable_rules\|namespace.*rules\|check.*violation" baml_language/crates/tools_stow/src/main.rs | head -30Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Read the validation logic around line 1112
sed -n '1100,1200p' baml_language/crates/tools_stow/src/main.rsRepository: BoundaryML/baml
Length of output: 3916
🏁 Script executed:
# Find the get_namespace function to understand how namespace membership is determined
rg -n "fn get_namespace\|get_namespace" baml_language/crates/tools_stow/src/main.rs -A 10 | head -40Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Check the actual stow.toml to see baml_cli's namespace assignment
sed -n '1,150p' baml_language/stow.tomlRepository: BoundaryML/baml
Length of output: 5426
🏁 Script executed:
# Find the get_namespace implementation
sed -n '1,2808p' baml_language/crates/tools_stow/src/main.rs | rg -n "fn get_namespace" -A 15Repository: BoundaryML/baml
Length of output: 1157
🏁 Script executed:
# Also check what name_exceptions does in the code
rg -n "name_exceptions" baml_language/crates/tools_stow/src/main.rs -B 3 -A 3Repository: BoundaryML/baml
Length of output: 2678
🏁 Script executed:
# Check if surface namespace has any dependency_rules restricting bex_*
sed -n '120,200p' baml_language/stow.tomlRepository: BoundaryML/baml
Length of output: 407
🏁 Script executed:
# Verify which get_namespace function is used in the dependency validation
sed -n '1100,1140p' baml_language/crates/tools_stow/src/main.rsRepository: BoundaryML/baml
Length of output: 1540
🏁 Script executed:
# Get more context around the validation logic
rg -n "check_dependency_rules\|applicable_rules" baml_language/crates/tools_stow/src/main.rs -B 5 -A 25 | head -60Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Confirm: does the validation code use get_namespace or get_namespace_for_crate?
rg -n "applicable_rules\|get_namespace" baml_language/crates/tools_stow/src/main.rs | grep -E "get_namespace|applicable_rules"Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Let's check the full function signature and context
rg -n "fn check_dependency_rules" baml_language/crates/tools_stow/src/main.rs -A 50 | head -80Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Search for where applicable_rules is populated
rg -n "applicable_rules.extend\|applicable_rules =" baml_language/crates/tools_stow/src/main.rs -B 10Repository: BoundaryML/baml
Length of output: 41
🏁 Script executed:
# Read the entire validation section more carefully
sed -n '1090,1150p' baml_language/crates/tools_stow/src/main.rsRepository: BoundaryML/baml
Length of output: 2075
🏁 Script executed:
# Final verification: confirm baml_cli is in surface, not baml namespace
rg -n "baml_cli" baml_language/stow.tomlRepository: BoundaryML/baml
Length of output: 500
baml_cli exceptions in baml namespace rules are semantic no-ops — move restrictions to surface namespace.
The dependency validation code uses get_namespace(), which only checks prefix matching (baml_*, etc.). Since baml_cli doesn't have the baml_ prefix and is registered in the surface namespace via name_exceptions, it will never match the baml namespace rules at lines 83 and 88. The validation therefore never applies these rules to baml_cli, making both allowed_crates entries unreachable.
The actual permission for baml_cli to depend on bex_* and bex_vm_types comes from the surface namespace having no corresponding [[namespaces.dependency_rules]] that restricts them. The reason strings ("controlled exception", "only … and baml_cli (DAP) should depend on …") incorrectly suggest stow enforces this boundary when it's actually unenforced.
To properly control baml_cli's bex_* dependencies, add explicit rules under the surface namespace:
Suggested fix
[[namespaces]]
name = "surface"
name_exceptions = {"baml_cli" = "baml_cli", "baml_playground_wasm" = "baml_playground_wasm" }
+
+[[namespaces.dependency_rules]]
+pattern.select = "bex_*"
+pattern.exclude = ["bex_vm_types"]
+allowed_crates = ["baml_cli"]
+reason = "Only baml_cli (DAP runtime integration) may depend on bex_* internals from the surface namespace."
+
+[[namespaces.dependency_rules]]
+pattern.select = "bex_vm_types"
+allowed_crates = ["baml_cli"]
+reason = "Only baml_cli (DAP) may depend on bex_vm_types from the surface namespace."| function sum_to(n: int) -> int { | ||
| let total = 0; | ||
| let i = 1; | ||
|
|
||
| while (i <= n) { | ||
| total = total + i; | ||
| i = i + 1; | ||
| } | ||
|
|
||
| total | ||
| } |
There was a problem hiding this comment.
sum_to silently returns 0 for negative n
When n < 0, the while (i <= n) condition is false from the start, so total remains 0 and is returned without any error or indication. For a smoke-test file this is harmless, but if sum_to is ever reused in a real DAP scenario with a user-supplied argument, the silent zero could be misleading. Consider a guard if the function moves beyond smoke-test use.
🛡️ Optional guard for negative input
function sum_to(n: int) -> int {
+ // n must be non-negative; callers passing n < 0 receive 0
let total = 0;
let i = 1;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function sum_to(n: int) -> int { | |
| let total = 0; | |
| let i = 1; | |
| while (i <= n) { | |
| total = total + i; | |
| i = i + 1; | |
| } | |
| total | |
| } | |
| function sum_to(n: int) -> int { | |
| // n must be non-negative; callers passing n < 0 receive 0 | |
| let total = 0; | |
| let i = 1; | |
| while (i <= n) { | |
| total = total + i; | |
| i = i + 1; | |
| } | |
| total | |
| } |
| function main() -> int { | ||
| let seed = 8; | ||
| let triangle = sum_to(seed); | ||
| triangle | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider annotating the expected result for easier manual smoke-test validation
sum_to(8) = 36, but this is nowhere stated in the file. During a manual DAP run you have to mentally compute the expected terminal value. A brief comment makes the session self-documenting.
📝 Proposed annotation
function main() -> int {
let seed = 8;
- let triangle = sum_to(seed);
+ let triangle = sum_to(seed); // expected: 36 (1+2+…+8)
triangle
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function main() -> int { | |
| let seed = 8; | |
| let triangle = sum_to(seed); | |
| triangle | |
| } | |
| function main() -> int { | |
| let seed = 8; | |
| let triangle = sum_to(seed); // expected: 36 (1+2+…+8) | |
| triangle | |
| } |
| "activationEvents": [ | ||
| "onLanguage:baml" | ||
| "onLanguage:baml", | ||
| "onDebug" | ||
| ], |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🌐 Web query:
VS Code extension activation events onDebug onDebugResolve documentation
💡 Result:
VS Code documents the debug-related extension activation events on the “Activation Events” reference page:
-
onDebug: activates your extension before any debug session starts."activationEvents": ["onDebug"]
[1]
-
More fine-grained debug activation events (recommended for “heavier” extensions):
onDebugInitialConfigurations: fires just beforeprovideDebugConfigurations(...)is called. [1]onDebugResolve:type: fires just beforeresolveDebugConfiguration(...)(and related resolve methods) are called for a specific debug type. Example:[1]"activationEvents": ["onDebugResolve:my-debug-type"]
References
- [1] VS Code Extension API – Activation Events (code.visualstudio.com)
Citations:
🌐 Web query:
onDebugAdapterProtocolTracker VS Code activation event
💡 Result:
Use the onDebugAdapterProtocolTracker activation event in your extension’s package.json.
- To activate only for a specific debug type (recommended), use
onDebugAdapterProtocolTracker:<debugType>(for examplenode). If you omit:<debugType>, VS Code will activate your extension for all debug types. [1] - This event fires when a debug session of the relevant type is about to be launched and VS Code may need a Debug Adapter Protocol tracker. [2]
Example:
{
"activationEvents": [
"onDebugAdapterProtocolTracker:node"
]
}Sources:
[1] VS Code Updates v1.30 (Debug Adapter Protocol tracker + activation event syntax) (code.visualstudio.com)
[2] VS Code “Activation Events” reference (code.visualstudio.com)
Citations:
- 1: https://code.visualstudio.com/updates/v1_30?source=post_page---------------------------&utm_source=openai
- 2: https://code.visualstudio.com/api/references/activation-events
🏁 Script executed:
# Let's examine the full context of the package.json file
cd typescript2/app-vscode-ext && cat -n package.json | head -120Repository: BoundaryML/baml
Length of output: 4168
onDebug activates the extension on every debug session, not just BAML sessions.
Consider using onDebugResolve:baml instead. This is the recommended approach for debug-related activation, as it activates only when resolving debug configurations for BAML, avoiding unnecessary startup overhead when debugging other languages.
Suggested fix
"activationEvents": [
"onLanguage:baml",
- "onDebug"
+ "onDebugResolve:baml"
],📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "activationEvents": [ | |
| "onLanguage:baml" | |
| "onLanguage:baml", | |
| "onDebug" | |
| ], | |
| "activationEvents": [ | |
| "onLanguage:baml", | |
| "onDebugResolve:baml" | |
| ], |
| createDebugAdapterDescriptor( | ||
| _session: vscode.DebugSession, | ||
| _executable: vscode.DebugAdapterExecutable | undefined, | ||
| ): vscode.ProviderResult<vscode.DebugAdapterDescriptor> { | ||
| const command = buildBamlDebugAdapterCommand(this.cliPath); | ||
| return new vscode.DebugAdapterExecutable(command.command, command.args); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider forwarding environment variables to the DAP subprocess.
vscode.DebugAdapterExecutable accepts an optional options parameter that supports env and cwd. Without it, users cannot configure e.g. RUST_LOG=debug for the DAP server without modifying the extension. This is not blocking, but worth considering for operator/developer ergonomics.
💡 Suggested extension
createDebugAdapterDescriptor(
_session: vscode.DebugSession,
_executable: vscode.DebugAdapterExecutable | undefined,
): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
const command = buildBamlDebugAdapterCommand(this.cliPath);
- return new vscode.DebugAdapterExecutable(command.command, command.args);
+ return new vscode.DebugAdapterExecutable(command.command, command.args, {
+ env: { ...process.env },
+ });
}| "[baml]": { | ||
| "editor.defaultFormatter": "Boundary.baml-extension" | ||
| }, | ||
| "baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli", |
There was a problem hiding this comment.
Remove this developer-local path — contradicts PR intent and pollutes shared workspace settings.
The PR objectives explicitly state "Local .vscode/settings.json (baml.cliPath) intentionally omitted from the PR", yet this line was committed. The path /media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli is:
- Machine-specific: will be missing on every other contributor's machine, likely causing the extension to emit an error or degrade silently.
- Information leak: exposes the developer's username, drive label, and internal directory layout.
The fix is to revert this line. If the intent is to document the setting for contributors, add a comment in README or .vscode/extensions.json; if it needs to be tracked locally, add .vscode/settings.json (or a *.local.json variant) to .gitignore.
🗑️ Proposed fix
- "baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "baml.cliPath": "/media/tony/WesternDigitalNvmeSsd/Code/baml/baml_language/target/debug/baml-cli", |
Summary
--
Notes
Add this to
.vscode/settings.json:Validation
Notes
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes / Improvements
Tests