Skip to main content

oino_rpc/
server.rs

1//! [`RpcServer`]: the RPC protocol over any [`SessionBackend`].
2//!
3//! Each input line is handled as soon as it arrives (commands are not serialised, so
4//! `abort` reaches a running `compact`); a command whose work completes without waiting
5//! answers before the next line is read.
6//!
7//! One writer task owns the output and the backend's event stream. Before it writes a
8//! reply it drains every event already queued, so events a command caused precede its
9//! reply (events are written synchronously while the command runs). A `prompt` reply is
10//! written before that run's `agent_start` (`preflightResult` fires before the agent
11//! starts).
12
13use std::pin::Pin;
14use std::sync::atomic::{AtomicUsize, Ordering};
15use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
16use std::task::Poll;
17use std::time::Duration;
18
19use futures::stream::{BoxStream, FuturesUnordered};
20use futures::{Future, FutureExt, StreamExt};
21use oino_ai::JsonValue;
22use oino_client::{
23    AgentCommand, ClientError, OinoCommand, RpcCommand, RpcRequest, RpcResponse, SessionBackend,
24};
25use oino_core::events::{AgentSessionEvent, ClientEvent};
26use oino_core::ui::{UiAnswer, UiResponse};
27use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
28use tokio::sync::mpsc;
29
30use crate::jsonl::{LineReader, serialize_line};
31use crate::wire::{Connection, WireEvent, rpc_event};
32
33/// Reply queue depth.
34const REPLY_QUEUE: usize = 1024;
35/// How long queued events may still be written after shutdown.
36const FORWARD_GRACE: Duration = Duration::from_millis(500);
37/// Longest time an `agent_start` waits for its prompt's reply.
38const PREFLIGHT_HOLD: Duration = Duration::from_secs(1);
39
40/// Failure of the server loop.
41#[derive(Debug, thiserror::Error)]
42#[non_exhaustive]
43pub enum RpcServerError {
44    /// Reading commands failed.
45    #[error("reading commands: {0}")]
46    Read(#[source] std::io::Error),
47}
48
49/// A reply for the writer.
50struct Reply {
51    response: RpcResponse,
52    /// A `prompt` reply (releases a held `agent_start`).
53    prompt: bool,
54}
55
56struct Shared {
57    backend: Arc<dyn SessionBackend>,
58    replies: mpsc::Sender<Reply>,
59    /// `_oino/hello` and the `ui` capabilities it declared (§10).
60    connection: Arc<Mutex<Arc<Connection>>>,
61    /// Prompts whose reply is not written yet.
62    preflight: Arc<AtomicUsize>,
63}
64
65fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
66    mutex.lock().unwrap_or_else(PoisonError::into_inner)
67}
68
69impl Shared {
70    async fn reply(&self, response: RpcResponse, prompt: bool) {
71        let _ = self.replies.send(Reply { response, prompt }).await;
72    }
73
74    async fn handle_line(self: Arc<Self>, line: String) {
75        let value: JsonValue = match serde_json::from_str(&line) {
76            Ok(value) => value,
77            Err(error) => {
78                let message = format!("Failed to parse command: {error}");
79                self.reply(
80                    RpcResponse::error(None, Some("parse".to_owned()), message),
81                    false,
82                )
83                .await;
84                return;
85            }
86        };
87        if value.get("type").and_then(JsonValue::as_str) == Some("extension_ui_response") {
88            if let Ok(response) = serde_json::from_value::<UiResponse>(value) {
89                self.backend.ui_respond(response);
90            }
91            return;
92        }
93        let request = match RpcRequest::from_value(value) {
94            Ok(request) => request,
95            Err(rejected) => {
96                let response = RpcResponse::error(rejected.id, rejected.command, rejected.error);
97                self.reply(response, false).await;
98                return;
99            }
100        };
101        let kind = request.command.kind();
102        let prompt = matches!(
103            request.command,
104            RpcCommand::Agent(AgentCommand::Prompt { .. })
105        );
106        let hello = match &request.command {
107            RpcCommand::Oino(OinoCommand::Hello { ui, .. }) => Some(hello_connection(ui.as_ref())),
108            _ => None,
109        };
110        if prompt {
111            self.preflight.fetch_add(1, Ordering::AcqRel);
112        }
113        let request_id = request.id.as_ref().map(id_text);
114        let result = self.backend.send_with_id(request_id, request.command).await;
115        if let (Some(connection), true) = (hello, result.is_ok()) {
116            *lock(&self.connection) = Arc::new(connection);
117        }
118        let response = match result {
119            Ok(data) => RpcResponse::success(request.id, kind, data),
120            Err(ClientError::Command(message)) => {
121                RpcResponse::error(request.id, Some(kind.to_owned()), message)
122            }
123            Err(error) => RpcResponse::error(request.id, Some(kind.to_owned()), error.to_string()),
124        };
125        self.reply(response, prompt).await;
126    }
127}
128
129/// The connection `_oino/hello` declares: v1 without `ui`, v2 with parseable capabilities
130/// whose own `protocol` is 2 (§10).
131fn hello_connection(ui: Option<&JsonValue>) -> Connection {
132    match ui.and_then(|value| {
133        serde_json::from_value::<oino_ui::slot::UiCapabilities>(value.clone()).ok()
134    }) {
135        Some(capabilities) if capabilities.protocol >= 2 => Connection::v2(capabilities),
136        _ => Connection::v1(),
137    }
138}
139
140/// The text of a JSON id as passed to `executeBash` (`bash_execution_update.id`).
141fn id_text(id: &JsonValue) -> String {
142    match id {
143        JsonValue::String(text) => text.clone(),
144        other => other.to_string(),
145    }
146}
147
148/// Owns the output: events and replies in protocol order.
149struct Writer<W> {
150    output: W,
151    events: BoxStream<'static, ClientEvent>,
152    backend: Arc<dyn SessionBackend>,
153    connection: Arc<Mutex<Arc<Connection>>>,
154    preflight: Arc<AtomicUsize>,
155    /// `agent_start` waiting for its prompt's reply.
156    held: Option<ClientEvent>,
157    broken: bool,
158}
159
160fn is_agent_start(event: &ClientEvent) -> bool {
161    matches!(event, ClientEvent::Session(AgentSessionEvent::AgentStart))
162}
163
164impl<W: AsyncWrite + Unpin> Writer<W> {
165    async fn line(&mut self, value: &impl serde::Serialize) {
166        if self.broken {
167            return;
168        }
169        let line = serialize_line(value);
170        if self.output.write_all(line.as_bytes()).await.is_err()
171            || self.output.flush().await.is_err()
172        {
173            self.broken = true;
174        }
175    }
176
177    async fn event(&mut self, event: &ClientEvent) {
178        let connection = Arc::clone(&lock(&self.connection));
179        match rpc_event(event, &connection) {
180            WireEvent::Line(value) => self.line(&value).await,
181            WireEvent::Drop => {}
182            WireEvent::CancelDialog(id) => self.backend.ui_respond(UiResponse {
183                id,
184                answer: UiAnswer::Cancelled,
185            }),
186        }
187    }
188
189    /// Writes queued events; for a prompt reply stops at (and holds) `agent_start`.
190    async fn drain(&mut self, prompt: bool) {
191        while self.held.is_none() {
192            let Some(Some(event)) = self.events.next().now_or_never() else {
193                return;
194            };
195            if prompt && is_agent_start(&event) {
196                self.held = Some(event);
197                return;
198            }
199            self.event(&event).await;
200        }
201    }
202
203    async fn reply(&mut self, reply: Reply) {
204        self.drain(reply.prompt).await;
205        self.line(&reply.response).await;
206        if reply.prompt {
207            self.preflight.fetch_sub(1, Ordering::AcqRel);
208            self.release().await;
209        }
210    }
211
212    async fn release(&mut self) {
213        if let Some(event) = self.held.take() {
214            self.event(&event).await;
215        }
216    }
217
218    async fn run(mut self, mut replies: mpsc::Receiver<Reply>) {
219        let mut events_open = true;
220        loop {
221            let holding = self.held.is_some();
222            tokio::select! {
223                reply = replies.recv() => match reply {
224                    Some(reply) => self.reply(reply).await,
225                    None => break,
226                },
227                event = self.events.next(), if events_open && !holding => match event {
228                    Some(event) if is_agent_start(&event)
229                        && self.preflight.load(Ordering::Acquire) > 0 =>
230                    {
231                        self.held = Some(event);
232                    }
233                    Some(event) => self.event(&event).await,
234                    None => events_open = false,
235                },
236                () = tokio::time::sleep(PREFLIGHT_HOLD), if holding => self.release().await,
237            }
238        }
239        self.release().await;
240        if events_open {
241            let rest = async {
242                while let Some(event) = self.events.next().await {
243                    self.event(&event).await;
244                }
245            };
246            let _ = tokio::time::timeout(FORWARD_GRACE, rest).await;
247        }
248        let _ = self.output.flush().await;
249    }
250}
251
252/// RPC mode over a backend.
253pub struct RpcServer {
254    backend: Arc<dyn SessionBackend>,
255}
256
257impl std::fmt::Debug for RpcServer {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        f.debug_struct("RpcServer").finish_non_exhaustive()
260    }
261}
262
263type Pending = Pin<Box<dyn Future<Output = ()> + Send>>;
264
265impl RpcServer {
266    /// A server for `backend`.
267    #[must_use]
268    pub fn new(backend: Arc<dyn SessionBackend>) -> Self {
269        Self { backend }
270    }
271
272    /// Serves commands from `input` and writes replies/events to `output` until `input`
273    /// ends or an extension requested shutdown and the session is idle, then shuts the
274    /// backend down (stdin end ⇒ orderly shutdown, exit 0).
275    ///
276    /// # Errors
277    ///
278    /// Reading `input` failed (the backend is still shut down).
279    pub async fn run<R, W>(self, input: R, output: W) -> Result<(), RpcServerError>
280    where
281        R: AsyncRead + Unpin,
282        W: AsyncWrite + Unpin + Send + 'static,
283    {
284        let (replies, replies_rx) = mpsc::channel::<Reply>(REPLY_QUEUE);
285        let connection = Arc::new(Mutex::new(Arc::new(Connection::plain())));
286        let preflight = Arc::new(AtomicUsize::new(0));
287        let writer = Writer {
288            output,
289            events: self.backend.events(),
290            backend: Arc::clone(&self.backend),
291            connection: Arc::clone(&connection),
292            preflight: Arc::clone(&preflight),
293            held: None,
294            broken: false,
295        };
296        let writer = tokio::spawn(writer.run(replies_rx));
297        let shared = Arc::new(Shared {
298            backend: Arc::clone(&self.backend),
299            replies,
300            connection,
301            preflight,
302        });
303        let mut reader = LineReader::new(input);
304        let mut pending: FuturesUnordered<Pending> = FuturesUnordered::new();
305        let mut shutdown = self.backend.shutdown_requested();
306        let result = loop {
307            tokio::select! {
308                () = &mut shutdown => break Ok(()),
309                line = reader.next_line() => match line {
310                    Ok(Some(line)) => {
311                        let mut handler: Pending = Box::pin(Arc::clone(&shared).handle_line(line));
312                        if futures::poll!(handler.as_mut()) == Poll::Pending {
313                            pending.push(handler);
314                        }
315                    }
316                    Ok(None) => break Ok(()),
317                    Err(error) => break Err(RpcServerError::Read(error)),
318                },
319                Some(()) = pending.next(), if !pending.is_empty() => {}
320            }
321        };
322        drop(pending);
323        self.backend.shutdown().await;
324        drop(shared);
325        let _ = writer.await;
326        result
327    }
328}