@openclaw/duckduckgo-plugin 2026.10.10-beta.5 → 2026.11.5-beta.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.
@@ -0,0 +1,122 @@
1
+ import { createRequire } from "node:module";
2
+ import { readPluginPackageVersion } from "openclaw/plugin-sdk/extension-shared";
3
+ import { decodeHtmlEntities } from "openclaw/plugin-sdk/html-entity-runtime";
4
+ import { ProviderHttpError, readProviderTextResponse } from "openclaw/plugin-sdk/provider-http";
5
+ import { DEFAULT_SEARCH_COUNT, externalContentStamp, readCachedSearchPayload, readResponseText, resolveSearchCacheTtlMs, resolveSearchCount, resolveSiteName, resolveTimeoutSeconds, withTrustedWebSearchEndpoint, wrapWebContent, writeCachedSearchPayload } from "openclaw/plugin-sdk/provider-web-search";
6
+ import { resolveProviderWebSearchPluginConfig } from "openclaw/plugin-sdk/provider-web-search-config-contract";
7
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
8
+ // extensions/duckduckgo/src/ddg-client.ts
9
+ const require = createRequire(import.meta.url);
10
+ const DDG_USER_AGENT = `openclaw-duckduckgo/${readPluginPackageVersion({ require })} (+https://docs.openclaw.ai)`;
11
+ const DDG_HTML_ENDPOINT = "https://html.duckduckgo.com/html";
12
+ const DEFAULT_TIMEOUT_SECONDS = 20;
13
+ const DDG_HTML_ENTITY_RE = /&(?:lt|gt|quot|apos|#39|#x27|#x2F|nbsp|ndash|mdash|hellip|amp|#\d+|#x[0-9a-f]+);/gi;
14
+ const DDG_ENTITY_TEXT = {
15
+ "\xA0": " ",
16
+ "–": "-",
17
+ "—": "--",
18
+ "…": "..."
19
+ };
20
+ const DDG_SAFE_SEARCH_PARAM = {
21
+ strict: "1",
22
+ moderate: "-1",
23
+ off: "-2"
24
+ };
25
+ function decodeHtmlEntities$1(text) {
26
+ return text.replace(DDG_HTML_ENTITY_RE, (entity) => {
27
+ const decoded = decodeHtmlEntities(entity.startsWith("&#") ? entity : entity.toLowerCase());
28
+ return DDG_ENTITY_TEXT[decoded] ?? decoded;
29
+ });
30
+ }
31
+ function stripHtml(html) {
32
+ return html.replace(/<\/?b\b[^>]*>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
33
+ }
34
+ async function runDuckDuckGoSearch(params) {
35
+ const count = resolveSearchCount(params.count, DEFAULT_SEARCH_COUNT);
36
+ const region = params.region ?? normalizeOptionalString(resolveProviderWebSearchPluginConfig(params.config, "duckduckgo")?.region);
37
+ let safeSearch = params.safeSearch;
38
+ if (safeSearch == null) {
39
+ const normalized = normalizeLowercaseStringOrEmpty(resolveProviderWebSearchPluginConfig(params.config, "duckduckgo")?.safeSearch);
40
+ safeSearch = normalized === "strict" || normalized === "off" ? normalized : "moderate";
41
+ }
42
+ const timeoutSeconds = resolveTimeoutSeconds(params.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS);
43
+ const cacheTtlMs = resolveSearchCacheTtlMs(params.config?.tools?.web?.search);
44
+ const cacheKey = JSON.stringify({
45
+ provider: "duckduckgo",
46
+ query: params.query,
47
+ count,
48
+ region: region ?? "",
49
+ safeSearch
50
+ });
51
+ const cached = readCachedSearchPayload(cacheKey, cacheTtlMs);
52
+ if (cached) return cached;
53
+ const url = new URL(DDG_HTML_ENDPOINT);
54
+ url.searchParams.set("q", params.query);
55
+ if (region) url.searchParams.set("kl", region);
56
+ url.searchParams.set("kp", DDG_SAFE_SEARCH_PARAM[safeSearch]);
57
+ const startedAt = Date.now();
58
+ const results = await withTrustedWebSearchEndpoint({
59
+ url: url.toString(),
60
+ timeoutSeconds,
61
+ signal: params.signal,
62
+ init: {
63
+ method: "GET",
64
+ headers: { "User-Agent": DDG_USER_AGENT }
65
+ }
66
+ }, async (response) => {
67
+ if (!response.ok) {
68
+ const detail = (await readResponseText(response, { maxBytes: 64e3 })).text;
69
+ throw new ProviderHttpError(`DuckDuckGo search error (${response.status}): ${detail || response.statusText}`, { status: response.status });
70
+ }
71
+ const html = await readProviderTextResponse(response, "DuckDuckGo search");
72
+ if (!/class="[^"]*\bresult__a\b[^"]*"/i.test(html) && /g-recaptcha|are you a human|id="challenge-form"|name="challenge"/i.test(html)) throw new Error("DuckDuckGo returned a bot-detection challenge.");
73
+ const parsedResults = [];
74
+ const resultRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__a\b[^"]*")([^>]*)>([\s\S]*?)<\/a>/gi;
75
+ const nextResultRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__a\b[^"]*")[^>]*>/i;
76
+ const snippetRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__snippet\b[^"]*")[^>]*>([\s\S]*?)<\/a>/i;
77
+ for (const match of html.matchAll(resultRegex)) {
78
+ const rawAttributes = match[1] ?? "";
79
+ const rawTitle = match[2] ?? "";
80
+ const rawUrl = /\bhref="([^"]*)"/i.exec(rawAttributes)?.[1] ?? "";
81
+ const matchEnd = (match.index ?? 0) + match[0].length;
82
+ const trailingHtml = html.slice(matchEnd);
83
+ const nextResultIndex = trailingHtml.search(nextResultRegex);
84
+ const scopedTrailingHtml = nextResultIndex >= 0 ? trailingHtml.slice(0, nextResultIndex) : trailingHtml;
85
+ const rawSnippet = snippetRegex.exec(scopedTrailingHtml)?.[1] ?? "";
86
+ const title = decodeHtmlEntities$1(stripHtml(rawTitle));
87
+ let resultUrl = decodeHtmlEntities$1(rawUrl);
88
+ try {
89
+ const normalized = resultUrl.startsWith("//") ? `https:${resultUrl}` : resultUrl;
90
+ const uddg = new URL(normalized).searchParams.get("uddg");
91
+ if (uddg) resultUrl = uddg;
92
+ } catch {}
93
+ const snippet = decodeHtmlEntities$1(stripHtml(rawSnippet));
94
+ if (title && resultUrl) {
95
+ parsedResults.push({
96
+ title,
97
+ url: resultUrl,
98
+ snippet
99
+ });
100
+ if (parsedResults.length >= count) break;
101
+ }
102
+ }
103
+ return parsedResults;
104
+ });
105
+ params.signal?.throwIfAborted();
106
+ const payload = {
107
+ query: params.query,
108
+ provider: "duckduckgo",
109
+ count: results.length,
110
+ tookMs: Date.now() - startedAt,
111
+ externalContent: externalContentStamp("duckduckgo"),
112
+ results: results.map((result) => ({
113
+ title: wrapWebContent(result.title, "web_search"),
114
+ url: result.url,
115
+ snippet: result.snippet ? wrapWebContent(result.snippet, "web_search") : "",
116
+ siteName: resolveSiteName(result.url) || void 0
117
+ }))
118
+ };
119
+ writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
120
+ return payload;
121
+ }
122
+ export { runDuckDuckGoSearch };
@@ -1,21 +1,12 @@
1
1
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
2
2
  import { readPositiveIntegerParam, readStringParam } from "openclaw/plugin-sdk/param-readers";
3
- import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-contract";
4
- //#region extensions/duckduckgo/src/ddg-search-provider.ts
5
- const loadDuckDuckGoClientModule = createLazyRuntimeModule(() => import("./ddg-client-CmdSShxb.mjs"));
3
+ import { WEB_SEARCH_QUERY_COUNT_PROPERTIES, createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-contract";
4
+ // extensions/duckduckgo/src/ddg-search-provider.ts
5
+ const loadDuckDuckGoClientModule = createLazyRuntimeModule(() => import("./ddg-client-DoTF0OSq.mjs"));
6
6
  const DuckDuckGoSearchSchema = {
7
7
  type: "object",
8
8
  properties: {
9
- query: {
10
- type: "string",
11
- description: "Search query string."
12
- },
13
- count: {
14
- type: "integer",
15
- description: "Number of results to return (1-10).",
16
- minimum: 1,
17
- maximum: 10
18
- },
9
+ ...WEB_SEARCH_QUERY_COUNT_PROPERTIES,
19
10
  region: {
20
11
  type: "string",
21
12
  description: "Optional DuckDuckGo region code such as us-en, uk-en, or de-de."
@@ -54,6 +45,7 @@ function createDuckDuckGoWebSearchProvider() {
54
45
  execute: async (args, context) => {
55
46
  context?.signal?.throwIfAborted();
56
47
  const { runDuckDuckGoSearch } = await loadDuckDuckGoClientModule();
48
+ const safeSearch = readStringParam(args, "safeSearch");
57
49
  const searchConfig = ctx.searchConfig;
58
50
  return await runDuckDuckGoSearch({
59
51
  config: ctx.config,
@@ -64,12 +56,11 @@ function createDuckDuckGoWebSearchProvider() {
64
56
  }) ?? searchConfig?.maxResults,
65
57
  timeoutSeconds: searchConfig?.timeoutSeconds,
66
58
  region: readStringParam(args, "region"),
67
- safeSearch: readStringParam(args, "safeSearch"),
59
+ safeSearch: safeSearch === "strict" || safeSearch === "moderate" || safeSearch === "off" ? safeSearch : void 0,
68
60
  ...context?.signal ? { signal: context.signal } : {}
69
61
  });
70
62
  }
71
63
  })
72
64
  };
73
65
  }
74
- //#endregion
75
66
  export { createDuckDuckGoWebSearchProvider as t };
@@ -1 +1 @@
1
- {"version":3,"entries":[{"output":"index.js","source":"extensions/duckduckgo/index.ts"},{"output":"web-search-contract-api.js","source":"extensions/duckduckgo/web-search-contract-api.ts"},{"output":"web-search-provider.js","source":"extensions/duckduckgo/web-search-provider.ts"}],"chunks":[{"output":".setup/ddg-client-CmdSShxb.mjs","sha256":"316f6e3f5e6750ef9f73018a2302c1c2580105204ac4fa71b63362bd183fd398","facadeSource":"extensions/duckduckgo/src/ddg-client.ts","sources":["extensions/duckduckgo/src/config.ts","extensions/duckduckgo/src/ddg-client.ts"]},{"output":".setup/ddg-search-provider-C555DOZE.mjs","sha256":"b577dd2fa033b3a4bef71116b8e7edd734a70a87fb3972259a826a68cb183ae6","facadeSource":null,"sources":["extensions/duckduckgo/src/ddg-search-provider.ts"]},{"output":"index.js","sha256":"ec1654ab39c45a5e5879d0ca711b9116777ef7729d443a507fda03d12f49493b","facadeSource":"extensions/duckduckgo/index.ts","sources":["extensions/duckduckgo/index.ts"]},{"output":"web-search-contract-api.js","sha256":"e4a759ab5b0a862d82a03dfe3c90f7b6ddeafc65371f1f18db14823725261255","facadeSource":"extensions/duckduckgo/web-search-contract-api.ts","sources":[]},{"output":"web-search-provider.js","sha256":"e3aafe15e84327b30794c732fbb63377afbb7710ccd848f38d5c576395301388","facadeSource":"extensions/duckduckgo/web-search-provider.ts","sources":[]}],"unrenderedSources":["extensions/duckduckgo/web-search-contract-api.ts","extensions/duckduckgo/web-search-provider.ts"]}
1
+ {"version":3,"entries":[{"output":"index.js","source":"extensions/duckduckgo/index.ts"},{"output":"web-search-contract-api.js","source":"extensions/duckduckgo/web-search-contract-api.ts"},{"output":"web-search-provider.js","source":"extensions/duckduckgo/web-search-provider.ts"}],"chunks":[{"output":".setup/ddg-client-DoTF0OSq.mjs","sha256":"1f7f5ac52f08da9f333fc9d9e09c3ff6993ccfc055adfb255e205e772d1aac7b","facadeSource":"extensions/duckduckgo/src/ddg-client.ts","sources":["extensions/duckduckgo/src/ddg-client.ts"]},{"output":".setup/ddg-search-provider-dPkRSeDL.mjs","sha256":"f13b7eb7f8d724d4958b7b7ed2a266b22eadacc14d14cd4eb19e5ba5f9f0c5c1","facadeSource":null,"sources":["extensions/duckduckgo/src/ddg-search-provider.ts"]},{"output":"index.js","sha256":"25d7f28cbc889799ee744904d7f62326557f2e7b3bc1749af3080740dd96d356","facadeSource":"extensions/duckduckgo/index.ts","sources":["extensions/duckduckgo/index.ts"]},{"output":"web-search-contract-api.js","sha256":"de98f62523f6b143688c9296911fb33c8bfe84fdcbf3e041618e1c75ec52b87d","facadeSource":"extensions/duckduckgo/web-search-contract-api.ts","sources":[]},{"output":"web-search-provider.js","sha256":"05a4b25f3e9b95ae20209f5343c971d90e4164e615835595af378987a14368e1","facadeSource":"extensions/duckduckgo/web-search-provider.ts","sources":[]}],"unrenderedSources":["extensions/duckduckgo/web-search-contract-api.ts","extensions/duckduckgo/web-search-provider.ts"]}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { t as createDuckDuckGoWebSearchProvider } from "./.setup/ddg-search-provider-C555DOZE.mjs";
1
+ import { t as createDuckDuckGoWebSearchProvider } from "./.setup/ddg-search-provider-dPkRSeDL.mjs";
2
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
- //#region extensions/duckduckgo/index.ts
3
+ // extensions/duckduckgo/index.ts
4
4
  var duckduckgo_default = definePluginEntry({
5
5
  id: "duckduckgo",
6
6
  name: "DuckDuckGo Plugin",
@@ -9,5 +9,4 @@ var duckduckgo_default = definePluginEntry({
9
9
  api.registerWebSearchProvider(createDuckDuckGoWebSearchProvider());
10
10
  }
11
11
  });
12
- //#endregion
13
12
  export { duckduckgo_default as default };
@@ -1,2 +1,2 @@
1
- export { t as createDuckDuckGoWebSearchProvider } from "./.setup/ddg-search-provider-C555DOZE.mjs";
1
+ export { t as createDuckDuckGoWebSearchProvider } from "./.setup/ddg-search-provider-dPkRSeDL.mjs";
2
2
  import "./web-search-provider.js";
@@ -1 +1 @@
1
- export { t as createDuckDuckGoWebSearchProvider } from "./.setup/ddg-search-provider-C555DOZE.mjs";
1
+ export { t as createDuckDuckGoWebSearchProvider } from "./.setup/ddg-search-provider-dPkRSeDL.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/duckduckgo-plugin",
3
- "version": "2026.10.10-beta.5",
3
+ "version": "2026.11.5-beta.1",
4
4
  "description": "OpenClaw DuckDuckGo plugin.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,10 @@
18
18
  "minHostVersion": ">=2026.7.2"
19
19
  },
20
20
  "compat": {
21
- "pluginApi": ">=2026.10.10-beta.5"
21
+ "pluginApi": ">=2026.11.5-beta.1"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.10.10-beta.5",
24
+ "openclawVersion": "2026.11.5-beta.1",
25
25
  "bundledDist": false
26
26
  },
27
27
  "release": {
@@ -40,7 +40,7 @@
40
40
  "assets/activity.svg"
41
41
  ],
42
42
  "peerDependencies": {
43
- "openclaw": ">=2026.10.10-beta.5"
43
+ "openclaw": ">=2026.11.5-beta.1"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "openclaw": {
@@ -1,152 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import { readPluginPackageVersion } from "openclaw/plugin-sdk/extension-shared";
3
- import { decodeHtmlEntities } from "openclaw/plugin-sdk/html-entity-runtime";
4
- import { ProviderHttpError, readProviderTextResponse } from "openclaw/plugin-sdk/provider-http";
5
- import { DEFAULT_CACHE_TTL_MINUTES, DEFAULT_SEARCH_COUNT, normalizeCacheKey, readCache, readResponseText, resolveCacheTtlMs, resolveSearchCount, resolveSiteName, resolveTimeoutSeconds, withTrustedWebSearchEndpoint, wrapWebContent, writeCache } from "openclaw/plugin-sdk/provider-web-search";
6
- import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
- //#region extensions/duckduckgo/src/config.ts
8
- const DEFAULT_DDG_SAFE_SEARCH = "moderate";
9
- function resolveDdgWebSearchConfig(config) {
10
- return asOptionalRecord(config?.plugins?.entries?.duckduckgo?.config?.webSearch);
11
- }
12
- function resolveDdgRegion(config) {
13
- return normalizeOptionalString(resolveDdgWebSearchConfig(config)?.region);
14
- }
15
- function resolveDdgSafeSearch(config) {
16
- const safeSearch = resolveDdgWebSearchConfig(config)?.safeSearch;
17
- const normalized = normalizeLowercaseStringOrEmpty(safeSearch);
18
- if (normalized === "strict" || normalized === "off") return normalized;
19
- return DEFAULT_DDG_SAFE_SEARCH;
20
- }
21
- //#endregion
22
- //#region extensions/duckduckgo/src/ddg-client.ts
23
- const require = createRequire(import.meta.url);
24
- const DDG_USER_AGENT = `openclaw-duckduckgo/${readPluginPackageVersion({ require })} (+https://docs.openclaw.ai)`;
25
- const DDG_HTML_ENDPOINT = "https://html.duckduckgo.com/html";
26
- const DEFAULT_TIMEOUT_SECONDS = 20;
27
- const DDG_HTML_ENTITY_RE = /&(?:lt|gt|quot|apos|#39|#x27|#x2F|nbsp|ndash|mdash|hellip|amp|#\d+|#x[0-9a-f]+);/gi;
28
- const DDG_ENTITY_TEXT = {
29
- "\xA0": " ",
30
- "–": "-",
31
- "—": "--",
32
- "…": "..."
33
- };
34
- const DDG_SAFE_SEARCH_PARAM = {
35
- strict: "1",
36
- moderate: "-1",
37
- off: "-2"
38
- };
39
- const DDG_SEARCH_CACHE = /* @__PURE__ */ new Map();
40
- function decodeHtmlEntities$1(text) {
41
- return text.replace(DDG_HTML_ENTITY_RE, (entity) => {
42
- const decoded = decodeHtmlEntities(entity.startsWith("&#") ? entity : entity.toLowerCase());
43
- return DDG_ENTITY_TEXT[decoded] ?? decoded;
44
- });
45
- }
46
- function stripHtml(html) {
47
- return html.replace(/<\/?b\b[^>]*>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
48
- }
49
- function decodeDuckDuckGoUrl(rawUrl) {
50
- try {
51
- const normalized = rawUrl.startsWith("//") ? `https:${rawUrl}` : rawUrl;
52
- const uddg = new URL(normalized).searchParams.get("uddg");
53
- if (uddg) return uddg;
54
- } catch {}
55
- return rawUrl;
56
- }
57
- function isBotChallenge(html) {
58
- if (/class="[^"]*\bresult__a\b[^"]*"/i.test(html)) return false;
59
- return /g-recaptcha|are you a human|id="challenge-form"|name="challenge"/i.test(html);
60
- }
61
- function parseDuckDuckGoHtml(html, count) {
62
- const results = [];
63
- const resultRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__a\b[^"]*")([^>]*)>([\s\S]*?)<\/a>/gi;
64
- const nextResultRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__a\b[^"]*")[^>]*>/i;
65
- const snippetRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__snippet\b[^"]*")[^>]*>([\s\S]*?)<\/a>/i;
66
- for (const match of html.matchAll(resultRegex)) {
67
- const rawAttributes = match[1] ?? "";
68
- const rawTitle = match[2] ?? "";
69
- const rawUrl = /\bhref="([^"]*)"/i.exec(rawAttributes)?.[1] ?? "";
70
- const matchEnd = (match.index ?? 0) + match[0].length;
71
- const trailingHtml = html.slice(matchEnd);
72
- const nextResultIndex = trailingHtml.search(nextResultRegex);
73
- const scopedTrailingHtml = nextResultIndex >= 0 ? trailingHtml.slice(0, nextResultIndex) : trailingHtml;
74
- const rawSnippet = snippetRegex.exec(scopedTrailingHtml)?.[1] ?? "";
75
- const title = decodeHtmlEntities$1(stripHtml(rawTitle));
76
- const url = decodeDuckDuckGoUrl(decodeHtmlEntities$1(rawUrl));
77
- const snippet = decodeHtmlEntities$1(stripHtml(rawSnippet));
78
- if (title && url) {
79
- results.push({
80
- title,
81
- url,
82
- snippet
83
- });
84
- if (results.length >= count) break;
85
- }
86
- }
87
- return results;
88
- }
89
- async function runDuckDuckGoSearch(params) {
90
- const count = resolveSearchCount(params.count, DEFAULT_SEARCH_COUNT);
91
- const region = params.region ?? resolveDdgRegion(params.config);
92
- const safeSearch = params.safeSearch === "strict" || params.safeSearch === "moderate" || params.safeSearch === "off" ? params.safeSearch : resolveDdgSafeSearch(params.config);
93
- const timeoutSeconds = resolveTimeoutSeconds(params.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS);
94
- const cacheTtlMs = resolveCacheTtlMs(params.cacheTtlMinutes ?? params.config?.tools?.web?.search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES);
95
- const cacheKey = normalizeCacheKey(JSON.stringify({
96
- provider: "duckduckgo",
97
- query: params.query,
98
- count,
99
- region: region ?? "",
100
- safeSearch
101
- }));
102
- const cached = readCache(DDG_SEARCH_CACHE, cacheKey, cacheTtlMs);
103
- if (cached) return {
104
- ...cached.value,
105
- cached: true
106
- };
107
- const url = new URL(DDG_HTML_ENDPOINT);
108
- url.searchParams.set("q", params.query);
109
- if (region) url.searchParams.set("kl", region);
110
- url.searchParams.set("kp", DDG_SAFE_SEARCH_PARAM[safeSearch]);
111
- const startedAt = Date.now();
112
- const results = await withTrustedWebSearchEndpoint({
113
- url: url.toString(),
114
- timeoutSeconds,
115
- signal: params.signal,
116
- init: {
117
- method: "GET",
118
- headers: { "User-Agent": DDG_USER_AGENT }
119
- }
120
- }, async (response) => {
121
- if (!response.ok) {
122
- const detail = (await readResponseText(response, { maxBytes: 64e3 })).text;
123
- throw new ProviderHttpError(`DuckDuckGo search error (${response.status}): ${detail || response.statusText}`, { status: response.status });
124
- }
125
- const html = await readProviderTextResponse(response, "DuckDuckGo search");
126
- if (isBotChallenge(html)) throw new Error("DuckDuckGo returned a bot-detection challenge.");
127
- return parseDuckDuckGoHtml(html, count);
128
- });
129
- params.signal?.throwIfAborted();
130
- const payload = {
131
- query: params.query,
132
- provider: "duckduckgo",
133
- count: results.length,
134
- tookMs: Date.now() - startedAt,
135
- externalContent: {
136
- untrusted: true,
137
- source: "web_search",
138
- provider: "duckduckgo",
139
- wrapped: true
140
- },
141
- results: results.map((result) => ({
142
- title: wrapWebContent(result.title, "web_search"),
143
- url: result.url,
144
- snippet: result.snippet ? wrapWebContent(result.snippet, "web_search") : "",
145
- siteName: resolveSiteName(result.url) || void 0
146
- }))
147
- };
148
- writeCache(DDG_SEARCH_CACHE, cacheKey, payload, cacheTtlMs);
149
- return payload;
150
- }
151
- //#endregion
152
- export { runDuckDuckGoSearch };