Skip to content

Commit cc3de13

Browse files
committed
fix: harden 0.3 client compatibility adapters
1 parent 7979c15 commit cc3de13

9 files changed

Lines changed: 105 additions & 14 deletions

File tree

‎compat-0.3/client/adapter-grpc/src/main/java/org/a2aproject/sdk/compat03/client/adapter/grpc/GrpcCompat03ClientTransport.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEv
5555
ProtoUtils.ToProto.sendMessageRequest(request), org.a2aproject.sdk.grpc.SendMessageRequest.class, context);
5656
Compat03ClientTransportSupport.run(() -> delegate.sendMessageStreaming(
5757
Compat03ClientTransportSupport.toV03(ProtoUtils.FromProto.messageSendParams(payloadPayload(payload))),
58-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors, legacyContext(context, payload)));
58+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
59+
Compat03ClientTransportSupport.mapAsyncError(errors), legacyContext(context, payload)));
5960
}
6061

6162
@Override
@@ -136,7 +137,8 @@ public void subscribeToTask(TaskIdParams request, Consumer<StreamingEventKind> e
136137
ProtoUtils.ToProto.subscribeToTaskRequest(request), org.a2aproject.sdk.grpc.SubscribeToTaskRequest.class, context);
137138
Compat03ClientTransportSupport.run(() -> delegate.resubscribe(
138139
Compat03ClientTransportSupport.toV03(ProtoUtils.FromProto.taskIdParams(subscribePayload(payload))),
139-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors, legacyContext(context, payload)));
140+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
141+
Compat03ClientTransportSupport.mapAsyncError(errors), legacyContext(context, payload)));
140142
}
141143

142144
private PayloadAndHeaders apply(String method, Object payload, Class<?> expected,

‎compat-0.3/client/adapter-jsonrpc/src/main/java/org/a2aproject/sdk/compat03/client/adapter/jsonrpc/JSONRPCCompat03ClientTransport.java‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEv
5757
Compat03ClientTransportSupport.run(() -> delegate.sendMessageStreaming(
5858
Compat03ClientTransportSupport.toV03(ProtoUtils.FromProto.messageSendParams(
5959
(org.a2aproject.sdk.grpc.SendMessageRequest) payload.getPayload())),
60-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors,
60+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
61+
Compat03ClientTransportSupport.mapAsyncError(errors),
6162
Compat03ClientCallContextMapper.toV03(contextWithHeaders(context, payload))));
6263
}
6364

@@ -134,12 +135,14 @@ public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationC
134135
@Override
135136
public void subscribeToTask(TaskIdParams request, Consumer<StreamingEventKind> events,
136137
Consumer<Throwable> errors, @Nullable ClientCallContext context) {
138+
Compat03ClientTransportSupport.validateTenant("subscribeToTask", request.tenant());
137139
PayloadAndHeaders payload = apply(A2AMethods.SUBSCRIBE_TO_TASK_METHOD,
138140
ProtoUtils.ToProto.subscribeToTaskRequest(request), org.a2aproject.sdk.grpc.SubscribeToTaskRequest.class, context);
139141
Compat03ClientTransportSupport.run(() -> delegate.resubscribe(
140142
Compat03ClientTransportSupport.toV03(ProtoUtils.FromProto.taskIdParams(
141143
(org.a2aproject.sdk.grpc.SubscribeToTaskRequest) payload.getPayload())),
142-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors,
144+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
145+
Compat03ClientTransportSupport.mapAsyncError(errors),
143146
Compat03ClientCallContextMapper.toV03(contextWithHeaders(context, payload))));
144147
}
145148

‎compat-0.3/client/adapter-jsonrpc/src/test/java/org/a2aproject/sdk/compat03/client/adapter/jsonrpc/JSONRPCCompat03ClientTransportTest.java‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
package org.a2aproject.sdk.compat03.client.adapter.jsonrpc;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
46
import static org.junit.jupiter.api.Assertions.assertTrue;
57

8+
import java.util.List;
9+
10+
import org.a2aproject.sdk.client.transport.spi.interceptors.ClientCallContext;
611
import org.a2aproject.sdk.client.transport.jsonrpc.JSONRPCTransport;
12+
import org.a2aproject.sdk.compat03.client.transport.spi.ClientTransport_v0_3;
13+
import org.a2aproject.sdk.compat03.client.transport.spi.interceptors.ClientCallContext_v0_3;
14+
import org.a2aproject.sdk.compat03.spec.AgentCard_v0_3;
15+
import org.a2aproject.sdk.compat03.spec.DeleteTaskPushNotificationConfigParams_v0_3;
16+
import org.a2aproject.sdk.compat03.spec.EventKind_v0_3;
17+
import org.a2aproject.sdk.compat03.spec.GetTaskPushNotificationConfigParams_v0_3;
18+
import org.a2aproject.sdk.compat03.spec.ListTaskPushNotificationConfigParams_v0_3;
19+
import org.a2aproject.sdk.compat03.spec.MessageSendParams_v0_3;
20+
import org.a2aproject.sdk.compat03.spec.StreamingEventKind_v0_3;
21+
import org.a2aproject.sdk.compat03.spec.TaskIdParams_v0_3;
22+
import org.a2aproject.sdk.compat03.spec.TaskPushNotificationConfig_v0_3;
23+
import org.a2aproject.sdk.compat03.spec.TaskQueryParams_v0_3;
24+
import org.a2aproject.sdk.spec.AgentCapabilities;
25+
import org.a2aproject.sdk.spec.AgentCard;
26+
import org.a2aproject.sdk.spec.AgentInterface;
727
import org.junit.jupiter.api.Test;
828

929
class JSONRPCCompat03ClientTransportTest {
@@ -16,4 +36,42 @@ void providerTargetsJsonRpcAndOrdinaryJsonRpcConfiguration() {
1636
assertEquals(JSONRPCTransport.class, provider.configuredTransportClass());
1737
assertTrue(provider.getClass().getPackageName().contains("adapter.jsonrpc"));
1838
}
39+
40+
@Test
41+
void rejectsTenantBeforeCallingLegacyDelegate() {
42+
RecordingDelegate delegate = new RecordingDelegate();
43+
JSONRPCCompat03ClientTransport transport = new JSONRPCCompat03ClientTransport(
44+
delegate, testCard(), List.of());
45+
46+
assertThrows(org.a2aproject.sdk.spec.A2AClientException.class,
47+
() -> transport.subscribeToTask(new org.a2aproject.sdk.spec.TaskIdParams("task", "tenant"),
48+
event -> { }, error -> { }, (ClientCallContext) null));
49+
assertFalse(delegate.called);
50+
}
51+
52+
private static AgentCard testCard() {
53+
return AgentCard.builder().name("agent").description("description").version("1")
54+
.capabilities(new AgentCapabilities(false, false, false, null))
55+
.defaultInputModes(List.of("text")).defaultOutputModes(List.of("text"))
56+
.skills(List.of()).supportedInterfaces(List.of(new AgentInterface("JSONRPC", "https://example.test", null, "0.3")))
57+
.build();
58+
}
59+
60+
private static final class RecordingDelegate implements ClientTransport_v0_3 {
61+
boolean called;
62+
63+
@Override public EventKind_v0_3 sendMessage(MessageSendParams_v0_3 request, ClientCallContext_v0_3 context) { return null; }
64+
@Override public void sendMessageStreaming(MessageSendParams_v0_3 request, java.util.function.Consumer<StreamingEventKind_v0_3> events,
65+
java.util.function.Consumer<Throwable> errors, ClientCallContext_v0_3 context) { }
66+
@Override public org.a2aproject.sdk.compat03.spec.Task_v0_3 getTask(TaskQueryParams_v0_3 request, ClientCallContext_v0_3 context) { return null; }
67+
@Override public org.a2aproject.sdk.compat03.spec.Task_v0_3 cancelTask(TaskIdParams_v0_3 request, ClientCallContext_v0_3 context) { return null; }
68+
@Override public TaskPushNotificationConfig_v0_3 setTaskPushNotificationConfiguration(TaskPushNotificationConfig_v0_3 request, ClientCallContext_v0_3 context) { return null; }
69+
@Override public TaskPushNotificationConfig_v0_3 getTaskPushNotificationConfiguration(GetTaskPushNotificationConfigParams_v0_3 request, ClientCallContext_v0_3 context) { return null; }
70+
@Override public List<TaskPushNotificationConfig_v0_3> listTaskPushNotificationConfigurations(ListTaskPushNotificationConfigParams_v0_3 request, ClientCallContext_v0_3 context) { return List.of(); }
71+
@Override public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationConfigParams_v0_3 request, ClientCallContext_v0_3 context) { }
72+
@Override public void resubscribe(TaskIdParams_v0_3 request, java.util.function.Consumer<StreamingEventKind_v0_3> events,
73+
java.util.function.Consumer<Throwable> errors, ClientCallContext_v0_3 context) { called = true; }
74+
@Override public AgentCard_v0_3 getAgentCard(ClientCallContext_v0_3 context) { return null; }
75+
@Override public void close() { }
76+
}
1977
}

‎compat-0.3/client/adapter-rest/src/main/java/org/a2aproject/sdk/compat03/client/adapter/rest/RestCompat03ClientTransport.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEv
5757
Compat03ClientTransportSupport.run(() -> delegate.sendMessageStreaming(
5858
Compat03ClientTransportSupport.toV03(ProtoUtils.FromProto.messageSendParams(
5959
(org.a2aproject.sdk.grpc.SendMessageRequest) payload.getPayload())),
60-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors,
60+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
61+
Compat03ClientTransportSupport.mapAsyncError(errors),
6162
Compat03ClientCallContextMapper.toV03(contextWithHeaders(context, payload))));
6263
}
6364

@@ -140,7 +141,8 @@ public void subscribeToTask(TaskIdParams request, Consumer<StreamingEventKind> e
140141
Compat03ClientTransportSupport.run(() -> delegate.resubscribe(
141142
Compat03ClientTransportSupport.toV03(ProtoUtils.FromProto.taskIdParams(
142143
(org.a2aproject.sdk.grpc.SubscribeToTaskRequest) payload.getPayload())),
143-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors,
144+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
145+
Compat03ClientTransportSupport.mapAsyncError(errors),
144146
Compat03ClientCallContextMapper.toV03(contextWithHeaders(context, payload))));
145147
}
146148

‎compat-0.3/client/adapter/src/main/java/org/a2aproject/sdk/compat03/client/adapter/Compat03ClientTransportBase.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEv
5959
Compat03ClientTransportSupport.validateMessageSend(request);
6060
Compat03ClientTransportSupport.run(() -> delegate.sendMessageStreaming(
6161
Compat03ClientTransportSupport.toV03(request),
62-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors,
62+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
63+
Compat03ClientTransportSupport.mapAsyncError(errors),
6364
Compat03ClientTransportSupport.toV03Context(context)));
6465
}
6566

@@ -126,7 +127,8 @@ public void subscribeToTask(TaskIdParams request, Consumer<StreamingEventKind> e
126127
Compat03ClientTransportSupport.validateTenant("subscribeToTask", request.tenant());
127128
Compat03ClientTransportSupport.run(() -> delegate.resubscribe(
128129
Compat03ClientTransportSupport.toV03(request),
129-
event -> events.accept(Compat03ClientTransportSupport.toV10(event)), errors,
130+
event -> events.accept(Compat03ClientTransportSupport.toV10(event)),
131+
Compat03ClientTransportSupport.mapAsyncError(errors),
130132
Compat03ClientTransportSupport.toV03Context(context)));
131133
}
132134

‎compat-0.3/client/adapter/src/main/java/org/a2aproject/sdk/compat03/client/adapter/Compat03ClientTransportSupport.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44
import java.util.Map;
5+
import java.util.function.Consumer;
56

67
import org.a2aproject.sdk.client.transport.spi.ClientTransportConfig;
78
import org.a2aproject.sdk.client.transport.spi.interceptors.ClientCallContext;
@@ -165,6 +166,11 @@ public static A2AClientException mapLegacyException(A2AClientException_v0_3 exce
165166
return Compat03ClientErrorMapper.toV10(exception);
166167
}
167168

169+
public static Consumer<Throwable> mapAsyncError(Consumer<Throwable> errors) {
170+
return error -> errors.accept(error instanceof A2AClientException_v0_3 legacy
171+
? mapLegacyException(legacy) : error);
172+
}
173+
168174
public static <T> T call(ThrowingSupplier<T> delegate) {
169175
try {
170176
return delegate.get();

‎compat-0.3/client/adapter/src/test/java/org/a2aproject/sdk/compat03/client/adapter/Compat03ClientTransportSupportTest.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.junit.jupiter.api.Assertions.assertTrue;
66

77
import java.util.Map;
8+
import java.util.concurrent.atomic.AtomicReference;
89

910
import org.a2aproject.sdk.client.transport.spi.interceptors.ClientCallContext;
1011
import org.a2aproject.sdk.compat03.client.transport.spi.interceptors.ClientCallContext_v0_3;
@@ -82,4 +83,16 @@ void rejectsGenericParametersAndMapsLegacyErrors() {
8283
assertTrue(mapped.getMessage().contains("legacy failure"));
8384
assertTrue(mapped.getCause() instanceof org.a2aproject.sdk.spec.TaskNotFoundError);
8485
}
86+
87+
@Test
88+
void mapsAsynchronousLegacyErrors() {
89+
AtomicReference<Throwable> mapped = new AtomicReference<>();
90+
91+
Compat03ClientTransportSupport.mapAsyncError(mapped::set).accept(
92+
new org.a2aproject.sdk.compat03.spec.A2AClientException_v0_3(
93+
"legacy stream failure", new org.a2aproject.sdk.compat03.spec.InternalError_v0_3("legacy")));
94+
95+
assertTrue(mapped.get() instanceof A2AClientException);
96+
assertTrue(mapped.get().getCause() instanceof org.a2aproject.sdk.spec.InternalError);
97+
}
8598
}

‎compat-0.3/conversion/src/main/java/org/a2aproject/sdk/compat03/conversion/mappers/domain/AgentCardMapper_v0_3.java‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public AgentCard toV10(AgentCard_v0_3 source) {
4040
return AgentCard.builder()
4141
.name(source.name()).description(source.description()).provider(toV10(source.provider()))
4242
.version(source.version()).documentationUrl(source.documentationUrl())
43-
.capabilities(toV10(source.capabilities()))
43+
.capabilities(toV10Capabilities(source))
4444
.defaultInputModes(source.defaultInputModes()).defaultOutputModes(source.defaultOutputModes())
4545
.skills(source.skills().stream().map(this::toV10).toList())
4646
.securitySchemes(toV10Security(source.securitySchemes()))
@@ -77,16 +77,18 @@ private AgentProvider_v0_3 fromV10(AgentProvider value) {
7777
return value == null ? null : new AgentProvider_v0_3(value.organization(), value.url());
7878
}
7979

80-
private AgentCapabilities toV10(AgentCapabilities_v0_3 value) {
81-
List<AgentExtension> extensions = value.extensions() == null ? null : value.extensions().stream()
80+
private AgentCapabilities toV10Capabilities(AgentCard_v0_3 value) {
81+
AgentCapabilities_v0_3 capabilities = value.capabilities();
82+
List<AgentExtension> extensions = capabilities.extensions() == null ? null : capabilities.extensions().stream()
8283
.map(e -> new AgentExtension(e.description(), e.params(), e.required(), e.uri())).toList();
83-
return new AgentCapabilities(value.streaming(), value.pushNotifications(), value.stateTransitionHistory(), extensions);
84+
return new AgentCapabilities(capabilities.streaming(), capabilities.pushNotifications(),
85+
value.supportsAuthenticatedExtendedCard(), extensions);
8486
}
8587

8688
private AgentCapabilities_v0_3 fromV10(AgentCapabilities value) {
8789
List<AgentExtension_v0_3> extensions = value.extensions() == null ? null : value.extensions().stream()
8890
.map(e -> new AgentExtension_v0_3(e.description(), e.params(), e.required(), e.uri())).toList();
89-
return new AgentCapabilities_v0_3(value.streaming(), value.pushNotifications(), value.extendedAgentCard(), extensions);
91+
return new AgentCapabilities_v0_3(value.streaming(), value.pushNotifications(), false, extensions);
9092
}
9193

9294
private AgentSkill toV10(AgentSkill_v0_3 value) {

‎compat-0.3/conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/mappers/domain/AgentCardMapper_v0_3_Test.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class AgentCardMapper_v0_3_Test {
2323
void projectsCompleteLegacyCardAndConvertsItBack() {
2424
AgentCard_v0_3 legacy = new AgentCard_v0_3(
2525
"agent", "description", "https://agent.example", null, "1", "https://docs.example",
26-
new AgentCapabilities_v0_3(true, true, true, null), List.of("text"), List.of("text"),
26+
new AgentCapabilities_v0_3(true, true, false, null), List.of("text"), List.of("text"),
2727
List.of(new AgentSkill_v0_3("skill", "Skill", "Does things", List.of("tag"),
2828
List.of("example"), List.of("text"), List.of("text"), List.of(Map.of("auth", List.of("read"))))),
2929
true, Map.of("auth", new APIKeySecurityScheme_v0_3("header", "Authorization", "token")),
@@ -36,12 +36,15 @@ void projectsCompleteLegacyCardAndConvertsItBack() {
3636
assertEquals("https://agent.example", current.url());
3737
assertEquals("JSONRPC", current.preferredTransport());
3838
assertEquals("0.3", current.supportedInterfaces().get(0).protocolVersion());
39+
assertEquals(true, current.capabilities().extendedAgentCard());
3940
assertEquals("Authorization", ((APIKeySecurityScheme) current.securitySchemes().get("auth")).name());
4041
assertNotNull(current.signatures());
4142

4243
AgentCard_v0_3 roundTrip = AgentCardMapper_v0_3.INSTANCE.fromV10(current);
4344
assertEquals("https://agent.example", roundTrip.url());
4445
assertEquals("0.3", roundTrip.protocolVersion());
46+
assertEquals(true, roundTrip.supportsAuthenticatedExtendedCard());
47+
assertEquals(false, roundTrip.capabilities().stateTransitionHistory());
4548
assertEquals("Authorization", ((APIKeySecurityScheme_v0_3) roundTrip.securitySchemes().get("auth")).name());
4649
assertEquals("signature", roundTrip.signatures().get(0).signature());
4750
}

0 commit comments

Comments
 (0)