@qvac/harness 0.4.0 → 0.6.0

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 (299) hide show
  1. package/CHANGELOG.md +740 -141
  2. package/README.md +75 -7
  3. package/dist/harness.bundle +72 -50
  4. package/dist-lib/index.d.ts +49 -39
  5. package/dist-lib/index.js +448 -161
  6. package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
  7. package/dist-lib/lib/audio-transcode-bare.js +28 -18
  8. package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
  9. package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
  10. package/dist-lib/lib/capabilities/audio-resample.js +30 -18
  11. package/dist-lib/lib/capabilities/completion.d.ts +20 -19
  12. package/dist-lib/lib/capabilities/completion.js +193 -132
  13. package/dist-lib/lib/capabilities/download.d.ts +6 -6
  14. package/dist-lib/lib/capabilities/download.js +15 -8
  15. package/dist-lib/lib/capabilities/embed.js +2 -1
  16. package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
  17. package/dist-lib/lib/capabilities/enhance.js +7 -4
  18. package/dist-lib/lib/capabilities/invoke.js +7 -4
  19. package/dist-lib/lib/capabilities/kv-cache.js +2 -1
  20. package/dist-lib/lib/capabilities/preview-model-source.d.ts +9 -7
  21. package/dist-lib/lib/capabilities/preview-model-source.js +80 -28
  22. package/dist-lib/lib/capabilities/speech-text.js +5 -4
  23. package/dist-lib/lib/capabilities/tool-selection.js +10 -9
  24. package/dist-lib/lib/capabilities/tool-turns.js +30 -16
  25. package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
  26. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  27. package/dist-lib/lib/capabilities/voice.js +22 -12
  28. package/dist-lib/lib/connect.d.ts +13 -32
  29. package/dist-lib/lib/connect.js +170 -112
  30. package/dist-lib/lib/context-window.js +25 -21
  31. package/dist-lib/lib/contract-types.d.ts +35 -15
  32. package/dist-lib/lib/diagram-repair.d.ts +4 -4
  33. package/dist-lib/lib/diagram-repair.js +6 -3
  34. package/dist-lib/lib/docx/index.d.ts +3 -2
  35. package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
  36. package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
  37. package/dist-lib/lib/hardware/capability-probe.js +20 -10
  38. package/dist-lib/lib/hardware/gpu-classify.js +6 -3
  39. package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
  40. package/dist-lib/lib/hardware/gpu-probe.js +4 -2
  41. package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
  42. package/dist-lib/lib/hardware/parsers.js +8 -4
  43. package/dist-lib/lib/helpers/buffer-burst.js +4 -2
  44. package/dist-lib/lib/helpers/control-tokens.js +4 -2
  45. package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
  46. package/dist-lib/lib/helpers/join-guard.js +4 -2
  47. package/dist-lib/lib/helpers/load-stall.js +4 -2
  48. package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
  49. package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
  50. package/dist-lib/lib/helpers/race-abort.js +2 -1
  51. package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
  52. package/dist-lib/lib/helpers/race-timeout.js +14 -0
  53. package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
  54. package/dist-lib/lib/helpers/run-outcome.js +36 -20
  55. package/dist-lib/lib/helpers/spawn-path.js +6 -3
  56. package/dist-lib/lib/helpers/text-coalescer.js +6 -3
  57. package/dist-lib/lib/helpers/thinking.js +38 -45
  58. package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
  59. package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
  60. package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
  61. package/dist-lib/lib/helpers/tool-intent.js +4 -2
  62. package/dist-lib/lib/helpers/tool-label.js +18 -9
  63. package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
  64. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  65. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  66. package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
  67. package/dist-lib/lib/hooks/pipeline.js +6 -3
  68. package/dist-lib/lib/hooks/run.js +12 -8
  69. package/dist-lib/lib/model-download-progress.js +10 -8
  70. package/dist-lib/lib/models/context.d.ts +1 -1
  71. package/dist-lib/lib/models/context.js +5 -1
  72. package/dist-lib/lib/models/custom-model-cache.d.ts +10 -1
  73. package/dist-lib/lib/models/custom-model-cache.js +43 -15
  74. package/dist-lib/lib/models/gguf-header.d.ts +3 -2
  75. package/dist-lib/lib/models/gguf-header.js +44 -22
  76. package/dist-lib/lib/models/idle-unload.d.ts +14 -0
  77. package/dist-lib/lib/models/idle-unload.js +51 -0
  78. package/dist-lib/lib/models/kv-cache.d.ts +3 -2
  79. package/dist-lib/lib/models/kv-cache.js +4 -3
  80. package/dist-lib/lib/models/llm-presets.d.ts +2 -2
  81. package/dist-lib/lib/models/llm-presets.js +4 -2
  82. package/dist-lib/lib/models/model-catalog-constants.js +3 -2
  83. package/dist-lib/lib/models/model-catalog.d.ts +15 -20
  84. package/dist-lib/lib/models/model-catalog.js +66 -31
  85. package/dist-lib/lib/models/model-delete.js +18 -8
  86. package/dist-lib/lib/models/model-handle.js +2 -1
  87. package/dist-lib/lib/models/model-list.d.ts +8 -3
  88. package/dist-lib/lib/models/model-list.js +11 -6
  89. package/dist-lib/lib/models/model-pool.d.ts +48 -10
  90. package/dist-lib/lib/models/model-pool.js +106 -45
  91. package/dist-lib/lib/models/model-source.js +10 -5
  92. package/dist-lib/lib/models/music-catalog.d.ts +2 -2
  93. package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
  94. package/dist-lib/lib/models/nmt-catalog.js +10 -5
  95. package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
  96. package/dist-lib/lib/models/opencl-cache.js +5 -3
  97. package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
  98. package/dist-lib/lib/models/sdk-faults.js +9 -6
  99. package/dist-lib/lib/models/shards.d.ts +11 -0
  100. package/dist-lib/lib/models/shards.js +15 -0
  101. package/dist-lib/lib/models/tool-dialect.js +10 -5
  102. package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
  103. package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
  104. package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
  105. package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
  106. package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
  107. package/dist-lib/lib/office/index.d.ts +3 -2
  108. package/dist-lib/lib/office/python.d.ts +3 -2
  109. package/dist-lib/lib/office/python.js +2 -1
  110. package/dist-lib/lib/prompt.d.ts +5 -4
  111. package/dist-lib/lib/prompt.js +8 -4
  112. package/dist-lib/lib/queue.js +10 -5
  113. package/dist-lib/lib/resolution-detector.js +21 -11
  114. package/dist-lib/lib/rpc-session.js +2 -1
  115. package/dist-lib/lib/sdk-loader.js +2 -1
  116. package/dist-lib/lib/sdk-storage.js +4 -2
  117. package/dist-lib/lib/serve.js +122 -66
  118. package/dist-lib/lib/spawn.js +7 -4
  119. package/dist-lib/lib/tmp-path.d.ts +7 -6
  120. package/dist-lib/lib/tool-call-display.d.ts +2 -2
  121. package/dist-lib/lib/tools/create-documents.js +18 -10
  122. package/dist-lib/lib/tools/edit-documents.js +23 -13
  123. package/dist-lib/lib/tools/edit-image.d.ts +2 -2
  124. package/dist-lib/lib/tools/edit-image.js +60 -36
  125. package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
  126. package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
  127. package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
  128. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
  129. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
  130. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
  131. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
  132. package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
  133. package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
  134. package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
  135. package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
  136. package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
  137. package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
  138. package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
  139. package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
  140. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
  141. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
  142. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
  143. package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
  144. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
  145. package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
  146. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
  147. package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
  148. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
  149. package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
  150. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
  151. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
  152. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
  153. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
  154. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
  155. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
  156. package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
  157. package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
  158. package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
  159. package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
  160. package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
  161. package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
  162. package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
  163. package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
  164. package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
  165. package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
  166. package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
  167. package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
  168. package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
  169. package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
  170. package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
  171. package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
  172. package/dist-lib/lib/tools/exec/runner.js +5 -3
  173. package/dist-lib/lib/tools/exec/scope.js +52 -28
  174. package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
  175. package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
  176. package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
  177. package/dist-lib/lib/tools/exec/video-tools.js +2 -2
  178. package/dist-lib/lib/tools/exec.d.ts +6 -8
  179. package/dist-lib/lib/tools/exec.js +113 -54
  180. package/dist-lib/lib/tools/file-glob.d.ts +4 -5
  181. package/dist-lib/lib/tools/file-glob.js +16 -11
  182. package/dist-lib/lib/tools/fs/glob.js +13 -18
  183. package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
  184. package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
  185. package/dist-lib/lib/tools/fs/remote.js +13 -7
  186. package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
  187. package/dist-lib/lib/tools/fs/scope.js +20 -12
  188. package/dist-lib/lib/tools/fs/walk.js +6 -3
  189. package/dist-lib/lib/tools/fs/write.d.ts +3 -2
  190. package/dist-lib/lib/tools/generate-image.d.ts +11 -9
  191. package/dist-lib/lib/tools/generate-image.js +73 -37
  192. package/dist-lib/lib/tools/generate-music.d.ts +22 -11
  193. package/dist-lib/lib/tools/generate-music.js +79 -48
  194. package/dist-lib/lib/tools/grep.js +66 -41
  195. package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
  196. package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
  197. package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
  198. package/dist-lib/lib/tools/http-request.d.ts +1 -1
  199. package/dist-lib/lib/tools/http-request.js +33 -18
  200. package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
  201. package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
  202. package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
  203. package/dist-lib/lib/tools/mcp-call.js +37 -16
  204. package/dist-lib/lib/tools/operations/builders.js +10 -6
  205. package/dist-lib/lib/tools/operations/compile.js +20 -10
  206. package/dist-lib/lib/tools/operations/decoders.js +12 -6
  207. package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
  208. package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
  209. package/dist-lib/lib/tools/outbound-guard.js +14 -8
  210. package/dist-lib/lib/tools/private-ip.js +8 -7
  211. package/dist-lib/lib/tools/rag-search.js +14 -7
  212. package/dist-lib/lib/tools/read-files.js +23 -20
  213. package/dist-lib/lib/tools/skill.js +19 -10
  214. package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
  215. package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
  216. package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
  217. package/dist-lib/lib/tools/skills/bin-check.js +10 -3
  218. package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
  219. package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
  220. package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
  221. package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
  222. package/dist-lib/lib/tools/skills/catalog.js +33 -28
  223. package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
  224. package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
  225. package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
  226. package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
  227. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
  228. package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
  229. package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
  230. package/dist-lib/lib/tools/skills/credential-check.js +24 -12
  231. package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
  232. package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
  233. package/dist-lib/lib/tools/skills/credential-health.js +5 -3
  234. package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
  235. package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
  236. package/dist-lib/lib/tools/skills/mention.js +7 -6
  237. package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
  238. package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
  239. package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
  240. package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
  241. package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
  242. package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
  243. package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
  244. package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
  245. package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
  246. package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
  247. package/dist-lib/lib/tools/skills/skill-state.js +48 -29
  248. package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
  249. package/dist-lib/lib/tools/toolsets.js +2 -1
  250. package/dist-lib/lib/tools/translate.d.ts +4 -5
  251. package/dist-lib/lib/tools/translate.js +31 -14
  252. package/dist-lib/lib/tools/url-guard.js +2 -1
  253. package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
  254. package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
  255. package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
  256. package/dist-lib/lib/tools/weather-lookup.js +18 -12
  257. package/dist-lib/lib/tools/web-fetch.js +47 -24
  258. package/dist-lib/lib/tools/web-search.js +8 -3
  259. package/dist-lib/lib/validating-lookup.d.ts +4 -4
  260. package/dist-lib/lib/wav-encode.d.ts +1 -1
  261. package/dist-lib/lib/wav-encode.js +11 -10
  262. package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
  263. package/dist-lib/lib/web-fetch/extract.js +4 -2
  264. package/dist-lib/lib/web-fetch/images.js +8 -4
  265. package/dist-lib/lib/web-fetch/video.d.ts +2 -2
  266. package/dist-lib/lib/web-fetch/video.js +8 -4
  267. package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
  268. package/dist-lib/lib/web-search/engines/aol.js +1 -1
  269. package/dist-lib/lib/web-search/engines/bing.js +1 -1
  270. package/dist-lib/lib/web-search/engines/brave.js +4 -1
  271. package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
  272. package/dist-lib/lib/web-search/engines/google.js +7 -3
  273. package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
  274. package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
  275. package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
  276. package/dist-lib/lib/web-search/fetch-html.js +13 -7
  277. package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
  278. package/dist-lib/lib/web-search/orchestrator.js +3 -3
  279. package/dist-lib/lib/wire.d.ts +3 -2
  280. package/dist-lib/lib/wire.js +23 -15
  281. package/dist-lib/schema/baseline.js +10 -5
  282. package/dist-lib/schema/build.js +2 -1
  283. package/dist-lib/schema/harness.d.ts +15 -2
  284. package/dist-lib/schema/harness.js +39 -2
  285. package/dist-lib/schema/release-tags.d.ts +3 -2
  286. package/dist-lib/schema/release-tags.js +6 -3
  287. package/dist-lib/spec/bind.js +9 -0
  288. package/dist-lib/spec/calls.js +3 -0
  289. package/dist-lib/spec/hrpc/hrpc.json +39 -0
  290. package/dist-lib/spec/hrpc/index.js +39 -3
  291. package/dist-lib/spec/hrpc/messages.js +105 -8
  292. package/dist-lib/spec/hyperschema/index.js +105 -8
  293. package/dist-lib/spec/hyperschema/schema.json +77 -1
  294. package/dist-lib/spec/hyperschema/types.d.ts +25 -1
  295. package/package.json +14 -14
  296. package/worker/harness-sidecar.mjs +15 -22
  297. package/worker/stow-entry.mjs +7 -4
  298. package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
  299. /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
@@ -1,6 +1,7 @@
1
- import defaultFetch from '#no-redirect-fetch';
1
+ import { noRedirectFetch } from '#no-redirect-fetch';
2
2
  import { AbortController } from 'bare-abort-controller';
3
3
  import { lookupHost } from '../dns-lookup.js';
4
+ import { errorMessage } from '../guards.js';
4
5
  import { AbortedError } from '../helpers/race-abort.js';
5
6
  import { extractEmbeddedState } from '../web-fetch/embedded-state.js';
6
7
  import { extractPageText, looksLikeHtml } from '../web-fetch/extract.js';
@@ -8,7 +9,7 @@ import { imageFileName, pageImageUrls } from '../web-fetch/images.js';
8
9
  import { pageVideoRef } from '../web-fetch/video.js';
9
10
  import { MAX_HTML_CHARS } from '../web-search/fetch-html.js';
10
11
  import { BROWSER_UA } from '../web-search/user-agents.js';
11
- import { isRedirectStatus } from './http-request/utils.js';
12
+ import { isRedirectStatus } from './http-request/request.js';
12
13
  import { resolveOutboundUrl } from './url-guard.js';
13
14
  const SMALL_CONTEXT_TOKENS = 12_288;
14
15
  const SMALL_CONTEXT_CHARS = 6_000;
@@ -64,9 +65,10 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
64
65
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
65
66
  return { error: `Scheme "${url.protocol}" not allowed. Use http: or https:.` };
66
67
  }
67
- if (ctx?.signal?.aborted)
68
+ if (ctx?.signal?.aborted) {
68
69
  return { error: 'web_fetch cancelled' };
69
- const fetch = fetchImpl ?? defaultFetch;
70
+ }
71
+ const fetch = fetchImpl ?? noRedirectFetch;
70
72
  const controller = new AbortController();
71
73
  const timeout = setTimeout(() => controller.abort(undefined), PAGE_TIMEOUT_MS);
72
74
  const onCallerAbort = () => controller.abort(undefined);
@@ -79,8 +81,9 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
79
81
  maxBytes: MAX_PAGE_BYTES,
80
82
  truncate: true
81
83
  });
82
- if (!got.ok)
84
+ if (!got.ok) {
83
85
  return { error: got.error };
86
+ }
84
87
  const { response, finalUrl: currentUrl } = got;
85
88
  if (response.status < 200 || response.status >= 300) {
86
89
  return {
@@ -91,18 +94,24 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
91
94
  const rawBody = await response.text();
92
95
  const body = rawBody.length > MAX_HTML_CHARS ? rawBody.slice(0, MAX_HTML_CHARS) : rawBody;
93
96
  let mimeType = response.headers?.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';
94
- if (body.startsWith('%PDF-'))
97
+ if (body.startsWith('%PDF-')) {
95
98
  mimeType = 'application/pdf';
99
+ }
96
100
  if (isBinaryMime(mimeType) || body.includes('\u0000')) {
97
101
  const attachMime = isBinaryMime(mimeType) ? mimeType : 'application/octet-stream';
98
- return await attachDocument(response, currentUrl, attachMime, ctx?.attach);
102
+ return await attachDocument(response, {
103
+ url: currentUrl,
104
+ mimeType: attachMime,
105
+ attach: ctx?.attach
106
+ });
99
107
  }
100
108
  const isHtml = looksLikeHtml(body);
101
109
  let page = isHtml ? extractPageText(body) : { title: null, text: body.trim() };
102
110
  if (isHtml && page.text.length < MIN_MARKUP_CHARS) {
103
111
  const embedded = extractEmbeddedState(body);
104
- if (embedded.length > page.text.length)
112
+ if (embedded.length > page.text.length) {
105
113
  page = { title: page.title, text: embedded };
114
+ }
106
115
  }
107
116
  const budget = charBudget(ctx?.ctxSize);
108
117
  const text = page.text.length > budget ? `${page.text.slice(0, budget)}…[truncated]` : page.text;
@@ -149,7 +158,7 @@ export function createWebFetchTool({ fetch: fetchImpl, resolveHost = lookupHost
149
158
  if (controller.signal.aborted) {
150
159
  return { error: `${url.hostname} timed out after ${PAGE_TIMEOUT_MS}ms`, code: 'timeout' };
151
160
  }
152
- return { error: err instanceof Error ? err.message : String(err), code: 'transport' };
161
+ return { error: errorMessage(err), code: 'transport' };
153
162
  }
154
163
  finally {
155
164
  clearTimeout(timeout);
@@ -164,7 +173,7 @@ function isBinaryMime(mimeType) {
164
173
  return true;
165
174
  return BINARY_MIME_PREFIXES.some((prefix) => mimeType.startsWith(prefix));
166
175
  }
167
- async function attachDocument(response, url, mimeType, attach) {
176
+ async function attachDocument(response, { url, mimeType, attach }) {
168
177
  const bytes = await response.bytes?.();
169
178
  if (!attach || !bytes || bytes.byteLength === 0) {
170
179
  return { error: 'This URL serves a file, not a readable page, and it could not be attached.' };
@@ -218,33 +227,39 @@ function attachmentFileName(url, mimeType) {
218
227
  }
219
228
  catch { }
220
229
  const base = decoded.split(/[/\\]/).filter(Boolean).pop() ?? '';
221
- if (!base)
230
+ if (!base) {
222
231
  return `${url.hostname}.${extensionFor(mimeType)}`;
232
+ }
223
233
  const extension = /\.([\w-]{1,8})$/.exec(base)?.[1]?.toLowerCase() ?? '';
224
- if (extension && acceptableExtensions(mimeType).has(extension))
234
+ if (extension && acceptableExtensions(mimeType).has(extension)) {
225
235
  return base;
226
- if (mimeType === 'application/octet-stream' && /^[a-z][\w-]*$/.test(extension))
236
+ }
237
+ if (mimeType === 'application/octet-stream' && /^[a-z][\w-]*$/.test(extension)) {
227
238
  return base;
239
+ }
228
240
  return `${base}.${extensionFor(mimeType)}`;
229
241
  }
230
242
  function acceptableExtensions(mimeType) {
231
243
  const accepted = new Set([extensionFor(mimeType)]);
232
244
  const subtype = mimeType.split('/')[1] ?? '';
233
- if (/^[a-z0-9]{1,8}$/.test(subtype))
245
+ if (/^[a-z0-9]{1,8}$/.test(subtype)) {
234
246
  accepted.add(subtype);
247
+ }
235
248
  return accepted;
236
249
  }
237
250
  function extensionFor(mimeType) {
238
251
  const known = MIME_EXTENSIONS[mimeType];
239
- if (known)
252
+ if (known) {
240
253
  return known;
254
+ }
241
255
  const subtype = mimeType.split('/')[1] ?? '';
242
256
  return /^[a-z0-9]{1,5}$/.test(subtype) ? subtype : 'bin';
243
257
  }
244
258
  async function guardedGet(fetch, url, { resolveHost, signal, accept, maxBytes, truncate }) {
245
259
  const initialCheck = await resolveOutboundUrl(url, resolveHost, signal);
246
- if (initialCheck.blocked)
260
+ if (initialCheck.blocked) {
247
261
  return { ok: false, error: initialCheck.blocked };
262
+ }
248
263
  let currentUrl = url;
249
264
  let validatedAddresses = initialCheck.addresses;
250
265
  for (let redirects = 0;; redirects++) {
@@ -257,11 +272,13 @@ async function guardedGet(fetch, url, { resolveHost, signal, accept, maxBytes, t
257
272
  maxBytes,
258
273
  truncate
259
274
  });
260
- if (!isRedirectStatus(response.status))
275
+ if (!isRedirectStatus(response.status)) {
261
276
  return { ok: true, response, finalUrl: currentUrl };
277
+ }
262
278
  const location = response.headers?.get('location');
263
- if (!location)
279
+ if (!location) {
264
280
  return { ok: true, response, finalUrl: currentUrl };
281
+ }
265
282
  let nextUrl;
266
283
  try {
267
284
  nextUrl = new URL(location, currentUrl);
@@ -272,11 +289,13 @@ async function guardedGet(fetch, url, { resolveHost, signal, accept, maxBytes, t
272
289
  if (nextUrl.protocol !== 'http:' && nextUrl.protocol !== 'https:') {
273
290
  return { ok: true, response, finalUrl: currentUrl };
274
291
  }
275
- if (redirects >= MAX_REDIRECTS)
292
+ if (redirects >= MAX_REDIRECTS) {
276
293
  return { ok: false, error: 'too many redirects' };
294
+ }
277
295
  const hopCheck = await resolveOutboundUrl(nextUrl, resolveHost, signal);
278
- if (hopCheck.blocked)
296
+ if (hopCheck.blocked) {
279
297
  return { ok: false, error: hopCheck.blocked };
298
+ }
280
299
  currentUrl = nextUrl;
281
300
  validatedAddresses = hopCheck.addresses;
282
301
  }
@@ -293,17 +312,21 @@ async function attachPageImages(html, pageUrl, { fetch, resolveHost, attach, sig
293
312
  accept: 'image/*',
294
313
  maxBytes: MAX_IMAGE_BYTES
295
314
  });
296
- if (!got.ok)
315
+ if (!got.ok) {
297
316
  continue;
317
+ }
298
318
  const { response, finalUrl } = got;
299
- if (response.status < 200 || response.status >= 300)
319
+ if (response.status < 200 || response.status >= 300) {
300
320
  continue;
321
+ }
301
322
  const mimeType = response.headers?.get('content-type')?.split(';')[0]?.trim() ?? '';
302
- if (!mimeType.startsWith('image/'))
323
+ if (!mimeType.startsWith('image/')) {
303
324
  continue;
325
+ }
304
326
  const bytes = await response.bytes?.();
305
- if (!bytes || bytes.byteLength === 0 || bytes.byteLength > MAX_IMAGE_BYTES)
327
+ if (!bytes || bytes.byteLength === 0 || bytes.byteLength > MAX_IMAGE_BYTES) {
306
328
  continue;
329
+ }
307
330
  const record = await attach({ fileName: imageFileName(finalUrl, mimeType), mimeType, bytes });
308
331
  attached.push(record.fileName);
309
332
  }
@@ -1,4 +1,5 @@
1
1
  import defaultFetch from '#fetch';
2
+ import { errorMessage } from '../guards.js';
2
3
  import { formatResultsForPrompt } from '../web-search/format-results.js';
3
4
  import { searchWeb } from '../web-search/orchestrator.js';
4
5
  const nodeFetch = async (url, init) => {
@@ -35,9 +36,13 @@ export function createWebSearchTool({ fetch } = {}) {
35
36
  return { error: 'query must be a non-empty string' };
36
37
  }
37
38
  try {
38
- const { results, engineSummary } = await searchWeb(fetcher, query, resultBudget(ctx?.ctxSize), ctx?.signal);
39
- if (ctx?.signal?.aborted)
39
+ const { results, engineSummary } = await searchWeb(fetcher, query, {
40
+ maxResults: resultBudget(ctx?.ctxSize),
41
+ signal: ctx?.signal
42
+ });
43
+ if (ctx?.signal?.aborted) {
40
44
  return { error: 'web_search cancelled' };
45
+ }
41
46
  if (results.length > 0) {
42
47
  return { content: formatResultsForPrompt(results), data: { results } };
43
48
  }
@@ -51,7 +56,7 @@ export function createWebSearchTool({ fetch } = {}) {
51
56
  return { content: `No web results found for "${query}".` };
52
57
  }
53
58
  catch (err) {
54
- return { error: err instanceof Error ? err.message : String(err), code: 'transport' };
59
+ return { error: errorMessage(err), code: 'transport' };
55
60
  }
56
61
  }
57
62
  };
@@ -1,14 +1,14 @@
1
1
  export type IPFamily = 4 | 6;
2
- export type LookupAddress = {
2
+ export interface LookupAddress {
3
3
  readonly address: string;
4
4
  readonly family: IPFamily;
5
- };
5
+ }
6
6
  export type LookupAllCb = (err: Error | null, addresses: ReadonlyArray<LookupAddress> | null) => void;
7
- export type BareLookupAllOpts = {
7
+ export interface BareLookupAllOpts {
8
8
  readonly all: true;
9
9
  readonly family?: IPFamily | 0;
10
10
  readonly hints?: number;
11
- };
11
+ }
12
12
  export type BareLookupAll = (hostname: string, opts: BareLookupAllOpts, cb: LookupAllCb) => void;
13
13
  export declare class BlockedAddressError extends Error {
14
14
  readonly code = "EBLOCKEDADDR";
@@ -2,4 +2,4 @@ export declare const TARGET_SAMPLE_RATE = 16000;
2
2
  export declare const WAV_HEADER_SIZE = 44;
3
3
  export declare const BITS_PER_SAMPLE = 16;
4
4
  export declare const CHANNELS = 1;
5
- export declare function buildWavBuffer(pcm: Buffer): Buffer;
5
+ export declare function buildWavBuffer(pcm: Uint8Array): Buffer;
@@ -1,24 +1,25 @@
1
+ import b4a from 'b4a';
1
2
  export const TARGET_SAMPLE_RATE = 16000;
2
3
  export const WAV_HEADER_SIZE = 44;
3
4
  export const BITS_PER_SAMPLE = 16;
4
5
  export const CHANNELS = 1;
5
6
  export function buildWavBuffer(pcm) {
6
7
  const dataSize = pcm.length;
7
- const buf = Buffer.alloc(WAV_HEADER_SIZE + dataSize);
8
+ const buf = b4a.alloc(WAV_HEADER_SIZE + dataSize);
8
9
  const blockAlign = CHANNELS * (BITS_PER_SAMPLE / 8);
9
- buf.write('RIFF', 0, 'ascii');
10
- buf.writeUInt32LE(WAV_HEADER_SIZE + dataSize - 8, 4);
11
- buf.write('WAVE', 8, 'ascii');
12
- buf.write('fmt ', 12, 'ascii');
13
- buf.writeUInt32LE(16, 16);
10
+ b4a.write(buf, 'RIFF', 0, 'ascii');
11
+ b4a.writeUInt32LE(buf, WAV_HEADER_SIZE + dataSize - 8, 4);
12
+ b4a.write(buf, 'WAVE', 8, 'ascii');
13
+ b4a.write(buf, 'fmt ', 12, 'ascii');
14
+ b4a.writeUInt32LE(buf, 16, 16);
14
15
  buf.writeUInt16LE(1, 20);
15
16
  buf.writeUInt16LE(CHANNELS, 22);
16
- buf.writeUInt32LE(TARGET_SAMPLE_RATE, 24);
17
- buf.writeUInt32LE(TARGET_SAMPLE_RATE * blockAlign, 28);
17
+ b4a.writeUInt32LE(buf, TARGET_SAMPLE_RATE, 24);
18
+ b4a.writeUInt32LE(buf, TARGET_SAMPLE_RATE * blockAlign, 28);
18
19
  buf.writeUInt16LE(blockAlign, 32);
19
20
  buf.writeUInt16LE(BITS_PER_SAMPLE, 34);
20
- buf.write('data', 36, 'ascii');
21
- buf.writeUInt32LE(dataSize, 40);
21
+ b4a.write(buf, 'data', 36, 'ascii');
22
+ b4a.writeUInt32LE(buf, dataSize, 40);
22
23
  buf.set(pcm, WAV_HEADER_SIZE);
23
24
  return buf;
24
25
  }
@@ -22,11 +22,13 @@ function structuredData(html) {
22
22
  const out = [];
23
23
  for (const match of html.matchAll(LDJSON_SCRIPT)) {
24
24
  const value = parseJson(match[1] ?? '');
25
- if (value === null)
25
+ if (value === null) {
26
26
  continue;
27
+ }
27
28
  walk(value, (key, text) => {
28
- if (STRUCTURED_PROSE_KEYS.has(key))
29
+ if (STRUCTURED_PROSE_KEYS.has(key)) {
29
30
  out.push(text);
31
+ }
30
32
  });
31
33
  }
32
34
  return out;
@@ -35,11 +37,13 @@ function hydrationProse(html) {
35
37
  const out = [];
36
38
  for (const match of html.matchAll(JSON_SCRIPT)) {
37
39
  const value = parseJson(match[1] ?? '');
38
- if (value === null)
40
+ if (value === null) {
39
41
  continue;
42
+ }
40
43
  walk(value, (_key, text) => {
41
- if (looksLikeProse(text))
44
+ if (looksLikeProse(text)) {
42
45
  out.push(text);
46
+ }
43
47
  });
44
48
  }
45
49
  return out;
@@ -51,13 +55,15 @@ function scriptLiteralProse(html) {
51
55
  if (literal.length < MIN_PROSE_CHARS)
52
56
  continue;
53
57
  const decoded = parseJson(literal);
54
- if (typeof decoded !== 'string')
58
+ if (typeof decoded !== 'string') {
55
59
  continue;
60
+ }
56
61
  const inner = decoded.trimStart().startsWith('{') ? parseJson(decoded) : null;
57
62
  if (inner !== null) {
58
63
  walk(inner, (_key, text) => {
59
- if (looksLikeProse(text))
64
+ if (looksLikeProse(text)) {
60
65
  out.push(text);
66
+ }
61
67
  });
62
68
  }
63
69
  else if (looksLikeProse(decoded.trim())) {
@@ -71,11 +77,13 @@ function metaTags(html) {
71
77
  const out = [];
72
78
  for (const match of html.matchAll(META_TAG)) {
73
79
  const tag = match[0];
74
- if (!META_ATTR.test(tag))
80
+ if (!META_ATTR.test(tag)) {
75
81
  continue;
82
+ }
76
83
  const content = META_CONTENT.exec(tag)?.[1];
77
- if (content)
84
+ if (content) {
78
85
  out.push(decodeHtmlEntities(content).trim());
86
+ }
79
87
  }
80
88
  return out;
81
89
  }
@@ -92,18 +100,21 @@ function parseJson(raw) {
92
100
  function walk(value, visit, key = '') {
93
101
  if (typeof value === 'string') {
94
102
  const text = value.trim();
95
- if (text)
103
+ if (text) {
96
104
  visit(key, text);
105
+ }
97
106
  return;
98
107
  }
99
108
  if (Array.isArray(value)) {
100
- for (const entry of value)
109
+ for (const entry of value) {
101
110
  walk(entry, visit, key);
111
+ }
102
112
  return;
103
113
  }
104
114
  if (value && typeof value === 'object') {
105
- for (const [k, v] of Object.entries(value))
115
+ for (const [k, v] of Object.entries(value)) {
106
116
  walk(v, visit, k);
117
+ }
107
118
  }
108
119
  }
109
120
  function looksLikeProse(text) {
@@ -112,8 +123,9 @@ function looksLikeProse(text) {
112
123
  if (/^(?:https?:|data:|\/|#|\.)/.test(text))
113
124
  return false;
114
125
  const letters = text.match(/\p{L}/gu)?.length ?? 0;
115
- if (letters / text.length <= 0.6)
126
+ if (letters / text.length <= 0.6) {
116
127
  return false;
128
+ }
117
129
  return text.includes(' ') || /[\u3000-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(text);
118
130
  }
119
131
  function dedupe(parts) {
@@ -25,16 +25,18 @@ export function extractPageText(html) {
25
25
  }
26
26
  function extractTitle(html) {
27
27
  const match = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
28
- if (!match?.[1])
28
+ if (!match?.[1]) {
29
29
  return null;
30
+ }
30
31
  const title = decodeHtmlEntities(stripHtmlTags(match[1])).replace(/\s+/g, ' ').trim();
31
32
  return title.length > 0 ? title : null;
32
33
  }
33
34
  function pickMainRegion(html) {
34
35
  for (const tag of ['main', 'article']) {
35
36
  const match = new RegExp(`<${tag}[\\s>][\\s\\S]*?</${tag}>`, 'i').exec(html);
36
- if (match)
37
+ if (match) {
37
38
  return match[0];
39
+ }
38
40
  }
39
41
  return html;
40
42
  }
@@ -9,11 +9,13 @@ export function pageImageUrls(html, pageUrl) {
9
9
  if (out.length >= MAX_PAGE_IMAGES)
10
10
  break;
11
11
  const tag = match[0];
12
- if (!IMAGE_ATTR.test(tag))
12
+ if (!IMAGE_ATTR.test(tag)) {
13
13
  continue;
14
+ }
14
15
  const content = META_CONTENT.exec(tag)?.[1];
15
- if (!content)
16
+ if (!content) {
16
17
  continue;
18
+ }
17
19
  let resolved;
18
20
  try {
19
21
  resolved = new URL(decodeHtmlEntities(content).trim(), pageUrl);
@@ -21,8 +23,9 @@ export function pageImageUrls(html, pageUrl) {
21
23
  catch {
22
24
  continue;
23
25
  }
24
- if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:')
26
+ if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') {
25
27
  continue;
28
+ }
26
29
  const href = resolved.toString();
27
30
  if (!out.some((seen) => seen.replace(/^https?:/, '') === href.replace(/^https?:/, ''))) {
28
31
  out.push(href);
@@ -36,8 +39,9 @@ export function imageFileName(url, mimeType) {
36
39
  last = decodeURIComponent(last);
37
40
  }
38
41
  catch { }
39
- if (/\.(png|jpe?g|gif|webp|avif)$/i.test(last))
42
+ if (/\.(png|jpe?g|gif|webp|avif)$/i.test(last)) {
40
43
  return last;
44
+ }
41
45
  const ext = mimeType.split('/')[1]?.split('+')[0] ?? 'img';
42
46
  return `${url.hostname}.${ext}`;
43
47
  }
@@ -1,5 +1,5 @@
1
- export type PageVideoRef = {
1
+ export interface PageVideoRef {
2
2
  readonly provider: string;
3
3
  readonly url: string;
4
- };
4
+ }
5
5
  export declare function pageVideoRef(html: string, pageUrl: URL): PageVideoRef | null;
@@ -6,11 +6,13 @@ const YOUTUBE_HOSTS = new Set(['youtube.com', 'youtube-nocookie.com', 'youtu.be'
6
6
  export function pageVideoRef(html, pageUrl) {
7
7
  for (const match of html.matchAll(META_TAG)) {
8
8
  const tag = match[0];
9
- if (!VIDEO_ATTR.test(tag))
9
+ if (!VIDEO_ATTR.test(tag)) {
10
10
  continue;
11
+ }
11
12
  const content = META_CONTENT.exec(tag)?.[1];
12
- if (!content)
13
+ if (!content) {
13
14
  continue;
15
+ }
14
16
  let resolved;
15
17
  try {
16
18
  resolved = new URL(decodeHtmlEntities(content).trim(), pageUrl);
@@ -18,8 +20,9 @@ export function pageVideoRef(html, pageUrl) {
18
20
  catch {
19
21
  continue;
20
22
  }
21
- if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:')
23
+ if (resolved.protocol !== 'http:' && resolved.protocol !== 'https:') {
22
24
  continue;
25
+ }
23
26
  return classify(resolved);
24
27
  }
25
28
  return null;
@@ -36,7 +39,8 @@ function classify(url) {
36
39
  }
37
40
  function watchUrl(url) {
38
41
  const embed = /^\/embed\/([\w-]+)/.exec(url.pathname);
39
- if (!embed)
42
+ if (!embed) {
40
43
  return url.toString();
44
+ }
41
45
  return `https://www.youtube.com/watch?v=${embed[1]}`;
42
46
  }
@@ -1,7 +1,7 @@
1
1
  import type { SearchEngine } from './types.ts';
2
- export type EngineEntry = {
2
+ export interface EngineEntry {
3
3
  readonly name: string;
4
4
  readonly fn: SearchEngine;
5
- };
5
+ }
6
6
  export declare const DEFAULT_MAX_RESULTS = 8;
7
7
  export declare const ENGINES: ReadonlyArray<EngineEntry>;
@@ -72,7 +72,7 @@ function parseRuFallback(html) {
72
72
  }
73
73
  export async function searchAol(query, fetcher, signal) {
74
74
  const url = `https://search.aol.com/aol/search?q=${encodeURIComponent(query)}`;
75
- const html = await fetchHtml(fetcher, url, undefined, signal);
75
+ const html = await fetchHtml(fetcher, url, { signal });
76
76
  const primary = parseAlgoBlocks(html);
77
77
  if (primary.length > 0) {
78
78
  return primary;
@@ -99,7 +99,7 @@ function parseH2Fallback(html) {
99
99
  }
100
100
  export async function searchBing(query, fetcher, signal) {
101
101
  const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
102
- const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': SAFARI_UA } }, signal);
102
+ const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': SAFARI_UA }, signal });
103
103
  if (html.includes('captcha') || html.includes('challenge/verify') || html.includes('Turnstile')) {
104
104
  throw new Error('Bing CAPTCHA detected');
105
105
  }
@@ -50,7 +50,10 @@ function parseLinkFallback(html) {
50
50
  }
51
51
  export async function searchBrave(query, fetcher, signal) {
52
52
  const url = `https://search.brave.com/search?q=${encodeURIComponent(query)}&source=web`;
53
- const html = await fetchHtml(fetcher, url, { cookies: { safesearch: 'off', useLocation: '0', summarizer: '0', country: 'us' } }, signal);
53
+ const html = await fetchHtml(fetcher, url, {
54
+ cookies: { safesearch: 'off', useLocation: '0', summarizer: '0', country: 'us' },
55
+ signal
56
+ });
54
57
  const primary = parseSnippetBlocks(html);
55
58
  if (primary.length > 0) {
56
59
  return primary;
@@ -98,8 +98,9 @@ export async function searchDuckDuckGo(query, fetcher, signal) {
98
98
  'Content-Type': 'application/x-www-form-urlencoded',
99
99
  Referer: 'https://html.duckduckgo.com/'
100
100
  },
101
- body
102
- }, signal);
101
+ body,
102
+ signal
103
+ });
103
104
  const primary = parseLinksMain(html);
104
105
  if (primary.length > 0) {
105
106
  return primary;
@@ -25,7 +25,7 @@ function extractTitle(anchorBody) {
25
25
  const titleMatch = anchorBody.match(/<(?:div|h3)[^>]*>([\s\S]*?)<\/(?:div|h3)>/i);
26
26
  return titleMatch ? clean(titleMatch[1] || '') : clean(anchorBody);
27
27
  }
28
- function parseAnchor(am, html, snippetPat, existing) {
28
+ function parseAnchor(am, { html, snippetPat, existing }) {
29
29
  const rawUrl = resolveGoogleUrl(am[1] || '');
30
30
  if (isSkippableUrl(rawUrl) || !rawUrl.startsWith('http')) {
31
31
  return null;
@@ -45,7 +45,7 @@ function parseDataVedAnchors(html) {
45
45
  const snippetPat = /<div[^>]*class="[^"]*VwiC3b[^"]*"[^>]*>([\s\S]*?)<\/div>/gi;
46
46
  const anchorPat = /<a[^>]*\shref="(\/url\?q=[^"]+|https?:\/\/[^"]+)"[^>]*data-ved="[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
47
47
  for (const am of html.matchAll(anchorPat)) {
48
- const parsed = parseAnchor(am, html, snippetPat, results);
48
+ const parsed = parseAnchor(am, { html, snippetPat, existing: results });
49
49
  if (parsed) {
50
50
  results.push({ ...parsed, position: results.length + 1 });
51
51
  }
@@ -80,7 +80,11 @@ function parseH3Fallback(html) {
80
80
  }
81
81
  export async function searchGoogle(query, fetcher, signal) {
82
82
  const url = `https://www.google.com/search?q=${encodeURIComponent(query)}&hl=en&ie=utf8&oe=utf8&filter=0`;
83
- const html = await fetchHtml(fetcher, url, { headers: { 'User-Agent': GOOGLE_GSA_UA, Accept: '*/*' }, cookies: { CONSENT: 'YES+' } }, signal);
83
+ const html = await fetchHtml(fetcher, url, {
84
+ headers: { 'User-Agent': GOOGLE_GSA_UA, Accept: '*/*' },
85
+ cookies: { CONSENT: 'YES+' },
86
+ signal
87
+ });
84
88
  if (html.includes('detected unusual traffic') || html.includes('g-recaptcha')) {
85
89
  throw new Error('Google CAPTCHA detected');
86
90
  }
@@ -4,11 +4,11 @@ function extractSearchItems(data) {
4
4
  if (typeof data !== 'object' || data === null) {
5
5
  return [];
6
6
  }
7
- const query = data.query;
7
+ const query = 'query' in data ? data.query : undefined;
8
8
  if (typeof query !== 'object' || query === null) {
9
9
  return [];
10
10
  }
11
- const search = query.search;
11
+ const search = 'search' in query ? query.search : undefined;
12
12
  if (!Array.isArray(search)) {
13
13
  return [];
14
14
  }
@@ -17,7 +17,8 @@ function extractSearchItems(data) {
17
17
  if (typeof entry !== 'object' || entry === null) {
18
18
  continue;
19
19
  }
20
- const { title, snippet } = entry;
20
+ const title = 'title' in entry ? entry.title : undefined;
21
+ const snippet = 'snippet' in entry ? entry.snippet : undefined;
21
22
  if (typeof title === 'string' && typeof snippet === 'string') {
22
23
  items.push({ title, snippet });
23
24
  }
@@ -26,7 +27,7 @@ function extractSearchItems(data) {
26
27
  }
27
28
  export async function searchWikipedia(query, fetcher, signal) {
28
29
  const apiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&format=json&srlimit=5&utf8=1`;
29
- const data = await fetchJson(fetcher, apiUrl, undefined, signal);
30
+ const data = await fetchJson(fetcher, apiUrl, { signal });
30
31
  const items = extractSearchItems(data);
31
32
  return items.map((item, idx) => ({
32
33
  title: item.title,
@@ -72,7 +72,7 @@ function parseRuFallback(html) {
72
72
  }
73
73
  export async function searchYahoo(query, fetcher, signal) {
74
74
  const url = `https://search.yahoo.com/search?p=${encodeURIComponent(query)}`;
75
- const html = await fetchHtml(fetcher, url, undefined, signal);
75
+ const html = await fetchHtml(fetcher, url, { signal });
76
76
  const primary = parseAlgoBlocks(html);
77
77
  if (primary.length > 0) {
78
78
  return primary;
@@ -8,18 +8,19 @@ export declare class TimeoutError extends Error {
8
8
  export declare class AbortError extends Error {
9
9
  constructor();
10
10
  }
11
- type FetchOptions = {
11
+ interface FetchOptions {
12
12
  readonly method?: string;
13
13
  readonly headers?: Readonly<Record<string, string>>;
14
14
  readonly body?: string;
15
15
  readonly cookies?: Readonly<Record<string, string>>;
16
16
  readonly accept?: string;
17
17
  readonly timeoutMs?: number;
18
- };
19
- export declare function fetchHtml(fetcher: Fetcher, url: string, options?: FetchOptions, signal?: BareAbortSignal): Promise<string>;
18
+ readonly signal?: BareAbortSignal;
19
+ }
20
+ export declare function fetchHtml(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<string>;
20
21
  export declare class JsonParseError extends Error {
21
22
  readonly cause?: unknown;
22
23
  constructor(message: string, cause?: unknown);
23
24
  }
24
- export declare function fetchJson(fetcher: Fetcher, url: string, options?: FetchOptions, signal?: BareAbortSignal): Promise<unknown>;
25
+ export declare function fetchJson(fetcher: Fetcher, url: string, options?: FetchOptions): Promise<unknown>;
25
26
  export {};