@duckmind/dm-windows-x64 0.63.0 → 0.63.4

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 (71) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +138 -2
  3. package/extensions/dm-web-access/SECURITY.md +5 -0
  4. package/extensions/dm-web-access/activity.js +65 -0
  5. package/extensions/dm-web-access/anysearch.js +158 -0
  6. package/extensions/dm-web-access/auth-fetch.js +131 -0
  7. package/extensions/dm-web-access/bocha.js +214 -0
  8. package/extensions/dm-web-access/brave.js +196 -0
  9. package/extensions/dm-web-access/brightdata-unlocker.js +202 -0
  10. package/extensions/dm-web-access/brightdata.js +334 -0
  11. package/extensions/dm-web-access/chrome-cookies.js +627 -0
  12. package/extensions/dm-web-access/content-find.js +114 -0
  13. package/extensions/dm-web-access/credential-source.js +150 -0
  14. package/extensions/dm-web-access/curator-page.js +3559 -0
  15. package/extensions/dm-web-access/curator-server.js +691 -0
  16. package/extensions/dm-web-access/data-uri-sanitize.js +312 -0
  17. package/extensions/dm-web-access/datalab-pdf-extract.js +346 -0
  18. package/extensions/dm-web-access/declared-web-links.js +167 -0
  19. package/extensions/dm-web-access/dm-web-fetch-demo.mp4 +0 -0
  20. package/extensions/dm-web-access/duckduckgo.js +118 -0
  21. package/extensions/dm-web-access/exa.js +401 -0
  22. package/extensions/dm-web-access/extract.js +1220 -0
  23. package/extensions/dm-web-access/feature-config.js +24 -0
  24. package/extensions/dm-web-access/fetch-params.js +81 -0
  25. package/extensions/dm-web-access/firecrawl.js +378 -0
  26. package/extensions/dm-web-access/gemini-adc.js +241 -0
  27. package/extensions/dm-web-access/gemini-api.js +258 -0
  28. package/extensions/dm-web-access/gemini-pdf-extract.js +74 -0
  29. package/extensions/dm-web-access/gemini-search.js +889 -0
  30. package/extensions/dm-web-access/gemini-url-context.js +97 -0
  31. package/extensions/dm-web-access/gemini-web-config.js +84 -0
  32. package/extensions/dm-web-access/gemini-web.js +351 -0
  33. package/extensions/dm-web-access/github-api.js +166 -0
  34. package/extensions/dm-web-access/github-extract.js +991 -0
  35. package/extensions/dm-web-access/github-issue-pr.js +750 -0
  36. package/extensions/dm-web-access/index.js +3117 -0
  37. package/extensions/dm-web-access/jina-search.js +242 -0
  38. package/extensions/dm-web-access/kagi.js +255 -0
  39. package/extensions/dm-web-access/kimi-search.js +214 -0
  40. package/extensions/dm-web-access/ollama.js +209 -0
  41. package/extensions/dm-web-access/openai-search.js +510 -0
  42. package/extensions/dm-web-access/package.json +34 -0
  43. package/extensions/dm-web-access/page-query.js +124 -0
  44. package/extensions/dm-web-access/parallel-mcp.js +223 -0
  45. package/extensions/dm-web-access/parallel.js +345 -0
  46. package/extensions/dm-web-access/pdf-extract.js +257 -0
  47. package/extensions/dm-web-access/perplexity.js +151 -0
  48. package/extensions/dm-web-access/querit.js +327 -0
  49. package/extensions/dm-web-access/query-rewrite.js +40 -0
  50. package/extensions/dm-web-access/render-search-error.js +80 -0
  51. package/extensions/dm-web-access/rsc-extract.js +347 -0
  52. package/extensions/dm-web-access/search1api.js +245 -0
  53. package/extensions/dm-web-access/searchinfinity.js +221 -0
  54. package/extensions/dm-web-access/searxng.js +223 -0
  55. package/extensions/dm-web-access/serpbase.js +205 -0
  56. package/extensions/dm-web-access/serpdive.js +238 -0
  57. package/extensions/dm-web-access/serper.js +200 -0
  58. package/extensions/dm-web-access/source-check.js +198 -0
  59. package/extensions/dm-web-access/ssrf-protection.js +436 -0
  60. package/extensions/dm-web-access/storage.js +451 -0
  61. package/extensions/dm-web-access/summary-model-scope.js +83 -0
  62. package/extensions/dm-web-access/summary-review.js +364 -0
  63. package/extensions/dm-web-access/tavily.js +199 -0
  64. package/extensions/dm-web-access/tinyfish.js +325 -0
  65. package/extensions/dm-web-access/utils.js +476 -0
  66. package/extensions/dm-web-access/valyu.js +189 -0
  67. package/extensions/dm-web-access/video-extract.js +336 -0
  68. package/extensions/dm-web-access/xai-search.js +285 -0
  69. package/extensions/dm-web-access/xcrawl.js +221 -0
  70. package/extensions/dm-web-access/youtube-extract.js +279 -0
  71. package/package.json +9 -1
@@ -0,0 +1,889 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.js";
3
+ import { CredentialResolutionError } from "./credential-source.js";
4
+ import { getApiKey, getVersionedApiBase, fetchGeminiApi, isGatewayConfigured, isGeminiApiAvailable, redactGeminiApiResponse } from "./gemini-api.js";
5
+ import { isGeminiAdcAvailable } from "./gemini-adc.js";
6
+ import { getGeminiWebAvailabilityDiagnostic, isGeminiWebAvailable, queryWithCookies } from "./gemini-web.js";
7
+ import { isPerplexityAvailable, searchWithPerplexity } from "./perplexity.js";
8
+ import { isExaAvailable, searchWithExa } from "./exa.js";
9
+ import { isBraveAvailable, searchWithBrave } from "./brave.js";
10
+ import {
11
+ isCurrentModelHostedSearchEligible,
12
+ isOpenAISearchAvailable,
13
+ searchWithCurrentModelOpenAI,
14
+ searchWithOpenAI
15
+ } from "./openai-search.js";
16
+ import { isParallelAvailable, searchWithParallel } from "./parallel.js";
17
+ import { isParallelMcpAvailable, searchWithParallelMcp } from "./parallel-mcp.js";
18
+ import { isTinyFishAvailable, searchWithTinyFish } from "./tinyfish.js";
19
+ import { isSearch1APIAvailable, searchWithSearch1API } from "./search1api.js";
20
+ import { isSearchinfinityAvailable, searchWithSearchinfinity } from "./searchinfinity.js";
21
+ import { isQueritAvailable, searchWithQuerit } from "./querit.js";
22
+ import { isTavilyAvailable, searchWithTavily } from "./tavily.js";
23
+ import { isFirecrawlAvailable, searchWithFirecrawl } from "./firecrawl.js";
24
+ import { isJinaSearchAvailable, searchWithJina } from "./jina-search.js";
25
+ import { isSerpdiveAvailable, searchWithSerpdive } from "./serpdive.js";
26
+ import { isKagiAvailable, searchWithKagi } from "./kagi.js";
27
+ import { isBochaAvailable, searchWithBocha } from "./bocha.js";
28
+ import { isOllamaAvailable, searchWithOllama } from "./ollama.js";
29
+ import { isSearXNGAvailable, searchWithSearXNG } from "./searxng.js";
30
+ import { isDuckDuckGoAvailable, searchWithDuckDuckGo } from "./duckduckgo.js";
31
+ import { isAnySearchAvailable, searchWithAnySearch } from "./anysearch.js";
32
+ import { isXcrawlAvailable, searchWithXCrawl } from "./xcrawl.js";
33
+ import { isXaiSearchAvailable, searchWithXai } from "./xai-search.js";
34
+ import { isBrightDataAvailable, searchWithBrightData } from "./brightdata.js";
35
+ import { isSerpBaseAvailable, searchWithSerpBase } from "./serpbase.js";
36
+ import { isSerperAvailable, searchWithSerper } from "./serper.js";
37
+ import { isValyuAvailable, searchWithValyu } from "./valyu.js";
38
+ import { isKimiSearchAvailable, searchWithKimi } from "./kimi-search.js";
39
+ import { getWebSearchConfigPath } from "./utils.js";
40
+ export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "serper", "valyu", "bocha", "xcrawl"];
41
+ export const SEARCH_PROVIDERS = ["auto", "all", ...RESOLVED_SEARCH_PROVIDERS];
42
+
43
+ export class SearchProviderError extends Error {
44
+ provider;
45
+ kind;
46
+ status;
47
+ causeError;
48
+ constructor(provider, kind, message, status, cause) {
49
+ super(`${provider} search failed (${kind}): ${message}`);
50
+ this.name = "SearchProviderError";
51
+ this.provider = provider;
52
+ this.kind = kind;
53
+ this.status = status;
54
+ this.causeError = cause;
55
+ }
56
+ }
57
+ const CONFIG_PATH = getWebSearchConfigPath();
58
+ const DEFAULT_SEARCH_MODEL = "gemini-3.6-flash";
59
+ export const ALL_SEARCH_PROVIDERS = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
60
+ const VALID_ROUTING_KINDS = ["transient", "quota", "network", "invalid-response", "unsupported"];
61
+ let cachedSearchConfig = null;
62
+ function getSearchConfig() {
63
+ if (cachedSearchConfig)
64
+ return cachedSearchConfig;
65
+ if (!existsSync(CONFIG_PATH)) {
66
+ cachedSearchConfig = { searchProvider: "auto", searchProviderConfigured: false };
67
+ return cachedSearchConfig;
68
+ }
69
+ const rawText = readFileSync(CONFIG_PATH, "utf-8");
70
+ let raw;
71
+ try {
72
+ const parsed = JSON.parse(rawText);
73
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
74
+ throw new Error("expected a JSON object");
75
+ }
76
+ raw = parsed;
77
+ } catch (err) {
78
+ const message = err instanceof Error ? err.message : String(err);
79
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
80
+ }
81
+ const searchModel = normalizeSearchModel(raw.searchModel);
82
+ const searchProviderConfigured = Object.hasOwn(raw, "searchProvider") || Object.hasOwn(raw, "provider");
83
+ cachedSearchConfig = {
84
+ searchProvider: normalizeSearchProviderSelection(raw.searchProvider ?? raw.provider, `provider in ${CONFIG_PATH}`),
85
+ searchProviderConfigured,
86
+ ...Object.hasOwn(raw, "searchRouting") ? { searchRouting: normalizeSearchRouting(raw.searchRouting) } : {},
87
+ ...searchModel ? { searchModel } : {}
88
+ };
89
+ return cachedSearchConfig;
90
+ }
91
+ function normalizeSearchRouting(value) {
92
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
93
+ throw new Error(`searchRouting in ${CONFIG_PATH} must be an object`);
94
+ }
95
+ const raw = value;
96
+ const providers = normalizeResolvedProviderList(raw.providers, `searchRouting.providers in ${CONFIG_PATH}`);
97
+ const useCurrentModel = raw.useCurrentModel;
98
+ if (useCurrentModel !== undefined && typeof useCurrentModel !== "boolean") {
99
+ throw new Error(`searchRouting.useCurrentModel in ${CONFIG_PATH} must be a boolean`);
100
+ }
101
+ if (!Array.isArray(raw.fallbackOn) || raw.fallbackOn.length === 0) {
102
+ throw new Error(`searchRouting.fallbackOn in ${CONFIG_PATH} must be a non-empty array`);
103
+ }
104
+ const fallbackOn = [];
105
+ for (const kind of raw.fallbackOn) {
106
+ if (typeof kind !== "string" || !VALID_ROUTING_KINDS.includes(kind)) {
107
+ throw new Error(`searchRouting.fallbackOn in ${CONFIG_PATH} may only contain transient, quota, network, invalid-response, or unsupported`);
108
+ }
109
+ if (!fallbackOn.includes(kind)) {
110
+ fallbackOn.push(kind);
111
+ }
112
+ }
113
+ return {
114
+ providers,
115
+ ...useCurrentModel !== undefined ? { useCurrentModel } : {},
116
+ fallbackOn
117
+ };
118
+ }
119
+ export function getConfiguredSearchRouting() {
120
+ const config = getSearchConfig();
121
+ return config.searchProviderConfigured ? undefined : config.searchRouting;
122
+ }
123
+ function normalizeSearchModel(value) {
124
+ if (typeof value !== "string")
125
+ return;
126
+ const normalized = value.trim();
127
+ return normalized.length > 0 ? normalized : undefined;
128
+ }
129
+ function normalizeResolvedProviderList(value, label) {
130
+ if (!Array.isArray(value) || value.length === 0) {
131
+ throw new Error(`${label} must be a non-empty array`);
132
+ }
133
+ const providers = [];
134
+ for (const provider of value) {
135
+ const normalized = typeof provider === "string" ? provider.trim().toLowerCase() : "";
136
+ if (!RESOLVED_SEARCH_PROVIDERS.includes(normalized)) {
137
+ throw new Error(`${label} contains an invalid provider: ${String(provider)}`);
138
+ }
139
+ if (providers.includes(normalized)) {
140
+ throw new Error(`${label} must not contain duplicates: ${normalized}`);
141
+ }
142
+ providers.push(normalized);
143
+ }
144
+ return providers;
145
+ }
146
+ export function normalizeSearchProviderSelection(value, label = "provider") {
147
+ if (Array.isArray(value))
148
+ return normalizeResolvedProviderList(value, label);
149
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
150
+ return SEARCH_PROVIDERS.includes(normalized) ? normalized : "auto";
151
+ }
152
+ function errorMessage(err) {
153
+ return err instanceof Error ? err.message : String(err);
154
+ }
155
+ function isAbortError(err) {
156
+ return errorMessage(err).toLowerCase().includes("abort");
157
+ }
158
+ function shouldTryOpenAIInAuto(options) {
159
+ if (options.recencyFilter)
160
+ return false;
161
+ if (typeof options.numResults === "number" && Number.isFinite(options.numResults) && Math.floor(options.numResults) !== 5) {
162
+ return false;
163
+ }
164
+ return true;
165
+ }
166
+ function isOpenAICodexSelected(ctx) {
167
+ return ctx?.model?.provider === "openai-codex";
168
+ }
169
+ async function tryOpenAIInAuto(query, options, fallbackErrors) {
170
+ if (!shouldTryOpenAIInAuto(options))
171
+ return null;
172
+ try {
173
+ if (await isOpenAISearchAvailable(options.extensionContext)) {
174
+ const result = await searchWithOpenAI(query, options, options.extensionContext);
175
+ return { ...result, provider: "openai" };
176
+ }
177
+ } catch (err) {
178
+ if (isAbortError(err))
179
+ throw err;
180
+ fallbackErrors.push(`OpenAI: ${errorMessage(err)}`);
181
+ }
182
+ return null;
183
+ }
184
+ async function searchWithGemini(query, options, strictErrors) {
185
+ const errors = [];
186
+ try {
187
+ const apiResult = await searchWithGeminiApi(query, options);
188
+ if (apiResult)
189
+ return apiResult;
190
+ } catch (err) {
191
+ if (err instanceof CredentialResolutionError || isAbortError(err))
192
+ throw err;
193
+ errors.push(`Gemini API: ${errorMessage(err)}`);
194
+ }
195
+ try {
196
+ const webResult = await searchWithGeminiWeb(query, options);
197
+ if (webResult)
198
+ return webResult;
199
+ const diagnostic = getGeminiWebAvailabilityDiagnostic();
200
+ if (diagnostic)
201
+ errors.push(`Gemini Web: ${diagnostic}`);
202
+ } catch (err) {
203
+ if (isAbortError(err))
204
+ throw err;
205
+ errors.push(`Gemini Web: ${errorMessage(err)}`);
206
+ }
207
+ if (strictErrors && errors.length > 0) {
208
+ throw new Error(`Gemini search failed:
209
+ - ${errors.join(`
210
+ - `)}`);
211
+ }
212
+ return null;
213
+ }
214
+ function providerErrorStatus(message) {
215
+ const match = message.match(/\b(?:error|status|http)\s+(\d{3})\b/i);
216
+ if (!match)
217
+ return;
218
+ return Number(match[1]);
219
+ }
220
+ function classifyProviderError(provider, err) {
221
+ if (err instanceof SearchProviderError)
222
+ return err;
223
+ const message = errorMessage(err);
224
+ const lower = message.toLowerCase();
225
+ const status = providerErrorStatus(message);
226
+ let kind = "unknown";
227
+ const mentionsUnsupportedWebSearch = /(?:web[_ -]?search|web[_ -]?search_preview|(?:the )?tool)\b.*\b(?:unsupported|not supported|does not support|doesn't support|unknown|unrecognized|unavailable|not found)|\b(?:unsupported|not supported|does not support|doesn't support|unknown|unrecognized|unavailable|not found)\b.*\b(?:web[_ -]?search|web[_ -]?search_preview|(?:the )?tool)/i.test(lower);
228
+ if (err instanceof CredentialResolutionError || /(?:api )?key (?:not found|missing)|credential resolution/.test(lower)) {
229
+ kind = "credential";
230
+ } else if (isAbortError(err)) {
231
+ kind = "aborted";
232
+ } else if (provider === "xai" && status === 403 && /spending[- ]limit|(?:no|out of) credits?|insufficient quota|quota (?:exceeded|exhausted)|credits? (?:exhausted|depleted|used up)/.test(lower)) {
233
+ kind = "quota";
234
+ } else if (status === 401 || status === 403) {
235
+ kind = "auth";
236
+ } else if (provider === "openai" && (status === 400 || status === 422) && mentionsUnsupportedWebSearch) {
237
+ kind = "unsupported";
238
+ } else if (status === 400 || status === 422) {
239
+ kind = "invalid-request";
240
+ } else if (status === 402 || status === 429) {
241
+ kind = "quota";
242
+ } else if (status !== undefined && (status === 408 || status === 425 || status >= 500)) {
243
+ kind = "transient";
244
+ } else if (/rate limit|quota|too many requests/.test(lower)) {
245
+ kind = "quota";
246
+ } else if (/unauthorized|forbidden|permission denied/.test(lower)) {
247
+ kind = "auth";
248
+ } else if (/bad request|invalid request/.test(lower)) {
249
+ kind = "invalid-request";
250
+ } else if (/invalid json|no parseable response|no parseable results|invalid response|returned empty response|no web_search_call/.test(lower)) {
251
+ kind = "invalid-response";
252
+ } else if (/temporar|service unavailable|server error/.test(lower)) {
253
+ kind = "transient";
254
+ } else if (err instanceof TypeError || /fetch failed|network|econnreset|econnrefused|enotfound|etimedout|timed out|socket/.test(lower)) {
255
+ kind = "network";
256
+ } else if (/invalid or missing|invalid config|failed to parse|must be an? |configuration/.test(lower)) {
257
+ kind = "config";
258
+ }
259
+ return new SearchProviderError(provider, kind, message, status, err);
260
+ }
261
+ async function searchWithResolvedProvider(provider, query, options, useCurrentModel = false) {
262
+ if (provider === "openai") {
263
+ const result = useCurrentModel ? await searchWithCurrentModelOpenAI(query, options, options.extensionContext) : await searchWithOpenAI(query, options, options.extensionContext);
264
+ return { ...result, provider };
265
+ }
266
+ if (provider === "brave")
267
+ return { ...await searchWithBrave(query, options), provider };
268
+ if (provider === "parallel")
269
+ return { ...await searchWithParallel(query, options), provider };
270
+ if (provider === "parallel-mcp")
271
+ return { ...await searchWithParallelMcp(query, options), provider };
272
+ if (provider === "tinyfish")
273
+ return { ...await searchWithTinyFish(query, options), provider };
274
+ if (provider === "search1api")
275
+ return { ...await searchWithSearch1API(query, options), provider };
276
+ if (provider === "searchinfinity")
277
+ return { ...await searchWithSearchinfinity(query, options), provider };
278
+ if (provider === "querit")
279
+ return { ...await searchWithQuerit(query, options), provider };
280
+ if (provider === "tavily")
281
+ return { ...await searchWithTavily(query, options), provider };
282
+ if (provider === "firecrawl")
283
+ return { ...await searchWithFirecrawl(query, options), provider };
284
+ if (provider === "jina")
285
+ return { ...await searchWithJina(query, options), provider };
286
+ if (provider === "serpdive")
287
+ return { ...await searchWithSerpdive(query, options), provider };
288
+ if (provider === "kagi")
289
+ return { ...await searchWithKagi(query, options), provider };
290
+ if (provider === "bocha")
291
+ return { ...await searchWithBocha(query, options), provider };
292
+ if (provider === "ollama")
293
+ return { ...await searchWithOllama(query, options), provider };
294
+ if (provider === "anysearch")
295
+ return { ...await searchWithAnySearch(query, options), provider };
296
+ if (provider === "xai")
297
+ return { ...await searchWithXai(query, options, options.extensionContext), provider };
298
+ if (provider === "brightdata")
299
+ return { ...await searchWithBrightData(query, options), provider };
300
+ if (provider === "serpbase")
301
+ return { ...await searchWithSerpBase(query, options), provider };
302
+ if (provider === "serper")
303
+ return { ...await searchWithSerper(query, options), provider };
304
+ if (provider === "valyu")
305
+ return { ...await searchWithValyu(query, options), provider };
306
+ if (provider === "xcrawl")
307
+ return { ...await searchWithXCrawl(query, options), provider };
308
+ if (provider === "perplexity")
309
+ return { ...await searchWithPerplexity(query, options), provider };
310
+ if (provider === "searxng")
311
+ return { ...await searchWithSearXNG(query, options), provider };
312
+ if (provider === "duckduckgo")
313
+ return { ...await searchWithDuckDuckGo(query, options), provider };
314
+ if (provider === "kimi")
315
+ return { ...await searchWithKimi(query, options, options.extensionContext), provider };
316
+ if (provider === "gemini") {
317
+ const result = await searchWithGemini(query, options, true);
318
+ if (result)
319
+ return { ...result, provider };
320
+ throw new Error(`Gemini search unavailable. Either:
321
+ ` + ` 1. Configure geminiApiKey in ${CONFIG_PATH} or set GEMINI_API_KEY
322
+ ` + ` 2. Set GOOGLE_GEMINI_BASE_URL + CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing
323
+ ` + ` 3. Set geminiAuth to "adc" in web-search.json with a Google Cloud ADC + project/location
324
+ ` + " 4. Sign into gemini.google.com in a supported Chromium-based browser");
325
+ }
326
+ const result = await searchWithExa(query, options);
327
+ if (result)
328
+ return { ...result, provider };
329
+ throw new Error("Exa search returned no results.");
330
+ }
331
+ async function isResolvedProviderAvailable(provider, options, useCurrentModel = false) {
332
+ if (provider === "openai") {
333
+ return useCurrentModel ? isCurrentModelHostedSearchEligible(options.extensionContext) : isOpenAISearchAvailable(options.extensionContext);
334
+ }
335
+ if (provider === "brave")
336
+ return isBraveAvailable();
337
+ if (provider === "parallel")
338
+ return isParallelAvailable();
339
+ if (provider === "parallel-mcp")
340
+ return isParallelMcpAvailable();
341
+ if (provider === "tinyfish")
342
+ return isTinyFishAvailable();
343
+ if (provider === "search1api")
344
+ return isSearch1APIAvailable();
345
+ if (provider === "searchinfinity")
346
+ return isSearchinfinityAvailable();
347
+ if (provider === "querit")
348
+ return isQueritAvailable();
349
+ if (provider === "tavily")
350
+ return isTavilyAvailable();
351
+ if (provider === "firecrawl")
352
+ return isFirecrawlAvailable();
353
+ if (provider === "jina")
354
+ return isJinaSearchAvailable();
355
+ if (provider === "serpdive")
356
+ return isSerpdiveAvailable();
357
+ if (provider === "kagi")
358
+ return isKagiAvailable();
359
+ if (provider === "bocha")
360
+ return isBochaAvailable();
361
+ if (provider === "ollama")
362
+ return isOllamaAvailable();
363
+ if (provider === "anysearch")
364
+ return isAnySearchAvailable();
365
+ if (provider === "xai")
366
+ return isXaiSearchAvailable(options.extensionContext);
367
+ if (provider === "brightdata")
368
+ return isBrightDataAvailable();
369
+ if (provider === "serpbase")
370
+ return isSerpBaseAvailable();
371
+ if (provider === "serper")
372
+ return isSerperAvailable();
373
+ if (provider === "valyu")
374
+ return isValyuAvailable();
375
+ if (provider === "xcrawl")
376
+ return isXcrawlAvailable();
377
+ if (provider === "perplexity")
378
+ return isPerplexityAvailable();
379
+ if (provider === "searxng")
380
+ return isSearXNGAvailable();
381
+ if (provider === "duckduckgo")
382
+ return isDuckDuckGoAvailable();
383
+ if (provider === "gemini")
384
+ return isGeminiApiAvailable() || await isGeminiWebOptionallyAvailable();
385
+ if (provider === "kimi")
386
+ return isKimiSearchAvailable(options.extensionContext);
387
+ return isExaAvailable();
388
+ }
389
+ async function isGeminiWebOptionallyAvailable() {
390
+ try {
391
+ return !!await isGeminiWebAvailable();
392
+ } catch {
393
+ return false;
394
+ }
395
+ }
396
+ function providerLabel(provider) {
397
+ if (provider === "openai")
398
+ return "OpenAI";
399
+ if (provider === "parallel-mcp")
400
+ return "Parallel MCP";
401
+ if (provider === "tinyfish")
402
+ return "TinyFish";
403
+ if (provider === "search1api")
404
+ return "Search1API";
405
+ if (provider === "searchinfinity")
406
+ return "Searchinfinity";
407
+ if (provider === "querit")
408
+ return "Querit";
409
+ if (provider === "firecrawl")
410
+ return "Firecrawl";
411
+ if (provider === "serpdive")
412
+ return "SERPdive";
413
+ if (provider === "searxng")
414
+ return "SearXNG";
415
+ if (provider === "duckduckgo")
416
+ return "DuckDuckGo";
417
+ if (provider === "kagi")
418
+ return "Kagi";
419
+ if (provider === "bocha")
420
+ return "Bocha";
421
+ if (provider === "xcrawl")
422
+ return "XCrawl";
423
+ if (provider === "kimi")
424
+ return "Kimi";
425
+ if (provider === "ollama")
426
+ return "Ollama";
427
+ if (provider === "xai")
428
+ return "xAI";
429
+ if (provider === "brightdata")
430
+ return "Bright Data";
431
+ if (provider === "serpbase")
432
+ return "SerpBase";
433
+ if (provider === "serper")
434
+ return "Serper";
435
+ if (provider === "valyu")
436
+ return "Valyu";
437
+ return provider.charAt(0).toUpperCase() + provider.slice(1);
438
+ }
439
+ async function searchWithAllProvider(provider, query, options) {
440
+ if (provider !== "gemini")
441
+ return searchWithResolvedProvider(provider, query, options);
442
+ const result = await searchWithGeminiApi(query, options);
443
+ if (result)
444
+ return { ...result, provider };
445
+ throw new Error("Gemini API search returned no results.");
446
+ }
447
+ async function searchWithProviders(query, options, selectedProviders) {
448
+ const providers = selectedProviders ?? (await Promise.all(ALL_SEARCH_PROVIDERS.map(async (provider) => ({
449
+ provider,
450
+ available: provider === "gemini" ? isGeminiApiAvailable() : await isResolvedProviderAvailable(provider, options)
451
+ })))).filter((entry) => entry.available).map((entry) => entry.provider);
452
+ if (providers.length === 0) {
453
+ throw new Error('No configured search provider available for provider "all". Parallel MCP, DuckDuckGo, Kimi, AnySearch, xAI, Bright Data, SerpBase, Serper, Valyu, and XCrawl are excluded.');
454
+ }
455
+ const settled = await Promise.allSettled(providers.map((provider) => selectedProviders ? searchWithResolvedProvider(provider, query, options) : searchWithAllProvider(provider, query, options)));
456
+ if (options.signal?.aborted)
457
+ throw new Error("Aborted");
458
+ const successes = [];
459
+ const failures = [];
460
+ for (let index = 0;index < settled.length; index++) {
461
+ const outcome = settled[index];
462
+ if (outcome.status === "fulfilled") {
463
+ successes.push(outcome.value);
464
+ } else {
465
+ failures.push({ provider: providers[index], error: errorMessage(outcome.reason) });
466
+ }
467
+ }
468
+ if (successes.length === 0) {
469
+ const label = selectedProviders ? "Selected-provider" : "All-provider";
470
+ throw new Error(`${label} search failed:
471
+ - ${failures.map(({ provider, error }) => `${providerLabel(provider)}: ${error}`).join(`
472
+ - `)}`);
473
+ }
474
+ const results = [];
475
+ const seenResultUrls = new Set;
476
+ const inlineContent = [];
477
+ const seenInlineUrls = new Set;
478
+ for (const response of successes) {
479
+ for (const result of response.results) {
480
+ if (seenResultUrls.has(result.url))
481
+ continue;
482
+ seenResultUrls.add(result.url);
483
+ results.push(result);
484
+ }
485
+ for (const content of response.inlineContent ?? []) {
486
+ if (seenInlineUrls.has(content.url))
487
+ continue;
488
+ seenInlineUrls.add(content.url);
489
+ inlineContent.push(content);
490
+ }
491
+ }
492
+ const answerSections = successes.map((response) => `## ${providerLabel(response.provider)}
493
+
494
+ ${response.answer || "(No answer text returned.)"}`);
495
+ if (failures.length > 0) {
496
+ answerSections.push(`## Provider errors
497
+
498
+ ${failures.map(({ provider, error }) => `- **${providerLabel(provider)}:** ${error}`).join(`
499
+ `)}`);
500
+ }
501
+ return {
502
+ provider: "all",
503
+ answer: answerSections.join(`
504
+
505
+ `),
506
+ results,
507
+ providerResponses: successes,
508
+ ...failures.length > 0 ? { providerErrors: failures } : {},
509
+ ...inlineContent.length > 0 ? { inlineContent } : {}
510
+ };
511
+ }
512
+ async function searchWithConfiguredRouting(query, options, routing) {
513
+ const diagnostics = [];
514
+ for (const provider of routing.providers) {
515
+ const useCurrentModel = provider === "openai" && routing.useCurrentModel === true;
516
+ if (!await isResolvedProviderAvailable(provider, options, useCurrentModel)) {
517
+ diagnostics.push(`${provider}: unavailable`);
518
+ continue;
519
+ }
520
+ try {
521
+ return await searchWithResolvedProvider(provider, query, options, useCurrentModel);
522
+ } catch (err) {
523
+ const classified = classifyProviderError(provider, err);
524
+ diagnostics.push(`${provider} [${classified.kind}]: ${errorMessage(err)}`);
525
+ if (!routing.fallbackOn.includes(classified.kind)) {
526
+ throw classified;
527
+ }
528
+ }
529
+ }
530
+ throw new Error(`Configured search routing exhausted:
531
+ - ${diagnostics.join(`
532
+ - `)}`);
533
+ }
534
+ export async function search(query, options = {}) {
535
+ const config = getSearchConfig();
536
+ const provider = options.provider === undefined || options.provider === "auto" ? config.searchProvider : options.provider;
537
+ if (Array.isArray(provider)) {
538
+ return searchWithProviders(query, options, normalizeResolvedProviderList(provider, "provider"));
539
+ }
540
+ if (provider === "all")
541
+ return searchWithProviders(query, options);
542
+ if (provider !== "auto")
543
+ return searchWithResolvedProvider(provider, query, options);
544
+ if (!config.searchProviderConfigured && config.searchRouting) {
545
+ return searchWithConfiguredRouting(query, options, config.searchRouting);
546
+ }
547
+ const fallbackErrors = [];
548
+ if (isSearXNGAvailable()) {
549
+ try {
550
+ const result = await searchWithSearXNG(query, options);
551
+ return { ...result, provider: "searxng" };
552
+ } catch (err) {
553
+ if (isAbortError(err))
554
+ throw err;
555
+ fallbackErrors.push(`SearXNG: ${errorMessage(err)}`);
556
+ }
557
+ }
558
+ let triedOpenAI = false;
559
+ if (!options.extensionContext || isOpenAICodexSelected(options.extensionContext)) {
560
+ triedOpenAI = true;
561
+ const result = await tryOpenAIInAuto(query, options, fallbackErrors);
562
+ if (result)
563
+ return result;
564
+ }
565
+ if (isExaAvailable()) {
566
+ try {
567
+ const result = await searchWithExa(query, options);
568
+ if (result)
569
+ return { ...result, provider: "exa" };
570
+ } catch (err) {
571
+ if (err instanceof CredentialResolutionError || isAbortError(err))
572
+ throw err;
573
+ fallbackErrors.push(`Exa: ${errorMessage(err)}`);
574
+ }
575
+ }
576
+ if (!triedOpenAI) {
577
+ const result = await tryOpenAIInAuto(query, options, fallbackErrors);
578
+ if (result)
579
+ return result;
580
+ }
581
+ if (isBraveAvailable()) {
582
+ try {
583
+ const result = await searchWithBrave(query, options);
584
+ return { ...result, provider: "brave" };
585
+ } catch (err) {
586
+ if (isAbortError(err))
587
+ throw err;
588
+ fallbackErrors.push(`Brave: ${errorMessage(err)}`);
589
+ }
590
+ }
591
+ if (isParallelAvailable()) {
592
+ try {
593
+ const result = await searchWithParallel(query, options);
594
+ return { ...result, provider: "parallel" };
595
+ } catch (err) {
596
+ if (isAbortError(err))
597
+ throw err;
598
+ fallbackErrors.push(`Parallel: ${errorMessage(err)}`);
599
+ }
600
+ }
601
+ if (isTinyFishAvailable()) {
602
+ try {
603
+ const result = await searchWithTinyFish(query, options);
604
+ return { ...result, provider: "tinyfish" };
605
+ } catch (err) {
606
+ if (isAbortError(err))
607
+ throw err;
608
+ fallbackErrors.push(`TinyFish: ${errorMessage(err)}`);
609
+ }
610
+ }
611
+ if (isSearch1APIAvailable()) {
612
+ try {
613
+ const result = await searchWithSearch1API(query, options);
614
+ return { ...result, provider: "search1api" };
615
+ } catch (err) {
616
+ if (isAbortError(err))
617
+ throw err;
618
+ fallbackErrors.push(`Search1API: ${errorMessage(err)}`);
619
+ }
620
+ }
621
+ if (isSearchinfinityAvailable()) {
622
+ try {
623
+ const result = await searchWithSearchinfinity(query, options);
624
+ return { ...result, provider: "searchinfinity" };
625
+ } catch (err) {
626
+ if (isAbortError(err))
627
+ throw err;
628
+ fallbackErrors.push(`Searchinfinity: ${errorMessage(err)}`);
629
+ }
630
+ }
631
+ if (isQueritAvailable()) {
632
+ try {
633
+ const result = await searchWithQuerit(query, options);
634
+ return { ...result, provider: "querit" };
635
+ } catch (err) {
636
+ if (isAbortError(err))
637
+ throw err;
638
+ fallbackErrors.push(`Querit: ${errorMessage(err)}`);
639
+ }
640
+ }
641
+ if (isTavilyAvailable()) {
642
+ try {
643
+ const result = await searchWithTavily(query, options);
644
+ return { ...result, provider: "tavily" };
645
+ } catch (err) {
646
+ if (isAbortError(err))
647
+ throw err;
648
+ fallbackErrors.push(`Tavily: ${errorMessage(err)}`);
649
+ }
650
+ }
651
+ if (isFirecrawlAvailable()) {
652
+ try {
653
+ const result = await searchWithFirecrawl(query, options);
654
+ return { ...result, provider: "firecrawl" };
655
+ } catch (err) {
656
+ if (isAbortError(err))
657
+ throw err;
658
+ fallbackErrors.push(`Firecrawl: ${errorMessage(err)}`);
659
+ }
660
+ }
661
+ if (isJinaSearchAvailable()) {
662
+ try {
663
+ const result = await searchWithJina(query, options);
664
+ return { ...result, provider: "jina" };
665
+ } catch (err) {
666
+ if (isAbortError(err))
667
+ throw err;
668
+ fallbackErrors.push(`Jina: ${errorMessage(err)}`);
669
+ }
670
+ }
671
+ if (isSerpdiveAvailable()) {
672
+ try {
673
+ const result = await searchWithSerpdive(query, options);
674
+ return { ...result, provider: "serpdive" };
675
+ } catch (err) {
676
+ if (isAbortError(err))
677
+ throw err;
678
+ fallbackErrors.push(`SERPdive: ${errorMessage(err)}`);
679
+ }
680
+ }
681
+ if (isKagiAvailable()) {
682
+ try {
683
+ const result = await searchWithKagi(query, options);
684
+ return { ...result, provider: "kagi" };
685
+ } catch (err) {
686
+ if (isAbortError(err))
687
+ throw err;
688
+ fallbackErrors.push(`Kagi: ${errorMessage(err)}`);
689
+ }
690
+ }
691
+ if (isBochaAvailable()) {
692
+ try {
693
+ const result = await searchWithBocha(query, options);
694
+ return { ...result, provider: "bocha" };
695
+ } catch (err) {
696
+ if (isAbortError(err))
697
+ throw err;
698
+ fallbackErrors.push(`Bocha: ${errorMessage(err)}`);
699
+ }
700
+ }
701
+ if (isOllamaAvailable()) {
702
+ try {
703
+ const result = await searchWithOllama(query, options);
704
+ return { ...result, provider: "ollama" };
705
+ } catch (err) {
706
+ if (isAbortError(err))
707
+ throw err;
708
+ fallbackErrors.push(`Ollama: ${errorMessage(err)}`);
709
+ }
710
+ }
711
+ if (isPerplexityAvailable()) {
712
+ try {
713
+ const result = await searchWithPerplexity(query, options);
714
+ return { ...result, provider: "perplexity" };
715
+ } catch (err) {
716
+ if (isAbortError(err))
717
+ throw err;
718
+ fallbackErrors.push(`Perplexity: ${errorMessage(err)}`);
719
+ }
720
+ }
721
+ try {
722
+ const geminiResult = await searchWithGemini(query, options, false);
723
+ if (geminiResult)
724
+ return { ...geminiResult, provider: "gemini" };
725
+ } catch (err) {
726
+ if (isAbortError(err))
727
+ throw err;
728
+ fallbackErrors.push(`Gemini: ${errorMessage(err)}`);
729
+ }
730
+ if (fallbackErrors.length > 0) {
731
+ throw new Error(`Auto provider search failed:
732
+ - ${fallbackErrors.join(`
733
+ - `)}`);
734
+ }
735
+ throw new Error(`No search provider available. Either:
736
+ ` + ` 1. Use /login to sign in with a Codex subscription for OpenAI web search
737
+ ` + ` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tinyfishApiKey, search1apiApiKey, searchinfinityApiKey, queritApiKey, tavilyApiKey, firecrawlBaseUrl, jinaApiKey, serpdiveApiKey, kagiApiKey, ollamaApiKey, searxngBaseUrl, perplexityApiKey, exaApiKey, geminiApiKey, bochaApiKey, or cloudflareApiKey in ${CONFIG_PATH}
738
+ ` + ` 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, FIRECRAWL_BASE_URL, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars
739
+ ` + ` 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing
740
+ ` + ` 5. Sign into gemini.google.com in a supported Chromium-based browser
741
+ ` + ' 6. Explicitly select provider: "anysearch" for anonymous AnySearch, "xcrawl" for XCrawl, "xai" for Grok, "brightdata" with brightdataSerpZone for paid Bright Data SERP, "serpbase" or "serper" for Google SERP, or "valyu" for research search');
742
+ }
743
+ async function searchWithGeminiApi(query, options = {}) {
744
+ const requestSignal = AbortSignal.any([
745
+ AbortSignal.timeout(120000),
746
+ ...options.signal ? [options.signal] : []
747
+ ]);
748
+ const apiKey = isGeminiAdcAvailable() ? null : await getApiKey(requestSignal);
749
+ if (!apiKey && !isGatewayConfigured() && !isGeminiAdcAvailable())
750
+ return null;
751
+ const activityId = activityMonitor.logStart({ type: "api", query });
752
+ try {
753
+ const model = getSearchConfig().searchModel ?? DEFAULT_SEARCH_MODEL;
754
+ const body = {
755
+ contents: [{ role: "user", parts: [{ text: query }] }],
756
+ tools: [{ google_search: {} }]
757
+ };
758
+ const res = await fetchGeminiApi(`${getVersionedApiBase()}/models/${model}:generateContent`, {
759
+ method: "POST",
760
+ headers: { "Content-Type": "application/json" },
761
+ body: JSON.stringify(body),
762
+ signal: requestSignal
763
+ }, apiKey);
764
+ if (!res.ok) {
765
+ const errorText = redactGeminiApiResponse(res, await res.text(), apiKey);
766
+ throw new Error(`Gemini API error ${res.status}: ${errorText.slice(0, 300)}`);
767
+ }
768
+ const data = await res.json();
769
+ activityMonitor.logComplete(activityId, res.status);
770
+ const answer = data.candidates?.[0]?.content?.parts?.map((p) => p.text).filter(Boolean).join(`
771
+ `) ?? "";
772
+ const metadata = data.candidates?.[0]?.groundingMetadata;
773
+ const results = await resolveGroundingChunks(metadata?.groundingChunks, options.signal);
774
+ if (!answer && results.length === 0)
775
+ return null;
776
+ return { answer, results };
777
+ } catch (err) {
778
+ const message = err instanceof Error ? err.message : String(err);
779
+ if (message.toLowerCase().includes("abort")) {
780
+ activityMonitor.logComplete(activityId, 0);
781
+ } else {
782
+ activityMonitor.logError(activityId, message);
783
+ }
784
+ throw err;
785
+ }
786
+ }
787
+ async function searchWithGeminiWeb(query, options = {}) {
788
+ const cookies = await isGeminiWebAvailable();
789
+ if (!cookies)
790
+ return null;
791
+ const prompt = buildSearchPrompt(query, options);
792
+ const activityId = activityMonitor.logStart({ type: "api", query });
793
+ try {
794
+ const text = await queryWithCookies(prompt, cookies, {
795
+ signal: options.signal,
796
+ timeoutMs: 120000
797
+ });
798
+ activityMonitor.logComplete(activityId, 200);
799
+ const results = extractSourceUrls(text);
800
+ return { answer: text, results };
801
+ } catch (err) {
802
+ const message = err instanceof Error ? err.message : String(err);
803
+ if (message.toLowerCase().includes("abort")) {
804
+ activityMonitor.logComplete(activityId, 0);
805
+ } else {
806
+ activityMonitor.logError(activityId, message);
807
+ }
808
+ throw err;
809
+ }
810
+ }
811
+ function buildSearchPrompt(query, options) {
812
+ let prompt = `Search the web and answer the following question. Include source URLs for your claims.
813
+ Format your response as:
814
+ 1. A direct answer to the question
815
+ 2. Cited sources as markdown links
816
+
817
+ Question: ${query}`;
818
+ if (options.recencyFilter) {
819
+ const labels = {
820
+ day: "past 24 hours",
821
+ week: "past week",
822
+ month: "past month",
823
+ year: "past year"
824
+ };
825
+ prompt += `
826
+
827
+ Only include results from the ${labels[options.recencyFilter]}.`;
828
+ }
829
+ if (options.domainFilter?.length) {
830
+ const includes = options.domainFilter.filter((d) => !d.startsWith("-"));
831
+ const excludes = options.domainFilter.filter((d) => d.startsWith("-")).map((d) => d.slice(1));
832
+ if (includes.length)
833
+ prompt += `
834
+
835
+ Only cite sources from: ${includes.join(", ")}`;
836
+ if (excludes.length)
837
+ prompt += `
838
+
839
+ Do not cite sources from: ${excludes.join(", ")}`;
840
+ }
841
+ return prompt;
842
+ }
843
+ function extractSourceUrls(markdown) {
844
+ const results = [];
845
+ const seen = new Set;
846
+ const linkRegex = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
847
+ for (const match of markdown.matchAll(linkRegex)) {
848
+ const url = match[2];
849
+ if (seen.has(url))
850
+ continue;
851
+ seen.add(url);
852
+ results.push({ title: match[1], url, snippet: "" });
853
+ }
854
+ return results;
855
+ }
856
+ async function resolveGroundingChunks(chunks, signal) {
857
+ if (!chunks?.length)
858
+ return [];
859
+ const results = [];
860
+ for (const chunk of chunks) {
861
+ if (!chunk.web)
862
+ continue;
863
+ const title = chunk.web.title || "";
864
+ let url = chunk.web.uri || "";
865
+ if (url.includes("vertexaisearch.cloud.google.com/grounding-api-redirect")) {
866
+ const resolved = await resolveRedirect(url, signal);
867
+ if (resolved)
868
+ url = resolved;
869
+ }
870
+ if (url)
871
+ results.push({ title, url, snippet: "" });
872
+ }
873
+ return results;
874
+ }
875
+ async function resolveRedirect(proxyUrl, signal) {
876
+ try {
877
+ const res = await fetch(proxyUrl, {
878
+ method: "HEAD",
879
+ redirect: "manual",
880
+ signal: AbortSignal.any([
881
+ AbortSignal.timeout(5000),
882
+ ...signal ? [signal] : []
883
+ ])
884
+ });
885
+ return res.headers.get("location") || null;
886
+ } catch {
887
+ return null;
888
+ }
889
+ }