feat(coding-agent): expose edit tool unified patch

closes #4821
This commit is contained in:
Mario Zechner
2026-05-21 12:15:39 +02:00
parent 7dad27e5f2
commit 60a55a2399
5 changed files with 28 additions and 3 deletions

View File

@@ -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)).

View File

@@ -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";

View File

@@ -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(

View File

@@ -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.

View File

@@ -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 () => {