Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/src/main/java/com/google/adk/sessions/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@ public Builder events(List<Event> events) {
return this;
}

/**
* Backs {@link Session#events()} with the given list directly, <b>without copying</b>, so the
* session reflects a caller-owned live or converting view (e.g. an engine-interop adapter
* exposing another framework's session, whose events grow as that engine appends).
*
* <p>For framework and interop adapters; ordinary code should use {@link #events(List)}, which
* defensively copies. The caller keeps ownership, so the list also keeps its own semantics: it
* is responsible for thread-safety, a read-only view throws on {@code add}, and {@code
* synchronized (session.events())} no longer excludes the owner's appends.
*/
@CanIgnoreReturnValue
public Builder eventsView(List<Event> events) {
this.events = events;
return this;
}

@CanIgnoreReturnValue
public Builder lastUpdateTime(Instant lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<module>tutorials/city-time-weather</module>
<module>tutorials/live-audio-single-agent</module>
<module>a2a</module>
<module>tokt</module>
</modules>

<properties>
Expand Down Expand Up @@ -67,6 +68,10 @@
<truth.version>1.4.5</truth.version>
<jspecify.version>1.0.0</jspecify.version>
<rxjava.version>3.1.12</rxjava.version>
<!-- ADK Kotlin engine interop (google-adk-tokt module). -->
<kotlin.version>2.1.20</kotlin.version>
<kotlinx-coroutines.version>1.11.0</kotlinx-coroutines.version>
<adk-kotlin.version>0.6.0</adk-kotlin.version>
<reactor-core.version>3.7.0</reactor-core.version>
<wiremock.version>2.35.1</wiremock.version>
<assertj.version>3.27.7</assertj.version>
Expand Down
142 changes: 142 additions & 0 deletions tokt/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-parent</artifactId>
<version>1.7.1-SNAPSHOT</version><!-- {x-version-update:google-adk:current} -->
</parent>

<artifactId>google-adk-tokt</artifactId>
<name>Agent Development Kit - Kotlin engine interop</name>
<description>One-way interop that adapts ADK Java agents, tools, toolsets, plugins, services, and models so they can run on the ADK Kotlin engine.</description>

<dependencies>
<!-- ADK Java facade types being adapted. -->
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>${project.version}</version>
</dependency>
<!-- ADK Kotlin engine the Java types are adapted to. -->
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-kotlin-core-jvm</artifactId>
<version>${adk-kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.google.genai</groupId>
<artifactId>google-genai</artifactId>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.0.0-jre</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core-jvm</artifactId>
<version>${kotlinx-coroutines.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-rx3</artifactId>
<version>${kotlinx-coroutines.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactive</artifactId>
<version>${kotlinx-coroutines.version}</version>
</dependency>

<!-- Test. -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Kotlin sources live under src/main/java (mirroring the Blaze layout), so point the
Kotlin compiler at those directories explicitly. -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
<args>
<arg>-opt-in=kotlin.time.ExperimentalTime</arg>
</args>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
155 changes: 155 additions & 0 deletions tokt/src/main/java/com/google/adk/tokt/JavaAdkToKt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* 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.tokt

import com.google.adk.agents.BaseAgent as JavaBaseAgent
import com.google.adk.artifacts.BaseArtifactService as JavaArtifactService
import com.google.adk.kt.agents.BaseAgent as KtBaseAgent
import com.google.adk.kt.artifacts.ArtifactService as KtArtifactService
import com.google.adk.kt.memory.MemoryService as KtMemoryService
import com.google.adk.kt.models.Model as KtModel
import com.google.adk.kt.plugins.Plugin as KtPlugin
import com.google.adk.kt.sessions.SessionService as KtSessionService
import com.google.adk.kt.tools.BaseTool as KtBaseTool
import com.google.adk.kt.tools.Toolset as KtToolset
import com.google.adk.memory.BaseMemoryService as JavaMemoryService
import com.google.adk.models.BaseLlm as JavaBaseLlm
import com.google.adk.plugins.Plugin as JavaPlugin
import com.google.adk.sessions.BaseSessionService as JavaSessionService
import com.google.adk.tokt.adapters.JavaModelToKt
import com.google.adk.tokt.adapters.JavaPluginToKt
import com.google.adk.tokt.adapters.JavaToolToKt
import com.google.adk.tokt.adapters.JavaToolsetToKt
import com.google.adk.tokt.adapters.javaAgentAsKt
import com.google.adk.tokt.services.javaArtifactServiceAsKt
import com.google.adk.tokt.services.javaMemoryServiceAsKt
import com.google.adk.tokt.services.javaSessionServiceAsKt
import com.google.adk.tools.BaseTool as JavaBaseTool
import com.google.adk.tools.BaseToolset as JavaBaseToolset

/**
* Forward interop entry point: adapt ADK Java agents, tools, toolsets, plugins, services, and
* models so they can run on the ADK Kotlin engine. [asKtAgent] adapts a whole Java agent (it runs
* its own flow internally); alternatively wrap the individual Java SPI pieces into a Kotlin
* `LlmAgent`.
*
* **Session is a live view.** An adapted Java agent / tool / toolset / plugin sees the session
* through `context.session()`, whose `events()` and `state()` read through live to the current
* Kotlin session (converting on access), so reads always reflect events the runner appended and
* state a tool set -- including earlier in the same turn. The `Session` may be held across calls:
* its `events()` (read-only) and `state()` never go stale, though `lastUpdateTime` is captured when
* the context is built. State *writes* via `toolContext.state()` / `callbackContext.state()` still
* propagate to the Kotlin engine through the live actions view. Setting `branch` on a bridged
* context is a no-op rather than an error: the Kotlin branch is immutable.
*
* **What does not cross.** The two type systems do not line up everywhere, and where the Kotlin
* side has no counterpart the value is dropped:
* - A genai `Part` carrying only `executableCode` or `codeExecutionResult` -- what
* `BuiltInCodeExecutionTool` produces -- has no Kotlin equivalent, so the whole part is dropped
* from the content. `PartCodec` logs this once per kind.
* - Kotlin `EventActions.agentState` and `rewindBeforeInvocationId` have no Java equivalent, so
* they are lost when events round-trip through an adapted Java session service
* ([asKtSessionService]). In-process this is masked, because the engine also keeps its own live
* session; it bites when a *persistent* Java service is read back in a new process, where a
* resumable workflow then restarts instead of resuming and a rewind marker is forgotten. Keep the
* Kotlin session service if you need either.
* - Smaller, same reason: `Event.cacheMetadata`, `LlmResponse.cacheMetadata`, and `MemoryEntry`'s
* `id` / `customMetadata`.
*/
object JavaAdkToKt {

/**
* Adapts a whole ADK Java agent so it runs on the Kotlin engine. The Java agent runs its own flow
* (model calls, tools, its own callbacks, and sub-agent transfers within its Java subtree); the
* Kotlin runner drives and persists it as an opaque leaf agent. Live mode is not supported, and
* being opaque costs plugin model/tool callbacks, streaming, and follow-up turn routing inside it
* -- see `JavaAgentToKt` for the full list.
*/
@JvmStatic fun asKtAgent(javaAgent: JavaBaseAgent): KtBaseAgent = javaAgentAsKt(javaAgent)

/**
* Adapts a whole collection of ADK Java agents (e.g. to hand a Kotlin agent its `subAgents`).
* Kept alongside [asKtAgent] for Java callers, as with [asKtTools].
*/
@JvmStatic
fun asKtAgents(javaAgents: List<JavaBaseAgent>): List<KtBaseAgent> = javaAgents.map {
asKtAgent(it)
}

/** Adapts an ADK Java tool. Its `ToolContext.session()` is a live view; see [JavaAdkToKt]. */
@JvmStatic fun asKtTool(javaTool: JavaBaseTool): KtBaseTool = JavaToolToKt(javaTool)

/**
* Adapts a whole collection of ADK Java tools (e.g. an `LlmAgent`'s `tools`). Kept alongside
* [asKtTool] for Java callers, who would otherwise write `stream().map(...).toList()`.
*/
@JvmStatic
fun asKtTools(javaTools: List<JavaBaseTool>): List<KtBaseTool> = javaTools.map { asKtTool(it) }

/**
* Adapts an ADK Java toolset. Its `ReadonlyContext` session is a live view; see [JavaAdkToKt].
*/
@JvmStatic fun asKtToolset(javaToolset: JavaBaseToolset): KtToolset = JavaToolsetToKt(javaToolset)

/** Adapts a whole collection of ADK Java toolsets. */
@JvmStatic
fun asKtToolsets(javaToolsets: List<JavaBaseToolset>): List<KtToolset> = javaToolsets.map {
asKtToolset(it)
}

/**
* Adapts an ADK Java plugin. Its callback contexts expose a live session view; see [JavaAdkToKt].
*/
@JvmStatic fun asKtPlugin(javaPlugin: JavaPlugin): KtPlugin = JavaPluginToKt(javaPlugin)

/** Adapts a whole collection of ADK Java plugins (e.g. a `Runner`'s `plugins`). */
@JvmStatic
fun asKtPlugins(javaPlugins: List<JavaPlugin>): List<KtPlugin> = javaPlugins.map {
asKtPlugin(it)
}

/**
* Adapts an ADK Java model so the Kotlin engine can call it. Callbacks it triggers see the live
* session view; see [JavaAdkToKt].
*/
@JvmStatic fun asKtModel(javaLlm: JavaBaseLlm): KtModel = JavaModelToKt(javaLlm)

/**
* Adapts an ADK Java session service for the Kotlin engine. Passing a service that is itself an
* adapted Kotlin one returns the original rather than stacking a second adapter. Note the
* `agentState` / `rewindBeforeInvocationId` loss described in [JavaAdkToKt].
*/
@JvmStatic
fun asKtSessionService(service: JavaSessionService): KtSessionService =
javaSessionServiceAsKt(service)

/**
* Adapts an ADK Java artifact service for the Kotlin engine, unwrapping a round-tripped Kotlin
* one rather than stacking adapters. An empty or unmapped artifact part is rejected outright.
*/
@JvmStatic
fun asKtArtifactService(service: JavaArtifactService): KtArtifactService =
javaArtifactServiceAsKt(service)

/**
* Adapts an ADK Java memory service for the Kotlin engine, unwrapping a round-tripped Kotlin one
* rather than stacking adapters.
*/
@JvmStatic
fun asKtMemoryService(service: JavaMemoryService): KtMemoryService =
javaMemoryServiceAsKt(service)
}
39 changes: 39 additions & 0 deletions tokt/src/main/java/com/google/adk/tokt/adapters/AgentAdapters.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.tokt.adapters

import com.google.adk.agents.BaseAgent as JavaBaseAgent
import com.google.adk.kt.agents.BaseAgent as KtBaseAgent

/**
* The Java view of a Kotlin agent, for handing to a Java plugin callback. An agent that is itself
* an adapted Java one is unwrapped to the original rather than wrapped again, so a Java plugin
* pairing callbacks by agent identity sees the instance it knows.
*
* Note the unwrapped case returns a *runnable* Java agent, so "a plugin must not run the agent it
* is given" ([KtAgentToJava]) is convention there rather than something the type enforces.
*/
internal fun ktAgentAsJava(ktAgent: KtBaseAgent): JavaBaseAgent =
(ktAgent as? JavaAgentToKt)?.javaAgent ?: KtAgentToJava(ktAgent)

/**
* The Kotlin agent for a Java one, so it can run on the engine ([JavaAgentToKt]). A Java agent that
* is itself a view of a Kotlin agent is unwrapped, so a Kt -> Java -> Kt round trip collapses to
* the original instead of running the engine through two adapters.
*/
internal fun javaAgentAsKt(javaAgent: JavaBaseAgent): KtBaseAgent =
(javaAgent as? KtAgentToJava)?.ktAgent ?: JavaAgentToKt(javaAgent)
Loading
Loading