7b62fef366
Review of the modern exec wire protocol surfaced defects the committed
suite did not pin.
The Pi bridge dropped frame arguments: `pi_read`'s offset/limit (ranged
reads returned whole files), `pi_grep`'s literal (fixed strings ran as
regexes), and the path/glob join emitted `./`-prefixed specs. These are
`optional int32`, so a present `0` is a value, not "unset" — `limit: 0`
now answers empty rather than reading everything, and `pi_find` clamps
to 1 like the reference client.
The provider synthesized its transcript block from a second, divergent
translation of the same frame, so the displayed operation differed from
the executed one. Both sides now share one mapper in `exec-modern.ts`.
End-of-transport cleanup reparsed every open block's streamed argument
buffer; blocks whose args arrive whole never set that buffer, and
`parseStreamingJson(undefined)` is `{}`, so a truncated turn erased
their arguments.
All fixes are mutation-verified: reverting each one fails a test.
(cherry picked from commit bb7bcfebce4200d436e17d6e39320da13fc85ca8)
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { buildMcpToolDefinitions } from "@oh-my-pi/pi-ai/providers/cursor";
|
|
import type { Tool } from "@oh-my-pi/pi-ai/types";
|
|
|
|
const tool = (name: string): Tool => ({
|
|
name,
|
|
description: `${name} tool`,
|
|
parameters: { type: "object", properties: {} },
|
|
});
|
|
|
|
describe("cursor buildMcpToolDefinitions", () => {
|
|
it("forwards the write transport alongside preview-staging devices so xd:// resolution stays reachable", () => {
|
|
// A Cursor session with xdev on: ast_edit is a mounted device, write is the
|
|
// xd:// transport carried top-level. ast_edit always stages a preview whose
|
|
// resolution rides `write xd://resolve` / `write xd://reject`.
|
|
const defs = buildMcpToolDefinitions([
|
|
tool("read"),
|
|
tool("write"),
|
|
tool("bash"),
|
|
tool("todo"),
|
|
tool("ast_edit"),
|
|
tool("task"),
|
|
]);
|
|
const names = defs.map(def => def.name);
|
|
|
|
expect(names).toContain("ast_edit");
|
|
expect(names).toContain("task");
|
|
// `write` is the sole native-filtered tool re-included: without it the
|
|
// staged preview can never be resolved and the SoftToolRequirement('write')
|
|
// escalation aborts the turn.
|
|
expect(names).toContain("write");
|
|
expect(names).not.toContain("read");
|
|
expect(names).not.toContain("bash");
|
|
expect(names).not.toContain("todo");
|
|
|
|
// The forwarded write must be a routable pi-agent MCP tool, so Cursor
|
|
// dispatches it back through the coding-agent write tool's xd:// handler.
|
|
const writeDef = defs.find(def => def.name === "write");
|
|
expect(writeDef?.providerIdentifier).toBe("pi-agent");
|
|
expect(writeDef?.toolName).toBe("write");
|
|
});
|
|
|
|
it("keeps write out when only native tools are advertised (no pi-agent device needs resolution)", () => {
|
|
const names = buildMcpToolDefinitions([tool("read"), tool("write"), tool("bash")]).map(def => def.name);
|
|
expect(names).toEqual([]);
|
|
});
|
|
|
|
it("advertises lsp, which Cursor has no native equivalent for", () => {
|
|
// `lsp` is deliberately absent from the native-filtered set: Cursor's own
|
|
// tools cover none of definition/references/rename, so filtering it out
|
|
// leaves the model with no way to reach them at all.
|
|
const defs = buildMcpToolDefinitions([tool("read"), tool("bash"), tool("lsp")]);
|
|
|
|
const lspDef = defs.find(def => def.name === "lsp");
|
|
expect(lspDef).toBeDefined();
|
|
expect(lspDef?.providerIdentifier).toBe("pi-agent");
|
|
expect(lspDef?.toolName).toBe("lsp");
|
|
});
|
|
});
|