Files
oh-my-pi/packages/coding-agent/test/eval/py/prelude.test.ts
T
2026-07-27 16:43:53 +02:00

105 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { PYTHON_PRELUDE } from "../../../src/eval/py/prelude";
const pythonPath = Bun.env.PYTHON ?? "python3";
async function runPrelude(
code: string,
env: Record<string, string>,
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
const prelude = PYTHON_PRELUDE.replace(
"from __future__ import annotations",
"from __future__ import annotations\n__omp_display = lambda *args, **kwargs: None",
);
const script = `${prelude}\n${code}`;
const proc = Bun.spawn([pythonPath, "-c", script], {
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, ...env },
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
return { stdout, stderr, exitCode };
}
describe("python prelude", () => {
it("exposes read(path, offset?, limit?) with positional optional args", () => {
// The eval docs advertise `read(path, offset?=1, limit?=None)`. A
// keyword-only signature (`def read(path, *, offset=1, limit=None)`)
// makes `read("file", 10)` raise `TypeError: read() takes 1 positional
// argument but 2 were given`, which agents in the wild repeatedly hit.
// Lock the contract so the helper accepts both positional and keyword
// forms.
const match = PYTHON_PRELUDE.match(/def\s+read\(([^)]+)\)/);
expect(match).not.toBeNull();
const signature = match?.[1] ?? "";
expect(signature).not.toContain("*,");
expect(signature).toContain("offset");
expect(signature).toContain("limit");
});
it("appends line selectors to delegated URI paths", async () => {
const requests: unknown[] = [];
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch: async request => {
requests.push(await request.json());
return Response.json({
ok: true,
value: { text: "resource contents", details: { resolvedPath: "/tmp/resource.txt" } },
});
},
});
try {
const result = await runPrelude(
[`print(read("artifact://21", 3, 2))`, `print(read("mcp://server/resource", 10, 5))`].join("\n"),
{
PI_TOOL_BRIDGE_URL: server.url.toString(),
PI_TOOL_BRIDGE_TOKEN: "test-token",
PI_TOOL_BRIDGE_SESSION: "test-session",
},
);
expect(result).toEqual({
stdout: "resource contents\nresource contents\n",
stderr: "",
exitCode: 0,
});
expect(requests).toEqual([
{
session: "test-session",
run: null,
name: "read",
args: { path: "artifact://21:3-4" },
},
{
session: "test-session",
run: null,
name: "read",
args: { path: "mcp://server/resource:10-14" },
},
]);
} finally {
server.stop(true);
}
});
it("exposes isolation artifacts on the agent() handle node", () => {
// agent(..., handle=True) is the only escape hatch for
// recovering apply=False patch/branch/nested artifacts (the bare
// schema return is just the parsed object), so the helper MUST
// translate the bridge's camelCase details onto the node — otherwise
// an isolated apply=False workflow loses captured nested patches.
expect(PYTHON_PRELUDE).toContain('("patchPath", "patch_path")');
expect(PYTHON_PRELUDE).toContain('("branchName", "branch_name")');
expect(PYTHON_PRELUDE).toContain('("nestedPatches", "nested_patches")');
expect(PYTHON_PRELUDE).toContain('("changesApplied", "changes_applied")');
expect(PYTHON_PRELUDE).toContain('("isolationSummary", "isolation_summary")');
});
});