1use std::{fmt::Debug, future::Future, hash::Hash};
2
3#[cfg(feature = "unstable_protocol_v2")]
4use futures::{StreamExt as _, future};
5#[cfg(feature = "unstable_protocol_v2")]
6use serde::{Serialize, de::DeserializeOwned};
7
8#[cfg(feature = "unstable_protocol_v2")]
9use crate::DynConnectTo;
10use crate::jsonrpc::{Builder, handlers::NullHandler, run::NullRun};
11#[cfg(feature = "unstable_protocol_v2")]
12use crate::jsonrpc::{
13 TransportBatch, TransportBatchEntry, TransportFrame, V2Builder, is_response_only_shape,
14 raw_is_response_only_shape,
15};
16use crate::role::{HasPeer, RemoteStyle};
17#[cfg(not(feature = "unstable_protocol_v2"))]
18use crate::schema::InitializeProxyRequest;
19use crate::schema::METHOD_INITIALIZE_PROXY;
20#[cfg(feature = "unstable_protocol_v2")]
21use crate::schema::v1::RequestId;
22use crate::schema::v1::{InitializeRequest, SessionId};
23#[cfg(not(feature = "unstable_protocol_v2"))]
24use crate::schema::v1::{NewSessionRequest, NewSessionResponse};
25#[cfg(feature = "unstable_protocol_v2")]
26use crate::schema::{ProtocolVersion, v2};
27use crate::util::MatchDispatchFrom;
28#[cfg(feature = "unstable_protocol_v2")]
29use crate::{
30 Channel, RawJsonRpcError, RawJsonRpcMessage, RawJsonRpcParams,
31 RawJsonRpcResponse as RpcResponse,
32};
33use crate::{ConnectTo, ConnectionTo, Dispatch, HandleDispatchFrom, Handled, Role, RoleId};
34
35#[cfg(feature = "unstable_protocol_v2")]
36#[derive(serde::Deserialize)]
37struct NewSessionResponseEnvelope {
38 #[serde(rename = "sessionId")]
39 session_id: SessionId,
40}
41
42#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct Client;
47
48impl Role for Client {
49 type Counterpart = Agent;
50
51 fn builder(self) -> Builder<Self> {
52 Builder::new(self).v1_client()
53 }
54
55 fn default_handle_dispatch_from(
56 &self,
57 message: Dispatch,
58 _connection: ConnectionTo<Client>,
59 ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
60 std::future::ready(Ok(Handled::No {
61 message,
62 retry: false,
63 }))
64 }
65
66 fn role_id(&self) -> RoleId {
67 RoleId::from_singleton(self)
68 }
69
70 fn counterpart(&self) -> Self::Counterpart {
71 Agent
72 }
73}
74
75impl Client {
76 pub fn builder(self) -> Builder<Client, NullHandler, NullRun> {
78 <Self as Role>::builder(self)
79 }
80
81 #[cfg(feature = "unstable_protocol_v2")]
89 pub fn v2(self) -> V2Builder<Client, NullHandler, NullRun> {
90 self.builder().v2_client()
91 }
92
93 #[cfg(feature = "unstable_protocol_v2")]
106 #[must_use]
107 pub fn protocol_connector(self) -> ClientProtocolConnector {
108 ClientProtocolConnector::new()
109 }
110
111 pub async fn connect_with<R>(
116 self,
117 agent: impl ConnectTo<Client>,
118 main_fn: impl AsyncFnOnce(ConnectionTo<Agent>) -> Result<R, crate::Error>,
119 ) -> Result<R, crate::Error> {
120 self.builder().connect_with(agent, main_fn).await
121 }
122}
123
124#[cfg(feature = "unstable_protocol_v2")]
131#[derive(Debug, Default)]
132pub struct ClientProtocolConnector {
133 v1: Option<DynConnectToFactory<Agent>>,
134 v2: Option<DynConnectToFactory<Agent>>,
135}
136
137#[cfg(feature = "unstable_protocol_v2")]
138impl ClientProtocolConnector {
139 #[must_use]
141 pub fn new() -> Self {
142 Self::default()
143 }
144
145 #[must_use]
147 pub fn with_v1<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
148 where
149 C: ConnectTo<Agent>,
150 {
151 self.v1 = Some(DynConnectToFactory::new(client));
152 self
153 }
154
155 #[must_use]
157 pub fn with_v2<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
158 where
159 C: ConnectTo<Agent>,
160 {
161 self.v2 = Some(DynConnectToFactory::new(client));
162 self
163 }
164
165 pub async fn connect_to<C>(
168 mut self,
169 mut agent: impl FnMut() -> C + Send + 'static,
170 ) -> Result<(), crate::Error>
171 where
172 C: ConnectTo<Client>,
173 {
174 let supported = SupportedClientProtocols {
175 v1: self.v1.is_some(),
176 v2: self.v2.is_some(),
177 };
178 let Some(selected) = supported.highest_configured() else {
179 return Err(crate::Error::invalid_request()
180 .data("client protocol connector has no configured ACP protocol implementations"));
181 };
182
183 match selected {
184 ClientProtocol::V1 => {
185 let client = self
186 .v1
187 .as_mut()
188 .expect("selected protocol is configured")
189 .create();
190 connect_client_protocol(ClientProtocol::V1, client, agent()).await
191 }
192 ClientProtocol::V2 => {
193 let client = self
194 .v2
195 .as_mut()
196 .expect("selected protocol is configured")
197 .create();
198 let agent_connection = RunningProtocolPeer::new(agent());
199 let (client, initialize) =
200 start_client_protocol(ClientProtocol::V2, client).await?;
201 let v2_initialize_as_v1 = normalize_v2_initialize_params_for_reuse(&initialize);
205 let (client, agent_connection, initialize_response) =
206 send_initialize_and_receive(client, agent_connection, initialize).await?;
207
208 if initialize_response_negotiated_v1(&initialize_response)
209 && let Some(v1) = self.v1.as_mut()
210 {
211 let fallback_client = v1.create();
212 let (fallback_client, fallback_initialize) =
213 start_client_protocol(ClientProtocol::V1, fallback_client).await?;
214 let v1_initialize =
215 validated_initialize_params::<InitializeRequest>(&fallback_initialize)?;
216
217 if v2_initialize_as_v1
218 .as_ref()
219 .is_ok_and(|v2_initialize| v2_initialize == &v1_initialize)
220 {
221 let fallback_response = initialize_response.with_id(
222 initialize_request_id(&fallback_initialize)
223 .expect("validated initialize request has an id"),
224 );
225 drop(client);
230 fallback_client.send(fallback_response)?;
231 return pipe_protocol_peers_until_done(fallback_client, agent_connection)
232 .await;
233 }
234
235 drop((
239 client,
240 fallback_client,
241 agent_connection,
242 initialize_response,
243 ));
244 return connect_client_protocol(ClientProtocol::V1, v1.create(), agent()).await;
245 }
246
247 client.send(initialize_response.into_message())?;
248 pipe_protocol_peers_until_done(client, agent_connection).await
249 }
250 }
251 }
252}
253
254#[cfg(feature = "unstable_protocol_v2")]
255struct DynConnectToFactory<R: Role> {
256 inner: Box<dyn FnMut() -> DynConnectTo<R> + Send>,
257}
258
259#[cfg(feature = "unstable_protocol_v2")]
260impl<R: Role> DynConnectToFactory<R> {
261 fn new<C>(mut factory: impl FnMut() -> C + Send + 'static) -> Self
262 where
263 C: ConnectTo<R>,
264 {
265 Self {
266 inner: Box::new(move || DynConnectTo::new(factory())),
267 }
268 }
269
270 fn create(&mut self) -> DynConnectTo<R> {
271 (self.inner)()
272 }
273}
274
275#[cfg(feature = "unstable_protocol_v2")]
276impl<R: Role> Debug for DynConnectToFactory<R> {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 f.debug_struct("DynConnectToFactory")
279 .finish_non_exhaustive()
280 }
281}
282
283impl HasPeer<Client> for Client {
284 fn remote_style(&self, _peer: Client) -> RemoteStyle {
285 RemoteStyle::Counterpart
286 }
287}
288
289#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
294pub struct Agent;
295
296impl Role for Agent {
297 type Counterpart = Client;
298
299 fn builder(self) -> Builder<Self> {
300 Builder::new(self).v1_agent()
301 }
302
303 fn role_id(&self) -> RoleId {
304 RoleId::from_singleton(self)
305 }
306
307 fn counterpart(&self) -> Self::Counterpart {
308 Client
309 }
310
311 async fn default_handle_dispatch_from(
312 &self,
313 message: Dispatch,
314 connection: ConnectionTo<Agent>,
315 ) -> Result<Handled<Dispatch>, crate::Error> {
316 MatchDispatchFrom::new(message, &connection)
317 .if_dispatch_from(Agent, async |message: Dispatch| {
318 #[cfg(feature = "unstable_protocol_v2")]
326 let retry = message.has_session_id()
327 && connection.acp_protocol_version()
328 != Some(crate::schema::ProtocolVersion::V2);
329 #[cfg(not(feature = "unstable_protocol_v2"))]
330 let retry = message.has_session_id();
331 Ok(Handled::No { message, retry })
332 })
333 .await
334 .done()
335 }
336}
337
338impl Agent {
339 pub fn builder(self) -> Builder<Agent, NullHandler, NullRun> {
341 <Self as Role>::builder(self)
342 }
343
344 #[cfg(feature = "unstable_protocol_v2")]
364 pub fn v2(self) -> V2Builder<Agent, NullHandler, NullRun> {
365 self.builder().v2_agent()
366 }
367
368 #[cfg(feature = "unstable_protocol_v2")]
389 #[must_use]
390 pub fn protocol_router(self) -> AgentProtocolRouter {
391 AgentProtocolRouter::new()
392 }
393}
394
395#[cfg(feature = "unstable_protocol_v2")]
401#[derive(Debug, Default)]
402pub struct AgentProtocolRouter {
403 v1: Option<DynConnectTo<Client>>,
404 v2: Option<DynConnectTo<Client>>,
405}
406
407#[cfg(feature = "unstable_protocol_v2")]
408impl AgentProtocolRouter {
409 #[must_use]
411 pub fn new() -> Self {
412 Self::default()
413 }
414
415 #[must_use]
417 pub fn with_v1(mut self, agent: impl ConnectTo<Client>) -> Self {
418 self.v1 = Some(DynConnectTo::new(agent));
419 self
420 }
421
422 #[must_use]
424 pub fn with_v2(mut self, agent: impl ConnectTo<Client>) -> Self {
425 self.v2 = Some(DynConnectTo::new(agent));
426 self
427 }
428}
429
430#[cfg(feature = "unstable_protocol_v2")]
431impl ConnectTo<Client> for AgentProtocolRouter {
432 async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), crate::Error> {
433 let supported = SupportedProtocols {
434 v1: self.v1.is_some(),
435 v2: self.v2.is_some(),
436 };
437 let mut client = RunningProtocolPeer::new(client);
438 let (first_frame, client, selected) = loop {
439 let Some((mut frame, next_client)) = client.next_frame().await? else {
440 return Ok(());
441 };
442 let message = match initialize_message_mut(&mut frame) {
443 Ok(Some(message)) => message,
444 Ok(None) => {
445 client = next_client;
446 continue;
447 }
448 Err(error) => return reject_initialize(next_client, &frame, error).await,
449 };
450 let selected = match select_agent_protocol(message, supported) {
451 Ok(selected) => selected,
452 Err(error) => return reject_initialize(next_client, &frame, error).await,
453 };
454 break (frame, next_client, selected);
455 };
456 let Some(agent) = selected.take_agent(self) else {
457 let error = selected.unsupported_error(supported);
458 return reject_initialize(client, &first_frame, error).await;
459 };
460
461 let agent = RunningProtocolPeer::new(agent);
462 agent.send_frame(first_frame)?;
463 pipe_protocol_peers_until_done(client, agent).await
464 }
465}
466
467#[cfg(feature = "unstable_protocol_v2")]
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
469enum SelectedProtocol {
470 V1,
471 V2,
472}
473
474#[cfg(feature = "unstable_protocol_v2")]
475impl SelectedProtocol {
476 fn take_agent(self, agent: AgentProtocolRouter) -> Option<DynConnectTo<Client>> {
477 match self {
478 Self::V1 => agent.v1,
479 Self::V2 => agent.v2,
480 }
481 }
482
483 fn version(self) -> ProtocolVersion {
484 match self {
485 Self::V1 => ProtocolVersion::V1,
486 Self::V2 => ProtocolVersion::V2,
487 }
488 }
489
490 fn name(self) -> &'static str {
491 match self {
492 Self::V1 => "1",
493 Self::V2 => "2",
494 }
495 }
496
497 fn unsupported_error(self, supported: SupportedProtocols) -> crate::Error {
498 crate::Error::invalid_request().data(format!(
499 "ACP protocol version {} is not configured; this endpoint supports {}",
500 self.name(),
501 supported.description()
502 ))
503 }
504}
505
506#[cfg(feature = "unstable_protocol_v2")]
507#[derive(Debug, Clone, Copy, PartialEq, Eq)]
508struct SupportedProtocols {
509 v1: bool,
510 v2: bool,
511}
512
513#[cfg(feature = "unstable_protocol_v2")]
514impl SupportedProtocols {
515 fn select_for_agent(self, requested: ProtocolVersion) -> Option<SelectedProtocol> {
516 if self.v1 && !self.v2 {
517 return Some(SelectedProtocol::V1);
519 }
520 if self.v2 && requested >= ProtocolVersion::V2 {
521 return Some(SelectedProtocol::V2);
522 }
523
524 if self.v1 && requested >= ProtocolVersion::V1 {
525 return Some(SelectedProtocol::V1);
526 }
527
528 None
529 }
530
531 fn exact(self, requested: ProtocolVersion) -> Option<SelectedProtocol> {
532 if self.v1 && requested == ProtocolVersion::V1 {
533 Some(SelectedProtocol::V1)
534 } else if self.v2 && requested == ProtocolVersion::V2 {
535 Some(SelectedProtocol::V2)
536 } else {
537 None
538 }
539 }
540
541 fn versions(self) -> Vec<ProtocolVersion> {
542 [
543 self.v1.then_some(ProtocolVersion::V1),
544 self.v2.then_some(ProtocolVersion::V2),
545 ]
546 .into_iter()
547 .flatten()
548 .collect()
549 }
550
551 fn description(self) -> String {
552 match (self.v1, self.v2) {
553 (true, true) => "ACP protocol versions 1 and 2".into(),
554 (true, false) => "ACP protocol version 1".into(),
555 (false, true) => "ACP protocol version 2".into(),
556 (false, false) => "no ACP protocol versions".into(),
557 }
558 }
559}
560
561#[cfg(feature = "unstable_protocol_v2")]
562fn select_agent_protocol(
563 message: &mut RawJsonRpcMessage,
564 supported: SupportedProtocols,
565) -> Result<SelectedProtocol, crate::Error> {
566 let RawJsonRpcMessage::Request(request) = message else {
567 return Err(
568 crate::Error::invalid_request().data("first ACP message must be an initialize request")
569 );
570 };
571
572 if request.method.as_ref() != "initialize" {
573 return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
574 }
575
576 let Some(RawJsonRpcParams::Object(params)) = &mut request.params else {
577 return Err(invalid_initialize_protocol_version());
578 };
579 let Some(protocol_version) = params.get("protocolVersion") else {
580 return Err(invalid_initialize_protocol_version());
581 };
582
583 let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
584 .map_err(|_| invalid_initialize_protocol_version())?;
585 let selected = select_configured_agent_protocol(requested, supported)?;
586 if selected == SelectedProtocol::V2 && request.id == RequestId::Null {
588 return Err(crate::Error::invalid_request()
589 .data("ACP protocol v2 request ids must be a string or an integer, not null"));
590 }
591 rewrite_initialize_params(params, requested, selected)?;
592
593 Ok(selected)
594}
595
596#[cfg(feature = "unstable_protocol_v2")]
597fn initialize_request_params(
598 message: &RawJsonRpcMessage,
599) -> Result<&serde_json::Map<String, serde_json::Value>, crate::Error> {
600 let RawJsonRpcMessage::Request(request) = message else {
601 return Err(
602 crate::Error::invalid_request().data("first ACP message must be an initialize request")
603 );
604 };
605
606 if request.method.as_ref() != "initialize" {
607 return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
608 }
609
610 let Some(RawJsonRpcParams::Object(params)) = &request.params else {
611 return Err(invalid_initialize_protocol_version());
612 };
613 if !params.contains_key("protocolVersion") {
614 return Err(invalid_initialize_protocol_version());
615 }
616 Ok(params)
617}
618
619#[cfg(feature = "unstable_protocol_v2")]
620fn validated_initialize_params<T: DeserializeOwned>(
621 message: &RawJsonRpcMessage,
622) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
623 let params = initialize_request_params(message)?;
624 parse_initialize_params::<T>(params)?;
625 Ok(params.clone())
626}
627
628#[cfg(feature = "unstable_protocol_v2")]
629fn normalize_v2_initialize_params_for_reuse(
630 message: &RawJsonRpcMessage,
631) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
632 let params = initialize_request_params(message)?;
633 let requested = params
634 .get("protocolVersion")
635 .cloned()
636 .ok_or_else(invalid_initialize_protocol_version)
637 .and_then(|version| {
638 serde_json::from_value::<ProtocolVersion>(version)
639 .map_err(|_| invalid_initialize_protocol_version())
640 })?;
641 if requested == ProtocolVersion::V1 {
642 parse_initialize_params::<InitializeRequest>(params)?;
643 return Ok(params.clone());
644 }
645 normalize_v2_initialize_params_for_v1(params, true)
646}
647
648#[cfg(feature = "unstable_protocol_v2")]
649fn rewrite_initialize_params(
650 params: &mut serde_json::Map<String, serde_json::Value>,
651 requested: ProtocolVersion,
652 selected: SelectedProtocol,
653) -> Result<(), crate::Error> {
654 if requested == selected.version() {
658 match selected {
659 SelectedProtocol::V1 => {
660 parse_initialize_params::<InitializeRequest>(params)?;
661 }
662 SelectedProtocol::V2 => {
663 parse_initialize_params::<v2::InitializeRequest>(params)?;
664 }
665 }
666 return Ok(());
667 }
668
669 match selected {
670 SelectedProtocol::V1 => {
671 if requested > ProtocolVersion::V1 {
672 *params = normalize_v2_initialize_params_for_v1(params, false)?;
673 } else {
674 let mut initialize = parse_initialize_params::<InitializeRequest>(params)?;
675 initialize.protocol_version = ProtocolVersion::V1;
676 *params = serialize_initialize_params(initialize)?;
677 }
678 Ok(())
679 }
680 SelectedProtocol::V2 => {
681 *params = crate::jsonrpc::normalize_newer_v2_initialize_request(params)?;
684 Ok(())
685 }
686 }
687}
688
689#[cfg(feature = "unstable_protocol_v2")]
690fn normalize_v2_initialize_params_for_v1(
691 params: &serde_json::Map<String, serde_json::Value>,
692 require_lossless: bool,
693) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
694 if !require_lossless {
695 return crate::jsonrpc::normalize_v1_initialize_request(&serde_json::Value::Object(
696 params.clone(),
697 ));
698 }
699 let initialize = parse_initialize_params::<v2::InitializeRequest>(params)?;
703 let mut target = serialize_initialize_params(initialize)?;
704 if require_lossless && target != *params {
705 return Err(invalid_initialize_params(
706 "v2 initialize parameters are not losslessly representable in v1",
707 ));
708 }
709
710 target.insert(
711 "protocolVersion".into(),
712 serde_json::to_value(ProtocolVersion::V1).map_err(crate::Error::into_internal_error)?,
713 );
714 let info = target
715 .remove("info")
716 .ok_or_else(|| invalid_initialize_params("v2 InitializeRequest.info is required"))?;
717 target.insert("clientInfo".into(), info);
718 let capabilities = target
719 .remove("capabilities")
720 .and_then(|capabilities| capabilities.as_object().cloned())
721 .ok_or_else(|| {
722 crate::util::internal_error("v2 initialize capabilities did not serialize as an object")
723 })?;
724 let mut capabilities = capabilities;
725 if let Some(auth) = capabilities
726 .get_mut("auth")
727 .and_then(serde_json::Value::as_object_mut)
728 {
729 let terminal = auth.remove("terminal");
730 if require_lossless
731 && terminal
732 .as_ref()
733 .and_then(serde_json::Value::as_object)
734 .is_some_and(|terminal| terminal.contains_key("_meta"))
735 {
736 return Err(invalid_initialize_params(
737 "v2 terminal authentication metadata is not representable in v1",
738 ));
739 }
740 auth.insert("terminal".into(), terminal.is_some().into());
741 }
742 capabilities.insert(
743 "session".into(),
744 serde_json::json!({ "configOptions": { "boolean": {} } }),
745 );
746 target.insert("clientCapabilities".into(), capabilities.into());
747
748 let initialize = parse_initialize_params::<InitializeRequest>(&target)?;
749 let normalized = serialize_initialize_params(initialize)?;
750 if require_lossless && !json_object_contains(&normalized, &target) {
751 return Err(invalid_initialize_params(
752 "v2 initialize parameters are not losslessly representable in v1",
753 ));
754 }
755 Ok(normalized)
756}
757
758#[cfg(all(test, feature = "unstable_protocol_v2"))]
759mod initialize_normalization_tests {
760 use super::*;
761
762 fn v2_initialize_params() -> serde_json::Map<String, serde_json::Value> {
763 let value = serde_json::to_value(v2::InitializeRequest::new(
764 ProtocolVersion::V2,
765 v2::Implementation::new("test-client", "1.0.0"),
766 ))
767 .expect("serialize v2 initialize request");
768 value
769 .as_object()
770 .expect("initialize params serialize as an object")
771 .clone()
772 }
773
774 #[test]
775 fn v2_tolerant_fields_are_canonicalized_before_v1_normalization() {
776 let mut params = v2_initialize_params();
777 params.insert(
778 "capabilities".into(),
779 serde_json::Value::String("malformed".into()),
780 );
781 params.insert(
782 "_meta".into(),
783 serde_json::Value::String("malformed".into()),
784 );
785
786 let normalized = normalize_v2_initialize_params_for_v1(¶ms, false)
787 .expect("tolerant v2 fields should normalize through their defaults");
788 let normalized = serde_json::Value::Object(normalized);
789
790 assert!(normalized.get("_meta").is_none());
791 assert_eq!(
792 normalized.pointer("/clientCapabilities/session/configOptions/boolean"),
793 Some(&serde_json::json!({}))
794 );
795 }
796
797 #[test]
798 fn noncanonical_v2_fields_disable_reuse_but_not_v1_routing() {
799 let mut params = v2_initialize_params();
800 params
801 .get_mut("info")
802 .and_then(serde_json::Value::as_object_mut)
803 .expect("v2 initialize info is an object")
804 .insert("buildCommit".into(), serde_json::json!("abc123"));
805
806 normalize_v2_initialize_params_for_v1(¶ms, false)
807 .expect("v1 routing may ignore parameters unavailable in v1");
808 normalize_v2_initialize_params_for_v1(¶ms, true)
809 .expect_err("connection reuse requires lossless normalization");
810 }
811
812 #[test]
813 fn v1_reuse_probe_preserves_raw_initialize_params() {
814 let mut params = v2_initialize_params();
815 params.insert(
816 "protocolVersion".into(),
817 serde_json::json!(ProtocolVersion::V1),
818 );
819 let message = RawJsonRpcMessage::request(
820 "initialize".into(),
821 serde_json::Value::Object(params.clone()),
822 RequestId::Number(1),
823 )
824 .expect("build initialize request");
825
826 let normalized = normalize_v2_initialize_params_for_reuse(&message)
827 .expect("v1-shaped initialize request should be valid");
828
829 assert_eq!(normalized, params);
830 }
831
832 #[test]
833 fn null_v2_terminal_marker_meta_is_omitted_before_v1_normalization() {
834 let mut params = v2_initialize_params();
835 params.insert(
836 "capabilities".into(),
837 serde_json::json!({
838 "auth": {
839 "terminal": { "_meta": null }
840 }
841 }),
842 );
843
844 let normalized = normalize_v2_initialize_params_for_v1(¶ms, false)
845 .expect("null marker metadata is equivalent to omission");
846 let normalized = serde_json::Value::Object(normalized);
847
848 assert_eq!(
849 normalized.pointer("/clientCapabilities/auth/terminal"),
850 Some(&serde_json::Value::Bool(true))
851 );
852 }
853
854 #[test]
855 fn terminal_marker_metadata_disables_reuse_but_not_v1_routing() {
856 let mut params = v2_initialize_params();
857 params.insert(
858 "capabilities".into(),
859 serde_json::json!({
860 "auth": {
861 "terminal": {
862 "_meta": { "source": "test" }
863 }
864 }
865 }),
866 );
867
868 normalize_v2_initialize_params_for_v1(¶ms, false)
869 .expect("v1 routing may discard terminal marker metadata");
870 normalize_v2_initialize_params_for_v1(¶ms, true)
871 .expect_err("connection reuse must preserve terminal marker metadata");
872 }
873}
874
875#[cfg(feature = "unstable_protocol_v2")]
876fn parse_initialize_params<T: DeserializeOwned>(
877 params: &serde_json::Map<String, serde_json::Value>,
878) -> Result<T, crate::Error> {
879 serde_json::from_value(serde_json::Value::Object(params.clone()))
880 .map_err(invalid_initialize_params)
881}
882
883#[cfg(feature = "unstable_protocol_v2")]
884fn serialize_initialize_params(
885 initialize: impl Serialize,
886) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
887 let value = serde_json::to_value(initialize).map_err(crate::Error::into_internal_error)?;
888 let serde_json::Value::Object(object) = value else {
889 return Err(crate::util::internal_error(
890 "initialize params did not serialize to an object",
891 ));
892 };
893 Ok(object)
894}
895
896#[cfg(feature = "unstable_protocol_v2")]
897fn json_object_contains(
898 actual: &serde_json::Map<String, serde_json::Value>,
899 expected: &serde_json::Map<String, serde_json::Value>,
900) -> bool {
901 fn contains(actual: &serde_json::Value, expected: &serde_json::Value) -> bool {
902 match (actual, expected) {
903 (serde_json::Value::Object(actual), serde_json::Value::Object(expected)) => expected
904 .iter()
905 .all(|(key, value)| actual.get(key).is_some_and(|item| contains(item, value))),
906 _ => actual == expected,
907 }
908 }
909
910 expected
911 .iter()
912 .all(|(key, value)| actual.get(key).is_some_and(|item| contains(item, value)))
913}
914
915#[cfg(feature = "unstable_protocol_v2")]
916fn select_configured_agent_protocol(
917 requested: ProtocolVersion,
918 supported: SupportedProtocols,
919) -> Result<SelectedProtocol, crate::Error> {
920 supported
921 .select_for_agent(requested)
922 .ok_or_else(|| crate::util::unsupported_protocol_version(requested, &supported.versions()))
923}
924
925#[cfg(feature = "unstable_protocol_v2")]
926fn invalid_initialize_protocol_version() -> crate::Error {
927 crate::Error::invalid_params()
928 .data("initialize.protocolVersion must be a valid ACP protocol version")
929}
930
931#[cfg(feature = "unstable_protocol_v2")]
932fn invalid_initialize_params(error: impl ToString) -> crate::Error {
933 crate::Error::invalid_params().data(format!("invalid initialize params: {}", error.to_string()))
934}
935
936#[cfg(feature = "unstable_protocol_v2")]
937fn send_initialize_error(
938 tx: &futures::channel::mpsc::UnboundedSender<TransportFrame>,
939 frame: &TransportFrame,
940 error: crate::Error,
941) -> Result<(), crate::Error> {
942 fn response_for_message(
943 entry: &RawJsonRpcMessage,
944 initialize_error: &crate::Error,
945 ) -> Option<RawJsonRpcMessage> {
946 match entry {
947 RawJsonRpcMessage::Request(request) => Some(RawJsonRpcMessage::response(
948 request.id.clone(),
949 Err(initialize_error.clone()),
950 )),
951 RawJsonRpcMessage::Notification(_) | RawJsonRpcMessage::Response(_) => None,
952 }
953 }
954
955 fn response_for_entry(
956 entry: &TransportBatchEntry,
957 initialize_error: &crate::Error,
958 ) -> Option<RawJsonRpcMessage> {
959 match entry {
960 TransportBatchEntry::Message(message) => {
961 response_for_message(message, initialize_error)
962 }
963 TransportBatchEntry::Malformed { raw, error } if !is_response_only_shape(raw) => Some(
964 RawJsonRpcMessage::response(RequestId::Null, Err(error.clone())),
965 ),
966 TransportBatchEntry::Malformed { .. } => None,
967 }
968 }
969
970 let response = match frame {
971 TransportFrame::Single(entry) => {
972 let Some(response) = response_for_message(entry, &error) else {
973 return Ok(());
974 };
975 TransportFrame::Single(response)
976 }
977 TransportFrame::Malformed { raw, error } if !raw_is_response_only_shape(raw) => {
978 TransportFrame::Single(RawJsonRpcMessage::response(
979 RequestId::Null,
980 Err(error.clone()),
981 ))
982 }
983 TransportFrame::Malformed { .. } => return Ok(()),
984 TransportFrame::Batch(batch) => {
985 let responses = batch
986 .entries()
987 .filter_map(|entry| response_for_entry(entry, &error))
988 .collect::<Vec<_>>();
989 let Some(responses) = TransportBatch::from_messages(responses) else {
990 return Ok(());
991 };
992 TransportFrame::Batch(responses)
993 }
994 };
995
996 tx.unbounded_send(response)
997 .map_err(crate::util::internal_error)
998}
999
1000#[cfg(feature = "unstable_protocol_v2")]
1001async fn reject_initialize(
1002 client: RunningProtocolPeer,
1003 frame: &TransportFrame,
1004 error: crate::Error,
1005) -> Result<(), crate::Error> {
1006 let RunningProtocolPeer { mut rx, tx, driver } = client;
1007 send_initialize_error(&tx, frame, error)?;
1008 drop(tx);
1009
1010 let Some(mut driver) = driver.into_driver() else {
1011 return Ok(());
1014 };
1015 if !driver.request_finish() {
1016 return crate::util::run_until(driver, future::ready(Ok(()))).await;
1019 }
1020
1021 let drain_incoming = async move {
1022 while rx.next().await.is_some() {}
1026 Ok::<_, crate::Error>(())
1027 };
1028
1029 match future::select(driver, Box::pin(drain_incoming)).await {
1030 future::Either::Left((result, _)) => result,
1031 future::Either::Right((result, driver)) => {
1032 result?;
1033 driver.await
1034 }
1035 }
1036}
1037
1038#[cfg(feature = "unstable_protocol_v2")]
1039struct RunningProtocolPeer {
1040 rx: futures::channel::mpsc::UnboundedReceiver<TransportFrame>,
1041 tx: futures::channel::mpsc::UnboundedSender<TransportFrame>,
1042 driver: ProtocolPeerDriver,
1043}
1044
1045#[cfg(feature = "unstable_protocol_v2")]
1046enum ProtocolPeerDriver {
1047 Passive,
1048 Active(crate::ConnectionDriver),
1049 Completed {
1050 finish: Option<crate::component::FinishControl>,
1051 },
1052}
1053
1054#[cfg(feature = "unstable_protocol_v2")]
1055impl ProtocolPeerDriver {
1056 fn into_driver(self) -> Option<crate::ConnectionDriver> {
1057 match self {
1058 Self::Passive => None,
1059 Self::Active(driver) => Some(driver),
1060 Self::Completed { finish } => Some(match finish {
1064 Some(mut finish) => {
1065 crate::ConnectionDriver::with_finish(future::ready(Ok(())), move || {
1066 finish.request();
1067 })
1068 }
1069 None => crate::ConnectionDriver::new(future::ready(Ok(()))),
1070 }),
1071 }
1072 }
1073}
1074
1075#[cfg(feature = "unstable_protocol_v2")]
1076impl RunningProtocolPeer {
1077 fn new<R: Role>(component: impl ConnectTo<R>) -> Self {
1078 let (Channel { rx, tx }, future) = component.into_channel_and_future();
1079 let driver = match future {
1080 None => ProtocolPeerDriver::Passive,
1081 Some(future) => ProtocolPeerDriver::Active(future),
1082 };
1083 Self { rx, tx, driver }
1084 }
1085
1086 async fn next_frame(self) -> Result<Option<(TransportFrame, Self)>, crate::Error> {
1087 let Self { mut rx, tx, driver } = self;
1088 let ProtocolPeerDriver::Active(mut future) = driver else {
1089 return Ok(rx
1090 .next()
1091 .await
1092 .map(|frame| (frame, Self { rx, tx, driver })));
1093 };
1094
1095 match future::select(&mut future, Box::pin(rx.next())).await {
1098 future::Either::Right((Some(frame), _)) => Ok(Some((
1099 frame,
1100 Self {
1101 rx,
1102 tx,
1103 driver: ProtocolPeerDriver::Active(future),
1104 },
1105 ))),
1106 future::Either::Right((None, _)) => {
1107 drop(tx);
1108 future.await?;
1109 Ok(None)
1110 }
1111 future::Either::Left((result, next_message)) => {
1112 result?;
1113 drop(next_message);
1114 rx.close();
1117 let Some(frame) = rx.next().await else {
1118 return Ok(None);
1119 };
1120 Ok(Some((
1121 frame,
1122 Self {
1123 rx,
1124 tx,
1125 driver: ProtocolPeerDriver::Completed {
1126 finish: future.take_finish(),
1127 },
1128 },
1129 )))
1130 }
1131 }
1132 }
1133
1134 async fn next_message(self) -> Result<Option<(RawJsonRpcMessage, Self)>, crate::Error> {
1135 let Some((frame, peer)) = self.next_frame().await? else {
1136 return Ok(None);
1137 };
1138 Ok(Some((initialize_message(frame)?, peer)))
1139 }
1140
1141 fn send(&self, message: RawJsonRpcMessage) -> Result<(), crate::Error> {
1142 self.send_frame(TransportFrame::Single(message))
1143 }
1144
1145 fn send_frame(&self, frame: TransportFrame) -> Result<(), crate::Error> {
1146 self.tx
1147 .unbounded_send(frame)
1148 .map_err(crate::util::internal_error)
1149 }
1150}
1151
1152#[cfg(feature = "unstable_protocol_v2")]
1153fn initialize_message(frame: TransportFrame) -> Result<RawJsonRpcMessage, crate::Error> {
1154 match frame {
1155 TransportFrame::Single(message) => Ok(message),
1156 TransportFrame::Malformed { error, .. } => Err(error),
1157 TransportFrame::Batch(_) => Err(crate::Error::invalid_request()
1158 .data("ACP initialize request and response messages must be sent individually")),
1159 }
1160}
1161
1162#[cfg(feature = "unstable_protocol_v2")]
1163fn initialize_message_mut(
1164 frame: &mut TransportFrame,
1165) -> Result<Option<&mut RawJsonRpcMessage>, crate::Error> {
1166 match frame {
1167 TransportFrame::Single(RawJsonRpcMessage::Response(_)) => Ok(None),
1168 TransportFrame::Single(entry) => Ok(Some(entry)),
1169 TransportFrame::Malformed { raw, .. } if raw_is_response_only_shape(raw) => Ok(None),
1170 TransportFrame::Malformed { error, .. } => Err(error.clone()),
1171 TransportFrame::Batch(batch) => {
1172 for entry in batch.entries_mut() {
1173 match entry {
1174 TransportBatchEntry::Message(RawJsonRpcMessage::Response(_)) => {}
1175 TransportBatchEntry::Message(message) => return Ok(Some(message)),
1176 TransportBatchEntry::Malformed { raw, .. } if is_response_only_shape(raw) => {}
1177 TransportBatchEntry::Malformed { error, .. } => return Err(error.clone()),
1178 }
1179 }
1180 Ok(None)
1181 }
1182 }
1183}
1184
1185#[cfg(feature = "unstable_protocol_v2")]
1189async fn pipe_protocol_peers_until_done(
1190 left: RunningProtocolPeer,
1191 right: RunningProtocolPeer,
1192) -> Result<(), crate::Error> {
1193 let mut left_driver = left.driver.into_driver();
1194 let mut right_driver = right.driver.into_driver();
1195 let left_passive = left_driver.is_none();
1196 let right_passive = right_driver.is_none();
1197 let left_finish = left_driver
1198 .as_mut()
1199 .and_then(crate::ConnectionDriver::take_finish);
1200 let right_finish = right_driver
1201 .as_mut()
1202 .and_then(crate::ConnectionDriver::take_finish);
1203 let (stop_left_tx, stop_left_rx) = futures::channel::oneshot::channel();
1204 let (stop_right_tx, stop_right_rx) = futures::channel::oneshot::channel();
1205 let stop = async |rx: futures::channel::oneshot::Receiver<()>| {
1206 if rx.await.is_err() {
1207 future::pending::<()>().await;
1208 }
1209 };
1210 let left_to_right = Box::pin(
1211 Channel {
1212 rx: left.rx,
1213 tx: right.tx,
1214 }
1215 .copy_with_driver_until(left_driver, stop(stop_left_rx)),
1216 );
1217 let right_to_left = Box::pin(
1218 Channel {
1219 rx: right.rx,
1220 tx: left.tx,
1221 }
1222 .copy_with_driver_until(right_driver, stop(stop_right_rx)),
1223 );
1224
1225 match future::select(left_to_right, right_to_left).await {
1226 future::Either::Left((result, right_to_left)) => {
1227 result?;
1228 if !left_passive {
1229 let _ = stop_right_tx.send(());
1230 }
1231 if left_passive || right_finish.is_some() {
1232 if !left_passive && let Some(mut finish) = right_finish {
1233 finish.request();
1234 }
1235 right_to_left.await
1236 } else {
1237 crate::util::run_until(right_to_left, future::ready(Ok(()))).await
1240 }
1241 }
1242 future::Either::Right((result, left_to_right)) => {
1243 result?;
1244 if !right_passive {
1245 let _ = stop_left_tx.send(());
1246 }
1247 if right_passive || left_finish.is_some() {
1248 if !right_passive && let Some(mut finish) = left_finish {
1249 finish.request();
1250 }
1251 left_to_right.await
1252 } else {
1253 crate::util::run_until(left_to_right, future::ready(Ok(()))).await
1254 }
1255 }
1256 }
1257}
1258
1259#[cfg(feature = "unstable_protocol_v2")]
1260#[derive(Debug)]
1261struct InitializeResponse {
1262 id: RequestId,
1263 result: Result<serde_json::Value, Box<RawJsonRpcError>>,
1264}
1265
1266#[cfg(feature = "unstable_protocol_v2")]
1267impl InitializeResponse {
1268 fn from_message(message: RawJsonRpcMessage) -> Result<Self, crate::Error> {
1269 match message {
1270 RawJsonRpcMessage::Response(RpcResponse::Result { id, result }) => Ok(Self {
1271 id,
1272 result: Ok(result),
1273 }),
1274 RawJsonRpcMessage::Response(RpcResponse::Error { id, error }) => Ok(Self {
1275 id,
1276 result: Err(error),
1277 }),
1278 message => Err(crate::Error::invalid_request().data(format!(
1279 "first ACP response must be an initialize response, got {message:?}",
1280 ))),
1281 }
1282 }
1283
1284 fn into_message(self) -> RawJsonRpcMessage {
1285 RawJsonRpcMessage::Response(RpcResponse::new(self.id, self.result))
1286 }
1287
1288 fn with_id(self, id: RequestId) -> RawJsonRpcMessage {
1289 RawJsonRpcMessage::Response(RpcResponse::new(id, self.result))
1290 }
1291
1292 fn protocol_version(&self) -> Option<ProtocolVersion> {
1293 serde_json::from_value(self.result.as_ref().ok()?.get("protocolVersion")?.clone()).ok()
1294 }
1295}
1296
1297#[cfg(all(test, feature = "unstable_protocol_v2"))]
1298mod raw_initialize_tests {
1299 use super::*;
1300 use serde_json::json;
1301
1302 #[test]
1303 fn initialize_error_forwarding_preserves_raw_fields() {
1304 for data in [
1305 None,
1306 Some(serde_json::Value::Null),
1307 Some(json!({"detail":"kept"})),
1308 ] {
1309 let mut error = json!({
1310 "code":-32000, "message":"peer", "extension":{"retry":true}
1311 });
1312 if let Some(data) = data {
1313 error["data"] = data;
1314 }
1315 let wire = json!({"jsonrpc":"2.0", "id":"original", "error":error});
1316 let response =
1317 InitializeResponse::from_message(serde_json::from_value(wire.clone()).unwrap())
1318 .unwrap();
1319 assert_eq!(serde_json::to_value(response.into_message()).unwrap(), wire);
1320 let response =
1321 InitializeResponse::from_message(serde_json::from_value(wire).unwrap()).unwrap();
1322 let forwarded = response.with_id(RequestId::Str("replacement".into()));
1323 assert_eq!(
1324 serde_json::to_value(forwarded).unwrap(),
1325 json!({"jsonrpc":"2.0", "id":"replacement", "error":error})
1326 );
1327 }
1328 }
1329}
1330
1331#[cfg(feature = "unstable_protocol_v2")]
1332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1333enum ClientProtocol {
1334 V1,
1335 V2,
1336}
1337
1338#[cfg(feature = "unstable_protocol_v2")]
1339impl ClientProtocol {
1340 fn name(self) -> &'static str {
1341 match self {
1342 Self::V1 => "1",
1343 Self::V2 => "2",
1344 }
1345 }
1346}
1347
1348#[cfg(feature = "unstable_protocol_v2")]
1349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1350struct SupportedClientProtocols {
1351 v1: bool,
1352 v2: bool,
1353}
1354
1355#[cfg(feature = "unstable_protocol_v2")]
1356impl SupportedClientProtocols {
1357 fn highest_configured(self) -> Option<ClientProtocol> {
1358 if self.v2 {
1359 return Some(ClientProtocol::V2);
1360 }
1361
1362 if self.v1 {
1363 return Some(ClientProtocol::V1);
1364 }
1365
1366 None
1367 }
1368}
1369
1370#[cfg(feature = "unstable_protocol_v2")]
1371async fn start_client_protocol(
1372 protocol: ClientProtocol,
1373 client: DynConnectTo<Agent>,
1374) -> Result<(RunningProtocolPeer, RawJsonRpcMessage), crate::Error> {
1375 let client = RunningProtocolPeer::new(client);
1376 let Some((initialize, client)) = client.next_message().await? else {
1377 return Err(crate::Error::invalid_request().data(format!(
1378 "ACP protocol version {} client implementation ended before initialize",
1379 protocol.name()
1380 )));
1381 };
1382 ensure_client_initialize_request(protocol, &initialize)?;
1383 Ok((client, initialize))
1384}
1385
1386#[cfg(feature = "unstable_protocol_v2")]
1387async fn send_initialize_and_receive(
1388 client: RunningProtocolPeer,
1389 agent: RunningProtocolPeer,
1390 initialize: RawJsonRpcMessage,
1391) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
1392 agent.send(initialize)?;
1393 let Some((response, agent)) = agent.next_message().await? else {
1394 return Err(crate::Error::internal_error().data("agent closed before initialize response"));
1395 };
1396 let response = InitializeResponse::from_message(response)?;
1397 Ok((client, agent, response))
1398}
1399
1400#[cfg(feature = "unstable_protocol_v2")]
1401async fn initialize_client_protocol(
1402 protocol: ClientProtocol,
1403 client: DynConnectTo<Agent>,
1404 agent: impl ConnectTo<Client>,
1405) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
1406 let agent = RunningProtocolPeer::new(agent);
1407 let (client, initialize) = start_client_protocol(protocol, client).await?;
1408 send_initialize_and_receive(client, agent, initialize).await
1409}
1410
1411#[cfg(feature = "unstable_protocol_v2")]
1412async fn connect_client_protocol(
1413 protocol: ClientProtocol,
1414 client: DynConnectTo<Agent>,
1415 agent: impl ConnectTo<Client>,
1416) -> Result<(), crate::Error> {
1417 let (client, agent, initialize_response) =
1418 initialize_client_protocol(protocol, client, agent).await?;
1419 client.send(initialize_response.into_message())?;
1420 pipe_protocol_peers_until_done(client, agent).await
1421}
1422
1423#[cfg(feature = "unstable_protocol_v2")]
1424fn ensure_client_initialize_request(
1425 protocol: ClientProtocol,
1426 message: &RawJsonRpcMessage,
1427) -> Result<(), crate::Error> {
1428 let RawJsonRpcMessage::Request(request) = message else {
1429 return Err(crate::Error::invalid_request().data(format!(
1430 "ACP protocol version {} client implementation must send initialize first",
1431 protocol.name()
1432 )));
1433 };
1434
1435 if request.method.as_ref() != "initialize" {
1436 return Err(crate::Error::invalid_request().data(format!(
1437 "ACP protocol version {} client implementation must send initialize first",
1438 protocol.name()
1439 )));
1440 }
1441
1442 Ok(())
1443}
1444
1445#[cfg(feature = "unstable_protocol_v2")]
1446fn initialize_request_id(message: &RawJsonRpcMessage) -> Option<RequestId> {
1447 let RawJsonRpcMessage::Request(request) = message else {
1448 return None;
1449 };
1450 Some(request.id.clone())
1451}
1452
1453#[cfg(feature = "unstable_protocol_v2")]
1454fn initialize_response_negotiated_v1(response: &InitializeResponse) -> bool {
1455 response.protocol_version() == Some(ProtocolVersion::V1)
1456}
1457
1458impl HasPeer<Agent> for Agent {
1459 fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1460 RemoteStyle::Counterpart
1461 }
1462}
1463
1464#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1473pub struct Proxy;
1474
1475impl Role for Proxy {
1476 type Counterpart = Conductor;
1477
1478 fn default_handle_dispatch_from(
1479 &self,
1480 message: crate::Dispatch,
1481 _connection: crate::ConnectionTo<Self>,
1482 ) -> impl Future<Output = Result<crate::Handled<crate::Dispatch>, crate::Error>> + Send {
1483 std::future::ready(Ok(Handled::No {
1484 message,
1485 retry: false,
1486 }))
1487 }
1488
1489 fn role_id(&self) -> RoleId {
1490 RoleId::from_singleton(self)
1491 }
1492
1493 fn counterpart(&self) -> Self::Counterpart {
1494 Conductor
1495 }
1496}
1497
1498impl Proxy {
1499 pub fn builder(self) -> Builder<Proxy, NullHandler, NullRun> {
1506 Builder::new(self)
1507 }
1508
1509 #[cfg(feature = "unstable_protocol_v2")]
1518 pub fn v2(self) -> V2Builder<Proxy, NullHandler, NullRun> {
1519 self.builder().v2_proxy()
1520 }
1521
1522 #[cfg(feature = "unstable_protocol_v2")]
1533 #[must_use]
1534 pub fn protocol_router(self) -> ProxyProtocolRouter {
1535 ProxyProtocolRouter::new()
1536 }
1537}
1538
1539#[cfg(feature = "unstable_protocol_v2")]
1547#[derive(Debug, Default)]
1548pub struct ProxyProtocolRouter {
1549 v1: Option<DynConnectTo<Conductor>>,
1550 v2: Option<DynConnectTo<Conductor>>,
1551}
1552
1553#[cfg(feature = "unstable_protocol_v2")]
1554impl ProxyProtocolRouter {
1555 #[must_use]
1557 pub fn new() -> Self {
1558 Self::default()
1559 }
1560
1561 #[must_use]
1563 pub fn with_v1(mut self, proxy: impl ConnectTo<Conductor>) -> Self {
1564 self.v1 = Some(DynConnectTo::new(proxy));
1565 self
1566 }
1567
1568 #[must_use]
1570 pub fn with_v2(mut self, proxy: impl ConnectTo<Conductor>) -> Self {
1571 self.v2 = Some(DynConnectTo::new(proxy));
1572 self
1573 }
1574}
1575
1576#[cfg(feature = "unstable_protocol_v2")]
1577impl ConnectTo<Conductor> for ProxyProtocolRouter {
1578 async fn connect_to(self, conductor: impl ConnectTo<Proxy>) -> Result<(), crate::Error> {
1579 let supported = SupportedProtocols {
1580 v1: self.v1.is_some(),
1581 v2: self.v2.is_some(),
1582 };
1583 let mut conductor = RunningProtocolPeer::new(conductor);
1584 let (first_frame, conductor, selected) = loop {
1585 let Some((mut frame, next_conductor)) = conductor.next_frame().await? else {
1586 return Ok(());
1587 };
1588 let message = match initialize_message_mut(&mut frame) {
1589 Ok(Some(message)) => message,
1590 Ok(None) => {
1591 conductor = next_conductor;
1592 continue;
1593 }
1594 Err(error) => return reject_initialize(next_conductor, &frame, error).await,
1595 };
1596 let selected = match select_proxy_protocol(message, supported) {
1597 Ok(selected) => selected,
1598 Err(error) => return reject_initialize(next_conductor, &frame, error).await,
1599 };
1600 break (frame, next_conductor, selected);
1601 };
1602 let Some(proxy) = selected.take_proxy(self) else {
1603 let error = selected.unsupported_error(supported);
1604 return reject_initialize(conductor, &first_frame, error).await;
1605 };
1606
1607 let proxy = RunningProtocolPeer::new(proxy);
1608 proxy.send_frame(first_frame)?;
1609 pipe_protocol_peers_until_done(conductor, proxy).await
1610 }
1611}
1612
1613#[cfg(feature = "unstable_protocol_v2")]
1614impl SelectedProtocol {
1615 fn take_proxy(self, proxy: ProxyProtocolRouter) -> Option<DynConnectTo<Conductor>> {
1616 match self {
1617 Self::V1 => proxy.v1,
1618 Self::V2 => proxy.v2,
1619 }
1620 }
1621}
1622
1623#[cfg(feature = "unstable_protocol_v2")]
1624fn select_proxy_protocol(
1625 message: &RawJsonRpcMessage,
1626 supported: SupportedProtocols,
1627) -> Result<SelectedProtocol, crate::Error> {
1628 let RawJsonRpcMessage::Request(request) = message else {
1629 return Err(crate::Error::invalid_request()
1630 .data("first ACP proxy message must be an `_proxy/initialize` request"));
1631 };
1632
1633 if request.method.as_ref() != METHOD_INITIALIZE_PROXY {
1634 return Err(crate::Error::invalid_request()
1635 .data("first ACP proxy request must be `_proxy/initialize`"));
1636 }
1637
1638 let Some(RawJsonRpcParams::Object(params)) = &request.params else {
1639 return Err(invalid_initialize_protocol_version());
1640 };
1641 let Some(protocol_version) = params.get("protocolVersion") else {
1642 return Err(invalid_initialize_protocol_version());
1643 };
1644 let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
1645 .map_err(|_| invalid_initialize_protocol_version())?;
1646 let selected = supported.exact(requested).ok_or_else(|| {
1647 crate::Error::invalid_request().data(format!(
1648 "unsupported ACP protocol version {requested}; this proxy supports {}",
1649 supported.description()
1650 ))
1651 })?;
1652
1653 match selected {
1654 SelectedProtocol::V1 => {
1655 parse_initialize_params::<crate::schema::InitializeProxyRequest>(params)?;
1656 }
1657 SelectedProtocol::V2 => {
1658 parse_initialize_params::<v2::InitializeProxyRequest>(params)?;
1659 }
1660 }
1661 Ok(selected)
1662}
1663
1664impl HasPeer<Proxy> for Proxy {
1665 fn remote_style(&self, _peer: Proxy) -> RemoteStyle {
1666 RemoteStyle::Counterpart
1667 }
1668}
1669
1670#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1675pub struct Conductor;
1676
1677impl Role for Conductor {
1678 type Counterpart = Proxy;
1679
1680 fn role_id(&self) -> RoleId {
1681 RoleId::from_singleton(self)
1682 }
1683
1684 fn counterpart(&self) -> Self::Counterpart {
1685 Proxy
1686 }
1687
1688 async fn default_handle_dispatch_from(
1689 &self,
1690 message: Dispatch,
1691 cx: ConnectionTo<Conductor>,
1692 ) -> Result<Handled<Dispatch>, crate::Error> {
1693 #[cfg(not(feature = "unstable_protocol_v2"))]
1694 {
1695 MatchDispatchFrom::new(message, &cx)
1696 .if_request_from(Client, async |_req: InitializeRequest, responder| {
1697 responder.respond_with_error(crate::Error::invalid_request().data(format!(
1698 "proxies must be initialized with `{METHOD_INITIALIZE_PROXY}`"
1699 )))
1700 })
1701 .await
1702 .if_request_from(
1703 Client,
1704 async |request: InitializeProxyRequest, responder| {
1705 let InitializeProxyRequest { initialize } = request;
1706 cx.send_ordered_request_to(Agent, initialize)
1707 .forward_response_to(responder)
1708 },
1709 )
1710 .await
1711 .if_request_from(Client, async |request: NewSessionRequest, responder| {
1712 let sent = cx.send_ordered_request_to(Agent, request);
1713 let sent = sent.forward_cancellation_from(responder.cancellation());
1714 sent.on_receiving_result({
1715 let cx = cx.clone();
1716 async move |result| {
1717 if let Ok(NewSessionResponse { session_id, .. }) = &result {
1718 cx.add_dynamic_handler(ProxySessionMessages::new(
1719 session_id.clone(),
1720 ))?
1721 .detach();
1722 }
1723 responder.respond_with_result(result)
1724 }
1725 })
1726 })
1727 .await
1728 .if_dispatch_from(Client, async |message: Dispatch| {
1729 cx.send_proxied_message_to(Agent, message)
1730 })
1731 .await
1732 .if_dispatch_from(Agent, async |message: Dispatch| {
1733 cx.send_proxied_message_to(Client, message)
1734 })
1735 .await
1736 .done()
1737 }
1738
1739 #[cfg(feature = "unstable_protocol_v2")]
1740 {
1741 let message = match message {
1742 Dispatch::Request(request, responder) if request.method() == "initialize" => {
1743 responder.respond_with_error(crate::Error::invalid_request().data(format!(
1744 "proxies must be initialized with `{METHOD_INITIALIZE_PROXY}`"
1745 )))?;
1746 return Ok(Handled::Yes);
1747 }
1748 Dispatch::Request(mut request, responder)
1749 if request.method() == METHOD_INITIALIZE_PROXY =>
1750 {
1751 request.method = "initialize".to_string();
1752 cx.send_ordered_request_to(Agent, request)
1753 .forward_response_to(responder)?;
1754 return Ok(Handled::Yes);
1755 }
1756 Dispatch::Request(request, responder) if request.method() == "session/new" => {
1757 let sent = cx.send_ordered_request_to(Agent, request);
1758 let sent = sent.forward_cancellation_from(responder.cancellation());
1763 sent.on_receiving_result({
1764 let cx = cx.clone();
1765 async move |result| {
1766 let result = result.and_then(|response| {
1767 let envelope: NewSessionResponseEnvelope =
1768 crate::util::json_cast(response.clone())?;
1769 cx.add_dynamic_handler(ProxySessionMessages::new(
1770 envelope.session_id,
1771 ))?
1772 .detach();
1773 Ok(response)
1774 });
1775 responder.respond_with_result(result)
1776 }
1777 })?;
1778 return Ok(Handled::Yes);
1779 }
1780 message => message,
1781 };
1782
1783 MatchDispatchFrom::new(message, &cx)
1784 .if_dispatch_from(Client, async |message: Dispatch| {
1785 cx.send_proxied_message_to(Agent, message)
1786 })
1787 .await
1788 .if_dispatch_from(Agent, async |message: Dispatch| {
1789 cx.send_proxied_message_to(Client, message)
1790 })
1791 .await
1792 .done()
1793 }
1794 }
1795}
1796
1797impl Conductor {
1798 pub fn builder(self) -> Builder<Conductor, NullHandler, NullRun> {
1800 Builder::new(self)
1801 }
1802}
1803
1804impl HasPeer<Client> for Conductor {
1805 fn remote_style(&self, _peer: Client) -> RemoteStyle {
1806 RemoteStyle::Predecessor
1807 }
1808}
1809
1810impl HasPeer<Agent> for Conductor {
1811 fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1812 RemoteStyle::Successor
1813 }
1814}
1815
1816pub(crate) struct ProxySessionMessages {
1821 session_id: SessionId,
1822}
1823
1824impl ProxySessionMessages {
1825 pub fn new(session_id: SessionId) -> Self {
1827 Self { session_id }
1828 }
1829}
1830
1831impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for ProxySessionMessages
1832where
1833 Counterpart: HasPeer<Agent> + HasPeer<Client>,
1834{
1835 async fn handle_dispatch_from(
1836 &mut self,
1837 message: Dispatch,
1838 connection: ConnectionTo<Counterpart>,
1839 ) -> Result<Handled<Dispatch>, crate::Error> {
1840 MatchDispatchFrom::new(message, &connection)
1841 .if_dispatch_from(Agent, async |message| {
1842 if let Some(session_id) = message.get_session_id()?
1844 && session_id == self.session_id
1845 {
1846 connection.send_proxied_message_to(Client, message)?;
1847 return Ok(Handled::Yes);
1848 }
1849
1850 Ok(Handled::No {
1852 message,
1853 retry: false,
1854 })
1855 })
1856 .await
1857 .done()
1858 }
1859
1860 fn describe_chain(&self) -> impl std::fmt::Debug {
1861 format!("ProxySessionMessages({})", self.session_id)
1862 }
1863}
1864
1865#[cfg(all(test, feature = "unstable_protocol_v2"))]
1866mod lifetime_tests {
1867 use super::*;
1868 use crate::{ConnectionDriver, UntypedRole};
1869 use futures::FutureExt as _;
1870
1871 fn frame() -> TransportFrame {
1872 TransportFrame::parse_json(r#"{"jsonrpc":"2.0","method":"test/queued","params":{}}"#)
1873 }
1874
1875 #[tokio::test]
1876 async fn passive_initialization_waits_for_a_frame_not_driver_readiness() {
1877 let (channel, remote) = Channel::duplex();
1878 let peer = RunningProtocolPeer::new::<UntypedRole>(channel);
1879 let mut next = Box::pin(peer.next_frame());
1880 assert!(next.as_mut().now_or_never().is_none());
1881
1882 remote.tx.unbounded_send(frame()).unwrap();
1883 let (_, peer) = next.await.unwrap().expect("passive peer remains connected");
1884 assert!(matches!(peer.driver, ProtocolPeerDriver::Passive));
1885 }
1886
1887 #[tokio::test]
1888 async fn owned_peer_preserves_finish_metadata_through_active_and_completed_states() {
1889 let (Channel { rx, tx }, remote) = Channel::duplex();
1890 let (done_tx, done_rx) = futures::channel::oneshot::channel();
1891 let (finish_tx, finish_rx) = futures::channel::oneshot::channel();
1892 let peer = RunningProtocolPeer {
1893 rx,
1894 tx,
1895 driver: ProtocolPeerDriver::Active(ConnectionDriver::with_finish(
1896 async move {
1897 done_rx.await.unwrap();
1898 Ok(())
1899 },
1900 move || {
1901 let _ = finish_tx.send(());
1902 },
1903 )),
1904 };
1905
1906 remote.tx.unbounded_send(frame()).unwrap();
1907 let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1908 assert!(matches!(&peer.driver, ProtocolPeerDriver::Active(_)));
1909
1910 remote.tx.unbounded_send(frame()).unwrap();
1911 done_tx.send(()).unwrap();
1912 let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1913 assert!(matches!(
1914 &peer.driver,
1915 ProtocolPeerDriver::Completed { finish: Some(_) }
1916 ));
1917 let mut driver = peer
1918 .driver
1919 .into_driver()
1920 .expect("completed owned work must not become passive");
1921 assert!(driver.request_finish());
1922 finish_rx.await.unwrap();
1923 driver.await.unwrap();
1924 }
1925
1926 #[tokio::test]
1927 async fn active_initialization_drains_accepted_frames_without_escaped_sender_eof() {
1928 let (Channel { rx, tx }, remote) = Channel::duplex();
1929 remote.tx.unbounded_send(frame()).unwrap();
1930 remote.tx.unbounded_send(frame()).unwrap();
1931 let mut polls = 0;
1932 let driver = ConnectionDriver::new(future::poll_fn(move |_| {
1933 polls += 1;
1934 assert_eq!(polls, 1, "the completed driver must never be re-polled");
1935 std::task::Poll::Ready(Ok(()))
1936 }));
1937 let peer = RunningProtocolPeer {
1938 rx,
1939 tx,
1940 driver: ProtocolPeerDriver::Active(driver),
1941 };
1942
1943 let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1944 assert!(
1945 remote.tx.unbounded_send(frame()).is_err(),
1946 "active completion must reject new output from escaped handles"
1947 );
1948 let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1949 assert!(peer.next_frame().await.unwrap().is_none());
1950 }
1951
1952 #[tokio::test]
1953 async fn ready_initialization_driver_error_beats_queued_frames() {
1954 let (Channel { rx, tx }, remote) = Channel::duplex();
1955 remote.tx.unbounded_send(frame()).unwrap();
1956 let error = crate::Error::internal_error().data("owned initialization failed");
1957 let peer = RunningProtocolPeer {
1958 rx,
1959 tx,
1960 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Err(
1961 error.clone()
1962 )))),
1963 };
1964
1965 match peer.next_frame().await {
1966 Err(actual) => assert_eq!(actual, error),
1967 Ok(_) => panic!("ready driver error must not be hidden by a queued frame"),
1968 }
1969 }
1970
1971 struct QueuedFinalClient;
1972
1973 impl ConnectTo<Agent> for QueuedFinalClient {
1974 async fn connect_to(self, agent: impl ConnectTo<Client>) -> Result<(), crate::Error> {
1975 let (mut channel, driver) = agent.into_channel_and_future();
1976 let foreground = async move {
1977 channel
1978 .tx
1979 .unbounded_send(TransportFrame::Single(RawJsonRpcMessage::request(
1980 "initialize".into(),
1981 serde_json::json!({ "protocolVersion": 1, "clientCapabilities": {} }),
1982 RequestId::Number(1),
1983 )?))
1984 .unwrap();
1985 assert!(channel.rx.next().await.is_some(), "initialize response");
1986 for index in 0..3 {
1989 channel
1990 .tx
1991 .unbounded_send(TransportFrame::Single(RawJsonRpcMessage::notification(
1992 "test/final".into(),
1993 serde_json::json!({ "index": index, "payload": "x".repeat(1024) }),
1994 )?))
1995 .unwrap();
1996 }
1997 Ok(())
1998 };
1999 match driver {
2000 Some(driver) => crate::util::run_until(driver, foreground).await,
2001 None => foreground.await,
2002 }
2003 }
2004 }
2005
2006 #[tokio::test]
2007 async fn connector_completion_flushes_byte_streams_without_remote_read_eof() {
2008 use tokio::io::{AsyncBufReadExt as _, AsyncWriteExt as _, BufReader};
2009 use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _};
2010
2011 let (writer, remote_reader) = tokio::io::duplex(64);
2012 let (mut remote_input, reader) = tokio::io::duplex(64);
2013 let physical = crate::ByteStreams::new(writer.compat_write(), reader.compat());
2014 let mut physical = Some(crate::DynConnectTo::<Client>::new(physical));
2015 let connector = tokio::spawn(
2016 ClientProtocolConnector::new()
2017 .with_v1(|| QueuedFinalClient)
2018 .connect_to(move || physical.take().expect("one physical connection")),
2019 );
2020 let mut lines = BufReader::new(remote_reader).lines();
2021
2022 tokio::time::timeout(std::time::Duration::from_secs(5), async {
2023 let initialize = lines
2024 .next_line()
2025 .await
2026 .unwrap()
2027 .expect("initialize request");
2028 let value: serde_json::Value = serde_json::from_str(&initialize).unwrap();
2029 assert_eq!(value["method"], "initialize");
2030 remote_input
2031 .write_all(b"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"protocolVersion\":1}}\n")
2032 .await
2033 .unwrap();
2034 for index in 0..3 {
2035 let line = lines
2036 .next_line()
2037 .await
2038 .unwrap()
2039 .expect("accepted final output");
2040 let value: serde_json::Value = serde_json::from_str(&line).unwrap();
2041 assert_eq!(value["params"]["index"], index);
2042 assert_eq!(value["params"]["payload"].as_str().unwrap().len(), 1024);
2043 }
2044 connector.await.unwrap().unwrap();
2045 assert!(lines.next_line().await.unwrap().is_none());
2046 })
2047 .await
2048 .expect("physical flush must not wait for independent remote input EOF");
2049 drop(remote_input);
2050 }
2051
2052 #[derive(Default, Debug)]
2053 struct GatedLineSinkState {
2054 pending: Vec<String>,
2055 flushed: Vec<String>,
2056 closed: bool,
2057 dropped: bool,
2058 }
2059
2060 struct GatedLineSink {
2061 state: std::sync::Arc<std::sync::Mutex<GatedLineSinkState>>,
2062 release: futures::channel::oneshot::Receiver<()>,
2063 released: bool,
2064 }
2065
2066 impl futures::Sink<String> for GatedLineSink {
2067 type Error = std::io::Error;
2068
2069 fn poll_ready(
2070 self: std::pin::Pin<&mut Self>,
2071 _: &mut std::task::Context<'_>,
2072 ) -> std::task::Poll<Result<(), Self::Error>> {
2073 std::task::Poll::Ready(Ok(()))
2074 }
2075
2076 fn start_send(self: std::pin::Pin<&mut Self>, line: String) -> Result<(), Self::Error> {
2077 self.state.lock().unwrap().pending.push(line);
2078 Ok(())
2079 }
2080
2081 fn poll_flush(
2082 mut self: std::pin::Pin<&mut Self>,
2083 cx: &mut std::task::Context<'_>,
2084 ) -> std::task::Poll<Result<(), Self::Error>> {
2085 if !self.released {
2086 if std::pin::Pin::new(&mut self.release).poll(cx).is_pending() {
2087 return std::task::Poll::Pending;
2088 }
2089 self.released = true;
2090 }
2091 let mut state = self.state.lock().unwrap();
2092 let pending = std::mem::take(&mut state.pending);
2093 state.flushed.extend(pending);
2094 std::task::Poll::Ready(Ok(()))
2095 }
2096
2097 fn poll_close(
2098 mut self: std::pin::Pin<&mut Self>,
2099 cx: &mut std::task::Context<'_>,
2100 ) -> std::task::Poll<Result<(), Self::Error>> {
2101 match self.as_mut().poll_flush(cx) {
2102 std::task::Poll::Ready(Ok(())) => {
2103 self.state.lock().unwrap().closed = true;
2104 std::task::Poll::Ready(Ok(()))
2105 }
2106 result => result,
2107 }
2108 }
2109 }
2110
2111 impl Drop for GatedLineSink {
2112 fn drop(&mut self) {
2113 self.state.lock().unwrap().dropped = true;
2114 }
2115 }
2116
2117 #[tokio::test]
2118 async fn foreground_completion_flushes_lines_with_already_normalized_remote_input() {
2119 let state = std::sync::Arc::new(std::sync::Mutex::new(GatedLineSinkState::default()));
2120 let (release_tx, release_rx) = futures::channel::oneshot::channel();
2121 let sink = GatedLineSink {
2122 state: state.clone(),
2123 release: release_rx,
2124 released: false,
2125 };
2126 let (remote_input, incoming) = futures::channel::mpsc::unbounded();
2127 remote_input
2128 .unbounded_send(Ok(
2129 r#"{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1}}"#.to_string(),
2130 ))
2131 .unwrap();
2132 remote_input
2133 .unbounded_send(Ok(
2134 r#"{"jsonrpc":"2.0","method":"test/queued","params":{}}"#.to_string(),
2135 ))
2136 .unwrap();
2137 let physical = RunningProtocolPeer::new::<Client>(crate::Lines::new(sink, incoming));
2138 let (initialize, physical) = physical.next_frame().await.unwrap().unwrap();
2140 assert_eq!(
2141 futures::Stream::size_hint(&physical.rx).0,
2142 1,
2143 "trailing input must already be in the original normalized queue"
2144 );
2145
2146 let (Channel { rx, tx }, mut local) = Channel::duplex();
2147 tx.unbounded_send(initialize).unwrap();
2148 let foreground = RunningProtocolPeer {
2149 rx,
2150 tx,
2151 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(async move {
2152 assert!(local.rx.next().await.is_some(), "initialize response");
2153 for index in 0..3 {
2154 local
2155 .tx
2156 .unbounded_send(TransportFrame::Single(RawJsonRpcMessage::notification(
2157 "test/final".into(),
2158 serde_json::json!({ "index": index, "payload": "x".repeat(1024) }),
2159 )?))
2160 .unwrap();
2161 }
2162 drop(local);
2164 Ok(())
2165 })),
2166 };
2167 let mut bridge = Box::pin(pipe_protocol_peers_until_done(foreground, physical));
2168 let early_result = bridge.as_mut().now_or_never();
2169 assert!(
2170 !state.lock().unwrap().pending.is_empty(),
2171 "the real physical writer must accept output before the gated flush"
2172 );
2173 let _released = release_tx.send(());
2175 let result = match early_result {
2176 Some(result) => result,
2177 None => tokio::time::timeout(std::time::Duration::from_secs(1), bridge)
2178 .await
2179 .expect("physical drain must not require remote input EOF"),
2180 };
2181 let state = state.lock().unwrap();
2182 assert!(
2183 result.is_ok() && state.flushed.len() == 3 && state.closed,
2184 "accepted output must drain cleanly despite queued remote input: result={result:?}, pending={}, flushed={}, closed={}, dropped={}",
2185 state.pending.len(),
2186 state.flushed.len(),
2187 state.closed,
2188 state.dropped,
2189 );
2190 for (index, line) in state.flushed.iter().enumerate() {
2191 let value: serde_json::Value = serde_json::from_str(line).unwrap();
2192 assert_eq!(value["method"], "test/final");
2193 assert_eq!(value["params"]["index"], index);
2194 assert_eq!(value["params"]["payload"].as_str().unwrap().len(), 1024);
2195 }
2196 drop(remote_input);
2197 }
2198
2199 #[tokio::test]
2200 async fn foreground_completion_keeps_read_errors_during_lines_drain() {
2201 let state = std::sync::Arc::new(std::sync::Mutex::new(GatedLineSinkState::default()));
2202 let (_release_tx, release_rx) = futures::channel::oneshot::channel();
2203 let sink = GatedLineSink {
2204 state: state.clone(),
2205 release: release_rx,
2206 released: false,
2207 };
2208 let (remote_input, incoming) = futures::channel::mpsc::unbounded();
2209 let physical = RunningProtocolPeer::new::<Client>(crate::Lines::new(sink, incoming));
2210 let (Channel { rx, tx }, local) = Channel::duplex();
2211 local.tx.unbounded_send(frame()).unwrap();
2212 drop(local);
2213 let foreground = RunningProtocolPeer {
2214 rx,
2215 tx,
2216 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Ok(())))),
2217 };
2218 let mut bridge = Box::pin(pipe_protocol_peers_until_done(foreground, physical));
2219 assert!(bridge.as_mut().now_or_never().is_none());
2220 assert_eq!(state.lock().unwrap().pending.len(), 1);
2221
2222 remote_input
2225 .unbounded_send(Ok(r#"{"jsonrpc":"2.0","method":"late"}"#.to_string()))
2226 .unwrap();
2227 remote_input
2228 .unbounded_send(Err(std::io::Error::other("read failed after foreground")))
2229 .unwrap();
2230 let error = tokio::time::timeout(std::time::Duration::from_secs(1), bridge)
2231 .await
2232 .expect("read failure must not wait for the sink gate")
2233 .expect_err("real read error must win over foreground success");
2234 assert!(
2235 error
2236 .data
2237 .unwrap()
2238 .to_string()
2239 .contains("read failed after foreground"),
2240 "the read error must not become a receiver-gone forwarding error"
2241 );
2242 assert_eq!(state.lock().unwrap().flushed, Vec::<String>::new());
2243 }
2244
2245 #[tokio::test]
2246 async fn foreground_completion_cancels_opposed_opaque_work_in_both_directions() {
2247 for foreground_on_left in [true, false] {
2248 let (Channel { rx, tx }, foreground_remote) = Channel::duplex();
2249 foreground_remote.tx.unbounded_send(frame()).unwrap();
2250 let foreground = RunningProtocolPeer {
2251 rx,
2252 tx,
2253 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Ok(())))),
2254 };
2255 let (Channel { rx, tx }, mut opposed_remote) = Channel::duplex();
2256 let (work_tx, work_rx) = futures::channel::oneshot::channel::<()>();
2257 let opposed = RunningProtocolPeer {
2258 rx,
2259 tx,
2260 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(async move {
2261 work_rx.await.map_err(crate::util::internal_error)?;
2262 Ok(())
2263 })),
2264 };
2265 let mut bridge = Box::pin(if foreground_on_left {
2266 pipe_protocol_peers_until_done(foreground, opposed)
2267 } else {
2268 pipe_protocol_peers_until_done(opposed, foreground)
2269 });
2270
2271 assert_eq!(
2272 bridge.as_mut().now_or_never(),
2273 Some(Ok(())),
2274 "finite foreground must not join an opaque pending peer"
2275 );
2276 assert!(work_tx.is_canceled(), "opaque work must be dropped");
2277 assert!(opposed_remote.rx.next().await.is_some());
2278 assert!(opposed_remote.rx.next().await.is_none());
2279 }
2280 }
2281
2282 #[tokio::test]
2283 async fn foreground_completion_does_not_hide_opposed_ready_driver_error() {
2284 let (Channel { rx, tx }, _foreground_remote) = Channel::duplex();
2285 let foreground = RunningProtocolPeer {
2286 rx,
2287 tx,
2288 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Ok(())))),
2289 };
2290 let (Channel { rx, tx }, _opposed_remote) = Channel::duplex();
2291 let error = crate::Error::internal_error().data("opposed driver failed");
2292 let opposed = RunningProtocolPeer {
2293 rx,
2294 tx,
2295 driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Err(
2296 error.clone()
2297 )))),
2298 };
2299
2300 assert_eq!(
2301 pipe_protocol_peers_until_done(foreground, opposed).await,
2302 Err(error)
2303 );
2304 }
2305
2306 #[tokio::test]
2307 async fn passive_protocol_bridge_preserves_the_reverse_half_after_eof() {
2308 let (left, mut remote_left) = Channel::duplex();
2309 let (right, mut remote_right) = Channel::duplex();
2310 let mut bridge = Box::pin(pipe_protocol_peers_until_done(
2311 RunningProtocolPeer::new::<UntypedRole>(left),
2312 RunningProtocolPeer::new::<UntypedRole>(right),
2313 ));
2314 remote_left.tx.close_channel();
2315 assert!(bridge.as_mut().now_or_never().is_none());
2316 assert!(remote_right.rx.next().await.is_none());
2317
2318 remote_right.tx.unbounded_send(frame()).unwrap();
2319 remote_right.tx.close_channel();
2320 bridge.await.unwrap();
2321 assert!(remote_left.rx.next().await.is_some());
2322 assert!(remote_left.rx.next().await.is_none());
2323 }
2324}