1use std::collections::HashMap;
9use std::io::{BufRead, BufReader, Write};
10use std::path::PathBuf;
11use std::process::{Child, ChildStdin, ChildStdout};
12use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
13use std::sync::{mpsc, Arc, Mutex};
14
15use serde_json::Value;
16
17pub type RuntimeHandler = Arc<dyn Fn(&str, Value) -> Result<Value, String> + Send + Sync>;
18
19type Response = Result<Value, String>;
20type Pending = Arc<Mutex<HashMap<u64, mpsc::Sender<Response>>>>;
21pub type ToolUpdateHandler = Arc<dyn Fn(Value) + Send + Sync>;
22
23const NODE_STOPPED: &str = "Node extension host stopped";
24
25struct Inner {
26 child: Arc<Mutex<Child>>,
29 stdin: Arc<Mutex<ChildStdin>>,
30 pending: Pending,
31 tool_update_handlers: Arc<Mutex<HashMap<String, ToolUpdateHandler>>>,
32 runtime_handlers: Arc<Mutex<Vec<RuntimeHandler>>>,
33 next_id: AtomicU64,
34 shutdown: Arc<AtomicBool>,
37 reader: Mutex<Option<std::thread::JoinHandle<()>>>,
38 cleanup_path: Option<PathBuf>,
39}
40
41impl Inner {
42 fn is_shutdown(&self) -> bool {
43 self.shutdown.load(Ordering::Acquire)
44 }
45
46 fn mark_failed(&self, error: &str) {
47 self.shutdown.store(true, Ordering::Release);
48 fail_pending(&self.pending, error);
49 terminate_child(&self.child);
50 }
51
52 fn shutdown(&self) {
59 let first_shutdown = !self.shutdown.swap(true, Ordering::AcqRel);
60 if first_shutdown {
64 fail_pending(&self.pending, NODE_STOPPED);
65 }
66 terminate_child(&self.child);
67 }
68}
69
70impl Drop for Inner {
71 fn drop(&mut self) {
72 self.shutdown();
73 if let Ok(reader) = self.reader.get_mut() {
74 if let Some(reader) = reader.take() {
75 let _ = reader.join();
76 }
77 }
78 if let Some(path) = self.cleanup_path.as_ref() {
79 let _ = std::fs::remove_file(path);
80 }
81 }
82}
83
84#[derive(Clone)]
85pub struct NodeTransport {
86 inner: Arc<Inner>,
87}
88
89pub struct PendingRequest {
90 id: u64,
91 receiver: mpsc::Receiver<Response>,
92}
93
94pub struct NodeTransportStartup {
99 transport: NodeTransport,
100 init_receiver: mpsc::Receiver<Response>,
101}
102
103impl NodeTransportStartup {
104 pub fn transport(&self) -> NodeTransport {
107 self.transport.clone()
108 }
109
110 pub fn wait(self) -> Result<(NodeTransport, Value), String> {
113 let result = match self.init_receiver.recv() {
114 Ok(Ok(init)) => Ok(init),
115 Ok(Err(error)) => Err(error),
116 Err(_) => Err("Node extension host exited before initialization".into()),
117 };
118 match result {
119 Ok(init) => Ok((self.transport, init)),
120 Err(error) => {
121 self.transport.shutdown();
122 Err(error)
123 }
124 }
125 }
126}
127
128impl PendingRequest {
129 pub fn id(&self) -> u64 {
130 self.id
131 }
132
133 pub fn wait(self) -> Response {
134 self.receiver
135 .recv()
136 .unwrap_or_else(|_| Err("Node extension response channel closed".into()))
137 }
138}
139
140impl NodeTransport {
141 pub fn start(
142 child: Child,
143 stdin: ChildStdin,
144 stdout: ChildStdout,
145 ) -> Result<(Self, Value), String> {
146 Self::start_with_cleanup_and_handlers(child, stdin, stdout, None, Vec::new())
147 }
148
149 pub fn start_with_cleanup(
154 child: Child,
155 stdin: ChildStdin,
156 stdout: ChildStdout,
157 cleanup_path: Option<PathBuf>,
158 ) -> Result<(Self, Value), String> {
159 Self::start_with_cleanup_and_handlers(child, stdin, stdout, cleanup_path, Vec::new())
160 }
161
162 pub fn start_with_cleanup_and_handlers(
168 child: Child,
169 stdin: ChildStdin,
170 stdout: ChildStdout,
171 cleanup_path: Option<PathBuf>,
172 initial_handlers: Vec<RuntimeHandler>,
173 ) -> Result<(Self, Value), String> {
174 Self::start_pending_with_cleanup_and_handlers(
175 child,
176 stdin,
177 stdout,
178 cleanup_path,
179 initial_handlers,
180 )?
181 .wait()
182 }
183
184 pub fn start_pending_with_cleanup_and_handlers(
189 child: Child,
190 stdin: ChildStdin,
191 stdout: ChildStdout,
192 cleanup_path: Option<PathBuf>,
193 initial_handlers: Vec<RuntimeHandler>,
194 ) -> Result<NodeTransportStartup, String> {
195 let stdout = BufReader::new(stdout);
196 let stdin = Arc::new(Mutex::new(stdin));
197 let pending = Arc::new(Mutex::new(HashMap::new()));
198 let tool_update_handlers = Arc::new(Mutex::new(HashMap::new()));
199 let runtime_handlers = Arc::new(Mutex::new(initial_handlers));
200 let shutdown = Arc::new(AtomicBool::new(false));
201 let (init_sender, init_receiver) = mpsc::channel();
202 let child = Arc::new(Mutex::new(child));
203 let inner = Arc::new(Inner {
204 child: child.clone(),
205 stdin: stdin.clone(),
206 pending: pending.clone(),
207 tool_update_handlers: tool_update_handlers.clone(),
208 runtime_handlers: runtime_handlers.clone(),
209 next_id: AtomicU64::new(1),
210 shutdown: shutdown.clone(),
211 reader: Mutex::new(None),
212 cleanup_path,
213 });
214 let reader = std::thread::Builder::new()
215 .name("rpi-node-transport".into())
216 .spawn(move || {
217 read_loop(
218 stdout,
219 stdin,
220 child,
221 pending,
222 tool_update_handlers,
223 runtime_handlers,
224 Some(init_sender),
225 shutdown,
226 )
227 })
228 .map_err(|error| {
229 terminate_child(&inner.child);
230 if let Some(path) = inner.cleanup_path.as_ref() {
231 let _ = std::fs::remove_file(path);
232 }
233 format!("could not start Node response reader: {error}")
234 })?;
235 if let Err(error) = inner.reader.lock().map(|mut slot| *slot = Some(reader)) {
236 terminate_child(&inner.child);
237 if let Some(path) = inner.cleanup_path.as_ref() {
238 let _ = std::fs::remove_file(path);
239 }
240 return Err(format!("Node reader lock poisoned: {error}"));
241 }
242
243 Ok(NodeTransportStartup {
248 transport: Self { inner },
249 init_receiver,
250 })
251 }
252
253 pub fn shutdown(&self) {
256 self.inner.shutdown();
257 }
258
259 pub fn is_shutdown(&self) -> bool {
263 self.inner.is_shutdown()
264 }
265
266 pub fn same_instance(&self, other: &Self) -> bool {
271 Arc::ptr_eq(&self.inner, &other.inner)
272 }
273
274 pub fn begin_request(&self, method: &str, payload: Value) -> Result<PendingRequest, String> {
275 if self.inner.is_shutdown() {
276 return Err(NODE_STOPPED.into());
277 }
278 let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
279 let (sender, receiver) = mpsc::channel();
280 let mut pending = self
281 .inner
282 .pending
283 .lock()
284 .map_err(|_| "Node pending-request lock poisoned")?;
285 if self.inner.is_shutdown() {
286 return Err(NODE_STOPPED.into());
287 }
288 pending.insert(id, sender);
289 if self.inner.is_shutdown() {
293 let sender = pending.remove(&id);
294 drop(pending);
295 if let Some(sender) = sender {
296 let _ = sender.send(Err(NODE_STOPPED.into()));
297 }
298 return Err(NODE_STOPPED.into());
299 }
300 drop(pending);
301
302 let mut request = serde_json::json!({"id": id, "method": method});
303 if let (Some(object), Some(values)) = (request.as_object_mut(), payload.as_object()) {
304 object.extend(values.clone());
305 }
306 if let Err(error) = write_value(&self.inner.stdin, &request) {
307 if let Ok(mut pending) = self.inner.pending.lock() {
308 pending.remove(&id);
309 }
310 self.inner.mark_failed(&error);
311 return Err(error);
312 }
313 Ok(PendingRequest { id, receiver })
314 }
315
316 pub fn request(&self, method: &str, payload: Value) -> Response {
317 self.begin_request(method, payload)?.wait()
318 }
319
320 pub fn send_event(&self, event: Value) -> Result<(), String> {
321 if self.inner.is_shutdown() {
322 return Err(NODE_STOPPED.into());
323 }
324 if let Err(error) = write_value(&self.inner.stdin, &event) {
325 self.inner.mark_failed(&error);
326 return Err(error);
327 }
328 Ok(())
329 }
330
331 pub fn request_event(&self, mut event: Value) -> Response {
335 if self.inner.is_shutdown() {
336 return Err(NODE_STOPPED.into());
337 }
338 let supported = event
339 .get("event")
340 .and_then(Value::as_str)
341 .is_some_and(|name| matches!(name, "custom_input" | "custom_resize"));
342 if !supported {
343 return Err("host event does not support request/response mode".into());
344 }
345 let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
346 let (sender, receiver) = mpsc::channel();
347 let mut pending = self
348 .inner
349 .pending
350 .lock()
351 .map_err(|_| "Node pending-request lock poisoned")?;
352 if self.inner.is_shutdown() {
353 return Err(NODE_STOPPED.into());
354 }
355 pending.insert(id, sender);
356 if self.inner.is_shutdown() {
357 let sender = pending.remove(&id);
358 drop(pending);
359 if let Some(sender) = sender {
360 let _ = sender.send(Err(NODE_STOPPED.into()));
361 }
362 return Err(NODE_STOPPED.into());
363 }
364 drop(pending);
365 let Some(object) = event.as_object_mut() else {
366 if let Ok(mut pending) = self.inner.pending.lock() {
367 pending.remove(&id);
368 }
369 return Err("Node host event must be a JSON object".into());
370 };
371 object.insert("id".into(), Value::from(id));
372 if let Err(error) = write_value(&self.inner.stdin, &event) {
373 if let Ok(mut pending) = self.inner.pending.lock() {
374 pending.remove(&id);
375 }
376 self.inner.mark_failed(&error);
377 return Err(error);
378 }
379 receiver
380 .recv()
381 .unwrap_or_else(|_| Err("Node extension response channel closed".into()))
382 }
383
384 pub fn cancel(&self, id: u64) -> Result<(), String> {
385 self.send_event(serde_json::json!({
386 "type": "host_event",
387 "event": "cancel_request",
388 "id": id,
389 }))
390 }
391
392 pub fn register_tool_update_handler(
398 &self,
399 tool_call_id: impl Into<String>,
400 handler: ToolUpdateHandler,
401 ) -> Result<(), String> {
402 self.inner
403 .tool_update_handlers
404 .lock()
405 .map_err(|_| "Node tool-update lock poisoned")?
406 .insert(tool_call_id.into(), handler);
407 Ok(())
408 }
409
410 pub fn unregister_tool_update_handler(&self, tool_call_id: &str) {
414 if let Ok(mut handlers) = self.inner.tool_update_handlers.lock() {
415 handlers.remove(tool_call_id);
416 }
417 }
418
419 pub fn replace_runtime_handlers(&self, handler: RuntimeHandler) -> Result<(), String> {
420 *self
421 .inner
422 .runtime_handlers
423 .lock()
424 .map_err(|_| "Node runtime handler lock poisoned")? = vec![handler];
425 Ok(())
426 }
427
428 pub fn add_runtime_handler(&self, handler: RuntimeHandler) -> Result<(), String> {
429 self.inner
430 .runtime_handlers
431 .lock()
432 .map_err(|_| "Node runtime handler lock poisoned")?
433 .push(handler);
434 Ok(())
435 }
436}
437
438fn read_loop(
439 mut stdout: BufReader<ChildStdout>,
440 stdin: Arc<Mutex<ChildStdin>>,
441 child: Arc<Mutex<Child>>,
442 pending: Pending,
443 tool_update_handlers: Arc<Mutex<HashMap<String, ToolUpdateHandler>>>,
444 handlers: Arc<Mutex<Vec<RuntimeHandler>>>,
445 init_sender: Option<mpsc::Sender<Response>>,
446 shutdown: Arc<AtomicBool>,
447) {
448 let mut init_sender = init_sender;
449 loop {
450 let message = match read_value(&mut stdout) {
451 Ok(message) => message,
452 Err(error) => {
453 shutdown.store(true, Ordering::Release);
458 fail_pending(&pending, &error);
459 terminate_child(&child);
460 if let Some(sender) = init_sender.take() {
461 let _ = sender.send(Err(error));
462 }
463 return;
464 }
465 };
466 if init_sender.is_some() && message.get("id").and_then(Value::as_u64) == Some(0) {
470 if let Some(sender) = init_sender.take() {
471 let _ = sender.send(Ok(message));
475 }
476 continue;
477 }
478 if message.get("type").and_then(Value::as_str) == Some("runtime_request") {
479 let background = message
480 .get("action")
481 .and_then(Value::as_str)
482 .is_some_and(runtime_action_may_block);
483 if background {
484 let stdin = stdin.clone();
485 let handlers = handlers.clone();
486 let pending = pending.clone();
487 let child = child.clone();
488 let shutdown = shutdown.clone();
489 std::thread::spawn(move || {
490 handle_runtime_request(message, &stdin, &handlers, &pending, &child, &shutdown)
491 });
492 } else {
493 handle_runtime_request(message, &stdin, &handlers, &pending, &child, &shutdown);
494 }
495 continue;
496 }
497 if message.get("type").and_then(Value::as_str) == Some("host_event")
498 && message.get("event").and_then(Value::as_str) == Some("tool_update")
499 {
500 let Some(tool_call_id) = message.get("toolCallId").and_then(Value::as_str) else {
501 continue;
502 };
503 let handler = tool_update_handlers
504 .lock()
505 .ok()
506 .and_then(|handlers| handlers.get(tool_call_id).cloned());
507 if let Some(handler) = handler {
508 handler(message.get("partialResult").cloned().unwrap_or_default());
509 }
510 continue;
511 }
512 let Some(id) = message.get("id").and_then(Value::as_u64) else {
513 continue;
514 };
515 let sender = pending
516 .lock()
517 .ok()
518 .and_then(|mut values| values.remove(&id));
519 let Some(sender) = sender else {
520 continue;
521 };
522 let response = response_from_message(&message);
523 let _ = sender.send(response);
524 }
525}
526
527fn terminate_child(child: &Arc<Mutex<Child>>) {
531 if let Ok(mut child) = child.lock() {
532 let _ = child.kill();
533 let _ = child.wait();
534 }
535}
536
537fn response_from_message(message: &Value) -> Response {
538 if message.get("ok").and_then(Value::as_bool) == Some(true) {
539 Ok(message.get("result").cloned().unwrap_or_default())
540 } else {
541 Err(message
542 .get("error")
543 .and_then(Value::as_str)
544 .unwrap_or("JS extension failed")
545 .to_string())
546 }
547}
548
549fn runtime_action_may_block(action: &str) -> bool {
550 action.starts_with("provider.")
551 || action.starts_with("tools.")
552 || action.starts_with("session.")
553 || action == "ui.dialog"
557}
558
559fn handle_runtime_request(
560 message: Value,
561 stdin: &Arc<Mutex<ChildStdin>>,
562 handlers: &Arc<Mutex<Vec<RuntimeHandler>>>,
563 pending: &Pending,
564 child: &Arc<Mutex<Child>>,
565 shutdown: &Arc<AtomicBool>,
566) {
567 let Some(request_id) = message.get("requestId").and_then(Value::as_u64) else {
568 return;
569 };
570 let Some(action) = message.get("action").and_then(Value::as_str) else {
571 return;
572 };
573 let args = message.get("args").cloned().unwrap_or_default();
574 let handlers = handlers.lock().map(|values| values.clone());
575 let mut result = Err(format!("unsupported capability: {action}"));
576 match handlers {
577 Ok(handlers) => {
578 for handler in handlers {
579 match handler(action, args.clone()) {
580 Ok(value) => {
581 result = Ok(value);
582 break;
583 }
584 Err(error) if error.starts_with("unsupported capability:") => {}
585 Err(error) => {
586 result = Err(error);
587 break;
588 }
589 }
590 }
591 }
592 Err(_) => result = Err("Node runtime handler lock poisoned".into()),
593 }
594 let response = match result {
595 Ok(result) => serde_json::json!({
596 "type": "runtime_response",
597 "requestId": request_id,
598 "ok": true,
599 "result": result,
600 }),
601 Err(error) => serde_json::json!({
602 "type": "runtime_response",
603 "requestId": request_id,
604 "ok": false,
605 "error": error,
606 }),
607 };
608 if let Err(error) = write_value(stdin, &response) {
609 shutdown.store(true, Ordering::Release);
614 fail_pending(pending, &error);
615 terminate_child(child);
616 }
617}
618
619fn write_value(stdin: &Arc<Mutex<ChildStdin>>, value: &Value) -> Result<(), String> {
620 let mut stdin = stdin.lock().map_err(|_| "Node host stdin lock poisoned")?;
621 writeln!(stdin, "{value}")
622 .map_err(|error| format!("could not write to Node extension host: {error}"))?;
623 stdin
624 .flush()
625 .map_err(|error| format!("could not flush Node extension host: {error}"))
626}
627
628fn read_value(reader: &mut BufReader<ChildStdout>) -> Result<Value, String> {
629 let mut line = String::new();
630 reader
631 .read_line(&mut line)
632 .map_err(|error| format!("could not read Node extension host: {error}"))?;
633 if line.trim().is_empty() {
634 return Err("Node extension host exited without a response".into());
635 }
636 serde_json::from_str(&line).map_err(|error| format!("invalid Node extension response: {error}"))
637}
638
639fn fail_pending(pending: &Pending, error: &str) {
640 let values = pending
641 .lock()
642 .map(|mut values| values.drain().map(|(_, sender)| sender).collect::<Vec<_>>())
643 .unwrap_or_default();
644 for sender in values {
645 let _ = sender.send(Err(error.to_string()));
646 }
647}