1use crate::cli::AppServerBuilder;
46use crate::error::{Error, ParseError, Result};
47use crate::jsonrpc::{
48 JsonRpcError, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, RequestId,
49};
50use crate::messages::{Notification, ServerMessage, ServerRequest};
51use crate::protocol::{
52 ClientInfo, InitializeParams, InitializeResponse, ThreadArchiveParams, ThreadArchiveResponse,
53 ThreadDeleteParams, ThreadDeleteResponse, ThreadForkParams, ThreadForkResponse,
54 ThreadItemsListParams, ThreadItemsListResponse, ThreadResumeParams, ThreadResumeResponse,
55 ThreadRevertParams, ThreadRevertResponse, ThreadStartParams, ThreadStartResponse,
56 ThreadTurnsListParams, ThreadTurnsListResponse, TurnInterruptParams, TurnInterruptResponse,
57 TurnStartParams, TurnStartResponse, TurnSteerParams, TurnSteerResponse,
58};
59use crate::protocol_generated::types::{
60 CancelLoginAccountParams, CancelLoginAccountResponse, GetAccountParams,
61 GetAccountRateLimitsParams, GetAccountRateLimitsResponse, GetAccountResponse,
62 GetAccountTokenUsageResponse, LoginAccountParams, LoginAccountResponse, LogoutAccountResponse,
63};
64use log::{debug, error, warn};
65use serde::de::DeserializeOwned;
66use serde::Serialize;
67use std::collections::VecDeque;
68use std::sync::atomic::{AtomicI64, Ordering};
69use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
70use tokio::process::Child;
71
72const STDOUT_BUFFER_SIZE: usize = 10 * 1024 * 1024;
74
75pub struct AsyncClient {
89 child: Child,
90 writer: BufWriter<tokio::process::ChildStdin>,
91 reader: BufReader<tokio::process::ChildStdout>,
92 inbound_frame: Vec<u8>,
94 _stderr_drain: tokio::task::JoinHandle<()>,
98 next_id: AtomicI64,
99 buffered: VecDeque<ServerMessage>,
102}
103
104impl AsyncClient {
105 pub fn new(mut child: Child) -> Result<Self> {
112 let stdin = child
113 .stdin
114 .take()
115 .ok_or_else(|| Error::Protocol("Failed to get stdin".to_string()))?;
116 let stdout = child
117 .stdout
118 .take()
119 .ok_or_else(|| Error::Protocol("Failed to get stdout".to_string()))?;
120 let stderr = child
121 .stderr
122 .take()
123 .ok_or_else(|| Error::Protocol("Failed to get stderr".to_string()))?;
124
125 let stderr_drain = crate::stderr_drain::spawn_async(stderr);
130
131 Ok(Self {
132 child,
133 writer: BufWriter::new(stdin),
134 reader: BufReader::with_capacity(STDOUT_BUFFER_SIZE, stdout),
135 inbound_frame: Vec::new(),
136 _stderr_drain: stderr_drain,
137 next_id: AtomicI64::new(1),
138 buffered: VecDeque::new(),
139 })
140 }
141
142 pub async fn start() -> Result<Self> {
154 Self::start_with(AppServerBuilder::new()).await
155 }
156
157 pub async fn start_with(builder: AppServerBuilder) -> Result<Self> {
168 let mut client = Self::spawn(builder).await?;
169 client
170 .initialize(&InitializeParams {
171 client_info: ClientInfo {
172 name: "codex-codes".to_string(),
173 version: env!("CARGO_PKG_VERSION").to_string(),
174 title: None,
175 },
176 capabilities: None,
177 })
178 .await?;
179 Ok(client)
180 }
181
182 pub async fn spawn(builder: AppServerBuilder) -> Result<Self> {
188 crate::version::check_codex_version_async().await?;
189 Self::new(builder.spawn().await?)
190 }
191
192 pub async fn request<P: Serialize, R: DeserializeOwned>(
205 &mut self,
206 method: &str,
207 params: &P,
208 ) -> Result<R> {
209 let id = RequestId::Integer(self.next_id.fetch_add(1, Ordering::Relaxed));
210
211 let req = JsonRpcRequest {
212 id: id.clone(),
213 method: method.to_string(),
214 params: Some(serde_json::to_value(params).map_err(Error::Json)?),
215 };
216
217 self.send_raw(&req).await?;
218
219 loop {
221 let msg = self.read_message().await?;
222 match msg {
223 JsonRpcMessage::Response(resp) if resp.id == id => {
224 let result: R = serde_json::from_value(resp.result).map_err(Error::Json)?;
225 return Ok(result);
226 }
227 JsonRpcMessage::Error(err) if err.id == id => {
228 return Err(Error::JsonRpc {
229 code: err.error.code,
230 message: err.error.message,
231 });
232 }
233 JsonRpcMessage::Notification(notif) => {
235 let typed = Notification::from_envelope(¬if.method, notif.params)
236 .map_err(Error::Json)?;
237 self.buffered.push_back(ServerMessage::Notification(typed));
238 }
239 JsonRpcMessage::Request(req) => {
240 let typed = ServerRequest::from_envelope(&req.method, req.params)
241 .map_err(Error::Json)?;
242 self.buffered.push_back(ServerMessage::Request {
243 id: req.id,
244 request: typed,
245 });
246 }
247 JsonRpcMessage::Response(resp) => {
249 warn!(
250 "[CLIENT] Unexpected response for id={}, expected id={}",
251 resp.id, id
252 );
253 }
254 JsonRpcMessage::Error(err) => {
255 warn!(
256 "[CLIENT] Unexpected error for id={}, expected id={}",
257 err.id, id
258 );
259 }
260 }
261 }
262 }
263
264 pub async fn thread_start(
269 &mut self,
270 params: &ThreadStartParams,
271 ) -> Result<ThreadStartResponse> {
272 self.request(crate::protocol::methods::THREAD_START, params)
273 .await
274 }
275
276 pub async fn thread_resume(
280 &mut self,
281 params: &ThreadResumeParams,
282 ) -> Result<ThreadResumeResponse> {
283 self.request(crate::protocol::methods::THREAD_RESUME, params)
284 .await
285 }
286
287 pub async fn thread_fork(&mut self, params: &ThreadForkParams) -> Result<ThreadForkResponse> {
289 self.request(crate::protocol::methods::THREAD_FORK, params)
290 .await
291 }
292
293 pub async fn turn_start(&mut self, params: &TurnStartParams) -> Result<TurnStartResponse> {
298 self.request(crate::protocol::methods::TURN_START, params)
299 .await
300 }
301
302 pub async fn turn_steer(&mut self, params: &TurnSteerParams) -> Result<TurnSteerResponse> {
305 self.request(crate::protocol::methods::TURN_STEER, params)
306 .await
307 }
308
309 pub async fn thread_items_list(
312 &mut self,
313 params: &ThreadItemsListParams,
314 ) -> Result<ThreadItemsListResponse> {
315 self.request(crate::protocol::methods::THREAD_ITEMS_LIST, params)
316 .await
317 }
318
319 pub async fn thread_turns_list(
322 &mut self,
323 params: &ThreadTurnsListParams,
324 ) -> Result<ThreadTurnsListResponse> {
325 self.request(crate::protocol::methods::THREAD_TURNS_LIST, params)
326 .await
327 }
328
329 pub async fn thread_revert(
333 &mut self,
334 params: &ThreadRevertParams,
335 ) -> Result<ThreadRevertResponse> {
336 self.request(crate::protocol::methods::THREAD_REVERT, params)
337 .await
338 }
339
340 pub async fn turn_interrupt(
342 &mut self,
343 params: &TurnInterruptParams,
344 ) -> Result<TurnInterruptResponse> {
345 self.request(crate::protocol::methods::TURN_INTERRUPT, params)
346 .await
347 }
348
349 pub async fn thread_archive(
351 &mut self,
352 params: &ThreadArchiveParams,
353 ) -> Result<ThreadArchiveResponse> {
354 self.request(crate::protocol::methods::THREAD_ARCHIVE, params)
355 .await
356 }
357
358 pub async fn thread_delete(
360 &mut self,
361 params: &ThreadDeleteParams,
362 ) -> Result<ThreadDeleteResponse> {
363 self.request(crate::protocol::methods::THREAD_DELETE, params)
364 .await
365 }
366
367 pub async fn initialize(&mut self, params: &InitializeParams) -> Result<InitializeResponse> {
373 let resp: InitializeResponse = self
374 .request(crate::protocol::methods::INITIALIZE, params)
375 .await?;
376 self.send_notification(crate::protocol::methods::INITIALIZED)
377 .await?;
378 Ok(resp)
379 }
380
381 pub async fn respond<R: Serialize>(&mut self, id: RequestId, result: &R) -> Result<()> {
388 let resp = JsonRpcResponse {
389 id,
390 result: serde_json::to_value(result).map_err(Error::Json)?,
391 };
392 self.send_raw(&resp).await
393 }
394
395 pub async fn respond_error(&mut self, id: RequestId, code: i64, message: &str) -> Result<()> {
397 let err = JsonRpcError {
398 id,
399 error: crate::jsonrpc::JsonRpcErrorData {
400 code,
401 message: message.to_string(),
402 data: None,
403 },
404 };
405 self.send_raw(&err).await
406 }
407
408 pub async fn next_message(&mut self) -> Result<Option<ServerMessage>> {
428 if let Some(msg) = self.buffered.pop_front() {
430 return Ok(Some(msg));
431 }
432
433 loop {
435 let msg = match self.read_message_opt().await? {
436 Some(m) => m,
437 None => return Ok(None),
438 };
439
440 match msg {
441 JsonRpcMessage::Notification(notif) => {
442 let JsonRpcNotification { method, params } = notif;
443 let typed =
444 Notification::from_envelope(&method, params.clone()).map_err(|e| {
445 Error::Deserialization(ParseError::from_envelope(method, params, e))
446 })?;
447 return Ok(Some(ServerMessage::Notification(typed)));
448 }
449 JsonRpcMessage::Request(req) => {
450 let JsonRpcRequest { id, method, params } = req;
451 let typed =
452 ServerRequest::from_envelope(&method, params.clone()).map_err(|e| {
453 Error::Deserialization(ParseError::from_envelope(method, params, e))
454 })?;
455 return Ok(Some(ServerMessage::Request { id, request: typed }));
456 }
457 JsonRpcMessage::Response(resp) => {
459 warn!(
460 "[CLIENT] Unexpected response (no pending request): id={}",
461 resp.id
462 );
463 }
464 JsonRpcMessage::Error(err) => {
465 warn!(
466 "[CLIENT] Unexpected error (no pending request): id={} code={}",
467 err.id, err.error.code
468 );
469 }
470 }
471 }
472 }
473
474 pub fn events(&mut self) -> EventStream<'_> {
480 EventStream { client: self }
481 }
482
483 pub fn pid(&self) -> Option<u32> {
485 self.child.id()
486 }
487
488 pub async fn account_read(&mut self, params: &GetAccountParams) -> Result<GetAccountResponse> {
493 self.request(crate::protocol::methods::ACCOUNT_READ, params)
494 .await
495 }
496
497 pub async fn account_login_start(
504 &mut self,
505 params: &LoginAccountParams,
506 ) -> Result<LoginAccountResponse> {
507 self.request(crate::protocol::methods::ACCOUNT_LOGIN_START, params)
508 .await
509 }
510
511 pub async fn account_login_cancel(
513 &mut self,
514 params: &CancelLoginAccountParams,
515 ) -> Result<CancelLoginAccountResponse> {
516 self.request(crate::protocol::methods::ACCOUNT_LOGIN_CANCEL, params)
517 .await
518 }
519
520 pub async fn account_logout(&mut self) -> Result<LogoutAccountResponse> {
522 self.request(
523 crate::protocol::methods::ACCOUNT_LOGOUT,
524 &serde_json::json!({}),
525 )
526 .await
527 }
528
529 pub async fn account_rate_limits_read(
535 &mut self,
536 params: GetAccountRateLimitsParams,
537 ) -> Result<GetAccountRateLimitsResponse> {
538 self.request(crate::protocol::methods::ACCOUNT_RATELIMITS_READ, ¶ms)
539 .await
540 }
541
542 pub async fn account_usage_read(&mut self) -> Result<GetAccountTokenUsageResponse> {
544 self.request(
545 crate::protocol::methods::ACCOUNT_USAGE_READ,
546 &serde_json::json!({}),
547 )
548 .await
549 }
550
551 pub fn is_alive(&mut self) -> bool {
553 self.child.try_wait().ok().flatten().is_none()
554 }
555
556 pub async fn shutdown(mut self) -> Result<()> {
561 debug!("[CLIENT] Shutting down");
562 self.child.kill().await.map_err(Error::Io)?;
563 Ok(())
564 }
565
566 async fn send_notification(&mut self, method: &str) -> Result<()> {
569 let notif = JsonRpcNotification {
570 method: method.to_string(),
571 params: None,
572 };
573 self.send_raw(¬if).await
574 }
575
576 async fn send_raw<T: Serialize>(&mut self, msg: &T) -> Result<()> {
577 let json = serde_json::to_string(msg).map_err(Error::Json)?;
578 debug!("[CLIENT] Sending: {}", json);
579 self.writer
580 .write_all(json.as_bytes())
581 .await
582 .map_err(Error::Io)?;
583 self.writer.write_all(b"\n").await.map_err(Error::Io)?;
584 self.writer.flush().await.map_err(Error::Io)?;
585 Ok(())
586 }
587
588 async fn read_message(&mut self) -> Result<JsonRpcMessage> {
589 self.read_message_opt().await?.ok_or(Error::ServerClosed)
590 }
591
592 async fn read_message_opt(&mut self) -> Result<Option<JsonRpcMessage>> {
593 loop {
594 let bytes_read = self
598 .reader
599 .read_until(b'\n', &mut self.inbound_frame)
600 .await
601 .map_err(Error::Io)?;
602
603 if bytes_read == 0 {
604 debug!("[CLIENT] Stream closed (EOF)");
605 if self.inbound_frame.is_empty() {
606 return Ok(None);
607 }
608 }
609
610 if !self.inbound_frame.ends_with(b"\n") && bytes_read != 0 {
611 continue;
612 }
613
614 let line = match std::str::from_utf8(&self.inbound_frame) {
615 Ok(line) => line,
616 Err(error) => {
617 let error = std::io::Error::new(std::io::ErrorKind::InvalidData, error);
618 self.inbound_frame.clear();
619 return Err(Error::Io(error));
620 }
621 };
622 let trimmed = line.trim();
623 if trimmed.is_empty() {
624 self.inbound_frame.clear();
625 continue;
626 }
627
628 debug!("[CLIENT] Received: {}", trimmed);
629
630 let decoded = serde_json::from_str::<JsonRpcMessage>(trimmed);
631 match decoded {
632 Ok(msg) => {
633 self.inbound_frame.clear();
634 return Ok(Some(msg));
635 }
636 Err(e) => {
637 warn!(
638 "[CLIENT] Failed to deserialize message. \
639 Please report this at https://github.com/meawoppl/rust-code-agent-sdks/issues"
640 );
641 warn!("[CLIENT] Parse error: {}", e);
642 warn!("[CLIENT] Raw: {}", trimmed);
643 let parse_error = ParseError::from_line(trimmed, e);
644 self.inbound_frame.clear();
645 return Err(Error::Deserialization(parse_error));
646 }
647 }
648 }
649 }
650}
651
652impl Drop for AsyncClient {
653 fn drop(&mut self) {
654 if self.is_alive() {
655 if let Err(e) = self.child.start_kill() {
656 error!("Failed to kill app-server process on drop: {}", e);
657 }
658 }
659 }
660}
661
662pub struct EventStream<'a> {
664 client: &'a mut AsyncClient,
665}
666
667impl EventStream<'_> {
668 pub async fn next(&mut self) -> Option<Result<ServerMessage>> {
670 match self.client.next_message().await {
671 Ok(Some(msg)) => Some(Ok(msg)),
672 Ok(None) => None,
673 Err(e) => Some(Err(e)),
674 }
675 }
676
677 pub async fn collect(mut self) -> Result<Vec<ServerMessage>> {
679 let mut msgs = Vec::new();
680 while let Some(result) = self.next().await {
681 msgs.push(result?);
682 }
683 Ok(msgs)
684 }
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690 use std::process::Stdio;
691 use tokio::process::Command;
692
693 #[cfg(unix)]
694 fn scripted_client(script: &str) -> AsyncClient {
695 let mut command = Command::new("sh");
696 command
697 .arg("-c")
698 .arg(script)
699 .stdin(Stdio::piped())
700 .stdout(Stdio::piped())
701 .stderr(Stdio::piped());
702 AsyncClient::new(command.spawn().expect("spawn scripted app-server"))
703 .expect("construct async client")
704 }
705
706 fn unknown_method(message: ServerMessage) -> String {
707 match message {
708 ServerMessage::Notification(Notification::Unknown { method, .. }) => method,
709 other => panic!("expected unknown notification, got {other:?}"),
710 }
711 }
712
713 #[test]
714 fn test_buffer_size() {
715 assert_eq!(STDOUT_BUFFER_SIZE, 10 * 1024 * 1024);
716 }
717
718 #[cfg(unix)]
719 #[tokio::test]
720 async fn cancelled_next_message_resumes_partial_frame_exactly_once() {
721 const PARTIAL: &[u8] = br#"{"method":"test/first","params":{"part":"#;
722 let mut client = scripted_client(
723 r#"printf '%s' '{"method":"test/first","params":{"part":'; IFS= read -r release; printf '%s\n' '1}}'; printf '%s\n' '{"method":"test/second","params":{}}'"#,
724 );
725
726 assert_eq!(
727 client
728 .reader
729 .fill_buf()
730 .await
731 .expect("buffer partial frame"),
732 PARTIAL
733 );
734 let mut pending_read = Box::pin(client.next_message());
735 tokio::select! {
736 biased;
737 result = &mut pending_read => panic!("partial frame completed unexpectedly: {result:?}"),
738 _ = async {} => {}
739 }
740 drop(pending_read);
741 assert!(!client.inbound_frame.is_empty());
744 assert!(PARTIAL.starts_with(&client.inbound_frame));
745 client
746 .writer
747 .write_all(b"release\n")
748 .await
749 .expect("release remaining frame");
750 client.writer.flush().await.expect("flush release");
751
752 let first = client
753 .next_message()
754 .await
755 .expect("resume first frame")
756 .expect("first message");
757 let second = client
758 .next_message()
759 .await
760 .expect("read second frame")
761 .expect("second message");
762
763 assert_eq!(unknown_method(first), "test/first");
764 assert_eq!(unknown_method(second), "test/second");
765 assert!(client.next_message().await.expect("read EOF").is_none());
766 }
767
768 #[cfg(unix)]
769 #[tokio::test]
770 async fn cancelled_request_preserves_shared_decoder_framing() {
771 const PARTIAL: &[u8] = br#"{"id":1,"result":{"abandoned":"#;
772 let mut client = scripted_client(
773 r#"printf '%s' '{"id":1,"result":{"abandoned":'; IFS= read -r first; IFS= read -r release; printf '%s\n' 'true}}' '{"method":"test/between","params":{}}'; IFS= read -r second; printf '%s\n' '{"id":2,"result":{"ok":true}}' '{"method":"test/after","params":{}}'"#,
774 );
775
776 assert_eq!(
777 client
778 .reader
779 .fill_buf()
780 .await
781 .expect("buffer partial response"),
782 PARTIAL
783 );
784 let params = serde_json::json!({});
785 let mut pending_request =
786 Box::pin(client.request::<_, serde_json::Value>("test/abandoned", ¶ms));
787 tokio::select! {
788 biased;
789 result = &mut pending_request => panic!("partial response completed unexpectedly: {result:?}"),
790 _ = async {} => {}
791 }
792 drop(pending_request);
793 assert!(!client.inbound_frame.is_empty());
796 assert!(PARTIAL.starts_with(&client.inbound_frame));
797 client
798 .writer
799 .write_all(b"release\n")
800 .await
801 .expect("release remaining response");
802 client.writer.flush().await.expect("flush release");
803
804 let response: serde_json::Value = client
805 .request("test/resumed", &serde_json::json!({}))
806 .await
807 .expect("second request should resume the shared decoder");
808 assert_eq!(response, serde_json::json!({"ok": true}));
809
810 let between = client
811 .next_message()
812 .await
813 .expect("read buffered notification")
814 .expect("between message");
815 let after = client
816 .next_message()
817 .await
818 .expect("read trailing notification")
819 .expect("after message");
820 assert_eq!(unknown_method(between), "test/between");
821 assert_eq!(unknown_method(after), "test/after");
822 assert!(client.next_message().await.expect("read EOF").is_none());
823 }
824
825 #[cfg(unix)]
826 #[tokio::test]
827 async fn next_message_resumes_notification_partially_read_by_cancelled_request() {
828 const PARTIAL: &[u8] = br#"{"method":"test/during-request","params":{"part":"#;
829 let mut client = scripted_client(
830 r#"printf '%s' '{"method":"test/during-request","params":{"part":'; IFS= read -r request; IFS= read -r release; printf '%s\n' '1}}' '{"id":1,"result":{}}'"#,
831 );
832
833 assert_eq!(
834 client
835 .reader
836 .fill_buf()
837 .await
838 .expect("buffer partial notification"),
839 PARTIAL
840 );
841 let params = serde_json::json!({});
842 let mut pending_request =
843 Box::pin(client.request::<_, serde_json::Value>("test/abandoned", ¶ms));
844 tokio::select! {
845 biased;
846 result = &mut pending_request => panic!("partial notification completed unexpectedly: {result:?}"),
847 _ = async {} => {}
848 }
849 drop(pending_request);
850 assert!(!client.inbound_frame.is_empty());
853 assert!(PARTIAL.starts_with(&client.inbound_frame));
854 client
855 .writer
856 .write_all(b"release\n")
857 .await
858 .expect("release remaining notification");
859 client.writer.flush().await.expect("flush release");
860
861 let notification = client
862 .next_message()
863 .await
864 .expect("resume partial notification")
865 .expect("notification");
866 assert_eq!(unknown_method(notification), "test/during-request");
867 assert!(client.next_message().await.expect("read EOF").is_none());
868 }
869
870 #[cfg(unix)]
871 #[tokio::test]
872 async fn request_resumes_notification_partially_read_by_cancelled_next_message() {
873 const PARTIAL: &[u8] = br#"{"method":"test/before-request","params":{"part":"#;
874 let mut client = scripted_client(
875 r#"printf '%s' '{"method":"test/before-request","params":{"part":'; IFS= read -r release; printf '%s\n' '1}}'; IFS= read -r request; printf '%s\n' '{"id":1,"result":{"ok":true}}'"#,
876 );
877
878 assert_eq!(
879 client
880 .reader
881 .fill_buf()
882 .await
883 .expect("buffer partial notification"),
884 PARTIAL
885 );
886 let mut pending_read = Box::pin(client.next_message());
887 tokio::select! {
888 biased;
889 result = &mut pending_read => panic!("partial notification completed unexpectedly: {result:?}"),
890 _ = async {} => {}
891 }
892 drop(pending_read);
893 assert!(!client.inbound_frame.is_empty());
896 assert!(PARTIAL.starts_with(&client.inbound_frame));
897 client
898 .writer
899 .write_all(b"release\n")
900 .await
901 .expect("release remaining notification");
902 client.writer.flush().await.expect("flush release");
903
904 let response: serde_json::Value = client
905 .request("test/resumed", &serde_json::json!({}))
906 .await
907 .expect("request should resume the shared decoder");
908 assert_eq!(response, serde_json::json!({"ok": true}));
909
910 let notification = client
911 .next_message()
912 .await
913 .expect("read buffered notification")
914 .expect("notification");
915 assert_eq!(unknown_method(notification), "test/before-request");
916 assert!(client.next_message().await.expect("read EOF").is_none());
917 }
918
919 #[cfg(unix)]
920 #[tokio::test]
921 async fn request_directly_resumes_response_partially_read_by_cancelled_next_message() {
922 const PARTIAL: &[u8] = br#"{"id":1,"result":{"ok":"#;
923 let mut client = scripted_client(
926 r#"printf '%s' '{"id":1,"result":{"ok":'; IFS= read -r request; printf '%s\n' 'true}}'"#,
927 );
928
929 assert_eq!(
930 client
931 .reader
932 .fill_buf()
933 .await
934 .expect("buffer partial response"),
935 PARTIAL
936 );
937 let mut pending_read = Box::pin(client.next_message());
938 tokio::select! {
939 biased;
940 result = &mut pending_read => panic!("partial response completed unexpectedly: {result:?}"),
941 _ = async {} => {}
942 }
943 drop(pending_read);
944 assert!(!client.inbound_frame.is_empty());
947 assert!(PARTIAL.starts_with(&client.inbound_frame));
948 assert!(client.buffered.is_empty());
949
950 let response: serde_json::Value = client
951 .request("test/resumed", &serde_json::json!({}))
952 .await
953 .expect("request should directly resume the partial response");
954 assert_eq!(response, serde_json::json!({"ok": true}));
955 assert!(client.buffered.is_empty());
956 assert!(client.next_message().await.expect("read EOF").is_none());
957 }
958}