import { defineTool } from "@sheason/pi-coding-agent";
import { Type } from "typebox";
import { jsonSchemaToTypebox } from "./json-schema-to-typebox.js";
const MAX_MCP_FULL_TOOL_NAME = 64;
const NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
const SYNTHETIC_LIST_RESOURCES = "list_resources";
const SYNTHETIC_READ_RESOURCE = "read_resource";
const SYNTHETIC_LIST_PROMPTS = "list_prompts";
const SYNTHETIC_GET_PROMPT = "get_prompt";
export function buildMcpPrefixedToolName(serverName, toolName) {
    return `mcp__${serverName}__${toolName}`;
}
function isUsableMcpToolName(name) {
    return name.length > 0 && name.length <= MAX_MCP_FULL_TOOL_NAME && NAME_PATTERN.test(name);
}
function textBlock(text) {
    return { type: "text", text };
}
function mcpTextFromContent(result) {
    const parts = [];
    for (const c of result.content) {
        if (c.type === "text" && "text" in c) {
            parts.push(c.text);
        }
    }
    if (result.structuredContent && typeof result.structuredContent === "object") {
        try {
            parts.push(JSON.stringify(result.structuredContent));
        }
        catch {
            // ignore
        }
    }
    return parts.join("\n") || "MCP tool reported an error";
}
/**
 * Map MCP `CallToolResult` blocks to the agent's content block model.
 * Non-text/image blocks are coerced to text for LLM safety.
 */
export function mapCallToolResultToAgentContent(result) {
    const out = [];
    for (const c of result.content) {
        if (c.type === "text" && "text" in c) {
            out.push({ type: "text", text: c.text });
            continue;
        }
        if (c.type === "image" && "data" in c && "mimeType" in c) {
            out.push({ type: "image", data: c.data, mimeType: c.mimeType });
            continue;
        }
        if (c.type === "resource" && "resource" in c) {
            const r = c.resource;
            if ("text" in r && typeof r.text === "string") {
                out.push({ type: "text", text: r.text });
            }
            else {
                try {
                    out.push({ type: "text", text: JSON.stringify(r) });
                }
                catch {
                    out.push({ type: "text", text: "[resource payload]" });
                }
            }
            continue;
        }
        if (c.type === "resource_link" && "name" in c) {
            const uri = c.uri;
            const name = c.name;
            const mime = c.mimeType;
            out.push({ type: "text", text: `resource_link: ${name} <${uri}>${mime ? ` (${mime})` : ""}` });
            continue;
        }
        if (c.type === "audio" && "data" in c) {
            out.push({ type: "text", text: `[audio ${c.mimeType}]` });
            continue;
        }
        try {
            out.push({ type: "text", text: JSON.stringify(c) });
        }
        catch {
            out.push({ type: "text", text: "[mcp content]" });
        }
    }
    return out.length > 0 ? out : [textBlock("")];
}
export function wrapMcpToolAsToolDefinition(serverName, client, tool) {
    const fullName = buildMcpPrefixedToolName(serverName, tool.name);
    if (!isUsableMcpToolName(fullName)) {
        return {
            skipped: true,
            toolName: tool.name,
            reason: `Prefixed name must match ${NAME_PATTERN.source}, length <= ${MAX_MCP_FULL_TOOL_NAME}`,
        };
    }
    const paramsSchema = tool.inputSchema === undefined ? Type.Object({}) : jsonSchemaToTypebox(tool.inputSchema);
    return defineTool({
        name: fullName,
        label: tool.name,
        description: tool.description ?? `MCP tool ${tool.name} on server ${serverName}`,
        parameters: paramsSchema,
        async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
            try {
                const result = (await client.callTool({ name: tool.name, arguments: params ?? {} }, undefined, { signal }));
                if (result.isError) {
                    throw new Error(mcpTextFromContent(result));
                }
                return {
                    content: mapCallToolResultToAgentContent(result),
                    details: {
                        structuredContent: result.structuredContent,
                    },
                };
            }
            catch (e) {
                const msg = e instanceof Error ? e.message : String(e);
                throw new Error(msg);
            }
        },
    });
}
async function listAllResources(client, signal) {
    const out = [];
    let cursor;
    for (;;) {
        const res = await client.listResources(cursor ? { cursor } : {}, { signal });
        for (const r of res.resources) {
            out.push(r);
        }
        if (!res.nextCursor) {
            break;
        }
        cursor = res.nextCursor;
    }
    return out;
}
async function listAllPrompts(client, signal) {
    const out = [];
    let cursor;
    for (;;) {
        const res = await client.listPrompts(cursor ? { cursor } : {}, { signal });
        for (const p of res.prompts) {
            out.push(p);
        }
        if (!res.nextCursor) {
            break;
        }
        cursor = res.nextCursor;
    }
    return out;
}
const listResourcesEmptySchema = Type.Object({});
const readResourceParamSchema = Type.Object({
    uri: Type.String({ description: "Resource URI to read" }),
});
const listPromptsEmptySchema = Type.Object({});
const getPromptParamSchema = Type.Object({
    name: Type.String({ description: "Prompt name from list_prompts" }),
    arguments: Type.Optional(Type.Unsafe({ type: "object", additionalProperties: { type: "string" } })),
});
/**
 * Synthesize the hub `list_resources` / `read_resource` / `list_prompts` / `get_prompt` tools
 * and merge them with server-declared tools.
 */
export function wrapMcpServerAsToolDefinitions(serverName, handle) {
    const client = handle.client;
    const usedNames = new Set();
    const tools = [];
    const skipped = [];
    const register = (def) => {
        usedNames.add(def.name);
        tools.push(def);
    };
    const tryAddSyn = (syntheticName, kind, build) => {
        const full = buildMcpPrefixedToolName(serverName, syntheticName);
        if (usedNames.has(full)) {
            skipped.push({
                kind,
                name: syntheticName,
                reason: "Prefixed name already used by another tool for this server",
            });
            return;
        }
        if (!isUsableMcpToolName(full)) {
            skipped.push({
                kind,
                name: syntheticName,
                reason: "Synthetic prefixed name is invalid or too long",
            });
            return;
        }
        register(build());
    };
    if (handle.supportedCapabilities.tools) {
        for (const t of handle.capabilities.tools) {
            const w = wrapMcpToolAsToolDefinition(serverName, client, t);
            if ("skipped" in w) {
                skipped.push({ kind: "tool", name: t.name, reason: w.reason });
                continue;
            }
            if (usedNames.has(w.name)) {
                skipped.push({
                    kind: "tool",
                    name: t.name,
                    reason: "Prefixed name already used (duplicate tool name?)",
                });
                continue;
            }
            register(w);
        }
    }
    if (handle.supportedCapabilities.resources) {
        tryAddSyn(SYNTHETIC_LIST_RESOURCES, "resource", () => defineTool({
            name: buildMcpPrefixedToolName(serverName, SYNTHETIC_LIST_RESOURCES),
            label: SYNTHETIC_LIST_RESOURCES,
            description: `List resource URIs and metadata for MCP server "${serverName}"`,
            parameters: listResourcesEmptySchema,
            async execute(_t, _p, signal, _on, _ctx) {
                const resources = await listAllResources(client, signal);
                return {
                    content: [{ type: "text", text: JSON.stringify(resources, null, 2) }],
                    details: { resources },
                };
            },
        }));
        tryAddSyn(SYNTHETIC_READ_RESOURCE, "resource", () => defineTool({
            name: buildMcpPrefixedToolName(serverName, SYNTHETIC_READ_RESOURCE),
            label: SYNTHETIC_READ_RESOURCE,
            description: `Read a resource by URI for MCP server "${serverName}"`,
            parameters: readResourceParamSchema,
            async execute(_t, params, signal, _on, _ctx) {
                try {
                    const r = await client.readResource({ uri: params.uri }, { signal });
                    return {
                        content: [
                            {
                                type: "text",
                                text: JSON.stringify(r.contents, null, 2),
                            },
                        ],
                        details: { contents: r.contents },
                    };
                }
                catch (e) {
                    const msg = e instanceof Error ? e.message : String(e);
                    throw new Error(msg);
                }
            },
        }));
    }
    else {
        skipped.push({
            kind: "resource",
            name: SYNTHETIC_LIST_RESOURCES,
            reason: "Server does not implement resources (method not found)",
        });
        skipped.push({
            kind: "resource",
            name: SYNTHETIC_READ_RESOURCE,
            reason: "Server does not implement resources (method not found)",
        });
    }
    if (handle.supportedCapabilities.prompts) {
        tryAddSyn(SYNTHETIC_LIST_PROMPTS, "prompt", () => defineTool({
            name: buildMcpPrefixedToolName(serverName, SYNTHETIC_LIST_PROMPTS),
            label: SYNTHETIC_LIST_PROMPTS,
            description: `List available prompts for MCP server "${serverName}"`,
            parameters: listPromptsEmptySchema,
            async execute(_t, _p, signal, _on, _ctx) {
                const prompts = await listAllPrompts(client, signal);
                return {
                    content: [{ type: "text", text: JSON.stringify(prompts, null, 2) }],
                    details: { prompts },
                };
            },
        }));
        tryAddSyn(SYNTHETIC_GET_PROMPT, "prompt", () => defineTool({
            name: buildMcpPrefixedToolName(serverName, SYNTHETIC_GET_PROMPT),
            label: SYNTHETIC_GET_PROMPT,
            description: `Load prompt messages for MCP server "${serverName}"`,
            parameters: getPromptParamSchema,
            async execute(_t, params, signal, _on, _ctx) {
                try {
                    const r = await client.getPrompt({ name: params.name, arguments: params.arguments }, { signal });
                    return {
                        content: [{ type: "text", text: JSON.stringify(r.messages, null, 2) }],
                        details: { messages: r.messages },
                    };
                }
                catch (e) {
                    const msg = e instanceof Error ? e.message : String(e);
                    throw new Error(msg);
                }
            },
        }));
    }
    else {
        skipped.push({
            kind: "prompt",
            name: SYNTHETIC_LIST_PROMPTS,
            reason: "Server does not implement prompts (method not found)",
        });
        skipped.push({
            kind: "prompt",
            name: SYNTHETIC_GET_PROMPT,
            reason: "Server does not implement prompts (method not found)",
        });
    }
    return { tools, skipped };
}
//# sourceMappingURL=mcp-tool-bridge.js.map