@omnicross/core 0.2.0 → 0.2.1

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 (122) hide show
  1. package/LICENSE +21 -21
  2. package/NOTICE +57 -57
  3. package/README.md +22 -22
  4. package/dist/{BuiltinToolExecutor-CS2WpXhM.d.cts → BuiltinToolExecutor-BHgH-EaZ.d.cts} +25 -2
  5. package/dist/{BuiltinToolExecutor-BluWyeob.d.ts → BuiltinToolExecutor-Bi1LmiTP.d.ts} +25 -2
  6. package/dist/{CompletionService-BiJqftc-.d.cts → CompletionService-BniP7j88.d.cts} +2 -2
  7. package/dist/{CompletionService-NXN_Av5W.d.ts → CompletionService-BqrKPwfA.d.ts} +2 -2
  8. package/dist/{ProviderProxy-CgIcCVwk.d.cts → ProviderProxy-Bz_nkOwq.d.cts} +1 -1
  9. package/dist/{ProviderProxy-DYoY5XoT.d.ts → ProviderProxy-Ckr6oTrE.d.ts} +1 -1
  10. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +2 -2
  11. package/dist/auth/GeminiCodeAssistProjectResolver.js +1 -1
  12. package/dist/{chunk-FJNKXOWE.js → chunk-2C3TTBS7.js} +4 -4
  13. package/dist/{chunk-OST5RJOJ.cjs → chunk-4Z3ZLLC2.cjs} +13 -7
  14. package/dist/chunk-7RJI3NDI.cjs +236 -0
  15. package/dist/chunk-D4WNI6BR.cjs +386 -0
  16. package/dist/chunk-DBBR6RHF.js +236 -0
  17. package/dist/chunk-E4QHVXV5.cjs +118 -0
  18. package/dist/chunk-HL2YSPL7.cjs +673 -0
  19. package/dist/chunk-LFPQ2OVH.js +386 -0
  20. package/dist/{chunk-WSJE42SG.js → chunk-LKDAN4D7.js} +3684 -2462
  21. package/dist/{chunk-DEIUR2TH.js → chunk-LQYS2N47.js} +12 -8
  22. package/dist/{chunk-NZYN7C3P.cjs → chunk-MPXOKQJP.cjs} +53 -53
  23. package/dist/chunk-MZEGQSKQ.js +118 -0
  24. package/dist/chunk-NLUOTJWB.js +673 -0
  25. package/dist/{chunk-CTKM773J.cjs → chunk-UPQIWJIE.cjs} +12 -8
  26. package/dist/{chunk-CFSGYFOZ.js → chunk-VL5TUDTM.js} +7 -1
  27. package/dist/{chunk-7B56AJBB.cjs → chunk-VSYQ6XYN.cjs} +2233 -1011
  28. package/dist/{chunk-CDHS2QDC.js → chunk-Y4CRNHJO.js} +53 -53
  29. package/dist/{chunk-TVYA5JSQ.cjs → chunk-ZAPN7XA6.cjs} +5 -5
  30. package/dist/completion/BuiltinToolExecutor.cjs +2 -2
  31. package/dist/completion/BuiltinToolExecutor.d.cts +3 -1
  32. package/dist/completion/BuiltinToolExecutor.d.ts +3 -1
  33. package/dist/completion/BuiltinToolExecutor.js +1 -1
  34. package/dist/completion/CompletionService.cjs +11 -10
  35. package/dist/completion/CompletionService.d.cts +6 -3
  36. package/dist/completion/CompletionService.d.ts +6 -3
  37. package/dist/completion/CompletionService.js +10 -9
  38. package/dist/completion.cjs +11 -10
  39. package/dist/completion.d.cts +6 -3
  40. package/dist/completion.d.ts +6 -3
  41. package/dist/completion.js +10 -9
  42. package/dist/egress-I46O_p5L.d.cts +126 -0
  43. package/dist/egress-I46O_p5L.d.ts +126 -0
  44. package/dist/frontends-Doz0RaWu.d.cts +97 -0
  45. package/dist/frontends-Doz0RaWu.d.ts +97 -0
  46. package/dist/image-generation.cjs +2 -2
  47. package/dist/image-generation.d.cts +7 -4
  48. package/dist/image-generation.d.ts +7 -4
  49. package/dist/image-generation.js +1 -1
  50. package/dist/index.cjs +25 -10
  51. package/dist/index.d.cts +12 -8
  52. package/dist/index.d.ts +12 -8
  53. package/dist/index.js +24 -9
  54. package/dist/openai-operation.d.cts +4 -1
  55. package/dist/openai-operation.d.ts +4 -1
  56. package/dist/outbound-api/routeResolver.d.cts +9 -5
  57. package/dist/outbound-api/routeResolver.d.ts +9 -5
  58. package/dist/outbound-api/subscriptionRegistryPort.d.cts +4 -1
  59. package/dist/outbound-api/subscriptionRegistryPort.d.ts +4 -1
  60. package/dist/outbound-api/types.d.cts +8 -4
  61. package/dist/outbound-api/types.d.ts +8 -4
  62. package/dist/outbound-api.cjs +25 -10
  63. package/dist/outbound-api.d.cts +117 -8
  64. package/dist/outbound-api.d.ts +117 -8
  65. package/dist/outbound-api.js +24 -9
  66. package/dist/pipeline/AccountAllowanceScheduling.d.cts +8 -4
  67. package/dist/pipeline/AccountAllowanceScheduling.d.ts +8 -4
  68. package/dist/pipeline/upstreamFetch.cjs +2 -2
  69. package/dist/pipeline/upstreamFetch.js +1 -1
  70. package/dist/ports.d.cts +8 -4
  71. package/dist/ports.d.ts +8 -4
  72. package/dist/provider-proxy/ProviderProxy.cjs +11 -10
  73. package/dist/provider-proxy/ProviderProxy.d.cts +5 -2
  74. package/dist/provider-proxy/ProviderProxy.d.ts +5 -2
  75. package/dist/provider-proxy/ProviderProxy.js +10 -9
  76. package/dist/provider-proxy/ingress/providerProxyShared.cjs +13 -10
  77. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +22 -3
  78. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +22 -3
  79. package/dist/provider-proxy/ingress/providerProxyShared.js +12 -9
  80. package/dist/provider-proxy/types.d.cts +4 -1
  81. package/dist/provider-proxy/types.d.ts +4 -1
  82. package/dist/provider-proxy.cjs +11 -10
  83. package/dist/provider-proxy.d.cts +10 -6
  84. package/dist/provider-proxy.d.ts +10 -6
  85. package/dist/provider-proxy.js +10 -9
  86. package/dist/proxy-UZTtjPZx.d.cts +18 -0
  87. package/dist/proxy-UZTtjPZx.d.ts +18 -0
  88. package/dist/{routeResolver-rpKQLyDO.d.cts → routeResolver-Da0yvkL1.d.cts} +2 -2
  89. package/dist/{routeResolver-CTCcSHWu.d.ts → routeResolver-STwMz_G4.d.ts} +2 -2
  90. package/dist/runtime-6QXNU526.cjs +8 -0
  91. package/dist/runtime-M5WXLD2R.js +8 -0
  92. package/dist/runtime-t8AZacFF.d.cts +233 -0
  93. package/dist/runtime-t8AZacFF.d.ts +233 -0
  94. package/dist/search/api.cjs +761 -0
  95. package/dist/search/api.d.cts +370 -0
  96. package/dist/search/api.d.ts +370 -0
  97. package/dist/search/api.js +761 -0
  98. package/dist/search/http.cjs +37 -0
  99. package/dist/search/http.d.cts +269 -0
  100. package/dist/search/http.d.ts +269 -0
  101. package/dist/search/http.js +37 -0
  102. package/dist/search.cjs +57 -0
  103. package/dist/search.d.cts +113 -0
  104. package/dist/search.d.ts +113 -0
  105. package/dist/search.js +57 -0
  106. package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -3
  107. package/dist/transformer/transformers/AnthropicTransformer.js +2 -2
  108. package/dist/transformer/transformers.cjs +7 -7
  109. package/dist/transformer/transformers.js +6 -6
  110. package/dist/transformer.cjs +6 -6
  111. package/dist/transformer.js +5 -5
  112. package/dist/{types-DsHBBk3h.d.cts → types-BSIJdsyl.d.cts} +15 -0
  113. package/dist/{types-CbglB73l.d.cts → types-Be-yt_6T.d.cts} +1 -1
  114. package/dist/{types-CVRge76p.d.ts → types-By9Tg6-I.d.ts} +15 -0
  115. package/dist/types-CbCZqLGg.d.cts +127 -0
  116. package/dist/types-CbCZqLGg.d.ts +127 -0
  117. package/dist/{types-jjU24eYG.d.cts → types-D-oHYusb.d.cts} +73 -4
  118. package/dist/{types-Bd72H4SB.d.ts → types-Dm4_36V4.d.ts} +1 -1
  119. package/dist/{types-5T223WYQ.d.ts → types-Dvlpjn0d.d.ts} +73 -4
  120. package/package.json +5 -3
  121. package/dist/{chunk-6OC3ORW6.js → chunk-EUZRH544.js} +3 -3
  122. package/dist/{chunk-SQMAEDNE.cjs → chunk-POUTD5AM.cjs} +2 -2
@@ -0,0 +1,673 @@
1
+ import {
2
+ getSearchProxyDispatcher,
3
+ resolveSearchProxySettings,
4
+ validateEgressUrl
5
+ } from "./chunk-DBBR6RHF.js";
6
+
7
+ // src/search/http/errors.ts
8
+ import {
9
+ isSearchProviderError,
10
+ SearchProviderError
11
+ } from "@omnicross/contracts/search-types";
12
+
13
+ // src/search/http/types.ts
14
+ var SEARCH_HTTP_TRANSPORT_ID = "undici";
15
+
16
+ // src/search/http/errors.ts
17
+ function searchHttpError(code, message, init) {
18
+ return new SearchProviderError(code, message, {
19
+ providerId: init.providerId,
20
+ retryable: init.retryable,
21
+ cause: init.cause,
22
+ // transport/stage last so a caller-supplied `details` can never shadow them.
23
+ details: { ...init.details, transport: SEARCH_HTTP_TRANSPORT_ID, stage: init.stage }
24
+ });
25
+ }
26
+ function asSearchProviderError(value, providerId, fallbackStage) {
27
+ if (isSearchProviderError(value)) {
28
+ const complete = value.providerId !== void 0 && value.details?.transport !== void 0 && value.details?.stage !== void 0;
29
+ if (complete) return value;
30
+ return searchHttpError(value.code, value.message, {
31
+ stage: value.details?.stage ?? fallbackStage,
32
+ providerId: value.providerId ?? providerId,
33
+ retryable: value.retryable,
34
+ cause: value,
35
+ details: value.details
36
+ });
37
+ }
38
+ return searchHttpError("upstream_unavailable", describeError(value), {
39
+ stage: fallbackStage,
40
+ providerId,
41
+ retryable: true,
42
+ cause: value
43
+ });
44
+ }
45
+ function describeError(value) {
46
+ if (value instanceof Error && value.message) return value.message;
47
+ if (typeof value === "string" && value) return value;
48
+ return "search request failed";
49
+ }
50
+ function hostOf(url) {
51
+ try {
52
+ return new URL(url).hostname;
53
+ } catch {
54
+ return "unknown-host";
55
+ }
56
+ }
57
+
58
+ // src/search/http/engine.ts
59
+ var SEARCH_RESPONSE_BYTES = 2 * 1024 * 1024;
60
+ var DEFAULT_SEARCH_TIMEOUT_MS = 15e3;
61
+ var DEFAULT_MAX_RESULTS = 5;
62
+ var MAX_RESULTS = 10;
63
+ function clampMaxResults(maxResults) {
64
+ return Math.min(Math.max(maxResults ?? DEFAULT_MAX_RESULTS, 1), MAX_RESULTS);
65
+ }
66
+ async function runHttpSearch(engine, transport, query, options = {}) {
67
+ const providerId = engine.providerId;
68
+ if (options.signal?.aborted) {
69
+ throw searchHttpError("cancelled", "search was cancelled before it started", {
70
+ stage: "fetch",
71
+ providerId,
72
+ retryable: false
73
+ });
74
+ }
75
+ const maxResults = clampMaxResults(options.maxResults);
76
+ const timeoutMs = options.timeout ?? DEFAULT_SEARCH_TIMEOUT_MS;
77
+ const deadline = Date.now() + timeoutMs;
78
+ const urls = engine.buildUrls(query);
79
+ let sawRecognizedEmpty = false;
80
+ let lastError = searchHttpError(
81
+ "timeout",
82
+ "search deadline expired before any request was attempted",
83
+ { stage: "fetch", providerId, retryable: true }
84
+ );
85
+ for (const [index, url] of urls.entries()) {
86
+ const remainingMs = deadline - Date.now();
87
+ if (remainingMs <= 0) break;
88
+ const attemptTimeoutMs = Math.max(1, Math.floor(remainingMs / (urls.length - index)));
89
+ try {
90
+ const resource = await transport(url, {
91
+ timeoutMs: attemptTimeoutMs,
92
+ maxResponseBytes: SEARCH_RESPONSE_BYTES,
93
+ signal: options.signal,
94
+ providerId
95
+ });
96
+ if (engine.isChallengeStatus(resource.status)) {
97
+ throw searchHttpError(
98
+ "upstream_unavailable",
99
+ `server returned a bot-challenge response (HTTP ${resource.status})`,
100
+ { stage: "challenge", providerId, retryable: false, details: { status: String(resource.status) } }
101
+ );
102
+ }
103
+ const trustError = engine.trustError?.(resource.finalUrl, resource.rawText);
104
+ if (trustError) {
105
+ throw searchHttpError("upstream_unavailable", trustError, {
106
+ stage: "trust",
107
+ providerId,
108
+ retryable: false
109
+ });
110
+ }
111
+ const parsed = engine.parse(resource.rawText, maxResults);
112
+ if (parsed.results.length > 0) return parsed.results;
113
+ if (parsed.recognized) {
114
+ sawRecognizedEmpty = true;
115
+ continue;
116
+ }
117
+ throw searchHttpError(
118
+ "parse_failed",
119
+ `response is not recognizable as a ${engine.label} search result page`,
120
+ { stage: "parse", providerId, retryable: false }
121
+ );
122
+ } catch (error) {
123
+ const normalized = asSearchProviderError(error, providerId, "fetch");
124
+ if (normalized.code === "cancelled") throw normalized;
125
+ lastError = normalized;
126
+ }
127
+ }
128
+ if (sawRecognizedEmpty) return [];
129
+ throw lastError;
130
+ }
131
+
132
+ // src/search/http/parsers/bing.ts
133
+ import { JSDOM } from "jsdom";
134
+
135
+ // src/search/http/text.ts
136
+ import { decodeHTML } from "entities";
137
+ var MAX_TEXT_LENGTH = 500;
138
+ function cleanText(value) {
139
+ return decodeHTML(value ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_TEXT_LENGTH);
140
+ }
141
+ function isUsableResult(result) {
142
+ return Boolean(result.title && /^https?:\/\//i.test(result.url));
143
+ }
144
+
145
+ // src/search/http/parsers/bing.ts
146
+ var BING_SERP_URL = "https://www.bing.com/search";
147
+ var BING_SERP_MARKERS = "#b_results, .b_algo";
148
+ function bingResultLinks(document) {
149
+ const scoped = [
150
+ ...document.querySelectorAll("li.b_algo h2 a, #b_results .b_algo h2 a")
151
+ ];
152
+ return scoped.length > 0 ? scoped : [...document.querySelectorAll("h2 a[href]")];
153
+ }
154
+ function isBingSerp(document) {
155
+ return document.querySelector(BING_SERP_MARKERS) !== null;
156
+ }
157
+ function parseBingSerp(html, maxResults) {
158
+ const document = new JSDOM(html, { url: BING_SERP_URL }).window.document;
159
+ const results = bingResultLinks(document).map((link) => {
160
+ const container = link.closest("li.b_algo, .b_algo");
161
+ return {
162
+ title: cleanText(link.textContent),
163
+ url: cleanBingUrl(link.href),
164
+ content: cleanText(container?.querySelector(".b_caption p, .b_algoSlug, p")?.textContent)
165
+ };
166
+ }).filter(isUsableResult).slice(0, maxResults);
167
+ return { recognized: isBingSerp(document), results };
168
+ }
169
+ function cleanBingUrl(rawUrl) {
170
+ try {
171
+ const url = new URL(rawUrl, "https://www.bing.com");
172
+ const hostname = url.hostname.toLowerCase();
173
+ if ((hostname === "bing.com" || hostname.endsWith(".bing.com")) && url.pathname === "/ck/a") {
174
+ const encodedTarget = url.searchParams.get("u");
175
+ if (encodedTarget?.startsWith("a1")) {
176
+ const decoded = Buffer.from(encodedTarget.slice(2), "base64url").toString("utf8");
177
+ if (/^https?:\/\//i.test(decoded)) return decoded;
178
+ }
179
+ }
180
+ return url.href;
181
+ } catch {
182
+ return rawUrl;
183
+ }
184
+ }
185
+
186
+ // src/search/http/trust.ts
187
+ import { JSDOM as JSDOM2 } from "jsdom";
188
+ var QUERY_STOP_WORDS = /* @__PURE__ */ new Set([
189
+ "a",
190
+ "an",
191
+ "and",
192
+ "at",
193
+ "by",
194
+ "for",
195
+ "from",
196
+ "in",
197
+ "of",
198
+ "on",
199
+ "or",
200
+ "the",
201
+ "to",
202
+ "with",
203
+ "site",
204
+ "filetype",
205
+ "inurl",
206
+ "intitle"
207
+ ]);
208
+ function bingSearchPageTrustError(finalUrl, html) {
209
+ let parsed;
210
+ try {
211
+ parsed = new URL(finalUrl);
212
+ } catch {
213
+ return null;
214
+ }
215
+ if (parsed.pathname !== "/search") return null;
216
+ const hostname = parsed.hostname.toLowerCase();
217
+ if (hostname !== "bing.com" && !hostname.endsWith(".bing.com")) return null;
218
+ return bingTrustError(parsed.searchParams.get("q") ?? "", html);
219
+ }
220
+ function bingTrustError(query, html) {
221
+ const document = new JSDOM2(html).window.document;
222
+ const links = bingResultLinks(document);
223
+ if (links.length === 0) return null;
224
+ const terms = meaningfulQueryTerms(query);
225
+ if (terms.length === 0) return null;
226
+ const titleSearchable = links.map((link) => link.textContent ?? "").join(" ").normalize("NFKC").toLocaleLowerCase();
227
+ if (!terms.some((term) => titleSearchable.includes(term))) {
228
+ return "Bing returned an untrusted search result page with zero query-term hits in result titles; refusing to return possible bot-decoy content.";
229
+ }
230
+ const searchable = links.map((link) => {
231
+ const container = link.closest("li.b_algo, .b_algo");
232
+ return `${link.textContent ?? ""} ${link.getAttribute("href") ?? ""} ${container?.querySelector(".b_caption p, .b_algoSlug, p")?.textContent ?? ""}`;
233
+ }).join(" ").normalize("NFKC").toLocaleLowerCase();
234
+ const coveredTerms = terms.filter((term) => searchable.includes(term));
235
+ const requiredCoverage = terms.length === 1 ? 1 : 2;
236
+ return new Set(coveredTerms).size >= requiredCoverage ? null : "Bing returned an untrusted search result page; refusing to return possible bot-decoy content.";
237
+ }
238
+ function meaningfulQueryTerms(query) {
239
+ const terms = query.normalize("NFKC").toLocaleLowerCase().match(/[\p{L}\p{N}]{2,}/gu) ?? [];
240
+ const expanded = terms.flatMap((term) => {
241
+ if (QUERY_STOP_WORDS.has(term) || !/\p{Script=Han}/u.test(term)) return [term];
242
+ const characters = [...term];
243
+ const bigrams = characters.slice(0, -1).map((character, index) => {
244
+ return character + characters[index + 1];
245
+ });
246
+ return [term, ...bigrams];
247
+ });
248
+ return [...new Set(expanded.filter((term) => !QUERY_STOP_WORDS.has(term)))];
249
+ }
250
+
251
+ // src/search/http/transport.ts
252
+ import { isSearchProviderError as isSearchProviderError2 } from "@omnicross/contracts/search-types";
253
+ import { fetch as undiciFetch } from "undici";
254
+
255
+ // src/search/http/body-decode.ts
256
+ var META_SNIFF_BYTES = 4096;
257
+ var UTF8_LABELS = /* @__PURE__ */ new Set(["utf-8", "utf8", "unicode-1-1-utf-8", "x-unicode20utf8"]);
258
+ function charsetFromContentType(contentType) {
259
+ const match = /charset\s*=\s*["']?([\w:.-]+)/i.exec(contentType);
260
+ return match ? match[1].toLowerCase() : null;
261
+ }
262
+ function charsetFromMeta(bytes) {
263
+ const head = Buffer.from(bytes.subarray(0, META_SNIFF_BYTES)).toString("latin1");
264
+ const metaCharset = /<meta[^>]+charset\s*=\s*["']?\s*([\w:.-]+)/i.exec(head);
265
+ return metaCharset ? metaCharset[1].toLowerCase() : null;
266
+ }
267
+ function decodeSearchBody(bytes, contentType = "") {
268
+ const declared = charsetFromContentType(contentType) ?? charsetFromMeta(bytes);
269
+ if (declared && !UTF8_LABELS.has(declared)) {
270
+ const decoder = decoderFor(declared);
271
+ if (decoder) return decoder.decode(bytes);
272
+ }
273
+ try {
274
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
275
+ } catch {
276
+ return new TextDecoder("windows-1252").decode(bytes);
277
+ }
278
+ }
279
+ function decoderFor(label) {
280
+ try {
281
+ return new TextDecoder(label);
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ // src/search/http/headers.ts
288
+ var CHROME_VERSION = "144.0.0.0";
289
+ var CHROME_MAJOR = CHROME_VERSION.split(".")[0];
290
+ var SEARCH_BROWSER_HEADERS = Object.freeze({
291
+ "User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROME_VERSION} Safari/537.36`,
292
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
293
+ "Accept-Language": "en-US,en;q=0.9",
294
+ "Cache-Control": "no-cache",
295
+ Pragma: "no-cache",
296
+ "Sec-CH-UA": `"Chromium";v="${CHROME_MAJOR}", "Not=A?Brand";v="24"`,
297
+ "Sec-CH-UA-Mobile": "?0",
298
+ "Sec-CH-UA-Platform": '"Windows"',
299
+ "Sec-Fetch-Dest": "document",
300
+ "Sec-Fetch-Mode": "navigate",
301
+ "Sec-Fetch-Site": "none",
302
+ "Sec-Fetch-User": "?1",
303
+ "Upgrade-Insecure-Requests": "1"
304
+ });
305
+ function searchBrowserHeaders() {
306
+ return { ...SEARCH_BROWSER_HEADERS };
307
+ }
308
+
309
+ // src/search/http/transport.ts
310
+ var DEFAULT_TIMEOUT_MS = 3e4;
311
+ var DEFAULT_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
312
+ var MAX_REDIRECTS = 5;
313
+ var REFUSED_STATUSES = /* @__PURE__ */ new Set([401, 403, 407, 429]);
314
+ function createSearchHttpTransport(options = {}) {
315
+ const fetchImpl = options.fetch ?? createUndiciFetch(options.env);
316
+ const maxRedirects = options.maxRedirects ?? MAX_REDIRECTS;
317
+ const egressPolicy = options.egressPolicy;
318
+ return async (url, request) => {
319
+ const context = {
320
+ providerId: request.providerId,
321
+ host: hostOf(url)
322
+ };
323
+ const controller = new AbortController();
324
+ const onAbort = () => controller.abort(request.signal?.reason);
325
+ request.signal?.addEventListener("abort", onAbort, { once: true });
326
+ if (request.signal?.aborted) onAbort();
327
+ const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
328
+ let deadlineExpired = false;
329
+ const timer = setTimeout(() => {
330
+ deadlineExpired = true;
331
+ controller.abort();
332
+ }, timeoutMs);
333
+ try {
334
+ const { response, finalUrl } = await fetchWithRedirectLimit(
335
+ fetchImpl,
336
+ url,
337
+ controller.signal,
338
+ maxRedirects,
339
+ context,
340
+ egressPolicy
341
+ );
342
+ context.host = hostOf(finalUrl);
343
+ const contentType = response.headers.get("content-type") ?? "";
344
+ const rawText = await readBodyWithLimit(
345
+ response,
346
+ request.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES,
347
+ context
348
+ );
349
+ assertStatusUsable(response.status, context);
350
+ assertNoBotChallenge(rawText, context);
351
+ return { finalUrl, status: response.status, contentType, rawText };
352
+ } catch (error) {
353
+ if (request.signal?.aborted) {
354
+ throw searchHttpError("cancelled", "search request was cancelled by the caller", {
355
+ stage: "fetch",
356
+ providerId: context.providerId,
357
+ retryable: false,
358
+ cause: error,
359
+ details: { host: context.host }
360
+ });
361
+ }
362
+ if (deadlineExpired) {
363
+ throw searchHttpError("timeout", `search request exceeded its ${timeoutMs}ms budget`, {
364
+ stage: "fetch",
365
+ providerId: context.providerId,
366
+ retryable: true,
367
+ cause: error,
368
+ details: { host: context.host }
369
+ });
370
+ }
371
+ throw asSearchProviderError(error, context.providerId, "fetch");
372
+ } finally {
373
+ clearTimeout(timer);
374
+ request.signal?.removeEventListener("abort", onAbort);
375
+ }
376
+ };
377
+ }
378
+ var defaultSearchHttpTransport = createSearchHttpTransport();
379
+ function createUndiciFetch(env) {
380
+ return async (url, init) => {
381
+ const proxy = resolveSearchProxySettings(env);
382
+ const dispatcher = proxy ? getSearchProxyDispatcher(proxy) : void 0;
383
+ const requestInit = dispatcher ? { ...init, dispatcher } : init;
384
+ return await undiciFetch(
385
+ url,
386
+ requestInit
387
+ );
388
+ };
389
+ }
390
+ async function fetchWithRedirectLimit(fetchImpl, url, signal, maxRedirects, context, egressPolicy) {
391
+ let currentUrl = url;
392
+ assertEgressAllowed(currentUrl, egressPolicy, "connect", context);
393
+ for (let redirects = 0; redirects <= maxRedirects; redirects += 1) {
394
+ let response;
395
+ context.host = hostOf(currentUrl);
396
+ try {
397
+ response = await fetchImpl(currentUrl, {
398
+ signal,
399
+ redirect: "manual",
400
+ headers: searchBrowserHeaders()
401
+ });
402
+ } catch (error) {
403
+ if (isAbortError(error)) throw error;
404
+ throw searchHttpError("upstream_unavailable", networkFailureMessage(error), {
405
+ stage: "connect",
406
+ providerId: context.providerId,
407
+ retryable: true,
408
+ cause: error,
409
+ details: { host: context.host }
410
+ });
411
+ }
412
+ if (response.status < 300 || response.status >= 400) {
413
+ return { response, finalUrl: response.url || currentUrl };
414
+ }
415
+ const location = response.headers.get("location");
416
+ if (!location) return { response, finalUrl: currentUrl };
417
+ void response.body?.cancel().catch(() => void 0);
418
+ if (redirects === maxRedirects) throw tooManyRedirects(maxRedirects, context);
419
+ const nextUrl = new URL(location, currentUrl).href;
420
+ assertEgressAllowed(nextUrl, egressPolicy, "redirect", context);
421
+ currentUrl = nextUrl;
422
+ }
423
+ throw tooManyRedirects(maxRedirects, context);
424
+ }
425
+ function assertEgressAllowed(url, policy, stage, context) {
426
+ try {
427
+ validateEgressUrl(url, policy, context.providerId);
428
+ } catch (error) {
429
+ if (!isSearchProviderError2(error)) throw error;
430
+ throw searchHttpError(error.code, error.message, {
431
+ stage,
432
+ providerId: context.providerId,
433
+ retryable: false,
434
+ cause: error,
435
+ details: error.details
436
+ });
437
+ }
438
+ }
439
+ function tooManyRedirects(maxRedirects, context) {
440
+ return searchHttpError("upstream_unavailable", `too many redirects (>${maxRedirects})`, {
441
+ stage: "redirect",
442
+ providerId: context.providerId,
443
+ retryable: false,
444
+ details: { host: context.host, maxRedirects: String(maxRedirects) }
445
+ });
446
+ }
447
+ async function readBodyWithLimit(response, maxResponseBytes, context) {
448
+ const reader = response.body?.getReader();
449
+ if (!reader) return "";
450
+ const chunks = [];
451
+ let totalBytes = 0;
452
+ while (true) {
453
+ const { done, value } = await reader.read();
454
+ if (done) break;
455
+ totalBytes += value.byteLength;
456
+ if (totalBytes > maxResponseBytes) {
457
+ await reader.cancel().catch(() => void 0);
458
+ throw searchHttpError(
459
+ "upstream_unavailable",
460
+ `search response exceeded the ${maxResponseBytes} byte cap`,
461
+ {
462
+ stage: "body-cap",
463
+ providerId: context.providerId,
464
+ retryable: false,
465
+ details: { host: context.host, maxResponseBytes: String(maxResponseBytes) }
466
+ }
467
+ );
468
+ }
469
+ chunks.push(value);
470
+ }
471
+ const rawBytes = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
472
+ let offset = 0;
473
+ for (const chunk of chunks) {
474
+ rawBytes.set(chunk, offset);
475
+ offset += chunk.byteLength;
476
+ }
477
+ return decodeSearchBody(rawBytes, response.headers.get("content-type") ?? "");
478
+ }
479
+ function assertStatusUsable(status, context) {
480
+ if (!REFUSED_STATUSES.has(status)) return;
481
+ throw searchHttpError(
482
+ "upstream_unavailable",
483
+ `server refused the browser-shaped request (HTTP ${status})`,
484
+ {
485
+ stage: "fetch",
486
+ providerId: context.providerId,
487
+ retryable: false,
488
+ details: { host: context.host, status: String(status) }
489
+ }
490
+ );
491
+ }
492
+ function assertNoBotChallenge(html, context) {
493
+ if (!/<(?:html|body|title|script)\b/i.test(html)) return;
494
+ const title = html.match(/<title[^>]*>(.*?)<\/title>/is)?.[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
495
+ const challengeTitle = /^(?:just a moment|attention required|access denied|robot check|security check)/i.test(
496
+ title ?? ""
497
+ );
498
+ const challengeInfrastructure = /cdn-cgi\/challenge-platform|cf-chl-|cf-turnstile|px-captcha|hcaptcha-container/i.test(html);
499
+ const challengeCopy = /verify (?:you are|that you are) human|unusual traffic from your computer|enable javascript and cookies to continue/i.test(
500
+ html
501
+ );
502
+ const duckDuckGoChallenge = /unfortunately, bots use duckduckgo too|complete the following challenge to confirm this search was made by a human/i.test(
503
+ html
504
+ );
505
+ if (challengeTitle || duckDuckGoChallenge || challengeInfrastructure && challengeCopy) {
506
+ throw searchHttpError(
507
+ "upstream_unavailable",
508
+ "server returned a bot-challenge page instead of the requested content",
509
+ {
510
+ stage: "challenge",
511
+ providerId: context.providerId,
512
+ retryable: false,
513
+ details: { host: context.host }
514
+ }
515
+ );
516
+ }
517
+ }
518
+ function isAbortError(error) {
519
+ return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
520
+ }
521
+ function networkFailureMessage(error) {
522
+ const code = nestedErrorCode(error);
523
+ return code ? `network request failed (${code})` : "network request failed";
524
+ }
525
+ function nestedErrorCode(error) {
526
+ let current = error;
527
+ for (let depth = 0; depth < 4 && current instanceof Error; depth += 1) {
528
+ const code = current.code;
529
+ if (typeof code === "string" && /^[A-Z][A-Z0-9_]+$/.test(code)) return code;
530
+ current = current.cause;
531
+ }
532
+ return void 0;
533
+ }
534
+
535
+ // src/search/http/HttpBingProvider.ts
536
+ var HTTP_BING_PROVIDER_ID = "http-bing";
537
+ var BING_ENGINE = {
538
+ providerId: HTTP_BING_PROVIDER_ID,
539
+ label: "Bing",
540
+ buildUrls: (query) => [`https://www.bing.com/search?q=${encodeURIComponent(query)}`],
541
+ // Bing has no challenge status of its own; it serves decoy CONTENT instead,
542
+ // which the trust check catches.
543
+ isChallengeStatus: () => false,
544
+ trustError: bingSearchPageTrustError,
545
+ parse: parseBingSerp
546
+ };
547
+ var HttpBingProvider = class {
548
+ id = HTTP_BING_PROVIDER_ID;
549
+ transport;
550
+ constructor(transport = defaultSearchHttpTransport) {
551
+ this.transport = transport;
552
+ }
553
+ search(query, options) {
554
+ return runHttpSearch(BING_ENGINE, this.transport, query, options);
555
+ }
556
+ };
557
+
558
+ // src/search/http/parsers/duckduckgo.ts
559
+ import { JSDOM as JSDOM3 } from "jsdom";
560
+ var DUCKDUCKGO_SERP_URL = "https://html.duckduckgo.com/html/";
561
+ var DUCKDUCKGO_SERP_MARKERS = "#links, .results, a.result-link";
562
+ function isDuckDuckGoSerp(document) {
563
+ return document.querySelector(DUCKDUCKGO_SERP_MARKERS) !== null;
564
+ }
565
+ function parseDuckDuckGoSerp(html, maxResults) {
566
+ const document = new JSDOM3(html, { url: DUCKDUCKGO_SERP_URL }).window.document;
567
+ const htmlResults = [
568
+ ...document.querySelectorAll(".result, .result--web, .web-result")
569
+ ].map((container) => {
570
+ const link = container.querySelector("a.result__a, .result__a");
571
+ return {
572
+ title: cleanText(link?.textContent),
573
+ url: cleanDuckDuckGoUrl(link?.href ?? ""),
574
+ content: cleanText(container.querySelector(".result__snippet, .result__body")?.textContent)
575
+ };
576
+ }).filter(isUsableResult);
577
+ if (htmlResults.length > 0) {
578
+ return { recognized: true, results: htmlResults.slice(0, maxResults) };
579
+ }
580
+ const liteResults = [
581
+ ...document.querySelectorAll("a.result-link")
582
+ ].map((link) => {
583
+ const row = link.closest("tr");
584
+ const snippet = row?.nextElementSibling?.querySelector(".result-snippet");
585
+ return {
586
+ title: cleanText(link.textContent),
587
+ url: cleanDuckDuckGoUrl(link.href),
588
+ content: cleanText(snippet?.textContent)
589
+ };
590
+ }).filter(isUsableResult).slice(0, maxResults);
591
+ return { recognized: isDuckDuckGoSerp(document), results: liteResults };
592
+ }
593
+ function cleanDuckDuckGoUrl(rawUrl) {
594
+ try {
595
+ const absoluteUrl = rawUrl.startsWith("//") ? `https:${rawUrl}` : rawUrl;
596
+ const url = new URL(absoluteUrl, "https://html.duckduckgo.com");
597
+ return url.searchParams.get("uddg") ?? url.href;
598
+ } catch {
599
+ return rawUrl;
600
+ }
601
+ }
602
+
603
+ // src/search/http/HttpDuckDuckGoProvider.ts
604
+ var HTTP_DUCKDUCKGO_PROVIDER_ID = "http-duckduckgo";
605
+ var DUCKDUCKGO_CHALLENGE_STATUS = 202;
606
+ var DUCKDUCKGO_ENGINE = {
607
+ providerId: HTTP_DUCKDUCKGO_PROVIDER_ID,
608
+ label: "DuckDuckGo",
609
+ buildUrls: (query) => [
610
+ `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`,
611
+ `https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`
612
+ ],
613
+ isChallengeStatus: (status) => status === DUCKDUCKGO_CHALLENGE_STATUS,
614
+ parse: parseDuckDuckGoSerp
615
+ };
616
+ var HttpDuckDuckGoProvider = class {
617
+ id = HTTP_DUCKDUCKGO_PROVIDER_ID;
618
+ transport;
619
+ constructor(transport = defaultSearchHttpTransport) {
620
+ this.transport = transport;
621
+ }
622
+ search(query, options) {
623
+ return runHttpSearch(DUCKDUCKGO_ENGINE, this.transport, query, options);
624
+ }
625
+ };
626
+
627
+ // src/search/http/contributions.ts
628
+ var HTTP_SEARCH_CAPABILITIES = Object.freeze({
629
+ requiresApiKey: false,
630
+ supportsRegion: false,
631
+ supportsLanguage: false,
632
+ supportsTimeRange: false,
633
+ supportsUrlRead: false,
634
+ supportsCancellation: true,
635
+ maxResults: MAX_RESULTS
636
+ });
637
+ function builtinHttpSearchContributions(transport) {
638
+ return [
639
+ {
640
+ id: HTTP_BING_PROVIDER_ID,
641
+ source: "builtin",
642
+ kind: "http",
643
+ provider: new HttpBingProvider(transport),
644
+ capabilities: { ...HTTP_SEARCH_CAPABILITIES }
645
+ },
646
+ {
647
+ id: HTTP_DUCKDUCKGO_PROVIDER_ID,
648
+ source: "builtin",
649
+ kind: "http",
650
+ provider: new HttpDuckDuckGoProvider(transport),
651
+ capabilities: { ...HTTP_SEARCH_CAPABILITIES }
652
+ }
653
+ ];
654
+ }
655
+
656
+ export {
657
+ SEARCH_HTTP_TRANSPORT_ID,
658
+ SEARCH_RESPONSE_BYTES,
659
+ DEFAULT_SEARCH_TIMEOUT_MS,
660
+ DEFAULT_MAX_RESULTS,
661
+ MAX_RESULTS,
662
+ clampMaxResults,
663
+ SEARCH_BROWSER_HEADERS,
664
+ MAX_REDIRECTS,
665
+ createSearchHttpTransport,
666
+ defaultSearchHttpTransport,
667
+ HTTP_BING_PROVIDER_ID,
668
+ HttpBingProvider,
669
+ HTTP_DUCKDUCKGO_PROVIDER_ID,
670
+ HttpDuckDuckGoProvider,
671
+ HTTP_SEARCH_CAPABILITIES,
672
+ builtinHttpSearchContributions
673
+ };