Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/web/parallel.ts

Raw
import type { SearchResult } from "./constants.js";
import { callMcpTool, McpError } from "./mcp.js";

/** Anonymous hosted MCP; preserve all excerpts, applying only the result-count limit. */
export async function parallelSearch(
	query: string,
	signal?: AbortSignal,
	count = 5,
	timeout = 30_000,
): Promise<SearchResult[]> {
	const deadline = AbortSignal.timeout(timeout);
	const requestSignal = signal ? AbortSignal.any([signal, deadline]) : deadline;
	const result = await callMcpTool({
		url: "https://search.parallel.ai/mcp",
		protocolVersion: "2024-11-05",
		signal: requestSignal,
		name: "web_search",
		arguments: { objective: query, search_queries: [query] },
	}).catch(async (error: unknown) => {
		if (!(error instanceof McpError)) throw error;
		if (error.response) {
			await error.response.body?.cancel();
			throw new Error(`Parallel ${error.message}`);
		}
		throw new Error(`Parallel MCP: ${error.message}`);
	});
	const text = result?.content
		?.filter((block) => block.type === "text")
		.map((block) => block.text ?? "")
		.join("\n");
	if (result?.isError)
		throw new Error(`Parallel search: ${text || "tool failed"}`);
	if (!text) throw new Error("Parallel search returned no content");
	const data = JSON.parse(text) as {
		results?: Array<{
			title?: string;
			url?: string;
			excerpts?: string[];
			publish_date?: string | null;
		}>;
	};
	if (!Array.isArray(data.results))
		throw new Error("Parallel search returned invalid results");
	return data.results
		.map((result) => {
			if (
				typeof result.url !== "string" ||
				!Array.isArray(result.excerpts) ||
				!result.excerpts.every((excerpt) => typeof excerpt === "string")
			) {
				throw new Error("Parallel search returned a malformed result");
			}
			return {
				title: result.title ?? result.url,
				url: result.url,
				snippet: result.excerpts.join("\n\n"),
				...(result.publish_date ? { date: result.publish_date } : {}),
			};
		})
		.slice(0, count);
}