//! Channel input for a headless harness. The console and stop signals are clients. use deno_core::{OpState, op2}; use deno_error::JsErrorBox; use serde::Serialize; use serde_json::Value; use std::{cell::RefCell, rc::Rc}; use tokio::sync::mpsc; #[derive(Serialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum Input { Submit { conversation: u64, content: Value, #[serde(rename = "requestId")] request_id: String, }, Api { id: String, method: String, path: String, body: Value, #[serde(skip)] reply: mpsc::UnboundedSender, }, Stop, Shutdown, } pub type Sender = mpsc::UnboundedSender; pub type Receiver = mpsc::UnboundedReceiver; pub fn channel() -> (Sender, Receiver) { mpsc::unbounded_channel() } pub(super) struct Inbox(pub tokio::sync::Mutex); #[op2] #[serde] pub async fn op_input(state: Rc>) -> Result { let inbox = state.borrow().borrow::>().clone(); let input = inbox.0.lock().await.recv().await; if let Some(Input::Api { id, reply, .. }) = &input { state .borrow_mut() .borrow_mut::() .insert( id.clone(), super::http::Reply { sender: reply.clone(), finished: Default::default(), }, ); } serde_json::to_value(input).map_err(|error| JsErrorBox::generic(error.to_string())) } pub(super) fn console(sender: Sender) { std::thread::spawn(move || { use std::io::BufRead; for line in std::io::stdin().lock().lines() { let Ok(line) = line else { break }; if line.trim().is_empty() { continue; } if sender .send(Input::Submit { conversation: 1, content: Value::String(line), request_id: uuid::Uuid::new_v4().to_string(), }) .is_err() { break; } } // EOF closes only this client. The signal client keeps the harness alive. }); } /// Register SIGTERM and SIGINT before the harness starts. pub fn stop_signal( sender: Sender, ) -> std::io::Result + Send> { // Register before V8 starts, so a stop cannot arrive before the receiver. let mut terminate = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?; let mut interrupt = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())?; Ok(async move { tokio::select! { _ = interrupt.recv() => {} _ = terminate.recv() => {} } let _ = sender.send(Input::Shutdown); }) }