Skip to content

Commit d98be39

Browse files
committed
fix(app): patch tool diff rendering
1 parent 039c601 commit d98be39

File tree

3 files changed

+125
-25
lines changed

3 files changed

+125
-25
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { patchFiles } from "./apply-patch-file"
3+
import { text } from "./session-diff"
4+
5+
describe("apply patch file", () => {
6+
test("parses patch metadata from the server", () => {
7+
const file = patchFiles([
8+
{
9+
filePath: "/tmp/a.ts",
10+
relativePath: "a.ts",
11+
type: "update",
12+
patch:
13+
"Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n",
14+
additions: 1,
15+
deletions: 1,
16+
},
17+
])[0]
18+
19+
expect(file).toBeDefined()
20+
expect(file?.view.fileDiff.name).toBe("a.ts")
21+
expect(text(file!.view, "deletions")).toBe("one\ntwo\n")
22+
expect(text(file!.view, "additions")).toBe("one\nthree\n")
23+
})
24+
25+
test("keeps legacy before and after payloads working", () => {
26+
const file = patchFiles([
27+
{
28+
filePath: "/tmp/a.ts",
29+
relativePath: "a.ts",
30+
type: "update",
31+
before: "one\n",
32+
after: "two\n",
33+
additions: 1,
34+
deletions: 1,
35+
},
36+
])[0]
37+
38+
expect(file).toBeDefined()
39+
expect(file?.view.patch).toContain("@@ -1,1 +1,1 @@")
40+
expect(text(file!.view, "deletions")).toBe("one\n")
41+
expect(text(file!.view, "additions")).toBe("two\n")
42+
})
43+
})
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { normalize, type ViewDiff } from "./session-diff"
2+
3+
type Kind = "add" | "update" | "delete" | "move"
4+
5+
type Raw = {
6+
filePath?: string
7+
relativePath?: string
8+
type?: Kind
9+
patch?: string
10+
diff?: string
11+
before?: string
12+
after?: string
13+
additions?: number
14+
deletions?: number
15+
movePath?: string
16+
}
17+
18+
export type ApplyPatchFile = {
19+
filePath: string
20+
relativePath: string
21+
type: Kind
22+
additions: number
23+
deletions: number
24+
movePath?: string
25+
view: ViewDiff
26+
}
27+
28+
function kind(value: unknown) {
29+
if (value === "add" || value === "update" || value === "delete" || value === "move") return value
30+
}
31+
32+
function status(type: Kind): "added" | "deleted" | "modified" {
33+
if (type === "add") return "added"
34+
if (type === "delete") return "deleted"
35+
return "modified"
36+
}
37+
38+
export function patchFile(raw: unknown): ApplyPatchFile | undefined {
39+
if (!raw || typeof raw !== "object") return
40+
41+
const value = raw as Raw
42+
const type = kind(value.type)
43+
const filePath = typeof value.filePath === "string" ? value.filePath : undefined
44+
const relativePath = typeof value.relativePath === "string" ? value.relativePath : filePath
45+
const patch = typeof value.patch === "string" ? value.patch : typeof value.diff === "string" ? value.diff : undefined
46+
const before = typeof value.before === "string" ? value.before : undefined
47+
const after = typeof value.after === "string" ? value.after : undefined
48+
49+
if (!type || !filePath || !relativePath) return
50+
if (!patch && before === undefined && after === undefined) return
51+
52+
const additions = typeof value.additions === "number" ? value.additions : 0
53+
const deletions = typeof value.deletions === "number" ? value.deletions : 0
54+
const movePath = typeof value.movePath === "string" ? value.movePath : undefined
55+
56+
return {
57+
filePath,
58+
relativePath,
59+
type,
60+
additions,
61+
deletions,
62+
movePath,
63+
view: normalize({
64+
file: relativePath,
65+
patch,
66+
before,
67+
after,
68+
additions,
69+
deletions,
70+
status: status(type),
71+
}),
72+
}
73+
}
74+
75+
export function patchFiles(raw: unknown) {
76+
if (!Array.isArray(raw)) return []
77+
return raw.map(patchFile).filter((file): file is ApplyPatchFile => !!file)
78+
}

packages/ui/src/components/message-part.tsx

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { Spinner } from "./spinner"
5454
import { TextShimmer } from "./text-shimmer"
5555
import { AnimatedCountList } from "./tool-count-summary"
5656
import { ToolStatusTitle } from "./tool-status-title"
57+
import { patchFiles } from "./apply-patch-file"
5758
import { animate } from "motion"
5859
import { useLocation } from "@solidjs/router"
5960
import { attached, inline, kind } from "./message-file"
@@ -2014,24 +2015,12 @@ ToolRegistry.register({
20142015
},
20152016
})
20162017

2017-
interface ApplyPatchFile {
2018-
filePath: string
2019-
relativePath: string
2020-
type: "add" | "update" | "delete" | "move"
2021-
diff: string
2022-
before: string
2023-
after: string
2024-
additions: number
2025-
deletions: number
2026-
movePath?: string
2027-
}
2028-
20292018
ToolRegistry.register({
20302019
name: "apply_patch",
20312020
render(props) {
20322021
const i18n = useI18n()
20332022
const fileComponent = useFileComponent()
2034-
const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[])
2023+
const files = createMemo(() => patchFiles(props.metadata.files))
20352024
const pending = createMemo(() => props.status === "pending" || props.status === "running")
20362025
const single = createMemo(() => {
20372026
const list = files()
@@ -2137,12 +2126,7 @@ ToolRegistry.register({
21372126
<Accordion.Content>
21382127
<Show when={visible()}>
21392128
<div data-component="apply-patch-file-diff">
2140-
<Dynamic
2141-
component={fileComponent}
2142-
mode="diff"
2143-
before={{ name: file.filePath, contents: file.before }}
2144-
after={{ name: file.movePath ?? file.filePath, contents: file.after }}
2145-
/>
2129+
<Dynamic component={fileComponent} mode="diff" fileDiff={file.view.fileDiff} />
21462130
</div>
21472131
</Show>
21482132
</Accordion.Content>
@@ -2212,12 +2196,7 @@ ToolRegistry.register({
22122196
}
22132197
>
22142198
<div data-component="apply-patch-file-diff">
2215-
<Dynamic
2216-
component={fileComponent}
2217-
mode="diff"
2218-
before={{ name: single()!.filePath, contents: single()!.before }}
2219-
after={{ name: single()!.movePath ?? single()!.filePath, contents: single()!.after }}
2220-
/>
2199+
<Dynamic component={fileComponent} mode="diff" fileDiff={single()!.view.fileDiff} />
22212200
</div>
22222201
</ToolFileAccordion>
22232202
</BasicTool>

0 commit comments

Comments
 (0)