fix(vscode): restore inline tool diffs (#9688)

* fix(vscode): restore inline tool diffs

* fix(vscode): render tool patches in kilo ui

* style: format long regex assignments and add change marker comment

Reformat multi-line regex match assignments in kilo-ui-contract test
to satisfy line length limits, and annotate the `contents(diff)` call
in session-diff with a kilocode_change tracking comment.

* fix(kilo-vscode): append trailing newlines to expected diff content assertions

Update test expectations in diff-session-source to include trailing
newlines in before/after content, matching actual file content behavior.

* fix(vscode): guard empty-patch diffs in session turn accordion

Match diff-session-source.ts:99 behavior by short-circuiting contents()
when the patch is empty (binary or summarized files), so the accordion
content stays empty instead of rendering a confusing whitespace-only
diff.

---------

Co-authored-by: Imanol Maiztegui <imanol.mzd@gmail.com>
This commit is contained in:
Marius
2026-05-07 11:01:47 +02:00
committed by GitHub
co-authored by Imanol Maiztegui
parent c1ea8100e1
commit 3095efcc4c
13 changed files with 409 additions and 99 deletions
+40 -8
View File
@@ -692,6 +692,17 @@ export const cursor = {
// kilocode_change start - strip bloated metadata fields from stored parts to prevent multi-MB payloads
// This handles both legacy data that was stored with full file contents and keeps the API response lean.
function stripPatch(value: unknown) {
if (typeof value !== "string") return undefined
if (Buffer.byteLength(value) > Snapshot.MAX_DIFF_SIZE) return undefined
return value
}
function withPatch(value: unknown) {
const kept = stripPatch(value)
return kept ? { patch: kept } : {}
}
export function stripPartMetadata(part: Part): Part {
// kilocode_change - exported for testing
if (part.type !== "tool") return part
@@ -703,20 +714,41 @@ export function stripPartMetadata(part: Part): Part {
let changed = false
let next = meta
// Strip edit tool's filediff.before/after (full file contents)
if (meta.filediff && (meta.filediff.before || meta.filediff.after)) {
const { before, after, ...rest } = meta.filediff
next = { ...next, filediff: rest }
if (meta.diff !== undefined) {
const { diff, ...rest } = next
next = rest
changed = true
}
// Strip apply_patch tool's files[].before/after (full file contents per file)
if (Array.isArray(meta.files) && meta.files.length > 0 && meta.files[0]?.before !== undefined) {
// Strip edit/write tool filediff.before/after (full file contents) and cap patches.
if (meta.filediff) {
const { before, after, patch, ...rest } = meta.filediff
next = { ...next, filediff: { ...rest, ...withPatch(patch) } }
changed = true
}
// Strip apply_patch tool's files[].before/after (full file contents per file) and cap per-file patches.
if (Array.isArray(meta.files) && meta.files.length > 0) {
next = {
...next,
files: meta.files.map((f: Record<string, unknown>) => {
const { before, after, ...rest } = f
return rest
const { before, after, patch, diff, ...rest } = f
const kept = stripPatch(patch) ?? stripPatch(diff)
return { ...rest, ...(kept ? { patch: kept } : {}) }
}),
}
changed = true
}
if (Array.isArray(meta.results) && meta.results.length > 0) {
next = {
...next,
results: meta.results.map((r: Record<string, unknown>) => {
const { diff, ...rest } = r
if (!r.filediff || typeof r.filediff !== "object") return rest
const fd = r.filediff as Record<string, unknown>
const { before, after, patch, ...file } = fd
return { ...rest, filediff: { ...file, ...withPatch(patch) } }
}),
}
changed = true
@@ -0,0 +1,129 @@
// kilocode_change - new file
import { describe, expect, test } from "bun:test"
import { MessageV2 } from "../../src/session/message-v2"
import { SessionID, MessageID, PartID } from "../../src/session/schema"
import { Snapshot } from "../../src/snapshot"
const sessionID = SessionID.make("session")
const patch =
"Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n"
function blob(size: number) {
return "x".repeat(size)
}
function part(tool: string, metadata: Record<string, unknown>): MessageV2.Part {
return {
id: PartID.make(`p-${tool}`),
sessionID,
messageID: MessageID.make("m-assistant"),
type: "tool",
callID: `call-${tool}`,
tool,
state: {
status: "completed",
input: {},
output: "ok",
title: tool,
metadata,
time: { start: 0, end: 1 },
},
} as MessageV2.Part
}
describe("session message metadata stripping", () => {
test("keeps bounded edit filediff patches and strips heavy fields", () => {
const input = part("edit", {
diff: blob(200_000),
filediff: {
file: "a.ts",
patch,
before: blob(200_000),
after: blob(200_000),
additions: 1,
deletions: 1,
},
diagnostics: {},
})
const stripped = MessageV2.stripPartMetadata(input) as Extract<MessageV2.Part, { type: "tool" }>
const meta = stripped.state.status === "completed" ? stripped.state.metadata : {}
expect(meta.diff).toBeUndefined()
expect(meta.filediff.before).toBeUndefined()
expect(meta.filediff.after).toBeUndefined()
expect(meta.filediff.patch).toBe(patch)
expect(JSON.stringify(stripped).length).toBeLessThan(10_000)
})
test("keeps bounded write filediff patches and strips heavy fields", () => {
const input = part("write", {
diff: blob(200_000),
filediff: {
file: "README.md",
patch,
before: blob(200_000),
after: blob(200_000),
additions: 1,
deletions: 1,
},
diagnostics: {},
})
const stripped = MessageV2.stripPartMetadata(input) as Extract<MessageV2.Part, { type: "tool" }>
const meta = stripped.state.status === "completed" ? stripped.state.metadata : {}
expect(meta.diff).toBeUndefined()
expect(meta.filediff.before).toBeUndefined()
expect(meta.filediff.after).toBeUndefined()
expect(meta.filediff.patch).toBe(patch)
expect(JSON.stringify(stripped).length).toBeLessThan(10_000)
})
test("keeps bounded apply_patch per-file patches and strips heavy fields", () => {
const input = part("apply_patch", {
diff: blob(200_000),
files: [
{
filePath: "/tmp/a.ts",
relativePath: "a.ts",
type: "update",
patch,
before: blob(200_000),
after: blob(200_000),
additions: 1,
deletions: 1,
},
],
diagnostics: {},
})
const stripped = MessageV2.stripPartMetadata(input) as Extract<MessageV2.Part, { type: "tool" }>
const meta = stripped.state.status === "completed" ? stripped.state.metadata : {}
expect(meta.diff).toBeUndefined()
expect(meta.files[0].before).toBeUndefined()
expect(meta.files[0].after).toBeUndefined()
expect(meta.files[0].patch).toBe(patch)
expect(JSON.stringify(stripped).length).toBeLessThan(10_000)
})
test("drops oversized tool patches from hydrated session metadata", () => {
const wide = `Index: a.ts\n--- a.ts\n+++ a.ts\n@@ -1,1 +1,1 @@\n-${blob(Snapshot.MAX_DIFF_SIZE)}\n+${blob(Snapshot.MAX_DIFF_SIZE)}\n`
const input = part("apply_patch", {
files: [
{
filePath: "/tmp/a.ts",
relativePath: "a.ts",
type: "update",
patch: wide,
additions: 1,
deletions: 1,
},
],
})
const stripped = MessageV2.stripPartMetadata(input) as Extract<MessageV2.Part, { type: "tool" }>
const meta = stripped.state.status === "completed" ? stripped.state.metadata : {}
expect(meta.files[0].patch).toBeUndefined()
expect(JSON.stringify(stripped).length).toBeLessThan(10_000)
})
})