import { b as readStringParam, h as readNumberParam, v as readStringArrayParam } from "./common-Df2rEPbV.js";
import { S as writeCachedSearchPayload, _ as resolveSearchTimeoutSeconds, f as readCachedSearchPayload, h as resolveSearchCacheTtlMs, x as withTrustedWebSearchEndpoint } from "./web-search-provider-common-CHG1U5Dw.js";
import { u as readPluginPackageVersion } from "./extension-shared-D4UymM0X.js";
import { i as resolveProviderWebSearchPluginConfig, r as mergeScopedSearchConfig } from "./web-search-provider-config-BQzMMhw8.js";
import "./provider-web-search-D4Y119HH.js";
import { a as normalizeParallelClientModel, c as normalizeParallelSearchQueries, d as stripParallelGeneratedSessionId, i as mapParallelResults, l as normalizeParallelSessionId, n as buildParallelCacheKey, o as normalizeParallelObjective, r as invalidSearchQueriesPayload, u as resolveParallelSearchCount } from "./parallel-search-normalize-BsXRWUaP.js";
import { createRequire } from "node:module";
import { randomUUID } from "node:crypto";
//#region extensions/parallel/src/parallel-mcp-search.runtime.ts
const PARALLEL_MCP_SEARCH_URL = "https://search.parallel.ai/mcp";
const MCP_PROTOCOL_VERSION = "2025-06-18";
const MCP_TIMEOUT_SECONDS = 30;
const PLUGIN_VERSION = readPluginPackageVersion({ require: createRequire(import.meta.url) });
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
function mcpHeaders(params) {
	const headers = {
		"Content-Type": "application/json",
		Accept: "application/json, text/event-stream"
	};
	if (params.sessionId) headers["Mcp-Session-Id"] = params.sessionId;
	if (params.protocolVersion) headers["MCP-Protocol-Version"] = params.protocolVersion;
	return headers;
}
/**
* Yield JSON-RPC message objects from a plain-JSON or SSE response body.
*
* Handles `application/json` (a single object) and `text/event-stream` (SSE:
* events separated by blank lines; an event's one-or-more `data:` lines
* concatenate into a single JSON payload). Streamable HTTP also allows batching
* responses into a JSON array, so arrays are flattened. Unparseable chunks and
* non-`data` SSE fields (`event:`/`id:`/comments) are skipped.
*/
function iterMcpMessages(text) {
	const out = [];
	const emit = (payload) => {
		if (Array.isArray(payload)) {
			for (const entry of payload) if (isRecord(entry)) out.push(entry);
		} else if (isRecord(payload)) out.push(payload);
	};
	const body = (text ?? "").trim();
	if (!body) return out;
	if (body.startsWith("{") || body.startsWith("[")) {
		try {
			emit(JSON.parse(body));
		} catch {}
		return out;
	}
	let dataLines = [];
	const flush = () => {
		if (dataLines.length === 0) return;
		try {
			emit(JSON.parse(dataLines.join("\n")));
		} catch {}
		dataLines = [];
	};
	for (const raw of body.split("\n")) {
		const line = raw.replace(/\r$/, "");
		if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
		else if (line.trim() === "") flush();
	}
	flush();
	return out;
}
/**
* Select the JSON-RPC response for `requestId` from an MCP response body.
*
* Streamable-HTTP servers may emit progress/log notifications before the final
* result, so scan the whole stream and return the result/error message whose
* `id` matches. Falls back to the last result/error-bearing message if no id
* matches; `{}` if none is present.
*/
function selectMcpEnvelope(text, requestId) {
	let fallback = {};
	for (const msg of iterMcpMessages(text)) {
		if (!("result" in msg || "error" in msg)) continue;
		if (msg.id === requestId) return msg;
		fallback = msg;
	}
	return fallback;
}
/**
* Extract the tool result payload from a `tools/call` envelope.
*
* Prefers `structuredContent` (authoritative machine-readable form); otherwise
* scans text blocks for the first JSON-parseable one. Throws on a JSON-RPC
* error or a tool-level `isError`.
*/
function extractMcpToolPayload(envelope) {
	if ("error" in envelope) throw new Error(`Parallel MCP error: ${JSON.stringify(envelope.error).slice(0, 500)}`);
	const result = isRecord(envelope.result) ? envelope.result : {};
	if (result.isError) throw new Error(`Parallel MCP tool error: ${JSON.stringify(result).slice(0, 500)}`);
	if (isRecord(result.structuredContent)) return result.structuredContent;
	const content = Array.isArray(result.content) ? result.content : [];
	for (const block of content) if (isRecord(block) && block.type === "text" && typeof block.text === "string" && block.text) try {
		const parsed = JSON.parse(block.text);
		if (isRecord(parsed)) return parsed;
	} catch {}
	throw new Error(`Parallel MCP returned no parseable content: ${JSON.stringify(result).slice(0, 500)}`);
}
async function postMcp(params) {
	return withTrustedWebSearchEndpoint({
		url: PARALLEL_MCP_SEARCH_URL,
		timeoutSeconds: params.timeoutSeconds,
		signal: params.signal,
		init: {
			method: "POST",
			headers: mcpHeaders({
				sessionId: params.sessionId,
				protocolVersion: params.protocolVersion
			}),
			body: JSON.stringify(params.body)
		}
	}, async (response) => ({
		ok: response.ok,
		status: response.status,
		statusText: response.statusText,
		text: await response.text(),
		sessionIdHeader: response.headers.get("mcp-session-id")
	}));
}
/**
* Run the MCP handshake then a single `tools/call`, returning the tool payload.
*
* initialize -> (capture `Mcp-Session-Id` header + negotiated protocolVersion)
* -> notifications/initialized -> tools/call. Anonymous (no bearer token).
*/
async function mcpCall(toolName, args, timeoutSeconds, signal) {
	const initId = randomUUID();
	const init = await postMcp({
		timeoutSeconds,
		signal,
		body: {
			jsonrpc: "2.0",
			id: initId,
			method: "initialize",
			params: {
				protocolVersion: MCP_PROTOCOL_VERSION,
				capabilities: {},
				clientInfo: {
					name: "openclaw-parallel",
					version: PLUGIN_VERSION
				}
			}
		}
	});
	if (!init.ok) throw new Error(`Parallel MCP initialize failed (${init.status}): ${init.text || init.statusText}`);
	const sessionId = init.sessionIdHeader ?? void 0;
	const initEnvelope = selectMcpEnvelope(init.text, initId);
	const negotiatedVersion = (isRecord(initEnvelope.result) && typeof initEnvelope.result.protocolVersion === "string" ? initEnvelope.result.protocolVersion : void 0) ?? MCP_PROTOCOL_VERSION;
	await postMcp({
		body: {
			jsonrpc: "2.0",
			method: "notifications/initialized"
		},
		sessionId,
		protocolVersion: negotiatedVersion,
		timeoutSeconds,
		signal
	});
	const callId = randomUUID();
	const call = await postMcp({
		body: {
			jsonrpc: "2.0",
			id: callId,
			method: "tools/call",
			params: {
				name: toolName,
				arguments: args
			}
		},
		sessionId,
		protocolVersion: negotiatedVersion,
		timeoutSeconds,
		signal
	});
	if (!call.ok) throw new Error(`Parallel MCP tools/call failed (${call.status}): ${call.text || call.statusText}`);
	return extractMcpToolPayload(selectMcpEnvelope(call.text, callId));
}
function normalizeMcpSessionId(value) {
	return value?.trim() || randomUUID();
}
/**
* Run a `web_search` tool call against the free hosted Search MCP and return a
* `ParallelSearchResponse`-compatible object so the runtime's existing result
* normalization (`normalizeParallelResults`) is reused verbatim.
*/
async function runParallelMcpSearch(params) {
	const sessionId = normalizeMcpSessionId(params.sessionId);
	const args = {
		objective: params.objective ?? params.searchQueries.join(" "),
		search_queries: [...params.searchQueries],
		session_id: sessionId
	};
	if (params.modelName) args.model_name = params.modelName;
	const payload = await mcpCall("web_search", args, params.timeoutSeconds ?? MCP_TIMEOUT_SECONDS, params.signal);
	const results = (Array.isArray(payload.results) ? payload.results : []).slice(0, Math.max(params.maxResults, 1));
	return {
		search_id: typeof payload.search_id === "string" ? payload.search_id : void 0,
		session_id: sessionId,
		results,
		warnings: payload.warnings,
		usage: payload.usage
	};
}
//#endregion
//#region extensions/parallel/src/parallel-free-web-search-provider.runtime.ts
async function executeParallelFreeWebSearchProviderTool(ctx, args, signal) {
	const searchConfig = mergeScopedSearchConfig(ctx.searchConfig, "parallel-free", resolveProviderWebSearchPluginConfig(ctx.config, "parallel-free"));
	const objective = normalizeParallelObjective(readStringParam(args, "objective"));
	const cliQuery = normalizeParallelObjective(readStringParam(args, "query"));
	let searchQueries = normalizeParallelSearchQueries(readStringArrayParam(args, "search_queries"));
	if (searchQueries.length === 0 && cliQuery) searchQueries = normalizeParallelSearchQueries([cliQuery]);
	if (searchQueries.length === 0) return invalidSearchQueriesPayload();
	const count = resolveParallelSearchCount(readNumberParam(args, "count", { integer: true }) ?? (typeof searchConfig?.maxResults === "number" ? searchConfig.maxResults : void 0) ?? 5);
	const sessionId = normalizeParallelSessionId(readStringParam(args, "session_id"), 100);
	const clientModel = normalizeParallelClientModel(readStringParam(args, "client_model"));
	const cacheKey = buildParallelCacheKey({
		endpoint: PARALLEL_MCP_SEARCH_URL,
		objective,
		searchQueries,
		count,
		sessionId,
		clientModel
	});
	const cached = readCachedSearchPayload(cacheKey);
	if (cached) return cached;
	const start = Date.now();
	const response = await runParallelMcpSearch({
		objective,
		searchQueries,
		maxResults: count,
		sessionId,
		modelName: clientModel,
		timeoutSeconds: resolveSearchTimeoutSeconds(searchConfig),
		signal
	});
	const results = mapParallelResults(response);
	const payload = {
		...objective ? { objective } : {},
		searchQueries,
		provider: "parallel-free",
		count: results.length,
		tookMs: Date.now() - start,
		externalContent: {
			untrusted: true,
			source: "web_search",
			provider: "parallel-free",
			wrapped: true
		},
		results
	};
	if (typeof response.search_id === "string") payload.searchId = response.search_id;
	if (typeof response.session_id === "string") payload.sessionId = response.session_id;
	if (Array.isArray(response.warnings) && response.warnings.length > 0) payload.warnings = response.warnings;
	if (Array.isArray(response.usage) && response.usage.length > 0) payload.usage = response.usage;
	writeCachedSearchPayload(cacheKey, sessionId ? payload : stripParallelGeneratedSessionId(payload), resolveSearchCacheTtlMs(searchConfig));
	return payload;
}
//#endregion
export { executeParallelFreeWebSearchProviderTool };
