1use crate::rpc::{RpcIncoming, RpcMessage, RpcNotification, RpcRequest, RpcResponse};
7use async_trait::async_trait;
8use serde_json::Value;
9use std::collections::HashMap;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicU64, Ordering};
12use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
13use tokio::sync::{Mutex, mpsc, oneshot};
14
15#[async_trait]
22pub trait Extension: Send + Sync {
23 fn name(&self) -> &str;
25
26 fn version(&self) -> &str;
28
29 async fn handle_rpc(&self, method: &str, params: Value) -> crate::Result<Value>;
40
41 async fn handle_notification(&self, _method: &str, _params: Value) {
46 }
48
49 async fn on_initialized(&self, _host: HostProxy) {
54 }
56
57 async fn on_config(&self, _config: std::collections::HashMap<String, serde_json::Value>) {
64 }
66}
67
68#[derive(Clone)]
75pub struct HostProxy {
76 writer_tx: mpsc::Sender<String>,
77 pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>>,
78 next_id: Arc<AtomicU64>,
79}
80
81impl HostProxy {
82 pub(crate) fn new(
83 writer_tx: mpsc::Sender<String>,
84 pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>>,
85 ) -> Self {
86 Self {
87 writer_tx,
88 pending,
89 next_id: Arc::new(AtomicU64::new(1)),
90 }
91 }
92
93 pub async fn notify(&self, method: &str, params: Value) -> crate::Result<()> {
95 let notif = RpcNotification::new(method, params);
96 let json = serde_json::to_string(¬if)?;
97 self.writer_tx
98 .send(json)
99 .await
100 .map_err(|_| crate::Error::internal_error("host connection closed"))?;
101 Ok(())
102 }
103
104 pub async fn request(&self, method: &str, params: Value) -> crate::Result<Value> {
110 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
111 let id_str = format!("ext-{}", id);
112
113 let (tx, rx) = oneshot::channel();
114 self.pending.lock().await.insert(id_str.clone(), tx);
115
116 let request = serde_json::json!({
117 "jsonrpc": "2.0",
118 "id": id_str,
119 "method": method,
120 "params": params,
121 });
122 let json = serde_json::to_string(&request)?;
123 self.writer_tx
124 .send(json)
125 .await
126 .map_err(|_| crate::Error::internal_error("host connection closed"))?;
127
128 match tokio::time::timeout(std::time::Duration::from_secs(30), rx).await {
129 Ok(Ok(response)) => response.into_result(),
130 Ok(Err(_)) => {
131 self.pending.lock().await.remove(&id_str);
132 Err(crate::Error::internal_error(format!(
133 "host dropped response channel for {method}"
134 )))
135 }
136 Err(_) => {
137 self.pending.lock().await.remove(&id_str);
138 Err(crate::Error::new(
139 crate::ErrorCode::Timeout,
140 format!("ext→host request '{method}' timed out after 30s"),
141 ))
142 }
143 }
144 }
145}
146
147pub(crate) struct MessageRouter<E: Extension> {
156 ext: Arc<E>,
157}
158
159impl<E: Extension + 'static> MessageRouter<E> {
160 pub(crate) fn new(ext: E) -> Self {
161 Self { ext: Arc::new(ext) }
162 }
163
164 pub(crate) async fn run(self) -> crate::Result<()> {
166 let stdin = tokio::io::stdin();
167 let stdout = tokio::io::stdout();
168
169 let reader = tokio::io::BufReader::new(stdin);
170 let mut writer = tokio::io::BufWriter::new(stdout);
171
172 let (writer_tx, mut writer_rx) = mpsc::channel::<String>(256);
174
175 let pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>> =
177 Arc::new(Mutex::new(HashMap::new()));
178
179 let host = HostProxy::new(writer_tx.clone(), pending.clone());
181
182 let ext_init = self.ext.clone();
185 let host_init = host.clone();
186 tokio::spawn(async move {
187 ext_init.on_initialized(host_init).await;
188 });
189
190 let writer_handle = tokio::spawn(async move {
192 while let Some(msg) = writer_rx.recv().await {
193 if writer.write_all(msg.as_bytes()).await.is_err() {
194 break;
195 }
196 if writer.write_all(b"\n").await.is_err() {
197 break;
198 }
199 if writer.flush().await.is_err() {
200 break;
201 }
202 }
203 });
204
205 self.read_loop(reader, writer_tx.clone(), pending).await?;
207
208 drop(writer_tx);
211 let _ = tokio::time::timeout(std::time::Duration::from_secs(2), writer_handle).await;
213 Ok(())
214 }
215
216 async fn read_loop(
217 &self,
218 mut reader: tokio::io::BufReader<tokio::io::Stdin>,
219 writer_tx: mpsc::Sender<String>,
220 pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>>,
221 ) -> crate::Result<()> {
222 let mut buf = Vec::with_capacity(4096);
223
224 loop {
225 buf.clear();
226 let line = match read_bounded_line(&mut reader, &mut buf).await {
227 Ok(Some(line)) => line,
228 Ok(None) => return Ok(()), Err(msg) => {
230 let error_response =
231 RpcResponse::error(None, crate::ErrorCode::ParseError, msg);
232 let json = serde_json::to_string(&error_response)?;
233 let _ = writer_tx.send(json).await;
234 continue;
235 }
236 };
237
238 let trimmed = line.trim();
239 if trimmed.is_empty() {
240 continue;
241 }
242
243 match RpcIncoming::parse(trimmed) {
245 Ok(RpcIncoming::Request(req)) => {
246 let ext = self.ext.clone();
248 let tx = writer_tx.clone();
249 tokio::spawn(async move {
250 let response = dispatch_request(&*ext, &req).await;
251 let json = serde_json::to_string(&response).unwrap_or_default();
252 let _ = tx.send(json).await;
253 });
254 }
255 Ok(RpcIncoming::Response(resp)) => {
256 if let Some(id) = resp.id.as_ref().and_then(|v| v.as_str()) {
258 if let Some(tx) = pending.lock().await.remove(id) {
259 let _ = tx.send(resp);
260 }
261 }
262 }
263 Ok(RpcIncoming::Notification(notif)) => {
264 let ext = self.ext.clone();
266 tokio::spawn(async move {
267 ext.handle_notification(¬if.method, notif.params).await;
268 });
269 }
270 Err(e) => {
271 let error_response =
273 RpcResponse::error(None, crate::ErrorCode::ParseError, e.to_string());
274 let json = serde_json::to_string(&error_response)?;
275 let _ = writer_tx.send(json).await;
276 }
277 }
278 }
279 }
280}
281
282const MAX_LINE_SIZE: usize = 16 * 1024 * 1024;
286
287async fn read_bounded_line<'a, R: tokio::io::AsyncBufRead + Unpin>(
298 reader: &mut R,
299 buf: &'a mut Vec<u8>,
300) -> Result<Option<&'a str>, String> {
301 buf.clear();
302
303 loop {
304 let available = reader.fill_buf().await.map_err(|e| e.to_string())?;
305
306 if available.is_empty() {
307 return if buf.is_empty() {
309 Ok(None)
310 } else {
311 let line = std::str::from_utf8(buf).map_err(|e| format!("invalid UTF-8: {e}"))?;
313 Ok(Some(line))
314 };
315 }
316
317 let (chunk, found_newline) = match available.iter().position(|&b| b == b'\n') {
319 Some(pos) => (&available[..=pos], true),
320 None => (available, false),
321 };
322
323 if buf.len() + chunk.len() > MAX_LINE_SIZE {
325 let chunk_len = chunk.len();
327 reader.consume(chunk_len);
328 if !found_newline {
329 let mut drain = Vec::new();
331 let _ = reader.read_until(b'\n', &mut drain).await;
332 }
333 return Err(format!(
334 "message too large (>{} bytes, limit {})",
335 MAX_LINE_SIZE, MAX_LINE_SIZE,
336 ));
337 }
338
339 buf.extend_from_slice(chunk);
340 let chunk_len = chunk.len();
341 reader.consume(chunk_len);
342
343 if found_newline {
344 let line = std::str::from_utf8(buf).map_err(|e| format!("invalid UTF-8: {e}"))?;
345 return Ok(Some(line));
346 }
347 }
348}
349
350async fn dispatch_request<E: Extension>(ext: &E, req: &RpcRequest) -> RpcResponse {
352 let result = ext.handle_rpc(&req.method, req.params.clone()).await;
353
354 match result {
355 Ok(value) => RpcResponse::success(req.id.clone(), value),
356 Err(e) => RpcResponse::error(req.id.clone(), e.code(), e.message()),
357 }
358}
359
360pub(crate) struct ExtensionServe<E: Extension> {
368 ext: E,
369}
370
371impl<E: Extension> ExtensionServe<E> {
372 pub(crate) fn new(ext: E) -> Self {
373 Self { ext }
374 }
375
376 pub(crate) async fn run(self) -> crate::Result<()> {
378 let stdin = tokio::io::stdin();
379 let stdout = tokio::io::stdout();
380
381 let mut reader = tokio::io::BufReader::new(stdin);
382 let mut writer = tokio::io::BufWriter::new(stdout);
383
384 let mut buf = Vec::with_capacity(4096);
385
386 loop {
387 buf.clear();
388 let line = match read_bounded_line(&mut reader, &mut buf).await {
389 Ok(Some(line)) => line,
390 Ok(None) => return Ok(()), Err(msg) => {
392 let error_response =
393 RpcResponse::error(None, crate::ErrorCode::ParseError, msg);
394 let response_json = serde_json::to_string(&error_response)?;
395 writer.write_all(response_json.as_bytes()).await?;
396 writer.write_all(b"\n").await?;
397 writer.flush().await?;
398 continue;
399 }
400 };
401
402 let msg: RpcMessage = match serde_json::from_str(line.trim()) {
404 Ok(msg) => msg,
405 Err(e) => {
406 let error_response =
407 RpcResponse::error(None, crate::ErrorCode::ParseError, e.to_string());
408 let response_json = serde_json::to_string(&error_response)?;
409 writer.write_all(response_json.as_bytes()).await?;
410 writer.write_all(b"\n").await?;
411 writer.flush().await?;
412 continue;
413 }
414 };
415
416 match msg {
417 RpcMessage::Request(req) => {
418 let response = self.handle_request(&req).await;
419 let response_json = serde_json::to_string(&response)?;
420 writer.write_all(response_json.as_bytes()).await?;
421 writer.write_all(b"\n").await?;
422 writer.flush().await?;
423 }
424 RpcMessage::Notification(_notif) => {
425 }
427 }
428 }
429 }
430
431 async fn handle_request(&self, req: &RpcRequest) -> RpcResponse {
432 let result = self.ext.handle_rpc(&req.method, req.params.clone()).await;
433
434 match result {
435 Ok(value) => RpcResponse::success(req.id.clone(), value),
436 Err(e) => RpcResponse::error(req.id.clone(), e.code(), e.message()),
437 }
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444
445 #[derive(Default)]
446 struct TestExtension;
447
448 #[async_trait]
449 impl Extension for TestExtension {
450 fn name(&self) -> &str {
451 "test-extension"
452 }
453
454 fn version(&self) -> &str {
455 "0.1.0"
456 }
457
458 async fn handle_rpc(&self, method: &str, _params: Value) -> crate::Result<Value> {
459 match method {
460 "echo" => Ok(serde_json::json!({"status": "ok"})),
461 "get_tools" => Ok(serde_json::json!([])),
462 _ => Err(crate::Error::method_not_found(method)),
463 }
464 }
465 }
466
467 #[tokio::test]
468 async fn test_extension_dispatch() {
469 let ext = TestExtension;
470
471 let req = RpcRequest {
472 jsonrpc: "2.0".to_string(),
473 id: Some(serde_json::Value::String("1".to_string())),
474 method: "echo".to_string(),
475 params: serde_json::json!({}),
476 };
477
478 let response = dispatch_request(&ext, &req).await;
479
480 assert_eq!(
481 response.id,
482 Some(serde_json::Value::String("1".to_string()))
483 );
484 assert!(response.result.is_some());
485 }
486
487 #[tokio::test]
488 async fn test_unknown_method() {
489 let ext = TestExtension;
490
491 let req = RpcRequest {
492 jsonrpc: "2.0".to_string(),
493 id: Some(serde_json::Value::String("1".to_string())),
494 method: "unknown".to_string(),
495 params: serde_json::json!({}),
496 };
497
498 let response = dispatch_request(&ext, &req).await;
499
500 assert_eq!(
501 response.id,
502 Some(serde_json::Value::String("1".to_string()))
503 );
504 assert!(response.error.is_some());
505 let error = response.error.unwrap();
506 assert_eq!(error.code, -32601);
507 assert_eq!(error.label, "MethodNotFound");
508 }
509
510 #[tokio::test]
511 async fn test_host_proxy_notification() {
512 let (tx, mut rx) = mpsc::channel(16);
513 let pending = Arc::new(Mutex::new(HashMap::new()));
514 let proxy = HostProxy::new(tx, pending);
515
516 proxy
517 .notify("notifications/tools/list_changed", serde_json::json!({}))
518 .await
519 .unwrap();
520
521 let msg = rx.recv().await.unwrap();
522 let parsed: serde_json::Value = serde_json::from_str(&msg).unwrap();
523 assert_eq!(parsed["method"], "notifications/tools/list_changed");
524 assert!(parsed.get("id").is_none());
525 }
526
527 #[tokio::test]
528 async fn test_host_proxy_request_response() {
529 let (tx, mut rx) = mpsc::channel(16);
530 let pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>> =
531 Arc::new(Mutex::new(HashMap::new()));
532 let proxy = HostProxy::new(tx, pending.clone());
533
534 let proxy_clone = proxy.clone();
536 let handle = tokio::spawn(async move {
537 proxy_clone
538 .request("sampling/create_message", serde_json::json!({"test": true}))
539 .await
540 });
541
542 let msg = rx.recv().await.unwrap();
544 let parsed: serde_json::Value = serde_json::from_str(&msg).unwrap();
545 let id = parsed["id"].as_str().unwrap().to_string();
546 assert!(id.starts_with("ext-"));
547 assert_eq!(parsed["method"], "sampling/create_message");
548
549 let response = RpcResponse::success(
551 Some(Value::String(id.clone())),
552 serde_json::json!({"role": "assistant", "content": "hello"}),
553 );
554 if let Some(tx) = pending.lock().await.remove(&id) {
555 tx.send(response).unwrap();
556 }
557
558 let result = handle.await.unwrap().unwrap();
560 assert_eq!(result["role"], "assistant");
561 }
562
563 #[tokio::test]
566 async fn test_bounded_read_normal_line() {
567 let data = b"hello world\n";
568 let mut reader = tokio::io::BufReader::new(&data[..]);
569 let mut buf = Vec::new();
570
571 let result = read_bounded_line(&mut reader, &mut buf).await;
572 let line = result.unwrap().unwrap();
573 assert_eq!(line.trim(), "hello world");
574 }
575
576 #[tokio::test]
577 async fn test_bounded_read_eof() {
578 let data = b"";
579 let mut reader = tokio::io::BufReader::new(&data[..]);
580 let mut buf = Vec::new();
581
582 let result = read_bounded_line(&mut reader, &mut buf).await;
583 assert!(result.unwrap().is_none());
584 }
585
586 #[tokio::test]
587 async fn test_bounded_read_partial_line_at_eof() {
588 let data = b"no newline";
589 let mut reader = tokio::io::BufReader::new(&data[..]);
590 let mut buf = Vec::new();
591
592 let result = read_bounded_line(&mut reader, &mut buf).await;
593 let line = result.unwrap().unwrap();
594 assert_eq!(line, "no newline");
595 }
596
597 #[tokio::test]
598 async fn test_bounded_read_multiple_lines() {
599 let data = b"line one\nline two\n";
600 let mut reader = tokio::io::BufReader::new(&data[..]);
601 let mut buf = Vec::new();
602
603 let line1 = read_bounded_line(&mut reader, &mut buf)
604 .await
605 .unwrap()
606 .unwrap();
607 assert_eq!(line1.trim(), "line one");
608
609 buf.clear();
610 let line2 = read_bounded_line(&mut reader, &mut buf)
611 .await
612 .unwrap()
613 .unwrap();
614 assert_eq!(line2.trim(), "line two");
615 }
616
617 #[tokio::test]
618 async fn test_bounded_read_rejects_oversized() {
619 let oversized = vec![b'x'; MAX_LINE_SIZE + 100];
621 let mut data = oversized;
622 data.push(b'\n');
623 let mut reader = tokio::io::BufReader::new(&data[..]);
624 let mut buf = Vec::new();
625
626 let result = read_bounded_line(&mut reader, &mut buf).await;
627 assert!(result.is_err());
628 let msg = result.unwrap_err();
629 assert!(msg.contains("too large"));
630 }
631
632 #[tokio::test]
633 async fn test_bounded_read_exactly_at_limit() {
634 let mut data = vec![b'x'; MAX_LINE_SIZE - 1];
636 data.push(b'\n');
637 let mut reader = tokio::io::BufReader::new(&data[..]);
638 let mut buf = Vec::new();
639
640 let result = read_bounded_line(&mut reader, &mut buf).await;
641 assert!(result.is_ok());
642 let line = result.unwrap().unwrap();
643 assert_eq!(line.len(), MAX_LINE_SIZE);
644 }
645
646 #[tokio::test]
647 async fn test_bounded_read_empty_lines() {
648 let data = b"\n\nhello\n";
649 let mut reader = tokio::io::BufReader::new(&data[..]);
650 let mut buf = Vec::new();
651
652 let line1 = read_bounded_line(&mut reader, &mut buf)
654 .await
655 .unwrap()
656 .unwrap();
657 assert_eq!(line1.trim(), "");
658
659 buf.clear();
660 let line2 = read_bounded_line(&mut reader, &mut buf)
661 .await
662 .unwrap()
663 .unwrap();
664 assert_eq!(line2.trim(), "");
665
666 buf.clear();
667 let line3 = read_bounded_line(&mut reader, &mut buf)
668 .await
669 .unwrap()
670 .unwrap();
671 assert_eq!(line3.trim(), "hello");
672 }
673}