Skip to content

Commit ca0eb30

Browse files
jmesnilclaude
andcommitted
refactor!: Convert Task class to Java record
Refactored the Task class in the spec module from a final class to a Java record to reduce boilerplate code and leverage modern Java features. Key changes: - Converted Task to record with compact canonical constructor - Preserved validation and defensive copying logic - Added deprecated getter methods for backward compatibility - Updated Builder to use record accessor methods Benefits: - Automatic equals(), hashCode(), and toString() implementations - Improved code readability and maintainability - Zero breaking changes (deprecated getters maintain API compatibility) Breaking change note: This is marked as breaking because while we maintain backward compatibility through deprecated methods, the underlying class structure has changed from a class to a record. See ADR 0002 (doc/adr/0002-refactor-task-to-java-record.md) for detailed architectural decision and rationale. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent cb084ec commit ca0eb30

File tree

43 files changed

+970
-874
lines changed

Some content is hidden

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

43 files changed

+970
-874
lines changed

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,21 @@ public Task saveTaskEvent(TaskStatusUpdateEvent taskStatusUpdateEvent) throws A2
5959
}
6060
Task task = currentTask;
6161
if (task == null) {
62-
task = new Task.Builder()
62+
task = Task.builder()
6363
.status(new TaskStatus(TaskState.UNKNOWN))
6464
.id(taskId)
6565
.contextId(contextId == null ? "" : contextId)
6666
.build();
6767
}
6868

69-
Task.Builder taskBuilder = new Task.Builder(task);
69+
Task.Builder taskBuilder = Task.builder(task);
7070
if (taskStatusUpdateEvent.getStatus().message() != null) {
71-
if (task.getHistory() == null) {
72-
taskBuilder.history(taskStatusUpdateEvent.getStatus().message());
73-
} else {
74-
List<Message> history = new ArrayList<>(task.getHistory());
75-
history.add(taskStatusUpdateEvent.getStatus().message());
76-
taskBuilder.history(history);
77-
}
71+
List<Message> history = new ArrayList<>(task.history());
72+
history.add(taskStatusUpdateEvent.getStatus().message());
73+
taskBuilder.history(history);
7874
}
7975
if (taskStatusUpdateEvent.getMetadata() != null) {
80-
Map<String, Object> newMetadata = task.getMetadata() != null ? new HashMap<>(task.getMetadata()) : new HashMap<>();
76+
Map<String, Object> newMetadata = task.metadata() != null ? new HashMap<>(task.metadata()) : new HashMap<>();
8177
newMetadata.putAll(taskStatusUpdateEvent.getMetadata());
8278
taskBuilder.metadata(newMetadata);
8379
}
@@ -95,7 +91,7 @@ public Task saveTaskEvent(TaskArtifactUpdateEvent taskArtifactUpdateEvent) {
9591
}
9692
Task task = currentTask;
9793
if (task == null) {
98-
task = new Task.Builder()
94+
task = Task.builder()
9995
.status(new TaskStatus(TaskState.UNKNOWN))
10096
.id(taskId)
10197
.contextId(contextId == null ? "" : contextId)
@@ -114,14 +110,14 @@ public Task saveTaskEvent(TaskArtifactUpdateEvent taskArtifactUpdateEvent) {
114110
* @return the updated task
115111
*/
116112
public Task updateWithMessage(Message message, Task task) {
117-
Task.Builder taskBuilder = new Task.Builder(task);
118-
List<Message> history = task.getHistory();
113+
Task.Builder taskBuilder = Task.builder(task);
114+
List<Message> history = task.history();
119115
if (history == null) {
120116
history = new ArrayList<>();
121117
}
122-
if (task.getStatus().message() != null) {
123-
history.add(task.getStatus().message());
124-
taskBuilder.status(new TaskStatus(task.getStatus().state(), null, task.getStatus().timestamp()));
118+
if (task.status().message() != null) {
119+
history.add(task.status().message());
120+
taskBuilder.status(new TaskStatus(task.status().state(), null, task.status().timestamp()));
125121
}
126122
history.add(message);
127123
taskBuilder.history(history);
@@ -132,8 +128,8 @@ public Task updateWithMessage(Message message, Task task) {
132128
private void saveTask(Task task) {
133129
currentTask = task;
134130
if (taskId == null) {
135-
taskId = currentTask.getId();
136-
contextId = currentTask.getContextId();
131+
taskId = currentTask.id();
132+
contextId = currentTask.contextId();
137133
}
138134
}
139135
}

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportStreamingTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,10 @@ public void testA2AClientResubscribeToTask() throws Exception {
159159
assertNotNull(eventKind);
160160
assertInstanceOf(Task.class, eventKind);
161161
Task task = (Task) eventKind;
162-
assertEquals("2", task.getId());
163-
assertEquals("context-1234", task.getContextId());
164-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
165-
List<Artifact> artifacts = task.getArtifacts();
162+
assertEquals("2", task.id());
163+
assertEquals("context-1234", task.contextId());
164+
assertEquals(TaskState.COMPLETED, task.status().state());
165+
List<Artifact> artifacts = task.artifacts();
166166
assertEquals(1, artifacts.size());
167167
Artifact artifact = artifacts.get(0);
168168
assertEquals("artifact-1", artifact.artifactId());

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JSONRPCTransportTest.java

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,18 @@ public void testA2AClientSendMessage() throws Exception {
122122
EventKind result = client.sendMessage(params, null);
123123
assertInstanceOf(Task.class, result);
124124
Task task = (Task) result;
125-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
126-
assertNotNull(task.getContextId());
127-
assertEquals(TaskState.COMPLETED,task.getStatus().state());
128-
assertEquals(1, task.getArtifacts().size());
129-
Artifact artifact = task.getArtifacts().get(0);
125+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
126+
assertNotNull(task.contextId());
127+
assertEquals(TaskState.COMPLETED,task.status().state());
128+
assertEquals(1, task.artifacts().size());
129+
Artifact artifact = task.artifacts().get(0);
130130
assertEquals("artifact-1", artifact.artifactId());
131131
assertEquals("joke", artifact.name());
132132
assertEquals(1, artifact.parts().size());
133133
Part<?> part = artifact.parts().get(0);
134134
assertEquals(Part.Kind.TEXT, part.getKind());
135135
assertEquals("Why did the chicken cross the road? To get to the other side!", ((TextPart) part).getText());
136-
assertTrue(task.getMetadata().isEmpty());
136+
assertTrue(task.metadata().isEmpty());
137137
}
138138

139139
@Test
@@ -235,18 +235,18 @@ public void testA2AClientGetTask() throws Exception {
235235
JSONRPCTransport client = new JSONRPCTransport("http://localhost:4001");
236236
Task task = client.getTask(new TaskQueryParams("de38c76d-d54c-436c-8b9f-4c2703648d64",
237237
10), null);
238-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
239-
assertEquals("c295ea44-7543-4f78-b524-7a38915ad6e4", task.getContextId());
240-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
241-
assertEquals(1, task.getArtifacts().size());
242-
Artifact artifact = task.getArtifacts().get(0);
238+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
239+
assertEquals("c295ea44-7543-4f78-b524-7a38915ad6e4", task.contextId());
240+
assertEquals(TaskState.COMPLETED, task.status().state());
241+
assertEquals(1, task.artifacts().size());
242+
Artifact artifact = task.artifacts().get(0);
243243
assertEquals(1, artifact.parts().size());
244244
assertEquals("artifact-1", artifact.artifactId());
245245
Part<?> part = artifact.parts().get(0);
246246
assertEquals(Part.Kind.TEXT, part.getKind());
247247
assertEquals("Why did the chicken cross the road? To get to the other side!", ((TextPart) part).getText());
248-
assertTrue(task.getMetadata().isEmpty());
249-
List<Message> history = task.getHistory();
248+
assertTrue(task.metadata().isEmpty());
249+
List<Message> history = task.history();
250250
assertNotNull(history);
251251
assertEquals(1, history.size());
252252
Message message = history.get(0);
@@ -267,7 +267,7 @@ public void testA2AClientGetTask() throws Exception {
267267
filePart = ((FilePart) part).getFile();
268268
assertEquals("aGVsbG8=", ((FileWithBytes) filePart).bytes());
269269
assertEquals("hello.txt", filePart.name());
270-
assertTrue(task.getMetadata().isEmpty());
270+
assertTrue(task.metadata().isEmpty());
271271
}
272272

273273
@Test
@@ -288,10 +288,10 @@ public void testA2AClientCancelTask() throws Exception {
288288
JSONRPCTransport client = new JSONRPCTransport("http://localhost:4001");
289289
Task task = client.cancelTask(new TaskIdParams("de38c76d-d54c-436c-8b9f-4c2703648d64",
290290
new HashMap<>()), null);
291-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
292-
assertEquals("c295ea44-7543-4f78-b524-7a38915ad6e4", task.getContextId());
293-
assertEquals(TaskState.CANCELED, task.getStatus().state());
294-
assertTrue(task.getMetadata().isEmpty());
291+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
292+
assertEquals("c295ea44-7543-4f78-b524-7a38915ad6e4", task.contextId());
293+
assertEquals(TaskState.CANCELED, task.status().state());
294+
assertTrue(task.metadata().isEmpty());
295295
}
296296

297297
@Test
@@ -553,20 +553,20 @@ public void testA2AClientSendMessageWithFilePart() throws Exception {
553553
EventKind result = client.sendMessage(params, null);
554554
assertInstanceOf(Task.class, result);
555555
Task task = (Task) result;
556-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
557-
assertNotNull(task.getContextId());
558-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
559-
assertEquals(1, task.getArtifacts().size());
560-
Artifact artifact = task.getArtifacts().get(0);
556+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
557+
assertNotNull(task.contextId());
558+
assertEquals(TaskState.COMPLETED, task.status().state());
559+
assertEquals(1, task.artifacts().size());
560+
Artifact artifact = task.artifacts().get(0);
561561
assertEquals("artifact-1", artifact.artifactId());
562562
assertEquals("image-analysis", artifact.name());
563563
assertEquals(1, artifact.parts().size());
564564
Part<?> part = artifact.parts().get(0);
565565
assertEquals(Part.Kind.TEXT, part.getKind());
566566
assertEquals("This is an image of a cat sitting on a windowsill.", ((TextPart) part).getText());
567-
assertFalse(task.getMetadata().isEmpty());
568-
assertEquals(1, task.getMetadata().size());
569-
assertEquals("metadata-test", task.getMetadata().get("test"));
567+
assertFalse(task.metadata().isEmpty());
568+
assertEquals(1, task.metadata().size());
569+
assertEquals("metadata-test", task.metadata().get("test"));
570570
}
571571

572572
@Test
@@ -613,18 +613,18 @@ public void testA2AClientSendMessageWithDataPart() throws Exception {
613613
EventKind result = client.sendMessage(params, null);
614614
assertInstanceOf(Task.class, result);
615615
Task task = (Task) result;
616-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
617-
assertNotNull(task.getContextId());
618-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
619-
assertEquals(1, task.getArtifacts().size());
620-
Artifact artifact = task.getArtifacts().get(0);
616+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
617+
assertNotNull(task.contextId());
618+
assertEquals(TaskState.COMPLETED, task.status().state());
619+
assertEquals(1, task.artifacts().size());
620+
Artifact artifact = task.artifacts().get(0);
621621
assertEquals("artifact-1", artifact.artifactId());
622622
assertEquals("data-analysis", artifact.name());
623623
assertEquals(1, artifact.parts().size());
624624
Part<?> part = artifact.parts().get(0);
625625
assertEquals(Part.Kind.TEXT, part.getKind());
626626
assertEquals("Processed weather data: Temperature is 25.5°C, humidity is 60.2% in San Francisco.", ((TextPart) part).getText());
627-
assertTrue(task.getMetadata().isEmpty());
627+
assertTrue(task.metadata().isEmpty());
628628
}
629629

630630
@Test
@@ -671,17 +671,17 @@ public void testA2AClientSendMessageWithMixedParts() throws Exception {
671671
EventKind result = client.sendMessage(params, null);
672672
assertInstanceOf(Task.class, result);
673673
Task task = (Task) result;
674-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
675-
assertNotNull(task.getContextId());
676-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
677-
assertEquals(1, task.getArtifacts().size());
678-
Artifact artifact = task.getArtifacts().get(0);
674+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
675+
assertNotNull(task.contextId());
676+
assertEquals(TaskState.COMPLETED, task.status().state());
677+
assertEquals(1, task.artifacts().size());
678+
Artifact artifact = task.artifacts().get(0);
679679
assertEquals("artifact-1", artifact.artifactId());
680680
assertEquals("mixed-analysis", artifact.name());
681681
assertEquals(1, artifact.parts().size());
682682
Part<?> part = artifact.parts().get(0);
683683
assertEquals(Part.Kind.TEXT, part.getKind());
684684
assertEquals("Analyzed chart image and data: Bar chart showing quarterly data with values [10, 20, 30, 40].", ((TextPart) part).getText());
685-
assertTrue(task.getMetadata().isEmpty());
685+
assertTrue(task.metadata().isEmpty());
686686
}
687687
}

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/sse/SSEEventListenerTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public void testOnEventWithTaskResult() throws Exception {
4949
assertNotNull(receivedEvent.get());
5050
assertTrue(receivedEvent.get() instanceof Task);
5151
Task task = (Task) receivedEvent.get();
52-
assertEquals("task-123", task.getId());
53-
assertEquals("context-456", task.getContextId());
54-
assertEquals(TaskState.WORKING, task.getStatus().state());
52+
assertEquals("task-123", task.id());
53+
assertEquals("context-456", task.contextId());
54+
assertEquals(TaskState.WORKING, task.status().state());
5555
}
5656

5757
@Test

client/transport/rest/src/test/java/io/a2a/client/transport/rest/RestTransportTest.java

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ public void testSendMessage() throws Exception {
133133
EventKind result = instance.sendMessage(messageSendParams, context);
134134
assertEquals("task", result.getKind());
135135
Task task = (Task) result;
136-
assertEquals("9b511af4-b27c-47fa-aecf-2a93c08a44f8", task.getId());
137-
assertEquals("context-1234", task.getContextId());
138-
assertEquals(TaskState.SUBMITTED, task.getStatus().state());
139-
assertNull(task.getStatus().message());
140-
assertNull(task.getMetadata());
141-
assertEquals(true, task.getArtifacts().isEmpty());
142-
assertEquals(1, task.getHistory().size());
143-
Message history = task.getHistory().get(0);
136+
assertEquals("9b511af4-b27c-47fa-aecf-2a93c08a44f8", task.id());
137+
assertEquals("context-1234", task.contextId());
138+
assertEquals(TaskState.SUBMITTED, task.status().state());
139+
assertNull(task.status().message());
140+
assertNull(task.metadata());
141+
assertEquals(true, task.artifacts().isEmpty());
142+
assertEquals(1, task.history().size());
143+
Message history = task.history().get(0);
144144
assertEquals("message", history.getKind());
145145
assertEquals(Message.Role.USER, history.getRole());
146146
assertEquals("context-1234", history.getContextId());
@@ -149,7 +149,7 @@ public void testSendMessage() throws Exception {
149149
assertEquals(1, history.getParts().size());
150150
assertEquals(Kind.TEXT, history.getParts().get(0).getKind());
151151
assertEquals("tell me a joke", ((TextPart) history.getParts().get(0)).getText());
152-
assertNull(task.getMetadata());
152+
assertNull(task.metadata());
153153
assertNull(history.getReferenceTaskIds());
154154
}
155155

@@ -173,11 +173,11 @@ public void testCancelTask() throws Exception {
173173
RestTransport instance = new RestTransport(CARD);
174174
Task task = instance.cancelTask(new TaskIdParams("de38c76d-d54c-436c-8b9f-4c2703648d64",
175175
new HashMap<>()), context);
176-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
177-
assertEquals(TaskState.CANCELED, task.getStatus().state());
178-
assertNull(task.getStatus().message());
179-
assertNotNull(task.getMetadata());
180-
assertTrue(task.getMetadata().isEmpty());
176+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
177+
assertEquals(TaskState.CANCELED, task.status().state());
178+
assertNull(task.status().message());
179+
assertNotNull(task.metadata());
180+
assertTrue(task.metadata().isEmpty());
181181
}
182182

183183
/**
@@ -199,21 +199,21 @@ public void testGetTask() throws Exception {
199199
TaskQueryParams request = new TaskQueryParams("de38c76d-d54c-436c-8b9f-4c2703648d64", 10);
200200
RestTransport instance = new RestTransport(CARD);
201201
Task task = instance.getTask(request, context);
202-
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.getId());
203-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
204-
assertNull(task.getStatus().message());
205-
assertNotNull(task.getMetadata());
206-
assertTrue(task.getMetadata().isEmpty());
207-
assertEquals(false, task.getArtifacts().isEmpty());
208-
assertEquals(1, task.getArtifacts().size());
209-
Artifact artifact = task.getArtifacts().get(0);
202+
assertEquals("de38c76d-d54c-436c-8b9f-4c2703648d64", task.id());
203+
assertEquals(TaskState.COMPLETED, task.status().state());
204+
assertNull(task.status().message());
205+
assertNotNull(task.metadata());
206+
assertTrue(task.metadata().isEmpty());
207+
assertEquals(false, task.artifacts().isEmpty());
208+
assertEquals(1, task.artifacts().size());
209+
Artifact artifact = task.artifacts().get(0);
210210
assertEquals("artifact-1", artifact.artifactId());
211211
assertNull(artifact.name());
212212
assertEquals(false, artifact.parts().isEmpty());
213213
assertEquals(Kind.TEXT, artifact.parts().get(0).getKind());
214214
assertEquals("Why did the chicken cross the road? To get to the other side!", ((TextPart) artifact.parts().get(0)).getText());
215-
assertEquals(1, task.getHistory().size());
216-
Message history = task.getHistory().get(0);
215+
assertEquals(1, task.history().size());
216+
Message history = task.history().get(0);
217217
assertEquals("message", history.getKind());
218218
assertEquals(Message.Role.USER, history.getRole());
219219
assertEquals("message-123", history.getMessageId());
@@ -280,7 +280,7 @@ public void testSendMessageStreaming() throws Exception {
280280
assertNotNull(receivedEvent.get());
281281
assertEquals("task", receivedEvent.get().getKind());
282282
Task task = (Task) receivedEvent.get();
283-
assertEquals("2", task.getId());
283+
assertEquals("2", task.id());
284284
}
285285

286286
/**
@@ -440,10 +440,10 @@ public void testResubscribe() throws Exception {
440440
assertNotNull(eventKind);
441441
assertInstanceOf(Task.class, eventKind);
442442
Task task = (Task) eventKind;
443-
assertEquals("2", task.getId());
444-
assertEquals("context-1234", task.getContextId());
445-
assertEquals(TaskState.COMPLETED, task.getStatus().state());
446-
List<Artifact> artifacts = task.getArtifacts();
443+
assertEquals("2", task.id());
444+
assertEquals("context-1234", task.contextId());
445+
assertEquals(TaskState.COMPLETED, task.status().state());
446+
List<Artifact> artifacts = task.artifacts();
447447
assertEquals(1, artifacts.size());
448448
Artifact artifact = artifacts.get(0);
449449
assertEquals("artifact-1", artifact.artifactId());

0 commit comments

Comments
 (0)