Skip to content

Commit ddd151d

Browse files
authored
Merge branch 'main' into issue_698
2 parents 0fdcf3d + 566978c commit ddd151d

File tree

67 files changed

+2097
-3799
lines changed

Some content is hidden

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

67 files changed

+2097
-3799
lines changed

AGENTS.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# AGENTS.md
2+
3+
## Project Overview
4+
5+
Java SDK for the [Agent2Agent (A2A) Protocol](https://a2a-protocol.org/). Multi-module Maven project (`io.github.a2asdk` group) providing client and server libraries for A2A agent communication over JSON-RPC, gRPC, and REST transports.
6+
7+
## Build
8+
9+
Requires Java 17+.
10+
Tests output is redirected to files by default.
11+
12+
```bash
13+
mvn clean install
14+
```
15+
16+
17+
## Project Structure
18+
19+
- `spec/` — A2A specification types (Java POJOs for the protocol)
20+
- `spec-grpc/` — gRPC protobuf definitions and generated classes
21+
- `common/` — Shared utilities used across modules
22+
- `client/` — Client SDK
23+
- `base/` — Core client API
24+
- `transport/spi/` — Transport SPI
25+
- `transport/jsonrpc/`, `transport/grpc/`, `transport/rest/` — Transport implementations
26+
- `server-common/` — Server-side core (AgentExecutor, TaskStore, QueueManager)
27+
- `transport/` — Server transport layer (jsonrpc, grpc, rest)
28+
- `http-client/` — HTTP client abstraction
29+
- `jsonrpc-common/` — Shared JSON-RPC utilities
30+
- `reference/` — Reference server implementations built on Quarkus
31+
- `common/`, `jsonrpc/`, `grpc/`, `rest/`
32+
- `tck/` — Technology Compatibility Kit (protocol conformance tests)
33+
- `tests/` — Integration tests
34+
- `extras/` — Optional add-ons (OpenTelemetry, JPA task/notification stores, replicated queue manager, Vert.x HTTP client)
35+
- `integrations/` — Runtime integrations (e.g., MicroProfile Config)
36+
- `boms/` — Bill of Materials POMs (sdk, extras, reference, test-utils)
37+
- `examples/` — Sample applications (helloworld, cloud-deployment)
38+
39+
## Key Conventions
40+
41+
- Package root: `io.a2a`
42+
- Serialization: Gson (see `gson.version` in parent POM)
43+
- Null safety: NullAway + JSpecify annotations enforced via Error Prone
44+
- Reference server runtime: Quarkus
45+
- Testing: JUnit 5, Mockito, REST Assured, Testcontainers
46+
47+
### Code Style
48+
49+
- Import statements are sorted
50+
- Remove any unused import statements
51+
- Do not use "star" imports (eg `import java.util.*`)
52+
- Use Java `record` for immutable data types
53+
- Use `@Nullable` (from org.jspecify.annotations) for optional fields
54+
- Use `io.a2a.util.Assert.checkNotNullParam()` in the compact constructor to validate required fields
55+
- Use `List.copyOf()` and `Map.copyOf()` for defensive copying of collections
56+
- Apply the Builder pattern for records with many fields (see `AgentCard.java` as reference)
57+
58+
### Code generation
59+
60+
- Be concise
61+
- Try to use existing code instead of generating new similar code
62+
- Use the same code convention than existing code. If the existing convention seems incorrect, make suggestion before doing any changes
63+
64+
### PR instructions
65+
- Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for the commit title and message
66+
- Always ask if the commit is related to a GitHub issue. If that's the case, add a `This fixes #{issue_number}` at the end of the commit message
67+
68+
### Skills
69+
70+
- [update-a2a-proto](skills/update-a2a-proto/SKILL.md) — Update the gRPC proto file `a2a.proto` from upstream and regenerate Java sources
71+
72+
### Commands
73+
74+
- `mvn clean install` — Clean build of the project
75+
76+
## Contributing
77+
78+
See [CONTRIBUTING.md](CONTRIBUTING.md). Fork the repo, create a branch per issue, submit PRs against `main`.

client/base/src/main/java/io/a2a/client/AbstractClient.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import io.a2a.spec.ListTasksParams;
2020
import io.a2a.spec.Message;
2121
import io.a2a.spec.MessageSendParams;
22-
import io.a2a.spec.PushNotificationConfig;
2322
import io.a2a.spec.Task;
2423
import io.a2a.spec.TaskIdParams;
2524
import io.a2a.spec.TaskPushNotificationConfig;
@@ -137,7 +136,7 @@ public abstract void sendMessage(@NonNull Message request,
137136
* @throws A2AClientException if sending the message fails for any reason
138137
*/
139138
public void sendMessage(@NonNull Message request,
140-
@Nullable PushNotificationConfig pushNotificationConfiguration,
139+
@Nullable TaskPushNotificationConfig pushNotificationConfiguration,
141140
@Nullable Map<String, Object> metadata) throws A2AClientException {
142141
sendMessage(request, pushNotificationConfiguration, metadata, null);
143142
}
@@ -158,7 +157,7 @@ public void sendMessage(@NonNull Message request,
158157
* @throws A2AClientException if sending the message fails for any reason
159158
*/
160159
public abstract void sendMessage(@NonNull Message request,
161-
@Nullable PushNotificationConfig pushNotificationConfiguration,
160+
@Nullable TaskPushNotificationConfig pushNotificationConfiguration,
162161
@Nullable Map<String, Object> metadata,
163162
@Nullable ClientCallContext context) throws A2AClientException;
164163

client/base/src/main/java/io/a2a/client/Client.java

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import io.a2a.spec.Message;
2727
import io.a2a.spec.MessageSendConfiguration;
2828
import io.a2a.spec.MessageSendParams;
29-
import io.a2a.spec.PushNotificationConfig;
3029
import io.a2a.spec.StreamingEventKind;
3130
import io.a2a.spec.Task;
3231
import io.a2a.spec.TaskArtifactUpdateEvent;
@@ -132,10 +131,11 @@
132131
* <b>Push notifications:</b> Configure webhooks to receive task updates:
133132
* <pre>{@code
134133
* // Configure push notifications for a task
135-
* PushNotificationConfig pushConfig = new PushNotificationConfig(
136-
* "https://my-app.com/webhooks/task-updates",
137-
* Map.of("Authorization", "Bearer my-token")
138-
* );
134+
* TaskPushNotificationConfig pushConfig = TaskPushNotificationConfig.builder()
135+
* .id("config-1")
136+
* .url("https://my-app.com/webhooks/task-updates")
137+
* .authentication(new AuthenticationInfo("Bearer", "my-token"))
138+
* .build();
139139
*
140140
* // Send message with push notifications
141141
* client.sendMessage(
@@ -236,7 +236,7 @@ public void sendMessage(@NonNull Message request,
236236
@NonNull List<BiConsumer<ClientEvent, AgentCard>> consumers,
237237
@Nullable Consumer<Throwable> streamingErrorHandler,
238238
@Nullable ClientCallContext context) throws A2AClientException {
239-
MessageSendConfiguration messageSendConfiguration = createMessageSendConfiguration(clientConfig.getPushNotificationConfig());
239+
MessageSendConfiguration messageSendConfiguration = createMessageSendConfiguration(clientConfig.getTaskPushNotificationConfig());
240240

241241
MessageSendParams messageSendParams = MessageSendParams.builder()
242242
.message(request)
@@ -267,10 +267,11 @@ public void sendMessage(@NonNull Message request,
267267
* <p>
268268
* <b>With push notifications:</b>
269269
* <pre>{@code
270-
* PushNotificationConfig pushConfig = new PushNotificationConfig(
271-
* "https://my-app.com/webhook",
272-
* Map.of("Authorization", "Bearer token")
273-
* );
270+
* TaskPushNotificationConfig pushConfig = TaskPushNotificationConfig.builder()
271+
* .id("config-1")
272+
* .url("https://my-app.com/webhook")
273+
* .authentication(new AuthenticationInfo("Bearer", "token"))
274+
* .build();
274275
* client.sendMessage(userMessage, pushConfig, null, null);
275276
* }</pre>
276277
* <p>
@@ -289,11 +290,11 @@ public void sendMessage(@NonNull Message request,
289290
* @param context custom call context for request interceptors (optional)
290291
* @throws A2AClientException if the message cannot be sent or if the agent returns an error
291292
* @see #sendMessage(Message, List, Consumer, ClientCallContext)
292-
* @see PushNotificationConfig
293+
* @see TaskPushNotificationConfig
293294
*/
294295
@Override
295296
public void sendMessage(@NonNull Message request,
296-
@Nullable PushNotificationConfig pushNotificationConfiguration,
297+
@Nullable TaskPushNotificationConfig pushNotificationConfiguration,
297298
@Nullable Map<String, Object> metadata,
298299
@Nullable ClientCallContext context) throws A2AClientException {
299300
MessageSendConfiguration messageSendConfiguration = createMessageSendConfiguration(pushNotificationConfiguration);
@@ -459,16 +460,12 @@ public Task cancelTask(CancelTaskParams request, @Nullable ClientCallContext con
459460
* <p>
460461
* Example:
461462
* <pre>{@code
462-
* TaskPushNotificationConfig config = new TaskPushNotificationConfig(
463-
* "task-123",
464-
* new PushNotificationConfig(
465-
* "https://my-app.com/webhooks/task-updates",
466-
* Map.of(
467-
* "Authorization", "Bearer my-webhook-secret",
468-
* "X-App-ID", "my-app"
469-
* )
470-
* )
471-
* );
463+
* TaskPushNotificationConfig config = TaskPushNotificationConfig.builder()
464+
* .id("config-1")
465+
* .taskId("task-123")
466+
* .url("https://my-app.com/webhooks/task-updates")
467+
* .authentication(new AuthenticationInfo("Bearer", "my-webhook-secret"))
468+
* .build();
472469
* client.createTaskPushNotificationConfiguration(config);
473470
* }</pre>
474471
*
@@ -477,7 +474,6 @@ public Task cancelTask(CancelTaskParams request, @Nullable ClientCallContext con
477474
* @return the stored configuration (may include server-assigned IDs)
478475
* @throws A2AClientException if the configuration cannot be set
479476
* @see TaskPushNotificationConfig
480-
* @see PushNotificationConfig
481477
*/
482478
@Override
483479
public TaskPushNotificationConfig createTaskPushNotificationConfiguration(
@@ -495,7 +491,7 @@ public TaskPushNotificationConfig createTaskPushNotificationConfiguration(
495491
* TaskPushNotificationConfig config =
496492
* client.getTaskPushNotificationConfiguration(params);
497493
* System.out.println("Webhook URL: " +
498-
* config.pushNotificationConfig().url());
494+
* config.url());
499495
* }</pre>
500496
*
501497
* @param request the parameters specifying which task's configuration to retrieve
@@ -522,7 +518,7 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration(
522518
* client.listTaskPushNotificationConfigurations(params);
523519
* for (TaskPushNotificationConfig config : result.configurations()) {
524520
* System.out.println("Task " + config.taskId() + " -> " +
525-
* config.pushNotificationConfig().url());
521+
* config.url());
526522
* }
527523
* }</pre>
528524
*
@@ -697,12 +693,12 @@ private ClientEvent getClientEvent(StreamingEventKind event, ClientTaskManager t
697693
}
698694
}
699695

700-
private MessageSendConfiguration createMessageSendConfiguration(@Nullable PushNotificationConfig pushNotificationConfig) {
696+
private MessageSendConfiguration createMessageSendConfiguration(@Nullable TaskPushNotificationConfig taskPushNotificationConfig) {
701697
return MessageSendConfiguration.builder()
702698
.acceptedOutputModes(clientConfig.getAcceptedOutputModes())
703699
.blocking(!clientConfig.isPolling())
704700
.historyLength(clientConfig.getHistoryLength())
705-
.pushNotificationConfig(pushNotificationConfig)
701+
.taskPushNotificationConfig(taskPushNotificationConfig)
706702
.build();
707703
}
708704

client/base/src/main/java/io/a2a/client/config/ClientConfig.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.util.List;
66
import java.util.Map;
77

8-
import io.a2a.spec.PushNotificationConfig;
8+
import io.a2a.spec.TaskPushNotificationConfig;
99
import org.jspecify.annotations.Nullable;
1010

1111
/**
@@ -79,12 +79,13 @@
7979
* <p>
8080
* <b>Push notifications:</b> Configure default webhook for all task updates:
8181
* <pre>{@code
82-
* PushNotificationConfig pushConfig = new PushNotificationConfig(
83-
* "https://my-app.com/webhooks/tasks",
84-
* Map.of("Authorization", "Bearer my-token")
85-
* );
82+
* TaskPushNotificationConfig pushConfig = TaskPushNotificationConfig.builder()
83+
* .id("config-1")
84+
* .url("https://my-app.com/webhooks/tasks")
85+
* .authentication(new AuthenticationInfo("bearer", "my-token"))
86+
* .build();
8687
* ClientConfig config = new ClientConfig.Builder()
87-
* .setPushNotificationConfig(pushConfig)
88+
* .setTaskPushNotificationConfig(pushConfig)
8889
* .build();
8990
* // All sendMessage() calls will use this webhook config
9091
* }</pre>
@@ -125,7 +126,7 @@
125126
* <li>useClientPreference: {@code false} (server preference)</li>
126127
* <li>acceptedOutputModes: empty list (accept all)</li>
127128
* <li>historyLength: {@code null} (no history)</li>
128-
* <li>pushNotificationConfig: {@code null} (no push notifications)</li>
129+
* <li>taskPushNotificationConfig: {@code null} (no push notifications)</li>
129130
* <li>metadata: empty map</li>
130131
* </ul>
131132
* <p>
@@ -134,15 +135,15 @@
134135
*
135136
* @see io.a2a.client.Client
136137
* @see io.a2a.client.ClientBuilder
137-
* @see PushNotificationConfig
138+
* @see TaskPushNotificationConfig
138139
*/
139140
public class ClientConfig {
140141

141142
private final Boolean streaming;
142143
private final Boolean polling;
143144
private final Boolean useClientPreference;
144145
private final List<String> acceptedOutputModes;
145-
private final @Nullable PushNotificationConfig pushNotificationConfig;
146+
private final @Nullable TaskPushNotificationConfig taskPushNotificationConfig;
146147
private final @Nullable Integer historyLength;
147148
private final Map<String, Object> metadata;
148149

@@ -151,7 +152,7 @@ private ClientConfig(Builder builder) {
151152
this.polling = builder.polling == null ? false : builder.polling;
152153
this.useClientPreference = builder.useClientPreference == null ? false : builder.useClientPreference;
153154
this.acceptedOutputModes = builder.acceptedOutputModes;
154-
this.pushNotificationConfig = builder.pushNotificationConfig;
155+
this.taskPushNotificationConfig = builder.taskPushNotificationConfig;
155156
this.historyLength = builder.historyLength;
156157
this.metadata = builder.metadata;
157158
}
@@ -219,10 +220,10 @@ public List<String> getAcceptedOutputModes() {
219220
* calls unless overridden with a different configuration.
220221
*
221222
* @return the push notification config, or {@code null} if not configured
222-
* @see io.a2a.client.Client#sendMessage(io.a2a.spec.Message, io.a2a.spec.PushNotificationConfig, java.util.Map, io.a2a.client.transport.spi.interceptors.ClientCallContext)
223+
* @see io.a2a.client.Client#sendMessage(io.a2a.spec.Message, io.a2a.spec.TaskPushNotificationConfig, java.util.Map, io.a2a.client.transport.spi.interceptors.ClientCallContext)
223224
*/
224-
public @Nullable PushNotificationConfig getPushNotificationConfig() {
225-
return pushNotificationConfig;
225+
public @Nullable TaskPushNotificationConfig getTaskPushNotificationConfig() {
226+
return taskPushNotificationConfig;
226227
}
227228

228229
/**
@@ -278,7 +279,7 @@ public static class Builder {
278279
private @Nullable Boolean polling;
279280
private @Nullable Boolean useClientPreference;
280281
private List<String> acceptedOutputModes = new ArrayList<>();
281-
private @Nullable PushNotificationConfig pushNotificationConfig;
282+
private @Nullable TaskPushNotificationConfig taskPushNotificationConfig;
282283
private @Nullable Integer historyLength;
283284
private Map<String, Object> metadata = new HashMap<>();
284285

@@ -347,12 +348,12 @@ public Builder setAcceptedOutputModes(List<String> acceptedOutputModes) {
347348
* This webhook configuration will be used for all sendMessage calls
348349
* unless overridden. The agent will POST task update events to the specified URL.
349350
*
350-
* @param pushNotificationConfig the push notification configuration
351+
* @param taskPushNotificationConfig the push notification configuration
351352
* @return this builder for method chaining
352-
* @see io.a2a.client.Client#sendMessage(io.a2a.spec.Message, io.a2a.spec.PushNotificationConfig, java.util.Map, io.a2a.client.transport.spi.interceptors.ClientCallContext)
353+
* @see io.a2a.client.Client#sendMessage(io.a2a.spec.Message, io.a2a.spec.TaskPushNotificationConfig, java.util.Map, io.a2a.client.transport.spi.interceptors.ClientCallContext)
353354
*/
354-
public Builder setPushNotificationConfig(PushNotificationConfig pushNotificationConfig) {
355-
this.pushNotificationConfig = pushNotificationConfig;
355+
public Builder setTaskPushNotificationConfig(TaskPushNotificationConfig taskPushNotificationConfig) {
356+
this.taskPushNotificationConfig = taskPushNotificationConfig;
356357
return this;
357358
}
358359

@@ -396,7 +397,7 @@ public Builder setMetadata(Map<String, Object> metadata) {
396397
* <li>polling: {@code false}</li>
397398
* <li>useClientPreference: {@code false}</li>
398399
* <li>acceptedOutputModes: empty list</li>
399-
* <li>pushNotificationConfig: {@code null}</li>
400+
* <li>taskPushNotificationConfig: {@code null}</li>
400401
* <li>historyLength: {@code null}</li>
401402
* <li>metadata: empty map</li>
402403
* </ul>

client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,7 @@ public TaskPushNotificationConfig createTaskPushNotificationConfiguration(TaskPu
235235
@Nullable ClientCallContext context) throws A2AClientException {
236236
checkNotNullParam("request", request);
237237

238-
String configId = request.config().id();
239-
io.a2a.grpc.CreateTaskPushNotificationConfigRequest grpcRequest = io.a2a.grpc.CreateTaskPushNotificationConfigRequest.newBuilder()
240-
.setTaskId(request.taskId())
241-
.setConfig(ToProto.taskPushNotificationConfig(request).getPushNotificationConfig())
242-
.setTenant(resolveTenant(request.tenant()))
243-
.build();
238+
io.a2a.grpc.TaskPushNotificationConfig grpcRequest = ToProto.taskPushNotificationConfig(request);
244239
PayloadAndHeaders payloadAndHeaders = applyInterceptors(SET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD, grpcRequest, agentCard, context);
245240

246241
try {

0 commit comments

Comments
 (0)