1use crate::rpc::{RpcIncoming, RpcMessage, RpcNotification, RpcRequest, RpcResponse};
7use crate::{JSONRPC_VERSION, METHOD_ACTIONS_EXECUTE};
8use async_trait::async_trait;
9use serde_json::Value;
10use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
14use tokio::sync::{Mutex, mpsc, oneshot};
15
16#[async_trait]
23pub trait Extension: Send + Sync {
24 fn name(&self) -> &str;
26
27 fn version(&self) -> &str;
29
30 async fn handle_rpc(&self, method: &str, params: Value) -> crate::Result<Value>;
41
42 async fn handle_notification(&self, _method: &str, _params: Value) {
47 }
49
50 async fn on_initialized(&self, _host: HostProxy) {
55 }
57
58 async fn on_config(&self, _config: std::collections::HashMap<String, serde_json::Value>) {
65 }
67}
68
69#[derive(Clone)]
76pub struct HostProxy {
77 writer_tx: mpsc::Sender<String>,
78 pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>>,
79 next_id: Arc<AtomicU64>,
80}
81
82impl HostProxy {
83 pub(crate) fn new(
84 writer_tx: mpsc::Sender<String>,
85 pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>>,
86 ) -> Self {
87 Self {
88 writer_tx,
89 pending,
90 next_id: Arc::new(AtomicU64::new(1)),
91 }
92 }
93
94 pub async fn notify(&self, method: &str, params: Value) -> crate::Result<()> {
96 let notif = RpcNotification::new(method, params);
97 let json = serde_json::to_string(¬if)?;
98 self.writer_tx
99 .send(json)
100 .await
101 .map_err(|_| crate::Error::internal_error("host connection closed"))?;
102 Ok(())
103 }
104
105 pub async fn request(&self, method: &str, params: Value) -> crate::Result<Value> {
111 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
112 let id_str = format!("ext-{}", id);
113
114 let (tx, rx) = oneshot::channel();
115 self.pending.lock().await.insert(id_str.clone(), tx);
116
117 let request = serde_json::json!({
118 "jsonrpc": JSONRPC_VERSION,
119 "id": id_str,
120 "method": method,
121 "params": params,
122 });
123 let json = serde_json::to_string(&request)?;
124 self.writer_tx
125 .send(json)
126 .await
127 .map_err(|_| crate::Error::internal_error("host connection closed"))?;
128
129 match tokio::time::timeout(std::time::Duration::from_secs(30), rx).await {
130 Ok(Ok(response)) => response.into_result(),
131 Ok(Err(_)) => {
132 self.pending.lock().await.remove(&id_str);
133 Err(crate::Error::internal_error(format!(
134 "host dropped response channel for {method}"
135 )))
136 }
137 Err(_) => {
138 self.pending.lock().await.remove(&id_str);
139 Err(crate::Error::new(
140 crate::ErrorCode::Timeout,
141 format!("ext→host request '{method}' timed out after 30s"),
142 ))
143 }
144 }
145 }
146
147 pub async fn execute_action(
152 &self,
153 action: crate::HostAction,
154 ) -> crate::Result<crate::HostActionOutcome> {
155 let value = self
156 .request(
157 METHOD_ACTIONS_EXECUTE,
158 serde_json::json!({ "action": action }),
159 )
160 .await?;
161 serde_json::from_value(value).map_err(|err| crate::Error::invalid_params(err.to_string()))
162 }
163}
164
165pub(crate) struct MessageRouter<E: Extension> {
174 ext: Arc<E>,
175}
176
177impl<E: Extension + 'static> MessageRouter<E> {
178 pub(crate) fn new(ext: E) -> Self {
179 Self { ext: Arc::new(ext) }
180 }
181
182 pub(crate) async fn run(self) -> crate::Result<()> {
184 let stdin = tokio::io::stdin();
185 let stdout = tokio::io::stdout();
186
187 let reader = tokio::io::BufReader::new(stdin);
188 let mut writer = tokio::io::BufWriter::new(stdout);
189
190 let (writer_tx, mut writer_rx) = mpsc::channel::<String>(256);
192
193 let pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>> =
195 Arc::new(Mutex::new(HashMap::new()));
196
197 let host = HostProxy::new(writer_tx.clone(), pending.clone());
199
200 let ext_init = self.ext.clone();
203 let host_init = host.clone();
204 tokio::spawn(async move {
205 ext_init.on_initialized(host_init).await;
206 });
207
208 let writer_handle = tokio::spawn(async move {
210 while let Some(msg) = writer_rx.recv().await {
211 if writer.write_all(msg.as_bytes()).await.is_err() {
212 break;
213 }
214 if writer.write_all(b"\n").await.is_err() {
215 break;
216 }
217 if writer.flush().await.is_err() {
218 break;
219 }
220 }
221 });
222
223 self.read_loop(reader, writer_tx.clone(), pending).await?;
225
226 drop(writer_tx);
229 let _ = tokio::time::timeout(std::time::Duration::from_secs(2), writer_handle).await;
231 Ok(())
232 }
233
234 async fn read_loop(
235 &self,
236 mut reader: tokio::io::BufReader<tokio::io::Stdin>,
237 writer_tx: mpsc::Sender<String>,
238 pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>>,
239 ) -> crate::Result<()> {
240 let mut buf = Vec::with_capacity(4096);
241
242 loop {
243 buf.clear();
244 let line = match read_bounded_line(&mut reader, &mut buf).await {
245 Ok(Some(line)) => line,
246 Ok(None) => return Ok(()), Err(msg) => {
248 let error_response =
249 RpcResponse::error(None, crate::ErrorCode::ParseError, msg);
250 let json = serde_json::to_string(&error_response)?;
251 let _ = writer_tx.send(json).await;
252 continue;
253 }
254 };
255
256 let trimmed = line.trim();
257 if trimmed.is_empty() {
258 continue;
259 }
260
261 match RpcIncoming::parse(trimmed) {
263 Ok(RpcIncoming::Request(req)) => {
264 let ext = self.ext.clone();
266 let tx = writer_tx.clone();
267 tokio::spawn(async move {
268 let response = dispatch_request(&*ext, &req).await;
269 let json = serde_json::to_string(&response).unwrap_or_default();
270 let _ = tx.send(json).await;
271 });
272 }
273 Ok(RpcIncoming::Response(resp)) => {
274 if let Some(id) = resp.id.as_ref().and_then(|v| v.as_str()) {
276 if let Some(tx) = pending.lock().await.remove(id) {
277 let _ = tx.send(resp);
278 }
279 }
280 }
281 Ok(RpcIncoming::Notification(notif)) => {
282 let ext = self.ext.clone();
284 tokio::spawn(async move {
285 ext.handle_notification(¬if.method, notif.params).await;
286 });
287 }
288 Err(e) => {
289 let error_response =
291 RpcResponse::error(None, crate::ErrorCode::ParseError, e.to_string());
292 let json = serde_json::to_string(&error_response)?;
293 let _ = writer_tx.send(json).await;
294 }
295 }
296 }
297 }
298}
299
300const MAX_LINE_SIZE: usize = 16 * 1024 * 1024;
304
305async fn read_bounded_line<'a, R: tokio::io::AsyncBufRead + Unpin>(
316 reader: &mut R,
317 buf: &'a mut Vec<u8>,
318) -> Result<Option<&'a str>, String> {
319 buf.clear();
320
321 loop {
322 let available = reader.fill_buf().await.map_err(|e| e.to_string())?;
323
324 if available.is_empty() {
325 return if buf.is_empty() {
327 Ok(None)
328 } else {
329 let line = std::str::from_utf8(buf).map_err(|e| format!("invalid UTF-8: {e}"))?;
331 Ok(Some(line))
332 };
333 }
334
335 let (chunk, found_newline) = match available.iter().position(|&b| b == b'\n') {
337 Some(pos) => (&available[..=pos], true),
338 None => (available, false),
339 };
340
341 if buf.len() + chunk.len() > MAX_LINE_SIZE {
343 let chunk_len = chunk.len();
345 reader.consume(chunk_len);
346 if !found_newline {
347 let mut drain = Vec::new();
349 let _ = reader.read_until(b'\n', &mut drain).await;
350 }
351 return Err(format!(
352 "message too large (>{} bytes, limit {})",
353 MAX_LINE_SIZE, MAX_LINE_SIZE,
354 ));
355 }
356
357 buf.extend_from_slice(chunk);
358 let chunk_len = chunk.len();
359 reader.consume(chunk_len);
360
361 if found_newline {
362 let line = std::str::from_utf8(buf).map_err(|e| format!("invalid UTF-8: {e}"))?;
363 return Ok(Some(line));
364 }
365 }
366}
367
368async fn dispatch_request<E: Extension>(ext: &E, req: &RpcRequest) -> RpcResponse {
370 let result = ext.handle_rpc(&req.method, req.params.clone()).await;
371
372 match result {
373 Ok(value) => RpcResponse::success(req.id.clone(), value),
374 Err(e) => RpcResponse::error(req.id.clone(), e.code(), e.message()),
375 }
376}
377
378pub(crate) struct ExtensionServe<E: Extension> {
386 ext: E,
387}
388
389impl<E: Extension> ExtensionServe<E> {
390 pub(crate) fn new(ext: E) -> Self {
391 Self { ext }
392 }
393
394 pub(crate) async fn run(self) -> crate::Result<()> {
396 let stdin = tokio::io::stdin();
397 let stdout = tokio::io::stdout();
398
399 let mut reader = tokio::io::BufReader::new(stdin);
400 let mut writer = tokio::io::BufWriter::new(stdout);
401
402 let mut buf = Vec::with_capacity(4096);
403
404 loop {
405 buf.clear();
406 let line = match read_bounded_line(&mut reader, &mut buf).await {
407 Ok(Some(line)) => line,
408 Ok(None) => return Ok(()), Err(msg) => {
410 let error_response =
411 RpcResponse::error(None, crate::ErrorCode::ParseError, msg);
412 let response_json = serde_json::to_string(&error_response)?;
413 writer.write_all(response_json.as_bytes()).await?;
414 writer.write_all(b"\n").await?;
415 writer.flush().await?;
416 continue;
417 }
418 };
419
420 let msg: RpcMessage = match serde_json::from_str(line.trim()) {
422 Ok(msg) => msg,
423 Err(e) => {
424 let error_response =
425 RpcResponse::error(None, crate::ErrorCode::ParseError, e.to_string());
426 let response_json = serde_json::to_string(&error_response)?;
427 writer.write_all(response_json.as_bytes()).await?;
428 writer.write_all(b"\n").await?;
429 writer.flush().await?;
430 continue;
431 }
432 };
433
434 match msg {
435 RpcMessage::Request(req) => {
436 let response = self.handle_request(&req).await;
437 let response_json = serde_json::to_string(&response)?;
438 writer.write_all(response_json.as_bytes()).await?;
439 writer.write_all(b"\n").await?;
440 writer.flush().await?;
441 }
442 RpcMessage::Notification(_notif) => {
443 }
445 }
446 }
447 }
448
449 async fn handle_request(&self, req: &RpcRequest) -> RpcResponse {
450 let result = self.ext.handle_rpc(&req.method, req.params.clone()).await;
451
452 match result {
453 Ok(value) => RpcResponse::success(req.id.clone(), value),
454 Err(e) => RpcResponse::error(req.id.clone(), e.code(), e.message()),
455 }
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 #[derive(Default)]
464 struct TestExtension;
465
466 #[async_trait]
467 impl Extension for TestExtension {
468 fn name(&self) -> &str {
469 "test-extension"
470 }
471
472 fn version(&self) -> &str {
473 "0.1.0"
474 }
475
476 async fn handle_rpc(&self, method: &str, _params: Value) -> crate::Result<Value> {
477 match method {
478 "echo" => Ok(serde_json::json!({"status": "ok"})),
479 "get_tools" => Ok(serde_json::json!([])),
480 _ => Err(crate::Error::method_not_found(method)),
481 }
482 }
483 }
484
485 #[tokio::test]
486 async fn test_extension_dispatch() {
487 let ext = TestExtension;
488
489 let req = RpcRequest {
490 jsonrpc: "2.0".to_string(),
491 id: Some(serde_json::Value::String("1".to_string())),
492 method: "echo".to_string(),
493 params: serde_json::json!({}),
494 };
495
496 let response = dispatch_request(&ext, &req).await;
497
498 assert_eq!(
499 response.id,
500 Some(serde_json::Value::String("1".to_string()))
501 );
502 assert!(response.result.is_some());
503 }
504
505 #[tokio::test]
506 async fn test_unknown_method() {
507 let ext = TestExtension;
508
509 let req = RpcRequest {
510 jsonrpc: "2.0".to_string(),
511 id: Some(serde_json::Value::String("1".to_string())),
512 method: "unknown".to_string(),
513 params: serde_json::json!({}),
514 };
515
516 let response = dispatch_request(&ext, &req).await;
517
518 assert_eq!(
519 response.id,
520 Some(serde_json::Value::String("1".to_string()))
521 );
522 assert!(response.error.is_some());
523 let error = response.error.unwrap();
524 assert_eq!(error.code, -32601);
525 assert_eq!(error.label, "MethodNotFound");
526 }
527
528 #[tokio::test]
529 async fn test_host_proxy_notification() {
530 let (tx, mut rx) = mpsc::channel(16);
531 let pending = Arc::new(Mutex::new(HashMap::new()));
532 let proxy = HostProxy::new(tx, pending);
533
534 proxy
535 .notify("notifications/tools/list_changed", serde_json::json!({}))
536 .await
537 .unwrap();
538
539 let msg = rx.recv().await.unwrap();
540 let parsed: serde_json::Value = serde_json::from_str(&msg).unwrap();
541 assert_eq!(parsed["method"], "notifications/tools/list_changed");
542 assert!(parsed.get("id").is_none());
543 }
544
545 #[tokio::test]
546 async fn test_host_proxy_request_response() {
547 let (tx, mut rx) = mpsc::channel(16);
548 let pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>> =
549 Arc::new(Mutex::new(HashMap::new()));
550 let proxy = HostProxy::new(tx, pending.clone());
551
552 let proxy_clone = proxy.clone();
554 let handle = tokio::spawn(async move {
555 proxy_clone
556 .request("sampling/create_message", serde_json::json!({"test": true}))
557 .await
558 });
559
560 let msg = rx.recv().await.unwrap();
562 let parsed: serde_json::Value = serde_json::from_str(&msg).unwrap();
563 let id = parsed["id"].as_str().unwrap().to_string();
564 assert!(id.starts_with("ext-"));
565 assert_eq!(parsed["method"], "sampling/create_message");
566
567 let response = RpcResponse::success(
569 Some(Value::String(id.clone())),
570 serde_json::json!({"role": "assistant", "content": "hello"}),
571 );
572 if let Some(tx) = pending.lock().await.remove(&id) {
573 tx.send(response).unwrap();
574 }
575
576 let result = handle.await.unwrap().unwrap();
578 assert_eq!(result["role"], "assistant");
579 }
580
581 #[tokio::test]
582 async fn test_host_proxy_execute_action_request_response() {
583 let (tx, mut rx) = mpsc::channel(16);
584 let pending: Arc<Mutex<HashMap<String, oneshot::Sender<RpcResponse>>>> =
585 Arc::new(Mutex::new(HashMap::new()));
586 let proxy = HostProxy::new(tx, pending.clone());
587
588 let action = crate::HostAction::new(
589 "open-reader",
590 crate::actions::terminal::TERMINAL_CREATE_V1,
591 crate::actions::terminal::TerminalCreateParams::new("bookokrat"),
592 )
593 .unwrap();
594
595 let proxy_clone = proxy.clone();
596 let handle = tokio::spawn(async move { proxy_clone.execute_action(action).await });
597
598 let msg = rx.recv().await.unwrap();
599 let parsed: serde_json::Value = serde_json::from_str(&msg).unwrap();
600 let id = parsed["id"].as_str().unwrap().to_string();
601 assert_eq!(parsed["method"], "actions/execute");
602 assert_eq!(parsed["params"]["action"]["id"], "open-reader");
603 assert_eq!(parsed["params"]["action"]["type"], "terminal.create@1");
604 assert_eq!(parsed["params"]["action"]["params"]["command"], "bookokrat");
605
606 let response = RpcResponse::success(
607 Some(Value::String(id.clone())),
608 serde_json::json!({
609 "action_id": "open-reader",
610 "status": "completed",
611 "result": {
612 "terminal_id": "term_123",
613 "backend": "zellij",
614 "actual_placement": "background_session"
615 }
616 }),
617 );
618 if let Some(tx) = pending.lock().await.remove(&id) {
619 tx.send(response).unwrap();
620 }
621
622 let outcome = handle.await.unwrap().unwrap();
623 assert_eq!(outcome.action_id, "open-reader");
624 assert_eq!(outcome.status, crate::HostActionStatus::Completed);
625 assert_eq!(outcome.result.unwrap()["terminal_id"], "term_123");
626 }
627
628 #[tokio::test]
631 async fn test_bounded_read_normal_line() {
632 let data = b"hello world\n";
633 let mut reader = tokio::io::BufReader::new(&data[..]);
634 let mut buf = Vec::new();
635
636 let result = read_bounded_line(&mut reader, &mut buf).await;
637 let line = result.unwrap().unwrap();
638 assert_eq!(line.trim(), "hello world");
639 }
640
641 #[tokio::test]
642 async fn test_bounded_read_eof() {
643 let data = b"";
644 let mut reader = tokio::io::BufReader::new(&data[..]);
645 let mut buf = Vec::new();
646
647 let result = read_bounded_line(&mut reader, &mut buf).await;
648 assert!(result.unwrap().is_none());
649 }
650
651 #[tokio::test]
652 async fn test_bounded_read_partial_line_at_eof() {
653 let data = b"no newline";
654 let mut reader = tokio::io::BufReader::new(&data[..]);
655 let mut buf = Vec::new();
656
657 let result = read_bounded_line(&mut reader, &mut buf).await;
658 let line = result.unwrap().unwrap();
659 assert_eq!(line, "no newline");
660 }
661
662 #[tokio::test]
663 async fn test_bounded_read_multiple_lines() {
664 let data = b"line one\nline two\n";
665 let mut reader = tokio::io::BufReader::new(&data[..]);
666 let mut buf = Vec::new();
667
668 let line1 = read_bounded_line(&mut reader, &mut buf)
669 .await
670 .unwrap()
671 .unwrap();
672 assert_eq!(line1.trim(), "line one");
673
674 buf.clear();
675 let line2 = read_bounded_line(&mut reader, &mut buf)
676 .await
677 .unwrap()
678 .unwrap();
679 assert_eq!(line2.trim(), "line two");
680 }
681
682 #[tokio::test]
683 async fn test_bounded_read_rejects_oversized() {
684 let oversized = vec![b'x'; MAX_LINE_SIZE + 100];
686 let mut data = oversized;
687 data.push(b'\n');
688 let mut reader = tokio::io::BufReader::new(&data[..]);
689 let mut buf = Vec::new();
690
691 let result = read_bounded_line(&mut reader, &mut buf).await;
692 assert!(result.is_err());
693 let msg = result.unwrap_err();
694 assert!(msg.contains("too large"));
695 }
696
697 #[tokio::test]
698 async fn test_bounded_read_exactly_at_limit() {
699 let mut data = vec![b'x'; MAX_LINE_SIZE - 1];
701 data.push(b'\n');
702 let mut reader = tokio::io::BufReader::new(&data[..]);
703 let mut buf = Vec::new();
704
705 let result = read_bounded_line(&mut reader, &mut buf).await;
706 assert!(result.is_ok());
707 let line = result.unwrap().unwrap();
708 assert_eq!(line.len(), MAX_LINE_SIZE);
709 }
710
711 #[tokio::test]
712 async fn test_bounded_read_empty_lines() {
713 let data = b"\n\nhello\n";
714 let mut reader = tokio::io::BufReader::new(&data[..]);
715 let mut buf = Vec::new();
716
717 let line1 = read_bounded_line(&mut reader, &mut buf)
719 .await
720 .unwrap()
721 .unwrap();
722 assert_eq!(line1.trim(), "");
723
724 buf.clear();
725 let line2 = read_bounded_line(&mut reader, &mut buf)
726 .await
727 .unwrap()
728 .unwrap();
729 assert_eq!(line2.trim(), "");
730
731 buf.clear();
732 let line3 = read_bounded_line(&mut reader, &mut buf)
733 .await
734 .unwrap()
735 .unwrap();
736 assert_eq!(line3.trim(), "hello");
737 }
738}