From ea02d541751dee04068c45c9bc1c6b341641873f Mon Sep 17 00:00:00 2001 From: Shahin Saadati Date: Thu, 20 Aug 2026 09:24:48 -0700 Subject: [PATCH] Add the Kotlin tab for driving a long-running tool to completion The call_reimbursement_tool group showed Python, TypeScript, Go and Java but not Kotlin, even though the page already carries a Kotlin tab for defining the tool a few sections earlier. Defining a long-running tool without showing how to resume it leaves the Kotlin reader at the point where the invocation pauses. The new region extends the file that already backs this page rather than adding a second one. It mirrors the Python flow: watch for the call whose id appears in Event.longRunningToolIds, keep the matching FunctionResponse, then send a copy of it back with the final status to resume the paused invocation. Scoped deliberately to that group. RequestInputTool and GetUserChoiceTool were also on this backlog row, but they have no host page in ANY language - they appear nowhere in the narrative docs, only in generated API reference - so adding a Kotlin-only section for them would invent structure rather than close a gap. Recorded for a product-docs request instead. --- docs/tools-custom/function-tools.md | 6 ++ .../tools/function-tools/LongRunningTool.kt | 56 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/docs/tools-custom/function-tools.md b/docs/tools-custom/function-tools.md index 701fde8fe6..ea4f80afb5 100644 --- a/docs/tools-custom/function-tools.md +++ b/docs/tools-custom/function-tools.md @@ -679,6 +679,12 @@ it's None) into the content of the `FunctionResponse` sent back to the LLM. --8<-- "examples/java/snippets/src/main/java/tools/LongRunningFunctionExample.java:full_code" ``` +=== "Kotlin" + + ```kotlin + --8<-- "examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt:call_reimbursement_tool" + ``` + ??? "Python complete example: File Processing Simulation" ```python diff --git a/examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt b/examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt index b6260418be..2446eaceb3 100644 --- a/examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt +++ b/examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt @@ -4,7 +4,12 @@ import com.google.adk.kt.agents.Instruction import com.google.adk.kt.agents.LlmAgent import com.google.adk.kt.annotations.Param import com.google.adk.kt.annotations.Tool +import com.google.adk.kt.events.Event import com.google.adk.kt.models.Gemini +import com.google.adk.kt.runners.InMemoryRunner +import com.google.adk.kt.types.Content +import com.google.adk.kt.types.FunctionResponse +import com.google.adk.kt.types.Part // --8<-- [start:long_running_tool] data class ReimbursementApproval( @@ -47,3 +52,54 @@ fun main() { ) } // --8<-- [end:long_running_tool] + +// --8<-- [start:call_reimbursement_tool] +private fun printText(event: Event) { + val text = event.content?.parts?.mapNotNull { it.text }?.joinToString("").orEmpty() + if (text.isNotEmpty()) println("[${event.author}]: $text") +} + +suspend fun callReimbursementAgent( + runner: InMemoryRunner, + userId: String, + sessionId: String, + query: String, +) { + var pendingCallId: String? = null + var pendingResponse: FunctionResponse? = null + + runner + .runAsync( + userId = userId, + sessionId = sessionId, + newMessage = Content(role = "user", parts = listOf(Part(text = query))), + ).collect { event -> + val callId = pendingCallId + if (callId == null) { + // A long-running call is the one whose id the event lists in longRunningToolIds. + pendingCallId = + event + .functionCalls() + .firstOrNull { it.id != null && it.id in event.longRunningToolIds } + ?.id + } else { + event + .functionResponses() + .firstOrNull { it.id == callId } + ?.let { pendingResponse = it } + } + printText(event) + } + + // The tool returned "pending" and the invocation paused. Resume it by sending the + // outcome back as a FunctionResponse carrying the same id. + val paused = pendingResponse ?: return + val updated = paused.copy(response = mapOf("status" to "approved")) + runner + .runAsync( + userId = userId, + sessionId = sessionId, + newMessage = Content(role = "user", parts = listOf(Part(functionResponse = updated))), + ).collect(::printText) +} +// --8<-- [end:call_reimbursement_tool]