/**
 * HTTP 客户端模块
 * 封装 MCP Tool 的 HTTP 调用
 */
import { logger } from "./logger.js";
function repairDuplicatePort(raw) {
    return raw.replace(/^((?:https?|wss?):\/\/(?:\[[^\]]+\]|[^/:?#]+):\d+):\d+(?=\/|$)/i, "$1");
}
function normalizeMcpHttpUrl(value, options = {}) {
    const raw = String(value || "").trim();
    if (!raw) {
        return undefined;
    }
    const repaired = repairDuplicatePort(raw);
    try {
        const url = new URL(repaired);
        if (url.protocol === "ws:") {
            url.protocol = "http:";
        }
        else if (url.protocol === "wss:") {
            url.protocol = "https:";
        }
        else {
            url.protocol = url.protocol.toLowerCase();
        }
        url.hostname = url.hostname.toLowerCase();
        const basePath = options.fromAgentWebSocketUrl
            ? url.pathname.replace(/\/ws\/agent\/?$/, "")
            : url.pathname;
        url.pathname = basePath.includes("/mcp/call")
            ? basePath
            : `${basePath.replace(/\/$/, "")}/mcp/call`;
        url.search = "";
        url.hash = "";
        return url.toString();
    }
    catch {
        const normalized = (options.fromAgentWebSocketUrl
            ? repaired.replace(/\/ws\/agent\/?$/i, "")
            : repaired).replace(/\/+$/, "");
        return normalized.endsWith("/mcp/call") ? normalized : `${normalized}/mcp/call`;
    }
}
export class HttpClient {
    serverConfig;
    mcpHttpUrl;
    constructor(serverConfig) {
        this.serverConfig = serverConfig;
        this.mcpHttpUrl = this.resolveMcpHttpUrl();
    }
    /**
     * 调用 MCP Tool
     */
    async callTool(toolName, args) {
        await this.ensureRuntimeAccessToken();
        let response = await this.executeToolRequest(toolName, args);
        if (response.status === 401 && this.serverConfig.runtimeBridgeBaseUrl) {
            logger.warn(`[HttpClient] MCP Tool ${toolName} 返回 401，正在强制刷新 runtime token 后重试一次`);
            await this.requestRuntimeAccessToken(true);
            response = await this.executeToolRequest(toolName, args);
        }
        if (!response.ok) {
            throw new Error(`MCP tool call failed: ${response.status} ${response.statusText}`);
        }
        const result = (await response.json());
        return result;
    }
    /**
     * 执行一次 MCP Tool HTTP 请求。
     * 主流程：构建当前 runtime token 请求头 -> POST 到调度中心 MCP 入口 -> 返回原始响应供调用方判断是否刷新 token。
     */
    async executeToolRequest(toolName, args) {
        return await fetch(this.mcpHttpUrl, {
            method: "POST",
            headers: this.buildRequestHeaders(),
            body: JSON.stringify({
                tool: toolName,
                arguments: args,
            }),
        });
    }
    /**
     * 构建 MCP HTTP 请求头。
     * 主流程：始终声明 JSON 内容类型 -> 当 runtime token 存在时同时兼容 X-Runtime-Token 与 Bearer 头。
     */
    buildRequestHeaders() {
        const headers = {
            "Content-Type": "application/json",
        };
        if (this.serverConfig.runtimeAccessToken) {
            headers["X-Runtime-Token"] = this.serverConfig.runtimeAccessToken;
            headers.Authorization =
                `Bearer ${this.serverConfig.runtimeAccessToken}`;
        }
        return headers;
    }
    async ensureRuntimeAccessToken() {
        if (this.serverConfig.runtimeAccessToken ||
            !this.serverConfig.runtimeBridgeBaseUrl) {
            return;
        }
        await this.requestRuntimeAccessToken(false);
    }
    /**
     * 从本机 runtime-bridge 获取或强制刷新 runtime token。
     * 主流程：向 loopback bridge 请求 token -> 校验响应载荷 -> 写回共享 serverConfig 供 HTTP 与 WebSocket 复用。
     */
    async requestRuntimeAccessToken(forceRefresh) {
        const runtimeBridgeBaseUrl = this.serverConfig.runtimeBridgeBaseUrl;
        if (!runtimeBridgeBaseUrl) {
            throw new Error("runtimeBridgeBaseUrl is required");
        }
        const url = `${runtimeBridgeBaseUrl.replace(/\/$/, "")}/runtime/binding/request-token`;
        logger.info(forceRefresh
            ? "[HttpClient] force refreshing runtime token from runtime-bridge"
            : "[HttpClient] runtime token missing, requesting one from runtime-bridge");
        const response = await fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                userId: this.serverConfig.userId,
                serverUrl: this.serverConfig.serverUrl,
                forceRefresh,
            }),
        });
        if (!response.ok) {
            const errorBody = await response.text().catch(() => "");
            throw new Error(`Failed to request runtime token from runtime-bridge: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`);
        }
        const payload = (await response.json());
        if (!payload.runtimeAccessToken) {
            throw new Error(payload.error || "runtime-bridge did not return runtime token");
        }
        this.serverConfig.runtimeAccessToken = payload.runtimeAccessToken;
    }
    /**
     * 解析 MCP HTTP URL
     */
    resolveMcpHttpUrl() {
        const configured = this.serverConfig.mcpHttpUrl;
        if (configured && configured.trim().length > 0) {
            const normalized = normalizeMcpHttpUrl(configured);
            if (normalized) {
                return normalized;
            }
        }
        const fromServerUrl = normalizeMcpHttpUrl(this.serverConfig.serverUrl, {
            fromAgentWebSocketUrl: true,
        });
        if (fromServerUrl) {
            return fromServerUrl;
        }
        return this.serverConfig.serverUrl;
    }
    /**
     * 获取 MCP HTTP URL
     */
    getMcpHttpUrl() {
        return this.mcpHttpUrl;
    }
}
//# sourceMappingURL=http-client.js.map