Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8137d66
fix: include usage_metadata events in live postprocessing
tilgalas Feb 18, 2026
2f99b72
chore(main): release 0.5.1-SNAPSHOT
tilgalas Feb 18, 2026
6fa3439
Merge pull request #857 from google:release-please--branches--main
copybara-github Feb 18, 2026
99b5fc2
fix: remove client-side function call IDs from LlmRequest
google-genai-bot Feb 19, 2026
b884a42
chore(main): release 0.6.0
tilgalas Feb 19, 2026
1df166b
refactor: GcsServiceArtifact becomes async
google-genai-bot Feb 19, 2026
e90b279
Merge pull request #878 from google:release-please--branches--main
copybara-github Feb 19, 2026
07188eb
chore(main): release 0.6.1-SNAPSHOT
tilgalas Feb 19, 2026
d24e6cc
Merge pull request #880 from google:release-please--branches--main
copybara-github Feb 19, 2026
d733a48
feat: Add ComputerUse tool
google-genai-bot Feb 19, 2026
5262d4a
docs: Update a parameter name in a comment
google-genai-bot Feb 19, 2026
7218295
feat: Update AgentExecutor so it builds new runner on execute and the…
google-genai-bot Feb 23, 2026
5f5869f
feat: Extend url_context support to Gemini 3 in Java ADK
google-genai-bot Feb 23, 2026
9a9ea48
refactor: Deprecate Optional in `BaseArtifactService` and `CallbackCo…
google-genai-bot Feb 24, 2026
fbb6253
refactor: Remove Optional parameter type from canonicalTools
google-genai-bot Feb 24, 2026
7953503
fix: drop explicit gemini-1 model version check in GoogleMapsTool
tilgalas Feb 24, 2026
2c9d4dd
feat: Extend url_context support to Gemini 3 in Java ADK
google-genai-bot Feb 24, 2026
ff07474
fix: deep-merge stateDelta maps when merging EventActions
google-genai-bot Feb 24, 2026
2b762cc
Merge branch 'main' into pr-133
Sandeep-BA Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 92 additions & 15 deletions a2a/src/main/java/com/google/adk/a2a/AgentExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

import com.google.adk.a2a.converters.EventConverter;
import com.google.adk.a2a.converters.PartConverter;
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.RunConfig;
import com.google.adk.apps.App;
import com.google.adk.artifacts.BaseArtifactService;
import com.google.adk.events.Event;
import com.google.adk.memory.BaseMemoryService;
import com.google.adk.plugins.Plugin;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.BaseSessionService;
import com.google.adk.sessions.Session;
Expand All @@ -21,6 +26,7 @@
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
Expand All @@ -41,29 +47,98 @@ public class AgentExecutor implements io.a2a.server.agentexecution.AgentExecutor
private static final RunConfig DEFAULT_RUN_CONFIG =
RunConfig.builder().setStreamingMode(RunConfig.StreamingMode.NONE).setMaxLlmCalls(20).build();

private final Runner runner;
private final Map<String, Disposable> activeTasks = new ConcurrentHashMap<>();
private final Runner.Builder runnerBuilder;
private final RunConfig runConfig;

private AgentExecutor(Runner runner) {
this.runner = runner;
private AgentExecutor(
App app,
BaseAgent agent,
String appName,
BaseArtifactService artifactService,
BaseSessionService sessionService,
BaseMemoryService memoryService,
List<? extends Plugin> plugins,
RunConfig runConfig) {
this.runnerBuilder =
Runner.builder()
.agent(agent)
.appName(appName)
.artifactService(artifactService)
.sessionService(sessionService)
.memoryService(memoryService)
.plugins(plugins);
if (app != null) {
this.runnerBuilder.app(app);
}
// Check that the runner is configured correctly and can be built.
var unused = runnerBuilder.build();
this.runConfig = runConfig == null ? DEFAULT_RUN_CONFIG : runConfig;
}

/** Builder for {@link AgentExecutor}. */
public static class Builder {
private Runner runner;
private App app;
private BaseAgent agent;
private String appName;
private BaseArtifactService artifactService;
private BaseSessionService sessionService;
private BaseMemoryService memoryService;
private List<? extends Plugin> plugins = ImmutableList.of();
private RunConfig runConfig;

@CanIgnoreReturnValue
public Builder app(App app) {
this.app = app;
return this;
}

@CanIgnoreReturnValue
public Builder agent(BaseAgent agent) {
this.agent = agent;
return this;
}

@CanIgnoreReturnValue
public Builder appName(String appName) {
this.appName = appName;
return this;
}

@CanIgnoreReturnValue
public Builder artifactService(BaseArtifactService artifactService) {
this.artifactService = artifactService;
return this;
}

@CanIgnoreReturnValue
public Builder sessionService(BaseSessionService sessionService) {
this.sessionService = sessionService;
return this;
}

@CanIgnoreReturnValue
public Builder runner(Runner runner) {
this.runner = runner;
public Builder memoryService(BaseMemoryService memoryService) {
this.memoryService = memoryService;
return this;
}

@CanIgnoreReturnValue
public Builder plugins(List<? extends Plugin> plugins) {
this.plugins = plugins;
return this;
}

@CanIgnoreReturnValue
public Builder runConfig(RunConfig runConfig) {
this.runConfig = runConfig;
return this;
}

@CanIgnoreReturnValue
public AgentExecutor build() {
if (runner == null) {
throw new IllegalStateException("Runner must be provided.");
}
return new AgentExecutor(runner);
return new AgentExecutor(
app, agent, appName, artifactService, sessionService, memoryService, plugins, runConfig);
}
}

Expand Down Expand Up @@ -96,13 +171,14 @@ public void execute(RequestContext ctx, EventQueue eventQueue) {

EventProcessor p = new EventProcessor();
Content content = PartConverter.messageToContent(message);
Runner runner = runnerBuilder.build();

taskDisposables.add(
prepareSession(ctx, runner.sessionService())
prepareSession(ctx, runner.appName(), runner.sessionService())
.flatMapPublisher(
session -> {
updater.startWork();
return runner.runAsync(getUserId(ctx), session.id(), content, DEFAULT_RUN_CONFIG);
return runner.runAsync(getUserId(ctx), session.id(), content, runConfig);
})
.subscribe(
event -> {
Expand Down Expand Up @@ -130,13 +206,14 @@ private String getUserId(RequestContext ctx) {
return USER_ID_PREFIX + ctx.getContextId();
}

private Maybe<Session> prepareSession(RequestContext ctx, BaseSessionService service) {
private Maybe<Session> prepareSession(
RequestContext ctx, String appName, BaseSessionService service) {
return service
.getSession(runner.appName(), getUserId(ctx), ctx.getContextId(), Optional.empty())
.getSession(appName, getUserId(ctx), ctx.getContextId(), Optional.empty())
.switchIfEmpty(
Maybe.defer(
() -> {
return service.createSession(runner.appName(), getUserId(ctx)).toMaybe();
return service.createSession(appName, getUserId(ctx)).toMaybe();
}));
}

Expand Down
82 changes: 82 additions & 0 deletions a2a/src/test/java/com/google/adk/a2a/AgentExecutorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.google.adk.a2a;

import static org.junit.Assert.assertThrows;

import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.InvocationContext;
import com.google.adk.apps.App;
import com.google.adk.artifacts.InMemoryArtifactService;
import com.google.adk.events.Event;
import com.google.adk.sessions.InMemorySessionService;
import com.google.common.collect.ImmutableList;
import io.reactivex.rxjava3.core.Flowable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class AgentExecutorTest {

private TestAgent testAgent;

@Before
public void setUp() {
testAgent = new TestAgent();
}

@Test
public void createAgentExecutor_noAgent_succeeds() {
var unused =
new AgentExecutor.Builder()
.app(App.builder().name("test_app").rootAgent(testAgent).build())
.sessionService(new InMemorySessionService())
.artifactService(new InMemoryArtifactService())
.build();
}

@Test
public void createAgentExecutor_withAgentAndApp_throwsException() {
assertThrows(
IllegalStateException.class,
() -> {
new AgentExecutor.Builder()
.agent(testAgent)
.app(App.builder().name("test_app").rootAgent(testAgent).build())
.sessionService(new InMemorySessionService())
.artifactService(new InMemoryArtifactService())
.build();
});
}

@Test
public void createAgentExecutor_withEmptyAgentAndApp_throwsException() {
assertThrows(
IllegalStateException.class,
() -> {
new AgentExecutor.Builder()
.sessionService(new InMemorySessionService())
.artifactService(new InMemoryArtifactService())
.build();
});
}

private static final class TestAgent extends BaseAgent {
private final Flowable<Event> eventsToEmit = Flowable.empty();

TestAgent() {
// BaseAgent constructor: name, description, examples, tools, model
super("test_agent", "test", ImmutableList.of(), null, null);
}

@Override
protected Flowable<Event> runAsyncImpl(InvocationContext invocationContext) {
return eventsToEmit;
}

@Override
protected Flowable<Event> runLiveImpl(InvocationContext invocationContext) {
return eventsToEmit;
}
}
}
18 changes: 12 additions & 6 deletions core/src/main/java/com/google/adk/agents/CallbackContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,20 @@ public Single<List<String>> listArtifacts() {
.map(ListArtifactsResponse::filenames);
}

/** Loads the latest version of an artifact from the service. */
public Maybe<Part> loadArtifact(String filename) {
return loadArtifact(filename, Optional.empty());
}

/** Loads a specific version of an artifact from the service. */
public Maybe<Part> loadArtifact(String filename, int version) {
return loadArtifact(filename, Optional.of(version));
}

/**
* Loads an artifact from the artifact service associated with the current session.
*
* @param filename Artifact file name.
* @param version Artifact version (optional).
* @return loaded part, or empty if not found.
* @throws IllegalStateException if the artifact service is not initialized.
* @deprecated Use {@link #loadArtifact(String)} or {@link #loadArtifact(String, int)} instead.
*/
@Deprecated
public Maybe<Part> loadArtifact(String filename, Optional<Integer> version) {
if (invocationContext.artifactService() == null) {
throw new IllegalStateException("Artifact service is not initialized.");
Expand Down
31 changes: 18 additions & 13 deletions core/src/main/java/com/google/adk/agents/LlmAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -755,21 +755,36 @@ public Single<Map.Entry<String, Boolean>> canonicalGlobalInstruction(ReadonlyCon
throw new IllegalStateException("Unknown Instruction subtype: " + globalInstruction.getClass());
}

/**
* @deprecated Use {@link #canonicalTools(ReadonlyContext)} instead.
*/
@Deprecated
public Flowable<BaseTool> canonicalTools(Optional<ReadonlyContext> context) {
return canonicalTools(context.orElse(null));
}

/**
* Constructs the list of tools for this agent based on the {@link #tools} field.
*
* <p>This method is only for use by Agent Development Kit.
* @return The resolved list of tools as a {@link Single} wrapped list of {@link BaseTool}.
*/
public Flowable<BaseTool> canonicalTools() {
return canonicalTools((ReadonlyContext) null);
}

/**
* Constructs the list of tools for this agent based on the {@link #tools} field.
*
* @param context The context to retrieve the session state.
* @return The resolved list of tools as a {@link Single} wrapped list of {@link BaseTool}.
*/
public Flowable<BaseTool> canonicalTools(Optional<ReadonlyContext> context) {
public Flowable<BaseTool> canonicalTools(@Nullable ReadonlyContext context) {
List<Flowable<BaseTool>> toolFlowables = new ArrayList<>();
for (Object toolOrToolset : toolsUnion) {
if (toolOrToolset instanceof BaseTool baseTool) {
toolFlowables.add(Flowable.just(baseTool));
} else if (toolOrToolset instanceof BaseToolset baseToolset) {
toolFlowables.add(baseToolset.getTools(context.orElse(null)));
toolFlowables.add(baseToolset.getTools(context));
} else {
throw new IllegalArgumentException(
"Object in tools list is not of a supported type: "
Expand All @@ -779,16 +794,6 @@ public Flowable<BaseTool> canonicalTools(Optional<ReadonlyContext> context) {
return Flowable.concat(toolFlowables);
}

/** Overload of canonicalTools that defaults to an empty context. */
public Flowable<BaseTool> canonicalTools() {
return canonicalTools(Optional.empty());
}

/** Convenience overload of canonicalTools that accepts a non-optional ReadonlyContext. */
public Flowable<BaseTool> canonicalTools(ReadonlyContext context) {
return canonicalTools(Optional.ofNullable(context));
}

public Instruction instruction() {
return instruction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,26 @@ Single<Integer> saveArtifact(
default Single<Part> saveAndReloadArtifact(
String appName, String userId, String sessionId, String filename, Part artifact) {
return saveArtifact(appName, userId, sessionId, filename, artifact)
.flatMap(
version ->
loadArtifact(appName, userId, sessionId, filename, Optional.of(version))
.toSingle());
.flatMap(version -> loadArtifact(appName, userId, sessionId, filename, version).toSingle());
}

/** Loads the latest version of an artifact from the service. */
default Maybe<Part> loadArtifact(
String appName, String userId, String sessionId, String filename) {
return loadArtifact(appName, userId, sessionId, filename, Optional.empty());
}

/** Loads a specific version of an artifact from the service. */
default Maybe<Part> loadArtifact(
String appName, String userId, String sessionId, String filename, int version) {
return loadArtifact(appName, userId, sessionId, filename, Optional.of(version));
}

/**
* Gets an artifact.
*
* @param appName the app name
* @param userId the user ID
* @param sessionId the session ID
* @param filename the filename
* @param version Optional version number. If null, loads the latest version.
* @return the artifact or empty if not found
* @deprecated Use {@link #loadArtifact(String, String, String, String)} or {@link
* #loadArtifact(String, String, String, String, int)} instead.
*/
@Deprecated
Maybe<Part> loadArtifact(
String appName, String userId, String sessionId, String filename, Optional<Integer> version);

Expand Down
Loading
Loading