@duckmind/dm-windows-x64 0.63.0 → 0.63.6

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 (101) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +200 -4
  3. package/extensions/dm-context/package.json +1 -1
  4. package/extensions/dm-context/skills/context-management/SKILL.md +173 -223
  5. package/extensions/dm-context/skills/context-management/references/development-and-troubleshooting.md +75 -0
  6. package/extensions/dm-context/skills/context-management/references/interleaved-async-work.md +143 -0
  7. package/extensions/dm-context/skills/context-management/references/planning-and-execution.md +81 -0
  8. package/extensions/dm-context/skills/context-management/references/repeated-items-and-batch-work.md +69 -0
  9. package/extensions/dm-context/skills/context-management/references/retry-branch-and-pivot.md +80 -0
  10. package/extensions/dm-context/skills/context-management/references/search-research-and-reading.md +103 -0
  11. package/extensions/dm-context/skills/context-management/references/task-switching-and-cleanup.md +73 -0
  12. package/extensions/dm-context/src/context.js +3 -2
  13. package/extensions/dm-context/src/index.js +168 -84
  14. package/extensions/dm-skills-manager/THIRD_PARTY_NOTICES.md +27 -0
  15. package/extensions/dm-skills-manager/extensions/skills-manager/components.js +265 -0
  16. package/extensions/dm-skills-manager/extensions/skills-manager/constants.js +31 -0
  17. package/extensions/dm-skills-manager/extensions/skills-manager/creation-fallback.js +20 -0
  18. package/extensions/dm-skills-manager/extensions/skills-manager/creation.js +145 -0
  19. package/extensions/dm-skills-manager/extensions/skills-manager/dialog.js +738 -0
  20. package/extensions/dm-skills-manager/extensions/skills-manager/dm-ai-compat.js +15 -0
  21. package/extensions/dm-skills-manager/extensions/skills-manager/format.js +125 -0
  22. package/extensions/dm-skills-manager/extensions/skills-manager/glyphs.js +142 -0
  23. package/extensions/dm-skills-manager/extensions/skills-manager/layout.js +93 -0
  24. package/extensions/dm-skills-manager/extensions/skills-manager/paths.js +76 -0
  25. package/extensions/dm-skills-manager/extensions/skills-manager/registry.js +95 -0
  26. package/extensions/dm-skills-manager/extensions/skills-manager/settings.js +93 -0
  27. package/extensions/dm-skills-manager/extensions/skills-manager/startup.js +32 -0
  28. package/extensions/dm-skills-manager/extensions/skills-manager/toggle.js +54 -0
  29. package/extensions/dm-skills-manager/extensions/skills-manager/types.js +14 -0
  30. package/extensions/dm-skills-manager/extensions/skills-manager/ui.js +121 -0
  31. package/extensions/dm-skills-manager/extensions/skills-manager.js +111 -0
  32. package/extensions/dm-skills-manager/package.json +121 -0
  33. package/extensions/dm-web-access/SECURITY.md +5 -0
  34. package/extensions/dm-web-access/activity.js +65 -0
  35. package/extensions/dm-web-access/anysearch.js +158 -0
  36. package/extensions/dm-web-access/auth-fetch.js +131 -0
  37. package/extensions/dm-web-access/bocha.js +214 -0
  38. package/extensions/dm-web-access/brave.js +196 -0
  39. package/extensions/dm-web-access/brightdata-unlocker.js +202 -0
  40. package/extensions/dm-web-access/brightdata.js +334 -0
  41. package/extensions/dm-web-access/chrome-cookies.js +627 -0
  42. package/extensions/dm-web-access/content-find.js +114 -0
  43. package/extensions/dm-web-access/credential-source.js +150 -0
  44. package/extensions/dm-web-access/curator-page.js +3559 -0
  45. package/extensions/dm-web-access/curator-server.js +691 -0
  46. package/extensions/dm-web-access/data-uri-sanitize.js +312 -0
  47. package/extensions/dm-web-access/datalab-pdf-extract.js +346 -0
  48. package/extensions/dm-web-access/declared-web-links.js +167 -0
  49. package/extensions/dm-web-access/dm-web-fetch-demo.mp4 +0 -0
  50. package/extensions/dm-web-access/duckduckgo.js +118 -0
  51. package/extensions/dm-web-access/exa.js +401 -0
  52. package/extensions/dm-web-access/extract.js +1220 -0
  53. package/extensions/dm-web-access/feature-config.js +24 -0
  54. package/extensions/dm-web-access/fetch-params.js +81 -0
  55. package/extensions/dm-web-access/firecrawl.js +378 -0
  56. package/extensions/dm-web-access/gemini-adc.js +241 -0
  57. package/extensions/dm-web-access/gemini-api.js +258 -0
  58. package/extensions/dm-web-access/gemini-pdf-extract.js +74 -0
  59. package/extensions/dm-web-access/gemini-search.js +889 -0
  60. package/extensions/dm-web-access/gemini-url-context.js +97 -0
  61. package/extensions/dm-web-access/gemini-web-config.js +84 -0
  62. package/extensions/dm-web-access/gemini-web.js +351 -0
  63. package/extensions/dm-web-access/github-api.js +166 -0
  64. package/extensions/dm-web-access/github-extract.js +991 -0
  65. package/extensions/dm-web-access/github-issue-pr.js +750 -0
  66. package/extensions/dm-web-access/index.js +3117 -0
  67. package/extensions/dm-web-access/jina-search.js +242 -0
  68. package/extensions/dm-web-access/kagi.js +255 -0
  69. package/extensions/dm-web-access/kimi-search.js +214 -0
  70. package/extensions/dm-web-access/ollama.js +209 -0
  71. package/extensions/dm-web-access/openai-search.js +510 -0
  72. package/extensions/dm-web-access/package.json +34 -0
  73. package/extensions/dm-web-access/page-query.js +124 -0
  74. package/extensions/dm-web-access/parallel-mcp.js +223 -0
  75. package/extensions/dm-web-access/parallel.js +345 -0
  76. package/extensions/dm-web-access/pdf-extract.js +257 -0
  77. package/extensions/dm-web-access/perplexity.js +151 -0
  78. package/extensions/dm-web-access/querit.js +327 -0
  79. package/extensions/dm-web-access/query-rewrite.js +40 -0
  80. package/extensions/dm-web-access/render-search-error.js +80 -0
  81. package/extensions/dm-web-access/rsc-extract.js +347 -0
  82. package/extensions/dm-web-access/search1api.js +245 -0
  83. package/extensions/dm-web-access/searchinfinity.js +221 -0
  84. package/extensions/dm-web-access/searxng.js +223 -0
  85. package/extensions/dm-web-access/serpbase.js +205 -0
  86. package/extensions/dm-web-access/serpdive.js +238 -0
  87. package/extensions/dm-web-access/serper.js +200 -0
  88. package/extensions/dm-web-access/source-check.js +198 -0
  89. package/extensions/dm-web-access/ssrf-protection.js +436 -0
  90. package/extensions/dm-web-access/storage.js +451 -0
  91. package/extensions/dm-web-access/summary-model-scope.js +83 -0
  92. package/extensions/dm-web-access/summary-review.js +364 -0
  93. package/extensions/dm-web-access/tavily.js +199 -0
  94. package/extensions/dm-web-access/tinyfish.js +325 -0
  95. package/extensions/dm-web-access/utils.js +476 -0
  96. package/extensions/dm-web-access/valyu.js +189 -0
  97. package/extensions/dm-web-access/video-extract.js +336 -0
  98. package/extensions/dm-web-access/xai-search.js +285 -0
  99. package/extensions/dm-web-access/xcrawl.js +221 -0
  100. package/extensions/dm-web-access/youtube-extract.js +279 -0
  101. package/package.json +9 -1
@@ -0,0 +1,202 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.js";
3
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
+ import { validateRemoteUrl } from "./ssrf-protection.js";
5
+ import { getWebSearchConfigPath } from "./utils.js";
6
+ const CONFIG_PATH = getWebSearchConfigPath();
7
+ const BRIGHTDATA_REQUEST_URL = "https://api.brightdata.com/request";
8
+ const EXTRACT_TIMEOUT_MS = 60000;
9
+ const DEFAULT_MAX_REDIRECTS = 5;
10
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
11
+ const ZONE_PATTERN = /^[a-z0-9_-]+$/i;
12
+ let cachedConfig = null;
13
+ function parseFailureDetail(err) {
14
+ const message = err instanceof Error ? err.message : String(err);
15
+ const position = message.match(/at position \d+(?: \(line \d+ column \d+\))?/);
16
+ return position ? `invalid JSON ${position[0]}` : "invalid JSON";
17
+ }
18
+ function loadConfig() {
19
+ if (cachedConfig)
20
+ return cachedConfig;
21
+ if (!existsSync(CONFIG_PATH)) {
22
+ cachedConfig = {};
23
+ return cachedConfig;
24
+ }
25
+ const raw = readFileSync(CONFIG_PATH, "utf8");
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(raw);
29
+ } catch (err) {
30
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${parseFailureDetail(err)}`);
31
+ }
32
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
33
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
34
+ }
35
+ cachedConfig = parsed;
36
+ return cachedConfig;
37
+ }
38
+ export function clearBrightDataUnlockerConfigCache() {
39
+ cachedConfig = null;
40
+ }
41
+ function normalizeZone(value) {
42
+ if (typeof value !== "string")
43
+ return null;
44
+ const trimmed = value.trim();
45
+ if (!trimmed)
46
+ return null;
47
+ return ZONE_PATTERN.test(trimmed) ? trimmed : null;
48
+ }
49
+ function zoneSetting() {
50
+ const fromEnv = process.env.BRIGHTDATA_UNLOCKER_ZONE;
51
+ if (typeof fromEnv === "string" && fromEnv.trim())
52
+ return { raw: fromEnv.trim(), label: "BRIGHTDATA_UNLOCKER_ZONE" };
53
+ const configured = loadConfig().brightdataUnlockerZone;
54
+ if (typeof configured === "string" && configured.trim())
55
+ return { raw: configured.trim(), label: `brightdataUnlockerZone in ${CONFIG_PATH}` };
56
+ return null;
57
+ }
58
+ function getZone() {
59
+ const setting = zoneSetting();
60
+ return setting ? normalizeZone(setting.raw) : null;
61
+ }
62
+ function requireZone() {
63
+ const setting = zoneSetting();
64
+ const zone = setting ? normalizeZone(setting.raw) : null;
65
+ if (zone)
66
+ return zone;
67
+ if (setting) {
68
+ throw new Error(`Invalid Bright Data Unlocker zone: ${setting.label} must be a zone name of letters, digits, "-", or "_". ` + 'The zone must be of type "unblocker"; a SERP zone will not serve Web Unlocker requests.');
69
+ }
70
+ throw new Error(`Bright Data Web Unlocker zone not configured. Either:
71
+ ` + ` 1. Set brightdataUnlockerZone in ${CONFIG_PATH}
72
+ ` + ` 2. Set BRIGHTDATA_UNLOCKER_ZONE environment variable
73
+ ` + 'The zone must be of type "unblocker"; a SERP zone will not serve Web Unlocker requests.');
74
+ }
75
+ async function getApiKey(signal) {
76
+ const apiKey = await resolveCredential({
77
+ provider: "Bright Data",
78
+ configuredValue: loadConfig().brightdataApiKey,
79
+ environmentValue: process.env.BRIGHTDATA_API_KEY,
80
+ signal
81
+ });
82
+ if (!apiKey) {
83
+ throw new Error(`Bright Data API key not found. Either:
84
+ ` + ` 1. Create ${CONFIG_PATH} with { "brightdataApiKey": "your-key" }
85
+ ` + ` 2. Set BRIGHTDATA_API_KEY environment variable
86
+ ` + "Get a key at https://brightdata.com/cp/setting/users");
87
+ }
88
+ return apiKey;
89
+ }
90
+ function requestSignal(timeoutMs, signal) {
91
+ const timeout = AbortSignal.timeout(timeoutMs);
92
+ return signal ? AbortSignal.any([timeout, signal]) : timeout;
93
+ }
94
+ function errorMessage(err) {
95
+ return err instanceof Error ? err.message : String(err);
96
+ }
97
+ function isAbortError(err) {
98
+ return errorMessage(err).toLowerCase().includes("abort");
99
+ }
100
+ function ssrfOptions(options) {
101
+ return {
102
+ allowRanges: options?.ssrf?.allowRanges ?? [],
103
+ trustEnvProxy: options?.ssrf?.trustEnvProxy ?? false,
104
+ ...options?.lookup ? { lookup: options.lookup } : {}
105
+ };
106
+ }
107
+ function withoutSensitiveHeaders(headers) {
108
+ const next = { ...headers };
109
+ delete next.Authorization;
110
+ delete next.authorization;
111
+ delete next.Cookie;
112
+ delete next.cookie;
113
+ delete next["X-API-Key"];
114
+ delete next["x-api-key"];
115
+ return next;
116
+ }
117
+ async function fetchBrightDataApi(url, init, options) {
118
+ let current = await validateRemoteUrl(url, ssrfOptions(options));
119
+ let headers = init.headers;
120
+ for (let redirects = 0;; redirects++) {
121
+ const response = await fetch(current, { ...init, headers, redirect: "manual" });
122
+ if (!REDIRECT_STATUSES.has(response.status))
123
+ return response;
124
+ const location = response.headers.get("location");
125
+ if (!location)
126
+ return response;
127
+ if (redirects === DEFAULT_MAX_REDIRECTS)
128
+ throw new Error(`Too many redirects fetching ${current.toString()}`);
129
+ const next = await validateRemoteUrl(new URL(location, current), ssrfOptions(options));
130
+ if (next.origin !== current.origin)
131
+ headers = withoutSensitiveHeaders(headers);
132
+ current = next;
133
+ }
134
+ }
135
+ function unlockerBody(url, zone) {
136
+ return {
137
+ url,
138
+ zone,
139
+ format: "raw",
140
+ data_format: "markdown"
141
+ };
142
+ }
143
+ async function brightDataRequest(url, zone, signal, options) {
144
+ const apiKey = await getApiKey(signal);
145
+ const headers = {
146
+ "Content-Type": "application/json",
147
+ Authorization: `Bearer ${apiKey}`
148
+ };
149
+ const activityId = activityMonitor.logStart({ type: "fetch", url });
150
+ try {
151
+ const response = await fetchBrightDataApi(BRIGHTDATA_REQUEST_URL, {
152
+ method: "POST",
153
+ headers,
154
+ body: JSON.stringify(unlockerBody(url, zone)),
155
+ signal: requestSignal(options?.timeoutMs ?? EXTRACT_TIMEOUT_MS, signal)
156
+ }, options);
157
+ if (!response.ok) {
158
+ const errorText = await response.text().catch(() => "");
159
+ throw new Error(`Bright Data Web Unlocker error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);
160
+ }
161
+ const text = await response.text();
162
+ activityMonitor.logComplete(activityId, response.status);
163
+ return text;
164
+ } catch (err) {
165
+ const message = errorMessage(err);
166
+ const redactedMessage = redactCredential(message, apiKey);
167
+ if (isAbortError(err))
168
+ activityMonitor.logComplete(activityId, 0);
169
+ else
170
+ activityMonitor.logError(activityId, redactedMessage);
171
+ if (redactedMessage === message)
172
+ throw err;
173
+ const redactedError = new Error(redactedMessage);
174
+ if (err instanceof Error)
175
+ redactedError.name = err.name;
176
+ throw redactedError;
177
+ }
178
+ }
179
+ function headingTitle(text) {
180
+ const match = text.match(/^#{1,2}\s+(.+)/m);
181
+ if (!match)
182
+ return "";
183
+ return match[1].replace(/\*+/g, "").trim();
184
+ }
185
+ export function isBrightDataUnlockerAvailable() {
186
+ if (getZone() === null)
187
+ return false;
188
+ return hasCredentialSource({
189
+ provider: "Bright Data",
190
+ configuredValue: loadConfig().brightdataApiKey,
191
+ environmentValue: process.env.BRIGHTDATA_API_KEY
192
+ });
193
+ }
194
+ export async function extractWithBrightDataUnlocker(url, signal, options) {
195
+ const zone = requireZone();
196
+ await validateRemoteUrl(url, ssrfOptions(options));
197
+ const raw = await brightDataRequest(url, zone, signal, options);
198
+ const content = raw.trim();
199
+ if (!content)
200
+ return null;
201
+ return { url, title: headingTitle(content), content, error: null };
202
+ }
@@ -0,0 +1,334 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.js";
3
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
+ import { getWebSearchConfigPath } from "./utils.js";
5
+ const BRIGHTDATA_API_URL = "https://api.brightdata.com/request";
6
+ const CONFIG_PATH = getWebSearchConfigPath();
7
+ const SEARCH_TIMEOUT_MS = 60000;
8
+ const ZONE_PATTERN = /^[A-Za-z0-9_-]+$/;
9
+ const RECENCY_TBS = {
10
+ day: "qdr:d",
11
+ week: "qdr:w",
12
+ month: "qdr:m",
13
+ year: "qdr:y"
14
+ };
15
+ let cachedConfig = null;
16
+ function configParseDetail(err) {
17
+ const position = errorMessage(err).match(/at position \d+(?: \(line \d+ column \d+\))?/i);
18
+ return position ? `not valid JSON, ${position[0]}` : "not valid JSON";
19
+ }
20
+ function loadConfig() {
21
+ if (cachedConfig)
22
+ return cachedConfig;
23
+ if (!existsSync(CONFIG_PATH)) {
24
+ cachedConfig = {};
25
+ return cachedConfig;
26
+ }
27
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(raw);
31
+ } catch (err) {
32
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${configParseDetail(err)}`);
33
+ }
34
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
35
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
36
+ }
37
+ cachedConfig = parsed;
38
+ return cachedConfig;
39
+ }
40
+ async function getApiKey(signal) {
41
+ return resolveCredential({
42
+ provider: "Bright Data",
43
+ configuredValue: loadConfig().brightdataApiKey,
44
+ environmentValue: process.env.BRIGHTDATA_API_KEY,
45
+ signal
46
+ });
47
+ }
48
+ async function requireApiKey(signal) {
49
+ const apiKey = await getApiKey(signal);
50
+ if (!apiKey) {
51
+ throw new Error(`Bright Data API key not found. Either:
52
+ ` + ` 1. Create ${CONFIG_PATH} with { "brightdataApiKey": "your-key" }
53
+ ` + ` 2. Set BRIGHTDATA_API_KEY environment variable
54
+ ` + "Get a key at https://brightdata.com/cp/setting/users");
55
+ }
56
+ return apiKey;
57
+ }
58
+ function normalizeZone(value) {
59
+ if (typeof value !== "string")
60
+ return null;
61
+ const trimmed = value.trim();
62
+ if (!trimmed)
63
+ return null;
64
+ return ZONE_PATTERN.test(trimmed) ? trimmed : null;
65
+ }
66
+ function serpZoneSetting() {
67
+ const fromEnv = process.env.BRIGHTDATA_SERP_ZONE;
68
+ if (typeof fromEnv === "string" && fromEnv.trim()) {
69
+ return { raw: fromEnv.trim(), label: "BRIGHTDATA_SERP_ZONE" };
70
+ }
71
+ const configured = loadConfig().brightdataSerpZone;
72
+ if (typeof configured === "string" && configured.trim()) {
73
+ return { raw: configured.trim(), label: `brightdataSerpZone in ${CONFIG_PATH}` };
74
+ }
75
+ return null;
76
+ }
77
+ function getSerpZone() {
78
+ const setting = serpZoneSetting();
79
+ return setting ? normalizeZone(setting.raw) : null;
80
+ }
81
+ function requireSerpZone() {
82
+ const setting = serpZoneSetting();
83
+ const zone = setting ? normalizeZone(setting.raw) : null;
84
+ if (zone)
85
+ return zone;
86
+ if (setting) {
87
+ throw new Error(`Bright Data SERP zone is invalid: ${setting.label} must be a zone name of letters, digits, "-", or "_" ` + `(got "${untrustedText(setting.raw, 60)}").
88
+ ` + "The zone must be of Bright Data type `serp`; a Web Unlocker zone (type `unblocker`) is a different product and does not return SERP JSON.\n" + "Create or rename one at https://brightdata.com/cp/zones");
89
+ }
90
+ throw new Error(`Bright Data SERP zone is invalid or missing. Either:
91
+ ` + ` 1. Create ${CONFIG_PATH} with { "brightdataSerpZone": "your_serp_zone" }
92
+ ` + ` 2. Set BRIGHTDATA_SERP_ZONE environment variable
93
+ ` + "The zone must be of Bright Data type `serp`; a Web Unlocker zone is a different product and does not return SERP JSON.\n" + "Create one at https://brightdata.com/cp/zones");
94
+ }
95
+ function normalizeCount(value) {
96
+ if (typeof value !== "number" || !Number.isFinite(value))
97
+ return 5;
98
+ return Math.max(1, Math.min(Math.floor(value), 20));
99
+ }
100
+ function normalizeDomain(value) {
101
+ let input = value.trim().toLowerCase();
102
+ if (!input)
103
+ return null;
104
+ if (input.startsWith("-"))
105
+ input = input.slice(1).trim();
106
+ if (!input)
107
+ return null;
108
+ try {
109
+ const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
110
+ input = parsed.hostname;
111
+ } catch {
112
+ input = input.split("/")[0]?.split(":")[0] ?? "";
113
+ }
114
+ input = input.replace(/^\.+|\.+$/g, "");
115
+ return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
116
+ }
117
+ function parseDomainFilter(domainFilter) {
118
+ const filters = { include: [], exclude: [] };
119
+ if (!domainFilter?.length)
120
+ return filters;
121
+ for (const raw of domainFilter) {
122
+ const domain = normalizeDomain(raw);
123
+ if (!domain)
124
+ continue;
125
+ const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
126
+ if (!target.includes(domain))
127
+ target.push(domain);
128
+ }
129
+ return filters;
130
+ }
131
+ function buildSearchQuery(query, filters) {
132
+ const parts = [query];
133
+ if (filters.include.length === 1) {
134
+ parts.push(`site:${filters.include[0]}`);
135
+ } else if (filters.include.length > 1) {
136
+ parts.push(filters.include.map((domain) => `site:${domain}`).join(" OR "));
137
+ }
138
+ for (const domain of filters.exclude) {
139
+ parts.push(`-site:${domain}`);
140
+ }
141
+ return parts.join(" ");
142
+ }
143
+ function buildSerpUrl(searchQuery, numResults, recencyFilter) {
144
+ const params = new URLSearchParams({ q: searchQuery });
145
+ params.set("num", String(Math.min(numResults + 5, 20)));
146
+ const tbs = recencyFilter ? RECENCY_TBS[recencyFilter] : undefined;
147
+ if (tbs)
148
+ params.set("tbs", tbs);
149
+ params.set("brd_json", "1");
150
+ return `https://www.google.com/search?${params.toString()}`;
151
+ }
152
+ function domainMatches(hostname, domain) {
153
+ return hostname === domain || hostname.endsWith(`.${domain}`);
154
+ }
155
+ function passesDomainFilters(url, filters) {
156
+ if (filters.include.length === 0 && filters.exclude.length === 0)
157
+ return true;
158
+ let hostname;
159
+ try {
160
+ hostname = new URL(url).hostname.toLowerCase();
161
+ } catch {
162
+ return false;
163
+ }
164
+ if (filters.exclude.some((domain) => domainMatches(hostname, domain)))
165
+ return false;
166
+ if (filters.include.length === 0)
167
+ return true;
168
+ return filters.include.some((domain) => domainMatches(hostname, domain));
169
+ }
170
+ function requestSignal(signal) {
171
+ const timeout = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
172
+ return signal ? AbortSignal.any([signal, timeout]) : timeout;
173
+ }
174
+ function errorMessage(err) {
175
+ return err instanceof Error ? err.message : String(err);
176
+ }
177
+ const STATUS_SHAPED_PATTERN = /\b(?:error|status|http)[\s:=-]{1,4}(\d{3})\b/gi;
178
+ const QUOTA_SHAPED_PATTERN = /rate limit|quota|too many requests/gi;
179
+ function untrustedText(text, limit = 300) {
180
+ return text.replace(/\s+/g, " ").trim().slice(0, limit).replace(STATUS_SHAPED_PATTERN, "upstream $1").replace(QUOTA_SHAPED_PATTERN, "upstream rate-limit notice");
181
+ }
182
+ function invalidResponse(zone, message) {
183
+ return new Error(`Bright Data API returned invalid response for zone ${zone}: ${message}`);
184
+ }
185
+ function envelopeError(envelope, apiKey) {
186
+ const parts = [];
187
+ const { error, errors } = envelope;
188
+ if (typeof error === "string" && error.trim())
189
+ parts.push(error.trim());
190
+ else if (error && typeof error === "object")
191
+ parts.push(JSON.stringify(error));
192
+ if (Array.isArray(errors) && errors.length > 0)
193
+ parts.push(JSON.stringify(errors));
194
+ else if (typeof errors === "string" && errors.trim())
195
+ parts.push(errors.trim());
196
+ if (parts.length === 0)
197
+ return null;
198
+ for (const key of ["code", "error_code"]) {
199
+ const code = envelope[key];
200
+ if (typeof code === "string" && code.trim())
201
+ parts.push(`${key} ${code.trim()}`);
202
+ else if (typeof code === "number")
203
+ parts.push(`${key} ${code}`);
204
+ }
205
+ return untrustedText(redactCredential(parts.join(", "), apiKey), 200);
206
+ }
207
+ function parseSerpResponse(value, zone, apiKey) {
208
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
209
+ throw invalidResponse(zone, "expected an object envelope");
210
+ }
211
+ const envelope = value;
212
+ const upstreamError = envelopeError(envelope, apiKey);
213
+ if (upstreamError) {
214
+ throw invalidResponse(zone, `Bright Data reported an error instead of a SERP: ${upstreamError}`);
215
+ }
216
+ if (envelope.organic === undefined || envelope.organic === null) {
217
+ throw invalidResponse(zone, "expected an organic array and the envelope carried none. A `serp` zone queried with brd_json=1 " + "returns { organic: [...] }; a zone of type `unblocker`, or a missing brd_json=1, is the usual cause");
218
+ }
219
+ if (!Array.isArray(envelope.organic))
220
+ throw invalidResponse(zone, "expected organic array");
221
+ const organic = [];
222
+ for (const [index, entry] of envelope.organic.entries()) {
223
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
224
+ throw invalidResponse(zone, `expected organic[${index}] object`);
225
+ }
226
+ organic.push(entry);
227
+ }
228
+ return { organic };
229
+ }
230
+ function mapResults(organic, numResults, filters) {
231
+ if (!Array.isArray(organic))
232
+ return [];
233
+ const mapped = [];
234
+ for (const item of organic) {
235
+ const url = typeof item.link === "string" ? item.link.trim() : "";
236
+ if (!url || !passesDomainFilters(url, filters))
237
+ continue;
238
+ const title = typeof item.title === "string" ? item.title.trim() : "";
239
+ mapped.push({
240
+ title: title || `Source ${mapped.length + 1}`,
241
+ url,
242
+ snippet: typeof item.description === "string" ? item.description.replace(/\s+/g, " ").trim() : ""
243
+ });
244
+ if (mapped.length >= numResults)
245
+ break;
246
+ }
247
+ return mapped;
248
+ }
249
+ function buildAnswer(results) {
250
+ return results.map((result) => result.snippet ? `${result.snippet}
251
+ Source: ${result.title} (${result.url})` : `Source: ${result.title} (${result.url})`).join(`
252
+
253
+ `);
254
+ }
255
+ export function isBrightDataAvailable() {
256
+ try {
257
+ if (getSerpZone() === null)
258
+ return false;
259
+ return hasCredentialSource({
260
+ provider: "Bright Data",
261
+ configuredValue: loadConfig().brightdataApiKey,
262
+ environmentValue: process.env.BRIGHTDATA_API_KEY
263
+ });
264
+ } catch {
265
+ return false;
266
+ }
267
+ }
268
+ export async function searchWithBrightData(query, options = {}) {
269
+ const zone = requireSerpZone();
270
+ const apiKey = await requireApiKey(options.signal);
271
+ const numResults = normalizeCount(options.numResults);
272
+ const filters = parseDomainFilter(options.domainFilter);
273
+ const searchQuery = buildSearchQuery(query, filters);
274
+ const body = {
275
+ url: buildSerpUrl(searchQuery, numResults, options.recencyFilter),
276
+ zone,
277
+ format: "raw",
278
+ data_format: "parsed_light"
279
+ };
280
+ const activityId = activityMonitor.logStart({ type: "api", query: searchQuery });
281
+ let response;
282
+ try {
283
+ response = await fetch(BRIGHTDATA_API_URL, {
284
+ method: "POST",
285
+ headers: {
286
+ Authorization: `Bearer ${apiKey}`,
287
+ "Content-Type": "application/json"
288
+ },
289
+ body: JSON.stringify(body),
290
+ signal: requestSignal(options.signal)
291
+ });
292
+ } catch (err) {
293
+ const message = errorMessage(err);
294
+ const redactedMessage = redactCredential(message, apiKey);
295
+ if (redactedMessage.toLowerCase().includes("abort"))
296
+ activityMonitor.logComplete(activityId, 0);
297
+ else
298
+ activityMonitor.logError(activityId, redactedMessage);
299
+ if (redactedMessage === message)
300
+ throw err;
301
+ const redactedError = new Error(redactedMessage);
302
+ if (err instanceof Error)
303
+ redactedError.name = err.name;
304
+ throw redactedError;
305
+ }
306
+ if (!response.ok) {
307
+ activityMonitor.logComplete(activityId, response.status);
308
+ const errorText = untrustedText(redactCredential(await response.text(), apiKey));
309
+ throw new Error(`Bright Data API error ${response.status} for zone ${zone}: ${errorText}`);
310
+ }
311
+ const raw = await response.text();
312
+ if (!raw.trim()) {
313
+ activityMonitor.logComplete(activityId, response.status);
314
+ throw new Error(`Bright Data API returned empty response for zone ${zone}`);
315
+ }
316
+ let rawData;
317
+ try {
318
+ rawData = JSON.parse(raw);
319
+ } catch (err) {
320
+ activityMonitor.logComplete(activityId, response.status);
321
+ const body = untrustedText(redactCredential(raw, apiKey));
322
+ throw new Error(`Bright Data API returned invalid JSON for zone ${zone}: ${body}`);
323
+ }
324
+ let data;
325
+ try {
326
+ data = parseSerpResponse(rawData, zone, apiKey);
327
+ } catch (err) {
328
+ activityMonitor.logComplete(activityId, response.status);
329
+ throw err;
330
+ }
331
+ activityMonitor.logComplete(activityId, response.status);
332
+ const results = mapResults(data.organic, numResults, filters);
333
+ return { answer: buildAnswer(results), results };
334
+ }