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
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ import kotlinx.coroutines.withContext
* Exposes an ADK Java [JavaPlugin] as a Kotlin [KtPlugin] so a Java app's plugins run on the Kotlin
* runner. Each callback converts the Kotlin context to its Java view, invokes the plugin off the
* engine dispatcher, and reconciles the actions it wrote back onto the Kotlin side. Tool-level
* callbacks apply only to Kt-backed Java tools ([JavaToolToKt]).
* callbacks fire for every tool: an adapted Java tool ([JavaToolToKt]) unwraps to its original
* instance, and a native Kotlin tool is presented through an inspection-only [KtToolToJava] view
* (see [ktToolAsJava]) -- so the plugin can read the tool and write actions, but must not run it.
*/
internal class JavaPluginToKt(internal val plugin: JavaPlugin) : KtPlugin {

Expand Down Expand Up @@ -189,14 +191,15 @@ internal class JavaPluginToKt(internal val plugin: JavaPlugin) : KtPlugin {
else CallbackChoice.Continue(Unit)
}

// Tool-level callbacks (only Kt-backed Java tools).
// Tool-level callbacks. An adapted Java tool fires natively; a native Kotlin tool is passed
// through an inspection-only Java view ([KtToolToJava]).

override suspend fun beforeTool(
context: KtToolContext,
tool: KtBaseTool,
args: Map<String, Any?>,
): CallbackChoice<Map<String, Any?>, Map<String, Any?>> {
val javaTool = (tool as? JavaToolToKt)?.javaTool ?: return CallbackChoice.Continue(args)
val javaTool = ktToolAsJava(tool)
val javaContext = ktToolContextToJava(context)
val mutableArgs = args.toMutableMap()
val override = onIo { plugin.beforeToolCallback(javaTool, mutableArgs, javaContext) }
Expand All @@ -211,7 +214,7 @@ internal class JavaPluginToKt(internal val plugin: JavaPlugin) : KtPlugin {
args: Map<String, Any?>,
result: Map<String, Any?>,
): Map<String, Any?> {
val javaTool = (tool as? JavaToolToKt)?.javaTool ?: return result
val javaTool = ktToolAsJava(tool)
val javaContext = ktToolContextToJava(context)
val override = onIo { plugin.afterToolCallback(javaTool, args, javaContext, result) }
reconcileActionsToKt(javaContext.actions(), context.actions)
Expand All @@ -224,7 +227,7 @@ internal class JavaPluginToKt(internal val plugin: JavaPlugin) : KtPlugin {
args: Map<String, Any?>,
error: Throwable,
): CallbackChoice<Unit, Map<String, Any?>> {
val javaTool = (tool as? JavaToolToKt)?.javaTool ?: return CallbackChoice.Continue(Unit)
val javaTool = ktToolAsJava(tool)
val javaContext = ktToolContextToJava(context)
val fallback = onIo { plugin.onToolErrorCallback(javaTool, args, javaContext, error) }
reconcileActionsToKt(javaContext.actions(), context.actions)
Expand Down
67 changes: 67 additions & 0 deletions tokt/src/main/kotlin/com/google/adk/tokt/adapters/KtToolToJava.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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.kt.tools.BaseTool as KtBaseTool
import com.google.adk.tokt.codecs.FunctionDeclarationCodec
import com.google.adk.tools.BaseTool as JavaBaseTool
import com.google.adk.tools.ToolContext as JavaToolContext
import com.google.genai.types.FunctionDeclaration as GenaiFunctionDeclaration
import io.reactivex.rxjava3.core.Single
import java.util.Optional

/**
* An inspection-only Java view of a native Kotlin [KtBaseTool], so ADK Java plugin tool callbacks
* (`beforeToolCallback`, `afterToolCallback`, `onToolErrorCallback`) can read a native Kotlin tool
* as an ADK Java [JavaBaseTool]: name, description, long-running flag, declaration, and custom
* metadata. It is not meant to be executed -- the Kotlin runner is the sole driver of tool
* execution -- so [runAsync] fails loud with a descriptive error rather than doing anything.
* Mirrors [KtAgentToJava].
*/
internal class KtToolToJava(internal val ktTool: KtBaseTool) :
JavaBaseTool(ktTool.name, ktTool.description, ktTool.isLongRunning) {

init {
for ((key, value) in ktTool.customMetadata) {
setCustomMetadata(key, value)
}
}

override fun declaration(): Optional<GenaiFunctionDeclaration> =
Optional.ofNullable(ktTool.declaration()?.let { FunctionDeclarationCodec.toJava(it) })

@JvmSuppressWildcards
override fun runAsync(
args: Map<String, Any>,
toolContext: JavaToolContext,
): Single<Map<String, Any>> =
Single.error(
UnsupportedOperationException(
"This is an inspection-only view of a Kotlin engine tool, handed to Java plugin callbacks " +
"via ktToolAsJava; the Kotlin runner is the sole driver of tool execution, so it cannot " +
"be run from Java."
)
)
}

/**
* Presents a Kotlin [tool] to a Java plugin callback as an ADK Java [JavaBaseTool]: a round-tripped
* adapted Java tool ([JavaToolToKt]) unwraps to its original instance (so it fires natively), and a
* native Kotlin tool gets an inspection-only [KtToolToJava] view.
*/
internal fun ktToolAsJava(tool: KtBaseTool): JavaBaseTool =
(tool as? JavaToolToKt)?.javaTool ?: KtToolToJava(tool)
Loading
Loading