4f03180ae6
- Moved the `INTENT_FIELD` constant from `@oh-my-pi/pi-agent-core` to the specialized `@oh-my-pi/pi-wire` package to permit broader usage across the monorepo. - Updated all references across `agent`, `ai`, `coding-agent`, `collab-web`, and `snapcompact` packages to import the constant from the new location. - Added `@oh-my-pi/pi-wire` as a dependency to all affected packages.
149 lines
4.1 KiB
TypeScript
149 lines
4.1 KiB
TypeScript
import { describe, expect, it, vi } from "bun:test";
|
|
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import { callSessionTool } from "@oh-my-pi/pi-coding-agent/eval/js/tool-bridge";
|
|
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
|
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
import { type } from "arktype";
|
|
|
|
function createTool(
|
|
name: string,
|
|
execute: (toolCallId: string, args: unknown, signal?: AbortSignal) => Promise<AgentToolResult>,
|
|
): AgentTool {
|
|
return {
|
|
name,
|
|
label: name,
|
|
description: `${name} tool`,
|
|
parameters: type({}),
|
|
concurrency: "parallel",
|
|
execute,
|
|
} as unknown as AgentTool;
|
|
}
|
|
|
|
function createSession(tools: AgentTool[]): ToolSession {
|
|
const registry = new Map(tools.map(tool => [tool.name, tool]));
|
|
return {
|
|
cwd: "/tmp/test",
|
|
hasUI: false,
|
|
getSessionFile: () => null,
|
|
getSessionSpawns: () => null,
|
|
settings: Settings.isolated(),
|
|
getToolByName: name => registry.get(name),
|
|
};
|
|
}
|
|
|
|
describe("callSessionTool", () => {
|
|
it("injects js intent and summarizes text results", async () => {
|
|
const execute = vi.fn().mockResolvedValue({
|
|
content: [{ type: "text", text: "hello" }],
|
|
});
|
|
const session = createSession([createTool("read", execute)]);
|
|
const statuses: Array<Record<string, unknown>> = [];
|
|
|
|
const result = await callSessionTool(
|
|
"read",
|
|
{ path: "/tmp/demo.txt" },
|
|
{
|
|
session,
|
|
emitStatus: event => {
|
|
statuses.push(event);
|
|
},
|
|
},
|
|
);
|
|
|
|
expect(result).toBe("hello");
|
|
expect(execute).toHaveBeenCalledWith(
|
|
expect.stringMatching(/^js-read-/),
|
|
{ path: "/tmp/demo.txt", [INTENT_FIELD]: "js prelude" },
|
|
undefined,
|
|
);
|
|
expect(statuses).toEqual([expect.objectContaining({ op: "read", path: "/tmp/demo.txt", chars: 5 })]);
|
|
});
|
|
|
|
it("returns structured tool results when details or images are present", async () => {
|
|
const session = createSession([
|
|
createTool("custom", async () => ({
|
|
content: [
|
|
{ type: "text", text: "done" },
|
|
{ type: "image", mimeType: "image/png", data: "abc123" },
|
|
],
|
|
details: { ok: true },
|
|
})),
|
|
]);
|
|
|
|
const result = await callSessionTool("custom", {}, { session });
|
|
|
|
expect(result).toEqual({
|
|
text: "done",
|
|
details: { ok: true },
|
|
images: [{ mimeType: "image/png", data: "abc123" }],
|
|
});
|
|
});
|
|
|
|
it("marks structured results when the underlying tool reports an error", async () => {
|
|
const session = createSession([
|
|
createTool("mcp__demo_fail", async () => ({
|
|
content: [{ type: "text", text: "Error: bad input" }],
|
|
details: { serverName: "demo", mcpToolName: "fail", isError: true },
|
|
})),
|
|
]);
|
|
const statuses: Array<Record<string, unknown>> = [];
|
|
|
|
const result = await callSessionTool(
|
|
"mcp__demo_fail",
|
|
{},
|
|
{ session, emitStatus: event => statuses.push(event) },
|
|
);
|
|
|
|
expect(result).toEqual({
|
|
text: "Error: bad input",
|
|
details: { serverName: "demo", mcpToolName: "fail", isError: true },
|
|
hasError: true,
|
|
});
|
|
expect(statuses).toEqual([
|
|
expect.objectContaining({
|
|
op: "mcp__demo_fail",
|
|
chars: 16,
|
|
hasError: true,
|
|
error: "Error: bad input",
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("marks results with top-level isError", async () => {
|
|
const session = createSession([
|
|
createTool(
|
|
"custom",
|
|
async () =>
|
|
({
|
|
content: [{ type: "text", text: "preview mismatch" }],
|
|
isError: true,
|
|
}) as AgentToolResult,
|
|
),
|
|
]);
|
|
const statuses: Array<Record<string, unknown>> = [];
|
|
|
|
const result = await callSessionTool("custom", {}, { session, emitStatus: event => statuses.push(event) });
|
|
|
|
expect(result).toEqual({
|
|
text: "preview mismatch",
|
|
details: undefined,
|
|
hasError: true,
|
|
});
|
|
expect(statuses).toEqual([
|
|
expect.objectContaining({
|
|
op: "custom",
|
|
chars: 16,
|
|
hasError: true,
|
|
error: "preview mismatch",
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("throws when the requested tool is not available in the session registry", async () => {
|
|
const session = createSession([]);
|
|
|
|
await expect(callSessionTool("missing", {}, { session })).rejects.toThrow("Unknown tool from js runtime");
|
|
});
|
|
});
|