Skip to content

Commit 222223a

Browse files
committed
Fix deserialization
1 parent eb83e01 commit 222223a

3 files changed

Lines changed: 29 additions & 7 deletions

File tree

kotlin/kotlin-acp/src/commonMain/kotlin/io/agentclientprotocol/rpc/JsonRpc.kt

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ package io.agentclientprotocol.rpc
44

55
import kotlinx.serialization.ExperimentalSerializationApi
66
import kotlinx.serialization.Serializable
7-
import kotlinx.serialization.json.ClassDiscriminatorMode
8-
import kotlinx.serialization.json.Json
9-
import kotlinx.serialization.json.JsonElement
7+
import kotlinx.serialization.json.*
108
import kotlin.jvm.JvmInline
119

1210
/**
@@ -85,7 +83,30 @@ public val ACPJson: Json by lazy {
8583
ignoreUnknownKeys = true
8684
encodeDefaults = true
8785
isLenient = true
88-
classDiscriminatorMode = ClassDiscriminatorMode.POLYMORPHIC
8986
explicitNulls = false
9087
}
88+
}
89+
90+
/**
91+
* Helper function to decode JSON-RPC messages based on field presence.
92+
* JSON-RPC 2.0 spec distinguishes message types by which fields are present:
93+
* - Response: has "id" and ("result" or "error")
94+
* - Request: has "id" and "method"
95+
* - Notification: has "method" but no "id"
96+
*/
97+
public fun decodeJsonRpcMessage(jsonString: String): JsonRpcMessage {
98+
val element = ACPJson.parseToJsonElement(jsonString)
99+
require(element is JsonObject) { "Expected JSON object" }
100+
101+
val hasId = element.containsKey("id")
102+
val hasMethod = element.containsKey("method")
103+
val hasResult = element.containsKey("result")
104+
val hasError = element.containsKey("error")
105+
106+
return when {
107+
hasId && (hasResult || hasError) -> ACPJson.decodeFromJsonElement(JsonRpcResponse.serializer(), element)
108+
hasId && hasMethod -> ACPJson.decodeFromJsonElement(JsonRpcRequest.serializer(), element)
109+
hasMethod -> ACPJson.decodeFromJsonElement(JsonRpcNotification.serializer(), element)
110+
else -> error("Unable to determine JsonRpcMessage type from JSON structure")
111+
}
91112
}

kotlin/kotlin-acp/src/commonMain/kotlin/io/agentclientprotocol/transport/StdioTransport.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.agentclientprotocol.transport
22

33
import io.agentclientprotocol.rpc.ACPJson
44
import io.agentclientprotocol.rpc.JsonRpcMessage
5+
import io.agentclientprotocol.rpc.decodeJsonRpcMessage
56
import io.agentclientprotocol.util.DispatcherIO
67
import io.github.oshai.kotlinlogging.KotlinLogging
78
import kotlinx.atomicfu.atomic
@@ -57,7 +58,7 @@ public class StdioTransport(
5758
}
5859

5960
val jsonRpcMessage = try {
60-
ACPJson.decodeFromString<JsonRpcMessage>(line)
61+
decodeJsonRpcMessage(line)
6162
} catch (t: Throwable) {
6263
logger.error(t) { "Failed to decode JSON message: $line" }
6364
continue

kotlin/kotlin-acp/src/commonTest/kotlin/io/agentclientprotocol/mock/TestTransport.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.agentclientprotocol.mock
22

3-
import io.agentclientprotocol.rpc.ACPJson
43
import io.agentclientprotocol.rpc.JsonRpcMessage
4+
import io.agentclientprotocol.rpc.decodeJsonRpcMessage
55
import io.agentclientprotocol.transport.Transport
66
import kotlinx.coroutines.channels.Channel
77
import kotlinx.coroutines.channels.ReceiveChannel
@@ -80,7 +80,7 @@ class TestTransport : Transport {
8080
* Parses the JSON string and determines the message type based on the presence of fields.
8181
*/
8282
public fun receiveMessage(jsonMessage: String) {
83-
val message = ACPJson.decodeFromString<JsonRpcMessage>(jsonMessage)
83+
val message = decodeJsonRpcMessage(jsonMessage)
8484
receiveMessage(message)
8585
}
8686
}

0 commit comments

Comments
 (0)