Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,12 @@ public Task onGetTask(TaskQueryParams params, ServerCallContext context) throws
* @return the task with limited history, or the original task if no limiting needed
*/
private static Task limitTaskHistory(Task task, @Nullable Integer historyLength) {
if (task.history() == null || historyLength == null || historyLength >= task.history().size()) {
// A negative historyLength is invalid (TaskQueryParams rejects it at construction, but
// guard defensively here to avoid IndexOutOfBoundsException in subList below). Consistent
// with the Python/JS SDKs, a negative value leaves the history untouched; 0 means an
// empty history, and N >= history size means no limiting is needed.
if (task.history() == null || historyLength == null || historyLength < 0
|| historyLength >= task.history().size()) {
return task;
}
// Keep only the most recent historyLength messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -50,6 +52,7 @@
import org.a2aproject.sdk.spec.TaskState;
import org.a2aproject.sdk.spec.TaskStatus;
import org.a2aproject.sdk.spec.TaskStatusUpdateEvent;
import org.a2aproject.sdk.spec.TaskQueryParams;
import org.a2aproject.sdk.spec.TextPart;
import org.a2aproject.sdk.spec.UnsupportedOperationError;

Expand Down Expand Up @@ -1146,4 +1149,58 @@ public void onComplete() {
assertEquals("1.0", pushConfigStore.getProtocolVersion(taskId, taskId),
"Protocol version should be stored when push config is provided via onMessageSendStream");
}

@Test
void testOnGetTaskHistoryLengthLimitsHistory() throws Exception {
Task task = taskWithHistory("task-hl-limit");
taskStore.save(task, false);

Task result = requestHandler.onGetTask(new TaskQueryParams("task-hl-limit", 2), NULL_CONTEXT);

assertEquals(2, result.history().size());
assertEquals("msg-2", result.history().get(0).messageId());
assertEquals("msg-3", result.history().get(1).messageId());
}

@Test
void testOnGetTaskHistoryLengthZeroReturnsEmptyHistory() throws Exception {
Task task = taskWithHistory("task-hl-zero");
taskStore.save(task, false);

Task result = requestHandler.onGetTask(new TaskQueryParams("task-hl-zero", 0), NULL_CONTEXT);

assertNotNull(result.history());
assertTrue(result.history().isEmpty());
}

@Test
void testLimitTaskHistoryNegativeHistoryLengthReturnsTaskUnchanged() throws Exception {
Task task = taskWithHistory("task-hl-negative");

Method method = DefaultRequestHandler.class.getDeclaredMethod(
"limitTaskHistory", Task.class, Integer.class);
method.setAccessible(true);
Task result = (Task) method.invoke(null, task, -1);

// A negative historyLength must not throw IndexOutOfBoundsException and must leave
// the history untouched (aligned with the Python/JS SDK semantics).
assertSame(task, result);
assertEquals(task.history(), result.history());
}

private Task taskWithHistory(String id) {
return Task.builder()
.id(id)
.contextId("ctx-history")
.status(new TaskStatus(TaskState.TASK_STATE_COMPLETED))
.history(List.of(
Message.builder().messageId("msg-1").role(Message.Role.ROLE_USER)
.parts(new TextPart("one")).build(),
Message.builder().messageId("msg-2").role(Message.Role.ROLE_AGENT)
.parts(new TextPart("two")).build(),
Message.builder().messageId("msg-3").role(Message.Role.ROLE_AGENT)
.parts(new TextPart("three")).build()))
.artifacts(List.of())
.build();
}
}
Loading