Skip to content

Commit 0fa0ef4

Browse files
authored
Merge pull request #122 from kabir/agentcardproducer
Add AgentCardProducer
2 parents b95f675 + ccb8ec6 commit 0fa0ef4

File tree

6 files changed

+329
-53
lines changed

6 files changed

+329
-53
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.a2a.client;
2+
3+
import static io.a2a.util.Utils.unmarshalFrom;
4+
5+
import java.io.IOException;
6+
import java.util.Map;
7+
import java.util.concurrent.CompletableFuture;
8+
import java.util.concurrent.CountDownLatch;
9+
import java.util.concurrent.atomic.AtomicReference;
10+
11+
import com.fasterxml.jackson.core.JsonProcessingException;
12+
import com.fasterxml.jackson.core.type.TypeReference;
13+
import io.a2a.http.A2AHttpClient;
14+
import io.a2a.http.A2AHttpResponse;
15+
import io.a2a.spec.A2A;
16+
import io.a2a.spec.A2AClientError;
17+
import io.a2a.spec.A2AClientJSONError;
18+
import io.a2a.spec.AgentCard;
19+
20+
public class A2ACardResolver {
21+
private final A2AHttpClient httpClient;
22+
private final String url;
23+
private final Map<String, String> authHeaders;
24+
25+
static String DEFAULT_AGENT_CARD_PATH = "/.well-known/agent.json";
26+
27+
static final TypeReference<AgentCard> AGENT_CARD_TYPE_REFERENCE = new TypeReference<>() {};
28+
/**
29+
* @param httpClient the http client to use
30+
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
31+
*/
32+
public A2ACardResolver(A2AHttpClient httpClient, String baseUrl) {
33+
this(httpClient, baseUrl, null, null);
34+
}
35+
36+
/**
37+
* @param httpClient the http client to use
38+
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
39+
* @param agentCardPath optional path to the agent card endpoint relative to the base
40+
* agent URL, defaults to ".well-known/agent.json"
41+
*/
42+
public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCardPath) {
43+
this(httpClient, baseUrl, agentCardPath, null);
44+
}
45+
46+
/**
47+
* @param httpClient the http client to use
48+
* @param baseUrl the base URL for the agent whose agent card we want to retrieve
49+
* @param agentCardPath optional path to the agent card endpoint relative to the base
50+
* agent URL, defaults to ".well-known/agent.json"
51+
* @param authHeaders the HTTP authentication headers to use. May be {@code null}
52+
*/
53+
public A2ACardResolver(A2AHttpClient httpClient, String baseUrl, String agentCardPath, Map<String, String> authHeaders) {
54+
this.httpClient = httpClient;
55+
if (!baseUrl.endsWith("/")) {
56+
baseUrl += "/";
57+
}
58+
agentCardPath = agentCardPath == null || agentCardPath.isEmpty() ? DEFAULT_AGENT_CARD_PATH : agentCardPath;
59+
if (agentCardPath.startsWith("/")) {
60+
agentCardPath = agentCardPath.substring(1);
61+
}
62+
this.url = baseUrl + agentCardPath;
63+
this.authHeaders = authHeaders;
64+
}
65+
66+
/**
67+
* Get the agent card for the configured A2A agent.
68+
*
69+
* @return the agent card
70+
* @throws A2AClientError If an HTTP error occurs fetching the card
71+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
72+
*/
73+
public AgentCard getAgentCard() throws A2AClientError, A2AClientJSONError {
74+
A2AHttpClient.GetBuilder builder = httpClient.createGet()
75+
.url(url)
76+
.addHeader("Content-Type", "application/json");
77+
78+
if (authHeaders != null) {
79+
for (Map.Entry<String, String> entry : authHeaders.entrySet()) {
80+
builder.addHeader(entry.getKey(), entry.getValue());
81+
}
82+
}
83+
84+
String body;
85+
try {
86+
A2AHttpResponse response = builder.get();
87+
if (!response.success()) {
88+
throw new A2AClientError("Failed to obtain agent card: " + response.status());
89+
}
90+
body = response.body();
91+
} catch (IOException | InterruptedException e) {
92+
throw new A2AClientError("Failed to obtain agent card", e);
93+
}
94+
95+
try {
96+
return unmarshalFrom(body, AGENT_CARD_TYPE_REFERENCE);
97+
} catch (JsonProcessingException e) {
98+
throw new A2AClientJSONError("Could not unmarshal agent card response", e);
99+
}
100+
101+
}
102+
103+
104+
}

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import io.a2a.http.A2AHttpResponse;
2626
import io.a2a.http.JdkA2AHttpClient;
2727
import io.a2a.spec.A2A;
28+
import io.a2a.spec.A2AClientError;
29+
import io.a2a.spec.A2AClientJSONError;
2830
import io.a2a.spec.A2AServerException;
2931
import io.a2a.spec.AgentCard;
3032
import io.a2a.spec.CancelTaskRequest;
@@ -86,14 +88,33 @@ public A2AClient(String agentUrl) {
8688
this.httpClient = new JdkA2AHttpClient();
8789
}
8890

91+
/**
92+
* Fetches the agent card and initialises an A2A client.
93+
*
94+
* @param httpClient the {@link A2AHttpClient} to use
95+
* @param baseUrl the base URL of the agent's host
96+
* @param agentCardPath the path to the agent card endpoint, relative to the {@code baseUrl}. If {@code null}, the
97+
* value {@link A2ACardResolver#DEFAULT_AGENT_CARD_PATH} will be used
98+
* @return an initialised {@code A2AClient} instance
99+
* @throws A2AClientError If an HTTP error occurs fetching the card
100+
* @throws A2AClientJSONError if the agent card response is invalid
101+
*/
102+
public static A2AClient getClientFromAgentCardUrl(A2AHttpClient httpClient, String baseUrl,
103+
String agentCardPath) throws A2AClientError, A2AClientJSONError {
104+
A2ACardResolver resolver = new A2ACardResolver(httpClient, baseUrl, agentCardPath);
105+
AgentCard card = resolver.getAgentCard();
106+
return new A2AClient(card);
107+
}
108+
89109
/**
90110
* Get the agent card for the A2A server this client will be communicating with from
91111
* the default public agent card endpoint.
92112
*
93113
* @return the agent card for the A2A server
94-
* @throws {@code A2AServerException} if the agent card for the A2A server cannot be obtained
114+
* @throws A2AClientError If an HTTP error occurs fetching the card
115+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
95116
*/
96-
public AgentCard getAgentCard() throws A2AServerException {
117+
public AgentCard getAgentCard() throws A2AClientError, A2AClientJSONError {
97118
if (this.agentCard == null) {
98119
this.agentCard = A2A.getAgentCard(this.httpClient, this.agentUrl);
99120
}
@@ -106,9 +127,10 @@ public AgentCard getAgentCard() throws A2AServerException {
106127
* @param relativeCardPath the path to the agent card endpoint relative to the base URL of the A2A server
107128
* @param authHeaders the HTTP authentication headers to use
108129
* @return the agent card for the A2A server
109-
* @throws {@code A2AServerException} if the agent card for the A2A server cannot be obtained
130+
* @throws A2AClientError If an HTTP error occurs fetching the card
131+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
110132
*/
111-
public AgentCard getAgentCard(String relativeCardPath, Map<String, String> authHeaders) throws A2AServerException {
133+
public AgentCard getAgentCard(String relativeCardPath, Map<String, String> authHeaders) throws A2AClientError, A2AClientJSONError {
112134
if (this.agentCard == null) {
113135
this.agentCard = A2A.getAgentCard(this.httpClient, this.agentUrl, relativeCardPath, authHeaders);
114136
}

core/src/main/java/io/a2a/spec/A2A.java

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
package io.a2a.spec;
22

3-
import static io.a2a.util.Utils.unmarshalFrom;
4-
5-
import java.io.IOException;
63
import java.util.Collections;
74
import java.util.Map;
85

9-
import com.fasterxml.jackson.core.type.TypeReference;
6+
import io.a2a.client.A2ACardResolver;
107
import io.a2a.http.A2AHttpClient;
11-
import io.a2a.http.A2AHttpResponse;
128
import io.a2a.http.JdkA2AHttpClient;
139

1410

@@ -27,13 +23,6 @@ public class A2A {
2723

2824
public static final String JSONRPC_VERSION = "2.0";
2925

30-
public static final String AGENT_CARD_REQUEST = ".well-known/agent.json";
31-
32-
private static final TypeReference<AgentCard> AGENT_CARD_TYPE_REFERENCE = new TypeReference<>() {};
33-
34-
public static String getRequestEndpoint(String agentUrl, String request) {
35-
return agentUrl.endsWith("/") ? agentUrl + request : agentUrl + "/" + request;
36-
}
3726

3827
/**
3928
* Convert the given text to a user message.
@@ -93,9 +82,10 @@ private static Message toMessage(String text, Message.Role role, String messageI
9382
*
9483
* @param agentUrl the base URL for the agent whose agent card we want to retrieve
9584
* @return the agent card
96-
* @throws A2AServerException if the agent card cannot be retrieved for any reason
85+
* @throws A2AClientError If an HTTP error occurs fetching the card
86+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
9787
*/
98-
public static AgentCard getAgentCard(String agentUrl) throws A2AServerException {
88+
public static AgentCard getAgentCard(String agentUrl) throws A2AClientError, A2AClientJSONError {
9989
return getAgentCard(new JdkA2AHttpClient(), agentUrl);
10090
}
10191

@@ -105,9 +95,10 @@ public static AgentCard getAgentCard(String agentUrl) throws A2AServerException
10595
* @param httpClient the http client to use
10696
* @param agentUrl the base URL for the agent whose agent card we want to retrieve
10797
* @return the agent card
108-
* @throws A2AServerException if the agent card cannot be retrieved for any reason
98+
* @throws A2AClientError If an HTTP error occurs fetching the card
99+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
109100
*/
110-
public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl) throws A2AServerException {
101+
public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl) throws A2AClientError, A2AClientJSONError {
111102
return getAgentCard(httpClient, agentUrl, null, null);
112103
}
113104

@@ -119,9 +110,10 @@ public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl)
119110
* agent URL, defaults to ".well-known/agent.json"
120111
* @param authHeaders the HTTP authentication headers to use
121112
* @return the agent card
122-
* @throws A2AServerException if the agent card cannot be retrieved for any reason
113+
* @throws A2AClientError If an HTTP error occurs fetching the card
114+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
123115
*/
124-
public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AServerException {
116+
public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AClientError, A2AClientJSONError {
125117
return getAgentCard(new JdkA2AHttpClient(), agentUrl, relativeCardPath, authHeaders);
126118
}
127119

@@ -134,38 +126,12 @@ public static AgentCard getAgentCard(String agentUrl, String relativeCardPath, M
134126
* agent URL, defaults to ".well-known/agent.json"
135127
* @param authHeaders the HTTP authentication headers to use
136128
* @return the agent card
137-
* @throws A2AServerException if the agent card cannot be retrieved for any reason
129+
* @throws A2AClientError If an HTTP error occurs fetching the card
130+
* @throws A2AClientJSONError f the response body cannot be decoded as JSON or validated against the AgentCard schema
138131
*/
139-
public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AServerException {
140-
if (relativeCardPath == null || relativeCardPath.isEmpty()) {
141-
relativeCardPath = AGENT_CARD_REQUEST;
142-
} else {
143-
if (relativeCardPath.startsWith("/")) {
144-
relativeCardPath = relativeCardPath.substring(1);
145-
}
146-
}
147-
A2AHttpClient.GetBuilder builder = httpClient.createGet()
148-
.url(getRequestEndpoint(agentUrl, relativeCardPath))
149-
.addHeader("Content-Type", "application/json");
150-
151-
if (authHeaders != null) {
152-
for (Map.Entry<String, String> entry : authHeaders.entrySet()) {
153-
builder.addHeader(entry.getKey(), entry.getValue());
154-
}
155-
}
156-
157-
try {
158-
A2AHttpResponse response = builder.get();
159-
if (!response.success()) {
160-
throw new A2AServerException("Failed to obtain agent card: " + response.status());
161-
}
162-
String body = response.body();
163-
return unmarshalFrom(body, AGENT_CARD_TYPE_REFERENCE);
164-
} catch (IOException e) {
165-
throw new A2AServerException("Failed to obtain agent card", e);
166-
} catch (InterruptedException e) {
167-
throw new A2AServerException("Timed out obtaining agent card", e);
168-
}
132+
public static AgentCard getAgentCard(A2AHttpClient httpClient, String agentUrl, String relativeCardPath, Map<String, String> authHeaders) throws A2AClientError, A2AClientJSONError {
133+
A2ACardResolver resolver = new A2ACardResolver(httpClient, agentUrl, relativeCardPath, authHeaders);
134+
return resolver.getAgentCard();
169135
}
170136

171137
protected static boolean isValidMethodName(String methodName) {

core/src/main/java/io/a2a/spec/A2AClientError.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ public A2AClientError() {
77
public A2AClientError(String message) {
88
super(message);
99
}
10+
11+
public A2AClientError(String message, Throwable cause) {
12+
super(message, cause);
13+
}
1014
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
package io.a2a.spec;
22

33
public class A2AClientJSONError extends A2AClientError {
4+
5+
public A2AClientJSONError() {
6+
}
7+
8+
public A2AClientJSONError(String message) {
9+
super(message);
10+
}
11+
12+
public A2AClientJSONError(String message, Throwable cause) {
13+
super(message, cause);
14+
}
415
}

0 commit comments

Comments
 (0)