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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 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,23 @@ 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 whose events grow as the owner
* appends. The caller keeps ownership and the list's own semantics - thread-safety, and a
* read-only view throwing on {@code add} - so ordinary code should use {@link #events(List)},
* which defensively copies.
*
* @deprecated Not a real deprecation - a warn-off for application code from a seam meant for
* ADK's own framework and interop adapters; use {@link #events(List)} instead.
*/
@Deprecated
@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 @@ -70,6 +71,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>1.0.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.9.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 tools, toolsets, plugins, services, and models so they can run on the ADK Kotlin engine. The name is short for "to Kotlin", matching the JavaAdkToKt entry point.</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/kotlin; Java test sources stay under src/test/java,
compiled by the default compiler plugin. -->
<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/kotlin</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/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
29 changes: 29 additions & 0 deletions tokt/src/main/kotlin/com/google/adk/tokt/InteropDispatcher.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

/**
* The dispatcher every crossing in this module hops to.
*
* ADK Java's SPI is RxJava, which is synchronous unless the implementation says otherwise, so a
* user-authored Java tool, plugin or service may block. Running it on the engine's dispatcher would
* stall the coroutine driving the agent loop, so each adapter moves the call here.
*/
internal val InteropDispatcher: CoroutineDispatcher = Dispatchers.IO
121 changes: 121 additions & 0 deletions tokt/src/main/kotlin/com/google/adk/tokt/JavaAdkToKt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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.artifacts.BaseArtifactService as JavaArtifactService
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.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: adapts ADK Java tools, toolsets, plugins, services, and models so
* they can run on the ADK Kotlin engine. Wrap the adapted pieces in a Kotlin `LlmAgent`; this does
* not convert a whole Java agent.
*
* An adapted component behaves as it does on ADK Java. It sees the session as it currently stands,
* including events and state written earlier in the same turn, and its state, artifact and
* control-flow writes reach the engine. Blocking work is fine: calls are dispatched off the thread
* driving the agent.
*
* The interop surfaces a signal the engine cannot honor rather than silently dropping it:
* - Setting `branch` on a bridged context throws. The branch is the engine's to set.
* - A bridged plugin that overrides `onRunErrorCallback` is skipped with a warning (not run): the
* engine surfaces run-level errors through the returned event stream to the caller, not to
* plugins. The `onModelErrorCallback` and `onToolErrorCallback` recovery hooks do fire.
* - A bridged tool's or plugin's `requestedAuthConfigs` or `deletedArtifactIds` write throws - the
* engine's event actions have no equivalent. Its `skipSummarization`,
* `requestedToolConfirmations` and `agentState` writes do cross, from a tool and a plugin alike.
* - Behind an adapted Java session service ([asKtSessionService]), a resumable workflow's engine
* state (`EventActions.agentState`) crosses and is restored, so it resumes rather than restarts.
* A rewind request (`rewindBeforeInvocationId`) still does not cross - ADK Java's `EventActions`
* has no such field.
*/
object JavaAdkToKt {

/** Adapts an ADK Java tool. */
@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. */
@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. */
@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. */
@JvmStatic fun asKtModel(javaLlm: JavaBaseLlm): KtModel = JavaModelToKt(javaLlm)

/**
* Adapts an ADK Java session service for the Kotlin engine, unwrapping a round-tripped Kotlin one
* rather than stacking a second adapter. A `rewindBeforeInvocationId` does not survive, since ADK
* Java has no such field.
*/
@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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.events.EventActions as JavaEventActions
import com.google.adk.kt.events.EventActions as KtEventActions
import com.google.adk.tokt.codecs.EventCompactionCodec
import com.google.adk.tokt.codecs.ToolConfirmationCodec
import com.google.adk.tokt.codecs.agentStateFromJava
import com.google.adk.tokt.codecs.reconcileRemovedSentinels

/**
* Copies what a bridged tool or plugin callback wrote on its [JavaEventActions] onto the live
* [KtEventActions] the engine reads. Fields the live view already writes through are re-applied
* idempotently; skip-summarization, tool confirmations, agentState and compaction are carried here.
* A signal the engine cannot represent (requestedAuthConfigs, deletedArtifactIds) throws.
*/
internal fun reconcileActionsToKt(java: JavaEventActions, kt: KtEventActions) {
require(java.requestedAuthConfigs().isNullOrEmpty()) {
"EventActions.requestedAuthConfigs is not supported by the ADK Kotlin engine"
}
require(java.deletedArtifactIds().isNullOrEmpty()) {
"EventActions.deletedArtifactIds is not supported by the ADK Kotlin engine"
}
java.escalate().ifPresent { kt.escalate = it }
java.transferToAgent().ifPresent { kt.transferToAgent = it }
java.skipSummarization().ifPresent { kt.skipSummarization = it }
java.agentState().ifPresent { kt.agentState = agentStateFromJava(it) }
java.compaction().ifPresent { kt.compaction = EventCompactionCodec.fromJava(it) }
kt.endOfAgent = kt.endOfAgent || java.endOfAgent()
// Merge deltas only when the tool replaced the actions (else they are the same live Kotlin map).
if (java.stateDelta() !== kt.stateDelta) kt.stateDelta.putAll(java.stateDelta())
if (java.artifactDelta() !== kt.artifactDelta) kt.artifactDelta.putAll(java.artifactDelta())
// A Java state removal writes the Java sentinel into the delta; map it to the Kotlin one.
reconcileRemovedSentinels(kt.stateDelta)
for ((id, confirmation) in java.requestedToolConfirmations()) {
kt.requestedToolConfirmations[id] = ToolConfirmationCodec.fromJava(confirmation)
}
}
Loading
Loading