From 683656818aa06912a264768b5b58150d03c49dec Mon Sep 17 00:00:00 2001 From: "Artem.Bukhonov" Date: Mon, 22 Dec 2025 14:59:39 +0100 Subject: [PATCH] Add default serializer for AvailableCommandInput without discriminator Fixes #43 --- .../model/SessionUpdate.kt | 3 ++ .../com/agentclientprotocol/rpc/JsonRpc.kt | 11 ++++ ...eResourceTest.kt => SerializationTests.kt} | 51 ++++++++++++++++++- build.gradle.kts | 2 +- 4 files changed, 65 insertions(+), 2 deletions(-) rename acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/{EmbeddedResourceResourceTest.kt => SerializationTests.kt} (50%) diff --git a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/SessionUpdate.kt b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/SessionUpdate.kt index ee0574a..445b2eb 100644 --- a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/SessionUpdate.kt +++ b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/SessionUpdate.kt @@ -13,6 +13,9 @@ import kotlinx.serialization.json.JsonElement * Input specification for a command. * * Specifies how the agent should collect input for this command. + * + * Note: Default deserializer for this sealed class is configured in [com.agentclientprotocol.rpc.ACPJson] + * to fall back to [Unstructured] when no type discriminator is present. */ @Serializable @JsonClassDiscriminator(TYPE_DISCRIMINATOR) diff --git a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt index eaf103c..24d0755 100644 --- a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt +++ b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt @@ -2,6 +2,7 @@ package com.agentclientprotocol.rpc +import com.agentclientprotocol.model.AvailableCommandInput import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -16,6 +17,8 @@ import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic import kotlin.jvm.JvmInline /** @@ -170,6 +173,13 @@ public enum class JsonRpcErrorCode(public val code: Int, public val message: Str RESOURCE_NOT_FOUND(-32002, "Resource not found") } +private val acpSerializersModule = SerializersModule { + polymorphic(AvailableCommandInput::class) { + subclass(AvailableCommandInput.Unstructured::class, AvailableCommandInput.Unstructured.serializer()) + defaultDeserializer { AvailableCommandInput.Unstructured.serializer() } + } +} + @OptIn(ExperimentalSerializationApi::class) public val ACPJson: Json by lazy { Json { @@ -177,6 +187,7 @@ public val ACPJson: Json by lazy { encodeDefaults = true isLenient = true explicitNulls = false + serializersModule = acpSerializersModule } } diff --git a/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/EmbeddedResourceResourceTest.kt b/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/SerializationTests.kt similarity index 50% rename from acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/EmbeddedResourceResourceTest.kt rename to acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/SerializationTests.kt index 111587f..836b78f 100644 --- a/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/EmbeddedResourceResourceTest.kt +++ b/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/SerializationTests.kt @@ -5,7 +5,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -class EmbeddedResourceResourceTest { +class SerializationTests { @Test fun `decodes resource content without discriminator`() { @@ -50,4 +50,53 @@ class EmbeddedResourceResourceTest { assertEquals("application/octet-stream", resource.mimeType) assertEquals("file:///tmp/data.bin", resource.uri) } + + @Test + fun `decodes AvailableCommandInput without discriminator defaults to Unstructured`() { + val payload = """ + { + "hint": "optional custom review instructions" + } + """.trimIndent() + + val input = ACPJson.decodeFromString(AvailableCommandInput.serializer(), payload) + + assertTrue(input is AvailableCommandInput.Unstructured) + assertEquals("optional custom review instructions", input.hint) + } + + @Test + fun `decodes AvailableCommandInput with explicit discriminator`() { + val payload = """ + { + "type": "unstructured", + "hint": "enter your query" + } + """.trimIndent() + + val input = ACPJson.decodeFromString(AvailableCommandInput.serializer(), payload) + + assertTrue(input is AvailableCommandInput.Unstructured) + assertEquals("enter your query", input.hint) + } + + @Test + fun `decodes AvailableCommand with input without discriminator`() { + val payload = """ + { + "name": "review", + "description": "Review code", + "input": { + "hint": "optional custom review instructions" + } + } + """.trimIndent() + + val command = ACPJson.decodeFromString(AvailableCommand.serializer(), payload) + + assertEquals("review", command.name) + assertEquals("Review code", command.description) + assertTrue(command.input is AvailableCommandInput.Unstructured) + assertEquals("optional custom review instructions", command.input.hint) + } } diff --git a/build.gradle.kts b/build.gradle.kts index 4eebce5..b011328 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { private val buildNumber: String? = System.getenv("GITHUB_RUN_NUMBER") private val isReleasePublication = System.getenv("RELEASE_PUBLICATION")?.toBoolean() ?: false -private val baseVersion = "0.10.1" +private val baseVersion = "0.10.2" allprojects { group = "com.agentclientprotocol"