diff options
| author | Aiden Cline <[email protected]> | 2026-01-17 22:35:09 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-18 00:35:09 -0600 |
| commit | b7ad6bd83922e2259a467fe59f27806af8060629 (patch) | |
| tree | 0e1412ec1941f5a7c72731b53f4503b1619e811f /packages/ui/src | |
| parent | 10433cb45b6ed932368fb147032d671eaed0d273 (diff) | |
| download | opencode-b7ad6bd83922e2259a467fe59f27806af8060629.tar.gz opencode-b7ad6bd83922e2259a467fe59f27806af8060629.zip | |
feat: apply_patch tool for openai models (#9127)
Diffstat (limited to 'packages/ui/src')
| -rw-r--r-- | packages/ui/src/components/message-part.css | 72 | ||||
| -rw-r--r-- | packages/ui/src/components/message-part.tsx | 94 |
2 files changed, 166 insertions, 0 deletions
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 4a249ec4f..184565e9c 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -689,3 +689,75 @@ } } } + +[data-component="apply-patch-files"] { + display: flex; + flex-direction: column; +} + +[data-component="apply-patch-file"] { + display: flex; + flex-direction: column; + border-top: 1px solid var(--border-weaker-base); + + &:first-child { + border-top: 1px solid var(--border-weaker-base); + } + + [data-slot="apply-patch-file-header"] { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background-color: var(--surface-inset-base); + } + + [data-slot="apply-patch-file-action"] { + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + color: var(--text-base); + flex-shrink: 0; + + &[data-type="delete"] { + color: var(--text-critical-base); + } + + &[data-type="add"] { + color: var(--text-success-base); + } + + &[data-type="move"] { + color: var(--text-warning-base); + } + } + + [data-slot="apply-patch-file-path"] { + font-family: var(--font-family-mono); + font-size: var(--font-size-small); + color: var(--text-weak); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex-grow: 1; + } + + [data-slot="apply-patch-deletion-count"] { + font-family: var(--font-family-mono); + font-size: var(--font-size-small); + color: var(--text-critical-base); + flex-shrink: 0; + } +} + +[data-component="apply-patch-file-diff"] { + max-height: 420px; + overflow-y: auto; + scrollbar-width: none; + -ms-overflow-style: none; + + &::-webkit-scrollbar { + display: none; + } +} diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 165f46f6c..47403786b 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -233,6 +233,12 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo { title: "Write", subtitle: input.filePath ? getFilename(input.filePath) : undefined, } + case "apply_patch": + return { + icon: "code-lines", + title: "Patch", + subtitle: input.files?.length ? `${input.files.length} file${input.files.length > 1 ? "s" : ""}` : undefined, + } case "todowrite": return { icon: "checklist", @@ -1027,6 +1033,94 @@ ToolRegistry.register({ }, }) +interface ApplyPatchFile { + filePath: string + relativePath: string + type: "add" | "update" | "delete" | "move" + diff: string + before: string + after: string + additions: number + deletions: number + movePath?: string +} + +ToolRegistry.register({ + name: "apply_patch", + render(props) { + const diffComponent = useDiffComponent() + const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[]) + + const subtitle = createMemo(() => { + const count = files().length + if (count === 0) return "" + return `${count} file${count > 1 ? "s" : ""}` + }) + + return ( + <BasicTool + {...props} + icon="code-lines" + trigger={{ + title: "Patch", + subtitle: subtitle(), + }} + > + <Show when={files().length > 0}> + <div data-component="apply-patch-files"> + <For each={files()}> + {(file) => ( + <div data-component="apply-patch-file"> + <div data-slot="apply-patch-file-header"> + <Switch> + <Match when={file.type === "delete"}> + <span data-slot="apply-patch-file-action" data-type="delete"> + Deleted + </span> + </Match> + <Match when={file.type === "add"}> + <span data-slot="apply-patch-file-action" data-type="add"> + Created + </span> + </Match> + <Match when={file.type === "move"}> + <span data-slot="apply-patch-file-action" data-type="move"> + Moved + </span> + </Match> + <Match when={file.type === "update"}> + <span data-slot="apply-patch-file-action" data-type="update"> + Patched + </span> + </Match> + </Switch> + <span data-slot="apply-patch-file-path">{file.relativePath}</span> + <Show when={file.type !== "delete"}> + <DiffChanges changes={{ additions: file.additions, deletions: file.deletions }} /> + </Show> + <Show when={file.type === "delete"}> + <span data-slot="apply-patch-deletion-count">-{file.deletions}</span> + </Show> + </div> + <Show when={file.type !== "delete"}> + <div data-component="apply-patch-file-diff"> + <Dynamic + component={diffComponent} + before={{ name: file.filePath, contents: file.before }} + after={{ name: file.filePath, contents: file.after }} + /> + </div> + </Show> + </div> + )} + </For> + </div> + </Show> + </BasicTool> + ) + }, +}) + ToolRegistry.register({ name: "todowrite", render(props) { |
