chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
@@ -4,7 +4,7 @@ import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { executeBashWithOperations } from "../src/core/bash-executor.ts";
|
||||
import type { ExtensionContext } from "../src/core/extensions/types.ts";
|
||||
import type { ExtensionToolContext } from "../src/core/extensions/types.ts";
|
||||
import {
|
||||
type BashOperations,
|
||||
createBashTool,
|
||||
@@ -89,6 +89,7 @@ describe("Coding Agent Tools", () => {
|
||||
// No truncation message since file fits within limits
|
||||
expect(getTextOutput(result)).not.toContain("Use offset=");
|
||||
expect(result.details).toBeUndefined();
|
||||
expect(result.structuredContent).toBe(content);
|
||||
});
|
||||
|
||||
it("should handle non-existent files", async () => {
|
||||
@@ -217,6 +218,13 @@ describe("Coding Agent Tools", () => {
|
||||
expect(imageBlock?.mimeType).toBe("image/png");
|
||||
expect(typeof imageBlock?.data).toBe("string");
|
||||
expect((imageBlock?.data ?? "").length).toBeGreaterThan(0);
|
||||
// Programmatic callers get the same image as a block. https://github.com/earendil-works/pi/issues/10251
|
||||
expect(result.structuredContent).toEqual({
|
||||
type: "image",
|
||||
data: imageBlock?.data,
|
||||
mimeType: "image/png",
|
||||
note: getTextOutput(result),
|
||||
});
|
||||
});
|
||||
|
||||
it("should read BMP files from disk as PNG image attachments", async () => {
|
||||
@@ -490,9 +498,95 @@ describe("Coding Agent Tools", () => {
|
||||
expect(result.details).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle command errors", async () => {
|
||||
await expect(bashTool.execute("test-call-9", { command: "exit 1" })).rejects.toThrow(
|
||||
/(Command failed|code 1)/,
|
||||
it("should report non-zero exit codes as error results with structured content", async () => {
|
||||
const result = await bashTool.execute("test-call-9", { command: "echo out; exit 3" });
|
||||
expect(result.isError).toBe(true);
|
||||
expect(getTextOutput(result)).toBe("out\n\n\nCommand exited with code 3");
|
||||
expect(result.structuredContent).toEqual({
|
||||
output: "out\n",
|
||||
truncated: false,
|
||||
exit_code: 3,
|
||||
wall_time_seconds: expect.any(Number),
|
||||
});
|
||||
|
||||
const ok = await bashTool.execute("test-call-9b", { command: "echo fine" });
|
||||
expect(ok.isError).toBeUndefined();
|
||||
expect(ok.structuredContent).toMatchObject({ output: "fine\n", exit_code: 0 });
|
||||
|
||||
const empty = await bashTool.execute("test-call-9c", { command: "true" });
|
||||
expect(getTextOutput(empty)).toBe("(no output)");
|
||||
expect(empty.structuredContent).toMatchObject({ output: "", truncated: false });
|
||||
});
|
||||
|
||||
it("should return up to 1 MiB of output in structured content", async () => {
|
||||
// 3000 lines exceed the model-facing 2000 line limit but not 1 MiB.
|
||||
const medium = await bashTool.execute("test-call-9d", { command: "seq 1 3000" });
|
||||
expect(getTextOutput(medium)).not.toContain("\n1\n2\n");
|
||||
expect(medium.details?.truncation?.truncated).toBe(true);
|
||||
const mediumOutput = medium.structuredContent as { output: string; truncated: boolean };
|
||||
expect(mediumOutput.truncated).toBe(false);
|
||||
expect(mediumOutput.output).toBe(`${Array.from({ length: 3000 }, (_, i) => i + 1).join("\n")}\n`);
|
||||
|
||||
// About 2 MB: keeps the first and last 512 KiB around an omission marker.
|
||||
const large = await bashTool.execute("test-call-9e", { command: "seq 1 300000" });
|
||||
const largeOutput = large.structuredContent as {
|
||||
output: string;
|
||||
truncated: boolean;
|
||||
full_output_path?: string;
|
||||
};
|
||||
expect(largeOutput.truncated).toBe(true);
|
||||
expect(largeOutput.output.startsWith("1\n2\n3\n")).toBe(true);
|
||||
expect(largeOutput.output.endsWith("299999\n300000\n")).toBe(true);
|
||||
expect(largeOutput.output).toMatch(/\n\n\[\.\.\. \d+ bytes omitted \.\.\.\]\n\n/);
|
||||
expect(Buffer.byteLength(largeOutput.output)).toBeLessThan(1024 * 1024 + 100);
|
||||
expect(largeOutput.full_output_path).toBe(large.details?.fullOutputPath);
|
||||
expect(readFileSync(largeOutput.full_output_path!, "utf-8").endsWith("300000\n")).toBe(true);
|
||||
});
|
||||
|
||||
// Regression tests for https://github.com/earendil-works/pi/issues/9577
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"should map signal-killed commands to 128 plus the signal number",
|
||||
async () => {
|
||||
const operations = createLocalBashOperations();
|
||||
for (const { signal, exitCode } of [
|
||||
{ signal: "KILL", exitCode: 137 },
|
||||
{ signal: "TERM", exitCode: 143 },
|
||||
]) {
|
||||
const result = await operations.exec(`kill -${signal} $$`, testDir, { onData: () => {} });
|
||||
expect(result.exitCode).toBe(exitCode);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"should report signal-killed commands as errors while preserving partial output",
|
||||
async () => {
|
||||
for (const { signal, exitCode } of [
|
||||
{ signal: "KILL", exitCode: 137 },
|
||||
{ signal: "TERM", exitCode: 143 },
|
||||
]) {
|
||||
const result = await bashTool.execute(`test-call-signal-${signal}`, {
|
||||
command: `printf 'before-kill\\n'; kill -${signal} $$`,
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(getTextOutput(result)).toMatch(
|
||||
new RegExp(`before-kill\\s+Command exited with code ${exitCode}$`),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("should reject a null exit code from custom operations", async () => {
|
||||
const operations: BashOperations = {
|
||||
exec: async (_command, _cwd, { onData }) => {
|
||||
onData(Buffer.from("partial\n", "utf-8"));
|
||||
return { exitCode: null };
|
||||
},
|
||||
};
|
||||
const bash = createBashTool(testDir, { operations });
|
||||
|
||||
await expect(bash.execute("test-call-null-exit", { command: "remote" })).rejects.toThrow(
|
||||
/partial\s+Command terminated without an exit code$/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -901,8 +995,8 @@ describe("Coding Agent Tools", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function fakeCtx(cwd: string): ExtensionContext {
|
||||
return { cwd } as ExtensionContext;
|
||||
function fakeCtx(cwd: string): ExtensionToolContext {
|
||||
return { cwd } as ExtensionToolContext;
|
||||
}
|
||||
|
||||
describe("tool cwd resolution", () => {
|
||||
|
||||
Reference in New Issue
Block a user