chenbhao commited on
Commit
04fbbc8
·
1 Parent(s): 76e6c36

feat: zero-search

Browse files
bun.lock CHANGED
@@ -54,7 +54,6 @@
54
  "cli-highlight": "^2.1.11",
55
  "code-excerpt": "^4.0.0",
56
  "diff": "^8.0.4",
57
- "duck-duck-scrape": "^2.2.7",
58
  "emoji-regex": "^10.6.0",
59
  "env-paths": "^4.0.0",
60
  "execa": "^9.6.1",
@@ -619,8 +618,6 @@
619
 
620
  "dom-mutator": ["dom-mutator@0.6.0", "", {}, "sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg=="],
621
 
622
- "duck-duck-scrape": ["duck-duck-scrape@2.2.7", "", { "dependencies": { "html-entities": "^2.3.3", "needle": "^3.2.0" } }, "sha512-BEcglwnfx5puJl90KQfX+Q2q5vCguqyMpZcSRPBWk8OY55qWwV93+E+7DbIkrGDW4qkqPfUvtOUdi0lXz6lEMQ=="],
623
-
624
  "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
625
 
626
  "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
@@ -745,8 +742,6 @@
745
 
746
  "hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
747
 
748
- "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="],
749
-
750
  "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
751
 
752
  "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
@@ -867,8 +862,6 @@
867
 
868
  "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
869
 
870
- "needle": ["needle@3.5.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, "bin": { "needle": "bin/needle" } }, "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w=="],
871
-
872
  "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
873
 
874
  "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
@@ -973,8 +966,6 @@
973
 
974
  "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
975
 
976
- "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
977
-
978
  "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
979
 
980
  "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -1435,8 +1426,6 @@
1435
 
1436
  "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1437
 
1438
- "needle/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
1439
-
1440
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1441
 
1442
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
 
54
  "cli-highlight": "^2.1.11",
55
  "code-excerpt": "^4.0.0",
56
  "diff": "^8.0.4",
 
57
  "emoji-regex": "^10.6.0",
58
  "env-paths": "^4.0.0",
59
  "execa": "^9.6.1",
 
618
 
619
  "dom-mutator": ["dom-mutator@0.6.0", "", {}, "sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg=="],
620
 
 
 
621
  "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
622
 
623
  "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
 
742
 
743
  "hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
744
 
 
 
745
  "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
746
 
747
  "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
 
862
 
863
  "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
864
 
 
 
865
  "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
866
 
867
  "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
 
966
 
967
  "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
968
 
 
 
969
  "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
970
 
971
  "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
 
1426
 
1427
  "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1428
 
 
 
1429
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
1430
 
1431
  "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
package.json CHANGED
@@ -69,7 +69,6 @@
69
  "cli-highlight": "^2.1.11",
70
  "code-excerpt": "^4.0.0",
71
  "diff": "^8.0.4",
72
- "duck-duck-scrape": "^2.2.7",
73
  "emoji-regex": "^10.6.0",
74
  "env-paths": "^4.0.0",
75
  "execa": "^9.6.1",
 
69
  "cli-highlight": "^2.1.11",
70
  "code-excerpt": "^4.0.0",
71
  "diff": "^8.0.4",
 
72
  "emoji-regex": "^10.6.0",
73
  "env-paths": "^4.0.0",
74
  "execa": "^9.6.1",
src/tools/WebSearchTool/WebSearchTool.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { search } from 'duck-duck-scrape'
2
  import type { PermissionResult } from 'src/utils/permissions/PermissionResult.js'
3
  import { z } from 'zod/v4'
4
  import { buildTool, type ToolDef } from '../../Tool.js'
@@ -64,6 +63,64 @@ export type { WebSearchProgress } from '../../types/tools.js'
64
 
65
  import type { WebSearchProgress } from '../../types/tools.js'
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  /**
68
  * Filter search results by domain
69
  */
@@ -183,15 +240,13 @@ export const WebSearchTool = buildTool<InputSchema, Output, WebSearchTool>({
183
 
184
  try {
185
  // Call DuckDuckGo search
186
- const response = await search(query, {
187
- safeSearch: 'moderate',
188
- })
189
 
190
  // Filter results by domain if specified
191
- let filteredResults = response.results
192
  if (allowed_domains || blocked_domains) {
193
  filteredResults = filterDomains(
194
- response.results,
195
  allowed_domains,
196
  blocked_domains
197
  )
@@ -210,12 +265,12 @@ export const WebSearchTool = buildTool<InputSchema, Output, WebSearchTool>({
210
  }
211
 
212
  // Convert to output format
213
- const results: (SearchResult | string)[] = []
214
 
215
  if (filteredResults.length === 0) {
216
- results.push(`No results for: ${query}`)
217
  } else {
218
- results.push({
219
  tool_use_id: 'search-1',
220
  content: filteredResults.map(r => ({
221
  title: r.title,
@@ -229,7 +284,7 @@ export const WebSearchTool = buildTool<InputSchema, Output, WebSearchTool>({
229
 
230
  return {
231
  query,
232
- results,
233
  durationSeconds,
234
  }
235
  } catch (error) {
 
 
1
  import type { PermissionResult } from 'src/utils/permissions/PermissionResult.js'
2
  import { z } from 'zod/v4'
3
  import { buildTool, type ToolDef } from '../../Tool.js'
 
63
 
64
  import type { WebSearchProgress } from '../../types/tools.js'
65
 
66
+ /**
67
+ * Search DuckDuckGo and return results
68
+ */
69
+ async function searchDuckDuckGo(query: string): Promise<Array<{ title: string; url: string }>> {
70
+ const url = new URL('https://duckduckgo.com/html/')
71
+ url.searchParams.set('q', query)
72
+
73
+ const response = await fetch(url.toString(), {
74
+ headers: {
75
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
76
+ },
77
+ })
78
+
79
+ if (!response.ok) {
80
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
81
+ }
82
+
83
+ const html = await response.text()
84
+
85
+ // Parse HTML to extract search results
86
+ const results: Array<{ title: string; url: string }> = []
87
+
88
+ // DuckDuckGo search results are in <a class="result__a"> tags
89
+ // with title as text content and href as URL
90
+ const resultRegex = /<a[^>]*class="result__a"[^>]*>(.*?)<\/a>/gi
91
+ let match: RegExpExecArray | null
92
+
93
+ while ((match = resultRegex.exec(html)) !== null && results.length < 10) {
94
+ const title = match[1]
95
+ .replace(/<[^>]*>/g, '') // Remove HTML tags
96
+ .replace(/&amp;/g, '&')
97
+ .replace(/&lt;/g, '<')
98
+ .replace(/&gt;/g, '>')
99
+ .replace(/&quot;/g, '"')
100
+ .trim()
101
+
102
+ // Extract URL from href attribute
103
+ const hrefMatch = match[0].match(/href="([^"]*)"/)
104
+ if (hrefMatch) {
105
+ let url = hrefMatch[1]
106
+
107
+ // DuckDuckGo URLs are often redirects
108
+ if (url.startsWith('/l/?uddg=')) {
109
+ const urlMatch = url.match(/uddg=([^&]+)/)
110
+ if (urlMatch) {
111
+ url = decodeURIComponent(urlMatch[1])
112
+ }
113
+ }
114
+
115
+ if (title && url) {
116
+ results.push({ title, url })
117
+ }
118
+ }
119
+ }
120
+
121
+ return results
122
+ }
123
+
124
  /**
125
  * Filter search results by domain
126
  */
 
240
 
241
  try {
242
  // Call DuckDuckGo search
243
+ const results = await searchDuckDuckGo(query)
 
 
244
 
245
  // Filter results by domain if specified
246
+ let filteredResults = results
247
  if (allowed_domains || blocked_domains) {
248
  filteredResults = filterDomains(
249
+ results,
250
  allowed_domains,
251
  blocked_domains
252
  )
 
265
  }
266
 
267
  // Convert to output format
268
+ const searchResults: (SearchResult | string)[] = []
269
 
270
  if (filteredResults.length === 0) {
271
+ searchResults.push(`No results for: ${query}`)
272
  } else {
273
+ searchResults.push({
274
  tool_use_id: 'search-1',
275
  content: filteredResults.map(r => ({
276
  title: r.title,
 
284
 
285
  return {
286
  query,
287
+ results: searchResults,
288
  durationSeconds,
289
  }
290
  } catch (error) {