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
2 changes: 0 additions & 2 deletions acp-model/api/acp-model.api
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,6 @@ public final class com/agentclientprotocol/model/EmbeddedResource$Companion {

public abstract class com/agentclientprotocol/model/EmbeddedResourceResource : com/agentclientprotocol/model/AcpWithMeta {
public static final field Companion Lcom/agentclientprotocol/model/EmbeddedResourceResource$Companion;
public synthetic fun <init> (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V
public static final synthetic fun write$Self (Lcom/agentclientprotocol/model/EmbeddedResourceResource;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
}

public final class com/agentclientprotocol/model/EmbeddedResourceResource$BlobResourceContents : com/agentclientprotocol/model/EmbeddedResourceResource {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@

package com.agentclientprotocol.model

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.JsonClassDiscriminator
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

/**
* Common discriminator key used for polymorphic serialization in ACP.
*/
internal const val TYPE_DISCRIMINATOR = "type"

/**
* Content blocks represent displayable information in the Agent Client Protocol.
Expand All @@ -18,10 +28,10 @@ import kotlinx.serialization.json.JsonElement
* See protocol docs: [Content](https://agentclientprotocol.com/protocol/content)
*/
@Serializable
@JsonClassDiscriminator("type")
@JsonClassDiscriminator(TYPE_DISCRIMINATOR)
public sealed class ContentBlock : AcpWithMeta {
public abstract val annotations: Annotations?

/**
* Plain text content
*
Expand Down Expand Up @@ -101,7 +111,7 @@ public sealed class ContentBlock : AcpWithMeta {
/**
* Resource content that can be embedded in a message.
*/
@Serializable
@Serializable(with = EmbeddedResourceResourceSerializer::class)
public sealed class EmbeddedResourceResource : AcpWithMeta {
/**
* Text-based resource contents.
Expand All @@ -128,6 +138,28 @@ public sealed class EmbeddedResourceResource : AcpWithMeta {
) : EmbeddedResourceResource()
}

/**
* Embedded resources are discriminator-less in the protocol; choose subtype by fields if
* discriminator is absent, but still honor an explicit discriminator when provided.
*/
internal object EmbeddedResourceResourceSerializer :
JsonContentPolymorphicSerializer<EmbeddedResourceResource>(EmbeddedResourceResource::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<EmbeddedResourceResource> {
val obj = element.jsonObject

val explicitType = obj[TYPE_DISCRIMINATOR]?.jsonPrimitive?.content
when (explicitType) {
EmbeddedResourceResource.TextResourceContents::class.simpleName -> return EmbeddedResourceResource.TextResourceContents.serializer()
EmbeddedResourceResource.BlobResourceContents::class.simpleName -> return EmbeddedResourceResource.BlobResourceContents.serializer()
}

if (EmbeddedResourceResource.TextResourceContents::text.name in obj) return EmbeddedResourceResource.TextResourceContents.serializer()
if (EmbeddedResourceResource.BlobResourceContents::blob.name in obj) return EmbeddedResourceResource.BlobResourceContents.serializer()

throw SerializationException("Cannot determine EmbeddedResourceResource type; expected '${EmbeddedResourceResource.TextResourceContents::text.name}' or '${EmbeddedResourceResource.BlobResourceContents::blob.name}'")
}
}

/**
* The contents of a resource, embedded into a prompt or tool call result.
*/
Expand All @@ -151,4 +183,4 @@ public data class ResourceLink(
val title: String? = null,
val annotations: Annotations? = null,
override val _meta: JsonElement? = null
) : AcpWithMeta
) : AcpWithMeta
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import kotlinx.serialization.json.JsonElement
* Specifies how the agent should collect input for this command.
*/
@Serializable
@JsonClassDiscriminator("type")
@JsonClassDiscriminator(TYPE_DISCRIMINATOR)
public sealed class AvailableCommandInput {
/**
* All text typed after the command name is provided as unstructured input.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.agentclientprotocol.model

import com.agentclientprotocol.rpc.ACPJson
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class EmbeddedResourceResourceTest {

@Test
fun `decodes resource content without discriminator`() {
val payload = """
{
"type": "resource",
"resource": {
"text": "hello",
"uri": "file:///tmp/example.txt"
}
}
""".trimIndent()

val block = ACPJson.decodeFromString(ContentBlock.serializer(), payload)

assertTrue(block is ContentBlock.Resource)
val resource = block.resource
assertTrue(resource is EmbeddedResourceResource.TextResourceContents)
assertEquals("hello", resource.text)
assertEquals("file:///tmp/example.txt", resource.uri)
}

@Test
fun `decodes blob resource content without discriminator`() {
val payload = """
{
"type": "resource",
"resource": {
"blob": "ZGF0YQ==",
"mimeType": "application/octet-stream",
"uri": "file:///tmp/data.bin"
}
}
""".trimIndent()

val block = ACPJson.decodeFromString(ContentBlock.serializer(), payload)

assertTrue(block is ContentBlock.Resource)
val resource = block.resource
assertTrue(resource is EmbeddedResourceResource.BlobResourceContents)
assertEquals("ZGF0YQ==", resource.blob)
assertEquals("application/octet-stream", resource.mimeType)
assertEquals("file:///tmp/data.bin", resource.uri)
}
}
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.9.1"
private val baseVersion = "0.9.2"

allprojects {
group = "com.agentclientprotocol"
Expand Down
Loading