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