import { BrowserSurface } from "../surfaces/browser.js";
import { SSRFGuard } from "../security/ssrf-guard.js";
import { SandboxManager } from "../security/sandbox-manager.js";
const DEFAULT_BROWSER_ID = "browser-default";
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
const DEFAULT_OPENAI_WEB_SEARCH_MODEL = "gpt-5";
const DEFAULT_PERPLEXITY_MODEL = "sonar-pro";
const DEFAULT_PERPLEXITY_OPENROUTER_MODEL = "perplexity/sonar-pro";
const DEFAULT_GROK_MODEL = "grok-4-1212";
const DEFAULT_GEMINI_MODEL = "gemini-2.0-flash";
const DEFAULT_KIMI_BASE_URL = "https://api.moonshot.ai/v1";
const DEFAULT_KIMI_MODEL = "moonshot-v1-128k";
function resolveActivePreviewSession(collaboration, context) {
    const sessionId = context.sessionId?.trim();
    if (!sessionId || !collaboration)
        return null;
    const getter = collaboration.getSessionByPreviewSessionId;
    if (typeof getter !== "function")
        return null;
    return getter.call(collaboration, sessionId);
}
function assertPreviewBrowserSelection(previewSession, browserId) {
    if (!previewSession)
        return;
    const previewBrowserId = previewSession.browserId?.trim();
    if (!previewBrowserId) {
        throw new Error("The active preview session is missing its linked browser. Restart the preview and try again.");
    }
    if (browserId && browserId !== previewBrowserId) {
        throw new Error(`An active preview is linked to this session. Use the visible preview browser '${previewBrowserId}' instead of '${browserId}'.`);
    }
}
async function ensureBrowserSurface(registry, context, browserId, collaboration) {
    const previewSession = resolveActivePreviewSession(collaboration, context);
    assertPreviewBrowserSelection(previewSession, browserId);
    if (previewSession?.browserId) {
        const preview = registry.getSurface(previewSession.browserId);
        if (preview?.type === "browser" && preview.state === "running") {
            return preview;
        }
        throw new Error(`The visible preview browser '${previewSession.browserId}' is not running. Restart the preview before using browser tools.`);
    }
    // When no explicit browserId is given, prefer the active preview browser
    // for this session so agents don't need to look it up separately.
    if (!browserId && context.sessionId) {
        const previewId = `preview-browser-${context.sessionId}`;
        const preview = registry.getSurface(previewId);
        if (preview?.type === "browser" && preview.state === "running") {
            return preview;
        }
    }
    const id = browserId ?? DEFAULT_BROWSER_ID;
    const existing = registry.getSurface(id);
    if (existing?.type === "browser" && existing.state === "running") {
        return existing;
    }
    const started = await registry.startSurface("browser", id, {
        sessionId: context.sessionId,
        projectRoot: context.projectRoot,
    });
    if (started.type !== "browser") {
        throw new Error(`Surface '${id}' is not a browser surface`);
    }
    return started;
}
function resolveBrowserSessionMetadata(collaboration, browserId) {
    if (!collaboration)
        return null;
    return collaboration.getSessionByBrowserId(browserId);
}
function isSecretSafeSession(session) {
    return Boolean(session?.secretSafe);
}
function redactBrowserCapturePayload(payload, session) {
    if (!isSecretSafeSession(session))
        return payload;
    return {
        ...payload,
        textPreview: "",
        interactiveElements: [],
        activeElement: null,
        dialogs: [],
        obstruction: null,
        target: null,
        captureSuppressed: true,
        suppressionReason: "Browser capture is suppressed while the session is marked secret-safe.",
    };
}
function buildSnapshotPayload(browserId, snapshot, session, target) {
    return redactBrowserCapturePayload({
        browserId,
        url: snapshot.url,
        title: snapshot.title,
        textPreview: snapshot.text.slice(0, 500),
        interactiveElements: snapshot.elements.slice(0, 10),
        activeElement: snapshot.activeElement ?? null,
        dialogs: snapshot.dialogs ?? [],
        obstruction: snapshot.obstruction ?? null,
        target: target ?? null,
        controller: session?.controller ?? null,
        secretSafe: session?.secretSafe ?? null,
        browserSession: session,
    }, session);
}
export function createBrowserNavigateTool(registry, collaboration) {
    return {
        name: "browser.navigate",
        description: "Navigate the browser to a URL and return a page summary. When a preview is active, automatically targets the preview browser.",
        tier: "standard",
        category: "browser",
        source: "builtin",
        parameters: {
            type: "object",
            properties: {
                url: { type: "string", description: "HTTP/HTTPS URL to open" },
                browserId: { type: "string", description: "Optional browser surface ID" },
            },
            required: ["url"],
        },
        async execute(input, context) {
            if (context.signal?.aborted)
                return { ok: false, message: "Cancelled" };
            const previewSession = resolveActivePreviewSession(collaboration, context);
            assertPreviewBrowserSelection(previewSession, input.browserId);
            collaboration?.assertAgentControl(previewSession?.browserId ?? input.browserId);
            const surface = await ensureBrowserSurface(registry, context, input.browserId, collaboration);
            const snapshot = await surface.navigate(input.url, context.signal);
            const session = resolveBrowserSessionMetadata(collaboration, surface.id);
            return {
                ok: true,
                message: `Navigated to ${snapshot.url}`,
                data: buildSnapshotPayload(surface.id, snapshot, session),
            };
        },
    };
}
export function createBrowserSnapshotTool(registry, collaboration) {
    return {
        name: "browser.snapshot",
        description: "Return a structured textual browser snapshot for the current page. When a preview is active, automatically targets the preview browser.",
        tier: "standard",
        category: "browser",
        source: "builtin",
        parameters: {
            type: "object",
            properties: {
                browserId: { type: "string", description: "Optional browser surface ID" },
            },
        },
        async execute(input, context) {
            if (context.signal?.aborted)
                return { ok: false, message: "Cancelled" };
            const previewSession = resolveActivePreviewSession(collaboration, context);
            assertPreviewBrowserSelection(previewSession, input.browserId);
            collaboration?.assertAgentControl(previewSession?.browserId ?? input.browserId);
            const surface = await ensureBrowserSurface(registry, context, input.browserId, collaboration);
            const description = await surface.describe(context.signal);
            const inspection = await surface.inspect(undefined, context.signal);
            const session = resolveBrowserSessionMetadata(collaboration, surface.id);
            return {
                ok: true,
                message: isSecretSafeSession(session)
                    ? "Browser snapshot suppressed because the session is marked secret-safe"
                    : "Browser snapshot captured",
                data: {
                    snapshot: isSecretSafeSession(session)
                        ? "Browser capture is suppressed while the session is marked secret-safe."
                        : description,
                    ...buildSnapshotPayload(surface.id, inspection.snapshot, session),
                },
            };
        },
    };
}
export function createBrowserInspectTool(registry, collaboration) {
    return {
        name: "browser.inspect",
        description: "Inspect the current browser page and optionally diagnose a target selector. When a preview is active, automatically targets the preview browser.",
        tier: "standard",
        category: "browser",
        source: "builtin",
        parameters: {
            type: "object",
            properties: {
                selector: { type: "string", description: "Optional CSS selector to diagnose" },
                browserId: { type: "string", description: "Optional browser surface ID" },
            },
        },
        async execute(input, context) {
            if (context.signal?.aborted)
                return { ok: false, message: "Cancelled" };
            const previewSession = resolveActivePreviewSession(collaboration, context);
            assertPreviewBrowserSelection(previewSession, input.browserId);
            collaboration?.assertAgentControl(previewSession?.browserId ?? input.browserId);
            const surface = await ensureBrowserSurface(registry, context, input.browserId, collaboration);
            const inspection = await surface.inspect(input.selector, context.signal);
            const session = resolveBrowserSessionMetadata(collaboration, surface.id);
            return {
                ok: true,
                message: isSecretSafeSession(session)
                    ? "Browser inspection suppressed because the session is marked secret-safe"
                    : input.selector
                        ? `Browser inspection captured for ${input.selector}`
                        : "Browser inspection captured",
                data: buildSnapshotPayload(surface.id, inspection.snapshot, session, inspection.target),
            };
        },
    };
}
function makeActionTool(registry, collaboration, name, description, properties, required, action) {
    return {
        name,
        description,
        tier: "standard",
        category: "browser",
        source: "builtin",
        parameters: { type: "object", properties, required },
        async execute(input, context) {
            if (context.signal?.aborted)
                return { ok: false, message: "Cancelled" };
            const browserId = typeof input === "object" && input !== null && "browserId" in input
                ? String(input.browserId ?? "") || undefined
                : undefined;
            const previewSession = resolveActivePreviewSession(collaboration, context);
            assertPreviewBrowserSelection(previewSession, browserId);
            collaboration?.assertAgentControl(previewSession?.browserId ?? browserId);
            const surface = await ensureBrowserSurface(registry, context, browserId, collaboration);
            const result = await action(surface, input, context.signal);
            // Return a page snapshot after every action so agents can verify the
            // result without a separate browser.snapshot / browser.inspect call.
            const inspection = await surface.inspect(undefined, context.signal).catch(() => null);
            const session = resolveBrowserSessionMetadata(collaboration, surface.id);
            const payload = inspection
                ? buildSnapshotPayload(surface.id, inspection.snapshot, session)
                : { browserId: surface.id };
            return {
                ok: true,
                message: `${name} executed`,
                data: { ...payload, actionResult: result },
            };
        },
    };
}
export function createBrowserInteractionTools(registry, collaboration) {
    return [
        makeActionTool(registry, collaboration, "browser.click", "Click an element in the browser and return the updated page snapshot. When a preview is active, automatically targets the preview browser. Supports CSS selectors and Playwright text selectors (e.g. 'button:has-text(\"Save\")' or 'text=Submit').", {
            selector: { type: "string", description: "CSS or Playwright selector for the target element (e.g. '#my-btn', 'button:has-text(\"OK\")')." },
            browserId: { type: "string", description: "Browser surface ID (from preview.inspect or preview.status). Auto-resolves to the preview browser when omitted." },
        }, ["selector"], async (surface, input, signal) => {
            await surface.click(input.selector, signal);
            return { selector: input.selector };
        }),
        makeActionTool(registry, collaboration, "browser.type", "Type text into an element and return the updated page snapshot. When a preview is active, automatically targets the preview browser.", {
            selector: { type: "string", description: "CSS or Playwright selector for the target input" },
            text: { type: "string", description: "Text to type" },
            browserId: { type: "string", description: "Browser surface ID. Auto-resolves to the preview browser when omitted." },
        }, ["selector", "text"], async (surface, input, signal) => {
            await surface.typeText(input.selector, input.text, signal);
            return { selector: input.selector };
        }),
        makeActionTool(registry, collaboration, "browser.scroll", "Scroll the browser viewport and return the updated page snapshot. When a preview is active, automatically targets the preview browser.", {
            x: { type: "number", description: "Horizontal scroll position" },
            y: { type: "number", description: "Vertical scroll position" },
            browserId: { type: "string", description: "Browser surface ID. Auto-resolves to the preview browser when omitted." },
        }, ["x", "y"], async (surface, input, signal) => {
            await surface.scroll(input.x, input.y, signal);
            return { x: input.x, y: input.y };
        }),
        makeActionTool(registry, collaboration, "browser.select", "Select a value from a select element and return the updated page snapshot. When a preview is active, automatically targets the preview browser.", {
            selector: { type: "string", description: "CSS or Playwright selector for the select element" },
            value: { type: "string", description: "Option value to choose" },
            browserId: { type: "string", description: "Browser surface ID. Auto-resolves to the preview browser when omitted." },
        }, ["selector", "value"], async (surface, input, signal) => {
            await surface.select(input.selector, input.value, signal);
            return { selector: input.selector, value: input.value };
        }),
        makeActionTool(registry, collaboration, "browser.wait", "Wait for an element to appear and return the page snapshot once found. When a preview is active, automatically targets the preview browser.", {
            selector: { type: "string", description: "CSS or Playwright selector to wait for" },
            timeoutMs: { type: "number", description: "Timeout in milliseconds (default 10000)" },
            browserId: { type: "string", description: "Browser surface ID. Auto-resolves to the preview browser when omitted." },
        }, ["selector"], async (surface, input, signal) => {
            await surface.waitFor(input.selector, input.timeoutMs ?? 10000, signal);
            return { selector: input.selector, timeoutMs: input.timeoutMs ?? 10000 };
        }),
        {
            name: "browser.screenshot",
            description: "Capture a browser screenshot",
            tier: "standard",
            category: "browser",
            source: "builtin",
            parameters: {
                type: "object",
                properties: {
                    path: { type: "string", description: "Optional output file path" },
                    browserId: { type: "string", description: "Optional browser surface ID" },
                },
            },
            async execute(input, context) {
                if (context.signal?.aborted)
                    return { ok: false, message: "Cancelled" };
                const browserId = input.browserId?.trim() || undefined;
                const previewSession = resolveActivePreviewSession(collaboration, context);
                assertPreviewBrowserSelection(previewSession, browserId);
                collaboration?.assertAgentControl(previewSession?.browserId ?? browserId);
                const surface = await ensureBrowserSurface(registry, context, browserId, collaboration);
                const session = resolveBrowserSessionMetadata(collaboration, surface.id);
                if (isSecretSafeSession(session)) {
                    return {
                        ok: false,
                        message: "Browser screenshot suppressed because the session is marked secret-safe",
                        data: {
                            browserId: surface.id,
                            captureSuppressed: true,
                            suppressionReason: "Browser capture is suppressed while the session is marked secret-safe.",
                        },
                    };
                }
                const screenshotPath = await surface.screenshot(input.path, context.signal);
                return {
                    ok: true,
                    message: "browser.screenshot executed",
                    data: { browserId: surface.id, result: { path: screenshotPath } },
                };
            },
        },
    ];
}
export function createWebFetchTool(guard = new SSRFGuard()) {
    return {
        name: "web.fetch",
        description: "Fetch URL content with SSRF protections for public web access",
        tier: "standard",
        category: "web",
        source: "builtin",
        parameters: {
            type: "object",
            properties: {
                url: { type: "string", description: "Public URL to fetch" },
                method: { type: "string", description: "HTTP method" },
                body: { type: "string", description: "Request body" },
                timeoutMs: { type: "number", description: "Request timeout in ms" },
                ignoreTlsErrors: { type: "boolean", description: "Allow retry with TLS verification disabled" },
            },
            required: ["url"],
        },
        async execute(input, context) {
            if (context?.signal?.aborted)
                return { ok: false, message: "Cancelled" };
            const url = guard.validate(input.url).toString();
            const timeoutMs = input.timeoutMs ?? 15000;
            const maxBytes = input.maxBytes ?? 50_000;
            const controller = new AbortController();
            // Link external abort signal to our controller so cancellation propagates
            if (context?.signal) {
                if (context.signal.aborted) {
                    controller.abort();
                }
                else {
                    context.signal.addEventListener("abort", () => controller.abort(), { once: true });
                }
            }
            const timeout = setTimeout(() => controller.abort(), timeoutMs);
            const ignoreTlsErrors = input.ignoreTlsErrors === true || process.env["WEB_FETCH_IGNORE_TLS_ERRORS"] === "true";
            try {
                const { response, insecureTlsUsed } = await fetchWithTlsFallback(url, {
                    method: input.method ?? "GET",
                    body: input.body,
                    headers: input.headers,
                    signal: controller.signal,
                }, ignoreTlsErrors);
                const text = await response.text();
                return {
                    ok: response.ok,
                    message: response.ok
                        ? insecureTlsUsed
                            ? `Fetched ${url} (TLS verification disabled)`
                            : `Fetched ${url}`
                        : `Request failed with ${response.status}`,
                    data: {
                        url,
                        status: response.status,
                        contentType: response.headers.get("content-type"),
                        body: text.slice(0, maxBytes),
                        insecureTlsUsed,
                    },
                };
            }
            catch (err) {
                const message = extractErrorMessage(err);
                return {
                    ok: false,
                    message: `Fetch failed: ${message}`,
                    data: { url, error: message },
                };
            }
            finally {
                clearTimeout(timeout);
            }
        },
    };
}
export function createWebSearchTool(guard = new SSRFGuard()) {
    return {
        name: "web.search",
        description: "Search the public web (auto, openai, brave, perplexity, grok, gemini, kimi)",
        tier: "standard",
        category: "web",
        source: "builtin",
        parameters: {
            type: "object",
            properties: {
                query: { type: "string", description: "Search query" },
                provider: { type: "string", description: "Provider: auto|openai|brave|perplexity|grok|gemini|kimi" },
                limit: { type: "number", description: "Maximum results to return" },
            },
            required: ["query"],
        },
        async execute(input, context) {
            if (context?.signal?.aborted)
                return { ok: false, message: "Cancelled" };
            const requestedProvider = input.provider ?? "auto";
            const getApiKey = (name) => normalizeApiKey(context.apiKeys?.[name]) ?? normalizeApiKey(process.env[name]);
            const provider = resolveWebSearchProvider(requestedProvider, getApiKey);
            const limit = Math.max(1, Math.min(input.limit ?? 5, 10));
            const openaiOptions = {
                apiKey: getApiKey("OPENAI_API_KEY"),
                baseUrl: normalizeApiKey(context.apiKeys?.["OPENAI_BASE_URL"]) ?? process.env["OPENAI_BASE_URL"],
                model: normalizeApiKey(context.apiKeys?.["OPENAI_WEB_SEARCH_MODEL"])
                    ?? normalizeApiKey(context.apiKeys?.["OPENAI_MODEL"])
                    ?? process.env["OPENAI_WEB_SEARCH_MODEL"]
                    ?? process.env["OPENAI_MODEL"]
                    ?? DEFAULT_OPENAI_WEB_SEARCH_MODEL,
            };
            if (provider === "brave") {
                const apiKey = getApiKey("BRAVE_API_KEY");
                if (!apiKey) {
                    return searchOpenAI(guard, input.query, limit, openaiOptions, "Missing BRAVE_API_KEY, falling back to openai web_search");
                }
                return searchBrave(guard, input.query, limit, apiKey);
            }
            if (provider === "perplexity") {
                const perplexityKey = getApiKey("PERPLEXITY_API_KEY");
                const openRouterKey = getApiKey("OPENROUTER_API_KEY");
                if (!perplexityKey && !openRouterKey) {
                    return searchOpenAI(guard, input.query, limit, openaiOptions, "Missing PERPLEXITY_API_KEY/OPENROUTER_API_KEY, falling back to openai web_search");
                }
                return searchPerplexity(guard, input.query, limit, { perplexityKey, openRouterKey });
            }
            if (provider === "grok") {
                const apiKey = getApiKey("XAI_API_KEY");
                if (!apiKey) {
                    return searchOpenAI(guard, input.query, limit, openaiOptions, "Missing XAI_API_KEY, falling back to openai web_search");
                }
                return searchGrok(guard, input.query, limit, apiKey);
            }
            if (provider === "gemini") {
                const apiKey = getApiKey("GEMINI_API_KEY");
                if (!apiKey) {
                    return searchOpenAI(guard, input.query, limit, openaiOptions, "Missing GEMINI_API_KEY, falling back to openai web_search");
                }
                return searchGemini(guard, input.query, limit, apiKey);
            }
            if (provider === "kimi") {
                const apiKey = getApiKey("MOONSHOT_API_KEY");
                if (!apiKey) {
                    return searchOpenAI(guard, input.query, limit, openaiOptions, "Missing MOONSHOT_API_KEY, falling back to openai web_search");
                }
                return searchKimi(guard, input.query, limit, apiKey);
            }
            if (provider === "openai") {
                return searchOpenAI(guard, input.query, limit, openaiOptions);
            }
            return searchOpenAI(guard, input.query, limit, openaiOptions, `Provider '${provider}' not available, using openai web_search`);
        },
    };
}
function normalizeApiKey(value) {
    const trimmed = value?.trim();
    return trimmed ? trimmed : undefined;
}
function resolveWebSearchProvider(provider, getApiKey) {
    if (provider !== "auto") {
        return provider;
    }
    if (getApiKey("BRAVE_API_KEY"))
        return "brave";
    if (getApiKey("PERPLEXITY_API_KEY") || getApiKey("OPENROUTER_API_KEY")) {
        return "perplexity";
    }
    if (getApiKey("XAI_API_KEY"))
        return "grok";
    if (getApiKey("GEMINI_API_KEY"))
        return "gemini";
    if (getApiKey("MOONSHOT_API_KEY"))
        return "kimi";
    return "openai";
}
async function searchOpenAI(guard, query, limit, options, prefixMessage) {
    const apiKey = normalizeApiKey(options.apiKey);
    if (!apiKey) {
        return {
            ok: false,
            message: `${prefixMessage ? `${prefixMessage}. ` : ""}Missing OPENAI_API_KEY`,
            data: { provider: "openai", query, results: [] },
        };
    }
    const baseUrl = options.baseUrl?.trim() || DEFAULT_OPENAI_BASE_URL;
    const endpoint = `${baseUrl.replace(/\/+$/, "")}/responses`;
    const model = options.model?.trim() || DEFAULT_OPENAI_WEB_SEARCH_MODEL;
    try {
        const url = guard.validate(endpoint);
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "content-type": "application/json",
                authorization: `Bearer ${apiKey}`,
            },
            body: JSON.stringify({
                model,
                tools: [{ type: "web_search" }],
                input: `Provide up to ${limit} web search results for: ${query}`,
            }),
        });
        const data = await response.json();
        if (!response.ok) {
            const detail = data.error?.message || `HTTP ${response.status}`;
            return {
                ok: false,
                message: `${prefixMessage ? `${prefixMessage}. ` : ""}OpenAI web_search failed (${response.status}): ${detail}`,
                data: { provider: "openai", query, results: [] },
            };
        }
        const { results, summary } = extractOpenAIWebSearchResults(data, query, limit);
        return {
            ok: true,
            message: prefixMessage
                ? `${prefixMessage}. Search results (openai)`
                : "Search results (openai)",
            data: {
                provider: "openai",
                model,
                query,
                results,
                summary,
            },
        };
    }
    catch (err) {
        return {
            ok: false,
            message: `${prefixMessage ? `${prefixMessage}. ` : ""}OpenAI web_search failed: ${extractErrorMessage(err)}`,
            data: { provider: "openai", query, results: [] },
        };
    }
}
async function searchBrave(guard, query, limit, apiKey) {
    try {
        const url = guard.validate(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${limit}`);
        const response = await fetch(url, {
            headers: {
                Accept: "application/json",
                "X-Subscription-Token": apiKey,
            },
        });
        const data = await response.json();
        const results = (data.web?.results ?? [])
            .slice(0, limit)
            .map((entry) => ({
            title: entry.title ?? "",
            url: entry.url ?? "",
            snippet: entry.description ?? "",
        }))
            .filter((entry) => entry.title || entry.url || entry.snippet);
        return {
            ok: response.ok,
            message: response.ok ? "Search results (brave)" : `Brave search failed (${response.status})`,
            data: { provider: "brave", query, results },
        };
    }
    catch (err) {
        return {
            ok: false,
            message: `Brave search failed: ${extractErrorMessage(err)}`,
            data: { provider: "brave", query, results: [] },
        };
    }
}
async function searchPerplexity(guard, query, limit, keys) {
    const useOpenRouter = !keys.perplexityKey && Boolean(keys.openRouterKey);
    const apiKey = useOpenRouter ? keys.openRouterKey : keys.perplexityKey;
    if (!apiKey) {
        return {
            ok: false,
            message: "Perplexity search failed: missing API key",
            data: { provider: "perplexity", query, results: [] },
        };
    }
    const model = useOpenRouter
        ? (process.env["PERPLEXITY_OPENROUTER_MODEL"]?.trim() || DEFAULT_PERPLEXITY_OPENROUTER_MODEL)
        : (process.env["PERPLEXITY_MODEL"]?.trim() || DEFAULT_PERPLEXITY_MODEL);
    const endpoint = useOpenRouter
        ? "https://openrouter.ai/api/v1/chat/completions"
        : "https://api.perplexity.ai/chat/completions";
    try {
        const url = guard.validate(endpoint);
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "content-type": "application/json",
                authorization: `Bearer ${apiKey}`,
            },
            body: JSON.stringify({
                model,
                messages: [{ role: "user", content: `Provide up to ${limit} web results for: ${query}` }],
            }),
        });
        const data = await response.json();
        const content = data.choices?.[0]?.message?.content ?? "";
        const citationResults = (data.citations ?? [])
            .slice(0, limit)
            .map((citationUrl) => ({
            title: safeHostname(citationUrl),
            url: citationUrl,
            snippet: content.slice(0, 500),
        }));
        const results = citationResults.length > 0
            ? citationResults
            : [{ title: query, url: "", snippet: content }];
        return {
            ok: response.ok,
            message: response.ok ? "Search results (perplexity)" : `Perplexity search failed (${response.status})`,
            data: { provider: "perplexity", query, results },
        };
    }
    catch (err) {
        return {
            ok: false,
            message: `Perplexity search failed: ${extractErrorMessage(err)}`,
            data: { provider: "perplexity", query, results: [] },
        };
    }
}
async function searchGrok(guard, query, limit, apiKey) {
    const model = process.env["GROK_MODEL"]?.trim() || DEFAULT_GROK_MODEL;
    try {
        const url = guard.validate("https://api.x.ai/v1/responses");
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "content-type": "application/json",
                authorization: `Bearer ${apiKey}`,
            },
            body: JSON.stringify({
                model,
                input: query,
                tools: [{ type: "web_search" }],
            }),
        });
        const data = await response.json();
        const contentFromOutput = (data.output ?? [])
            .flatMap((output) => output.content ?? [])
            .find((part) => part.type === "output_text" && typeof part.text === "string")?.text;
        const content = contentFromOutput ?? data.output_text ?? "";
        const citationResults = (data.citations ?? [])
            .slice(0, limit)
            .map((citationUrl) => ({
            title: safeHostname(citationUrl),
            url: citationUrl,
            snippet: content.slice(0, 500),
        }));
        const results = citationResults.length > 0
            ? citationResults
            : [{ title: query, url: "", snippet: content }];
        return {
            ok: response.ok,
            message: response.ok ? "Search results (grok)" : `Grok search failed (${response.status})`,
            data: { provider: "grok", query, results },
        };
    }
    catch (err) {
        return {
            ok: false,
            message: `Grok search failed: ${extractErrorMessage(err)}`,
            data: { provider: "grok", query, results: [] },
        };
    }
}
async function searchGemini(guard, query, limit, apiKey) {
    const model = process.env["GEMINI_MODEL"]?.trim() || DEFAULT_GEMINI_MODEL;
    try {
        const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
        const url = guard.validate(endpoint);
        const response = await fetch(url, {
            method: "POST",
            headers: { "content-type": "application/json" },
            body: JSON.stringify({
                contents: [{ parts: [{ text: query }] }],
                tools: [{ google_search: {} }],
            }),
        });
        const data = await response.json();
        const candidate = data.candidates?.[0];
        const content = (candidate?.content?.parts ?? [])
            .map((part) => part.text ?? "")
            .join("\n")
            .trim();
        const grounded = (candidate?.groundingMetadata?.groundingChunks ?? [])
            .map((chunk) => chunk.web)
            .filter((web) => Boolean(web && web.uri))
            .slice(0, limit)
            .map((web) => ({
            title: web.title ?? safeHostname(web.uri ?? ""),
            url: web.uri ?? "",
            snippet: content.slice(0, 500),
        }));
        const results = grounded.length > 0
            ? grounded
            : [{ title: query, url: "", snippet: content }];
        return {
            ok: response.ok,
            message: response.ok ? "Search results (gemini)" : `Gemini search failed (${response.status})`,
            data: { provider: "gemini", query, results },
        };
    }
    catch (err) {
        return {
            ok: false,
            message: `Gemini search failed: ${extractErrorMessage(err)}`,
            data: { provider: "gemini", query, results: [] },
        };
    }
}
async function searchKimi(guard, query, limit, apiKey) {
    const baseUrl = process.env["KIMI_BASE_URL"]?.trim() || DEFAULT_KIMI_BASE_URL;
    const model = process.env["KIMI_MODEL"]?.trim() || DEFAULT_KIMI_MODEL;
    const endpoint = `${baseUrl.replace(/\/+$/, "")}/chat/completions`;
    try {
        const url = guard.validate(endpoint);
        const response = await fetch(url, {
            method: "POST",
            headers: {
                "content-type": "application/json",
                authorization: `Bearer ${apiKey}`,
            },
            body: JSON.stringify({
                model,
                messages: [{ role: "user", content: query }],
                tools: [{ type: "builtin_function", function: { name: "$web_search" } }],
                tool_choice: "auto",
            }),
        });
        const data = await response.json();
        const results = (data.search_results ?? [])
            .slice(0, limit)
            .map((entry) => ({
            title: entry.title ?? safeHostname(entry.url ?? ""),
            url: entry.url ?? "",
            snippet: entry.content ?? "",
        }))
            .filter((entry) => entry.title || entry.url || entry.snippet);
        if (results.length === 0) {
            results.push({
                title: query,
                url: "",
                snippet: data.choices?.[0]?.message?.content ?? "",
            });
        }
        return {
            ok: response.ok,
            message: response.ok ? "Search results (kimi)" : `Kimi search failed (${response.status})`,
            data: { provider: "kimi", query, results },
        };
    }
    catch (err) {
        return {
            ok: false,
            message: `Kimi search failed: ${extractErrorMessage(err)}`,
            data: { provider: "kimi", query, results: [] },
        };
    }
}
function safeHostname(url) {
    try {
        return new URL(url).hostname;
    }
    catch {
        return url || "source";
    }
}
function extractOpenAIWebSearchResults(data, query, limit) {
    const contentBlocks = (data.output ?? []).flatMap((entry) => entry.content ?? []);
    const summary = normalizeText(data.output_text
        || contentBlocks
            .filter((block) => block.type === "output_text" && typeof block.text === "string")
            .map((block) => block.text ?? "")
            .join("\n"));
    const sourceMap = new Map();
    const push = (source) => {
        const url = source.url?.trim() ?? "";
        if (!url || sourceMap.has(url))
            return;
        sourceMap.set(url, {
            title: source.title?.trim() || safeHostname(url),
            url,
            snippet: summary.slice(0, 500),
        });
    };
    for (const entry of data.output ?? []) {
        for (const source of entry.action?.sources ?? [])
            push(source);
    }
    for (const block of contentBlocks) {
        for (const annotation of block.annotations ?? []) {
            if (annotation.type === "url_citation") {
                push(annotation);
            }
        }
    }
    const results = Array.from(sourceMap.values()).slice(0, limit);
    if (results.length === 0) {
        results.push({
            title: query,
            url: "",
            snippet: summary || "OpenAI web_search returned no citation URLs.",
        });
    }
    return { results, summary };
}
function normalizeText(value) {
    return value.replace(/\s+/g, " ").trim();
}
function extractErrorMessage(err) {
    if (err instanceof Error)
        return err.message;
    return String(err);
}
function isTlsFailure(err) {
    const msg = extractErrorMessage(err).toLowerCase();
    return (msg.includes("certificate") ||
        msg.includes("self signed") ||
        msg.includes("unable to get local issuer certificate") ||
        msg.includes("tls"));
}
async function fetchWithTlsFallback(url, init, allowInsecureTls) {
    try {
        const response = await fetch(url, init);
        return { response, insecureTlsUsed: false };
    }
    catch (err) {
        if (!allowInsecureTls || !isTlsFailure(err))
            throw err;
        const retryInit = {
            ...init,
            tls: { rejectUnauthorized: false },
        };
        const response = await fetch(url, retryInit);
        return { response, insecureTlsUsed: true };
    }
}
export function createBrowserSandboxStartTool(sandboxManager = new SandboxManager()) {
    return {
        name: "browser.sandbox.start",
        description: "Start Chromium sandbox container with noVNC access",
        tier: "standard",
        category: "browser",
        source: "builtin",
        parameters: {
            type: "object",
            properties: {
                novncPort: { type: "number", description: "Host noVNC port (default 6080)" },
                vncPort: { type: "number", description: "Host VNC port (default 5900)" },
                mountMode: { type: "string", description: "Project mount mode: none, read-only, read-write" },
            },
            required: [],
        },
        async execute(input, context) {
            const result = await sandboxManager.startBrowserSandbox({
                projectRoot: context.projectRoot,
                novncPort: input.novncPort,
                vncPort: input.vncPort,
                mountMode: input.mountMode ?? "read-only",
            });
            return {
                ok: true,
                message: "Sandbox browser started",
                data: result,
            };
        },
    };
}
//# sourceMappingURL=browser-tools.js.map