export function dedupeUrls(urls: string[]): string[] { return [...new Set(urls)]; } import type { Freshness } from "./exa-search.js"; const VALID_SEARCH_TYPES = new Set(["auto", "instant", "deep"]); const VALID_CATEGORIES = new Set([ "company", "research paper", "news", "tweet", "people", "personal site", "financial report", "pdf", ]); const VALID_DETAIL_VALUES = new Set(["summary", "highlights"]); const VALID_FRESHNESS_VALUES = new Set(["realtime", "day", "week", "any"]); export type NormalizedWebSearchInput = { queries: string[]; numResults: number; type?: "auto" | "instant" | "deep"; category?: string; includeDomains?: string[]; excludeDomains?: string[]; detail?: "summary" | "highlights"; freshness?: Freshness; similarUrl?: string; }; export type NormalizedFetchContentInput = { urls: string[]; forceClone?: boolean; prompt?: string; noCache?: boolean; }; export type NormalizedCodeSearchInput = { query: string; tokensNum?: number; }; export type NormalizedGetSearchContentInput = { responseId: string; query?: string; queryIndex?: number; url?: string; urlIndex?: number; maxChars?: number; }; export function normalizeWebSearchInput(params: { query?: unknown; queries?: unknown; numResults?: unknown; type?: unknown; category?: unknown; includeDomains?: unknown; excludeDomains?: unknown; detail?: unknown; freshness?: unknown; similarUrl?: unknown; }): NormalizedWebSearchInput { const query = typeof params.query === "string" ? params.query : undefined; const queries = Array.isArray(params.queries) ? params.queries.filter((q): q is string => typeof q === "string") : undefined; const similarUrl = typeof params.similarUrl === "string" && params.similarUrl ? params.similarUrl : undefined; const queryList = (queries && queries.length > 0) ? queries : (query ? [query] : []); if (queryList.length > 0 && similarUrl) { throw new Error("'similarUrl' and 'query'/'queries' are mutually exclusive."); } if (queryList.length === 0 && !similarUrl) { throw new Error("Either 'query' or 'queries' must be provided."); } let numResults: number; if (typeof params.numResults === "number" && Number.isFinite(params.numResults)) { numResults = Math.max(1, Math.min(20, Math.round(params.numResults))); } else { numResults = 5; } const type = typeof params.type === "string" && VALID_SEARCH_TYPES.has(params.type) ? params.type as "auto" | "instant" | "deep" : undefined; const category = typeof params.category === "string" && VALID_CATEGORIES.has(params.category) ? params.category : undefined; const includeDomains = Array.isArray(params.includeDomains) ? params.includeDomains.filter((d): d is string => typeof d === "string") : undefined; const excludeDomains = Array.isArray(params.excludeDomains) ? params.excludeDomains.filter((d): d is string => typeof d === "string") : undefined; const detail = typeof params.detail === "string" && VALID_DETAIL_VALUES.has(params.detail) ? params.detail as "summary" | "highlights" : undefined; const freshness = typeof params.freshness === "string" && VALID_FRESHNESS_VALUES.has(params.freshness) ? params.freshness as Freshness : undefined; return { queries: queryList, numResults, type, category, includeDomains, excludeDomains, detail, freshness, similarUrl }; } export function normalizeFetchContentInput(params: { url?: unknown; urls?: unknown; forceClone?: unknown; prompt?: unknown; noCache?: unknown }): NormalizedFetchContentInput { const url = typeof params.url === "string" ? params.url : undefined; const urls = Array.isArray(params.urls) ? params.urls.filter((u): u is string => typeof u === "string") : undefined; const urlList = (urls && urls.length > 0) ? urls : (url ? [url] : []); if (urlList.length === 0) { throw new Error("Either 'url' or 'urls' must be provided."); } const forceClone = typeof params.forceClone === "boolean" ? params.forceClone : undefined; const prompt = typeof params.prompt === "string" ? params.prompt : undefined; const noCache = typeof params.noCache === "boolean" ? params.noCache : undefined; return { urls: dedupeUrls(urlList), forceClone, prompt, noCache }; } export function normalizeCodeSearchInput(params: { query?: unknown; tokensNum?: unknown; }): NormalizedCodeSearchInput { const query = typeof params.query === "string" ? params.query : undefined; if (!query) { throw new Error("'query' must be provided."); } let tokensNum: number | undefined; if (typeof params.tokensNum === "number" && Number.isFinite(params.tokensNum)) { tokensNum = Math.max(50, Math.min(100000, Math.round(params.tokensNum))); } return { query, tokensNum }; } export function normalizeGetSearchContentInput(params: { responseId?: unknown; query?: unknown; queryIndex?: unknown; url?: unknown; urlIndex?: unknown; maxChars?: unknown; }): NormalizedGetSearchContentInput { const responseId = typeof params.responseId === "string" ? params.responseId : undefined; if (!responseId) { throw new Error("'responseId' must be provided."); } const query = typeof params.query === "string" ? params.query : undefined; const queryIndex = typeof params.queryIndex === "number" && Number.isFinite(params.queryIndex) ? params.queryIndex : undefined; const url = typeof params.url === "string" ? params.url : undefined; const urlIndex = typeof params.urlIndex === "number" && Number.isFinite(params.urlIndex) ? params.urlIndex : undefined; let maxChars: number | undefined; if (typeof params.maxChars === "number" && Number.isFinite(params.maxChars)) { maxChars = Math.max(1, Math.round(params.maxChars)); } return { responseId, query, queryIndex, url, urlIndex, maxChars }; }