Skip to content

Commit e077233

Browse files
committed
fix: Add details about how to use the new Client to the README and add some convenience methods to AbstractClient
1 parent 7c955fc commit e077233

File tree

7 files changed

+437
-97
lines changed

7 files changed

+437
-97
lines changed

README.md

Lines changed: 223 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,14 @@ public class WeatherAgentExecutorProducer {
211211

212212
## A2A Client
213213

214-
The A2A Java SDK provides a Java client implementation of the [Agent2Agent (A2A) Protocol](https://google-a2a.github.io/A2A), allowing communication with A2A servers.
215-
To make use of the Java `A2AClient`, simply add the following dependency:
214+
The A2A Java SDK provides a Java client implementation of the [Agent2Agent (A2A) Protocol](https://google-a2a.github.io/A2A), allowing communication with A2A servers. The Java client implementation currently supports two transport protocols: JSON-RPC 2.0 and gRPC.
215+
216+
To make use of the Java `Client`:
217+
218+
### 1. Add the A2A Java SDK Client dependency to your project
219+
220+
Adding a dependency on `a2a-java-sdk-client` will provide access to a `ClientFactory`
221+
that you can use to create your A2A `Client`.
216222

217223
----
218224
> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*
@@ -227,67 +233,222 @@ To make use of the Java `A2AClient`, simply add the following dependency:
227233
</dependency>
228234
```
229235

236+
### 2. Add dependencies on the A2A Java SDK Client Transport(s) you'd like to use
237+
238+
You need to add a dependency on at least one of the following client transport modules:
239+
240+
----
241+
> *⚠️ The `io.github.a2asdk` `groupId` below is temporary and will likely change for future releases.*
242+
----
243+
244+
```xml
245+
<dependency>
246+
<groupId>io.github.a2asdk</groupId>
247+
<artifactId>a2a-java-sdk-client-transport-jsonrpc</artifactId>
248+
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
249+
<version>${io.a2a.sdk.version}</version>
250+
</dependency>
251+
```
252+
253+
```xml
254+
<dependency>
255+
<groupId>io.github.a2asdk</groupId>
256+
<artifactId>a2a-java-sdk-client-transport-grpc</artifactId>
257+
<!-- Use a released version from https://github.com/a2aproject/a2a-java/releases -->
258+
<version>${io.a2a.sdk.version}</version>
259+
</dependency>
260+
```
261+
262+
Support for the HTTP+JSON/REST transport will be coming soon.
263+
230264
### Sample Usage
231265

232-
#### Create an A2A client
266+
#### Create a Client using the ClientFactory
267+
268+
```java
269+
// First, get the agent card for the A2A server agent you want to connect to
270+
AgentCard agentCard = new A2ACardResolver("http://localhost:1234").getAgentCard();
271+
272+
// Specify configuration for the ClientFactory
273+
ClientConfig clientConfig = new ClientConfig.Builder()
274+
.setAcceptedOutputModes(List.of("text"))
275+
.build();
276+
277+
// Create event consumers to handle responses that will be received from the A2A server
278+
// (these consumers will be used for both streaming and non-streaming responses)
279+
List<BiConsumer<ClientEvent, AgentCard>> consumers = List.of(
280+
(event, card) -> {
281+
if (event instanceof MessageEvent messageEvent) {
282+
// handle the messageEvent.getMessage()
283+
...
284+
} else if (event instanceof TaskEvent taskEvent) {
285+
// handle the taskEvent.getTask()
286+
...
287+
} else if (event instanceof TaskUpdateEvent updateEvent) {
288+
// handle the updateEvent.getTask()
289+
...
290+
}
291+
}
292+
);
293+
294+
// Create a handler that will be used for any errors that occur during streaming
295+
Consumer<Throwable> errorHandler = error -> {
296+
// handle the error.getMessage()
297+
...
298+
};
299+
300+
// Create the client using ClientFactory
301+
ClientFactory clientFactory = new ClientFactory(clientConfig);
302+
Client client = clientFactory.create(agentCard, consumers, errorHandler);
303+
```
304+
305+
#### Configuring Transport-Specific Settings
306+
307+
Different transport protocols can be configured with specific settings using `ClientTransportConfig` implementations. The A2A Java SDK provides `JSONRPCTransportConfig` for the JSON-RPC transport and `GrpcTransportConfig` for the gRPC transport.
308+
309+
##### JSON-RPC Transport Configuration
310+
311+
For the JSON-RPC transport, if you'd like to use the default `JdkA2AHttpClient`, no additional
312+
configuration is needed. To use a custom HTTP client instead, simply create a `JSONRPCTransportConfig`
313+
as follows:
314+
315+
```java
316+
// Create a custom HTTP client
317+
A2AHttpClient customHttpClient = ...
318+
319+
// Create JSON-RPC transport configuration
320+
JSONRPCTransportConfig jsonrpcConfig = new JSONRPCTransportConfig(customHttpClient);
321+
322+
// Configure the client with transport-specific settings
323+
ClientConfig clientConfig = new ClientConfig.Builder()
324+
.setAcceptedOutputModes(List.of("text"))
325+
.setClientTransportConfigs(List.of(jsonrpcConfig))
326+
.build();
327+
```
328+
329+
##### gRPC Transport Configuration
330+
331+
For the gRPC transport, you must configure a channel factory:
233332

234333
```java
235-
// Create an A2AClient (the URL specified is the server agent's URL, be sure to replace it with the actual URL of the A2A server you want to connect to)
236-
A2AClient client = new A2AClient("http://localhost:1234");
334+
// Create a channel factory function that takes the agent URL and returns a Channel
335+
Function<String, Channel> channelFactory = agentUrl -> {
336+
return ManagedChannelBuilder.forTarget(agentUrl)
337+
...
338+
.build();
339+
};
340+
341+
// Create gRPC transport configuration
342+
GrpcTransportConfig grpcConfig = new GrpcTransportConfig(channelFactory);
343+
344+
// Configure the client with transport-specific settings
345+
ClientConfig clientConfig = new ClientConfig.Builder()
346+
.setAcceptedOutputModes(List.of("text"))
347+
.setClientTransportConfigs(List.of(grpcConfig))
348+
.build();
349+
```
350+
351+
##### Multiple Transport Configurations
352+
353+
You can specify configuration for multiple transports, the appropriate configuration
354+
will be used based on the selected transport:
355+
356+
```java
357+
// Configure both JSON-RPC and gRPC transports
358+
List<ClientTransportConfig> transportConfigs = List.of(
359+
new JSONRPCTransportConfig(...),
360+
new GrpcTransportConfig(...)
361+
);
362+
363+
ClientConfig clientConfig = new ClientConfig.Builder()
364+
.setAcceptedOutputModes(List.of("text"))
365+
.setClientTransportConfigs(transportConfigs)
366+
.build();
237367
```
238368

239369
#### Send a message to the A2A server agent
240370

241371
```java
242372
// Send a text message to the A2A server agent
243-
Message message = A2A.toUserMessage("tell me a joke"); // the message ID will be automatically generated for you
244-
MessageSendParams params = new MessageSendParams.Builder()
245-
.message(message)
246-
.build();
247-
SendMessageResponse response = client.sendMessage(params);
373+
Message message = A2A.toUserMessage("tell me a joke");
374+
375+
// Send the message (uses configured consumers to handle responses)
376+
// Streaming will automatically be used if supported by both client and server,
377+
// otherwise the non-streaming send message method will be used automatically
378+
client.sendMessage(message);
379+
380+
// You can also optionally specify a ClientCallContext with call-specific config to use
381+
client.sendMessage(message, clientCallContext);
248382
```
249383

250-
Note that `A2A#toUserMessage` will automatically generate a message ID for you when creating the `Message`
251-
if you don't specify it. You can also explicitly specify a message ID like this:
384+
#### Send a message with custom event handling
252385

253386
```java
254-
Message message = A2A.toUserMessage("tell me a joke", "message-1234"); // messageId is message-1234
387+
// Create custom consumers for this specific message
388+
List<BiConsumer<ClientEvent, AgentCard>> customConsumers = List.of(
389+
(event, card) -> {
390+
// handle this specific message's responses
391+
...
392+
}
393+
);
394+
395+
// Create custom error handler
396+
Consumer<Throwable> customErrorHandler = error -> {
397+
// handle the error
398+
...
399+
};
400+
401+
Message message = A2A.toUserMessage("tell me a joke");
402+
client.sendMessage(message, customConsumers, customErrorHandler);
255403
```
256404

257405
#### Get the current state of a task
258406

259407
```java
260408
// Retrieve the task with id "task-1234"
261-
GetTaskResponse response = client.getTask("task-1234");
409+
Task task = client.getTask(new TaskQueryParams("task-1234"));
262410

263411
// You can also specify the maximum number of items of history for the task
264-
// to include in the response
265-
GetTaskResponse response = client.getTask(new TaskQueryParams("task-1234", 10));
412+
// to include in the response and
413+
Task task = client.getTask(new TaskQueryParams("task-1234", 10));
414+
415+
// You can also optionally specify a ClientCallContext with call-specific config to use
416+
Task task = client.getTask(new TaskQueryParams("task-1234"), clientCallContext);
266417
```
267418

268419
#### Cancel an ongoing task
269420

270421
```java
271422
// Cancel the task we previously submitted with id "task-1234"
272-
CancelTaskResponse response = client.cancelTask("task-1234");
423+
Task cancelledTask = client.cancelTask(new TaskIdParams("task-1234"));
273424

274425
// You can also specify additional properties using a map
275-
Map<String, Object> metadata = ...
276-
CancelTaskResponse response = client.cancelTask(new TaskIdParams("task-1234", metadata));
426+
Map<String, Object> metadata = Map.of("reason", "user_requested");
427+
Task cancelledTask = client.cancelTask(new TaskIdParams("task-1234", metadata));
428+
429+
// You can also optionally specify a ClientCallContext with call-specific config to use
430+
Task cancelledTask = client.cancelTask(new TaskIdParams("task-1234"), clientCallContext);
277431
```
278432

279433
#### Get a push notification configuration for a task
280434

281435
```java
282436
// Get task push notification configuration
283-
GetTaskPushNotificationConfigResponse response = client.getTaskPushNotificationConfig("task-1234");
437+
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
438+
new GetTaskPushNotificationConfigParams("task-1234"));
284439

285440
// The push notification configuration ID can also be optionally specified
286-
GetTaskPushNotificationConfigResponse response = client.getTaskPushNotificationConfig("task-1234", "config-4567");
441+
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
442+
new GetTaskPushNotificationConfigParams("task-1234", "config-4567"));
287443

288444
// Additional properties can be specified using a map
289-
Map<String, Object> metadata = ...
290-
GetTaskPushNotificationConfigResponse response = client.getTaskPushNotificationConfig(new GetTaskPushNotificationConfigParams("task-1234", "config-1234", metadata));
445+
Map<String, Object> metadata = Map.of("source", "client");
446+
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
447+
new GetTaskPushNotificationConfigParams("task-1234", "config-1234", metadata));
448+
449+
// You can also optionally specify a ClientCallContext with call-specific config to use
450+
TaskPushNotificationConfig config = client.getTaskPushNotificationConfiguration(
451+
new GetTaskPushNotificationConfigParams("task-1234"), clientCallContext);
291452
```
292453

293454
#### Set a push notification configuration for a task
@@ -298,79 +459,75 @@ PushNotificationConfig pushNotificationConfig = new PushNotificationConfig.Build
298459
.url("https://example.com/callback")
299460
.authenticationInfo(new AuthenticationInfo(Collections.singletonList("jwt"), null))
300461
.build();
301-
SetTaskPushNotificationResponse response = client.setTaskPushNotificationConfig("task-1234", pushNotificationConfig);
302-
```
303462

304-
#### List the push notification configurations for a task
463+
TaskPushNotificationConfig taskConfig = new TaskPushNotificationConfig.Builder()
464+
.taskId("task-1234")
465+
.pushNotificationConfig(pushNotificationConfig)
466+
.build();
305467

306-
```java
307-
ListTaskPushNotificationConfigResponse response = client.listTaskPushNotificationConfig("task-1234");
468+
TaskPushNotificationConfig result = client.setTaskPushNotificationConfiguration(taskConfig);
308469

309-
// Additional properties can be specified using a map
310-
Map<String, Object> metadata = ...
311-
ListTaskPushNotificationConfigResponse response = client.listTaskPushNotificationConfig(new ListTaskPushNotificationConfigParams("task-123", metadata));
470+
// You can also optionally specify a ClientCallContext with call-specific config to use
471+
TaskPushNotificationConfig result = client.setTaskPushNotificationConfiguration(taskConfig, clientCallContext);
312472
```
313473

314-
#### Delete a push notification configuration for a task
474+
#### List the push notification configurations for a task
315475

316476
```java
317-
DeleteTaskPushNotificationConfigResponse response = client.deleteTaskPushNotificationConfig("task-1234", "config-4567");
477+
List<TaskPushNotificationConfig> configs = client.listTaskPushNotificationConfigurations(
478+
new ListTaskPushNotificationConfigParams("task-1234"));
318479

319480
// Additional properties can be specified using a map
320-
Map<String, Object> metadata = ...
321-
DeleteTaskPushNotificationConfigResponse response = client.deleteTaskPushNotificationConfig(new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567", metadata));
481+
Map<String, Object> metadata = Map.of("filter", "active");
482+
List<TaskPushNotificationConfig> configs = client.listTaskPushNotificationConfigurations(
483+
new ListTaskPushNotificationConfigParams("task-1234", metadata));
484+
485+
// You can also optionally specify a ClientCallContext with call-specific config to use
486+
List<TaskPushNotificationConfig> configs = client.listTaskPushNotificationConfigurations(
487+
new ListTaskPushNotificationConfigParams("task-1234"), clientCallContext);
322488
```
323489

324-
#### Send a streaming message
490+
#### Delete a push notification configuration for a task
325491

326492
```java
327-
// Send a text message to the remote agent
328-
Message message = A2A.toUserMessage("tell me some jokes"); // the message ID will be automatically generated for you
329-
MessageSendParams params = new MessageSendParams.Builder()
330-
.message(message)
331-
.build();
332-
333-
// Create a handler that will be invoked for Task, Message, TaskStatusUpdateEvent, and TaskArtifactUpdateEvent
334-
Consumer<StreamingEventKind> eventHandler = event -> {...};
493+
client.deleteTaskPushNotificationConfigurations(
494+
new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567"));
335495

336-
// Create a handler that will be invoked if an error is received
337-
Consumer<JSONRPCError> errorHandler = error -> {...};
338-
339-
// Create a handler that will be invoked in the event of a failure
340-
Runnable failureHandler = () -> {...};
496+
// Additional properties can be specified using a map
497+
Map<String, Object> metadata = Map.of("reason", "cleanup");
498+
client.deleteTaskPushNotificationConfigurations(
499+
new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567", metadata));
341500

342-
// Send the streaming message to the remote agent
343-
client.sendStreamingMessage(params, eventHandler, errorHandler, failureHandler);
501+
// You can also optionally specify a ClientCallContext with call-specific config to use
502+
client.deleteTaskPushNotificationConfigurations(
503+
new DeleteTaskPushNotificationConfigParams("task-1234", "config-4567", clientCallContext);
344504
```
345505

346506
#### Resubscribe to a task
347507

348508
```java
349-
// Create a handler that will be invoked for Task, Message, TaskStatusUpdateEvent, and TaskArtifactUpdateEvent
350-
Consumer<StreamingEventKind> eventHandler = event -> {...};
509+
// Resubscribe to an ongoing task with id "task-1234" using configured consumers
510+
TaskIdParams taskIdParams = new TaskIdParams("task-1234");
511+
client.resubscribe(taskIdParams);
351512

352-
// Create a handler that will be invoked if an error is received
353-
Consumer<JSONRPCError> errorHandler = error -> {...};
513+
// Or resubscribe with custom consumers and error handler
514+
List<BiConsumer<ClientEvent, AgentCard>> customConsumers = List.of(
515+
(event, card) -> System.out.println("Resubscribe event: " + event)
516+
);
517+
Consumer<Throwable> customErrorHandler = error ->
518+
System.err.println("Resubscribe error: " + error.getMessage());
354519

355-
// Create a handler that will be invoked in the event of a failure
356-
Runnable failureHandler = () -> {...};
520+
client.resubscribe(taskIdParams, customConsumers, customErrorHandler);
357521

358-
// Resubscribe to an ongoing task with id "task-1234"
359-
TaskIdParams taskIdParams = new TaskIdParams("task-1234");
360-
client.resubscribeToTask("request-1234", taskIdParams, eventHandler, errorHandler, failureHandler);
522+
// You can also optionally specify a ClientCallContext with call-specific config to use
523+
client.resubscribe(taskIdParams, clientCallContext);
361524
```
362525

363526
#### Retrieve details about the server agent that this client agent is communicating with
364527
```java
365528
AgentCard serverAgentCard = client.getAgentCard();
366529
```
367530

368-
An agent card can also be retrieved using the `A2A#getAgentCard` method:
369-
```java
370-
// http://localhost:1234 is the base URL for the agent whose card we want to retrieve
371-
AgentCard agentCard = A2A.getAgentCard("http://localhost:1234");
372-
```
373-
374531
## Additional Examples
375532

376533
### Hello World Client Example

0 commit comments

Comments
 (0)