#![recursion_limit = "256"]
mod debugger;
pub mod framing;
mod host_bridge;
mod protocol;
use std::io;
use std::sync::atomic::AtomicI64;
use std::sync::mpsc::{channel, Sender};
use std::sync::{Arc, Mutex};
use debugger::Debugger;
use framing::SharedWriter;
use host_bridge::{deliver_reply, pending_map_new, DapHostBridge, DapHostCallReply, PendingMap};
use protocol::{DapMessage, DapResponse};
pub fn run() {
with_adapter_environment(run_stdio);
}
fn with_adapter_environment<T>(body: impl FnOnce() -> T) -> T {
let _environment = harn_vm::stdlib::process::declare_session_environment_if_absent(
harn_vm::security::SessionEnvironment::inherited(),
);
body()
}
#[cfg(all(test, unix))]
mod environment_tests {
use super::with_adapter_environment;
#[test]
fn adapter_keeps_sdk_profile_but_withholds_it_from_children() {
const PROFILE: &str = "AWS_PROFILE";
const FUTURE: &str = "DAP_TEST_FUTURE_CREDENTIAL";
const ORDINARY: &str = "DAP_TEST_ENV_CONTROL";
let old_profile = std::env::var_os(PROFILE);
let old_future = std::env::var_os(FUTURE);
let old_ordinary = std::env::var_os(ORDINARY);
let overlay = harn_vm::llm_config::parse_config_toml(
"[providers.future_dap]\nbase_url = \"https://future.example.test/v1\"\nauth_style = \"bearer\"\nauth_env = \"DAP_TEST_FUTURE_CREDENTIAL\"\nchat_endpoint = \"/chat/completions\"\n",
)
.expect("future provider parses");
harn_vm::llm_config::set_user_overrides(Some(overlay));
std::env::set_var(PROFILE, "dap-test-profile");
std::env::set_var(FUTURE, "future-dummy-canary");
std::env::set_var(ORDINARY, "ordinary-value");
let inherited = std::process::Command::new("printenv")
.arg(PROFILE)
.output()
.expect("plain child runs");
assert_eq!(inherited.stdout, b"dap-test-profile\n");
with_adapter_environment(|| {
assert_eq!(std::env::var(PROFILE).as_deref(), Ok("dap-test-profile"));
let profile_child =
harn_vm::process_sandbox::std_command_for("printenv", &[PROFILE.to_string()])
.expect("build policy-governed child")
.output()
.expect("run policy-governed child");
assert!(!profile_child.status.success());
assert!(profile_child.stdout.is_empty());
let future_child =
harn_vm::process_sandbox::std_command_for("printenv", &[FUTURE.to_string()])
.expect("build future-provider child")
.output()
.expect("run future-provider child");
assert!(!future_child.status.success());
assert!(future_child.stdout.is_empty());
let ordinary_child =
harn_vm::process_sandbox::std_command_for("printenv", &[ORDINARY.to_string()])
.expect("build ordinary child")
.output()
.expect("run ordinary child");
assert!(ordinary_child.status.success());
assert_eq!(ordinary_child.stdout, b"ordinary-value\n");
});
match old_profile {
Some(value) => std::env::set_var(PROFILE, value),
None => std::env::remove_var(PROFILE),
}
match old_future {
Some(value) => std::env::set_var(FUTURE, value),
None => std::env::remove_var(FUTURE),
}
match old_ordinary {
Some(value) => std::env::set_var(ORDINARY, value),
None => std::env::remove_var(ORDINARY),
}
harn_vm::llm_config::clear_user_overrides();
}
}
fn run_stdio() {
harn_vm::stdlib::force_link();
harn_parser::install_builtin_manifest(harn_vm::stdlib::all_builtin_manifest());
let seq = Arc::new(AtomicI64::new(1_000_000));
let stdout: SharedWriter = Arc::new(Mutex::new(Box::new(io::stdout())));
let pending: PendingMap = pending_map_new();
let (request_tx, request_rx) = channel::<DapMessage>();
let pending_for_reader = Arc::clone(&pending);
harn_parser::runtime_stack::spawn(move || stdin_reader(request_tx, pending_for_reader));
let bridge = Arc::new(DapHostBridge::new(
Arc::clone(&seq),
Arc::clone(&stdout),
Arc::clone(&pending),
));
let mut debugger = Debugger::new();
debugger.attach_host_bridge(Arc::clone(&bridge));
use std::sync::mpsc::TryRecvError;
loop {
loop {
match request_rx.try_recv() {
Ok(msg) => {
let responses = debugger.handle_message(msg);
for response in responses {
send_response(&stdout, &response);
}
}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => return,
}
}
if debugger.is_running() {
let responses = debugger.step_running_vm();
for response in responses {
send_response(&stdout, &response);
}
} else {
match request_rx.recv() {
Ok(msg) => {
let responses = debugger.handle_message(msg);
for response in responses {
send_response(&stdout, &response);
}
}
Err(_) => return,
}
}
}
}
fn stdin_reader(request_tx: Sender<DapMessage>, pending: PendingMap) {
let stdin = io::stdin();
let mut reader = io::BufReader::new(stdin.lock());
loop {
let body_bytes = match framing::read_frame(&mut reader) {
Ok(Some(body_bytes)) => body_bytes,
Ok(None) => break,
Err(error) => {
eprintln!("Failed to read DAP frame: {error}");
break;
}
};
let body = String::from_utf8_lossy(&body_bytes);
match serde_json::from_str::<DapMessage>(&body) {
Ok(msg) => {
if msg.msg_type == "response" {
if let Some(request_seq) = msg.request_seq {
deliver_reply(
&pending,
request_seq,
DapHostCallReply {
success: msg.success.unwrap_or(false),
body: msg.body,
message: msg.message,
},
);
continue;
}
}
if request_tx.send(msg).is_err() {
break;
}
}
Err(e) => {
eprintln!("Failed to parse DAP message: {e}");
eprintln!("Body: {body}");
}
}
}
}
fn send_response(stdout: &SharedWriter, response: &DapResponse) {
let _ = framing::write_json_frame(stdout, response);
}