From 429ffb0e55ec9889718f0f0128f1ed6cddcd810c Mon Sep 17 00:00:00 2001 From: SeanChinJunKai Date: Wed, 9 Apr 2025 00:57:56 +0800 Subject: [PATCH 1/3] feat: Add audio type according to 2025-03-26 spec --- .../modelcontextprotocol/kotlin/sdk/types.kt | 37 +++++++++++++++---- .../kotlin/sdk/types.util.kt | 8 ++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index d2fa2cb..acf3b87 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -886,8 +886,8 @@ public sealed interface PromptMessageContent { /** * Represents prompt message content that is either text or an image. */ -@Serializable(with = PromptMessageContentTextOrImagePolymorphicSerializer::class) -public sealed interface PromptMessageContentTextOrImage : PromptMessageContent +@Serializable(with = PromptMessageContentMultimodalPolymorphicSerializer::class) +public sealed interface PromptMessageContentMultimodal : PromptMessageContent /** * Text provided to or from an LLM. @@ -898,7 +898,7 @@ public data class TextContent( * The text content of the message. */ val text: String? = null, -) : PromptMessageContentTextOrImage { +) : PromptMessageContentMultimodal { override val type: String = TYPE public companion object { @@ -920,7 +920,7 @@ public data class ImageContent( * The MIME type of the image. Different providers may support different image types. */ val mimeType: String, -) : PromptMessageContentTextOrImage { +) : PromptMessageContentMultimodal { override val type: String = TYPE public companion object { @@ -928,13 +928,36 @@ public data class ImageContent( } } +/** + * Audio provided to or from an LLM. + */ +@Serializable +public data class AudioContent( + /** + * The base64-encoded audio data. + */ + val data: String, + + /** + * The MIME type of the audio. Different providers may support different audio types. + */ + val mimeType: String, +) : PromptMessageContentMultimodal { + override val type: String = TYPE + + public companion object { + public const val TYPE: String = "audio" + } +} + + /** * An image provided to or from an LLM. */ @Serializable public data class UnknownContent( override val type: String, -) : PromptMessageContentTextOrImage +) : PromptMessageContentMultimodal /** * The contents of a resource, embedded into a prompt or tool call result. @@ -1204,7 +1227,7 @@ public class ModelPreferences( @Serializable public data class SamplingMessage( val role: Role, - val content: PromptMessageContentTextOrImage, + val content: PromptMessageContentMultimodal, ) /** @@ -1286,7 +1309,7 @@ public data class CreateMessageResult( */ val stopReason: StopReason? = null, val role: Role, - val content: PromptMessageContentTextOrImage, + val content: PromptMessageContentMultimodal, override val _meta: JsonObject = EmptyJsonObject, ) : ClientResult diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt index cb7b100..53f21d0 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt @@ -80,17 +80,19 @@ internal object PromptMessageContentPolymorphicSerializer : ImageContent.TYPE -> ImageContent.serializer() TextContent.TYPE -> TextContent.serializer() EmbeddedResource.TYPE -> EmbeddedResource.serializer() + AudioContent.TYPE -> AudioContent.serializer() else -> UnknownContent.serializer() } } } -internal object PromptMessageContentTextOrImagePolymorphicSerializer : - JsonContentPolymorphicSerializer(PromptMessageContentTextOrImage::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { +internal object PromptMessageContentMultimodalPolymorphicSerializer : + JsonContentPolymorphicSerializer(PromptMessageContentMultimodal::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { return when (element.jsonObject.getValue("type").jsonPrimitive.content) { ImageContent.TYPE -> ImageContent.serializer() TextContent.TYPE -> TextContent.serializer() + AudioContent.TYPE -> AudioContent.serializer() else -> UnknownContent.serializer() } } From df57c946943c12102ffefd9967eb9b52664bfc03 Mon Sep 17 00:00:00 2001 From: SeanChinJunKai Date: Thu, 10 Apr 2025 08:07:42 +0800 Subject: [PATCH 2/3] chore: update documentation --- .../kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index acf3b87..576673f 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -884,7 +884,7 @@ public sealed interface PromptMessageContent { } /** - * Represents prompt message content that is either text or an image. + * Represents prompt message content that is either text, image or audio. */ @Serializable(with = PromptMessageContentMultimodalPolymorphicSerializer::class) public sealed interface PromptMessageContentMultimodal : PromptMessageContent @@ -952,7 +952,7 @@ public data class AudioContent( /** - * An image provided to or from an LLM. + * Unknown content provided to or from an LLM. */ @Serializable public data class UnknownContent( From 95a28575775455900883e9c48889ad9a7dfa79cb Mon Sep 17 00:00:00 2001 From: SeanChinJunKai Date: Sat, 12 Apr 2025 15:23:15 +0800 Subject: [PATCH 3/3] feat: add unit test --- .../kotlin/AudioContentSerializationTest.kt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/commonTest/kotlin/AudioContentSerializationTest.kt diff --git a/src/commonTest/kotlin/AudioContentSerializationTest.kt b/src/commonTest/kotlin/AudioContentSerializationTest.kt new file mode 100644 index 0000000..6745972 --- /dev/null +++ b/src/commonTest/kotlin/AudioContentSerializationTest.kt @@ -0,0 +1,34 @@ +package io.modelcontextprotocol.kotlin.sdk + +import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import kotlinx.serialization.encodeToString +import kotlin.test.Test +import kotlin.test.assertEquals + +class AudioContentSerializationTest { + + private val audioContentJson = """ + { + "data": "base64-encoded-audio-data", + "mimeType": "audio/wav", + "type": "audio" + } + """.trimIndent() + + private val audioContent = AudioContent( + data = "base64-encoded-audio-data", + mimeType = "audio/wav" + ) + + @Test + fun `should serialize audio content`() { + McpJson.encodeToString(audioContent) shouldEqualJson audioContentJson + } + + @Test + fun `should deserialize audio content`() { + val content = McpJson.decodeFromString(audioContentJson) + assertEquals(expected = audioContent, actual = content) + } +} \ No newline at end of file