1use std::{future::Future, path::Path};
2
3use futures::{
4 channel::oneshot,
5 future::{self, Either},
6};
7
8use crate::{
9 Agent, Client, ConnectionTo, DynamicHandlerGuard, JsonRpcRequest, Responder, SentRequest,
10 V2ConnectionTo,
11 jsonrpc::run::{NullRun, RunWithConnectionTo},
12 role::{HasPeer, acp::ProxySessionMessages},
13 schema::v2,
14};
15
16#[cfg(feature = "unstable_mcp_over_acp")]
17use crate::{jsonrpc::run::ChainRun, mcp_server::McpServer};
18
19async fn run_pending_session_setup<Counterpart, Run>(
20 connection: ConnectionTo<Counterpart>,
21 run: Run,
22 started_tx: oneshot::Sender<Result<(), crate::Error>>,
23 promotion_rx: oneshot::Receiver<()>,
24) -> Result<(), crate::Error>
25where
26 Counterpart: HasPeer<Agent>,
27 Run: RunWithConnectionTo<Counterpart>,
28{
29 let mut run = Box::pin(run.run_with_connection_to(connection));
30 let first_poll =
31 future::poll_fn(|cx| std::task::Poll::Ready(std::future::Future::poll(run.as_mut(), cx)))
32 .await;
33 let readiness = match &first_poll {
34 std::task::Poll::Ready(result) => result.clone(),
35 std::task::Poll::Pending => Ok(()),
36 };
37 drop(started_tx.send(readiness));
38
39 match first_poll {
40 std::task::Poll::Ready(Ok(())) => {
41 let _ = promotion_rx.await;
42 Ok(())
43 }
44 std::task::Poll::Ready(Err(_)) => Ok(()),
48 std::task::Poll::Pending => match future::select(run, promotion_rx).await {
49 Either::Left((result, promotion_rx)) => {
50 result?;
55 let _ = promotion_rx.await;
56 Ok(())
57 }
58 Either::Right((Ok(()), run)) => run.await,
59 Either::Right((Err(_), _run)) => Ok(()),
60 },
61 }
62}
63
64fn send_session_setup<Counterpart, Request, Run>(
65 connection: V2ConnectionTo<Counterpart>,
66 request: Request,
67 dynamic_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
68 run: Run,
69 ordered: bool,
70) -> SentRequest<Request::Response>
71where
72 Counterpart: HasPeer<Agent>,
73 Request: JsonRpcRequest + 'static,
74 Request::Response: 'static,
75 Run: RunWithConnectionTo<Counterpart> + 'static,
76{
77 let raw_connection = connection.raw_connection().clone();
78 if dynamic_handler_registrations.is_empty() {
79 drop(run);
80 if ordered {
81 raw_connection.send_ordered_request_to(Agent, request)
82 } else {
83 raw_connection.send_request_to(Agent, request)
84 }
85 } else {
86 let handlers_ready = raw_connection.dynamic_handler_barrier();
87 let (runner_started_tx, runner_started_rx) = oneshot::channel();
88 let (promotion_tx, promotion_rx) = oneshot::channel();
89 let runner_started = match raw_connection.spawn(run_pending_session_setup(
90 raw_connection.clone(),
91 run,
92 runner_started_tx,
93 promotion_rx,
94 )) {
95 Ok(()) => Either::Left(async move {
96 runner_started_rx.await.map_err(|error| {
97 crate::util::internal_error(format!(
98 "session setup runner stopped before its initial poll: {error}"
99 ))
100 })?
101 }),
102 Err(error) => Either::Right(future::ready(Err(error))),
103 };
104 let readiness = async move {
105 future::try_join(handlers_ready, runner_started).await?;
106 Ok(())
107 };
108 let response_hook = move |_response: &Request::Response| {
109 promotion_tx.send(()).map_err(|()| {
110 crate::util::internal_error("session setup runner stopped before setup completed")
111 })?;
112 dynamic_handler_registrations
113 .into_iter()
114 .for_each(DynamicHandlerGuard::detach);
115 Ok(())
116 };
117
118 if ordered {
119 raw_connection.send_ordered_request_to_with_response_hook_after(
120 Agent,
121 request,
122 readiness,
123 response_hook,
124 )
125 } else {
126 raw_connection.send_request_to_with_response_hook_after(
127 Agent,
128 request,
129 readiness,
130 response_hook,
131 )
132 }
133 }
134}
135
136impl<Counterpart> V2ConnectionTo<Counterpart>
137where
138 Counterpart: HasPeer<Agent>,
139{
140 pub fn build_session(&self, cwd: impl AsRef<Path>) -> V2SessionBuilder<Counterpart> {
142 V2SessionBuilder::new(self, v2::NewSessionRequest::new(cwd.as_ref()))
143 }
144
145 pub fn build_session_cwd(&self) -> Result<V2SessionBuilder<Counterpart>, crate::Error> {
149 let cwd = std::env::current_dir().map_err(|error| {
150 crate::Error::internal_error().data(format!("cannot get current directory: {error}"))
151 })?;
152 Ok(self.build_session(cwd))
153 }
154
155 pub fn build_session_from(
157 &self,
158 request: v2::NewSessionRequest,
159 ) -> V2SessionBuilder<Counterpart> {
160 V2SessionBuilder::new(self, request)
161 }
162
163 #[cfg(feature = "unstable_session_fork")]
169 pub fn fork_session(
170 &self,
171 session_id: impl Into<v2::SessionId>,
172 cwd: impl AsRef<Path>,
173 ) -> V2ForkSessionBuilder<Counterpart> {
174 self.fork_session_from(v2::ForkSessionRequest::new(session_id, cwd.as_ref()))
175 }
176
177 #[cfg(feature = "unstable_session_fork")]
183 pub fn fork_session_from(
184 &self,
185 request: v2::ForkSessionRequest,
186 ) -> V2ForkSessionBuilder<Counterpart> {
187 V2ForkSessionBuilder::new(self, request)
188 }
189
190 pub fn resume_session(
196 &self,
197 session_id: impl Into<v2::SessionId>,
198 cwd: impl AsRef<Path>,
199 ) -> V2ResumeSessionBuilder<Counterpart> {
200 self.resume_session_from(v2::ResumeSessionRequest::new(session_id, cwd.as_ref()))
201 }
202
203 pub fn resume_session_from(
210 &self,
211 request: v2::ResumeSessionRequest,
212 ) -> V2ResumeSessionBuilder<Counterpart> {
213 V2ResumeSessionBuilder::new(self, request)
214 }
215}
216
217#[must_use = "call `start_session` or `on_proxy_session_start` to send the `session/new` request"]
228#[derive(Debug)]
229pub struct V2SessionBuilder<Counterpart, Run = NullRun>
230where
231 Counterpart: HasPeer<Agent>,
232 Run: RunWithConnectionTo<Counterpart>,
233{
234 connection: V2ConnectionTo<Counterpart>,
235 request: v2::NewSessionRequest,
236 dynamic_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
237 run: Run,
238}
239
240impl<Counterpart> V2SessionBuilder<Counterpart, NullRun>
241where
242 Counterpart: HasPeer<Agent>,
243{
244 fn new(connection: &V2ConnectionTo<Counterpart>, request: v2::NewSessionRequest) -> Self {
245 Self {
246 connection: connection.clone(),
247 request,
248 dynamic_handler_registrations: Vec::new(),
249 run: NullRun,
250 }
251 }
252}
253
254impl<Counterpart, Run> V2SessionBuilder<Counterpart, Run>
255where
256 Counterpart: HasPeer<Agent>,
257 Run: RunWithConnectionTo<Counterpart>,
258{
259 #[cfg(feature = "unstable_mcp_over_acp")]
267 pub fn with_mcp_server<McpRun>(
268 mut self,
269 mcp_server: McpServer<Counterpart, McpRun>,
270 ) -> Result<V2SessionBuilder<Counterpart, ChainRun<Run, McpRun>>, crate::Error>
271 where
272 McpRun: RunWithConnectionTo<Counterpart>,
273 {
274 let (handler, mcp_run) = mcp_server.into_v2_handler_and_runner();
275 self.dynamic_handler_registrations
276 .push(handler.into_dynamic_handler(&mut self.request.mcp_servers, &self.connection)?);
277 Ok(V2SessionBuilder {
278 connection: self.connection,
279 request: self.request,
280 dynamic_handler_registrations: self.dynamic_handler_registrations,
281 run: ChainRun::new(self.run, mcp_run),
282 })
283 }
284
285 fn send_new_session(self, ordered: bool) -> SentRequest<v2::NewSessionResponse>
286 where
287 Run: 'static,
288 {
289 let Self {
290 connection,
291 request,
292 dynamic_handler_registrations,
293 run,
294 } = self;
295 send_session_setup(
296 connection,
297 request,
298 dynamic_handler_registrations,
299 run,
300 ordered,
301 )
302 }
303
304 pub fn start_session(self) -> SentRequest<OpenedV2Session<Counterpart, v2::NewSessionResponse>>
317 where
318 Run: 'static,
319 {
320 let session_connection = self.connection.clone();
321 self.send_new_session(false).map(move |response| {
322 let session = V2Session {
323 session_id: response.session_id.clone(),
324 connection: session_connection,
325 };
326 Ok(OpenedV2Session { session, response })
327 })
328 }
329
330 pub fn on_proxy_session_start<F, Fut>(
342 self,
343 responder: Responder<v2::NewSessionResponse>,
344 op: F,
345 ) -> Result<(), crate::Error>
346 where
347 Counterpart: HasPeer<Client>,
348 Run: 'static,
349 F: FnOnce(OpenedV2Session<Counterpart, v2::NewSessionResponse>) -> Fut + Send + 'static,
350 Fut: Future<Output = Result<(), crate::Error>> + Send,
351 {
352 let session_connection = self.connection.clone();
353 self.send_new_session(true)
354 .forward_cancellation_from(responder.cancellation())
355 .on_receiving_ok_result(responder, async move |response, responder| {
356 let session_id = response.session_id.clone();
357 let raw_connection = session_connection.raw_connection();
358 let route = match raw_connection.add_dynamic_handler(ProxySessionMessages::new(
359 crate::schema::v1::SessionId::from(session_id.0.clone()),
360 )) {
361 Ok(route) => route,
362 Err(error) => return responder.respond_with_error(error),
363 };
364
365 let opened = OpenedV2Session {
366 session: V2Session {
367 session_id,
368 connection: session_connection.clone(),
369 },
370 response: response.clone(),
371 };
372 responder.respond(response)?;
373 route.detach();
374 raw_connection.spawn(async move { op(opened).await })
375 })
376 }
377}
378
379#[cfg(feature = "unstable_session_fork")]
392#[must_use = "call `start_session` or `on_proxy_session_start` to send the `session/fork` request"]
393#[derive(Debug)]
394pub struct V2ForkSessionBuilder<Counterpart, Run = NullRun>
395where
396 Counterpart: HasPeer<Agent>,
397 Run: RunWithConnectionTo<Counterpart>,
398{
399 connection: V2ConnectionTo<Counterpart>,
400 request: v2::ForkSessionRequest,
401 dynamic_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
402 run: Run,
403}
404
405#[cfg(feature = "unstable_session_fork")]
406impl<Counterpart> V2ForkSessionBuilder<Counterpart, NullRun>
407where
408 Counterpart: HasPeer<Agent>,
409{
410 fn new(connection: &V2ConnectionTo<Counterpart>, request: v2::ForkSessionRequest) -> Self {
411 Self {
412 connection: connection.clone(),
413 request,
414 dynamic_handler_registrations: Vec::new(),
415 run: NullRun,
416 }
417 }
418}
419
420#[cfg(feature = "unstable_session_fork")]
421impl<Counterpart, Run> V2ForkSessionBuilder<Counterpart, Run>
422where
423 Counterpart: HasPeer<Agent>,
424 Run: RunWithConnectionTo<Counterpart>,
425{
426 #[cfg(feature = "unstable_mcp_over_acp")]
435 pub fn with_mcp_server<McpRun>(
436 mut self,
437 mcp_server: McpServer<Counterpart, McpRun>,
438 ) -> Result<V2ForkSessionBuilder<Counterpart, ChainRun<Run, McpRun>>, crate::Error>
439 where
440 McpRun: RunWithConnectionTo<Counterpart>,
441 {
442 let (handler, mcp_run) = mcp_server.into_v2_handler_and_runner();
443 self.dynamic_handler_registrations
444 .push(handler.into_dynamic_handler(&mut self.request.mcp_servers, &self.connection)?);
445 Ok(V2ForkSessionBuilder {
446 connection: self.connection,
447 request: self.request,
448 dynamic_handler_registrations: self.dynamic_handler_registrations,
449 run: ChainRun::new(self.run, mcp_run),
450 })
451 }
452
453 fn send_fork_session(self, ordered: bool) -> SentRequest<v2::ForkSessionResponse>
454 where
455 Run: 'static,
456 {
457 let Self {
458 connection,
459 request,
460 dynamic_handler_registrations,
461 run,
462 } = self;
463 send_session_setup(
464 connection,
465 request,
466 dynamic_handler_registrations,
467 run,
468 ordered,
469 )
470 }
471
472 pub fn start_session(self) -> SentRequest<OpenedV2Session<Counterpart, v2::ForkSessionResponse>>
486 where
487 Run: 'static,
488 {
489 let session_connection = self.connection.clone();
490 self.send_fork_session(false).map(move |response| {
491 let session = V2Session {
492 session_id: response.session_id.clone(),
493 connection: session_connection,
494 };
495 Ok(OpenedV2Session { session, response })
496 })
497 }
498
499 pub fn on_proxy_session_start<F, Fut>(
512 self,
513 responder: Responder<v2::ForkSessionResponse>,
514 op: F,
515 ) -> Result<(), crate::Error>
516 where
517 Counterpart: HasPeer<Client>,
518 Run: 'static,
519 F: FnOnce(OpenedV2Session<Counterpart, v2::ForkSessionResponse>) -> Fut + Send + 'static,
520 Fut: Future<Output = Result<(), crate::Error>> + Send,
521 {
522 let session_connection = self.connection.clone();
523 self.send_fork_session(true)
524 .forward_cancellation_from(responder.cancellation())
525 .on_receiving_ok_result(responder, async move |response, responder| {
526 let session_id = response.session_id.clone();
527 let raw_connection = session_connection.raw_connection();
528 let route = match raw_connection.add_dynamic_handler(ProxySessionMessages::new(
529 crate::schema::v1::SessionId::from(session_id.0.clone()),
530 )) {
531 Ok(route) => route,
532 Err(error) => return responder.respond_with_error(error),
533 };
534
535 let opened = OpenedV2Session {
536 session: V2Session {
537 session_id,
538 connection: session_connection.clone(),
539 },
540 response: response.clone(),
541 };
542 responder.respond(response)?;
543 route.detach();
544 raw_connection.spawn(async move { op(opened).await })
545 })
546 }
547}
548
549#[must_use = "call `start_session` or `on_proxy_session_start` to send the `session/resume` request"]
561#[derive(Debug)]
562pub struct V2ResumeSessionBuilder<Counterpart, Run = NullRun>
563where
564 Counterpart: HasPeer<Agent>,
565 Run: RunWithConnectionTo<Counterpart>,
566{
567 connection: V2ConnectionTo<Counterpart>,
568 request: v2::ResumeSessionRequest,
569 dynamic_handler_registrations: Vec<DynamicHandlerGuard<Counterpart>>,
570 run: Run,
571}
572
573impl<Counterpart> V2ResumeSessionBuilder<Counterpart, NullRun>
574where
575 Counterpart: HasPeer<Agent>,
576{
577 fn new(connection: &V2ConnectionTo<Counterpart>, request: v2::ResumeSessionRequest) -> Self {
578 Self {
579 connection: connection.clone(),
580 request,
581 dynamic_handler_registrations: Vec::new(),
582 run: NullRun,
583 }
584 }
585}
586
587impl<Counterpart, Run> V2ResumeSessionBuilder<Counterpart, Run>
588where
589 Counterpart: HasPeer<Agent>,
590 Run: RunWithConnectionTo<Counterpart>,
591{
592 #[cfg(feature = "unstable_mcp_over_acp")]
601 pub fn with_mcp_server<McpRun>(
602 mut self,
603 mcp_server: McpServer<Counterpart, McpRun>,
604 ) -> Result<V2ResumeSessionBuilder<Counterpart, ChainRun<Run, McpRun>>, crate::Error>
605 where
606 McpRun: RunWithConnectionTo<Counterpart>,
607 {
608 let (handler, mcp_run) = mcp_server.into_v2_handler_and_runner();
609 self.dynamic_handler_registrations
610 .push(handler.into_dynamic_handler(&mut self.request.mcp_servers, &self.connection)?);
611 Ok(V2ResumeSessionBuilder {
612 connection: self.connection,
613 request: self.request,
614 dynamic_handler_registrations: self.dynamic_handler_registrations,
615 run: ChainRun::new(self.run, mcp_run),
616 })
617 }
618
619 fn send_resume_session(self, ordered: bool) -> SentRequest<v2::ResumeSessionResponse>
620 where
621 Run: 'static,
622 {
623 let Self {
624 connection,
625 request,
626 dynamic_handler_registrations,
627 run,
628 } = self;
629 send_session_setup(
630 connection,
631 request,
632 dynamic_handler_registrations,
633 run,
634 ordered,
635 )
636 }
637
638 pub fn start_session(
651 self,
652 ) -> SentRequest<OpenedV2Session<Counterpart, v2::ResumeSessionResponse>>
653 where
654 Run: 'static,
655 {
656 let session_id = self.request.session_id.clone();
657 let session_connection = self.connection.clone();
658 self.send_resume_session(false).map(move |response| {
659 let session = V2Session {
660 session_id,
661 connection: session_connection,
662 };
663 Ok(OpenedV2Session { session, response })
664 })
665 }
666
667 pub fn on_proxy_session_start<F, Fut>(
681 mut self,
682 responder: Responder<v2::ResumeSessionResponse>,
683 op: F,
684 ) -> Result<(), crate::Error>
685 where
686 Counterpart: HasPeer<Client>,
687 Run: 'static,
688 F: FnOnce(OpenedV2Session<Counterpart, v2::ResumeSessionResponse>) -> Fut + Send + 'static,
689 Fut: Future<Output = Result<(), crate::Error>> + Send,
690 {
691 let session_id = self.request.session_id.clone();
692 let session_connection = self.connection.clone();
693 self.dynamic_handler_registrations.push(
694 session_connection
695 .raw_connection()
696 .add_dynamic_handler(ProxySessionMessages::new(
697 crate::schema::v1::SessionId::from(session_id.0.clone()),
698 ))?,
699 );
700
701 self.send_resume_session(true)
702 .forward_cancellation_from(responder.cancellation())
703 .on_receiving_ok_result(responder, async move |response, responder| {
704 let opened = OpenedV2Session {
705 session: V2Session {
706 session_id,
707 connection: session_connection.clone(),
708 },
709 response: response.clone(),
710 };
711 responder.respond(response)?;
712 session_connection
713 .raw_connection()
714 .spawn(async move { op(opened).await })
715 })
716 }
717}
718
719#[derive(Debug)]
726pub struct OpenedV2Session<Link, Response>
727where
728 Link: HasPeer<Agent>,
729{
730 session: V2Session<Link>,
731 response: Response,
732}
733
734impl<Link, Response> OpenedV2Session<Link, Response>
735where
736 Link: HasPeer<Agent>,
737{
738 pub fn session(&self) -> &V2Session<Link> {
740 &self.session
741 }
742
743 pub fn response(&self) -> &Response {
745 &self.response
746 }
747
748 pub fn into_parts(self) -> (V2Session<Link>, Response) {
750 (self.session, self.response)
751 }
752
753 pub fn into_session(self) -> V2Session<Link> {
755 self.session
756 }
757}
758
759#[derive(Debug, Clone)]
766pub struct V2Session<Link>
767where
768 Link: HasPeer<Agent>,
769{
770 session_id: v2::SessionId,
771 connection: V2ConnectionTo<Link>,
772}
773
774impl<Link> V2Session<Link>
775where
776 Link: HasPeer<Agent>,
777{
778 pub fn session_id(&self) -> &v2::SessionId {
780 &self.session_id
781 }
782
783 pub fn connection(&self) -> &V2ConnectionTo<Link> {
785 &self.connection
786 }
787
788 pub fn send_prompt(&self, prompt: impl ToString) -> SentRequest<v2::PromptResponse> {
794 self.send_prompt_blocks(vec![prompt.to_string().into()])
795 }
796
797 pub fn send_prompt_blocks(
803 &self,
804 prompt: Vec<v2::ContentBlock>,
805 ) -> SentRequest<v2::PromptResponse> {
806 self.connection.send_request_to(
807 Agent,
808 v2::PromptRequest::new(self.session_id.clone(), prompt),
809 )
810 }
811
812 pub fn cancel_active_work(&self) -> Result<(), crate::Error> {
821 self.connection.send_notification_to(
822 Agent,
823 v2::CancelSessionNotification::new(self.session_id.clone()),
824 )
825 }
826
827 pub fn set_config_option(
832 &self,
833 config_id: impl Into<v2::SessionConfigId>,
834 value: impl Into<v2::SessionConfigOptionValue>,
835 ) -> SentRequest<v2::SetSessionConfigOptionResponse> {
836 self.connection.send_request_to(
837 Agent,
838 v2::SetSessionConfigOptionRequest::new(self.session_id.clone(), config_id, value),
839 )
840 }
841
842 pub fn close(&self) -> SentRequest<v2::CloseSessionResponse> {
847 self.connection
848 .send_request_to(Agent, v2::CloseSessionRequest::new(self.session_id.clone()))
849 }
850}