Skip to content

Commit 198504b

Browse files
authored
feat: Implement the spec updates for v0.3.0 (#212)
# Description This PR introduces some new spec classes and updates some existing ones based off the updates in the [v0.3.0 spec](https://github.com/a2aproject/A2A/releases/tag/v0.3.0). Note that the TCK job will fail until the v0.3.0 TCK is available. - [X] Follow the [`CONTRIBUTING` Guide](../CONTRIBUTING.md). - [X] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [X] Ensure the tests pass - [X] Appropriate READMEs were updated (if necessary) Fixes #209 🦕
1 parent 77b0c7b commit 198504b

File tree

109 files changed

+4555
-485
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+4555
-485
lines changed

.github/workflows/run-tck.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ on:
1212

1313
env:
1414
# Tag of the TCK
15-
TCK_VERSION: v0.2.5
15+
TCK_VERSION: 0.3.0.alpha
1616
# Tells uv to not need a venv, and instead use system
1717
UV_SYSTEM_PYTHON: 1
1818

@@ -57,7 +57,7 @@ jobs:
5757
working-directory: tck
5858
- name: Wait for SUT to start
5959
run: |
60-
URL="http://localhost:9999/.well-known/agent.json"
60+
URL="http://localhost:9999/.well-known/agent-card.json"
6161
EXPECTED_STATUS=200
6262
TIMEOUT=120
6363
RETRY_INTERVAL=2

client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>io.github.a2asdk</groupId>
99
<artifactId>a2a-java-sdk-parent</artifactId>
10-
<version>0.2.6.Beta1-SNAPSHOT</version>
10+
<version>0.3.0.Beta1-SNAPSHOT</version>
1111
</parent>
1212
<artifactId>a2a-java-sdk-client</artifactId>
1313

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl)
101101
*
102102
* @param agentUrl the base URL for the agent whose agent card we want to retrieve
103103
* @param relativeCardPath optional path to the agent card endpoint relative to the base
104-
* agent URL, defaults to ".well-known/agent.json"
104+
* agent URL, defaults to ".well-known/agent-card.json"
105105
* @param authHeaders the HTTP authentication headers to use
106106
* @return the agent card
107107
* @throws A2AClientError If an HTTP error occurs fetching the card
@@ -117,7 +117,7 @@ public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, M
117117
* @param httpClient the http client to use
118118
* @param agentUrl the base URL for the agent whose agent card we want to retrieve
119119
* @param relativeCardPath optional path to the agent card endpoint relative to the base
120-
* agent URL, defaults to ".well-known/agent.json"
120+
* agent URL, defaults to ".well-known/agent-card.json"
121121
* @param authHeaders the HTTP authentication headers to use
122122
* @return the agent card
123123
* @throws A2AClientError If an HTTP error occurs fetching the card

client/src/main/java/io/a2a/client/A2ACardResolver.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class A2ACardResolver {
2020
private final String url;
2121
private final Map<String, String> authHeaders;
2222

23-
private static final String DEFAULT_AGENT_CARD_PATH = "/.well-known/agent.json";
23+
private static final String DEFAULT_AGENT_CARD_PATH = "/.well-known/agent-card.json";
2424

2525
private static final TypeReference<AgentCard> AGENT_CARD_TYPE_REFERENCE = new TypeReference<>() {};
2626

@@ -37,7 +37,7 @@ public A2ACardResolver(A2AHttpClient httpClient, String baseUrl) throws A2AClien
3737
* @param httpClient the http client to use
3838
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
3939
* @param agentCardPath optional path to the agent card endpoint relative to the base
40-
* agent URL, defaults to ".well-known/agent.json"
40+
* agent URL, defaults to ".well-known/agent-card.json"
4141
* @throws A2AClientError if the URL for the agent is invalid
4242
*/
4343
public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCardPath) throws A2AClientError {
@@ -48,7 +48,7 @@ public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCar
4848
* @param httpClient the http client to use
4949
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
5050
* @param agentCardPath optional path to the agent card endpoint relative to the base
51-
* agent URL, defaults to ".well-known/agent.json"
51+
* agent URL, defaults to ".well-known/agent-card.json"
5252
* @param authHeaders the HTTP authentication headers to use. May be {@code null}
5353
* @throws A2AClientError if the URL for the agent is invalid
5454
*/

client/src/main/java/io/a2a/client/A2AClient.java

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import io.a2a.spec.DeleteTaskPushNotificationConfigParams;
2525
import io.a2a.spec.DeleteTaskPushNotificationConfigRequest;
2626
import io.a2a.spec.DeleteTaskPushNotificationConfigResponse;
27+
import io.a2a.spec.GetAuthenticatedExtendedCardRequest;
28+
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
2729
import io.a2a.spec.GetTaskPushNotificationConfigParams;
2830
import io.a2a.spec.GetTaskPushNotificationConfigRequest;
2931
import io.a2a.spec.GetTaskPushNotificationConfigResponse;
@@ -61,6 +63,7 @@ public class A2AClient {
6163
private static final TypeReference<SetTaskPushNotificationConfigResponse> SET_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
6264
private static final TypeReference<ListTaskPushNotificationConfigResponse> LIST_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
6365
private static final TypeReference<DeleteTaskPushNotificationConfigResponse> DELETE_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
66+
private static final TypeReference<GetAuthenticatedExtendedCardResponse> GET_AUTHENTICATED_EXTENDED_CARD_RESPONSE_REFERENCE = new TypeReference<>() {};
6467
private final A2AHttpClient httpClient;
6568
private final String agentUrl;
6669
private AgentCard agentCard;
@@ -632,8 +635,51 @@ public void resubscribeToTask(String requestId, TaskIdParams taskIdParams, Consu
632635
}
633636
}
634637

638+
/**
639+
* Retrieve the authenticated extended agent card.
640+
*
641+
* @param authHeaders the HTTP authentication headers to use
642+
* @return the response
643+
* @throws A2AServerException if retrieving the authenticated extended agent card fails for any reason
644+
*/
645+
public GetAuthenticatedExtendedCardResponse getAuthenticatedExtendedCard(Map<String, String> authHeaders) throws A2AServerException {
646+
return getAuthenticatedExtendedCard(null, authHeaders);
647+
}
648+
649+
/**
650+
* Retrieve the authenticated extended agent card.
651+
*
652+
* @param requestId the request ID to use
653+
* @param authHeaders the HTTP authentication headers to use
654+
* @return the response
655+
* @throws A2AServerException if retrieving the authenticated extended agent card fails for any reason
656+
*/
657+
public GetAuthenticatedExtendedCardResponse getAuthenticatedExtendedCard(String requestId,
658+
Map<String, String> authHeaders) throws A2AServerException {
659+
GetAuthenticatedExtendedCardRequest.Builder requestBuilder = new GetAuthenticatedExtendedCardRequest.Builder()
660+
.jsonrpc(JSONRPCMessage.JSONRPC_VERSION)
661+
.method(GetAuthenticatedExtendedCardRequest.METHOD);
662+
663+
if (requestId != null) {
664+
requestBuilder.id(requestId);
665+
}
666+
667+
GetAuthenticatedExtendedCardRequest request = requestBuilder.build();
668+
669+
try {
670+
String httpResponseBody = sendPostRequest(request, authHeaders);
671+
return unmarshalResponse(httpResponseBody, GET_AUTHENTICATED_EXTENDED_CARD_RESPONSE_REFERENCE);
672+
} catch (IOException | InterruptedException e) {
673+
throw new A2AServerException("Failed to get authenticated extended agent card: " + e, e);
674+
}
675+
}
676+
635677
private String sendPostRequest(Object value) throws IOException, InterruptedException {
636-
A2AHttpClient.PostBuilder builder = createPostBuilder(value);
678+
return sendPostRequest(value, null);
679+
}
680+
681+
private String sendPostRequest(Object value, Map<String, String> authHeaders) throws IOException, InterruptedException {
682+
A2AHttpClient.PostBuilder builder = createPostBuilder(value, authHeaders);
637683
A2AHttpResponse response = builder.post();
638684
if (!response.success()) {
639685
throw new IOException("Request failed " + response.status());
@@ -642,11 +688,20 @@ private String sendPostRequest(Object value) throws IOException, InterruptedExce
642688
}
643689

644690
private A2AHttpClient.PostBuilder createPostBuilder(Object value) throws JsonProcessingException {
645-
return httpClient.createPost()
691+
return createPostBuilder(value, null);
692+
}
693+
694+
private A2AHttpClient.PostBuilder createPostBuilder(Object value, Map<String, String> authHeaders) throws JsonProcessingException {
695+
A2AHttpClient.PostBuilder builder = httpClient.createPost()
646696
.url(agentUrl)
647697
.addHeader("Content-Type", "application/json")
648698
.body(Utils.OBJECT_MAPPER.writeValueAsString(value));
649-
699+
if (authHeaders != null) {
700+
for (Map.Entry<String, String> entry : authHeaders.entrySet()) {
701+
builder.addHeader(entry.getKey(), entry.getValue());
702+
}
703+
}
704+
return builder;
650705
}
651706

652707
private <T extends JSONRPCResponse> T unmarshalResponse(String response, TypeReference<T> typeReference)

client/src/test/java/io/a2a/client/A2ACardResolverTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
public class A2ACardResolverTest {
2222

23-
private static final String AGENT_CARD_PATH = "/.well-known/agent.json";
23+
private static final String AGENT_CARD_PATH = "/.well-known/agent-card.json";
2424
private static final TypeReference<AgentCard> AGENT_CARD_TYPE_REFERENCE = new TypeReference<>() {};
2525

2626
@Test

client/src/test/java/io/a2a/client/A2AClientTest.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import static io.a2a.client.JsonMessages.AUTHENTICATION_EXTENDED_AGENT_CARD;
55
import static io.a2a.client.JsonMessages.CANCEL_TASK_TEST_REQUEST;
66
import static io.a2a.client.JsonMessages.CANCEL_TASK_TEST_RESPONSE;
7+
import static io.a2a.client.JsonMessages.GET_AUTHENTICATED_EXTENDED_AGENT_CARD_REQUEST;
8+
import static io.a2a.client.JsonMessages.GET_AUTHENTICATED_EXTENDED_AGENT_CARD_RESPONSE;
79
import static io.a2a.client.JsonMessages.GET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_REQUEST;
810
import static io.a2a.client.JsonMessages.GET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_RESPONSE;
911
import static io.a2a.client.JsonMessages.GET_TASK_TEST_REQUEST;
@@ -38,6 +40,8 @@
3840

3941
import io.a2a.spec.A2AServerException;
4042
import io.a2a.spec.AgentCard;
43+
import io.a2a.spec.AgentCardSignature;
44+
import io.a2a.spec.AgentInterface;
4145
import io.a2a.spec.AgentSkill;
4246
import io.a2a.spec.Artifact;
4347
import io.a2a.spec.CancelTaskResponse;
@@ -46,6 +50,7 @@
4650
import io.a2a.spec.FilePart;
4751
import io.a2a.spec.FileWithBytes;
4852
import io.a2a.spec.FileWithUri;
53+
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
4954
import io.a2a.spec.GetTaskPushNotificationConfigParams;
5055
import io.a2a.spec.GetTaskPushNotificationConfigResponse;
5156
import io.a2a.spec.GetTaskResponse;
@@ -65,6 +70,8 @@
6570
import io.a2a.spec.TaskQueryParams;
6671
import io.a2a.spec.TaskState;
6772
import io.a2a.spec.TextPart;
73+
import io.a2a.spec.TransportProtocol;
74+
6875
import org.junit.jupiter.api.AfterEach;
6976
import org.junit.jupiter.api.BeforeEach;
7077
import org.junit.jupiter.api.Test;
@@ -382,7 +389,7 @@ public void testA2AClientGetAgentCard() throws Exception {
382389
this.server.when(
383390
request()
384391
.withMethod("GET")
385-
.withPath("/.well-known/agent.json")
392+
.withPath("/.well-known/agent-card.json")
386393
)
387394
.respond(
388395
response()
@@ -443,24 +450,36 @@ public void testA2AClientGetAgentCard() throws Exception {
443450
assertEquals(outputModes, skills.get(1).outputModes());
444451
assertTrue(agentCard.supportsAuthenticatedExtendedCard());
445452
assertEquals("https://georoute-agent.example.com/icon.png", agentCard.iconUrl());
446-
assertEquals("0.2.5", agentCard.protocolVersion());
453+
assertEquals("0.2.9", agentCard.protocolVersion());
454+
assertEquals("JSONRPC", agentCard.preferredTransport());
455+
List<AgentInterface> additionalInterfaces = agentCard.additionalInterfaces();
456+
assertEquals(3, additionalInterfaces.size());
457+
AgentInterface jsonrpc = new AgentInterface(TransportProtocol.JSONRPC.asString(), "https://georoute-agent.example.com/a2a/v1");
458+
AgentInterface grpc = new AgentInterface(TransportProtocol.GRPC.asString(), "https://georoute-agent.example.com/a2a/grpc");
459+
AgentInterface httpJson = new AgentInterface(TransportProtocol.HTTP_JSON.asString(), "https://georoute-agent.example.com/a2a/json");
460+
assertEquals(jsonrpc, additionalInterfaces.get(0));
461+
assertEquals(grpc, additionalInterfaces.get(1));
462+
assertEquals(httpJson, additionalInterfaces.get(2));
447463
}
448464

449465
@Test
450466
public void testA2AClientGetAuthenticatedExtendedAgentCard() throws Exception {
451467
this.server.when(
452468
request()
453-
.withMethod("GET")
454-
.withPath("/agent/authenticatedExtendedCard")
469+
.withMethod("POST")
470+
.withPath("/")
471+
.withBody(JsonBody.json(GET_AUTHENTICATED_EXTENDED_AGENT_CARD_REQUEST, MatchType.STRICT))
472+
455473
)
456474
.respond(
457475
response()
458476
.withStatusCode(200)
459-
.withBody(AUTHENTICATION_EXTENDED_AGENT_CARD)
477+
.withBody(GET_AUTHENTICATED_EXTENDED_AGENT_CARD_RESPONSE)
460478
);
461479

462480
A2AClient client = new A2AClient("http://localhost:4001");
463-
AgentCard agentCard = client.getAgentCard("/agent/authenticatedExtendedCard", null);
481+
GetAuthenticatedExtendedCardResponse response = client.getAuthenticatedExtendedCard("1", null);
482+
AgentCard agentCard = response.getResult();
464483
assertEquals("GeoSpatial Route Planner Agent Extended", agentCard.name());
465484
assertEquals("Extended description", agentCard.description());
466485
assertEquals("https://georoute-agent.example.com/a2a/v1", agentCard.url());
@@ -516,7 +535,13 @@ public void testA2AClientGetAuthenticatedExtendedAgentCard() throws Exception {
516535
assertEquals(List.of("extended"), skills.get(2).tags());
517536
assertTrue(agentCard.supportsAuthenticatedExtendedCard());
518537
assertEquals("https://georoute-agent.example.com/icon.png", agentCard.iconUrl());
519-
assertEquals("0.2.5", agentCard.protocolVersion());
538+
assertEquals("0.2.9", agentCard.protocolVersion());
539+
List<AgentCardSignature> signatures = agentCard.signatures();
540+
assertEquals(1, signatures.size());
541+
AgentCardSignature signature = new AgentCardSignature(null,
542+
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpPU0UiLCJraWQiOiJrZXktMSIsImprdSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vYWdlbnQvandrcy5qc29uIn0",
543+
"QFdkNLNszlGj3z3u0YQGt_T9LixY3qtdQpZmsTdDHDe3fXV9y9-B3m2-XgCpzuhiLt8E0tV6HXoZKHv4GtHgKQ");
544+
assertEquals(signature, signatures.get(0));
520545
}
521546

522547
@Test

0 commit comments

Comments
 (0)