mirror of
https://github.com/cline/cline.git
synced 2026-08-28 19:48:08 +08:00
fix(vscode): avoid render crash on malformed api_req payloads in combineApiRequests (#13560)
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, it } from "bun:test"
|
||||
import { strict as assert } from "node:assert"
|
||||
import type { ClineMessage } from "./ExtensionMessage"
|
||||
import { combineApiRequests } from "./combineApiRequests"
|
||||
|
||||
function started(ts: number, text: string) {
|
||||
return { ts, type: "say", say: "api_req_started", text } as ClineMessage
|
||||
}
|
||||
|
||||
function finished(ts: number, text: string) {
|
||||
return { ts, type: "say", say: "api_req_finished", text } as ClineMessage
|
||||
}
|
||||
|
||||
describe("combineApiRequests", () => {
|
||||
it("merges a started request with its following finished payload", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
started(1, JSON.stringify({ request: "GET /api/data" })),
|
||||
finished(2, JSON.stringify({ cost: 0.005, tokensIn: 10, tokensOut: 5 })),
|
||||
]
|
||||
|
||||
const combined = combineApiRequests(messages)
|
||||
|
||||
assert.equal(combined.length, 1)
|
||||
assert.equal(combined[0]?.say, "api_req_started")
|
||||
assert.deepEqual(JSON.parse(combined[0]?.text ?? "{}"), {
|
||||
request: "GET /api/data",
|
||||
cost: 0.005,
|
||||
tokensIn: 10,
|
||||
tokensOut: 5,
|
||||
})
|
||||
})
|
||||
|
||||
it("keeps an api_req_started without a matching finished payload", () => {
|
||||
const messages: ClineMessage[] = [started(1, JSON.stringify({ request: "A" }))]
|
||||
|
||||
const combined = combineApiRequests(messages)
|
||||
|
||||
assert.equal(combined.length, 1)
|
||||
assert.deepEqual(JSON.parse(combined[0]?.text ?? "{}"), { request: "A" })
|
||||
})
|
||||
|
||||
it("does not throw on a malformed api_req_started payload and keeps the row", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
started(1, "{not-json"),
|
||||
{ ts: 2, type: "say", say: "text", text: "hello" } as ClineMessage,
|
||||
]
|
||||
|
||||
const combined = combineApiRequests(messages)
|
||||
|
||||
assert.equal(combined[0]?.text, "{not-json")
|
||||
assert.equal(combined[1]?.text, "hello")
|
||||
})
|
||||
|
||||
it("does not throw on a malformed api_req_finished payload", () => {
|
||||
const messages: ClineMessage[] = [
|
||||
started(1, JSON.stringify({ request: "A", cost: 0.1 })),
|
||||
finished(2, "{not-json"),
|
||||
]
|
||||
|
||||
const combined = combineApiRequests(messages)
|
||||
|
||||
// Pair is still consumed; the started row survives with its own payload.
|
||||
assert.equal(combined.length, 1)
|
||||
assert.deepEqual(JSON.parse(combined[0]?.text ?? "{}"), { request: "A", cost: 0.1 })
|
||||
})
|
||||
})
|
||||
@@ -23,12 +23,29 @@ export function combineApiRequests(messages: ClineMessage[]): ClineMessage[] {
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
if (messages[i].type === "say" && messages[i].say === "api_req_started") {
|
||||
const startedRequest = JSON.parse(messages[i].text || "{}")
|
||||
// Malformed payloads are tolerated everywhere else that reads this row
|
||||
// (see messageUtils, RequestStartRow, getApiMetrics) because this runs
|
||||
// inside the render pipeline. A JSON.parse throw here would crash the
|
||||
// whole chat view, so degrade to keeping the original row instead.
|
||||
let startedRequest: Record<string, unknown>
|
||||
try {
|
||||
startedRequest = JSON.parse(messages[i].text || "{}")
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
let j = i + 1
|
||||
|
||||
while (j < messages.length) {
|
||||
if (messages[j].type === "say" && messages[j].say === "api_req_finished") {
|
||||
const finishedRequest = JSON.parse(messages[j].text || "{}")
|
||||
let finishedRequest: Record<string, unknown>
|
||||
try {
|
||||
finishedRequest = JSON.parse(messages[j].text || "{}")
|
||||
} catch {
|
||||
// Unparseable finish payload — nothing to merge, keep the
|
||||
// started row as-is but still consume the pair.
|
||||
finishedRequest = {}
|
||||
}
|
||||
const combinedRequest = {
|
||||
...startedRequest,
|
||||
...finishedRequest,
|
||||
|
||||
Reference in New Issue
Block a user