Skip to main content

zerolaunch_plugin_host/
client.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::sync::Arc;
3use std::time::Duration;
4
5use dashmap::DashMap;
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use tokio::io::{AsyncBufRead, AsyncWrite};
9use tokio::sync::{mpsc, oneshot};
10use tracing::{debug, error, warn};
11
12use zerolaunch_plugin_protocol::{codes, JsonRpcError, Message, ProtocolError, Request, Response};
13
14use crate::transport::codec;
15
16/// Incoming request from a plugin subprocess.
17///
18/// Responses are sent via `JsonRpcClient::respond_ok` / `respond_err`,
19/// which write through the outbound channel.
20#[derive(Debug)]
21pub struct IncomingRequest {
22    pub id: u64,
23    pub method: String,
24    pub params: serde_json::Value,
25}
26
27/// Bidirectional JSON-RPC 2.0 client over a framed stdio transport.
28#[derive(Debug)]
29pub struct JsonRpcClient {
30    next_id: AtomicU64,
31    pending: Arc<DashMap<u64, oneshot::Sender<Result<serde_json::Value, JsonRpcError>>>>,
32    outbound_tx: mpsc::Sender<Message>,
33    /// Handle to the read-loop task.
34    _read_handle: tokio::task::JoinHandle<()>,
35    /// Handle to the write-loop task.
36    _write_handle: tokio::task::JoinHandle<()>,
37}
38
39impl JsonRpcClient {
40    /// Creates a new client, spawning background read/write loops.
41    ///
42    /// - `reader` / `writer`: the stdio handles from the child process.
43    /// - `incoming_request_tx`: channel for forwarding plugin→host requests.
44    /// - `incoming_notification_tx`: channel for forwarding plugin→host notifications.
45    /// - Returns an `Arc<JsonRpcClient>` so it can be shared with host_dispatch.
46    pub fn new<R, W>(
47        reader: R,
48        writer: W,
49        incoming_request_tx: mpsc::Sender<IncomingRequest>,
50        incoming_notification_tx: mpsc::Sender<(String, serde_json::Value)>,
51    ) -> Arc<Self>
52    where
53        R: AsyncBufRead + Send + Unpin + 'static,
54        W: AsyncWrite + Send + Unpin + 'static,
55    {
56        let pending: Arc<DashMap<u64, oneshot::Sender<Result<serde_json::Value, JsonRpcError>>>> =
57            Arc::new(DashMap::new());
58
59        // 串行写入:防止多线程并发写入导致 JSON-RPC 帧交错
60        let (outbound_tx, outbound_rx) = mpsc::channel::<Message>(256);
61
62        // Read loop
63        let pending_r = pending.clone();
64        let read_handle = tokio::spawn(async move {
65            let mut reader = reader;
66            loop {
67                let frame = match codec::read_frame(&mut reader).await {
68                    Ok(f) => f,
69                    Err(ProtocolError::TransportClosed) => {
70                        debug!("Transport closed (read loop)");
71                        break;
72                    }
73                    Err(e) => {
74                        error!("Frame read error: {}", e);
75                        break;
76                    }
77                };
78
79                debug!(
80                    "收到插件原始帧 ({} bytes): {:?}",
81                    frame.len(),
82                    String::from_utf8_lossy(&frame)
83                );
84
85                let msg: Message = match serde_json::from_slice(&frame) {
86                    Ok(m) => m,
87                    Err(e) => {
88                        warn!(
89                            "Failed to parse JSON-RPC message: {}; raw frame ({} bytes): {:?}",
90                            e,
91                            frame.len(),
92                            String::from_utf8_lossy(&frame)
93                        );
94                        continue;
95                    }
96                };
97                debug!(
98                    "收到插件消息 ({} bytes): {:?}",
99                    frame.len(),
100                    zerolaunch_plugin_protocol::codec::summarize_message(&msg)
101                );
102
103                match msg {
104                    Message::Response(resp) => {
105                        if let Some((_, tx)) = pending_r.remove(&resp.id) {
106                            let result = if let Some(err) = resp.error {
107                                Err(err)
108                            } else {
109                                Ok(resp.result.unwrap_or(serde_json::Value::Null))
110                            };
111                            let _ = tx.send(result);
112                        } else {
113                            // 正常不应该走到这
114                            debug!("Response for unknown id: {}", resp.id);
115                        }
116                    }
117                    Message::Request(req) => {
118                        let incoming = IncomingRequest {
119                            id: req.id,
120                            method: req.method,
121                            params: req.params,
122                        };
123                        if incoming_request_tx.send(incoming).await.is_err() {
124                            debug!("Incoming request channel closed");
125                        }
126                    }
127                    Message::Notification(notif) => {
128                        if incoming_notification_tx
129                            .send((notif.method, notif.params))
130                            .await
131                            .is_err()
132                        {
133                            debug!("Incoming notification channel closed");
134                        }
135                    }
136                }
137            }
138
139            // Clean up all pending requests on transport close
140            let keys: Vec<u64> = pending_r.iter().map(|e| *e.key()).collect();
141            for key in keys {
142                if let Some((_, tx)) = pending_r.remove(&key) {
143                    let _ = tx.send(Err(JsonRpcError::new(
144                        codes::PLUGIN_CRASHED,
145                        "transport closed",
146                    )));
147                }
148            }
149        });
150
151        // Write loop
152        let write_handle = tokio::spawn(async move {
153            let mut writer = writer;
154            let mut rx = outbound_rx;
155            while let Some(msg) = rx.recv().await {
156                debug!(
157                    "发送给插件的消息: {:?}",
158                    zerolaunch_plugin_protocol::codec::summarize_message(&msg)
159                );
160                let payload = match serde_json::to_vec(&msg) {
161                    Ok(p) => p,
162                    Err(e) => {
163                        error!("Failed to serialize message: {}", e);
164                        continue;
165                    }
166                };
167                if let Err(e) = codec::write_frame(&mut writer, &payload).await {
168                    error!("Failed to write frame: {}", e);
169                    break;
170                }
171            }
172        });
173
174        Arc::new(Self {
175            next_id: AtomicU64::new(1),
176            pending,
177            outbound_tx,
178            _read_handle: read_handle,
179            _write_handle: write_handle,
180        })
181    }
182
183    /// 关闭传输:中止写循环,释放子进程 stdin 句柄(EOF 是 SDK 进程的退出信号)。
184    /// 由 shutdown 调用,此后不再发送任何消息。
185    pub fn close_transport(&self) {
186        self._write_handle.abort();
187    }
188
189    /// Send a request and wait for the response.
190    pub async fn call<P: Serialize, R: DeserializeOwned>(
191        &self,
192        method: &str,
193        params: P,
194        timeout: Duration,
195    ) -> Result<R, ProtocolError> {
196        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
197        let params_value = serde_json::to_value(params)?;
198
199        let (tx, rx) = oneshot::channel();
200        self.pending.insert(id, tx);
201
202        let request = Request::new(id, method, params_value);
203        // send 超时防护:插件停读 stdin 后管道填满,send().await 永久挂起
204        //(既定 RPC 超时形同虚设)。5s 后按传输关闭处理。
205        tokio::time::timeout(
206            Duration::from_secs(5),
207            self.outbound_tx.send(Message::Request(request)),
208        )
209        .await
210        .map_err(|_| ProtocolError::TransportClosed)?
211        .map_err(|_| ProtocolError::TransportClosed)?;
212
213        match tokio::time::timeout(timeout, rx).await {
214            Ok(Ok(Ok(value))) => {
215                let result: R = serde_json::from_value(value)?;
216                Ok(result)
217            }
218            Ok(Ok(Err(err))) => Err(ProtocolError::Rpc {
219                code: err.code,
220                message: err.message,
221            }),
222            Ok(Err(_)) => {
223                self.pending.remove(&id);
224                Err(ProtocolError::TransportClosed)
225            }
226            Err(_) => {
227                self.pending.remove(&id);
228                Err(ProtocolError::Timeout)
229            }
230        }
231    }
232
233    /// Send a notification (fire-and-forget, no response expected).
234    pub async fn notify<P: Serialize>(&self, method: &str, params: P) -> Result<(), ProtocolError> {
235        let params_value = serde_json::to_value(params)?;
236        let notif = zerolaunch_plugin_protocol::Notification::new(method, params_value);
237        tokio::time::timeout(
238            Duration::from_secs(5),
239            self.outbound_tx.send(Message::Notification(notif)),
240        )
241        .await
242        .map_err(|_| ProtocolError::TransportClosed)?
243        .map_err(|_| ProtocolError::TransportClosed)?;
244        Ok(())
245    }
246
247    /// Send a success response to a previously received request.
248    pub async fn respond_ok(
249        &self,
250        id: u64,
251        result: serde_json::Value,
252    ) -> Result<(), ProtocolError> {
253        let response = Response::ok(id, result);
254        tokio::time::timeout(
255            Duration::from_secs(5),
256            self.outbound_tx.send(Message::Response(response)),
257        )
258        .await
259        .map_err(|_| ProtocolError::TransportClosed)?
260        .map_err(|_| ProtocolError::TransportClosed)?;
261        Ok(())
262    }
263
264    /// Send an error response to a previously received request.
265    pub async fn respond_err(&self, id: u64, error: JsonRpcError) -> Result<(), ProtocolError> {
266        let response = Response::err(id, error);
267        tokio::time::timeout(
268            Duration::from_secs(5),
269            self.outbound_tx.send(Message::Response(response)),
270        )
271        .await
272        .map_err(|_| ProtocolError::TransportClosed)?
273        .map_err(|_| ProtocolError::TransportClosed)?;
274        Ok(())
275    }
276}