diff --git a/.github/workflows/pr-commit-check.yml b/.github/workflows/pr-commit-check.yml
index ec6644311..1e31e42f3 100644
--- a/.github/workflows/pr-commit-check.yml
+++ b/.github/workflows/pr-commit-check.yml
@@ -21,7 +21,7 @@ jobs:
# Step 1: Check out the code
# This action checks out your repository under $GITHUB_WORKSPACE, so your workflow can access it.
- name: Checkout Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
# We need to fetch all commits to accurately count them.
# '0' means fetch all history for all branches and tags.
diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml
index 6d3142907..258cf90db 100644
--- a/.github/workflows/release-please.yaml
+++ b/.github/workflows/release-please.yaml
@@ -14,4 +14,4 @@ jobs:
steps:
- uses: googleapis/release-please-action@v4
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml
index d9035a579..65e66f8fd 100644
--- a/.github/workflows/validation.yml
+++ b/.github/workflows/validation.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Set up Java ${{ matrix.java-version }}
uses: actions/setup-java@v4
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index d6a5f76bd..b0f3ba770 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,4 @@
{
- ".": "0.8.0"
+ ".": "0.9.0"
}
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d5b9e5eb..ab111e90c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,61 @@
# Changelog
+## [0.9.0](https://github.com/google/adk-java/compare/v0.8.0...v0.9.0) (2026-03-13)
+
+
+### ⚠ BREAKING CHANGES
+
+* refactor ApiClient constructors hierarchy to remove Optional parameters
+* remove deprecated LlmAgent.canonicalTools method
+* remove deprecated LoadArtifactsTool.loadArtifacts method
+* update LoopAgent's maxIteration field and methods to be @Nullable instead of Optional
+* Remove Optional parameters in EventActions
+* remove deprecated url method in ComputerState.Builder
+* Remove deprecated create method in ResponseProcessor
+* remove McpAsyncToolset constructors
+* use @Nullable fields in Event class
+* remove methods with Optional params from VertexCredential.Builder
+
+### Features
+
+* add formatting to the RemoteA2A agent so it filters out the previous agent responses and updates the context of the function calls and responses ([0d6dd55](https://github.com/google/adk-java/commit/0d6dd55f4870007e79db23e21bd261879dbfba79))
+* add multiple LLM responses to LLM recordings for conformance tests ([bdfb7a7](https://github.com/google/adk-java/commit/bdfb7a72188ce6e72c12c16c0abedb824b846160))
+* add support for gemini models in VertexAiRagRetrieval ([924fb71](https://github.com/google/adk-java/commit/924fb7174855b46a58be43373c1a29284c47dfa8))
+* Fixing the spans produced by agent calls to have the right parent spans ([3c8f488](https://github.com/google/adk-java/commit/3c8f4886f0e4c76abdbeb64a348bfccd5c16120e))
+* Fixing the spans produced by agent calls to have the right parent spans ([973f887](https://github.com/google/adk-java/commit/973f88743cabebcd2e6e7a8d5f141142b596dbbb))
+* refactor ApiClient constructors hierarchy to remove Optional parameters ([910d727](https://github.com/google/adk-java/commit/910d727f1981498151dea4cb91b9e5836f91e3ba))
+* Remove deprecated create method in ResponseProcessor ([5e1e1d4](https://github.com/google/adk-java/commit/5e1e1d434fa1f3931af30194422800757de96cb6))
+* remove deprecated LlmAgent.canonicalTools method ([aabf15a](https://github.com/google/adk-java/commit/aabf15a526ba525cdb47c74c246c178eff1851d5))
+* remove deprecated LoadArtifactsTool.loadArtifacts method ([bc38558](https://github.com/google/adk-java/commit/bc385589057a6daf0209a335280bf19d20b2126b))
+* remove deprecated url method in ComputerState.Builder ([a86ede0](https://github.com/google/adk-java/commit/a86ede007c3442ed73ee08a5c6ad0e2efa12998a))
+* remove executionId method that takes Optional param from CodeExecutionUtils ([be3b3f8](https://github.com/google/adk-java/commit/be3b3f8360888ea1f13796969bb19893c32727e0))
+* remove McpAsyncToolset constructors ([82ef5ac](https://github.com/google/adk-java/commit/82ef5ac2689e01676aa95d2616e3b4d8463e573e))
+* remove methods with Optional params from VertexCredential.Builder ([0b9057c](https://github.com/google/adk-java/commit/0b9057c9ccab98ea58597ec55b8168e32ac7c9a6))
+* Remove Optional parameters in EventActions ([b8316b1](https://github.com/google/adk-java/commit/b8316b1944ce17cc9208963cc09d900c379444c6))
+* replace Optional type of version in BaseArtifactService.loadArtifact with Nullable ([5fd4c53](https://github.com/google/adk-java/commit/5fd4c53c88e977d004b9eee8fa3697625ec85f47))
+* Trigger traceCallLlm to set call_llm attributes before span ends ([d9d84ee](https://github.com/google/adk-java/commit/d9d84ee67406cce8eeb66abcf1be24fad9c58e29))
+* Update converters for task and artifact events; add long running tools ids ([9ce78d7](https://github.com/google/adk-java/commit/9ce78d7c3e1b0fb6d8d4fdce9052a572ffb9e515))
+* update LoopAgent's maxIteration field and methods to be @Nullable instead of Optional ([e0d833b](https://github.com/google/adk-java/commit/e0d833b337e958e299d0d11a03f6bfa1468731bc))
+* update return type for artifactDelta getter and setter to Map from ConcurrentMap ([d1d5539](https://github.com/google/adk-java/commit/d1d5539ef763b6bfd5057c6ea0f2591225a98535))
+* update return type for requestedToolConfirmations getter and setter to Map from ConcurrentMap ([143b656](https://github.com/google/adk-java/commit/143b656949d61363d135e0b74ef5696e78eb270a))
+* update return type for stateDelta() to Map from ConcurrentMap ([3f6504e](https://github.com/google/adk-java/commit/3f6504e9416f9f644ef431e612ec983b9a2edd9d))
+* update State constructors to accept general Map types ([c6fdb63](https://github.com/google/adk-java/commit/c6fdb63c92e2f3481a01cfeafa946b6dce728c51))
+* use @Nullable fields in Event class ([67b602f](https://github.com/google/adk-java/commit/67b602f245f564238ea22298a37bf70049e56a12))
+
+
+### Bug Fixes
+
+* Explicitly setting the otel parent spans in agents, llm flow and function calls ([20f863f](https://github.com/google/adk-java/commit/20f863f716f653979551c481d85d4e7fa56a35da))
+* Make sure that `InvocationContext.callbackContextData` remains the same instance ([14ee28b](https://github.com/google/adk-java/commit/14ee28ba593a9f6f5f7b9bb6003441539fe33a18))
+* Removing deprecated InvocationContext methods ([41f5af0](https://github.com/google/adk-java/commit/41f5af0dceb78501ca8b94e434e4d751f608a699))
+* Removing deprecated methods in Runner ([0d8e22d](https://github.com/google/adk-java/commit/0d8e22d6e9fe4e8d29c87d485915ba51a22eb350))
+* Removing deprecated methods in Runner ([b857f01](https://github.com/google/adk-java/commit/b857f010a0f51df0eb25ecdc364465ffdd9fef65))
+
+
+### Miscellaneous Chores
+
+* override new version to 0.9.0 ([a47b651](https://github.com/google/adk-java/commit/a47b651b5c4868a603fd79df164b70bc712c3a80))
+
## [0.8.0](https://github.com/google/adk-java/compare/v0.7.0...v0.8.0) (2026-03-06)
diff --git a/README.md b/README.md
index 4a5dab81f..de1cfbef7 100644
--- a/README.md
+++ b/README.md
@@ -50,13 +50,13 @@ If you're using Maven, add the following to your dependencies:
com.google.adk
google-adk
- 0.8.0
+ 0.9.0
com.google.adk
google-adk-dev
- 0.8.0
+ 0.9.0
```
diff --git a/a2a/pom.xml b/a2a/pom.xml
index 0e969600e..1a6da6e41 100644
--- a/a2a/pom.xml
+++ b/a2a/pom.xml
@@ -5,7 +5,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
google-adk-a2a
diff --git a/a2a/src/main/java/com/google/adk/a2a/RemoteA2AAgent.java b/a2a/src/main/java/com/google/adk/a2a/agent/RemoteA2AAgent.java
similarity index 88%
rename from a2a/src/main/java/com/google/adk/a2a/RemoteA2AAgent.java
rename to a2a/src/main/java/com/google/adk/a2a/agent/RemoteA2AAgent.java
index b8ff39808..ccb662b7c 100644
--- a/a2a/src/main/java/com/google/adk/a2a/RemoteA2AAgent.java
+++ b/a2a/src/main/java/com/google/adk/a2a/agent/RemoteA2AAgent.java
@@ -1,4 +1,19 @@
-package com.google.adk.a2a;
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.adk.a2a.agent;
import static com.google.common.base.Strings.nullToEmpty;
@@ -44,26 +59,21 @@
import org.slf4j.LoggerFactory;
/**
- * Agent that communicates with a remote A2A agent via A2A client.
+ * Agent that communicates with a remote A2A agent via an A2A client.
*
- *
This agent supports multiple ways to specify the remote agent:
+ *
The remote agent can be specified directly by providing an {@link AgentCard} to the builder,
+ * or it can be resolved automatically using the provided A2A client.
*
- *
- * - Direct AgentCard object
- *
- URL to agent card JSON
- *
- File path to agent card JSON
- *
- *
- * The agent handles:
+ *
Key responsibilities of this agent include:
*
*
* - Agent card resolution and validation
- *
- A2A message conversion and error handling
- *
- Session state management across requests
+ *
- Converting ADK session history events into A2A requests ({@link io.a2a.spec.Message})
+ *
- Handling streaming and non-streaming responses from the A2A client
+ *
- Buffering and aggregating streamed response chunks into ADK {@link
+ * com.google.adk.events.Event}s
+ *
- Converting A2A client responses back into ADK format
*
- *
- * **EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not
- * use in production code.
*/
public class RemoteA2AAgent extends BaseAgent {
@@ -171,6 +181,25 @@ public RemoteA2AAgent build() {
}
}
+ private Message.Builder newA2AMessage(Message.Role role, List> parts) {
+ return new Message.Builder().messageId(UUID.randomUUID().toString()).role(role).parts(parts);
+ }
+
+ private Message prepareMessage(InvocationContext invocationContext) {
+ Event userCall = EventConverter.findUserFunctionCall(invocationContext.session().events());
+ if (userCall != null) {
+ ImmutableList> parts =
+ EventConverter.contentToParts(userCall.content(), userCall.partial().orElse(false));
+ return newA2AMessage(Message.Role.USER, parts)
+ .taskId(EventConverter.taskId(userCall))
+ .contextId(EventConverter.contextId(userCall))
+ .build();
+ }
+ return newA2AMessage(
+ Message.Role.USER, EventConverter.messagePartsFromContext(invocationContext))
+ .build();
+ }
+
@Override
protected Flowable runAsyncImpl(InvocationContext invocationContext) {
// Construct A2A Message from the last ADK event
@@ -181,14 +210,7 @@ protected Flowable runAsyncImpl(InvocationContext invocationContext) {
return Flowable.empty();
}
- Optional a2aMessageOpt = EventConverter.convertEventsToA2AMessage(invocationContext);
-
- if (a2aMessageOpt.isEmpty()) {
- logger.warn("Failed to convert event to A2A message.");
- return Flowable.empty();
- }
-
- Message originalMessage = a2aMessageOpt.get();
+ Message originalMessage = prepareMessage(invocationContext);
String requestJson = serializeMessageToJson(originalMessage);
return Flowable.create(
@@ -436,7 +458,7 @@ private boolean mergeAggregatedContentIntoEvent(Event event) {
}
Content aggregatedContent = Content.builder().role("model").parts(parts).build();
- event.setContent(Optional.of(aggregatedContent));
+ event.setContent(aggregatedContent);
ImmutableList.Builder newMetadata = ImmutableList.builder();
event.customMetadata().ifPresent(newMetadata::addAll);
diff --git a/a2a/src/main/java/com/google/adk/a2a/common/A2AClientError.java b/a2a/src/main/java/com/google/adk/a2a/common/A2AClientError.java
index 8e8282742..466c89223 100644
--- a/a2a/src/main/java/com/google/adk/a2a/common/A2AClientError.java
+++ b/a2a/src/main/java/com/google/adk/a2a/common/A2AClientError.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.common;
/** Exception thrown when the A2A client encounters an error. */
diff --git a/a2a/src/main/java/com/google/adk/a2a/common/A2AMetadata.java b/a2a/src/main/java/com/google/adk/a2a/common/A2AMetadata.java
index 5c75faeac..a5faeff2a 100644
--- a/a2a/src/main/java/com/google/adk/a2a/common/A2AMetadata.java
+++ b/a2a/src/main/java/com/google/adk/a2a/common/A2AMetadata.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.common;
/** Constants and utilities for A2A metadata keys. */
diff --git a/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java b/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java
index a5947dcb8..0ac56fc01 100644
--- a/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java
+++ b/a2a/src/main/java/com/google/adk/a2a/common/GenAiFieldMissingException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.common;
/** Exception thrown when the the genai class has an empty field. */
diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java b/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java
index b5b53c49a..e0e97c8e9 100644
--- a/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java
+++ b/a2a/src/main/java/com/google/adk/a2a/converters/A2ADataPartMetadataType.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.converters;
/** Enum for the type of A2A DataPart metadata. */
diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java
index 1a49b0070..71573070e 100644
--- a/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java
+++ b/a2a/src/main/java/com/google/adk/a2a/converters/EventConverter.java
@@ -1,73 +1,124 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.converters;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.adk.agents.InvocationContext;
+import com.google.adk.events.Event;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.genai.types.Content;
-import io.a2a.spec.Message;
+import com.google.genai.types.FunctionResponse;
import io.a2a.spec.Part;
import java.util.Collection;
+import java.util.List;
import java.util.Optional;
import java.util.UUID;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.jspecify.annotations.Nullable;
-/**
- * Converter for ADK Events to A2A Messages.
- *
- * **EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not
- * use in production code.
- */
+/** Converter for ADK Events to A2A Messages. */
public final class EventConverter {
- private static final Logger logger = LoggerFactory.getLogger(EventConverter.class);
+ public static final String ADK_TASK_ID_KEY = "adk_task_id";
+ public static final String ADK_CONTEXT_ID_KEY = "adk_context_id";
private EventConverter() {}
/**
- * Converts an ADK InvocationContext to an A2A Message.
+ * Returns the task ID from the event.
*
- *
It combines all the events in the session, plus the user content, converted into A2A Parts,
- * into a single A2A Message.
+ *
Task ID is stored in the event's custom metadata with the key {@link #ADK_TASK_ID_KEY}.
*
- *
If the context has no events, or no suitable content to build the message, an empty optional
- * is returned.
- *
- * @param context The ADK InvocationContext to convert.
- * @return The converted A2A Message.
+ * @param event The event to get the task ID from.
+ * @return The task ID, or an empty string if not found.
*/
- public static Optional convertEventsToA2AMessage(InvocationContext context) {
- if (context.session().events().isEmpty()) {
- logger.warn("No events in session, cannot convert to A2A message.");
- return Optional.empty();
- }
-
- ImmutableList.Builder> partsBuilder = ImmutableList.builder();
+ public static String taskId(Event event) {
+ return metadataValue(event, ADK_TASK_ID_KEY);
+ }
- context
- .session()
- .events()
- .forEach(
- event ->
- partsBuilder.addAll(
- contentToParts(event.content(), event.partial().orElse(false))));
- partsBuilder.addAll(contentToParts(context.userContent(), false));
+ /**
+ * Returns the context ID from the event.
+ *
+ * Context ID is stored in the event's custom metadata with the key {@link
+ * #ADK_CONTEXT_ID_KEY}.
+ *
+ * @param event The event to get the context ID from.
+ * @return The context ID, or an empty string if not found.
+ */
+ public static String contextId(Event event) {
+ return metadataValue(event, ADK_CONTEXT_ID_KEY);
+ }
- ImmutableList> parts = partsBuilder.build();
+ /**
+ * Returns the last user function call event from the list of events.
+ *
+ * @param events The list of events to find the user function call event from.
+ * @return The user function call event, or null if not found.
+ */
+ public static @Nullable Event findUserFunctionCall(List events) {
+ Event candidate = Iterables.getLast(events);
+ if (!candidate.author().equals("user")) {
+ return null;
+ }
+ FunctionResponse functionResponse = findUserFunctionResponse(candidate);
+ if (functionResponse == null || functionResponse.id().isEmpty()) {
+ return null;
+ }
+ for (int i = events.size() - 2; i >= 0; i--) {
+ Event event = events.get(i);
+ if (isUserFunctionCall(event, functionResponse.id().get())) {
+ return event;
+ }
+ }
+ return null;
+ }
- if (parts.isEmpty()) {
- logger.warn("No suitable content found to build A2A request message.");
- return Optional.empty();
+ private static @Nullable FunctionResponse findUserFunctionResponse(Event candidate) {
+ if (candidate.content().isEmpty() || candidate.content().get().parts().isEmpty()) {
+ return null;
}
+ return candidate.content().get().parts().get().stream()
+ .filter(part -> part.functionResponse().isPresent())
+ .findFirst()
+ .map(part -> part.functionResponse().get())
+ .orElse(null);
+ }
- return Optional.of(
- new Message.Builder()
- .messageId(UUID.randomUUID().toString())
- .parts(parts)
- .role(Message.Role.USER)
- .build());
+ private static boolean isUserFunctionCall(Event event, String functionResponseId) {
+ if (event.content().isEmpty()) {
+ return false;
+ }
+ return event.content().get().parts().get().stream()
+ .anyMatch(
+ part ->
+ part.functionCall().isPresent()
+ && part.functionCall()
+ .get()
+ .id()
+ .map(id -> id.equals(functionResponseId))
+ .orElse(false));
}
+ /**
+ * Converts a GenAI Content object to a list of A2A Parts.
+ *
+ * @param content The GenAI Content object to convert.
+ * @param isPartial Whether the content is partial.
+ * @return A list of A2A Parts.
+ */
public static ImmutableList> contentToParts(
Optional content, boolean isPartial) {
return content.flatMap(Content::parts).stream()
@@ -75,4 +126,80 @@ public static ImmutableList> contentToParts(
.map(part -> PartConverter.fromGenaiPart(part, isPartial))
.collect(toImmutableList());
}
+
+ /**
+ * Returns the parts from the context events that should be sent to the agent.
+ *
+ * All session events from the previous remote agent response (or the beginning of the session
+ * in case of the first agent invocation) are included into the A2A message. Events from other
+ * agents are presented as user messages and rephased as if a user was telling what happened in
+ * the session up to the point.
+ *
+ * @param context The invocation context to get the parts from.
+ * @return A list of A2A Parts.
+ */
+ public static ImmutableList> messagePartsFromContext(InvocationContext context) {
+ if (context.session().events().isEmpty()) {
+ return ImmutableList.of();
+ }
+ List events = context.session().events();
+ int lastResponseIndex = -1;
+ String contextId = "";
+ for (int i = events.size() - 1; i >= 0; i--) {
+ Event event = events.get(i);
+ if (event.author().equals(context.agent().name())) {
+ lastResponseIndex = i;
+ contextId = contextId(event);
+ break;
+ }
+ }
+ ImmutableList.Builder> partsBuilder = ImmutableList.builder();
+ for (int i = lastResponseIndex + 1; i < events.size(); i++) {
+ Event event = events.get(i);
+ if (!event.author().equals("user") && !event.author().equals(context.agent().name())) {
+ event = presentAsUserMessage(event, contextId);
+ }
+ contentToParts(event.content(), event.partial().orElse(false)).forEach(partsBuilder::add);
+ }
+ return partsBuilder.build();
+ }
+
+ private static Event presentAsUserMessage(Event event, String contextId) {
+ Event.Builder userEvent =
+ new Event.Builder().id(UUID.randomUUID().toString()).invocationId(contextId).author("user");
+ ImmutableList parts =
+ event.content().flatMap(Content::parts).stream()
+ .flatMap(Collection::stream)
+ // convert only non-thought parts to user message parts, skip thought parts as they are
+ // not meant to be shown to the user
+ .filter(part -> !part.thought().orElse(false))
+ .map(part -> PartConverter.remoteCallAsUserPart(event.author(), part))
+ .collect(toImmutableList());
+ if (parts.isEmpty()) {
+ return userEvent.build();
+ }
+ com.google.genai.types.Part forContext =
+ com.google.genai.types.Part.builder().text("For context:").build();
+ return userEvent
+ .content(
+ Content.builder()
+ .parts(
+ ImmutableList.builder()
+ .add(forContext)
+ .addAll(parts)
+ .build())
+ .build())
+ .build();
+ }
+
+ private static String metadataValue(Event event, String key) {
+ if (event.customMetadata().isEmpty()) {
+ return "";
+ }
+ return event.customMetadata().get().stream()
+ .filter(m -> m.key().map(k -> k.equals(key)).orElse(false))
+ .findFirst()
+ .flatMap(m -> m.stringValue())
+ .orElse("");
+ }
}
diff --git a/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java b/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
index 05125d170..61f24fa21 100644
--- a/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
+++ b/a2a/src/main/java/com/google/adk/a2a/converters/PartConverter.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.converters;
import static com.google.common.collect.ImmutableList.toImmutableList;
@@ -32,12 +47,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/**
- * Utility class for converting between Google GenAI Parts and A2A DataParts.
- *
- * **EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not
- * use in production code.
- */
+/** Utility class for converting between Google GenAI Parts and A2A DataParts. */
public final class PartConverter {
private static final Logger logger = LoggerFactory.getLogger(PartConverter.class);
@@ -68,13 +78,13 @@ public static Optional toTextPart(io.a2a.spec.Part> part) {
}
/** Convert an A2A JSON part into a Google GenAI part representation. */
- public static Optional toGenaiPart(io.a2a.spec.Part> a2aPart) {
+ public static com.google.genai.types.Part toGenaiPart(io.a2a.spec.Part> a2aPart) {
if (a2aPart == null) {
- return Optional.empty();
+ throw new IllegalArgumentException("A2A part cannot be null");
}
if (a2aPart instanceof TextPart textPart) {
- return Optional.of(com.google.genai.types.Part.builder().text(textPart.getText()).build());
+ return com.google.genai.types.Part.builder().text(textPart.getText()).build();
}
if (a2aPart instanceof FilePart filePart) {
@@ -85,56 +95,41 @@ public static Optional toGenaiPart(io.a2a.spec.Part
return convertDataPartToGenAiPart(dataPart);
}
- logger.warn("Unsupported A2A part type: {}", a2aPart.getClass());
- return Optional.empty();
+ throw new IllegalArgumentException("Unsupported A2A part type: " + a2aPart.getClass());
}
public static ImmutableList toGenaiParts(
List> a2aParts) {
- return a2aParts.stream()
- .map(PartConverter::toGenaiPart)
- .flatMap(Optional::stream)
- .collect(toImmutableList());
+ return a2aParts.stream().map(PartConverter::toGenaiPart).collect(toImmutableList());
}
- private static Optional convertFilePartToGenAiPart(
- FilePart filePart) {
+ private static com.google.genai.types.Part convertFilePartToGenAiPart(FilePart filePart) {
FileContent fileContent = filePart.getFile();
if (fileContent instanceof FileWithUri fileWithUri) {
- return Optional.of(
- com.google.genai.types.Part.builder()
- .fileData(
- FileData.builder()
- .fileUri(fileWithUri.uri())
- .mimeType(fileWithUri.mimeType())
- .build())
- .build());
+ return com.google.genai.types.Part.builder()
+ .fileData(
+ FileData.builder()
+ .fileUri(fileWithUri.uri())
+ .mimeType(fileWithUri.mimeType())
+ .build())
+ .build();
}
if (fileContent instanceof FileWithBytes fileWithBytes) {
String bytesString = fileWithBytes.bytes();
if (bytesString == null) {
- logger.warn("FileWithBytes missing byte content");
- return Optional.empty();
- }
- try {
- byte[] decoded = Base64.getDecoder().decode(bytesString);
- return Optional.of(
- com.google.genai.types.Part.builder()
- .inlineData(Blob.builder().data(decoded).mimeType(fileWithBytes.mimeType()).build())
- .build());
- } catch (IllegalArgumentException e) {
- logger.warn("Failed to decode base64 file content", e);
- return Optional.empty();
+ throw new GenAiFieldMissingException("FileWithBytes missing byte content");
}
+ byte[] decoded = Base64.getDecoder().decode(bytesString);
+ return com.google.genai.types.Part.builder()
+ .inlineData(Blob.builder().data(decoded).mimeType(fileWithBytes.mimeType()).build())
+ .build();
}
- logger.warn("Unsupported FilePart content: {}", fileContent.getClass());
- return Optional.empty();
+ throw new IllegalArgumentException("Unsupported FilePart content: " + fileContent.getClass());
}
- private static Optional convertDataPartToGenAiPart(
- DataPart dataPart) {
+ private static com.google.genai.types.Part convertDataPartToGenAiPart(DataPart dataPart) {
Map data =
Optional.ofNullable(dataPart.getData()).map(HashMap::new).orElseGet(HashMap::new);
Map metadata =
@@ -144,14 +139,12 @@ private static Optional convertDataPartToGenAiPart(
if ((data.containsKey(NAME_KEY) && data.containsKey(ARGS_KEY))
|| metadataType.equals(A2ADataPartMetadataType.FUNCTION_CALL.getType())) {
- String functionName = String.valueOf(data.getOrDefault(NAME_KEY, null));
- String functionId = String.valueOf(data.getOrDefault(ID_KEY, null));
+ String functionName = String.valueOf(data.getOrDefault(NAME_KEY, ""));
+ String functionId = String.valueOf(data.getOrDefault(ID_KEY, ""));
Map args = coerceToMap(data.get(ARGS_KEY));
- return Optional.of(
- com.google.genai.types.Part.builder()
- .functionCall(
- FunctionCall.builder().name(functionName).id(functionId).args(args).build())
- .build());
+ return com.google.genai.types.Part.builder()
+ .functionCall(FunctionCall.builder().name(functionName).id(functionId).args(args).build())
+ .build();
}
if ((data.containsKey(NAME_KEY) && data.containsKey(RESPONSE_KEY))
@@ -159,15 +152,14 @@ private static Optional convertDataPartToGenAiPart(
String functionName = String.valueOf(data.getOrDefault(NAME_KEY, ""));
String functionId = String.valueOf(data.getOrDefault(ID_KEY, ""));
Map response = coerceToMap(data.get(RESPONSE_KEY));
- return Optional.of(
- com.google.genai.types.Part.builder()
- .functionResponse(
- FunctionResponse.builder()
- .name(functionName)
- .id(functionId)
- .response(response)
- .build())
- .build());
+ return com.google.genai.types.Part.builder()
+ .functionResponse(
+ FunctionResponse.builder()
+ .name(functionName)
+ .id(functionId)
+ .response(response)
+ .build())
+ .build();
}
if ((data.containsKey(CODE_KEY) && data.containsKey(LANGUAGE_KEY))
@@ -175,13 +167,11 @@ private static Optional convertDataPartToGenAiPart(
String code = String.valueOf(data.getOrDefault(CODE_KEY, ""));
String language =
String.valueOf(
- data.getOrDefault(LANGUAGE_KEY, Language.Known.LANGUAGE_UNSPECIFIED.toString())
- .toString());
- return Optional.of(
- com.google.genai.types.Part.builder()
- .executableCode(
- ExecutableCode.builder().code(code).language(new Language(language)).build())
- .build());
+ data.getOrDefault(LANGUAGE_KEY, Language.Known.LANGUAGE_UNSPECIFIED.toString()));
+ return com.google.genai.types.Part.builder()
+ .executableCode(
+ ExecutableCode.builder().code(code).language(new Language(language)).build())
+ .build();
}
if ((data.containsKey(OUTCOME_KEY) && data.containsKey(OUTPUT_KEY))
@@ -189,22 +179,17 @@ private static Optional convertDataPartToGenAiPart(
String outcome =
String.valueOf(data.getOrDefault(OUTCOME_KEY, Outcome.Known.OUTCOME_OK).toString());
String output = String.valueOf(data.getOrDefault(OUTPUT_KEY, ""));
- return Optional.of(
- com.google.genai.types.Part.builder()
- .codeExecutionResult(
- CodeExecutionResult.builder()
- .outcome(new Outcome(outcome))
- .output(output)
- .build())
- .build());
+ return com.google.genai.types.Part.builder()
+ .codeExecutionResult(
+ CodeExecutionResult.builder().outcome(new Outcome(outcome)).output(output).build())
+ .build();
}
try {
String json = objectMapper.writeValueAsString(data);
- return Optional.of(com.google.genai.types.Part.builder().text(json).build());
+ return com.google.genai.types.Part.builder().text(json).build();
} catch (JsonProcessingException e) {
- logger.warn("Failed to serialize DataPart payload", e);
- return Optional.empty();
+ throw new IllegalArgumentException("Failed to serialize DataPart payload", e);
}
}
@@ -374,6 +359,50 @@ private static FilePart filePartToA2A(Part part, ImmutableMap.BuilderEvents are rephrased as if a user was telling what happened in the session up to the point.
+ * E.g.
+ *
+ * {@code
+ * For context:
+ * User said: Now help me with Z
+ * Agent A said: Agent B can help you with it!
+ * Agent B said: Agent C might know better.*
+ * }
+ *
+ * @param author The author of the part.
+ * @param part The part to convert.
+ * @return The converted part.
+ */
+ public static Part remoteCallAsUserPart(String author, Part part) {
+ if (part.text().isPresent()) {
+ String partText = String.format("[%s] said: %s", author, part.text().get());
+ return Part.builder().text(partText).build();
+ } else if (part.functionCall().isPresent()) {
+ FunctionCall functionCall = part.functionCall().get();
+ String partText =
+ String.format(
+ "[%s] called tool %s with parameters: %s",
+ author,
+ functionCall.name().orElse(""),
+ functionCall.args().orElse(ImmutableMap.of()));
+ return Part.builder().text(partText).build();
+ } else if (part.functionResponse().isPresent()) {
+ FunctionResponse functionResponse = part.functionResponse().get();
+ String partText =
+ String.format(
+ "[%s] %s tool returned result: %s",
+ author,
+ functionResponse.name().orElse(""),
+ functionResponse.response().orElse(ImmutableMap.of()));
+ return Part.builder().text(partText).build();
+ } else {
+ return part;
+ }
+ }
+
@SuppressWarnings("unchecked") // safe conversion from objectMapper.readValue
private static Map coerceToMap(Object value) {
if (value == null) {
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 ccbb1b9cf..503432a30 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
@@ -1,6 +1,23 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.converters;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.collect.Streams.zip;
import com.google.adk.agents.InvocationContext;
import com.google.adk.events.Event;
@@ -14,6 +31,7 @@
import io.a2a.client.TaskEvent;
import io.a2a.client.TaskUpdateEvent;
import io.a2a.spec.Artifact;
+import io.a2a.spec.DataPart;
import io.a2a.spec.Message;
import io.a2a.spec.Task;
import io.a2a.spec.TaskArtifactUpdateEvent;
@@ -21,18 +39,14 @@
import io.a2a.spec.TaskStatusUpdateEvent;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/**
- * Utility for converting ADK events to A2A spec messages (and back).
- *
- * **EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not
- * use in production code.
- */
+/** Utility for converting ADK events to A2A spec messages (and back). */
public final class ResponseConverter {
private static final Logger logger = LoggerFactory.getLogger(ResponseConverter.class);
private static final ImmutableSet PENDING_STATES =
@@ -60,6 +74,14 @@ public static Optional clientEventToEvent(
throw new IllegalArgumentException("Unsupported ClientEvent type: " + event.getClass());
}
+ private static boolean isPartial(Map metadata) {
+ if (metadata == null) {
+ return false;
+ }
+ return Objects.equals(
+ metadata.getOrDefault(PartConverter.A2A_DATA_PART_METADATA_IS_PARTIAL_KEY, false), true);
+ }
+
/**
* Converts a A2A {@link TaskUpdateEvent} to an ADK {@link Event}, if applicable. Returns null if
* the event is not a final update for TaskArtifactUpdateEvent or if the message is empty for
@@ -75,8 +97,15 @@ private static Optional handleTaskUpdate(
boolean isAppend = Objects.equals(artifactEvent.isAppend(), true);
boolean isLastChunk = Objects.equals(artifactEvent.isLastChunk(), true);
+ if (isLastChunk && isPartial(artifactEvent.getMetadata())) {
+ return Optional.empty();
+ }
+
Event eventPart = artifactToEvent(artifactEvent.getArtifact(), context);
- eventPart.setPartial(Optional.of(isAppend || !isLastChunk));
+ if (eventPart.content().flatMap(Content::parts).orElse(ImmutableList.of()).isEmpty()) {
+ return Optional.empty();
+ }
+ eventPart.setPartial(isAppend || !isLastChunk);
// append=true, lastChunk=false: emit as partial, update aggregation
// append=false, lastChunk=false: emit as partial, reset aggregation
// append=true, lastChunk=true: emit as partial, update aggregation and emit as non-partial
@@ -105,9 +134,8 @@ private static Optional handleTaskUpdate(
.map(builder -> builder.turnComplete(true))
.map(builder -> builder.partial(false))
.map(Event.Builder::build);
- } else {
- return messageEvent;
}
+ return messageEvent;
}
throw new IllegalArgumentException(
"Unsupported TaskUpdateEvent type: " + updateEvent.getClass());
@@ -115,16 +143,12 @@ private static Optional handleTaskUpdate(
/** Converts an artifact to an ADK event. */
public static Event artifactToEvent(Artifact artifact, InvocationContext invocationContext) {
- Message message =
- new Message.Builder().role(Message.Role.AGENT).parts(artifact.parts()).build();
- return messageToEvent(message, invocationContext);
- }
-
- /** Converts an A2A message back to ADK events. */
- public static Event messageToEvent(Message message, InvocationContext invocationContext) {
- return remoteAgentEventBuilder(invocationContext)
- .content(fromModelParts(PartConverter.toGenaiParts(message.getParts())))
- .build();
+ Event.Builder eventBuilder = remoteAgentEventBuilder(invocationContext);
+ ImmutableList genaiParts = PartConverter.toGenaiParts(artifact.parts());
+ eventBuilder
+ .content(fromModelParts(genaiParts))
+ .longRunningToolIds(getLongRunningToolIds(artifact.parts(), genaiParts));
+ return eventBuilder.build();
}
/** Converts an A2A message for a failed task to ADK event filling in the error message. */
@@ -137,6 +161,13 @@ public static Event messageToFailedEvent(Message message, InvocationContext invo
return builder.build();
}
+ /** Converts an A2A message back to ADK events. */
+ public static Event messageToEvent(Message message, InvocationContext invocationContext) {
+ return remoteAgentEventBuilder(invocationContext)
+ .content(fromModelParts(PartConverter.toGenaiParts(message.getParts())))
+ .build();
+ }
+
/**
* Converts an A2A message back to ADK events. For streaming task in pending state it sets the
* thought field to true, to mark them as thought updates.
@@ -158,25 +189,71 @@ public static Event messageToEvent(
* If none of these are present, an empty event is returned.
*/
public static Event taskToEvent(Task task, InvocationContext invocationContext) {
- Message taskMessage = null;
-
- if (!task.getArtifacts().isEmpty()) {
- taskMessage =
- new Message.Builder()
- .messageId("")
- .role(Message.Role.AGENT)
- .parts(Iterables.getLast(task.getArtifacts()).parts())
- .build();
- } else if (task.getStatus().message() != null) {
- taskMessage = task.getStatus().message();
- } else if (!task.getHistory().isEmpty()) {
- taskMessage = Iterables.getLast(task.getHistory());
+ ImmutableList.Builder genaiParts = ImmutableList.builder();
+ ImmutableSet.Builder longRunningToolIds = ImmutableSet.builder();
+
+ for (Artifact artifact : task.getArtifacts()) {
+ ImmutableList converted = PartConverter.toGenaiParts(artifact.parts());
+ longRunningToolIds.addAll(getLongRunningToolIds(artifact.parts(), converted));
+ genaiParts.addAll(converted);
}
- if (taskMessage != null) {
- return messageToEvent(taskMessage, invocationContext);
+ Event.Builder eventBuilder = remoteAgentEventBuilder(invocationContext);
+
+ if (task.getStatus().message() != null) {
+ ImmutableList msgParts =
+ PartConverter.toGenaiParts(task.getStatus().message().getParts());
+ longRunningToolIds.addAll(
+ getLongRunningToolIds(task.getStatus().message().getParts(), msgParts));
+ if (task.getStatus().state() == TaskState.FAILED
+ && msgParts.size() == 1
+ && msgParts.get(0).text().isPresent()) {
+ eventBuilder.errorMessage(msgParts.get(0).text().get());
+ } else {
+ genaiParts.addAll(msgParts);
+ }
+ }
+
+ ImmutableList finalParts = genaiParts.build();
+ boolean isFinal =
+ task.getStatus().state().isFinal() || task.getStatus().state() == TaskState.INPUT_REQUIRED;
+
+ if (finalParts.isEmpty() && !isFinal) {
+ return emptyEvent(invocationContext);
}
- return emptyEvent(invocationContext);
+ if (!finalParts.isEmpty()) {
+ eventBuilder.content(fromModelParts(finalParts));
+ }
+ if (task.getStatus().state() == TaskState.INPUT_REQUIRED) {
+ eventBuilder.longRunningToolIds(longRunningToolIds.build());
+ }
+ eventBuilder.turnComplete(isFinal);
+ return eventBuilder.build();
+ }
+
+ private static ImmutableSet getLongRunningToolIds(
+ List> parts, List convertedParts) {
+ return zip(
+ parts.stream(),
+ convertedParts.stream(),
+ (part, convertedPart) -> {
+ if (!(part instanceof DataPart dataPart)) {
+ return Optional.empty();
+ }
+ Object isLongRunning =
+ dataPart
+ .getMetadata()
+ .get(PartConverter.A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY);
+ if (!Objects.equals(isLongRunning, true)) {
+ return Optional.empty();
+ }
+ if (convertedPart.functionCall().isEmpty()) {
+ return Optional.empty();
+ }
+ return convertedPart.functionCall().get().id();
+ })
+ .flatMap(Optional::stream)
+ .collect(toImmutableSet());
}
private static Event emptyEvent(InvocationContext invocationContext) {
diff --git a/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java b/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java
index b7b4e9953..7252cdec1 100644
--- a/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java
+++ b/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutor.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.executor;
import static java.util.Objects.requireNonNull;
@@ -44,12 +59,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/**
- * Implementation of the A2A AgentExecutor interface that uses ADK to execute agent tasks.
- *
- * **EXPERIMENTAL:** Subject to change, rename, or removal in any future patch release. Do not
- * use in production code.
- */
+/** Implementation of the A2A AgentExecutor interface that uses ADK to execute agent tasks. */
public class AgentExecutor implements io.a2a.server.agentexecution.AgentExecutor {
private static final Logger logger = LoggerFactory.getLogger(AgentExecutor.class);
private static final String USER_ID_PREFIX = "A2A_USER_";
diff --git a/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutorConfig.java b/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutorConfig.java
index ba0177dc4..3ee8656d2 100644
--- a/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutorConfig.java
+++ b/a2a/src/main/java/com/google/adk/a2a/executor/AgentExecutorConfig.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.executor;
import com.google.adk.a2a.executor.Callbacks.AfterEventCallback;
diff --git a/a2a/src/main/java/com/google/adk/a2a/executor/Callbacks.java b/a2a/src/main/java/com/google/adk/a2a/executor/Callbacks.java
index 666f1d8a0..3483c527f 100644
--- a/a2a/src/main/java/com/google/adk/a2a/executor/Callbacks.java
+++ b/a2a/src/main/java/com/google/adk/a2a/executor/Callbacks.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2026 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.adk.a2a.executor;
import com.google.adk.events.Event;
diff --git a/a2a/src/test/java/com/google/adk/a2a/RemoteA2AAgentTest.java b/a2a/src/test/java/com/google/adk/a2a/agent/RemoteA2AAgentTest.java
similarity index 99%
rename from a2a/src/test/java/com/google/adk/a2a/RemoteA2AAgentTest.java
rename to a2a/src/test/java/com/google/adk/a2a/agent/RemoteA2AAgentTest.java
index 87eaa2321..b1ffa248a 100644
--- a/a2a/src/test/java/com/google/adk/a2a/RemoteA2AAgentTest.java
+++ b/a2a/src/test/java/com/google/adk/a2a/agent/RemoteA2AAgentTest.java
@@ -1,4 +1,4 @@
-package com.google.adk.a2a;
+package com.google.adk.a2a.agent;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -412,10 +412,11 @@ public void runAsync_constructsRequestWithHistory() {
.sendMessage(messageCaptor.capture(), any(List.class), any(Consumer.class), any());
Message message = messageCaptor.getValue();
assertThat(message.getRole()).isEqualTo(Message.Role.USER);
- assertThat(message.getParts()).hasSize(3);
+ assertThat(message.getParts()).hasSize(4);
assertThat(((TextPart) message.getParts().get(0)).getText()).isEqualTo("hello");
- assertThat(((TextPart) message.getParts().get(1)).getText()).isEqualTo("hi");
- assertThat(((TextPart) message.getParts().get(2)).getText()).isEqualTo("how are you?");
+ assertThat(((TextPart) message.getParts().get(1)).getText()).isEqualTo("For context:");
+ assertThat(((TextPart) message.getParts().get(2)).getText()).isEqualTo("[model] said: hi");
+ assertThat(((TextPart) message.getParts().get(3)).getText()).isEqualTo("how are you?");
}
@Test
diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java
index 8d460c457..207019199 100644
--- a/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java
+++ b/a2a/src/test/java/com/google/adk/a2a/converters/EventConverterTest.java
@@ -4,23 +4,17 @@
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.InvocationContext;
-import com.google.adk.artifacts.InMemoryArtifactService;
import com.google.adk.events.Event;
-import com.google.adk.plugins.PluginManager;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Content;
+import com.google.genai.types.CustomMetadata;
import com.google.genai.types.FunctionCall;
import com.google.genai.types.FunctionResponse;
import com.google.genai.types.Part;
-import io.a2a.spec.DataPart;
-import io.a2a.spec.Message;
import io.a2a.spec.TextPart;
import io.reactivex.rxjava3.core.Flowable;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,101 +24,180 @@
public final class EventConverterTest {
@Test
- public void convertEventsToA2AMessage_preservesFunctionCallAndResponseParts() {
- // Arrange session events: user text, function call, function response.
- Part userTextPart = Part.builder().text("Roll a die").build();
- Event userEvent =
+ public void testTaskId() {
+ Event e =
+ Event.builder()
+ .customMetadata(
+ ImmutableList.of(
+ CustomMetadata.builder()
+ .key(EventConverter.ADK_TASK_ID_KEY)
+ .stringValue("task-123")
+ .build()))
+ .build();
+ assertThat(EventConverter.taskId(e)).isEqualTo("task-123");
+ }
+
+ @Test
+ public void testTaskId_empty() {
+ Event e = Event.builder().build();
+ assertThat(EventConverter.taskId(e)).isEmpty();
+ }
+
+ @Test
+ public void testContextId() {
+ Event e =
+ Event.builder()
+ .customMetadata(
+ ImmutableList.of(
+ CustomMetadata.builder()
+ .key(EventConverter.ADK_CONTEXT_ID_KEY)
+ .stringValue("context-456")
+ .build()))
+ .build();
+ assertThat(EventConverter.contextId(e)).isEqualTo("context-456");
+ }
+
+ @Test
+ public void testContextId_empty() {
+ Event e = Event.builder().build();
+ assertThat(EventConverter.contextId(e)).isEmpty();
+ }
+
+ @Test
+ public void testFindUserFunctionCall_success() {
+ Event agentEvent = Event.builder().author("agent").build();
+ FunctionCall fc = FunctionCall.builder().name("my-func").id("fc-id").build();
+ Event userEventWithCall =
Event.builder()
- .id("event-user")
.author("user")
- .content(Content.builder().role("user").parts(ImmutableList.of(userTextPart)).build())
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().functionCall(fc).build()))
+ .build())
.build();
- Part functionCallPart =
- Part.builder()
- .functionCall(
- FunctionCall.builder()
- .name("roll_die")
- .id("adk-call-1")
- .args(ImmutableMap.of("sides", 6))
+ FunctionResponse fr = FunctionResponse.builder().name("my-func").id("fc-id").build();
+ Event userEventWithResponse =
+ Event.builder()
+ .author("user")
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().functionResponse(fr).build()))
.build())
.build();
- Event callEvent =
+
+ ImmutableList events =
+ ImmutableList.of(userEventWithCall, agentEvent, userEventWithResponse);
+ assertThat(EventConverter.findUserFunctionCall(events)).isEqualTo(userEventWithCall);
+ }
+
+ @Test
+ public void testFindUserFunctionCall_noMatchingCall() {
+ Event agentEvent = Event.builder().author("agent").build();
+ FunctionCall fc = FunctionCall.builder().name("my-func").id("other-id").build();
+ Event userEventWithCall =
Event.builder()
- .id("event-call")
- .author("root_agent")
+ .author("user")
.content(
Content.builder()
- .role("assistant")
- .parts(ImmutableList.of(functionCallPart))
+ .parts(ImmutableList.of(Part.builder().functionCall(fc).build()))
.build())
.build();
- Part functionResponsePart =
- Part.builder()
- .functionResponse(
- FunctionResponse.builder()
- .name("roll_die")
- .id("adk-call-1")
- .response(ImmutableMap.of("result", 3))
+ FunctionResponse fr = FunctionResponse.builder().name("my-func").id("fc-id").build();
+ Event userEventWithResponse =
+ Event.builder()
+ .author("user")
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().functionResponse(fr).build()))
+ .build())
+ .build();
+
+ ImmutableList events =
+ ImmutableList.of(userEventWithCall, agentEvent, userEventWithResponse);
+ assertThat(EventConverter.findUserFunctionCall(events)).isNull();
+ }
+
+ @Test
+ public void testFindUserFunctionCall_lastEventNotUser() {
+ Event agentEvent = Event.builder().author("agent").build();
+ FunctionCall fc = FunctionCall.builder().name("my-func").id("fc-id").build();
+ Event userEventWithCall =
+ Event.builder()
+ .author("user")
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().functionCall(fc).build()))
.build())
.build();
- Event responseEvent =
+ FunctionResponse fr = FunctionResponse.builder().name("my-func").id("fc-id").build();
+ // Last event is not a user event, so should return null.
+ Event agentEventWithResponse =
Event.builder()
- .id("event-response")
- .author("roll_agent")
+ .author("agent")
.content(
Content.builder()
- .role("tool")
- .parts(ImmutableList.of(functionResponsePart))
+ .parts(ImmutableList.of(Part.builder().functionResponse(fr).build()))
.build())
.build();
- List events = new ArrayList<>(ImmutableList.of(userEvent, callEvent, responseEvent));
- Session session =
- Session.builder("session-1").appName("demo").userId("user").events(events).build();
+ ImmutableList events =
+ ImmutableList.of(userEventWithCall, agentEvent, agentEventWithResponse);
- InvocationContext context =
+ assertThat(EventConverter.findUserFunctionCall(events)).isNull();
+ }
+
+ @Test
+ public void testContentToParts() {
+ Part textPart = Part.builder().text("hello").build();
+ Content content = Content.builder().parts(ImmutableList.of(textPart)).build();
+ ImmutableList> list =
+ EventConverter.contentToParts(Optional.of(content), false);
+ assertThat(list).hasSize(1);
+ assertThat(((TextPart) list.get(0)).getText()).isEqualTo("hello");
+ }
+
+ @Test
+ public void testMessagePartsFromContext() {
+ Session session =
+ Session.builder("session1")
+ .events(
+ ImmutableList.of(
+ Event.builder()
+ .author("user")
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().text("hello").build()))
+ .build())
+ .build(),
+ Event.builder()
+ .author("test_agent")
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().text("hi").build()))
+ .build())
+ .build(),
+ Event.builder()
+ .author("other_agent")
+ .content(
+ Content.builder()
+ .parts(ImmutableList.of(Part.builder().text("hey").build()))
+ .build())
+ .build()))
+ .build();
+ BaseAgent agent = new TestAgent();
+ InvocationContext ctx =
InvocationContext.builder()
- .sessionService(new InMemorySessionService())
- .artifactService(new InMemoryArtifactService())
- .pluginManager(new PluginManager())
- .invocationId("invocation-1")
- .agent(new TestAgent())
.session(session)
- .userContent(
- Content.builder().role("user").parts(ImmutableList.of(userTextPart)).build())
- .endInvocation(false)
+ .sessionService(new InMemorySessionService())
+ .agent(agent)
.build();
+ ImmutableList> parts = EventConverter.messagePartsFromContext(ctx);
- // Act
- Optional maybeMessage = EventConverter.convertEventsToA2AMessage(context);
-
- // Assert
- assertThat(maybeMessage).isPresent();
- Message message = maybeMessage.get();
- assertThat(message.getParts()).hasSize(4);
- assertThat(message.getParts().get(0)).isInstanceOf(TextPart.class);
- assertThat(message.getParts().get(1)).isInstanceOf(DataPart.class);
- assertThat(message.getParts().get(2)).isInstanceOf(DataPart.class);
- assertThat(message.getParts().get(3)).isInstanceOf(TextPart.class);
-
- DataPart callDataPart = (DataPart) message.getParts().get(1);
- assertThat(callDataPart.getMetadata().get(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY))
- .isEqualTo(A2ADataPartMetadataType.FUNCTION_CALL.getType());
- assertThat(callDataPart.getData()).containsEntry("name", "roll_die");
- assertThat(callDataPart.getData()).containsEntry("id", "adk-call-1");
- assertThat(callDataPart.getData()).containsEntry("args", ImmutableMap.of("sides", 6));
-
- DataPart responseDataPart = (DataPart) message.getParts().get(2);
- assertThat(responseDataPart.getMetadata().get(PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY))
- .isEqualTo(A2ADataPartMetadataType.FUNCTION_RESPONSE.getType());
- assertThat(responseDataPart.getData()).containsEntry("name", "roll_die");
- assertThat(responseDataPart.getData()).containsEntry("id", "adk-call-1");
- assertThat(responseDataPart.getData()).containsEntry("response", ImmutableMap.of("result", 3));
-
- TextPart lastTextPart = (TextPart) message.getParts().get(3);
- assertThat(lastTextPart.getText()).isEqualTo("Roll a die");
+ assertThat(parts).hasSize(2);
+ assertThat(((TextPart) parts.get(0)).getText()).isEqualTo("For context:");
+ assertThat(((TextPart) parts.get(1)).getText()).isEqualTo("[other_agent] said: hey");
}
private static final class TestAgent extends BaseAgent {
diff --git a/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java b/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java
index 8e8982ffa..d93466dd2 100644
--- a/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java
+++ b/a2a/src/test/java/com/google/adk/a2a/converters/PartConverterTest.java
@@ -18,7 +18,6 @@
import io.a2a.spec.FileWithUri;
import io.a2a.spec.TextPart;
import java.util.Base64;
-import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -27,29 +26,27 @@
public class PartConverterTest {
@Test
- public void toGenaiPart_withNullPart_returnsEmpty() {
- assertThat(PartConverter.toGenaiPart(null)).isEmpty();
+ public void toGenaiPart_withNullPart_throwsException() {
+ assertThrows(IllegalArgumentException.class, () -> PartConverter.toGenaiPart(null));
}
@Test
public void toGenaiPart_withTextPart_returnsGenaiTextPart() {
TextPart textPart = new TextPart("Hello");
- Optional result = PartConverter.toGenaiPart(textPart);
+ Part result = PartConverter.toGenaiPart(textPart);
- assertThat(result).isPresent();
- assertThat(result.get().text()).hasValue("Hello");
+ assertThat(result.text()).hasValue("Hello");
}
@Test
public void toGenaiPart_withFilePartUri_returnsGenaiFilePart() {
FilePart filePart = new FilePart(new FileWithUri("text/plain", "file.txt", "http://file.txt"));
- Optional result = PartConverter.toGenaiPart(filePart);
+ Part result = PartConverter.toGenaiPart(filePart);
- assertThat(result).isPresent();
- assertThat(result.get().fileData()).isPresent();
- FileData fileData = result.get().fileData().get();
+ assertThat(result.fileData()).isPresent();
+ FileData fileData = result.fileData().get();
assertThat(fileData.mimeType()).hasValue("text/plain");
assertThat(fileData.fileUri()).hasValue("http://file.txt");
}
@@ -60,26 +57,25 @@ public void toGenaiPart_withFilePartBytes_returnsGenaiBlobPart() {
String encoded = Base64.getEncoder().encodeToString(bytes);
FilePart filePart = new FilePart(new FileWithBytes("text/plain", "file.txt", encoded));
- Optional result = PartConverter.toGenaiPart(filePart);
+ Part result = PartConverter.toGenaiPart(filePart);
- assertThat(result).isPresent();
- assertThat(result.get().inlineData()).isPresent();
- Blob blob = result.get().inlineData().get();
+ assertThat(result.inlineData()).isPresent();
+ Blob blob = result.inlineData().get();
assertThat(blob.mimeType()).hasValue("text/plain");
assertThat(blob.data().get()).isEqualTo(bytes);
}
@Test
- public void toGenaiPart_withFilePartBytes_handlesNullBytes() {
+ public void toGenaiPart_withFilePartBytes_handlesNullBytes_throwsException() {
FilePart filePart = new FilePart(new FileWithBytes("text/plain", "file.txt", null));
- assertThat(PartConverter.toGenaiPart(filePart)).isEmpty();
+ assertThrows(GenAiFieldMissingException.class, () -> PartConverter.toGenaiPart(filePart));
}
@Test
public void toGenaiPart_withFilePartBytes_handlesInvalidBase64() {
FilePart filePart =
new FilePart(new FileWithBytes("text/plain", "file.txt", "invalid-base64!"));
- assertThat(PartConverter.toGenaiPart(filePart)).isEmpty();
+ assertThrows(IllegalArgumentException.class, () -> PartConverter.toGenaiPart(filePart));
}
@Test
@@ -93,11 +89,10 @@ public void toGenaiPart_withDataPartFunctionCall_returnsGenaiFunctionCallPart()
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
A2ADataPartMetadataType.FUNCTION_CALL.getType()));
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().functionCall()).isPresent();
- FunctionCall functionCall = result.get().functionCall().get();
+ assertThat(result.functionCall()).isPresent();
+ FunctionCall functionCall = result.functionCall().get();
assertThat(functionCall.name()).hasValue("func");
assertThat(functionCall.id()).hasValue("1");
assertThat(functionCall.args()).hasValue(ImmutableMap.of());
@@ -109,11 +104,10 @@ public void toGenaiPart_withDataPartFunctionCallByNameAndArgs_returnsGenaiFuncti
ImmutableMap.of("name", "func", "id", "1", "args", ImmutableMap.of("param", "value"));
DataPart dataPart = new DataPart(data, null);
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().functionCall()).isPresent();
- FunctionCall functionCall = result.get().functionCall().get();
+ assertThat(result.functionCall()).isPresent();
+ FunctionCall functionCall = result.functionCall().get();
assertThat(functionCall.name()).hasValue("func");
assertThat(functionCall.id()).hasValue("1");
assertThat(functionCall.args()).hasValue(ImmutableMap.of("param", "value"));
@@ -130,11 +124,10 @@ public void toGenaiPart_withDataPartFunctionResponse_returnsGenaiFunctionRespons
PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
A2ADataPartMetadataType.FUNCTION_RESPONSE.getType()));
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().functionResponse()).isPresent();
- FunctionResponse functionResponse = result.get().functionResponse().get();
+ assertThat(result.functionResponse()).isPresent();
+ FunctionResponse functionResponse = result.functionResponse().get();
assertThat(functionResponse.name()).hasValue("func");
assertThat(functionResponse.id()).hasValue("1");
assertThat(functionResponse.response()).hasValue(ImmutableMap.of());
@@ -147,11 +140,10 @@ public void toGenaiPart_withDataPartFunctionResponse_returnsGenaiFunctionRespons
ImmutableMap.of("name", "func", "id", "1", "response", ImmutableMap.of("result", "value"));
DataPart dataPart = new DataPart(data, null);
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().functionResponse()).isPresent();
- FunctionResponse functionResponse = result.get().functionResponse().get();
+ assertThat(result.functionResponse()).isPresent();
+ FunctionResponse functionResponse = result.functionResponse().get();
assertThat(functionResponse.name()).hasValue("func");
assertThat(functionResponse.id()).hasValue("1");
assertThat(functionResponse.response()).hasValue(ImmutableMap.of("result", "value"));
@@ -162,10 +154,9 @@ public void toGenaiPart_withOtherDataPart_returnsGenaiTextPartWithJson() {
ImmutableMap data = ImmutableMap.of("key", "value");
DataPart dataPart = new DataPart(data, null);
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().text()).hasValue("{\"key\":\"value\"}");
+ assertThat(result.text()).hasValue("{\"key\":\"value\"}");
}
@Test
@@ -293,11 +284,10 @@ public void toGenaiPart_dataPartWithEmptyStringCoercedToEmptyMap() {
ImmutableMap data = ImmutableMap.of("name", "func", "id", "1", "args", "");
DataPart dataPart = new DataPart(data, null);
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().functionCall()).isPresent();
- assertThat(result.get().functionCall().get().args()).hasValue(ImmutableMap.of());
+ assertThat(result.functionCall()).isPresent();
+ assertThat(result.functionCall().get().args()).hasValue(ImmutableMap.of());
}
@Test
@@ -305,10 +295,9 @@ public void toGenaiPart_dataPartWithNonMapCoercedToMap() {
ImmutableMap data = ImmutableMap.of("name", "func", "id", "1", "args", 123);
DataPart dataPart = new DataPart(data, null);
- Optional result = PartConverter.toGenaiPart(dataPart);
+ Part result = PartConverter.toGenaiPart(dataPart);
- assertThat(result).isPresent();
- assertThat(result.get().functionCall()).isPresent();
- assertThat(result.get().functionCall().get().args()).hasValue(ImmutableMap.of("value", 123));
+ assertThat(result.functionCall()).isPresent();
+ assertThat(result.functionCall().get().args()).hasValue(ImmutableMap.of("value", 123));
}
}
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 5378bdd7b..d84dc42cd 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
@@ -11,10 +11,12 @@
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Content;
import io.a2a.client.MessageEvent;
import io.a2a.client.TaskUpdateEvent;
import io.a2a.spec.Artifact;
+import io.a2a.spec.DataPart;
import io.a2a.spec.Message;
import io.a2a.spec.Task;
import io.a2a.spec.TaskArtifactUpdateEvent;
@@ -144,6 +146,81 @@ public void taskToEvent_withNoMessage_returnsEmptyEvent() {
assertThat(event.invocationId()).isEqualTo(invocationContext.invocationId());
}
+ @Test
+ public void taskToEvent_withInputRequired_parsesLongRunningToolIds() {
+ ImmutableMap data =
+ ImmutableMap.of("name", "myTool", "id", "call_123", "args", ImmutableMap.of());
+ ImmutableMap metadata =
+ ImmutableMap.of(
+ PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
+ "function_call",
+ PartConverter.A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY,
+ true);
+ DataPart dataPart = new DataPart(data, metadata);
+ ImmutableMap statusData =
+ ImmutableMap.of("name", "messageTools", "id", "msg_123", "args", ImmutableMap.of());
+ ImmutableMap statusMetadata =
+ ImmutableMap.of(
+ PartConverter.A2A_DATA_PART_METADATA_TYPE_KEY,
+ "function_call",
+ PartConverter.A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY,
+ 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.INPUT_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");
+ }
+
+ @Test
+ public void taskToEvent_withFailedState_setsErrorCode() {
+ Message statusMessage =
+ new Message.Builder()
+ .role(Message.Role.AGENT)
+ .parts(ImmutableList.of(new TextPart("Task failed")))
+ .build();
+ TaskStatus status = new TaskStatus(TaskState.FAILED, statusMessage, null);
+ Task task = testTask().status(status).artifacts(ImmutableList.of()).build();
+
+ Event event = ResponseConverter.taskToEvent(task, invocationContext);
+ assertThat(event).isNotNull();
+ assertThat(event.errorMessage()).hasValue("Task failed");
+ }
+
+ @Test
+ public void taskToEvent_withFinalEvent_returnsEmptyEvent() {
+ TaskStatus status = new TaskStatus(TaskState.COMPLETED);
+ Task task = testTask().status(status).artifacts(ImmutableList.of()).build();
+
+ Event event = ResponseConverter.taskToEvent(task, invocationContext);
+ assertThat(event).isNotNull();
+ assertThat(event.invocationId()).isEqualTo(invocationContext.invocationId());
+ assertThat(event.turnComplete()).hasValue(true);
+ assertThat(event.content().flatMap(Content::parts).orElse(ImmutableList.of())).isEmpty();
+ }
+
+ @Test
+ public void taskToEvent_withEmptyParts_returnsEmptyEvent() {
+ TaskStatus status = new TaskStatus(TaskState.SUBMITTED);
+ Task task = testTask().status(status).artifacts(ImmutableList.of()).build();
+
+ Event event = ResponseConverter.taskToEvent(task, invocationContext);
+ assertThat(event).isNotNull();
+ assertThat(event.invocationId()).isEqualTo(invocationContext.invocationId());
+ assertThat(event.content()).isPresent();
+ assertThat(event.content().get().parts().orElse(ImmutableList.of())).isEmpty();
+ }
+
@Test
public void clientEventToEvent_withTaskUpdateEventAndThought_returnsThoughtEvent() {
Message statusMessage =
diff --git a/a2a/src/test/java/com/google/adk/a2a/executor/AgentExecutorTest.java b/a2a/src/test/java/com/google/adk/a2a/executor/AgentExecutorTest.java
index 5570f40d0..647aaf21f 100644
--- a/a2a/src/test/java/com/google/adk/a2a/executor/AgentExecutorTest.java
+++ b/a2a/src/test/java/com/google/adk/a2a/executor/AgentExecutorTest.java
@@ -361,7 +361,7 @@ private RequestContext createRequestContext() {
public void process_statefulAggregation_tracksArtifactIdAndAppendForAuthor() {
Event partial1 =
Event.builder()
- .partial(Optional.of(true))
+ .partial(true)
.author("agent_author")
.content(
Content.builder()
@@ -370,7 +370,7 @@ public void process_statefulAggregation_tracksArtifactIdAndAppendForAuthor() {
.build();
Event partial2 =
Event.builder()
- .partial(Optional.of(true))
+ .partial(true)
.author("agent_author")
.content(
Content.builder()
@@ -379,7 +379,7 @@ public void process_statefulAggregation_tracksArtifactIdAndAppendForAuthor() {
.build();
Event finalEvent =
Event.builder()
- .partial(Optional.of(false))
+ .partial(false)
.author("agent_author")
.content(
Content.builder()
diff --git a/contrib/firestore-session-service/pom.xml b/contrib/firestore-session-service/pom.xml
index 0b421e361..f0e1f0d0e 100644
--- a/contrib/firestore-session-service/pom.xml
+++ b/contrib/firestore-session-service/pom.xml
@@ -20,7 +20,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
../../pom.xml
diff --git a/contrib/langchain4j/pom.xml b/contrib/langchain4j/pom.xml
index 75dc8b58e..a180c2e69 100644
--- a/contrib/langchain4j/pom.xml
+++ b/contrib/langchain4j/pom.xml
@@ -20,7 +20,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
../../pom.xml
diff --git a/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/RunLoop.java b/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/RunLoop.java
index 04a2aa585..2dca5c49c 100644
--- a/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/RunLoop.java
+++ b/contrib/langchain4j/src/test/java/com/google/adk/models/langchain4j/RunLoop.java
@@ -53,7 +53,7 @@ public static List runLoop(BaseAgent agent, boolean streaming, Object...
allEvents.addAll(
runner
.runAsync(
- session,
+ session.sessionKey(),
messageContent,
RunConfig.builder()
.setStreamingMode(
diff --git a/contrib/samples/a2a_basic/A2AAgent.java b/contrib/samples/a2a_basic/A2AAgent.java
index e4e79a4eb..e08a87a67 100644
--- a/contrib/samples/a2a_basic/A2AAgent.java
+++ b/contrib/samples/a2a_basic/A2AAgent.java
@@ -1,6 +1,6 @@
package com.example.a2a_basic;
-import com.google.adk.a2a.RemoteA2AAgent;
+import com.google.adk.a2a.agent.RemoteA2AAgent;
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.FunctionTool;
diff --git a/contrib/samples/a2a_basic/pom.xml b/contrib/samples/a2a_basic/pom.xml
index 13c8e50b2..708debeb6 100644
--- a/contrib/samples/a2a_basic/pom.xml
+++ b/contrib/samples/a2a_basic/pom.xml
@@ -5,7 +5,7 @@
com.google.adk
google-adk-samples
- 0.8.0
+ 0.9.0
..
diff --git a/contrib/samples/a2a_server/pom.xml b/contrib/samples/a2a_server/pom.xml
index 83a29727f..3fc0141fe 100644
--- a/contrib/samples/a2a_server/pom.xml
+++ b/contrib/samples/a2a_server/pom.xml
@@ -5,7 +5,7 @@
com.google.adk
google-adk-samples
- 0.8.0
+ 0.9.0
..
diff --git a/contrib/samples/configagent/pom.xml b/contrib/samples/configagent/pom.xml
index b9e90a0e0..61f9f3010 100644
--- a/contrib/samples/configagent/pom.xml
+++ b/contrib/samples/configagent/pom.xml
@@ -5,7 +5,7 @@
com.google.adk
google-adk-samples
- 0.8.0
+ 0.9.0
..
diff --git a/contrib/samples/helloworld/pom.xml b/contrib/samples/helloworld/pom.xml
index 8dd9b3a85..d0a2cf686 100644
--- a/contrib/samples/helloworld/pom.xml
+++ b/contrib/samples/helloworld/pom.xml
@@ -20,7 +20,7 @@
com.google.adk
google-adk-samples
- 0.8.0
+ 0.9.0
..
diff --git a/contrib/samples/mcpfilesystem/pom.xml b/contrib/samples/mcpfilesystem/pom.xml
index f1514a0ee..ab606a392 100644
--- a/contrib/samples/mcpfilesystem/pom.xml
+++ b/contrib/samples/mcpfilesystem/pom.xml
@@ -20,7 +20,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
../../..
diff --git a/contrib/samples/pom.xml b/contrib/samples/pom.xml
index 413bd9722..7ce31df73 100644
--- a/contrib/samples/pom.xml
+++ b/contrib/samples/pom.xml
@@ -5,7 +5,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
../..
diff --git a/contrib/spring-ai/pom.xml b/contrib/spring-ai/pom.xml
index c80428650..c2672d359 100644
--- a/contrib/spring-ai/pom.xml
+++ b/contrib/spring-ai/pom.xml
@@ -20,7 +20,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
../../pom.xml
diff --git a/contrib/spring-ai/src/test/java/com/google/adk/models/springai/SpringAIIntegrationTest.java b/contrib/spring-ai/src/test/java/com/google/adk/models/springai/SpringAIIntegrationTest.java
index 6843c8eaa..328df0415 100644
--- a/contrib/spring-ai/src/test/java/com/google/adk/models/springai/SpringAIIntegrationTest.java
+++ b/contrib/spring-ai/src/test/java/com/google/adk/models/springai/SpringAIIntegrationTest.java
@@ -18,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.*;
import com.google.adk.agents.LlmAgent;
+import com.google.adk.agents.RunConfig;
import com.google.adk.events.Event;
import com.google.adk.models.springai.integrations.tools.WeatherTool;
import com.google.adk.runner.InMemoryRunner;
@@ -73,14 +74,15 @@ public ChatResponse call(Prompt prompt) {
// when
Runner runner = new InMemoryRunner(agent);
- Session session = runner.sessionService().createSession("test-app", "test-user").blockingGet();
+ Session session =
+ runner.sessionService().createSession(agent.name(), "test-user").blockingGet();
Content userMessage =
Content.builder().role("user").parts(List.of(Part.fromText("What is a qubit?"))).build();
List events =
runner
- .runAsync(session, userMessage, com.google.adk.agents.RunConfig.builder().build())
+ .runAsync(session.sessionKey(), userMessage, RunConfig.builder().build())
.toList()
.blockingGet();
@@ -149,7 +151,8 @@ public ChatResponse call(Prompt prompt) {
// when
Runner runner = new InMemoryRunner(agent);
- Session session = runner.sessionService().createSession("test-app", "test-user").blockingGet();
+ Session session =
+ runner.sessionService().createSession(agent.name(), "test-user").blockingGet();
Content userMessage =
Content.builder()
@@ -159,7 +162,7 @@ public ChatResponse call(Prompt prompt) {
List events =
runner
- .runAsync(session, userMessage, com.google.adk.agents.RunConfig.builder().build())
+ .runAsync(session.userId(), session.id(), userMessage, RunConfig.builder().build())
.toList()
.blockingGet();
@@ -217,7 +220,8 @@ public Flux stream(Prompt prompt) {
// when
Runner runner = new InMemoryRunner(agent);
- Session session = runner.sessionService().createSession("test-app", "test-user").blockingGet();
+ Session session =
+ runner.sessionService().createSession(agent.name(), "test-user").blockingGet();
Content userMessage =
Content.builder()
@@ -228,11 +232,10 @@ public Flux stream(Prompt prompt) {
List events =
runner
.runAsync(
- session,
+ session.userId(),
+ session.id(),
userMessage,
- com.google.adk.agents.RunConfig.builder()
- .setStreamingMode(com.google.adk.agents.RunConfig.StreamingMode.SSE)
- .build())
+ RunConfig.builder().setStreamingMode(RunConfig.StreamingMode.SSE).build())
.toList()
.blockingGet();
diff --git a/contrib/spring-ai/src/test/java/com/google/adk/models/springai/TestUtils.java b/contrib/spring-ai/src/test/java/com/google/adk/models/springai/TestUtils.java
index f18ded055..c23e68eae 100644
--- a/contrib/spring-ai/src/test/java/com/google/adk/models/springai/TestUtils.java
+++ b/contrib/spring-ai/src/test/java/com/google/adk/models/springai/TestUtils.java
@@ -46,7 +46,7 @@ public static List askAgent(BaseAgent agent, boolean streaming, Object...
allEvents.addAll(
runner
.runAsync(
- session,
+ session.sessionKey(),
messageContent,
RunConfig.builder()
.setStreamingMode(
@@ -67,13 +67,17 @@ public static List askBlockingAgent(BaseAgent agent, Object... messages)
}
Runner runner = new InMemoryRunner(agent);
- Session session = runner.sessionService().createSession("test-app", "test-user").blockingGet();
+ Session session =
+ runner.sessionService().createSession(agent.name(), "test-user").blockingGet();
List events = new ArrayList<>();
for (Content content : contents) {
List batchEvents =
- runner.runAsync(session, content, RunConfig.builder().build()).toList().blockingGet();
+ runner
+ .runAsync(session.userId(), session.id(), content, RunConfig.builder().build())
+ .toList()
+ .blockingGet();
events.addAll(batchEvents);
}
@@ -88,7 +92,8 @@ public static List askAgentStreaming(BaseAgent agent, Object... messages)
}
Runner runner = new InMemoryRunner(agent);
- Session session = runner.sessionService().createSession("test-app", "test-user").blockingGet();
+ Session session =
+ runner.sessionService().createSession(agent.name(), "test-user").blockingGet();
List events = new ArrayList<>();
@@ -96,7 +101,8 @@ public static List askAgentStreaming(BaseAgent agent, Object... messages)
List batchEvents =
runner
.runAsync(
- session,
+ session.userId(),
+ session.id(),
content,
RunConfig.builder().setStreamingMode(RunConfig.StreamingMode.SSE).build())
.toList()
diff --git a/core/pom.xml b/core/pom.xml
index b455a9fc8..059e481a9 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -20,7 +20,7 @@
com.google.adk
google-adk-parent
- 0.8.0
+ 0.9.0
google-adk
@@ -92,6 +92,10 @@
com.google.errorprone
error_prone_annotations
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
com.fasterxml.jackson.core
jackson-databind
@@ -209,10 +213,29 @@
maven-surefire-plugin
+
basic
test
+
+
+ false
+
+
+
+
+ vertex-ai-rag-retrieval
+
+ test
+
+
+
+ true
+
+
+ VertexAiRagRetrievalTest#processLlmRequest_gemini2Model_addVertexRagStoreToConfig, VertexAiRagRetrievalTest#processLlmRequest_otherModel_doNotAddVertexRagStoreToConfig
+
apigee-llm
diff --git a/core/src/main/java/com/google/adk/Version.java b/core/src/main/java/com/google/adk/Version.java
index 1dc0282c3..a7aeb8b1f 100644
--- a/core/src/main/java/com/google/adk/Version.java
+++ b/core/src/main/java/com/google/adk/Version.java
@@ -22,7 +22,7 @@
*/
public final class Version {
// Don't touch this, release-please should keep it up to date.
- public static final String JAVA_ADK_VERSION = "0.8.0"; // x-release-please-released-version
+ public static final String JAVA_ADK_VERSION = "0.9.0"; // x-release-please-released-version
private Version() {}
}
diff --git a/core/src/main/java/com/google/adk/agents/BaseAgent.java b/core/src/main/java/com/google/adk/agents/BaseAgent.java
index 226e61abe..ed6631c50 100644
--- a/core/src/main/java/com/google/adk/agents/BaseAgent.java
+++ b/core/src/main/java/com/google/adk/agents/BaseAgent.java
@@ -29,10 +29,10 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.DoNotCall;
import com.google.genai.types.Content;
+import io.opentelemetry.context.Context;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Single;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -312,37 +312,41 @@ public Flowable runAsync(InvocationContext parentContext) {
private Flowable run(
InvocationContext parentContext,
Function> runImplementation) {
+ Context parentSpanContext = Context.current();
return Flowable.defer(
() -> {
InvocationContext invocationContext = createInvocationContext(parentContext);
+ Flowable mainAndAfterEvents =
+ Flowable.defer(() -> runImplementation.apply(invocationContext))
+ .concatWith(
+ Flowable.defer(
+ () ->
+ callCallback(
+ afterCallbacksToFunctions(
+ invocationContext.pluginManager(), afterAgentCallback),
+ invocationContext)
+ .toFlowable()));
+
return callCallback(
beforeCallbacksToFunctions(
invocationContext.pluginManager(), beforeAgentCallback),
invocationContext)
.flatMapPublisher(
- beforeEventOpt -> {
+ beforeEvent -> {
if (invocationContext.endInvocation()) {
- return Flowable.fromOptional(beforeEventOpt);
+ return Flowable.just(beforeEvent);
}
-
- Flowable beforeEvents = Flowable.fromOptional(beforeEventOpt);
- Flowable mainEvents =
- Flowable.defer(() -> runImplementation.apply(invocationContext));
- Flowable afterEvents =
- Flowable.defer(
- () ->
- callCallback(
- afterCallbacksToFunctions(
- invocationContext.pluginManager(), afterAgentCallback),
- invocationContext)
- .flatMapPublisher(Flowable::fromOptional));
-
- return Flowable.concat(beforeEvents, mainEvents, afterEvents);
+ return Flowable.just(beforeEvent).concatWith(mainAndAfterEvents);
})
+ .switchIfEmpty(mainAndAfterEvents)
.compose(
- Tracing.traceAgent(
- "invoke_agent " + name(), name(), description(), invocationContext));
+ Tracing.trace("invoke_agent " + name())
+ .setParent(parentSpanContext)
+ .configure(
+ span ->
+ Tracing.traceAgentInvocation(
+ span, name(), description(), invocationContext)));
});
}
@@ -383,13 +387,13 @@ private ImmutableList>> callbacksTo
*
* @param agentCallbacks Callback functions.
* @param invocationContext Current invocation context.
- * @return single emitting first event, or empty if none.
+ * @return maybe emitting first event, or empty if none.
*/
- private Single> callCallback(
+ private Maybe callCallback(
List>> agentCallbacks,
InvocationContext invocationContext) {
if (agentCallbacks.isEmpty()) {
- return Single.just(Optional.empty());
+ return Maybe.empty();
}
CallbackContext callbackContext =
@@ -404,21 +408,20 @@ private Single> callCallback(
.map(
content -> {
invocationContext.setEndInvocation(true);
- return Optional.of(
- Event.builder()
- .id(Event.generateEventId())
- .invocationId(invocationContext.invocationId())
- .author(name())
- .branch(invocationContext.branch())
- .actions(callbackContext.eventActions())
- .content(content)
- .build());
+ return Event.builder()
+ .id(Event.generateEventId())
+ .invocationId(invocationContext.invocationId())
+ .author(name())
+ .branch(invocationContext.branch().orElse(null))
+ .actions(callbackContext.eventActions())
+ .content(content)
+ .build();
})
.toFlowable();
})
.firstElement()
.switchIfEmpty(
- Single.defer(
+ Maybe.defer(
() -> {
if (callbackContext.state().hasDelta()) {
Event.Builder eventBuilder =
@@ -426,12 +429,12 @@ private Single> callCallback(
.id(Event.generateEventId())
.invocationId(invocationContext.invocationId())
.author(name())
- .branch(invocationContext.branch())
+ .branch(invocationContext.branch().orElse(null))
.actions(callbackContext.eventActions());
- return Single.just(Optional.of(eventBuilder.build()));
+ return Maybe.just(eventBuilder.build());
} else {
- return Single.just(Optional.empty());
+ return Maybe.empty();
}
}));
}
diff --git a/core/src/main/java/com/google/adk/agents/CallbackContext.java b/core/src/main/java/com/google/adk/agents/CallbackContext.java
index a29783769..da5b0d794 100644
--- a/core/src/main/java/com/google/adk/agents/CallbackContext.java
+++ b/core/src/main/java/com/google/adk/agents/CallbackContext.java
@@ -19,12 +19,12 @@
import com.google.adk.artifacts.ListArtifactsResponse;
import com.google.adk.events.EventActions;
import com.google.adk.sessions.State;
+import com.google.common.base.Preconditions;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import java.util.List;
-import java.util.Optional;
/** The context of various callbacks for an agent invocation. */
public class CallbackContext extends ReadonlyContext {
@@ -94,22 +94,19 @@ public Single> listArtifacts() {
/** Loads the latest version of an artifact from the service. */
public Maybe loadArtifact(String filename) {
- return loadArtifact(filename, Optional.empty());
+ checkArtifactServiceInitialized();
+ return invocationContext
+ .artifactService()
+ .loadArtifact(
+ invocationContext.appName(),
+ invocationContext.userId(),
+ invocationContext.session().id(),
+ filename);
}
/** Loads a specific version of an artifact from the service. */
public Maybe loadArtifact(String filename, int version) {
- return loadArtifact(filename, Optional.of(version));
- }
-
- /**
- * @deprecated Use {@link #loadArtifact(String)} or {@link #loadArtifact(String, int)} instead.
- */
- @Deprecated
- public Maybe loadArtifact(String filename, Optional version) {
- if (invocationContext.artifactService() == null) {
- throw new IllegalStateException("Artifact service is not initialized.");
- }
+ checkArtifactServiceInitialized();
return invocationContext
.artifactService()
.loadArtifact(
@@ -120,6 +117,11 @@ public Maybe loadArtifact(String filename, Optional version) {
version);
}
+ private void checkArtifactServiceInitialized() {
+ Preconditions.checkState(
+ invocationContext.artifactService() != null, "Artifact service is not initialized.");
+ }
+
/**
* Saves an artifact and records it as a delta for the current session.
*
diff --git a/core/src/main/java/com/google/adk/agents/InvocationContext.java b/core/src/main/java/com/google/adk/agents/InvocationContext.java
index 7602ca9f2..91ce13a87 100644
--- a/core/src/main/java/com/google/adk/agents/InvocationContext.java
+++ b/core/src/main/java/com/google/adk/agents/InvocationContext.java
@@ -27,7 +27,6 @@
import com.google.adk.sessions.Session;
import com.google.adk.summarizer.EventsCompactionConfig;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import com.google.errorprone.annotations.InlineMe;
import com.google.genai.types.Content;
import java.util.Map;
import java.util.Objects;
@@ -75,63 +74,10 @@ protected InvocationContext(Builder builder) {
this.eventsCompactionConfig = builder.eventsCompactionConfig;
this.contextCacheConfig = builder.contextCacheConfig;
this.invocationCostManager = builder.invocationCostManager;
- this.callbackContextData = new ConcurrentHashMap<>(builder.callbackContextData);
- }
-
- /**
- * @deprecated Use {@link #builder()} instead.
- */
- @InlineMe(
- replacement =
- "InvocationContext.builder()"
- + ".sessionService(sessionService)"
- + ".artifactService(artifactService)"
- + ".invocationId(invocationId)"
- + ".agent(agent)"
- + ".session(session)"
- + ".userContent(userContent)"
- + ".runConfig(runConfig)"
- + ".build()",
- imports = {"com.google.adk.agents.InvocationContext"})
- @Deprecated(forRemoval = true)
- public static InvocationContext create(
- BaseSessionService sessionService,
- BaseArtifactService artifactService,
- String invocationId,
- BaseAgent agent,
- Session session,
- Content userContent,
- RunConfig runConfig) {
- return builder()
- .sessionService(sessionService)
- .artifactService(artifactService)
- .invocationId(invocationId)
- .agent(agent)
- .session(session)
- .userContent(userContent)
- .runConfig(runConfig)
- .build();
- }
-
- /**
- * @deprecated Use {@link #builder()} instead.
- */
- @Deprecated(forRemoval = true)
- public static InvocationContext create(
- BaseSessionService sessionService,
- BaseArtifactService artifactService,
- BaseAgent agent,
- Session session,
- LiveRequestQueue liveRequestQueue,
- RunConfig runConfig) {
- return builder()
- .sessionService(sessionService)
- .artifactService(artifactService)
- .agent(agent)
- .session(session)
- .liveRequestQueue(liveRequestQueue)
- .runConfig(runConfig)
- .build();
+ // Don't copy the callback context data. This should be the same instance for the full
+ // invocation invocation so that Plugins can access the same data it during the invocation
+ // across all types of callbacks.
+ this.callbackContextData = builder.callbackContextData;
}
/** Returns a new {@link Builder} for creating {@link InvocationContext} instances. */
@@ -345,7 +291,10 @@ private Builder(InvocationContext context) {
this.eventsCompactionConfig = context.eventsCompactionConfig;
this.contextCacheConfig = context.contextCacheConfig;
this.invocationCostManager = context.invocationCostManager;
- this.callbackContextData = new ConcurrentHashMap<>(context.callbackContextData);
+ // Don't copy the callback context data. This should be the same instance for the full
+ // invocation invocation so that Plugins can access the same data it during the invocation
+ // across all types of callbacks.
+ this.callbackContextData = context.callbackContextData;
}
private BaseSessionService sessionService;
diff --git a/core/src/main/java/com/google/adk/agents/LlmAgent.java b/core/src/main/java/com/google/adk/agents/LlmAgent.java
index bbed217f4..d326d8154 100644
--- a/core/src/main/java/com/google/adk/agents/LlmAgent.java
+++ b/core/src/main/java/com/google/adk/agents/LlmAgent.java
@@ -757,14 +757,6 @@ public Single> canonicalGlobalInstruction(ReadonlyCon
throw new IllegalStateException("Unknown Instruction subtype: " + globalInstruction.getClass());
}
- /**
- * @deprecated Use {@link #canonicalTools(ReadonlyContext)} instead.
- */
- @Deprecated
- public Flowable canonicalTools(Optional context) {
- return canonicalTools(context.orElse(null));
- }
-
/**
* Constructs the list of tools for this agent based on the {@link #tools} field.
*
diff --git a/core/src/main/java/com/google/adk/agents/LoopAgent.java b/core/src/main/java/com/google/adk/agents/LoopAgent.java
index d9d049f80..743d569b9 100644
--- a/core/src/main/java/com/google/adk/agents/LoopAgent.java
+++ b/core/src/main/java/com/google/adk/agents/LoopAgent.java
@@ -21,7 +21,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.reactivex.rxjava3.core.Flowable;
import java.util.List;
-import java.util.Optional;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,7 +34,7 @@
public class LoopAgent extends BaseAgent {
private static final Logger logger = LoggerFactory.getLogger(LoopAgent.class);
- private final Optional maxIterations;
+ private final @Nullable Integer maxIterations;
/**
* Constructor for LoopAgent.
@@ -50,7 +50,7 @@ private LoopAgent(
String name,
String description,
List extends BaseAgent> subAgents,
- Optional maxIterations,
+ @Nullable Integer maxIterations,
List beforeAgentCallback,
List afterAgentCallback) {
@@ -60,16 +60,10 @@ private LoopAgent(
/** Builder for {@link LoopAgent}. */
public static class Builder extends BaseAgent.Builder {
- private Optional maxIterations = Optional.empty();
+ private @Nullable Integer maxIterations;
@CanIgnoreReturnValue
- public Builder maxIterations(int maxIterations) {
- this.maxIterations = Optional.of(maxIterations);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder maxIterations(Optional maxIterations) {
+ public Builder maxIterations(@Nullable Integer maxIterations) {
this.maxIterations = maxIterations;
return this;
}
@@ -124,7 +118,7 @@ protected Flowable runAsyncImpl(InvocationContext invocationContext) {
return Flowable.fromIterable(subAgents)
.concatMap(subAgent -> subAgent.runAsync(invocationContext))
- .repeat(maxIterations.orElse(Integer.MAX_VALUE))
+ .repeat(maxIterations != null ? maxIterations : Integer.MAX_VALUE)
.takeUntil(LoopAgent::hasEscalateAction);
}
@@ -137,4 +131,8 @@ protected Flowable runLiveImpl(InvocationContext invocationContext) {
private static boolean hasEscalateAction(Event event) {
return event.actions().escalate().orElse(false);
}
+
+ public @Nullable Integer maxIterations() {
+ return maxIterations;
+ }
}
diff --git a/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java b/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java
index a9bb6ba4d..acf5979c2 100644
--- a/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java
+++ b/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java
@@ -22,7 +22,7 @@
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
-import java.util.Optional;
+import org.jspecify.annotations.Nullable;
/** Base interface for artifact services. */
public interface BaseArtifactService {
@@ -75,7 +75,7 @@ default Single saveAndReloadArtifact(
/** Loads the latest version of an artifact from the service. */
default Maybe loadArtifact(
String appName, String userId, String sessionId, String filename) {
- return loadArtifact(appName, userId, sessionId, filename, Optional.empty());
+ return loadArtifact(appName, userId, sessionId, filename, /* version= */ (Integer) null);
}
/** Loads the latest version of an artifact from the service. */
@@ -86,7 +86,7 @@ default Maybe loadArtifact(SessionKey sessionKey, String filename) {
/** Loads a specific version of an artifact from the service. */
default Maybe loadArtifact(
String appName, String userId, String sessionId, String filename, int version) {
- return loadArtifact(appName, userId, sessionId, filename, Optional.of(version));
+ return loadArtifact(appName, userId, sessionId, filename, Integer.valueOf(version));
}
default Maybe loadArtifact(SessionKey sessionKey, String filename, int version) {
@@ -94,13 +94,8 @@ default Maybe loadArtifact(SessionKey sessionKey, String filename, int ver
sessionKey.appName(), sessionKey.userId(), sessionKey.id(), filename, version);
}
- /**
- * @deprecated Use {@link #loadArtifact(String, String, String, String)} or {@link
- * #loadArtifact(String, String, String, String, int)} instead.
- */
- @Deprecated
Maybe loadArtifact(
- String appName, String userId, String sessionId, String filename, Optional version);
+ String appName, String userId, String sessionId, String filename, @Nullable Integer version);
/**
* Lists all the artifact filenames within a session.
diff --git a/core/src/main/java/com/google/adk/artifacts/GcsArtifactService.java b/core/src/main/java/com/google/adk/artifacts/GcsArtifactService.java
index e31d50327..977153828 100644
--- a/core/src/main/java/com/google/adk/artifacts/GcsArtifactService.java
+++ b/core/src/main/java/com/google/adk/artifacts/GcsArtifactService.java
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import org.jspecify.annotations.Nullable;
/** An artifact service implementation using Google Cloud Storage (GCS). */
public final class GcsArtifactService implements BaseArtifactService {
@@ -126,8 +127,8 @@ public Single saveArtifact(
*/
@Override
public Maybe loadArtifact(
- String appName, String userId, String sessionId, String filename, Optional version) {
- return version
+ String appName, String userId, String sessionId, String filename, @Nullable Integer version) {
+ return Optional.ofNullable(version)
.map(Maybe::just)
.orElseGet(
() ->
diff --git a/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java b/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java
index 8c8ec2af8..510c96c2e 100644
--- a/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java
+++ b/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java
@@ -28,8 +28,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.IntStream;
+import org.jspecify.annotations.Nullable;
/** An in-memory implementation of the {@link BaseArtifactService}. */
public final class InMemoryArtifactService implements BaseArtifactService {
@@ -61,7 +61,7 @@ public Single saveArtifact(
*/
@Override
public Maybe loadArtifact(
- String appName, String userId, String sessionId, String filename, Optional version) {
+ String appName, String userId, String sessionId, String filename, @Nullable Integer version) {
List versions =
getArtifactsMap(appName, userId, sessionId)
.computeIfAbsent(filename, unused -> new ArrayList<>());
@@ -69,10 +69,9 @@ public Maybe loadArtifact(
if (versions.isEmpty()) {
return Maybe.empty();
}
- if (version.isPresent()) {
- int v = version.get();
- if (v >= 0 && v < versions.size()) {
- return Maybe.just(versions.get(v));
+ if (version != null) {
+ if (version >= 0 && version < versions.size()) {
+ return Maybe.just(versions.get(version));
} else {
return Maybe.empty();
}
diff --git a/core/src/main/java/com/google/adk/codeexecutors/CodeExecutionUtils.java b/core/src/main/java/com/google/adk/codeexecutors/CodeExecutionUtils.java
index b9afdcaff..a4d3771c3 100644
--- a/core/src/main/java/com/google/adk/codeexecutors/CodeExecutionUtils.java
+++ b/core/src/main/java/com/google/adk/codeexecutors/CodeExecutionUtils.java
@@ -34,6 +34,7 @@
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.jspecify.annotations.Nullable;
/** Utility functions for code execution. */
public final class CodeExecutionUtils {
@@ -237,8 +238,7 @@ public abstract static class CodeExecutionInput extends JsonBaseModel {
public static Builder builder() {
return new AutoValue_CodeExecutionUtils_CodeExecutionInput.Builder()
- .inputFiles(ImmutableList.of())
- .executionId(Optional.empty());
+ .inputFiles(ImmutableList.of());
}
/** Builder for {@link CodeExecutionInput}. */
@@ -248,7 +248,7 @@ public abstract static class Builder {
public abstract Builder inputFiles(List inputFiles);
- public abstract Builder executionId(Optional executionId);
+ public abstract Builder executionId(@Nullable String executionId);
public abstract CodeExecutionInput build();
}
diff --git a/core/src/main/java/com/google/adk/codeexecutors/VertexAiCodeExecutor.java b/core/src/main/java/com/google/adk/codeexecutors/VertexAiCodeExecutor.java
index 5268edf39..af2219d18 100644
--- a/core/src/main/java/com/google/adk/codeexecutors/VertexAiCodeExecutor.java
+++ b/core/src/main/java/com/google/adk/codeexecutors/VertexAiCodeExecutor.java
@@ -36,7 +36,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -140,7 +140,7 @@ public CodeExecutionResult executeCode(
executeCodeInterpreter(
getCodeWithImports(codeExecutionInput.code()),
codeExecutionInput.inputFiles(),
- codeExecutionInput.executionId());
+ codeExecutionInput.executionId().orElse(null));
// Save output file as artifacts.
List savedFiles = new ArrayList<>();
@@ -173,7 +173,7 @@ public CodeExecutionResult executeCode(
}
private Map executeCodeInterpreter(
- String code, List inputFiles, Optional sessionId) {
+ String code, List inputFiles, @Nullable String sessionId) {
ExtensionExecutionServiceClient codeInterpreterExtension = getCodeInterpreterExtension();
if (codeInterpreterExtension == null) {
logger.warn("Vertex AI Code Interpreter execution is not available. Returning empty result.");
@@ -196,8 +196,9 @@ private Map executeCodeInterpreter(
paramsBuilder.putFields(
"files", Value.newBuilder().setListValue(listBuilder.build()).build());
}
- sessionId.ifPresent(
- s -> paramsBuilder.putFields("session_id", Value.newBuilder().setStringValue(s).build()));
+ if (sessionId != null) {
+ paramsBuilder.putFields("session_id", Value.newBuilder().setStringValue(sessionId).build());
+ }
ExecuteExtensionRequest request =
ExecuteExtensionRequest.newBuilder()
diff --git a/core/src/main/java/com/google/adk/events/Event.java b/core/src/main/java/com/google/adk/events/Event.java
index 91dc79a56..2677b635d 100644
--- a/core/src/main/java/com/google/adk/events/Event.java
+++ b/core/src/main/java/com/google/adk/events/Event.java
@@ -49,21 +49,21 @@ public class Event extends JsonBaseModel {
private String id;
private String invocationId;
private String author;
- private Optional content = Optional.empty();
+ private @Nullable Content content;
private EventActions actions;
- private Optional> longRunningToolIds = Optional.empty();
- private Optional partial = Optional.empty();
- private Optional turnComplete = Optional.empty();
- private Optional errorCode = Optional.empty();
- private Optional errorMessage = Optional.empty();
- private Optional finishReason = Optional.empty();
- private Optional usageMetadata = Optional.empty();
- private Optional avgLogprobs = Optional.empty();
- private Optional interrupted = Optional.empty();
- private Optional branch = Optional.empty();
- private Optional groundingMetadata = Optional.empty();
- private Optional> customMetadata = Optional.empty();
- private Optional modelVersion = Optional.empty();
+ private @Nullable Set longRunningToolIds;
+ private @Nullable Boolean partial;
+ private @Nullable Boolean turnComplete;
+ private @Nullable FinishReason errorCode;
+ private @Nullable String errorMessage;
+ private @Nullable FinishReason finishReason;
+ private @Nullable GenerateContentResponseUsageMetadata usageMetadata;
+ private @Nullable Double avgLogprobs;
+ private @Nullable Boolean interrupted;
+ private @Nullable String branch;
+ private @Nullable GroundingMetadata groundingMetadata;
+ private @Nullable List customMetadata;
+ private @Nullable String modelVersion;
private long timestamp;
private Event() {}
@@ -104,10 +104,10 @@ public void setAuthor(String author) {
@JsonProperty("content")
public Optional content() {
- return content;
+ return Optional.ofNullable(content);
}
- public void setContent(Optional content) {
+ public void setContent(@Nullable Content content) {
this.content = content;
}
@@ -126,10 +126,10 @@ public void setActions(EventActions actions) {
*/
@JsonProperty("longRunningToolIds")
public Optional> longRunningToolIds() {
- return longRunningToolIds;
+ return Optional.ofNullable(longRunningToolIds);
}
- public void setLongRunningToolIds(Optional> longRunningToolIds) {
+ public void setLongRunningToolIds(@Nullable Set longRunningToolIds) {
this.longRunningToolIds = longRunningToolIds;
}
@@ -139,73 +139,79 @@ public void setLongRunningToolIds(Optional> longRunningToolIds) {
*/
@JsonProperty("partial")
public Optional partial() {
- return partial;
+ return Optional.ofNullable(partial);
}
- public void setPartial(Optional partial) {
+ public void setPartial(@Nullable Boolean partial) {
this.partial = partial;
}
@JsonProperty("turnComplete")
public Optional turnComplete() {
- return turnComplete;
+ return Optional.ofNullable(turnComplete);
}
- public void setTurnComplete(Optional turnComplete) {
+ public void setTurnComplete(@Nullable Boolean turnComplete) {
this.turnComplete = turnComplete;
}
@JsonProperty("errorCode")
public Optional errorCode() {
- return errorCode;
+ return Optional.ofNullable(errorCode);
}
@JsonProperty("finishReason")
public Optional finishReason() {
- return finishReason;
+ return Optional.ofNullable(finishReason);
}
- public void setErrorCode(Optional errorCode) {
+ public void setErrorCode(@Nullable FinishReason errorCode) {
this.errorCode = errorCode;
}
+ @Deprecated
+ @SuppressWarnings("checkstyle:IllegalType")
public void setFinishReason(Optional finishReason) {
+ this.finishReason = finishReason.orElse(null);
+ }
+
+ public void setFinishReason(@Nullable FinishReason finishReason) {
this.finishReason = finishReason;
}
@JsonProperty("errorMessage")
public Optional errorMessage() {
- return errorMessage;
+ return Optional.ofNullable(errorMessage);
}
- public void setErrorMessage(Optional errorMessage) {
+ public void setErrorMessage(@Nullable String errorMessage) {
this.errorMessage = errorMessage;
}
@JsonProperty("usageMetadata")
public Optional usageMetadata() {
- return usageMetadata;
+ return Optional.ofNullable(usageMetadata);
}
- public void setUsageMetadata(Optional usageMetadata) {
+ public void setUsageMetadata(@Nullable GenerateContentResponseUsageMetadata usageMetadata) {
this.usageMetadata = usageMetadata;
}
@JsonProperty("avgLogprobs")
public Optional avgLogprobs() {
- return avgLogprobs;
+ return Optional.ofNullable(avgLogprobs);
}
- public void setAvgLogprobs(Optional avgLogprobs) {
+ public void setAvgLogprobs(@Nullable Double avgLogprobs) {
this.avgLogprobs = avgLogprobs;
}
@JsonProperty("interrupted")
public Optional interrupted() {
- return interrupted;
+ return Optional.ofNullable(interrupted);
}
- public void setInterrupted(Optional interrupted) {
+ public void setInterrupted(@Nullable Boolean interrupted) {
this.interrupted = interrupted;
}
@@ -216,7 +222,7 @@ public void setInterrupted(Optional interrupted) {
*/
@JsonProperty("branch")
public Optional branch() {
- return branch;
+ return Optional.ofNullable(branch);
}
/**
@@ -227,40 +233,36 @@ public Optional branch() {
* @param branch Branch identifier.
*/
public void branch(@Nullable String branch) {
- this.branch = Optional.ofNullable(branch);
- }
-
- public void branch(Optional branch) {
this.branch = branch;
}
/** The grounding metadata of the event. */
@JsonProperty("groundingMetadata")
public Optional groundingMetadata() {
- return groundingMetadata;
+ return Optional.ofNullable(groundingMetadata);
}
- public void setGroundingMetadata(Optional groundingMetadata) {
+ public void setGroundingMetadata(@Nullable GroundingMetadata groundingMetadata) {
this.groundingMetadata = groundingMetadata;
}
/** The custom metadata of the event. */
@JsonProperty("customMetadata")
public Optional> customMetadata() {
- return customMetadata;
+ return Optional.ofNullable(customMetadata);
}
public void setCustomMetadata(@Nullable List customMetadata) {
- this.customMetadata = Optional.ofNullable(customMetadata);
+ this.customMetadata = customMetadata;
}
/** The model version used to generate the response. */
@JsonProperty("modelVersion")
public Optional modelVersion() {
- return modelVersion;
+ return Optional.ofNullable(modelVersion);
}
- public void setModelVersion(Optional modelVersion) {
+ public void setModelVersion(@Nullable String modelVersion) {
this.modelVersion = modelVersion;
}
@@ -345,22 +347,22 @@ public static class Builder {
private String id;
private String invocationId;
private String author;
- private Optional content = Optional.empty();
+ private @Nullable Content content;
private EventActions actions;
- private Optional> longRunningToolIds = Optional.empty();
- private Optional partial = Optional.empty();
- private Optional turnComplete = Optional.empty();
- private Optional errorCode = Optional.empty();
- private Optional errorMessage = Optional.empty();
- private Optional finishReason = Optional.empty();
- private Optional usageMetadata = Optional.empty();
- private Optional avgLogprobs = Optional.empty();
- private Optional interrupted = Optional.empty();
- private Optional branch = Optional.empty();
- private Optional groundingMetadata = Optional.empty();
- private Optional> customMetadata = Optional.empty();
- private Optional modelVersion = Optional.empty();
- private Optional timestamp = Optional.empty();
+ private @Nullable Set longRunningToolIds;
+ private @Nullable Boolean partial;
+ private @Nullable Boolean turnComplete;
+ private @Nullable FinishReason errorCode;
+ private @Nullable String errorMessage;
+ private @Nullable FinishReason finishReason;
+ private @Nullable GenerateContentResponseUsageMetadata usageMetadata;
+ private @Nullable Double avgLogprobs;
+ private @Nullable Boolean interrupted;
+ private @Nullable String branch;
+ private @Nullable GroundingMetadata groundingMetadata;
+ private @Nullable List customMetadata;
+ private @Nullable String modelVersion;
+ private @Nullable Long timestamp;
@JsonCreator
private static Builder create() {
@@ -391,12 +393,6 @@ public Builder author(String value) {
@CanIgnoreReturnValue
@JsonProperty("content")
public Builder content(@Nullable Content value) {
- this.content = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder content(Optional value) {
this.content = value;
return this;
}
@@ -415,12 +411,6 @@ Optional actions() {
@CanIgnoreReturnValue
@JsonProperty("longRunningToolIds")
public Builder longRunningToolIds(@Nullable Set value) {
- this.longRunningToolIds = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder longRunningToolIds(Optional> value) {
this.longRunningToolIds = value;
return this;
}
@@ -428,12 +418,6 @@ public Builder longRunningToolIds(Optional> value) {
@CanIgnoreReturnValue
@JsonProperty("partial")
public Builder partial(@Nullable Boolean value) {
- this.partial = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder partial(Optional value) {
this.partial = value;
return this;
}
@@ -441,12 +425,6 @@ public Builder partial(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("turnComplete")
public Builder turnComplete(@Nullable Boolean value) {
- this.turnComplete = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder turnComplete(Optional value) {
this.turnComplete = value;
return this;
}
@@ -454,12 +432,6 @@ public Builder turnComplete(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("errorCode")
public Builder errorCode(@Nullable FinishReason value) {
- this.errorCode = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder errorCode(Optional value) {
this.errorCode = value;
return this;
}
@@ -467,12 +439,6 @@ public Builder errorCode(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("errorMessage")
public Builder errorMessage(@Nullable String value) {
- this.errorMessage = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder errorMessage(Optional value) {
this.errorMessage = value;
return this;
}
@@ -480,12 +446,6 @@ public Builder errorMessage(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("finishReason")
public Builder finishReason(@Nullable FinishReason value) {
- this.finishReason = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder finishReason(Optional value) {
this.finishReason = value;
return this;
}
@@ -493,12 +453,6 @@ public Builder finishReason(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("usageMetadata")
public Builder usageMetadata(@Nullable GenerateContentResponseUsageMetadata value) {
- this.usageMetadata = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder usageMetadata(Optional value) {
this.usageMetadata = value;
return this;
}
@@ -506,12 +460,6 @@ public Builder usageMetadata(Optional valu
@CanIgnoreReturnValue
@JsonProperty("avgLogprobs")
public Builder avgLogprobs(@Nullable Double value) {
- this.avgLogprobs = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder avgLogprobs(Optional value) {
this.avgLogprobs = value;
return this;
}
@@ -519,12 +467,6 @@ public Builder avgLogprobs(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("interrupted")
public Builder interrupted(@Nullable Boolean value) {
- this.interrupted = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder interrupted(Optional value) {
this.interrupted = value;
return this;
}
@@ -532,84 +474,52 @@ public Builder interrupted(Optional value) {
@CanIgnoreReturnValue
@JsonProperty("timestamp")
public Builder timestamp(long value) {
- this.timestamp = Optional.of(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder timestamp(Optional value) {
this.timestamp = value;
return this;
}
// Getter for builder's timestamp, used in build()
Optional timestamp() {
- return timestamp;
+ return Optional.ofNullable(timestamp);
}
@CanIgnoreReturnValue
@JsonProperty("branch")
public Builder branch(@Nullable String value) {
- this.branch = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder branch(Optional value) {
this.branch = value;
return this;
}
// Getter for builder's branch, used in build()
Optional branch() {
- return branch;
+ return Optional.ofNullable(branch);
}
@CanIgnoreReturnValue
@JsonProperty("groundingMetadata")
public Builder groundingMetadata(@Nullable GroundingMetadata value) {
- this.groundingMetadata = Optional.ofNullable(value);
- return this;
- }
-
- @CanIgnoreReturnValue
- public Builder groundingMetadata(Optional value) {
this.groundingMetadata = value;
return this;
}
Optional groundingMetadata() {
- return groundingMetadata;
+ return Optional.ofNullable(groundingMetadata);
}
@CanIgnoreReturnValue
@JsonProperty("customMetadata")
public Builder customMetadata(@Nullable List