From 2d20d1b213a0cc98994be9cf3ed5e070abed6df2 Mon Sep 17 00:00:00 2001 From: Google Team Member Date: Mon, 17 Aug 2026 05:27:04 -0700 Subject: [PATCH] fix: update Java ADK ResponseConverter to propagate long-running tool IDs on AUTH_REQUIRED state PiperOrigin-RevId: 965910141 --- .../adk/a2a/converters/ResponseConverter.java | 11 ++- .../a2a/converters/ResponseConverterTest.java | 83 +++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java index c8d20dbdd..ef9318a26 100644 --- a/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java +++ b/a2a/src/main/java/com/google/adk/a2a/converters/ResponseConverter.java @@ -150,7 +150,9 @@ private static Optional handleTaskUpdate( return messageToEvent(value, context, PENDING_STATES.contains(taskState)); }); - if (statusEvent.isFinal()) { + if (statusEvent.isFinal() + || taskState == TaskState.INPUT_REQUIRED + || taskState == TaskState.AUTH_REQUIRED) { messageEvent = messageEvent .map(Event::toBuilder) @@ -256,7 +258,9 @@ public static Event taskToEvent(Task task, InvocationContext invocationContext) ImmutableList finalParts = genaiParts.build(); boolean isFinal = - task.getStatus().state().isFinal() || task.getStatus().state() == TaskState.INPUT_REQUIRED; + task.getStatus().state().isFinal() + || task.getStatus().state() == TaskState.INPUT_REQUIRED + || task.getStatus().state() == TaskState.AUTH_REQUIRED; if (finalParts.isEmpty() && !isFinal) { return emptyEvent(invocationContext); @@ -264,7 +268,8 @@ public static Event taskToEvent(Task task, InvocationContext invocationContext) if (!finalParts.isEmpty()) { eventBuilder.content(fromModelParts(finalParts)); } - if (task.getStatus().state() == TaskState.INPUT_REQUIRED) { + if (task.getStatus().state() == TaskState.INPUT_REQUIRED + || task.getStatus().state() == TaskState.AUTH_REQUIRED) { eventBuilder.longRunningToolIds(longRunningToolIds.build()); } eventBuilder.turnComplete(isFinal); diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java index 9b854b616..c57866c99 100644 --- a/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java +++ b/a2a/src/test/java/com/google/adk/a2a/converters/ResponseConverterTest.java @@ -359,6 +359,43 @@ public void taskToEvent_withInputRequired_parsesLongRunningToolIds() { assertThat(event.longRunningToolIds().get()).containsExactly("call_123", "msg_123"); } + @Test + public void taskToEvent_withAuthRequired_parsesLongRunningToolIds() { + ImmutableMap data = + ImmutableMap.of("name", "myTool", "id", "call_123", "args", ImmutableMap.of()); + ImmutableMap metadata = + ImmutableMap.of( + A2AMetadataKey.TYPE.getType(), + "function_call", + A2AMetadataKey.IS_LONG_RUNNING.getType(), + true); + DataPart dataPart = new DataPart(data, metadata); + ImmutableMap statusData = + ImmutableMap.of("name", "messageTools", "id", "msg_123", "args", ImmutableMap.of()); + ImmutableMap statusMetadata = + ImmutableMap.of( + A2AMetadataKey.TYPE.getType(), + "function_call", + A2AMetadataKey.IS_LONG_RUNNING.getType(), + true); + DataPart statusDataPart = new DataPart(statusData, statusMetadata); + Message statusMessage = + new Message.Builder() + .role(Message.Role.AGENT) + .parts(ImmutableList.of(statusDataPart)) + .build(); + TaskStatus status = new TaskStatus(TaskState.AUTH_REQUIRED, statusMessage, null); + + Artifact artifact = + new Artifact.Builder().artifactId("artifact-1").parts(ImmutableList.of(dataPart)).build(); + Task task = testTask().status(status).artifacts(ImmutableList.of(artifact)).build(); + + Event event = ResponseConverter.taskToEvent(task, invocationContext); + assertThat(event).isNotNull(); + assertThat(event.longRunningToolIds().get()).containsExactly("call_123", "msg_123"); + assertThat(event.turnComplete()).hasValue(true); + } + @Test public void taskToEvent_withDataPartWithoutMetadata_fallsBackToInlineJson() { DataPart dataPart = @@ -597,6 +634,52 @@ public void clientEventToEvent_withFinalTaskStatusUpdateEvent_withoutMessage_ret assertThat(resultEvent.turnComplete()).hasValue(true); } + @Test + public void + clientEventToEvent_withAuthRequiredTaskStatusUpdateEvent_evenIfNonFinal_returnsTurnComplete() { + Message statusMessage = + new Message.Builder() + .role(Message.Role.AGENT) + .parts(ImmutableList.of(new TextPart("Auth required message"))) + .build(); + TaskStatus status = new TaskStatus(TaskState.AUTH_REQUIRED, statusMessage, null); + TaskStatusUpdateEvent updateEvent = + testTaskStatusUpdateEvent().isFinal(false).status(status).build(); + + TaskUpdateEvent event = new TaskUpdateEvent(testTask().status(status).build(), updateEvent); + + Optional optionalEvent = ResponseConverter.clientEventToEvent(event, invocationContext); + assertThat(optionalEvent).isPresent(); + Event resultEvent = optionalEvent.get(); + assertThat(resultEvent.content().get().parts().get().get(0).text()) + .hasValue("Auth required message"); + assertThat(resultEvent.partial().orElse(false)).isFalse(); + assertThat(resultEvent.turnComplete()).hasValue(true); + } + + @Test + public void + clientEventToEvent_withInputRequiredTaskStatusUpdateEvent_evenIfNonFinal_returnsTurnComplete() { + Message statusMessage = + new Message.Builder() + .role(Message.Role.AGENT) + .parts(ImmutableList.of(new TextPart("Input required message"))) + .build(); + TaskStatus status = new TaskStatus(TaskState.INPUT_REQUIRED, statusMessage, null); + TaskStatusUpdateEvent updateEvent = + testTaskStatusUpdateEvent().isFinal(false).status(status).build(); + + TaskUpdateEvent event = new TaskUpdateEvent(testTask().status(status).build(), updateEvent); + + Optional optionalEvent = ResponseConverter.clientEventToEvent(event, invocationContext); + assertThat(optionalEvent).isPresent(); + Event resultEvent = optionalEvent.get(); + assertThat(resultEvent.content().get().parts().get().get(0).text()) + .hasValue("Input required message"); + assertThat(resultEvent.partial().orElse(false)).isFalse(); + assertThat(resultEvent.turnComplete()).hasValue(true); + } + @Test public void clientEventToEvent_withNonFinalTaskStatusUpdateEvent_withoutMessage_returnsEmpty() { TaskStatus status = new TaskStatus(TaskState.WORKING, null, null);