webcrawl 1.2.4 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/cli.js +3 -2
  2. package/dist/core/engine/cache.d.ts +70 -0
  3. package/dist/core/engine/cache.js +297 -0
  4. package/dist/core/engine/crawl-policy.d.ts +36 -0
  5. package/dist/core/engine/crawl-policy.js +134 -0
  6. package/dist/core/engine/crawler.d.ts +151 -0
  7. package/dist/core/engine/crawler.js +885 -0
  8. package/dist/core/engine/fetcher.d.ts +66 -0
  9. package/dist/core/engine/fetcher.js +185 -0
  10. package/dist/core/engine/index.d.ts +4 -0
  11. package/dist/core/engine/index.js +4 -0
  12. package/dist/core/engine/markdown.d.ts +33 -0
  13. package/dist/core/engine/markdown.js +334 -0
  14. package/dist/core/engine/path-scope.d.ts +21 -0
  15. package/dist/core/engine/path-scope.js +88 -0
  16. package/dist/core/engine/safe-url.d.ts +23 -0
  17. package/dist/core/engine/safe-url.js +92 -0
  18. package/dist/core/engine/stealth.d.ts +79 -0
  19. package/dist/core/engine/stealth.js +155 -0
  20. package/dist/core/engine.d.ts +1 -0
  21. package/dist/core/engine.js +1 -0
  22. package/dist/core/extract/business.d.ts +28 -0
  23. package/dist/core/extract/business.js +344 -0
  24. package/dist/core/extract/currency.d.ts +15 -0
  25. package/dist/core/extract/currency.js +259 -0
  26. package/dist/core/extract/cve-cache.d.ts +62 -0
  27. package/dist/core/extract/cve-cache.js +277 -0
  28. package/dist/core/extract/cve.d.ts +57 -0
  29. package/dist/core/extract/cve.js +397 -0
  30. package/dist/core/extract/dedupe-key.d.ts +12 -0
  31. package/dist/core/extract/dedupe-key.js +36 -0
  32. package/dist/core/extract/flights.d.ts +37 -0
  33. package/dist/core/extract/flights.js +373 -0
  34. package/dist/core/extract/hotels.d.ts +35 -0
  35. package/dist/core/extract/hotels.js +473 -0
  36. package/dist/core/extract/index.d.ts +8 -0
  37. package/dist/core/extract/index.js +8 -0
  38. package/dist/core/extract/real-estate.d.ts +53 -0
  39. package/dist/core/extract/real-estate.js +835 -0
  40. package/dist/core/extract/scaledown.d.ts +14 -0
  41. package/dist/core/extract/scaledown.js +484 -0
  42. package/dist/core/extract/shopping.d.ts +44 -0
  43. package/dist/core/extract/shopping.js +393 -0
  44. package/dist/core/image-url.d.ts +5 -0
  45. package/dist/core/image-url.js +13 -0
  46. package/dist/core/interact/driver.d.ts +49 -0
  47. package/dist/core/interact/driver.js +264 -0
  48. package/dist/core/interact/remote.d.ts +41 -0
  49. package/dist/core/interact/remote.js +133 -0
  50. package/dist/core/interact/spans.d.ts +46 -0
  51. package/dist/core/interact/spans.js +32 -0
  52. package/dist/core/interact/validate.d.ts +34 -0
  53. package/dist/core/interact/validate.js +135 -0
  54. package/dist/core/interact/vm.d.ts +42 -0
  55. package/dist/core/interact/vm.js +296 -0
  56. package/dist/core/local-engine.d.ts +35 -0
  57. package/dist/core/local-engine.js +367 -0
  58. package/dist/core/parse/detect.d.ts +29 -0
  59. package/dist/core/parse/detect.js +152 -0
  60. package/dist/core/parse/fetch.d.ts +20 -0
  61. package/dist/core/parse/fetch.js +39 -0
  62. package/dist/core/parse/image.d.ts +13 -0
  63. package/dist/core/parse/image.js +100 -0
  64. package/dist/core/parse/index.d.ts +53 -0
  65. package/dist/core/parse/index.js +153 -0
  66. package/dist/core/parse/limits.d.ts +12 -0
  67. package/dist/core/parse/limits.js +29 -0
  68. package/dist/core/parse/media.d.ts +42 -0
  69. package/dist/core/parse/media.js +238 -0
  70. package/dist/core/parse/merge.d.ts +19 -0
  71. package/dist/core/parse/merge.js +48 -0
  72. package/dist/core/parse/ocr.d.ts +25 -0
  73. package/dist/core/parse/ocr.js +121 -0
  74. package/dist/core/parse/office.d.ts +28 -0
  75. package/dist/core/parse/office.js +83 -0
  76. package/dist/core/parse/pdf.d.ts +27 -0
  77. package/dist/core/parse/pdf.js +156 -0
  78. package/dist/core/parse/types.d.ts +38 -0
  79. package/dist/core/parse/types.js +1 -0
  80. package/dist/core/parse/youtube.d.ts +30 -0
  81. package/dist/core/parse/youtube.js +264 -0
  82. package/dist/core/ranker/decision.d.ts +28 -0
  83. package/dist/core/ranker/decision.js +151 -0
  84. package/dist/core/ranker/index.d.ts +5 -0
  85. package/dist/core/ranker/index.js +361 -0
  86. package/dist/core/ranker/lexical.d.ts +82 -0
  87. package/dist/core/ranker/lexical.js +642 -0
  88. package/dist/core/ranker/types.d.ts +90 -0
  89. package/dist/core/ranker/types.js +1 -0
  90. package/dist/core/ranker.d.ts +1 -0
  91. package/dist/core/ranker.js +1 -0
  92. package/dist/core/sandbox.d.ts +29 -0
  93. package/dist/core/sandbox.js +182 -0
  94. package/dist/core/search/catalog.d.ts +174 -0
  95. package/dist/core/search/catalog.js +488 -0
  96. package/dist/core/search/index.d.ts +3 -0
  97. package/dist/core/search/index.js +3 -0
  98. package/dist/core/search/pipeline.d.ts +7 -0
  99. package/dist/core/search/pipeline.js +704 -0
  100. package/dist/core/search/providers.d.ts +62 -0
  101. package/dist/core/search/providers.js +1175 -0
  102. package/dist/core/search.d.ts +1 -0
  103. package/dist/core/search.js +1 -0
  104. package/dist/core/targets/developer.d.ts +44 -0
  105. package/dist/core/targets/developer.js +338 -0
  106. package/dist/core/targets/index.d.ts +3 -0
  107. package/dist/core/targets/index.js +3 -0
  108. package/dist/core/targets/registry.d.ts +32 -0
  109. package/dist/core/targets/registry.js +411 -0
  110. package/dist/core/targets/types.d.ts +72 -0
  111. package/dist/core/targets/types.js +8 -0
  112. package/dist/core/targets.d.ts +1 -0
  113. package/dist/core/targets.js +1 -0
  114. package/package.json +82 -4
@@ -0,0 +1,488 @@
1
+ import { extractDomainHost, } from "../extract/index.js";
2
+ export const SEARCH_PROVIDER_CHOICES = ["auto", "ddg", "jina", "serper", "brave"];
3
+ /** Providers that bill an extra credit when they serve the search. */
4
+ export const PAID_SEARCH_PROVIDERS = new Set(["jina", "serper", "brave"]);
5
+ export const SEARCH_PROVIDER_ENV_KEYS = {
6
+ jina: "JINA_API_KEY",
7
+ serper: "SERPER_API_KEY",
8
+ brave: "BRAVE_SEARCH_API_KEY",
9
+ };
10
+ /** Declarative category catalog. Categories/subcategories passed by agents are free-form; this is only the default engine plan. */
11
+ export const SEARCH_CATEGORY_CATALOG = {
12
+ general: {
13
+ description: "Broad web search across general-purpose engines.",
14
+ engines: ["brave-native", "ddg-lite", "wikipedia"],
15
+ localizeBy: "none",
16
+ preferLocalDomains: false,
17
+ subcategories: {
18
+ reference: { description: "Encyclopedic / reference answers" },
19
+ howto: { description: "Guides and tutorials" },
20
+ },
21
+ },
22
+ news: {
23
+ description: "Recent news and current events, localized to the user's news edition (country).",
24
+ engines: ["google-news-rss", "brave-native", "ddg-lite"],
25
+ // Never append the city: "paris" + "Sydney" turns a news query into flights.
26
+ // Google News localizes through its edition (gl/hl/ceid) instead.
27
+ localizeBy: "none",
28
+ preferLocalDomains: true,
29
+ subcategories: {
30
+ local: { description: "Local news" },
31
+ world: { description: "International news" },
32
+ business: { description: "Business and finance news" },
33
+ sports: { description: "Sports news" },
34
+ },
35
+ },
36
+ shopping: {
37
+ description: "Product-oriented shopping search with live multi-retailer prices, normalized offers, and bestOffer comparison, localized to the user's country.",
38
+ engines: ["bing-shopping", "google-shopping", "brave-native", "ddg-lite"],
39
+ localizeBy: "country",
40
+ preferLocalDomains: true,
41
+ subcategories: {
42
+ deals: { description: "Discounts and promotions" },
43
+ "price-comparison": { description: "Compare prices across retailers" },
44
+ reviews: { description: "Product reviews" },
45
+ },
46
+ },
47
+ "real-estate": {
48
+ description: "Real-estate property search with structured listings (address, price, bedrooms, bathrooms, parking, property type, agency, and portal URL), localized to the user's city/country.",
49
+ engines: ["brave-native", "ddg-lite"],
50
+ localizeBy: "city",
51
+ preferLocalDomains: true,
52
+ subcategories: {
53
+ buy: { description: "Properties for sale" },
54
+ rent: { description: "Properties for rent / lease" },
55
+ sold: { description: "Recently sold properties and sale prices" },
56
+ commercial: { description: "Commercial real estate" },
57
+ land: { description: "Vacant land and house-and-land packages" },
58
+ },
59
+ },
60
+ flights: {
61
+ description: "Flight search with structured fare offers (airline, origin, destination, duration, stops, cabin class, and fare price), localized to the user's country.",
62
+ engines: ["brave-native", "ddg-lite"],
63
+ localizeBy: "country",
64
+ preferLocalDomains: true,
65
+ subcategories: {
66
+ "one-way": { description: "One-way flight fares" },
67
+ "round-trip": { description: "Round-trip / return flight fares" },
68
+ nonstop: { description: "Direct / nonstop flights" },
69
+ "business-class": {
70
+ description: "Business and first class cabin fares",
71
+ },
72
+ "cheap-flights": {
73
+ description: "Low-cost carriers and budget flight deals",
74
+ },
75
+ status: { description: "Live flight status, schedules, and routes" },
76
+ },
77
+ },
78
+ hotels: {
79
+ description: "Hotel and accommodation search with structured nightly rates, star & guest ratings, review counts, address, and amenities, localized to the user's city/country.",
80
+ engines: ["brave-native", "ddg-lite"],
81
+ localizeBy: "city",
82
+ preferLocalDomains: true,
83
+ subcategories: {
84
+ luxury: { description: "5-star luxury hotels and resorts" },
85
+ budget: { description: "Budget hotels, motels, and hostels" },
86
+ resorts: { description: "Beach, ski, and spa resorts" },
87
+ apartments: {
88
+ description: "Serviced apartments, suites, and vacation rentals",
89
+ },
90
+ "pet-friendly": { description: "Pet-friendly hotels and stays" },
91
+ "last-minute": { description: "Last-minute hotel deals" },
92
+ },
93
+ },
94
+ business: {
95
+ description: "Local business, service provider, restaurant, and company directory search with structured contact details, address, ratings, opening hours, and website.",
96
+ engines: ["brave-native", "ddg-lite"],
97
+ localizeBy: "city",
98
+ preferLocalDomains: true,
99
+ subcategories: {
100
+ local: { description: "Local shops, trades, and neighborhood services" },
101
+ restaurants: {
102
+ description: "Restaurants, cafes, bars, and dining",
103
+ },
104
+ services: {
105
+ description: "Home and professional services (plumbers, electricians, cleaners)",
106
+ },
107
+ b2b: { description: "B2B vendors, SaaS companies, and agencies" },
108
+ "company-profile": {
109
+ description: "Company headquarters, leadership, and corporate info",
110
+ },
111
+ healthcare: {
112
+ description: "Doctors, dentists, clinics, and medical practices",
113
+ },
114
+ legal: { description: "Law firms, solicitors, and legal services" },
115
+ },
116
+ },
117
+ academic: {
118
+ description: "Research papers, ArXiv preprints, and scholarly literature.",
119
+ engines: ["arxiv"],
120
+ localizeBy: "none",
121
+ preferLocalDomains: false,
122
+ subcategories: {
123
+ papers: { description: "Preprints and peer-reviewed research papers" },
124
+ arxiv: { description: "ArXiv preprints in CS, AI/ML, physics, and mathematics" },
125
+ ml: { description: "Machine learning, AI, and NLP conference papers (NeurIPS, ICML, ICLR, ACL)" },
126
+ pubmed: { description: "Biomedical and life-sciences literature (PubMed / PMC)" },
127
+ math: { description: "Mathematics, statistics, and theoretical physics papers" },
128
+ },
129
+ },
130
+ tech: {
131
+ description: "Software, developer and technology content.",
132
+ engines: ["brave-native", "ddg-lite", "wikipedia", "hn-algolia"],
133
+ localizeBy: "none",
134
+ preferLocalDomains: false,
135
+ subcategories: {
136
+ code: { description: "Code and documentation" },
137
+ discussion: { description: "Developer discussions" },
138
+ },
139
+ },
140
+ security: {
141
+ description: "Vulnerabilities and security advisories. Returns structured CVE records (CVSS, affected and fixed versions, KEV) from NVD and OSV.",
142
+ engines: ["nvd", "osv", "brave-native", "ddg-lite"],
143
+ localizeBy: "none",
144
+ preferLocalDomains: false,
145
+ subcategories: {
146
+ cve: { description: "CVE records by id, product or package (e.g. CVE-2024-3094, openssl, lodash npm)" },
147
+ advisories: { description: "Vendor and GitHub security advisories" },
148
+ exploits: { description: "Exploitation reports and proof-of-concept write-ups" },
149
+ },
150
+ },
151
+ };
152
+ export function resolveCategory(req) {
153
+ const raw = (req.category || req.categories?.[0] || "").trim().toLowerCase();
154
+ if (raw === "realestate" ||
155
+ raw === "real_estate" ||
156
+ raw === "property" ||
157
+ raw === "properties" ||
158
+ raw === "housing" ||
159
+ raw === "homes" ||
160
+ raw === "rentals") {
161
+ return "real-estate";
162
+ }
163
+ if (raw === "flight" ||
164
+ raw === "flights" ||
165
+ raw === "airfare" ||
166
+ raw === "airlines" ||
167
+ raw === "airline") {
168
+ return "flights";
169
+ }
170
+ if (raw === "hotel" ||
171
+ raw === "hotels" ||
172
+ raw === "accommodation" ||
173
+ raw === "lodging" ||
174
+ raw === "stays" ||
175
+ raw === "resorts") {
176
+ return "hotels";
177
+ }
178
+ if (raw === "business" ||
179
+ raw === "businesses" ||
180
+ raw === "local-business" ||
181
+ raw === "local_business" ||
182
+ raw === "company" ||
183
+ raw === "companies" ||
184
+ raw === "directory" ||
185
+ raw === "restaurants") {
186
+ return "business";
187
+ }
188
+ if (raw === "academic" ||
189
+ raw === "paper" ||
190
+ raw === "papers" ||
191
+ raw === "research" ||
192
+ raw === "arxiv" ||
193
+ raw === "scholar" ||
194
+ raw === "scholarly" ||
195
+ raw === "science") {
196
+ return "academic";
197
+ }
198
+ if (raw === "cve" ||
199
+ raw === "cves" ||
200
+ raw === "vulnerability" ||
201
+ raw === "vulnerabilities" ||
202
+ raw === "vuln" ||
203
+ raw === "vulns" ||
204
+ raw === "infosec" ||
205
+ raw === "advisories") {
206
+ return "security";
207
+ }
208
+ if (raw && raw !== "general") {
209
+ return raw;
210
+ }
211
+ // Auto-detect vertical intent when category is omitted or "general"
212
+ const q = (req.query || "").toLowerCase();
213
+ if (/\b(cve-\d{4}-\d{4,7}|ghsa(?:-[a-z0-9]{4}){3})\b/.test(q)) {
214
+ return "security";
215
+ }
216
+ if (/\b(houses?|homes?|apartments?|flats?|townhouses?|units?|villas?|condos?|duplex(?:es)?|land|property|properties|real\s+estate)\b/.test(q) &&
217
+ /\b(for\s+sale|for\s+rent|to\s+rent|for\s+lease|sold|recently\s+sold|buy|rent|rental|auction|bedroom|bed)\b/.test(q)) {
218
+ return "real-estate";
219
+ }
220
+ if (/\b(flights?\s+(?:from|to|between)|cheap\s+flights?|direct\s+flights?|nonstop\s+flights?|airfare|round[\s-]trip\s+flights?|one[\s-]way\s+flights?|fly\s+from)\b/.test(q)) {
221
+ return "flights";
222
+ }
223
+ if (/\b(hotels?\s+in|best\s+hotels?|cheap\s+hotels?|luxury\s+hotels?|resorts?\s+in|where\s+to\s+stay\s+in|boutique\s+hotels?|5[\s-]star\s+hotels?|accommodation\s+in)\b/.test(q)) {
224
+ return "hotels";
225
+ }
226
+ if (/\b(best\s+(?:restaurants?|cafes?|coffee\s+shops?|plumbers?|electricians?|dentists?|lawyers?|mechanics?|gyms?|salons?|barbers?|accountants?|clinics?)\s+(?:in|near)|near\s+me|opening\s+hours|phone\s+number\s+for)\b/.test(q)) {
227
+ return "business";
228
+ }
229
+ return raw || "general";
230
+ }
231
+ export function planEngines(category, filters) {
232
+ const base = (SEARCH_CATEGORY_CATALOG[category] ?? SEARCH_CATEGORY_CATALOG.general).engines;
233
+ let plan = filters?.engines?.length ? [...filters.engines] : [...base];
234
+ if (filters?.excludeEngines?.length) {
235
+ const excluded = filters.excludeEngines;
236
+ plan = plan.filter((e) => !excluded.includes(e));
237
+ }
238
+ return plan;
239
+ }
240
+ /** DuckDuckGo region param (`kl`) derived from ISO-3166 country + language. */
241
+ export function ddgRegion(country, lang) {
242
+ if (!country || country.length !== 2) {
243
+ return undefined;
244
+ }
245
+ const cc = country.toLowerCase();
246
+ const DDG_REGION_MAP = {
247
+ eu: "wt-wt",
248
+ gb: "uk-en",
249
+ uk: "uk-en",
250
+ au: "au-en",
251
+ us: "us-en",
252
+ ca: "ca-en",
253
+ nz: "nz-en",
254
+ ie: "ie-en",
255
+ sg: "sg-en",
256
+ in: "in-en",
257
+ vn: "vn-en",
258
+ my: "my-en",
259
+ ph: "ph-en",
260
+ hk: "hk-tzh",
261
+ de: "de-de",
262
+ fr: "fr-fr",
263
+ es: "es-es",
264
+ it: "it-it",
265
+ nl: "nl-nl",
266
+ pl: "pl-pl",
267
+ se: "se-sv",
268
+ no: "no-no",
269
+ dk: "dk-da",
270
+ fi: "fi-fi",
271
+ pt: "pt-pt",
272
+ ch: "ch-de",
273
+ at: "at-de",
274
+ be: "be-fr",
275
+ jp: "jp-jp",
276
+ kr: "kr-kr",
277
+ th: "th-en",
278
+ id: "id-en",
279
+ };
280
+ if (lang) {
281
+ const cleanLang = lang.toLowerCase().slice(0, 2);
282
+ return `${cc === "gb" ? "uk" : cc}-${cleanLang}`;
283
+ }
284
+ return DDG_REGION_MAP[cc] || `${cc}-en`;
285
+ }
286
+ export function getSearchCategoriesCatalog() {
287
+ return {
288
+ categories: Object.entries(SEARCH_CATEGORY_CATALOG).map(([id, def]) => ({
289
+ id,
290
+ ...def,
291
+ subcategories: Object.entries(def.subcategories).map(([sid, s]) => ({
292
+ id: sid,
293
+ ...s,
294
+ })),
295
+ })),
296
+ notes: "`category` and `subcategory` accept any string; unknown categories fall back to the general engine plan.",
297
+ filters: {
298
+ engines: "SearchEngineSource[]: override engine plan",
299
+ excludeEngines: "SearchEngineSource[]",
300
+ preferLocalDomains: "boolean",
301
+ localizeBy: "auto | city | country | region | none",
302
+ mustIncludeTerms: "string[]",
303
+ excludeTerms: "string[]",
304
+ boostTerms: "string[]",
305
+ includeDomains: "string[]: post-fetch host allowlist (supports subdomains)",
306
+ excludeDomains: "string[]: post-fetch host blocklist (supports subdomains)",
307
+ sites: "string[]: target specific retailer domains or URLs (e.g. ['jbhifi.com.au', 'amazon.com.au'])",
308
+ productMode: "boolean: return structured products[] and offers[] with prices (default true for category='shopping')",
309
+ allowBrowserEscalation: "boolean: allow headless browser escalation on blocked candidate pages (default false)",
310
+ customInstruction: "string: ranking hint for decision model",
311
+ strict: "boolean: hard-drop results that miss mustIncludeTerms or match excludeTerms (default false = score penalty only)",
312
+ },
313
+ };
314
+ }
315
+ export function normalizeHostList(domainsOrUrls) {
316
+ if (!domainsOrUrls?.length) {
317
+ return [];
318
+ }
319
+ const out = [];
320
+ for (const raw of domainsOrUrls) {
321
+ const trimmed = (raw || "").trim().toLowerCase();
322
+ if (!trimmed) {
323
+ continue;
324
+ }
325
+ if (trimmed.includes("://")) {
326
+ const h = extractDomainHost(trimmed);
327
+ if (h) {
328
+ out.push(h);
329
+ }
330
+ }
331
+ else {
332
+ const h = trimmed
333
+ .replace(/^www\./i, "")
334
+ .split("/")[0]
335
+ .trim();
336
+ if (h) {
337
+ out.push(h);
338
+ }
339
+ }
340
+ }
341
+ return Array.from(new Set(out));
342
+ }
343
+ export function hostMatchesAny(urlOrHost, allowedHosts) {
344
+ if (!allowedHosts.length) {
345
+ return true;
346
+ }
347
+ const host = urlOrHost.includes("://")
348
+ ? extractDomainHost(urlOrHost)
349
+ : urlOrHost.replace(/^www\./i, "").toLowerCase();
350
+ if (!host) {
351
+ return false;
352
+ }
353
+ return allowedHosts.some((target) => host === target || host.endsWith(`.${target}`));
354
+ }
355
+ const COUNTRY_NAME_TO_CODE = {
356
+ australia: "AU",
357
+ "united states": "US",
358
+ usa: "US",
359
+ "united kingdom": "GB",
360
+ uk: "GB",
361
+ britain: "GB",
362
+ england: "GB",
363
+ europe: "EU",
364
+ "european union": "EU",
365
+ india: "IN",
366
+ singapore: "SG",
367
+ vietnam: "VN",
368
+ "viet nam": "VN",
369
+ germany: "DE",
370
+ france: "FR",
371
+ italy: "IT",
372
+ spain: "ES",
373
+ netherlands: "NL",
374
+ switzerland: "CH",
375
+ sweden: "SE",
376
+ norway: "NO",
377
+ denmark: "DK",
378
+ poland: "PL",
379
+ ireland: "IE",
380
+ austria: "AT",
381
+ belgium: "BE",
382
+ portugal: "PT",
383
+ finland: "FI",
384
+ canada: "CA",
385
+ "new zealand": "NZ",
386
+ japan: "JP",
387
+ "south korea": "KR",
388
+ korea: "KR",
389
+ thailand: "TH",
390
+ malaysia: "MY",
391
+ indonesia: "ID",
392
+ philippines: "PH",
393
+ "hong kong": "HK",
394
+ taiwan: "TW",
395
+ uae: "AE",
396
+ "united arab emirates": "AE",
397
+ };
398
+ function normalizeCountryInput(raw) {
399
+ if (!raw || !raw.trim()) {
400
+ return {};
401
+ }
402
+ const clean = raw.trim();
403
+ if (clean.length === 2) {
404
+ const upper = clean.toUpperCase();
405
+ return {
406
+ code: upper === "UK" ? "GB" : upper,
407
+ ...(upper === "EU" ? { countryName: "Europe" } : {}),
408
+ ...(upper === "SG" ? { cityHint: "Singapore" } : {}),
409
+ };
410
+ }
411
+ const mapped = COUNTRY_NAME_TO_CODE[clean.toLowerCase()];
412
+ if (mapped) {
413
+ return {
414
+ code: mapped,
415
+ countryName: clean,
416
+ ...(mapped === "SG" ? { cityHint: "Singapore" } : {}),
417
+ };
418
+ }
419
+ return {};
420
+ }
421
+ export function resolveUserLocation(req) {
422
+ const loc = {};
423
+ if (typeof req.location === "string" && req.location.trim()) {
424
+ const parts = req.location
425
+ .split(",")
426
+ .map((s) => s.trim())
427
+ .filter(Boolean);
428
+ if (parts.length === 1) {
429
+ const norm = normalizeCountryInput(parts[0]);
430
+ if (norm.code) {
431
+ loc.country = norm.code;
432
+ if (norm.countryName) {
433
+ loc.countryName = norm.countryName;
434
+ }
435
+ if (norm.cityHint && !loc.city) {
436
+ loc.city = norm.cityHint;
437
+ }
438
+ }
439
+ else {
440
+ loc.city = parts[0];
441
+ }
442
+ }
443
+ else if (parts.length === 2) {
444
+ loc.city = parts[0];
445
+ const norm = normalizeCountryInput(parts[1]);
446
+ loc.country = norm.code || parts[1];
447
+ if (norm.countryName) {
448
+ loc.countryName = norm.countryName;
449
+ }
450
+ }
451
+ else if (parts.length >= 3) {
452
+ loc.city = parts[0];
453
+ loc.region = parts[1];
454
+ const norm = normalizeCountryInput(parts[parts.length - 1]);
455
+ loc.country = norm.code || parts[parts.length - 1];
456
+ if (norm.countryName) {
457
+ loc.countryName = norm.countryName;
458
+ }
459
+ }
460
+ }
461
+ else if (req.location && typeof req.location === "object") {
462
+ Object.assign(loc, req.location);
463
+ }
464
+ if (req.country) {
465
+ const norm = normalizeCountryInput(req.country);
466
+ loc.country = norm.code || req.country.trim().toUpperCase();
467
+ if (norm.countryName && !loc.countryName) {
468
+ loc.countryName = norm.countryName;
469
+ }
470
+ if (norm.cityHint && !loc.city && !req.city) {
471
+ loc.city = norm.cityHint;
472
+ }
473
+ }
474
+ if (req.region) {
475
+ loc.region = req.region.trim();
476
+ }
477
+ if (req.city) {
478
+ loc.city = req.city.trim();
479
+ }
480
+ if (req.timezone) {
481
+ loc.timezone = req.timezone.trim();
482
+ }
483
+ // Infer city from IANA timezone (e.g. "Australia/Sydney" -> Sydney) when nothing else is known
484
+ if (loc.timezone && !loc.city && !loc.country && loc.timezone.includes("/")) {
485
+ loc.city = loc.timezone.split("/").pop()?.replace(/_/g, " ");
486
+ }
487
+ return loc;
488
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./catalog.js";
2
+ export * from "./providers.js";
3
+ export * from "./pipeline.js";
@@ -0,0 +1,3 @@
1
+ export * from "./catalog.js";
2
+ export * from "./providers.js";
3
+ export * from "./pipeline.js";
@@ -0,0 +1,7 @@
1
+ import { type SearchRequest, type SearchResponse } from "./catalog.js";
2
+ /**
3
+ * High-speed Native Multi-Engine Web Search + 5-Stage Document & Decision Model Reranker (`/v1/search` & `/api/v1/search`).
4
+ * Uses Hedged Quorum Racing + Weighted Reciprocal Rank Fusion (W-RRF) + Fielded BM25F+ + ColBERT Subword MaxSim +
5
+ * Vercel AI Gateway Decision Models (`baseten/mammoth-1` & `convaiinnovations/laya`).
6
+ */
7
+ export declare function searchWeb(req: SearchRequest): Promise<SearchResponse>;