Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/run-tck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:

env:
# Tag of the TCK
TCK_VERSION: v0.2.5
TCK_VERSION: 0.3.0.alpha
# Tells uv to not need a venv, and instead use system
UV_SYSTEM_PYTHON: 1

Expand Down Expand Up @@ -57,7 +57,7 @@ jobs:
working-directory: tck
- name: Wait for SUT to start
run: |
URL="http://localhost:9999/.well-known/agent.json"
URL="http://localhost:9999/.well-known/agent-card.json"
EXPECTED_STATUS=200
TIMEOUT=120
RETRY_INTERVAL=2
Expand Down
2 changes: 1 addition & 1 deletion client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-parent</artifactId>
<version>0.2.6.Beta1-SNAPSHOT</version>
<version>0.3.0.Beta1-SNAPSHOT</version>
</parent>
<artifactId>a2a-java-sdk-client</artifactId>

Expand Down
4 changes: 2 additions & 2 deletions client/src/main/java/io/a2a/A2A.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl)
*
* @param agentUrl the base URL for the agent whose agent card we want to retrieve
* @param relativeCardPath optional path to the agent card endpoint relative to the base
* agent URL, defaults to ".well-known/agent.json"
* agent URL, defaults to ".well-known/agent-card.json"
* @param authHeaders the HTTP authentication headers to use
* @return the agent card
* @throws A2AClientError If an HTTP error occurs fetching the card
Expand All @@ -117,7 +117,7 @@ public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, M
* @param httpClient the http client to use
* @param agentUrl the base URL for the agent whose agent card we want to retrieve
* @param relativeCardPath optional path to the agent card endpoint relative to the base
* agent URL, defaults to ".well-known/agent.json"
* agent URL, defaults to ".well-known/agent-card.json"
* @param authHeaders the HTTP authentication headers to use
* @return the agent card
* @throws A2AClientError If an HTTP error occurs fetching the card
Expand Down
6 changes: 3 additions & 3 deletions client/src/main/java/io/a2a/client/A2ACardResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class A2ACardResolver {
private final String url;
private final Map<String, String> authHeaders;

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

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

Expand All @@ -37,7 +37,7 @@ public A2ACardResolver(A2AHttpClient httpClient, String baseUrl) throws A2AClien
* @param httpClient the http client to use
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
* @param agentCardPath optional path to the agent card endpoint relative to the base
* agent URL, defaults to ".well-known/agent.json"
* agent URL, defaults to ".well-known/agent-card.json"
* @throws A2AClientError if the URL for the agent is invalid
*/
public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCardPath) throws A2AClientError {
Expand All @@ -48,7 +48,7 @@ public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCar
* @param httpClient the http client to use
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
* @param agentCardPath optional path to the agent card endpoint relative to the base
* agent URL, defaults to ".well-known/agent.json"
* agent URL, defaults to ".well-known/agent-card.json"
* @param authHeaders the HTTP authentication headers to use. May be {@code null}
* @throws A2AClientError if the URL for the agent is invalid
*/
Expand Down
61 changes: 58 additions & 3 deletions client/src/main/java/io/a2a/client/A2AClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import io.a2a.spec.DeleteTaskPushNotificationConfigParams;
import io.a2a.spec.DeleteTaskPushNotificationConfigRequest;
import io.a2a.spec.DeleteTaskPushNotificationConfigResponse;
import io.a2a.spec.GetAuthenticatedExtendedCardRequest;
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
import io.a2a.spec.GetTaskPushNotificationConfigParams;
import io.a2a.spec.GetTaskPushNotificationConfigRequest;
import io.a2a.spec.GetTaskPushNotificationConfigResponse;
Expand Down Expand Up @@ -61,6 +63,7 @@ public class A2AClient {
private static final TypeReference<SetTaskPushNotificationConfigResponse> SET_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
private static final TypeReference<ListTaskPushNotificationConfigResponse> LIST_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
private static final TypeReference<DeleteTaskPushNotificationConfigResponse> DELETE_TASK_PUSH_NOTIFICATION_CONFIG_RESPONSE_REFERENCE = new TypeReference<>() {};
private static final TypeReference<GetAuthenticatedExtendedCardResponse> GET_AUTHENTICATED_EXTENDED_CARD_RESPONSE_REFERENCE = new TypeReference<>() {};
private final A2AHttpClient httpClient;
private final String agentUrl;
private AgentCard agentCard;
Expand Down Expand Up @@ -632,8 +635,51 @@ public void resubscribeToTask(String requestId, TaskIdParams taskIdParams, Consu
}
}

/**
* Retrieve the authenticated extended agent card.
*
* @param authHeaders the HTTP authentication headers to use
* @return the response
* @throws A2AServerException if retrieving the authenticated extended agent card fails for any reason
*/
public GetAuthenticatedExtendedCardResponse getAuthenticatedExtendedCard(Map<String, String> authHeaders) throws A2AServerException {
return getAuthenticatedExtendedCard(null, authHeaders);
}

/**
* Retrieve the authenticated extended agent card.
*
* @param requestId the request ID to use
* @param authHeaders the HTTP authentication headers to use
* @return the response
* @throws A2AServerException if retrieving the authenticated extended agent card fails for any reason
*/
public GetAuthenticatedExtendedCardResponse getAuthenticatedExtendedCard(String requestId,
Map<String, String> authHeaders) throws A2AServerException {
GetAuthenticatedExtendedCardRequest.Builder requestBuilder = new GetAuthenticatedExtendedCardRequest.Builder()
.jsonrpc(JSONRPCMessage.JSONRPC_VERSION)
.method(GetAuthenticatedExtendedCardRequest.METHOD);

if (requestId != null) {
requestBuilder.id(requestId);
}

GetAuthenticatedExtendedCardRequest request = requestBuilder.build();

try {
String httpResponseBody = sendPostRequest(request, authHeaders);
return unmarshalResponse(httpResponseBody, GET_AUTHENTICATED_EXTENDED_CARD_RESPONSE_REFERENCE);
} catch (IOException | InterruptedException e) {
throw new A2AServerException("Failed to get authenticated extended agent card: " + e, e);
}
}

private String sendPostRequest(Object value) throws IOException, InterruptedException {
A2AHttpClient.PostBuilder builder = createPostBuilder(value);
return sendPostRequest(value, null);
}

private String sendPostRequest(Object value, Map<String, String> authHeaders) throws IOException, InterruptedException {
A2AHttpClient.PostBuilder builder = createPostBuilder(value, authHeaders);
A2AHttpResponse response = builder.post();
if (!response.success()) {
throw new IOException("Request failed " + response.status());
Expand All @@ -642,11 +688,20 @@ private String sendPostRequest(Object value) throws IOException, InterruptedExce
}

private A2AHttpClient.PostBuilder createPostBuilder(Object value) throws JsonProcessingException {
return httpClient.createPost()
return createPostBuilder(value, null);
}

private A2AHttpClient.PostBuilder createPostBuilder(Object value, Map<String, String> authHeaders) throws JsonProcessingException {
A2AHttpClient.PostBuilder builder = httpClient.createPost()
.url(agentUrl)
.addHeader("Content-Type", "application/json")
.body(Utils.OBJECT_MAPPER.writeValueAsString(value));

if (authHeaders != null) {
for (Map.Entry<String, String> entry : authHeaders.entrySet()) {
builder.addHeader(entry.getKey(), entry.getValue());
}
}
return builder;
}

private <T extends JSONRPCResponse> T unmarshalResponse(String response, TypeReference<T> typeReference)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

public class A2ACardResolverTest {

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

@Test
Expand Down
39 changes: 32 additions & 7 deletions client/src/test/java/io/a2a/client/A2AClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static io.a2a.client.JsonMessages.AUTHENTICATION_EXTENDED_AGENT_CARD;
import static io.a2a.client.JsonMessages.CANCEL_TASK_TEST_REQUEST;
import static io.a2a.client.JsonMessages.CANCEL_TASK_TEST_RESPONSE;
import static io.a2a.client.JsonMessages.GET_AUTHENTICATED_EXTENDED_AGENT_CARD_REQUEST;
import static io.a2a.client.JsonMessages.GET_AUTHENTICATED_EXTENDED_AGENT_CARD_RESPONSE;
import static io.a2a.client.JsonMessages.GET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_REQUEST;
import static io.a2a.client.JsonMessages.GET_TASK_PUSH_NOTIFICATION_CONFIG_TEST_RESPONSE;
import static io.a2a.client.JsonMessages.GET_TASK_TEST_REQUEST;
Expand Down Expand Up @@ -38,6 +40,8 @@

import io.a2a.spec.A2AServerException;
import io.a2a.spec.AgentCard;
import io.a2a.spec.AgentCardSignature;
import io.a2a.spec.AgentInterface;
import io.a2a.spec.AgentSkill;
import io.a2a.spec.Artifact;
import io.a2a.spec.CancelTaskResponse;
Expand All @@ -46,6 +50,7 @@
import io.a2a.spec.FilePart;
import io.a2a.spec.FileWithBytes;
import io.a2a.spec.FileWithUri;
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
import io.a2a.spec.GetTaskPushNotificationConfigParams;
import io.a2a.spec.GetTaskPushNotificationConfigResponse;
import io.a2a.spec.GetTaskResponse;
Expand All @@ -65,6 +70,8 @@
import io.a2a.spec.TaskQueryParams;
import io.a2a.spec.TaskState;
import io.a2a.spec.TextPart;
import io.a2a.spec.TransportProtocol;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -382,7 +389,7 @@ public void testA2AClientGetAgentCard() throws Exception {
this.server.when(
request()
.withMethod("GET")
.withPath("/.well-known/agent.json")
.withPath("/.well-known/agent-card.json")
)
.respond(
response()
Expand Down Expand Up @@ -443,24 +450,36 @@ public void testA2AClientGetAgentCard() throws Exception {
assertEquals(outputModes, skills.get(1).outputModes());
assertTrue(agentCard.supportsAuthenticatedExtendedCard());
assertEquals("https://georoute-agent.example.com/icon.png", agentCard.iconUrl());
assertEquals("0.2.5", agentCard.protocolVersion());
assertEquals("0.2.9", agentCard.protocolVersion());
assertEquals("JSONRPC", agentCard.preferredTransport());
List<AgentInterface> additionalInterfaces = agentCard.additionalInterfaces();
assertEquals(3, additionalInterfaces.size());
AgentInterface jsonrpc = new AgentInterface(TransportProtocol.JSONRPC.asString(), "https://georoute-agent.example.com/a2a/v1");
AgentInterface grpc = new AgentInterface(TransportProtocol.GRPC.asString(), "https://georoute-agent.example.com/a2a/grpc");
AgentInterface httpJson = new AgentInterface(TransportProtocol.HTTP_JSON.asString(), "https://georoute-agent.example.com/a2a/json");
assertEquals(jsonrpc, additionalInterfaces.get(0));
assertEquals(grpc, additionalInterfaces.get(1));
assertEquals(httpJson, additionalInterfaces.get(2));
}

@Test
public void testA2AClientGetAuthenticatedExtendedAgentCard() throws Exception {
this.server.when(
request()
.withMethod("GET")
.withPath("/agent/authenticatedExtendedCard")
.withMethod("POST")
.withPath("/")
.withBody(JsonBody.json(GET_AUTHENTICATED_EXTENDED_AGENT_CARD_REQUEST, MatchType.STRICT))

)
.respond(
response()
.withStatusCode(200)
.withBody(AUTHENTICATION_EXTENDED_AGENT_CARD)
.withBody(GET_AUTHENTICATED_EXTENDED_AGENT_CARD_RESPONSE)
);

A2AClient client = new A2AClient("http://localhost:4001");
AgentCard agentCard = client.getAgentCard("/agent/authenticatedExtendedCard", null);
GetAuthenticatedExtendedCardResponse response = client.getAuthenticatedExtendedCard("1", null);
AgentCard agentCard = response.getResult();
assertEquals("GeoSpatial Route Planner Agent Extended", agentCard.name());
assertEquals("Extended description", agentCard.description());
assertEquals("https://georoute-agent.example.com/a2a/v1", agentCard.url());
Expand Down Expand Up @@ -516,7 +535,13 @@ public void testA2AClientGetAuthenticatedExtendedAgentCard() throws Exception {
assertEquals(List.of("extended"), skills.get(2).tags());
assertTrue(agentCard.supportsAuthenticatedExtendedCard());
assertEquals("https://georoute-agent.example.com/icon.png", agentCard.iconUrl());
assertEquals("0.2.5", agentCard.protocolVersion());
assertEquals("0.2.9", agentCard.protocolVersion());
List<AgentCardSignature> signatures = agentCard.signatures();
assertEquals(1, signatures.size());
AgentCardSignature signature = new AgentCardSignature(null,
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpPU0UiLCJraWQiOiJrZXktMSIsImprdSI6Imh0dHBzOi8vZXhhbXBsZS5jb20vYWdlbnQvandrcy5qc29uIn0",
"QFdkNLNszlGj3z3u0YQGt_T9LixY3qtdQpZmsTdDHDe3fXV9y9-B3m2-XgCpzuhiLt8E0tV6HXoZKHv4GtHgKQ");
assertEquals(signature, signatures.get(0));
}

@Test
Expand Down
Loading