import assert from "node:assert/strict"; import test from "node:test"; import type { ToolRendererResolver, ToolRenderers } from "@selesai/code"; import { Text } from "@earendil-works/pi-tui"; import { registerToolDisplayOverrides } from "../src/tool-overrides.ts"; import { isMcpToolIdentity, isNativeMcpToolName } from "../src/tool-metadata.ts"; import { DEFAULT_TOOL_DISPLAY_CONFIG, type ToolDisplayConfig } from "../src/types.ts"; import { createExtensionApiHarness, createTestTheme, renderComponentText, resolveToolRenderers, } from "./tool-renderer-harness.ts"; // ─── Test Helpers ──────────────────────────────────────────────────────────── function buildConfig(overrides: Partial = {}): ToolDisplayConfig { return { ...DEFAULT_TOOL_DISPLAY_CONFIG, ...overrides, boxedToolCalls: overrides.boxedToolCalls ?? false, registerToolOverrides: { ...DEFAULT_TOOL_DISPLAY_CONFIG.registerToolOverrides, ...overrides.registerToolOverrides, }, }; } function createDisplayResolver(config: ToolDisplayConfig): ToolRendererResolver { const harness = createExtensionApiHarness(); registerToolDisplayOverrides(harness.api, () => config); assert.equal(harness.resolvers.length, 1, "pi-tool-display registers one resolver"); return harness.resolvers[0]!; } function createSentinelRenderers(text: string): ToolRenderers { return { renderCall: () => new Text(text, 0, 0), renderResult: () => new Text(text, 0, 0), }; } /** * The native MCP extension's resolver as written in `src/extensions/mcp/index.ts`: * `next() ?? createMcpToolRenderers(...)`. The fallback renderer here only proves * which side drew a row. */ const nativeMcpFallback = "NATIVE MCP FALLBACK"; const nativeMcpResolver: ToolRendererResolver = (toolName, next) => { const match = /^mcp__(.+?)__(.+)$/.exec(toolName); return next() ?? (match ? createSentinelRenderers(nativeMcpFallback) : undefined); }; function resolveInOrder( resolver: ToolRendererResolver, toolName: string, base: () => ToolRenderers | undefined, nativeFirst = false, ): ToolRenderers | undefined { const resolvers = nativeFirst ? [nativeMcpResolver, resolver] : [resolver, nativeMcpResolver]; return resolveToolRenderers(resolvers, toolName, base); } function renderCall(renderers: ToolRenderers | undefined, args: Record): string { assert.ok(renderers?.renderCall, "expected renderCall"); return renderComponentText(renderers.renderCall(args, createTestTheme() as never, {} as never)); } function renderResult( renderers: ToolRenderers | undefined, text: string, options: { expanded?: boolean; isPartial?: boolean } = {}, ): string { assert.ok(renderers?.renderResult, "expected renderResult"); return renderComponentText( renderers.renderResult( { content: [{ type: "text", text }], details: {}, isError: false } as never, { expanded: options.expanded ?? false, isPartial: options.isPartial ?? false } as never, createTestTheme() as never, {} as never, ), ); } // ─── Resolver order and historical names ──────────────────────────────────── test("native MCP renders when pi-tool-display resolves before the native MCP resolver", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary" })); const existingDefinition = createSentinelRenderers("REGISTERED DEFINITION"); const renderers = resolveInOrder(resolver, "mcp__filesystem__read_file", () => existingDefinition); assert.equal(renderCall(renderers, { path: "a.txt" }), "MCP filesystem/read_file (1 arg)"); assert.equal(renderResult(renderers, "one\ntwo\n"), "↳ 2 lines returned • Ctrl+O to expand"); }); test("native MCP renders when the native MCP resolver resolves before pi-tool-display", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary" })); const existingDefinition = createSentinelRenderers("REGISTERED DEFINITION"); const renderers = resolveInOrder(resolver, "mcp__filesystem__read_file", () => existingDefinition, true); assert.equal(renderCall(renderers, { path: "a.txt" }), "MCP filesystem/read_file (1 arg)"); assert.equal(renderResult(renderers, "one\ntwo\n"), "↳ 2 lines returned • Ctrl+O to expand"); }); test("historical MCP call still renders when its tool definition is absent, in both resolver orders", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary" })); for (const nativeFirst of [false, true]) { const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined, nativeFirst); assert.equal(renderCall(renderers, { query: "hooks" }), "MCP docs/search (1 arg)"); assert.equal(renderResult(renderers, "alpha\nbeta\n"), "↳ 2 lines returned • Ctrl+O to expand"); } }); // ─── mcpOutputMode ────────────────────────────────────────────────────────── test("mcpOutputMode hidden keeps expanded output available", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "hidden" })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); assert.equal(renderResult(renderers, "secret\nnoise\n"), ""); assert.equal(renderResult(renderers, "secret\nnoise\n", { expanded: true }), "secret\nnoise"); }); test("mcpOutputMode summary summarizes collapsed output and previews it expanded", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary" })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); assert.equal(renderResult(renderers, "alpha\nbeta\n"), "↳ 2 lines returned • Ctrl+O to expand"); assert.equal(renderResult(renderers, "alpha\nbeta\n", { expanded: true }), "alpha\nbeta"); assert.equal(renderResult(renderers, "still running", { isPartial: true }), "running..."); }); test("mcpOutputMode preview follows previewLines and expands to the full output", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "preview", previewLines: 2 })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); assert.equal( renderResult(renderers, "alpha\nbeta\ngamma\ndelta\n"), "alpha\nbeta\n... (2 more lines • Ctrl+O to expand)", ); assert.equal( renderResult(renderers, "alpha\nbeta\ngamma\ndelta\n", { expanded: true }), "alpha\nbeta\ngamma\ndelta", ); }); test("native MCP rendering does not depend on registerToolOverrides", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary", registerToolOverrides: { read: false, grep: false, find: false, ls: false, bash: false, powershell: false, edit: false, write: false, }, })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); assert.equal(renderCall(renderers, { query: "hooks" }), "MCP docs/search (1 arg)"); }); test("native MCP rendering follows the configured boxed frame", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary", boxedToolCalls: true })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); const text = renderCall(renderers, { query: "hooks" }); assert.equal(renderers?.renderShell, "self"); assert.match(text, /MCP docs\/search \(1 arg\)/); assert.match(text, /[┌│└]/); }); // ─── Classification ───────────────────────────────────────────────────────── test("codemode is not MCP despite an MCP mention in its description", () => { const codemodeTool = { name: "codemode", label: "codemode", description: "Run TypeScript that calls tools, including MCP tools and any other registered tool.", parameters: { type: "object", properties: {} }, sourceInfo: { path: "builtin:codemode", source: "builtin", scope: "temporary", origin: "top-level", }, }; assert.equal(isMcpToolIdentity(codemodeTool), false); assert.equal(isNativeMcpToolName(codemodeTool.name), false); const resolver = createDisplayResolver(buildConfig({ codemodeDisplay: false })); const nextRenderers = createSentinelRenderers("CODEMODE DEFINITION"); assert.equal(resolveInOrder(resolver, "codemode", () => nextRenderers), nextRenderers); }); test("missing or non-object tool values do not classify as MCP", () => { for (const value of [undefined, null, "mcp__docs__search", 42, []]) { assert.equal(isMcpToolIdentity(value), false); } }); test("MCP-like names and schemas alone do not classify a tool as MCP", () => { for (const name of ["server:tool", "ctx_tool", "tool_mcp", "mcp_tool", "mcpify"]) { assert.equal(isNativeMcpToolName(name), false, `${name} is not a native MCP name`); assert.equal( isMcpToolIdentity({ name, description: "MCP tool for external servers.", parameters: { mcpServer: { type: "string" }, serverUrl: { type: "string" } }, }), false, `${name} must not be classified as MCP from its description or schema`, ); } }); test("native mcp__ names and credible MCP source metadata classify as MCP", () => { assert.equal(isNativeMcpToolName("mcp__docs__search"), true); assert.equal(isMcpToolIdentity({ name: "mcp__docs__search" }), true); assert.equal( isMcpToolIdentity({ name: "xcodebuild_list_sims", sourceInfo: { path: "/Users/me/.selesai/agent/extensions/pi-mcp-adapter/index.ts" }, }), true, ); assert.equal( isMcpToolIdentity({ name: "lint", sourceInfo: { path: "/Users/me/work/mcp-project/extensions/lint/index.ts" }, }), false, "a project directory containing 'mcp' is not a credible MCP source", ); }); test("unknown names call next()", () => { const resolver = createDisplayResolver(buildConfig()); const nextRenderers = createSentinelRenderers("OWNER RENDERERS"); assert.equal(resolveInOrder(resolver, "some_extension_tool", () => nextRenderers), nextRenderers); }); test("enabled:false calls next() for every tool", () => { const resolver = createDisplayResolver(buildConfig({ enabled: false })); const nextRenderers = createSentinelRenderers("OWNER RENDERERS"); for (const name of ["read", "mcp__docs__search", "some_extension_tool"]) { assert.equal(resolveToolRenderers([resolver], name, () => nextRenderers), nextRenderers, name); } }); test("enabled:false leaves the native MCP resolver's fallback in charge", () => { const resolver = createDisplayResolver(buildConfig({ enabled: false })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined, true); assert.equal(renderCall(renderers, {}), nativeMcpFallback); }); test("a disabled custom override calls next() for a non-MCP tool", () => { const resolver = createDisplayResolver(buildConfig({ customToolOverrides: { ide_find_symbol: { enabled: false, kind: "generic", outputMode: "summary" }, }, })); const nextRenderers = createSentinelRenderers("OWNER RENDERERS"); assert.equal(resolveToolRenderers([resolver], "ide_find_symbol", () => nextRenderers), nextRenderers); }); test("a disabled custom override on a native MCP name keeps the native MCP renderer", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary", customToolOverrides: { mcp__docs__search: { enabled: false, kind: "generic", outputMode: "hidden" }, }, })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); assert.equal(renderCall(renderers, { query: "hooks" }), "MCP docs/search (1 arg)"); assert.equal(renderResult(renderers, "one\ntwo\n"), "↳ 2 lines returned • Ctrl+O to expand"); }); test("an enabled custom override takes precedence over native MCP rendering", () => { const resolver = createDisplayResolver(buildConfig({ mcpOutputMode: "summary", customToolOverrides: { mcp__docs__search: { enabled: true, kind: "generic", outputMode: "hidden" }, }, })); const renderers = resolveInOrder(resolver, "mcp__docs__search", () => undefined); assert.equal(renderCall(renderers, { query: "hooks" }), "mcp__docs__search (1 arg)"); assert.equal(renderResult(renderers, "one\ntwo\n"), ""); }); test("native MCP rendering does not mutate the tool definition or call registerTool", () => { const harness = createExtensionApiHarness({ tools: [{ name: "mcp__docs__search", description: "Search the docs.", parameters: { type: "object", properties: { query: { type: "string" } } }, promptSnippet: "Search docs", promptGuidelines: ["Use for docs."], }], }); registerToolDisplayOverrides(harness.api, () => buildConfig({ mcpOutputMode: "summary" })); assert.equal(harness.resolvers.length, 1); assert.equal(harness.registeredTools.length, 0); assert.equal(harness.api.getAllTools().length, 1); const definition = harness.api.getAllTools()[0]!; const before = JSON.stringify(definition); resolveToolRenderers(harness.resolvers, "mcp__docs__search", () => undefined); assert.equal(JSON.stringify(definition), before); });