55import io .a2a .client .transport .spi .ClientTransportConfig ;
66import io .a2a .client .transport .spi .ClientTransportConfigBuilder ;
77import io .a2a .client .transport .spi .ClientTransportProvider ;
8+ import io .a2a .client .transport .spi .ClientTransportWrapper ;
89import io .a2a .spec .A2AClientException ;
910import io .a2a .spec .AgentCard ;
1011import io .a2a .spec .AgentInterface ;
1112import io .a2a .spec .TransportProtocol ;
13+ import org .jspecify .annotations .NonNull ;
14+ import org .jspecify .annotations .Nullable ;
15+ import org .slf4j .Logger ;
16+ import org .slf4j .LoggerFactory ;
1217
1318import java .util .ArrayList ;
1419import java .util .HashMap ;
1520import java .util .LinkedHashMap ;
1621import java .util .List ;
1722import java .util .Map ;
1823import java .util .ServiceLoader ;
24+ import java .util .ServiceLoader .Provider ;
1925import java .util .function .BiConsumer ;
2026import java .util .function .Consumer ;
21- import org .jspecify .annotations .NonNull ;
22- import org .jspecify .annotations .Nullable ;
27+ import java .util .stream .Collectors ;
2328
2429public class ClientBuilder {
2530
2631 private static final Map <String , ClientTransportProvider <? extends ClientTransport , ? extends ClientTransportConfig <?>>> transportProviderRegistry = new HashMap <>();
2732 private static final Map <Class <? extends ClientTransport >, String > transportProtocolMapping = new HashMap <>();
33+ private static final Logger LOGGER = LoggerFactory .getLogger (ClientBuilder .class );
2834
2935 static {
3036 ServiceLoader <ClientTransportProvider > loader = ServiceLoader .load (ClientTransportProvider .class );
@@ -37,7 +43,8 @@ public class ClientBuilder {
3743 private final AgentCard agentCard ;
3844
3945 private final List <BiConsumer <ClientEvent , AgentCard >> consumers = new ArrayList <>();
40- private @ Nullable Consumer <Throwable > streamErrorHandler ;
46+ private @ Nullable
47+ Consumer <Throwable > streamErrorHandler ;
4148 private ClientConfig clientConfig = new ClientConfig .Builder ().build ();
4249
4350 private final Map <Class <? extends ClientTransport >, ClientTransportConfig <? extends ClientTransport >> clientTransports = new LinkedHashMap <>();
@@ -105,7 +112,7 @@ private ClientTransport buildClientTransport() throws A2AClientException {
105112 throw new A2AClientException ("Missing required TransportConfig for " + agentInterface .transport ());
106113 }
107114
108- return clientTransportProvider .create (clientTransportConfig , agentCard , agentInterface .url ());
115+ return wrap ( clientTransportProvider .create (clientTransportConfig , agentCard , agentInterface .url ()), clientTransportConfig );
109116 }
110117
111118 private Map <String , String > getServerPreferredTransports () {
@@ -160,10 +167,50 @@ private AgentInterface findBestClientTransport() throws A2AClientException {
160167 if (transportProtocol == null || transportUrl == null ) {
161168 throw new A2AClientException ("No compatible transport found" );
162169 }
163- if (! transportProviderRegistry .containsKey (transportProtocol )) {
170+ if (!transportProviderRegistry .containsKey (transportProtocol )) {
164171 throw new A2AClientException ("No client available for " + transportProtocol );
165172 }
166173
167174 return new AgentInterface (transportProtocol , transportUrl );
168175 }
176+
177+ /**
178+ * Wraps the transport with all available transport wrappers discovered via ServiceLoader.
179+ * Wrappers are applied in priority order (highest priority first).
180+ *
181+ * @param transport the base transport to wrap
182+ * @param clientTransportConfig the transport configuration
183+ * @return the wrapped transport (or original if no wrappers are available/applicable)
184+ */
185+ private ClientTransport wrap (ClientTransport transport , ClientTransportConfig <? extends ClientTransport > clientTransportConfig ) {
186+ ServiceLoader <ClientTransportWrapper > wrapperLoader = ServiceLoader .load (ClientTransportWrapper .class );
187+
188+ // Collect all wrappers and sort by natural order (uses Comparable implementation)
189+ List <ClientTransportWrapper > wrappers = wrapperLoader .stream ().map (Provider ::get )
190+ .sorted ()
191+ .collect (Collectors .toList ());
192+
193+ if (wrappers .isEmpty ()) {
194+ LOGGER .debug ("No client transport wrappers found via ServiceLoader" );
195+ return transport ;
196+ }
197+
198+ // Apply wrappers in priority order
199+ ClientTransport wrapped = transport ;
200+ for (ClientTransportWrapper wrapper : wrappers ) {
201+ try {
202+ ClientTransport newWrapped = wrapper .wrap (wrapped , clientTransportConfig );
203+ if (newWrapped != wrapped ) {
204+ LOGGER .debug ("Applied transport wrapper: {} (priority: {})" ,
205+ wrapper .getClass ().getName (), wrapper .priority ());
206+ }
207+ wrapped = newWrapped ;
208+ } catch (Exception e ) {
209+ LOGGER .warn ("Failed to apply transport wrapper {}: {}" ,
210+ wrapper .getClass ().getName (), e .getMessage (), e );
211+ }
212+ }
213+
214+ return wrapped ;
215+ }
169216}
0 commit comments