Skip to content

Commit 4bc9d22

Browse files
committed
do not fail if input contains some garbage, still try to parse json
1 parent d0b446e commit 4bc9d22

File tree

4 files changed

+88
-2
lines changed

4 files changed

+88
-2
lines changed

acp-model/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,13 @@ kotlin {
1212
api(libs.kotlinx.collections.immutable)
1313
}
1414
}
15+
16+
commonTest {
17+
dependencies {
18+
implementation(kotlin("test"))
19+
implementation(libs.kotlinx.coroutines.test)
20+
}
21+
}
22+
1523
}
1624
}

acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.agentclientprotocol.rpc
44

55
import kotlinx.serialization.ExperimentalSerializationApi
66
import kotlinx.serialization.Serializable
7+
import kotlinx.serialization.SerializationException
78
import kotlinx.serialization.json.Json
89
import kotlinx.serialization.json.JsonElement
910
import kotlinx.serialization.json.JsonObject
@@ -103,7 +104,17 @@ public val ACPJson: Json by lazy {
103104
* - Notification: has "method" but no "id"
104105
*/
105106
public fun decodeJsonRpcMessage(jsonString: String): JsonRpcMessage {
106-
val element = ACPJson.parseToJsonElement(jsonString)
107+
val element = try {
108+
ACPJson.parseToJsonElement(jsonString)
109+
} catch (e: SerializationException) {
110+
// maybe there is some garbage output at the beginning of the like, try to find where JSON starts
111+
val jsonStart = jsonString.indexOfFirst { it == '{' }
112+
if (jsonStart == -1) {
113+
throw e
114+
}
115+
val jsonStartTrimmed = jsonString.substring(jsonStart)
116+
ACPJson.parseToJsonElement(jsonStartTrimmed)
117+
}
107118
require(element is JsonObject) { "Expected JSON object" }
108119

109120
val hasId = element.containsKey("id")
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.agentclientprotocol.rpc
2+
3+
import kotlinx.serialization.SerializationException
4+
import kotlinx.serialization.json.jsonObject
5+
import kotlinx.serialization.json.jsonPrimitive
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
import kotlin.test.assertTrue
9+
import kotlin.test.fail
10+
11+
class JsonDecodeTest {
12+
13+
@Test
14+
fun testDecode() {
15+
val result = decodeJsonRpcMessage(
16+
"""
17+
{
18+
"jsonrpc": "2.0",
19+
"id": 5,
20+
"result": {
21+
"outcome": {
22+
"outcome": "selected",
23+
"optionId": "allow-once"
24+
}
25+
}
26+
}
27+
""".trimIndent()
28+
)
29+
assertTrue(result is JsonRpcResponse)
30+
}
31+
32+
@Test
33+
fun testDecodeWithPrefix() {
34+
val result = decodeJsonRpcMessage(
35+
"""
36+
asdfasdfasdf(){
37+
"jsonrpc": "2.0",
38+
"id": 5,
39+
"result": {
40+
"outcome": {
41+
"outcome": "selected",
42+
"optionId": "allow-once"
43+
}
44+
}
45+
}
46+
""".trimIndent()
47+
)
48+
assertTrue(result is JsonRpcResponse)
49+
assertEquals("selected", result.result!!.jsonObject["outcome"]!!.jsonObject["outcome"]!!.jsonPrimitive.content)
50+
}
51+
52+
@Test
53+
fun testDecodeError() {
54+
try {
55+
decodeJsonRpcMessage("""
56+
asdfasdfas
57+
"outcome": {
58+
"outcome": "selected",
59+
"optionId": "allow-once"
60+
}
61+
}
62+
}
63+
""".trimIndent())
64+
fail("Exception expected")
65+
} catch (_: SerializationException) {}
66+
}
67+
68+
}

acp/src/commonMain/kotlin/com/agentclientprotocol/transport/StdioTransport.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public class StdioTransport(
6060
decodeJsonRpcMessage(line)
6161
} catch (t: Throwable) {
6262
logger.trace(t) { "Failed to decode JSON message: $line" }
63-
fireError(t)
6463
continue
6564
}
6665
logger.trace { "Sending message to channel: $jsonRpcMessage" }

0 commit comments

Comments
 (0)