1use std::{future::Future, marker::PhantomData, path::Path};
2
3use futures::channel::{mpsc, oneshot};
4
5use crate::{
6 Agent, Client, ConnectionTo, Dispatch, HandleDispatchFrom, Handled, JsonRpcRequest, Responder,
7 Role,
8 jsonrpc::{
9 DynamicHandlerGuard,
10 run::{NullRun, RunWithConnectionTo},
11 },
12 role::{HasPeer, acp::ProxySessionMessages},
13 schema::v1::{
14 ContentBlock, ContentChunk, LoadSessionRequest, LoadSessionResponse, Meta,
15 NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ResumeSessionRequest,
16 ResumeSessionResponse, SessionConfigOption, SessionId, SessionModeState,
17 SessionNotification, SessionUpdate, StopReason,
18 },
19 util::{MatchDispatch, MatchDispatchFrom, run_until},
20};
21
22#[cfg(feature = "unstable_mcp_over_acp")]
23use crate::{jsonrpc::run::ChainRun, mcp_server::McpServer};
24
25#[cfg(feature = "unstable_protocol_v2")]
26mod v2;
27#[cfg(feature = "unstable_protocol_v2")]
28pub use v2::*;
29
30#[derive(Debug)]
32pub struct Blocking;
33impl SessionBlockState for Blocking {}
34
35#[derive(Debug)]
37pub struct NonBlocking;
38impl SessionBlockState for NonBlocking {}
39
40pub trait SessionBlockState: Send + 'static + Sync + std::fmt::Debug {}
43
44impl<Counterpart: Role> ConnectionTo<Counterpart>
45where
46 Counterpart: HasPeer<Agent>,
47{
48 pub fn build_session(&self, cwd: impl AsRef<Path>) -> SessionBuilder<Counterpart, NullRun> {
53 SessionBuilder::new(self, NewSessionRequest::new(cwd.as_ref()))
54 }
55
56 pub fn build_session_cwd(&self) -> Result<SessionBuilder<Counterpart, NullRun>, crate::Error> {
63 let cwd = std::env::current_dir().map_err(|e| {
64 crate::Error::internal_error().data(format!("cannot get current directory: {e}"))
65 })?;
66 Ok(self.build_session(cwd))
67 }
68
69 pub fn build_session_from(
74 &self,
75 request: NewSessionRequest,
76 ) -> SessionBuilder<Counterpart, NullRun> {
77 SessionBuilder::new(self, request)
78 }
79
80 pub fn load_session(
89 &self,
90 session_id: impl Into<SessionId>,
91 cwd: impl AsRef<Path>,
92 ) -> RestoreSessionBuilder<Counterpart, LoadSessionRequest> {
93 self.load_session_from(LoadSessionRequest::new(session_id, cwd.as_ref()))
94 }
95
96 pub fn load_session_from(
102 &self,
103 request: LoadSessionRequest,
104 ) -> RestoreSessionBuilder<Counterpart, LoadSessionRequest> {
105 RestoreSessionBuilder::new(self, request)
106 }
107
108 pub fn resume_session(
115 &self,
116 session_id: impl Into<SessionId>,
117 cwd: impl AsRef<Path>,
118 ) -> RestoreSessionBuilder<Counterpart, ResumeSessionRequest> {
119 self.resume_session_from(ResumeSessionRequest::new(session_id, cwd.as_ref()))
120 }
121
122 pub fn resume_session_from(
128 &self,
129 request: ResumeSessionRequest,
130 ) -> RestoreSessionBuilder<Counterpart, ResumeSessionRequest> {
131 RestoreSessionBuilder::new(self, request)
132 }
133
134 pub(crate) fn attach_session<'runner>(
145 &self,
146 response: NewSessionResponse,
147 mcp_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
148 ) -> Result<ActiveSession<'runner, Counterpart>, crate::Error> {
149 let NewSessionResponse {
150 session_id,
151 modes,
152 config_options,
153 meta,
154 ..
155 } = response;
156
157 let prepared = self.prepare_session_routing(&session_id)?;
158 Ok(prepared.into_active_session(
159 self.clone(),
160 session_id,
161 modes,
162 config_options,
163 meta,
164 mcp_handler_registrations,
165 ))
166 }
167
168 fn prepare_session_routing(
173 &self,
174 session_id: &SessionId,
175 ) -> Result<PreparedSession<Counterpart>, crate::Error> {
176 let (update_tx, update_rx) = mpsc::unbounded();
177 let handler = ActiveSessionHandler::new(session_id.clone(), update_tx.clone());
178 let session_handler_registration = self.add_dynamic_handler(handler)?;
179
180 Ok(PreparedSession {
181 update_rx,
182 update_tx,
183 session_handler_registration,
184 })
185 }
186}
187
188struct PreparedSession<Counterpart: Role>
190where
191 Counterpart: HasPeer<Agent>,
192{
193 update_rx: mpsc::UnboundedReceiver<SessionMessage>,
194 update_tx: mpsc::UnboundedSender<SessionMessage>,
195 session_handler_registration: DynamicHandlerGuard<Counterpart>,
196}
197
198impl<Counterpart> PreparedSession<Counterpart>
199where
200 Counterpart: HasPeer<Agent>,
201{
202 fn into_active_session<'runner>(
203 self,
204 connection: ConnectionTo<Counterpart>,
205 session_id: SessionId,
206 modes: Option<SessionModeState>,
207 config_options: Option<Vec<SessionConfigOption>>,
208 meta: Option<Meta>,
209 mcp_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
210 ) -> ActiveSession<'runner, Counterpart> {
211 ActiveSession {
212 session_id,
213 modes,
214 config_options,
215 meta,
216 update_rx: self.update_rx,
217 update_tx: self.update_tx,
218 connection,
219 session_handler_registration: self.session_handler_registration,
220 mcp_handler_registrations,
221 _runner: PhantomData,
222 }
223 }
224}
225
226trait RestoreRequest: JsonRpcRequest {
228 fn session_id(&self) -> &SessionId;
229 fn response_modes(response: &Self::Response) -> Option<SessionModeState>;
230 fn response_config_options(response: &Self::Response) -> Option<Vec<SessionConfigOption>>;
231 fn response_meta(response: &Self::Response) -> Option<Meta>;
232}
233
234impl RestoreRequest for LoadSessionRequest {
235 fn session_id(&self) -> &SessionId {
236 &self.session_id
237 }
238
239 fn response_modes(response: &Self::Response) -> Option<SessionModeState> {
240 response.modes.clone()
241 }
242
243 fn response_config_options(response: &Self::Response) -> Option<Vec<SessionConfigOption>> {
244 response.config_options.clone()
245 }
246
247 fn response_meta(response: &Self::Response) -> Option<Meta> {
248 response.meta.clone()
249 }
250}
251
252impl RestoreRequest for ResumeSessionRequest {
253 fn session_id(&self) -> &SessionId {
254 &self.session_id
255 }
256
257 fn response_modes(response: &Self::Response) -> Option<SessionModeState> {
258 response.modes.clone()
259 }
260
261 fn response_config_options(response: &Self::Response) -> Option<Vec<SessionConfigOption>> {
262 response.config_options.clone()
263 }
264
265 fn response_meta(response: &Self::Response) -> Option<Meta> {
266 response.meta.clone()
267 }
268}
269
270#[must_use = "use `start_session` or `on_session_start` to restore the session"]
287#[derive(Debug)]
288pub struct RestoreSessionBuilder<Counterpart, Request, BlockState = NonBlocking>
289where
290 Counterpart: HasPeer<Agent>,
291 BlockState: SessionBlockState,
292{
293 connection: ConnectionTo<Counterpart>,
294 request: Request,
295 block_state: PhantomData<BlockState>,
296}
297
298impl<Counterpart, Request> RestoreSessionBuilder<Counterpart, Request, NonBlocking>
299where
300 Counterpart: HasPeer<Agent>,
301{
302 fn new(connection: &ConnectionTo<Counterpart>, request: Request) -> Self {
303 Self {
304 connection: connection.clone(),
305 request,
306 block_state: PhantomData,
307 }
308 }
309
310 pub fn block_task(self) -> RestoreSessionBuilder<Counterpart, Request, Blocking> {
314 RestoreSessionBuilder {
315 connection: self.connection,
316 request: self.request,
317 block_state: PhantomData,
318 }
319 }
320}
321
322fn restored_session<Counterpart, Request>(
323 connection: ConnectionTo<Counterpart>,
324 session_id: SessionId,
325 prepared: PreparedSession<Counterpart>,
326 response: Request::Response,
327) -> RestoredSession<'static, Counterpart, Request::Response>
328where
329 Counterpart: HasPeer<Agent>,
330 Request: RestoreRequest,
331{
332 let session = prepared.into_active_session(
333 connection,
334 session_id,
335 Request::response_modes(&response),
336 Request::response_config_options(&response),
337 Request::response_meta(&response),
338 Vec::new(),
339 );
340
341 RestoredSession { session, response }
342}
343
344fn on_restore_session_start<Counterpart, Request, F, Fut>(
345 builder: RestoreSessionBuilder<Counterpart, Request>,
346 op: F,
347) -> Result<(), crate::Error>
348where
349 Counterpart: HasPeer<Agent>,
350 Request: RestoreRequest,
351 F: FnOnce(RestoredSession<'static, Counterpart, Request::Response>) -> Fut + Send + 'static,
352 Fut: Future<Output = Result<(), crate::Error>> + Send,
353{
354 ensure_v1_session_protocol(&builder.connection)?;
355
356 let RestoreSessionBuilder {
357 connection,
358 request,
359 block_state: _,
360 } = builder;
361 let session_id = request.session_id().clone();
362 let prepared = connection.prepare_session_routing(&session_id)?;
363 let routing_ready = connection.dynamic_handler_barrier();
364
365 connection
366 .send_ordered_request_to_after(Agent, request, routing_ready)
367 .on_receiving_result({
368 let connection = connection.clone();
369 async move |result| {
370 let response = result?;
371 let restored = restored_session::<_, Request>(
372 connection.clone(),
373 session_id,
374 prepared,
375 response,
376 );
377 connection.spawn(async move { op(restored).await })
378 }
379 })
380}
381
382async fn start_restored_session<Counterpart, Request>(
383 builder: RestoreSessionBuilder<Counterpart, Request, Blocking>,
384) -> Result<RestoredSession<'static, Counterpart, Request::Response>, crate::Error>
385where
386 Counterpart: HasPeer<Agent>,
387 Request: RestoreRequest,
388{
389 ensure_v1_session_protocol(&builder.connection)?;
390
391 let RestoreSessionBuilder {
392 connection,
393 request,
394 block_state: _,
395 } = builder;
396 let session_id = request.session_id().clone();
397 let prepared = connection.prepare_session_routing(&session_id)?;
398 let routing_ready = connection.dynamic_handler_barrier();
399 let session_connection = connection.clone();
400
401 connection
402 .send_ordered_request_to_after(Agent, request, routing_ready)
403 .block_task_with_ordered_result(move |result| {
404 let response = result?;
405 Ok(restored_session::<_, Request>(
406 session_connection,
407 session_id,
408 prepared,
409 response,
410 ))
411 })
412 .await
413}
414
415impl<Counterpart> RestoreSessionBuilder<Counterpart, LoadSessionRequest>
416where
417 Counterpart: HasPeer<Agent>,
418{
419 pub fn on_session_start<F, Fut>(self, op: F) -> Result<(), crate::Error>
426 where
427 F: FnOnce(RestoredSession<'static, Counterpart, LoadSessionResponse>) -> Fut
428 + Send
429 + 'static,
430 Fut: Future<Output = Result<(), crate::Error>> + Send,
431 {
432 on_restore_session_start(self, op)
433 }
434}
435
436impl<Counterpart> RestoreSessionBuilder<Counterpart, ResumeSessionRequest>
437where
438 Counterpart: HasPeer<Agent>,
439{
440 pub fn on_session_start<F, Fut>(self, op: F) -> Result<(), crate::Error>
446 where
447 F: FnOnce(RestoredSession<'static, Counterpart, ResumeSessionResponse>) -> Fut
448 + Send
449 + 'static,
450 Fut: Future<Output = Result<(), crate::Error>> + Send,
451 {
452 on_restore_session_start(self, op)
453 }
454}
455
456impl<Counterpart> RestoreSessionBuilder<Counterpart, LoadSessionRequest, Blocking>
457where
458 Counterpart: HasPeer<Agent>,
459{
460 pub async fn start_session(
467 self,
468 ) -> Result<RestoredSession<'static, Counterpart, LoadSessionResponse>, crate::Error> {
469 start_restored_session(self).await
470 }
471}
472
473impl<Counterpart> RestoreSessionBuilder<Counterpart, ResumeSessionRequest, Blocking>
474where
475 Counterpart: HasPeer<Agent>,
476{
477 pub async fn start_session(
484 self,
485 ) -> Result<RestoredSession<'static, Counterpart, ResumeSessionResponse>, crate::Error> {
486 start_restored_session(self).await
487 }
488}
489
490pub struct RestoredSession<'runner, Link, Response>
498where
499 Link: HasPeer<Agent>,
500{
501 session: ActiveSession<'runner, Link>,
502 response: Response,
503}
504
505impl<'runner, Link, Response> RestoredSession<'runner, Link, Response>
506where
507 Link: HasPeer<Agent>,
508{
509 pub fn session(&self) -> &ActiveSession<'runner, Link> {
511 &self.session
512 }
513
514 pub fn session_mut(&mut self) -> &mut ActiveSession<'runner, Link> {
516 &mut self.session
517 }
518
519 pub fn response(&self) -> &Response {
521 &self.response
522 }
523
524 pub fn into_parts(self) -> (ActiveSession<'runner, Link>, Response) {
526 (self.session, self.response)
527 }
528
529 pub fn into_session(self) -> ActiveSession<'runner, Link> {
531 self.session
532 }
533}
534
535impl<Link, Response> std::fmt::Debug for RestoredSession<'_, Link, Response>
536where
537 Link: HasPeer<Agent>,
538 Response: std::fmt::Debug,
539{
540 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
541 formatter
542 .debug_struct("RestoredSession")
543 .field("session_id", self.session.session_id())
544 .field("response", &self.response)
545 .finish()
546 }
547}
548
549#[must_use = "use `start_session`, `run_until`, or `on_session_start` to start the session"]
557#[derive(Debug)]
558pub struct SessionBuilder<
559 Counterpart,
560 Run: RunWithConnectionTo<Counterpart> = NullRun,
561 BlockState: SessionBlockState = NonBlocking,
562> where
563 Counterpart: HasPeer<Agent>,
564{
565 connection: ConnectionTo<Counterpart>,
566 request: NewSessionRequest,
567 dynamic_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
568 run: Run,
569 block_state: PhantomData<BlockState>,
570}
571
572impl<Counterpart> SessionBuilder<Counterpart, NullRun, NonBlocking>
573where
574 Counterpart: HasPeer<Agent>,
575{
576 fn new(connection: &ConnectionTo<Counterpart>, request: NewSessionRequest) -> Self {
577 SessionBuilder {
578 connection: connection.clone(),
579 request,
580 dynamic_handler_registrations: Vec::default(),
581 run: NullRun,
582 block_state: PhantomData,
583 }
584 }
585}
586
587impl<Counterpart, R, BlockState> SessionBuilder<Counterpart, R, BlockState>
588where
589 Counterpart: HasPeer<Agent>,
590 R: RunWithConnectionTo<Counterpart>,
591 BlockState: SessionBlockState,
592{
593 #[cfg(feature = "unstable_mcp_over_acp")]
595 pub fn with_mcp_server<McpRun>(
596 mut self,
597 mcp_server: McpServer<Counterpart, McpRun>,
598 ) -> Result<SessionBuilder<Counterpart, ChainRun<R, McpRun>, BlockState>, crate::Error>
599 where
600 McpRun: RunWithConnectionTo<Counterpart>,
601 {
602 let (handler, mcp_run) = mcp_server.into_handler_and_runner();
603 self.dynamic_handler_registrations
604 .push(handler.into_dynamic_handler(&mut self.request, &self.connection)?);
605 Ok(SessionBuilder {
606 connection: self.connection,
607 request: self.request,
608 dynamic_handler_registrations: self.dynamic_handler_registrations,
609 run: ChainRun::new(self.run, mcp_run),
610 block_state: self.block_state,
611 })
612 }
613
614 pub fn on_session_start<F, Fut>(self, op: F) -> Result<(), crate::Error>
659 where
660 R: 'static,
661 F: FnOnce(ActiveSession<'static, Counterpart>) -> Fut + Send + 'static,
662 Fut: Future<Output = Result<(), crate::Error>> + Send,
663 {
664 ensure_v1_session_protocol(&self.connection)?;
665
666 let Self {
667 connection,
668 request,
669 dynamic_handler_registrations,
670 run,
671 block_state: _,
672 } = self;
673
674 connection
675 .send_ordered_request_to(Agent, request)
676 .on_receiving_result({
677 let connection = connection.clone();
678 async move |result| {
679 let response = result?;
680
681 connection.spawn(run.run_with_connection_to(connection.clone()))?;
682
683 let active_session =
684 connection.attach_session(response, dynamic_handler_registrations)?;
685
686 connection.spawn(async move { op(active_session).await })
687 }
688 })
689 }
690
691 pub fn on_proxy_session_start<F, Fut>(
744 self,
745 responder: Responder<NewSessionResponse>,
746 op: F,
747 ) -> Result<(), crate::Error>
748 where
749 F: FnOnce(SessionId) -> Fut + Send + 'static,
750 Fut: Future<Output = Result<(), crate::Error>> + Send,
751 Counterpart: HasPeer<Client>,
752 R: 'static,
753 {
754 ensure_v1_session_protocol(&self.connection)?;
755
756 let Self {
757 connection,
758 request,
759 dynamic_handler_registrations,
760 run,
761 block_state: _,
762 } = self;
763
764 let sent = connection.send_ordered_request_to(Agent, request);
766 let sent = sent.forward_cancellation_from(responder.cancellation());
767
768 sent.on_receiving_ok_result(responder, {
769 let connection = connection.clone();
770 async move |response, responder| {
771 let session_id = response.session_id.clone();
774 responder.respond(response)?;
775
776 connection
778 .add_dynamic_handler(ProxySessionMessages::new(session_id.clone()))?
779 .detach();
780
781 connection.spawn(run.run_with_connection_to(connection.clone()))?;
783 dynamic_handler_registrations
784 .into_iter()
785 .for_each(DynamicHandlerGuard::detach);
786
787 connection.spawn(async move { op(session_id).await })
788 }
789 })
790 }
791}
792
793impl<Counterpart, R> SessionBuilder<Counterpart, R, NonBlocking>
794where
795 Counterpart: HasPeer<Agent>,
796 R: RunWithConnectionTo<Counterpart>,
797{
798 pub fn block_task(self) -> SessionBuilder<Counterpart, R, Blocking> {
807 SessionBuilder {
808 connection: self.connection,
809 request: self.request,
810 dynamic_handler_registrations: self.dynamic_handler_registrations,
811 run: self.run,
812 block_state: PhantomData,
813 }
814 }
815}
816
817impl<Counterpart, R> SessionBuilder<Counterpart, R, Blocking>
818where
819 Counterpart: HasPeer<Agent>,
820 R: RunWithConnectionTo<Counterpart>,
821{
822 pub async fn run_until<T>(
833 self,
834 op: impl for<'runner> AsyncFnOnce(
835 ActiveSession<'runner, Counterpart>,
836 ) -> Result<T, crate::Error>,
837 ) -> Result<T, crate::Error> {
838 ensure_v1_session_protocol(&self.connection)?;
839
840 let Self {
841 connection,
842 request,
843 dynamic_handler_registrations,
844 run,
845 block_state: _,
846 } = self;
847
848 let response = connection
849 .send_request_to(Agent, request)
850 .block_task()
851 .await?;
852
853 let active_session = connection.attach_session(response, dynamic_handler_registrations)?;
854
855 run_until(
856 run.run_with_connection_to(connection.clone()),
857 op(active_session),
858 )
859 .await
860 }
861
862 pub async fn start_session(self) -> Result<ActiveSession<'static, Counterpart>, crate::Error>
872 where
873 R: 'static,
874 {
875 ensure_v1_session_protocol(&self.connection)?;
876
877 let Self {
878 connection,
879 request,
880 dynamic_handler_registrations,
881 run,
882 block_state: _,
883 } = self;
884
885 let (active_session_tx, active_session_rx) = oneshot::channel();
886
887 connection.clone().spawn(async move {
888 let response = connection
889 .send_request_to(Agent, request)
890 .block_task()
891 .await?;
892
893 connection.spawn(run.run_with_connection_to(connection.clone()))?;
894
895 let active_session =
896 connection.attach_session(response, dynamic_handler_registrations)?;
897
898 active_session_tx
899 .send(active_session)
900 .map_err(|_| crate::Error::internal_error())?;
901
902 Ok(())
903 })?;
904
905 active_session_rx
906 .await
907 .map_err(|_| crate::Error::internal_error())
908 }
909
910 pub async fn start_session_proxy(
928 self,
929 responder: Responder<NewSessionResponse>,
930 ) -> Result<SessionId, crate::Error>
931 where
932 Counterpart: HasPeer<Client>,
933 R: 'static,
934 {
935 let active_session = self.start_session().await?;
936 let session_id = active_session.session_id().clone();
937 responder.respond(active_session.response())?;
938 active_session.proxy_remaining_messages()?;
939 Ok(session_id)
940 }
941}
942
943#[derive(Debug)]
952pub struct ActiveSession<'runner, Link>
953where
954 Link: HasPeer<Agent>,
955{
956 session_id: SessionId,
957 update_rx: mpsc::UnboundedReceiver<SessionMessage>,
958 update_tx: mpsc::UnboundedSender<SessionMessage>,
959 modes: Option<SessionModeState>,
960 config_options: Option<Vec<SessionConfigOption>>,
961 meta: Option<serde_json::Map<String, serde_json::Value>>,
962 connection: ConnectionTo<Link>,
963
964 session_handler_registration: DynamicHandlerGuard<Link>,
968
969 mcp_handler_registrations: Vec<DynamicHandlerGuard<Link>>,
973
974 _runner: PhantomData<&'runner ()>,
976}
977
978#[non_exhaustive]
980#[derive(Debug)]
981#[allow(
982 clippy::large_enum_variant,
983 reason = "Dispatch messages vastly outnumber StopReason; boxing would add a heap allocation"
984)]
985pub enum SessionMessage {
986 SessionMessage(Dispatch),
989
990 StopReason(StopReason),
992}
993
994impl<Link> ActiveSession<'_, Link>
995where
996 Link: HasPeer<Agent>,
997{
998 pub fn session_id(&self) -> &SessionId {
1000 &self.session_id
1001 }
1002
1003 pub fn modes(&self) -> Option<&SessionModeState> {
1005 self.modes.as_ref()
1006 }
1007
1008 pub fn config_options(&self) -> Option<&[SessionConfigOption]> {
1010 self.config_options.as_deref()
1011 }
1012
1013 pub fn meta(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
1015 self.meta.as_ref()
1016 }
1017
1018 pub fn response(&self) -> NewSessionResponse {
1023 NewSessionResponse::new(self.session_id.clone())
1024 .modes(self.modes.clone())
1025 .config_options(self.config_options.clone())
1026 .meta(self.meta.clone())
1027 }
1028
1029 pub fn connection(&self) -> &ConnectionTo<Link> {
1031 &self.connection
1032 }
1033
1034 pub fn send_prompt(&mut self, prompt: impl ToString) -> Result<(), crate::Error> {
1036 let update_tx = self.update_tx.clone();
1037 self.connection
1038 .send_ordered_request_to(
1039 Agent,
1040 PromptRequest::new(self.session_id.clone(), vec![prompt.to_string().into()]),
1041 )
1042 .on_receiving_result(async move |result| {
1043 let PromptResponse { stop_reason, .. } = result?;
1044
1045 update_tx
1046 .unbounded_send(SessionMessage::StopReason(stop_reason))
1047 .map_err(crate::util::internal_error)?;
1048
1049 Ok(())
1050 })
1051 }
1052
1053 pub async fn read_update(&mut self) -> Result<SessionMessage, crate::Error> {
1055 use futures::StreamExt;
1056 let message =
1057 self.update_rx.next().await.ok_or_else(|| {
1058 crate::util::internal_error("session channel closed unexpectedly")
1059 })?;
1060
1061 Ok(message)
1062 }
1063
1064 pub async fn read_to_string(&mut self) -> Result<String, crate::Error> {
1067 let mut output = String::new();
1068 loop {
1069 let update = self.read_update().await?;
1070 tracing::trace!(?update, "read_to_string update");
1071 match update {
1072 SessionMessage::SessionMessage(dispatch) => MatchDispatch::new(dispatch)
1073 .if_notification(async |notif: SessionNotification| match notif.update {
1074 SessionUpdate::AgentMessageChunk(ContentChunk {
1075 content: ContentBlock::Text(text),
1076 ..
1077 }) => {
1078 output.push_str(&text.text);
1079 Ok(())
1080 }
1081 _ => Ok(()),
1082 })
1083 .await
1084 .otherwise_ignore()?,
1085 SessionMessage::StopReason(_stop_reason) => break,
1086 }
1087 }
1088 Ok(output)
1089 }
1090}
1091
1092impl<Link> ActiveSession<'static, Link>
1093where
1094 Link: HasPeer<Agent>,
1095{
1096 pub fn proxy_remaining_messages(self) -> Result<(), crate::Error>
1125 where
1126 Link: HasPeer<Client>,
1127 {
1128 let ActiveSession {
1130 session_id,
1131 mut update_rx,
1132 update_tx,
1133 connection,
1134 session_handler_registration,
1135 mcp_handler_registrations,
1136 modes: _,
1138 config_options: _,
1139 meta: _,
1140 _runner,
1141 } = self;
1142
1143 drop(session_handler_registration);
1147
1148 drop(update_tx);
1152
1153 while let Ok(message) = update_rx.try_recv() {
1157 match message {
1158 SessionMessage::SessionMessage(dispatch) => {
1159 connection.send_proxied_message_to(Client, dispatch)?;
1161 }
1162 SessionMessage::StopReason(_) => {
1163 }
1165 }
1166 }
1167
1168 connection
1172 .add_dynamic_handler(ProxySessionMessages::new(session_id))?
1173 .detach();
1174
1175 for registration in mcp_handler_registrations {
1177 registration.detach();
1178 }
1179
1180 Ok(())
1181 }
1182}
1183
1184struct ActiveSessionHandler {
1185 session_id: SessionId,
1186 update_tx: mpsc::UnboundedSender<SessionMessage>,
1187}
1188
1189impl ActiveSessionHandler {
1190 pub fn new(session_id: SessionId, update_tx: mpsc::UnboundedSender<SessionMessage>) -> Self {
1191 Self {
1192 session_id,
1193 update_tx,
1194 }
1195 }
1196}
1197
1198impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for ActiveSessionHandler
1199where
1200 Counterpart: HasPeer<Agent>,
1201{
1202 async fn handle_dispatch_from(
1203 &mut self,
1204 message: Dispatch,
1205 cx: ConnectionTo<Counterpart>,
1206 ) -> Result<Handled<Dispatch>, crate::Error> {
1207 tracing::trace!(
1209 ?message,
1210 handler_session_id = ?self.session_id,
1211 "ActiveSessionHandler::handle_dispatch"
1212 );
1213 MatchDispatchFrom::new(message, &cx)
1214 .if_dispatch_from(Agent, async |message| {
1215 if let Some(session_id) = message.get_session_id()? {
1216 tracing::trace!(
1217 message_session_id = ?session_id,
1218 handler_session_id = ?self.session_id,
1219 "ActiveSessionHandler::handle_dispatch"
1220 );
1221 if session_id == self.session_id {
1222 self.update_tx
1223 .unbounded_send(SessionMessage::SessionMessage(message))
1224 .map_err(crate::util::internal_error)?;
1225 return Ok(Handled::Yes);
1226 }
1227 }
1228
1229 Ok(Handled::No {
1231 message,
1232 retry: false,
1233 })
1234 })
1235 .await
1236 .done()
1237 }
1238
1239 fn describe_chain(&self) -> impl std::fmt::Debug {
1240 format!("ActiveSessionHandler({})", self.session_id)
1241 }
1242}
1243
1244#[cfg(not(feature = "unstable_protocol_v2"))]
1245#[allow(
1246 clippy::unnecessary_wraps,
1247 reason = "signature matches the feature-enabled protocol guard"
1248)]
1249fn ensure_v1_session_protocol<Counterpart: Role>(
1250 _connection: &ConnectionTo<Counterpart>,
1251) -> Result<(), crate::Error> {
1252 Ok(())
1253}
1254
1255#[cfg(feature = "unstable_protocol_v2")]
1256fn ensure_v1_session_protocol<Counterpart: Role>(
1257 connection: &ConnectionTo<Counterpart>,
1258) -> Result<(), crate::Error> {
1259 if connection.acp_protocol_version() != Some(crate::schema::ProtocolVersion::V2) {
1260 return Ok(());
1261 }
1262
1263 Err(crate::Error::invalid_request().data(
1264 "stable session builders use ACP protocol v1 types, but this is a protocol v2 connection; \
1265 use the `V2ConnectionTo` supplied to `Client.v2()` callbacks",
1266 ))
1267}