mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-13 21:02:08 +00:00
Claude supplies structured patches for Edit and Write, but ACP diff blocks previously carried only the reconstructed texts. AIR had to compare those texts again to display added and removed counts. Count patch operations in the existing conversion loop and publish `_meta.jetbrains.air.diffStats` on each diff block. This uses the same contract as https://github.com/agentclientprotocol/codex-acp/pull/501. Each block has its own counts. No extra traversal, full file comparison, or navigation calculation is needed. Omit statistics when patches are absent or inconsistent, so clients retain their existing fallback. Exclude EOF markers from the displayed texts. Document the extension and cover per-block counts, Edit and Write hooks, EOF changes, and fallback behavior. Validation: - `npm run build` - `npm run check` - `env -u ANTHROPIC_BASE_URL npm run test:run` (the local endpoint override affects unrelated provider tests)
132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { toolUpdateFromDiffToolResponse } from "../diff.js";
|
|
|
|
describe("diff statistics", () => {
|
|
it("counts each block independently without reading full file content", () => {
|
|
const result = toolUpdateFromDiffToolResponse({
|
|
filePath: "/file.ts",
|
|
get originalFile() {
|
|
throw new Error("The patch already contains the changes");
|
|
},
|
|
get content() {
|
|
throw new Error("The patch already contains the changes");
|
|
},
|
|
structuredPatch: [
|
|
{
|
|
oldStart: 1,
|
|
oldLines: 3,
|
|
newStart: 1,
|
|
newLines: 2,
|
|
lines: [" context", "-first", "-second", "+replacement"],
|
|
},
|
|
{
|
|
oldStart: 1_000_000,
|
|
oldLines: 0,
|
|
newStart: 1_000_000,
|
|
newLines: 2,
|
|
lines: ["+first", "+second"],
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(result.content?.map((block) => block._meta)).toEqual([
|
|
{ jetbrains: { air: { version: 1, diffStats: { version: 1, added: 1, removed: 2 } } } },
|
|
{ jetbrains: { air: { version: 1, diffStats: { version: 1, added: 2, removed: 0 } } } },
|
|
]);
|
|
});
|
|
|
|
it("counts patch operations when an EOF change produces equal displayed texts", () => {
|
|
const result = toolUpdateFromDiffToolResponse({
|
|
filePath: "/file.ts",
|
|
structuredPatch: [
|
|
{
|
|
oldStart: 1,
|
|
oldLines: 1,
|
|
newStart: 1,
|
|
newLines: 1,
|
|
lines: ["-same", "\\ No newline at end of file", "+same"],
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(result.content).toEqual([
|
|
{
|
|
type: "diff",
|
|
path: "/file.ts",
|
|
oldText: "same",
|
|
newText: "same",
|
|
_meta: {
|
|
jetbrains: { air: { version: 1, diffStats: { version: 1, added: 1, removed: 1 } } },
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("publishes zero counts for a block with only context", () => {
|
|
const result = toolUpdateFromDiffToolResponse({
|
|
filePath: "/file.ts",
|
|
structuredPatch: [
|
|
{ oldStart: 1, oldLines: 1, newStart: 1, newLines: 1, lines: [" unchanged"] },
|
|
],
|
|
});
|
|
|
|
expect(result.content?.[0]._meta).toEqual({
|
|
jetbrains: { air: { version: 1, diffStats: { version: 1, added: 0, removed: 0 } } },
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{ oldLines: 2, newLines: 1, lines: ["-old", "+new"] },
|
|
{ oldLines: 1, newLines: 2, lines: ["-old", "+new"] },
|
|
{ oldLines: 1, newLines: 1, lines: ["?unsupported"] },
|
|
{ oldLines: "1", newLines: 1, lines: ["-old", "+new"] },
|
|
{ oldStart: -1, oldLines: 1, newLines: 1, lines: ["-old", "+new"] },
|
|
{ newStart: Number.NaN, oldLines: 1, newLines: 1, lines: ["-old", "+new"] },
|
|
{
|
|
oldLines: 1,
|
|
newLines: 1,
|
|
lines: ["\\ No newline at end of file", "-old", "+new"],
|
|
},
|
|
])("omits statistics for an inconsistent patch: %j", (hunk) => {
|
|
const result = toolUpdateFromDiffToolResponse({
|
|
filePath: "/file.ts",
|
|
structuredPatch: [{ oldStart: 1, newStart: 1, ...hunk }],
|
|
});
|
|
|
|
expect(result.content).toHaveLength(1);
|
|
expect(result.content?.[0]).not.toHaveProperty("_meta");
|
|
});
|
|
|
|
it("omits statistics once hunk coordinates stop increasing", () => {
|
|
const result = toolUpdateFromDiffToolResponse({
|
|
filePath: "/file.ts",
|
|
structuredPatch: [
|
|
{ oldStart: 10, oldLines: 1, newStart: 10, newLines: 1, lines: ["-old", "+new"] },
|
|
{ oldStart: 5, oldLines: 1, newStart: 5, newLines: 1, lines: ["-old", "+new"] },
|
|
],
|
|
});
|
|
|
|
expect(result.content?.[0]).toHaveProperty("_meta");
|
|
expect(result.content?.[1]).not.toHaveProperty("_meta");
|
|
});
|
|
|
|
it("omits statistics when later hunk coordinates ignore the accumulated line delta", () => {
|
|
const result = toolUpdateFromDiffToolResponse({
|
|
filePath: "/file.ts",
|
|
structuredPatch: [
|
|
{
|
|
oldStart: 10,
|
|
oldLines: 1,
|
|
newStart: 10,
|
|
newLines: 2,
|
|
lines: ["-old", "+new", "+extra"],
|
|
},
|
|
{ oldStart: 20, oldLines: 1, newStart: 20, newLines: 1, lines: ["-old", "+new"] },
|
|
],
|
|
});
|
|
|
|
expect(result.content?.[0]).toHaveProperty("_meta");
|
|
expect(result.content?.[1]).not.toHaveProperty("_meta");
|
|
});
|
|
});
|