Skip to content

Commit 2b6de00

Browse files
committed
feat: fixing issue 547 by using the tenant from the agent card on the
client side * tenant can be set as a request parameter. * the tenant defined in the agent card is the default one. * the tenant is validated to avoid security issues. Signed-off-by: Emmanuel Hugonnet <[email protected]>
1 parent 487cd46 commit 2b6de00

File tree

16 files changed

+620
-137
lines changed

16 files changed

+620
-137
lines changed

client/base/src/main/java/io/a2a/A2A.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, M
182182
* @throws A2AClientJSONError If the response body cannot be decoded as JSON or validated against the AgentCard schema
183183
*/
184184
public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AClientError, A2AClientJSONError {
185-
A2ACardResolver resolver = new A2ACardResolver(httpClient, agentUrl, relativeCardPath, authHeaders);
185+
A2ACardResolver resolver = new A2ACardResolver(httpClient, agentUrl, "", relativeCardPath, authHeaders);
186186
return resolver.getAgentCard();
187187
}
188188
}

client/base/src/main/java/io/a2a/client/ClientBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private ClientTransport buildClientTransport() throws A2AClientException {
105105
throw new A2AClientException("Missing required TransportConfig for " + agentInterface.protocolBinding());
106106
}
107107

108-
return clientTransportProvider.create(clientTransportConfig, agentCard, agentInterface.url());
108+
return clientTransportProvider.create(clientTransportConfig, agentCard, agentInterface);
109109
}
110110

111111
private Map<String, String> getServerPreferredTransports() throws A2AClientException {

client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java

Lines changed: 67 additions & 32 deletions
Large diffs are not rendered by default.

client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransportProvider.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.a2a.client.transport.spi.ClientTransportProvider;
44
import io.a2a.spec.A2AClientException;
55
import io.a2a.spec.AgentCard;
6+
import io.a2a.spec.AgentInterface;
67
import io.a2a.spec.TransportProtocol;
78
import io.grpc.Channel;
89

@@ -12,12 +13,12 @@
1213
public class GrpcTransportProvider implements ClientTransportProvider<GrpcTransport, GrpcTransportConfig> {
1314

1415
@Override
15-
public GrpcTransport create(GrpcTransportConfig grpcTransportConfig, AgentCard agentCard, String agentUrl) throws A2AClientException {
16+
public GrpcTransport create(GrpcTransportConfig grpcTransportConfig, AgentCard agentCard, AgentInterface agentInterface) throws A2AClientException {
1617
// not making use of the interceptors for gRPC for now
1718

18-
Channel channel = grpcTransportConfig.getChannelFactory().apply(agentUrl);
19+
Channel channel = grpcTransportConfig.getChannelFactory().apply(agentInterface.url());
1920
if (channel != null) {
20-
return new GrpcTransport(channel, agentCard, grpcTransportConfig.getInterceptors());
21+
return new GrpcTransport(channel, agentCard, agentInterface.tenant(), grpcTransportConfig.getInterceptors());
2122
}
2223

2324
throw new A2AClientException("Missing required GrpcTransportConfig");

client/transport/jsonrpc/src/main/java/io/a2a/client/transport/jsonrpc/JSONRPCTransport.java

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.a2a.spec.A2AClientError;
2626
import io.a2a.spec.A2AClientException;
2727
import io.a2a.spec.AgentCard;
28+
import io.a2a.spec.AgentInterface;
2829
import io.a2a.spec.CancelTaskRequest;
2930
import io.a2a.spec.CancelTaskResponse;
3031
import io.a2a.spec.DeleteTaskPushNotificationConfigParams;
@@ -66,24 +67,24 @@
6667
public class JSONRPCTransport implements ClientTransport {
6768

6869
private final A2AHttpClient httpClient;
69-
private final String agentUrl;
70+
private final AgentInterface agentInterface;
7071
private final @Nullable List<ClientCallInterceptor> interceptors;
7172
private @Nullable AgentCard agentCard;
7273
private boolean needsExtendedCard = false;
7374

7475
public JSONRPCTransport(String agentUrl) {
75-
this(null, null, agentUrl, null);
76+
this(null, null, new AgentInterface("JSONRPC", agentUrl), null);
7677
}
7778

7879
public JSONRPCTransport(AgentCard agentCard) {
7980
this(null, agentCard, Utils.getFavoriteInterface(agentCard), null);
8081
}
8182

8283
public JSONRPCTransport(@Nullable A2AHttpClient httpClient, @Nullable AgentCard agentCard,
83-
String agentUrl, @Nullable List<ClientCallInterceptor> interceptors) {
84+
AgentInterface agentInterface, @Nullable List<ClientCallInterceptor> interceptors) {
8485
this.httpClient = httpClient == null ? new JdkA2AHttpClient() : httpClient;
8586
this.agentCard = agentCard;
86-
this.agentUrl = agentUrl;
87+
this.agentInterface = agentInterface;
8788
this.interceptors = interceptors;
8889
this.needsExtendedCard = agentCard == null || agentCard.supportsExtendedAgentCard();
8990
}
@@ -95,7 +96,7 @@ public EventKind sendMessage(MessageSendParams request, @Nullable ClientCallCont
9596
agentCard, context);
9697

9798
try {
98-
String httpResponseBody = sendPostRequest(payloadAndHeaders, SendMessageRequest.METHOD);
99+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, SendMessageRequest.METHOD);
99100
SendMessageResponse response = unmarshalResponse(httpResponseBody, SendMessageRequest.METHOD);
100101
return response.getResult();
101102
} catch (A2AClientException e) {
@@ -117,7 +118,7 @@ public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEv
117118
SSEEventListener sseEventListener = new SSEEventListener(eventConsumer, errorConsumer);
118119

119120
try {
120-
A2AHttpClient.PostBuilder builder = createPostBuilder(payloadAndHeaders, SendStreamingMessageRequest.METHOD);
121+
A2AHttpClient.PostBuilder builder = createPostBuilder(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, SendStreamingMessageRequest.METHOD);
121122
ref.set(builder.postAsyncSSE(
122123
msg -> sseEventListener.onMessage(msg, ref.get()),
123124
throwable -> sseEventListener.onError(throwable, ref.get()),
@@ -141,7 +142,7 @@ public Task getTask(TaskQueryParams request, @Nullable ClientCallContext context
141142
agentCard, context);
142143

143144
try {
144-
String httpResponseBody = sendPostRequest(payloadAndHeaders, GetTaskRequest.METHOD);
145+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, GetTaskRequest.METHOD);
145146
GetTaskResponse response = unmarshalResponse(httpResponseBody, GetTaskRequest.METHOD);
146147
return response.getResult();
147148
} catch (A2AClientException e) {
@@ -158,7 +159,7 @@ public Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context
158159
agentCard, context);
159160

160161
try {
161-
String httpResponseBody = sendPostRequest(payloadAndHeaders, CancelTaskRequest.METHOD);
162+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, CancelTaskRequest.METHOD);
162163
CancelTaskResponse response = unmarshalResponse(httpResponseBody, CancelTaskRequest.METHOD);
163164
return response.getResult();
164165
} catch (A2AClientException e) {
@@ -174,7 +175,7 @@ public ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallCo
174175
PayloadAndHeaders payloadAndHeaders = applyInterceptors(ListTasksRequest.METHOD, ProtoUtils.ToProto.listTasksParams(request),
175176
agentCard, context);
176177
try {
177-
String httpResponseBody = sendPostRequest(payloadAndHeaders, ListTasksRequest.METHOD);
178+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, ListTasksRequest.METHOD);
178179
ListTasksResponse response = unmarshalResponse(httpResponseBody, ListTasksRequest.METHOD);
179180
return response.getResult();
180181
} catch (IOException | InterruptedException | JsonProcessingException e) {
@@ -190,7 +191,7 @@ public TaskPushNotificationConfig setTaskPushNotificationConfiguration(TaskPushN
190191
ProtoUtils.ToProto.setTaskPushNotificationConfigRequest(request), agentCard, context);
191192

192193
try {
193-
String httpResponseBody = sendPostRequest(payloadAndHeaders, SetTaskPushNotificationConfigRequest.METHOD);
194+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, SetTaskPushNotificationConfigRequest.METHOD);
194195
SetTaskPushNotificationConfigResponse response = unmarshalResponse(httpResponseBody,
195196
SetTaskPushNotificationConfigRequest.METHOD);
196197
return response.getResult();
@@ -209,7 +210,7 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(GetTaskPu
209210
ProtoUtils.ToProto.getTaskPushNotificationConfigRequest(request), agentCard, context);
210211

211212
try {
212-
String httpResponseBody = sendPostRequest(payloadAndHeaders,GetTaskPushNotificationConfigRequest.METHOD);
213+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders,GetTaskPushNotificationConfigRequest.METHOD);
213214
GetTaskPushNotificationConfigResponse response = unmarshalResponse(httpResponseBody,
214215
GetTaskPushNotificationConfigRequest.METHOD);
215216
return response.getResult();
@@ -229,7 +230,7 @@ public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations(
229230
ProtoUtils.ToProto.listTaskPushNotificationConfigRequest(request), agentCard, context);
230231

231232
try {
232-
String httpResponseBody = sendPostRequest(payloadAndHeaders, ListTaskPushNotificationConfigRequest.METHOD);
233+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders, ListTaskPushNotificationConfigRequest.METHOD);
233234
ListTaskPushNotificationConfigResponse response = unmarshalResponse(httpResponseBody,
234235
ListTaskPushNotificationConfigRequest.METHOD);
235236
return response.getResult();
@@ -248,7 +249,7 @@ public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationC
248249
ProtoUtils.ToProto.deleteTaskPushNotificationConfigRequest(request), agentCard, context);
249250

250251
try {
251-
String httpResponseBody = sendPostRequest(payloadAndHeaders,DeleteTaskPushNotificationConfigRequest.METHOD);
252+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders,DeleteTaskPushNotificationConfigRequest.METHOD);
252253
DeleteTaskPushNotificationConfigResponse response = unmarshalResponse(httpResponseBody, DeleteTaskPushNotificationConfigRequest.METHOD);
253254
// Response validated (no error), but no result to return
254255
} catch (A2AClientException e) {
@@ -271,7 +272,7 @@ public void resubscribe(TaskIdParams request, Consumer<StreamingEventKind> event
271272
SSEEventListener sseEventListener = new SSEEventListener(eventConsumer, errorConsumer);
272273

273274
try {
274-
A2AHttpClient.PostBuilder builder = createPostBuilder(payloadAndHeaders,SubscribeToTaskRequest.METHOD);
275+
A2AHttpClient.PostBuilder builder = createPostBuilder(Utils.buildBaseUrl(agentInterface, request.tenant()), payloadAndHeaders,SubscribeToTaskRequest.METHOD);
275276
ref.set(builder.postAsyncSSE(
276277
msg -> sseEventListener.onMessage(msg, ref.get()),
277278
throwable -> sseEventListener.onError(throwable, ref.get()),
@@ -293,7 +294,7 @@ public AgentCard getAgentCard(@Nullable ClientCallContext context) throws A2ACli
293294
A2ACardResolver resolver;
294295
try {
295296
if (agentCard == null) {
296-
resolver = new A2ACardResolver(httpClient, agentUrl, null, getHttpHeaders(context));
297+
resolver = new A2ACardResolver(httpClient, agentInterface.url(), agentInterface.tenant(), null, getHttpHeaders(context));
297298
agentCard = resolver.getAgentCard();
298299
needsExtendedCard = agentCard.supportsExtendedAgentCard();
299300
}
@@ -309,7 +310,7 @@ public AgentCard getAgentCard(@Nullable ClientCallContext context) throws A2ACli
309310
ProtoUtils.ToProto.extendedAgentCard(getExtendedAgentCardRequest), agentCard, context);
310311

311312
try {
312-
String httpResponseBody = sendPostRequest(payloadAndHeaders,GetAuthenticatedExtendedCardRequest.METHOD);
313+
String httpResponseBody = sendPostRequest(Utils.buildBaseUrl(agentInterface, ""), payloadAndHeaders,GetAuthenticatedExtendedCardRequest.METHOD);
313314
GetAuthenticatedExtendedCardResponse response = unmarshalResponse(httpResponseBody,
314315
GetAuthenticatedExtendedCardRequest.METHOD);
315316
agentCard = response.getResult();
@@ -340,18 +341,18 @@ private PayloadAndHeaders applyInterceptors(String methodName, @Nullable Object
340341
return payloadAndHeaders;
341342
}
342343

343-
private String sendPostRequest(PayloadAndHeaders payloadAndHeaders, String method) throws IOException, InterruptedException, JsonProcessingException {
344-
A2AHttpClient.PostBuilder builder = createPostBuilder(payloadAndHeaders,method);
344+
private String sendPostRequest(String url, PayloadAndHeaders payloadAndHeaders, String method) throws IOException, InterruptedException, JsonProcessingException {
345+
A2AHttpClient.PostBuilder builder = createPostBuilder(url, payloadAndHeaders,method);
345346
A2AHttpResponse response = builder.post();
346347
if (!response.success()) {
347348
throw new IOException("Request failed " + response.status());
348349
}
349350
return response.body();
350351
}
351352

352-
private A2AHttpClient.PostBuilder createPostBuilder(PayloadAndHeaders payloadAndHeaders, String method) throws JsonProcessingException {
353+
private A2AHttpClient.PostBuilder createPostBuilder(String url, PayloadAndHeaders payloadAndHeaders, String method) throws JsonProcessingException {
353354
A2AHttpClient.PostBuilder postBuilder = httpClient.createPost()
354-
.url(agentUrl)
355+
.url(url)
355356
.addHeader("Content-Type", "application/json")
356357
.body(JSONRPCUtils.toJsonRPCRequest(null, method, (MessageOrBuilder) payloadAndHeaders.getPayload()));
357358

client/transport/jsonrpc/src/main/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
import io.a2a.client.transport.spi.ClientTransportProvider;
55
import io.a2a.spec.A2AClientException;
66
import io.a2a.spec.AgentCard;
7+
import io.a2a.spec.AgentInterface;
78
import io.a2a.spec.TransportProtocol;
89
import org.jspecify.annotations.Nullable;
910

1011
public class JSONRPCTransportProvider implements ClientTransportProvider<JSONRPCTransport, JSONRPCTransportConfig> {
1112

1213
@Override
13-
public JSONRPCTransport create(@Nullable JSONRPCTransportConfig clientTransportConfig, AgentCard agentCard, String agentUrl) throws A2AClientException {
14+
public JSONRPCTransport create(@Nullable JSONRPCTransportConfig clientTransportConfig, AgentCard agentCard, AgentInterface agentInterface) throws A2AClientException {
1415
JSONRPCTransportConfig currentClientTransportConfig = clientTransportConfig;
1516
if (currentClientTransportConfig == null) {
1617
currentClientTransportConfig = new JSONRPCTransportConfig(new JdkA2AHttpClient());
1718
}
18-
return new JSONRPCTransport(currentClientTransportConfig.getHttpClient(), agentCard, agentUrl, currentClientTransportConfig.getInterceptors());
19+
return new JSONRPCTransport(currentClientTransportConfig.getHttpClient(), agentCard, agentInterface, currentClientTransportConfig.getInterceptors());
1920
}
2021

2122
@Override

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public void testA2AClientSetTaskPushNotificationConfig() throws Exception {
342342
.url("https://example.com/callback")
343343
.authentication(new AuthenticationInfo(Collections.singletonList("jwt"),
344344
null))
345-
.build(), "tenant"), null);
345+
.build(), ""), null);
346346
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
347347
assertNotNull(pushNotificationConfig);
348348
assertEquals("https://example.com/callback", pushNotificationConfig.url());
@@ -369,7 +369,7 @@ public void testA2AClientGetAgentCard() throws Exception {
369369
AgentCard agentCard = client.getAgentCard(null);
370370
assertEquals("GeoSpatial Route Planner Agent", agentCard.name());
371371
assertEquals("Provides advanced route planning, traffic analysis, and custom map generation services. This agent can calculate optimal routes, estimate travel times considering real-time traffic, and create personalized maps with points of interest.", agentCard.description());
372-
assertEquals("https://georoute-agent.example.com/a2a/v1", Utils.getFavoriteInterface(agentCard));
372+
assertEquals("https://georoute-agent.example.com/a2a/v1", Utils.getFavoriteInterface(agentCard).url());
373373
assertEquals("Example Geo Services Inc.", agentCard.provider().organization());
374374
assertEquals("https://www.examplegeoservices.com", agentCard.provider().url());
375375
assertEquals("1.2.0", agentCard.version());
@@ -457,7 +457,7 @@ public void testA2AClientGetAuthenticatedExtendedAgentCard() throws Exception {
457457
AgentCard agentCard = client.getAgentCard(null);
458458
assertEquals("GeoSpatial Route Planner Agent Extended", agentCard.name());
459459
assertEquals("Extended description", agentCard.description());
460-
assertEquals("https://georoute-agent.example.com/a2a/v1", Utils.getFavoriteInterface(agentCard));
460+
assertEquals("https://georoute-agent.example.com/a2a/v1", Utils.getFavoriteInterface(agentCard).url());
461461
assertEquals("Example Geo Services Inc.", agentCard.provider().organization());
462462
assertEquals("https://www.examplegeoservices.com", agentCard.provider().url());
463463
assertEquals("1.2.0", agentCard.version());

0 commit comments

Comments
 (0)