@@ -40,6 +40,7 @@ import com.google.adk.kt.apps.App as KtApp
4040import com.google.adk.kt.callbacks.AfterModelCallback as KtAfterModelCallback
4141import com.google.adk.kt.events.Event as KtEvent
4242import com.google.adk.kt.events.EventActions as KtEventActions
43+ import com.google.adk.kt.models.CacheMetadata as KtCacheMetadata
4344import com.google.adk.kt.models.LlmResponse as KtLlmResponse
4445import com.google.adk.kt.runners.InMemoryRunner as KtInMemoryRunner
4546import com.google.adk.kt.runners.Runner as KtRunner
@@ -95,6 +96,7 @@ import com.google.adk.kt.workflow.Workflow as KtWorkflow
9596import com.google.adk.memory.InMemoryMemoryService as JavaInMemoryMemoryService
9697import com.google.adk.models.BaseLlm as JavaBaseLlm
9798import com.google.adk.models.BaseLlmConnection as JavaBaseLlmConnection
99+ import com.google.adk.models.CacheMetadata as JavaCacheMetadata
98100import com.google.adk.models.LlmRequest as JavaLlmRequest
99101import com.google.adk.models.LlmResponse as JavaLlmResponse
100102import com.google.adk.plugins.BasePlugin as JavaBasePlugin
@@ -3293,6 +3295,52 @@ class KtRunnerInteropTest {
32933295 )
32943296 }
32953297
3298+ @Test
3299+ fun ktRunner_bridgedPluginReturningTheResponse_keepsItsCacheMetadata () = runBlocking {
3300+ // The event keeps the response's cache metadata; the next turn finds its cache there.
3301+ val model =
3302+ object : JavaBaseLlm (" java-model" ) {
3303+ override fun generateContent (
3304+ llmRequest : JavaLlmRequest ,
3305+ stream : Boolean ,
3306+ ): Flowable <JavaLlmResponse > =
3307+ Flowable .just(
3308+ JavaLlmResponse .builder()
3309+ .content(modelText(" cached" ))
3310+ .cacheMetadata(
3311+ JavaCacheMetadata .builder().fingerprint(" abc" ).contentsCount(2 ).build()
3312+ )
3313+ .build()
3314+ )
3315+
3316+ override fun connect (llmRequest : JavaLlmRequest ): JavaBaseLlmConnection =
3317+ throw UnsupportedOperationException ()
3318+ }
3319+ // Returning the response sends it through LlmResponseCodec to Java and back.
3320+ val passthrough =
3321+ object : JavaBasePlugin (" passthrough" ) {
3322+ override fun afterModelCallback (
3323+ callbackContext : JavaCallbackContext ,
3324+ llmResponse : JavaLlmResponse ,
3325+ ): Maybe <JavaLlmResponse > = Maybe .just(llmResponse)
3326+ }
3327+ val runner =
3328+ KtInMemoryRunner (
3329+ KtApp (
3330+ appName = " app" ,
3331+ rootAgent = KtLlmAgent (name = " a" , model = JavaAdkToKt .asKtModel(model)),
3332+ plugins = listOf (JavaAdkToKt .asKtPlugin(passthrough)),
3333+ )
3334+ )
3335+
3336+ val events = runner.turn()
3337+
3338+ assertEquals(
3339+ KtCacheMetadata (fingerprint = " abc" , contentsCount = 2 ),
3340+ events.single { it.author == " a" }.cacheMetadata,
3341+ )
3342+ }
3343+
32963344 @Test
32973345 fun ktRunner_bridgedNoOpPlugin_preservesAgentTransfer () = runBlocking {
32983346 // Regression: bridging any Java plugin must not drop the request's Kotlin-only toolsDict in
@@ -4310,6 +4358,31 @@ class KtRunnerInteropTest {
43104358 )
43114359 }
43124360
4361+ @Test
4362+ fun asKtSessionService_cacheMetadata_survivesTheJavaStore () = runBlocking {
4363+ // The next turn finds the context cache to reuse in the stored events.
4364+ val sessions = JavaAdkToKt .asKtSessionService(JavaInMemorySessionService ())
4365+ val key = KtSessionKey (" app" , " u" , " s" )
4366+ val session = sessions.createSession(key)
4367+ val fingerprintOnly = KtCacheMetadata (fingerprint = " abc" , contentsCount = 2 )
4368+ val active =
4369+ KtCacheMetadata (
4370+ fingerprint = " abc" ,
4371+ contentsCount = 2 ,
4372+ cacheName = " cachedContents/1" ,
4373+ expireTime = 2_000_000_000_000L ,
4374+ invocationsUsed = 3 ,
4375+ createdAt = 1_999_999_000_000L ,
4376+ )
4377+
4378+ for (cache in listOf (fingerprintOnly, active)) {
4379+ val unused = sessions.appendEvent(session, KtEvent (author = " model" , cacheMetadata = cache))
4380+ }
4381+
4382+ val stored = assertNotNull(sessions.getSession(key)).events.map { it.cacheMetadata }
4383+ assertEquals(listOf (fingerprintOnly, active), stored)
4384+ }
4385+
43134386 @Test
43144387 fun eventCodec_workflowFields_roundTripThroughTheJavaEvent () {
43154388 // Java code reads these fields too, so a Kotlin Content in the output crosses as a genai one.
0 commit comments