From 60a55a23991a1033243cb6f6d6e29670b9964c11 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 21 May 2026 12:15:39 +0200 Subject: [PATCH] feat(coding-agent): expose edit tool unified patch closes #4821 --- packages/coding-agent/CHANGELOG.md | 4 ++++ packages/coding-agent/docs/sdk.md | 2 ++ packages/coding-agent/src/core/tools/edit-diff.ts | 10 +++++++++- packages/coding-agent/src/core/tools/edit.ts | 8 ++++++-- packages/coding-agent/test/tools.test.ts | 7 +++++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 61f7ad6e..5d14b69a 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added a standard unified patch to edit tool result details for SDK consumers ([#4821](https://github.com/earendil-works/pi/issues/4821)). + ### Fixed - Fixed final bash tool cards to avoid rendering duplicate full-output truncation paths ([#4819](https://github.com/earendil-works/pi/issues/4819)). diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md index 2fa85cd0..8377185e 100644 --- a/packages/coding-agent/docs/sdk.md +++ b/packages/coding-agent/docs/sdk.md @@ -473,6 +473,8 @@ Specify which built-in tools to enable: - `noTools: "all"` disables all tools - `noTools: "builtin"` disables default built-ins while keeping extension and custom tools enabled +The `edit` tool returns `details.diff` for Pi's TUI display and `details.patch` as a standard unified patch for SDK consumers. + ```typescript import { createAgentSession } from "@earendil-works/pi-coding-agent"; diff --git a/packages/coding-agent/src/core/tools/edit-diff.ts b/packages/coding-agent/src/core/tools/edit-diff.ts index 691d882a..f280bf19 100644 --- a/packages/coding-agent/src/core/tools/edit-diff.ts +++ b/packages/coding-agent/src/core/tools/edit-diff.ts @@ -259,8 +259,16 @@ export function applyEditsToNormalizedContent( return { baseContent, newContent }; } +/** Generate a standard unified patch. */ +export function generateUnifiedPatch(path: string, oldContent: string, newContent: string, contextLines = 4): string { + return Diff.createTwoFilesPatch(path, path, oldContent, newContent, undefined, undefined, { + context: contextLines, + headerOptions: Diff.FILE_HEADERS_ONLY, + }); +} + /** - * Generate a unified diff string with line numbers and context. + * Generate a display-oriented diff string with line numbers and context. * Returns both the diff string and the first changed line number (in the new file). */ export function generateDiffString( diff --git a/packages/coding-agent/src/core/tools/edit.ts b/packages/coding-agent/src/core/tools/edit.ts index e5656378..5d915d45 100644 --- a/packages/coding-agent/src/core/tools/edit.ts +++ b/packages/coding-agent/src/core/tools/edit.ts @@ -13,6 +13,7 @@ import { type EditDiffError, type EditDiffResult, generateDiffString, + generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, @@ -57,8 +58,10 @@ type LegacyEditToolInput = EditToolInput & { }; export interface EditToolDetails { - /** Unified diff of the changes made */ + /** Display-oriented diff of the changes made */ diff: string; + /** Standard unified patch of the changes made */ + patch: string; /** Line number of the first change in the new file (for editor navigation) */ firstChangedLine?: number; } @@ -394,6 +397,7 @@ export function createEditToolDefinition( } const diffResult = generateDiffString(baseContent, newContent); + const patch = generateUnifiedPatch(path, baseContent, newContent); resolve({ content: [ { @@ -401,7 +405,7 @@ export function createEditToolDefinition( text: `Successfully replaced ${edits.length} block(s) in ${path}.`, }, ], - details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }, + details: { diff: diffResult.diff, patch, firstChangedLine: diffResult.firstChangedLine }, }); } catch (error: unknown) { // Clean up abort handler. diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 70d4ec2d..1d850e49 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -1,3 +1,4 @@ +import { applyPatch } from "diff"; import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; @@ -239,6 +240,12 @@ describe("Coding Agent Tools", () => { expect(result.details.diff).toBeDefined(); expect(typeof result.details.diff).toBe("string"); expect(result.details.diff).toContain("testing"); + expect(result.details.patch).toContain("--- "); + expect(result.details.patch).toContain("+++ "); + expect(result.details.patch).toContain("@@"); + expect(result.details.patch).toContain("-Hello, world!"); + expect(result.details.patch).toContain("+Hello, testing!"); + expect(applyPatch(originalContent, result.details.patch)).toBe("Hello, testing!"); }); it("should fail if text not found", async () => {