mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
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:
co-authored by
Imanol Maiztegui
parent
c1ea8100e1
commit
3095efcc4c
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user