import { DeepSeekCodeAbortError, throwIfAborted } from "../utils/abort.js";
export class McpHttpClient {
    url;
    timeoutMs;
    signal;
    nextId = 1;
    sessionId = "";
    constructor(url, timeoutMs = 10_000, signal) {
        this.url = url;
        this.timeoutMs = timeoutMs;
        this.signal = signal;
    }
    async connect() {
        const result = await this.request("initialize", {
            protocolVersion: "2024-11-05",
            capabilities: {},
            clientInfo: {
                name: "deepseekcode",
                version: "0.1.0",
            },
        });
        await this.notify("notifications/initialized", {});
        return result;
    }
    async listTools() {
        const result = await this.request("tools/list", {});
        if (isRecord(result) && Array.isArray(result.tools))
            return result.tools;
        return [];
    }
    async callTool(name, args) {
        return this.request("tools/call", {
            name,
            arguments: args,
        });
    }
    close() {
        this.sessionId = "";
    }
    async request(method, params) {
        const id = this.nextId++;
        const response = await this.post({ jsonrpc: "2.0", id, method, params });
        if (response.error)
            throw new Error(response.error.message);
        return response.result;
    }
    async notify(method, params) {
        await this.post({ jsonrpc: "2.0", method, params }, true);
    }
    async post(message, notification = false) {
        const controller = new AbortController();
        throwIfAborted(this.signal);
        const timer = setTimeout(() => controller.abort(new Error("MCP HTTP request timed out")), this.timeoutMs);
        const abortFromCaller = () => controller.abort(new DeepSeekCodeAbortError(this.signal?.reason));
        if (this.signal?.aborted)
            abortFromCaller();
        else
            this.signal?.addEventListener("abort", abortFromCaller, { once: true });
        try {
            const response = await fetch(this.url, {
                method: "POST",
                headers: {
                    "content-type": "application/json",
                    "accept": "application/json, text/event-stream",
                    "mcp-protocol-version": "2024-11-05",
                    ...(this.sessionId ? { "mcp-session-id": this.sessionId } : {}),
                },
                body: JSON.stringify(message),
                signal: controller.signal,
            });
            const session = response.headers.get("mcp-session-id");
            if (session)
                this.sessionId = session;
            if (notification && (response.status === 202 || response.status === 204)) {
                return { jsonrpc: "2.0", id: 0, result: null };
            }
            if (!response.ok)
                throw new Error(`MCP HTTP request failed: ${response.status} ${response.statusText}`);
            const text = await response.text();
            if (!text.trim())
                return { jsonrpc: "2.0", id: 0, result: null };
            const parsed = response.headers.get("content-type")?.includes("text/event-stream")
                ? parseSseJsonRpc(text)
                : JSON.parse(text);
            if (Array.isArray(parsed)) {
                const id = isRecord(message) && typeof message.id === "number" ? message.id : 0;
                return parsed.find((entry) => isRecord(entry) && entry.id === id);
            }
            return parsed;
        }
        finally {
            clearTimeout(timer);
            this.signal?.removeEventListener("abort", abortFromCaller);
        }
    }
}
export async function probeMcpHttpServer(url, timeoutMs = 10_000) {
    const client = new McpHttpClient(url, timeoutMs);
    const server = await client.connect();
    const tools = await client.listTools();
    return {
        server,
        tools,
        stderr: "",
    };
}
export async function callMcpHttpTool(url, toolName, args, timeoutMs = 10_000, signal) {
    const client = new McpHttpClient(url, timeoutMs, signal);
    await client.connect();
    const result = await client.callTool(toolName, args);
    return { result, stderr: "" };
}
function parseSseJsonRpc(text) {
    const events = [];
    let current = [];
    for (const line of text.split(/\r?\n/)) {
        if (!line.trim()) {
            if (current.length > 0) {
                events.push(current.join("\n"));
                current = [];
            }
            continue;
        }
        if (line.startsWith("data:"))
            current.push(line.slice("data:".length).trimStart());
    }
    if (current.length > 0)
        events.push(current.join("\n"));
    const parsed = events.map((event) => JSON.parse(event));
    return parsed.length === 1 ? parsed[0] : parsed;
}
function isRecord(value) {
    return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
