@nhtio/adk 1.20260924.0 → 1.20260928.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 (317) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/artifact_methods-Cg0go7Q4.mjs +1 -0
  3. package/artifact_methods-CxeKTocm.cjs +1 -0
  4. package/batteries/artifacts/ecmascript.cjs +1 -1
  5. package/batteries/artifacts/ecmascript.mjs +1 -1
  6. package/batteries/artifacts/toon.cjs +1 -1
  7. package/batteries/artifacts/toon.mjs +1 -1
  8. package/batteries/artifacts/xml.cjs +1 -1
  9. package/batteries/artifacts/xml.mjs +1 -1
  10. package/batteries/artifacts/yaml.cjs +1 -1
  11. package/batteries/artifacts/yaml.mjs +1 -1
  12. package/batteries/artifacts.cjs +4 -4
  13. package/batteries/artifacts.mjs +4 -4
  14. package/batteries/dev-tools/forge.cjs +3 -3
  15. package/batteries/dev-tools/forge.mjs +3 -3
  16. package/batteries/dev-tools.cjs +3 -3
  17. package/batteries/dev-tools.mjs +3 -3
  18. package/batteries/encoding.cjs +7 -7
  19. package/batteries/encoding.mjs +7 -7
  20. package/batteries/generation/gemini/adapter.cjs +1 -1
  21. package/batteries/generation/gemini/adapter.mjs +1 -1
  22. package/batteries/generation/local_diffusion/adapter.cjs +1 -1
  23. package/batteries/generation/local_diffusion/adapter.mjs +1 -1
  24. package/batteries/generation/openai/adapter.cjs +1 -1
  25. package/batteries/generation/openai/adapter.mjs +1 -1
  26. package/batteries/llm/anthropic_messages/adapter.cjs +6 -6
  27. package/batteries/llm/anthropic_messages/adapter.mjs +6 -6
  28. package/batteries/llm/anthropic_messages/count_tokens.cjs +1 -1
  29. package/batteries/llm/anthropic_messages/count_tokens.mjs +1 -1
  30. package/batteries/llm/anthropic_messages/helpers.cjs +3 -3
  31. package/batteries/llm/anthropic_messages/helpers.mjs +3 -3
  32. package/batteries/llm/anthropic_messages/validation.cjs +2 -2
  33. package/batteries/llm/anthropic_messages/validation.mjs +2 -2
  34. package/batteries/llm/anthropic_messages.cjs +2 -2
  35. package/batteries/llm/anthropic_messages.mjs +2 -2
  36. package/batteries/llm/bedrock_converse/adapter.cjs +162 -21
  37. package/batteries/llm/bedrock_converse/adapter.cjs.map +1 -1
  38. package/batteries/llm/bedrock_converse/adapter.mjs +162 -21
  39. package/batteries/llm/bedrock_converse/adapter.mjs.map +1 -1
  40. package/batteries/llm/bedrock_converse/helpers.cjs +1 -1
  41. package/batteries/llm/bedrock_converse/helpers.mjs +1 -1
  42. package/batteries/llm/bedrock_converse/validation.cjs +2 -2
  43. package/batteries/llm/bedrock_converse/validation.mjs +2 -2
  44. package/batteries/llm/bedrock_converse.cjs +2 -2
  45. package/batteries/llm/bedrock_converse.mjs +2 -2
  46. package/batteries/llm/claude_code_cli/adapter.cjs +66 -25
  47. package/batteries/llm/claude_code_cli/adapter.cjs.map +1 -1
  48. package/batteries/llm/claude_code_cli/adapter.mjs +66 -25
  49. package/batteries/llm/claude_code_cli/adapter.mjs.map +1 -1
  50. package/batteries/llm/claude_code_cli/helpers.cjs +3 -3
  51. package/batteries/llm/claude_code_cli/helpers.mjs +3 -3
  52. package/batteries/llm/claude_code_cli/line_queue.d.ts +39 -0
  53. package/batteries/llm/claude_code_cli/message_id_state.d.ts +48 -0
  54. package/batteries/llm/claude_code_cli/types.d.ts +76 -0
  55. package/batteries/llm/claude_code_cli/validation.cjs +5 -2
  56. package/batteries/llm/claude_code_cli/validation.cjs.map +1 -1
  57. package/batteries/llm/claude_code_cli/validation.mjs +5 -2
  58. package/batteries/llm/claude_code_cli/validation.mjs.map +1 -1
  59. package/batteries/llm/claude_code_cli/wire.cjs.map +1 -1
  60. package/batteries/llm/claude_code_cli/wire.d.ts +45 -1
  61. package/batteries/llm/claude_code_cli/wire.mjs.map +1 -1
  62. package/batteries/llm/claude_code_cli/wrapper.d.ts +5 -4
  63. package/batteries/llm/claude_code_cli.cjs +2 -2
  64. package/batteries/llm/claude_code_cli.mjs +2 -2
  65. package/batteries/llm/gemini_generate_content/adapter.cjs +160 -19
  66. package/batteries/llm/gemini_generate_content/adapter.cjs.map +1 -1
  67. package/batteries/llm/gemini_generate_content/adapter.mjs +160 -19
  68. package/batteries/llm/gemini_generate_content/adapter.mjs.map +1 -1
  69. package/batteries/llm/gemini_generate_content/helpers.cjs +1 -1
  70. package/batteries/llm/gemini_generate_content/helpers.mjs +1 -1
  71. package/batteries/llm/gemini_generate_content/validation.cjs +2 -2
  72. package/batteries/llm/gemini_generate_content/validation.mjs +2 -2
  73. package/batteries/llm/gemini_generate_content.cjs +1 -1
  74. package/batteries/llm/gemini_generate_content.mjs +1 -1
  75. package/batteries/llm/litert_lm/adapter.cjs +6 -6
  76. package/batteries/llm/litert_lm/adapter.mjs +6 -6
  77. package/batteries/llm/litert_lm/helpers.cjs +3 -3
  78. package/batteries/llm/litert_lm/helpers.mjs +3 -3
  79. package/batteries/llm/litert_lm/validation.cjs +2 -2
  80. package/batteries/llm/litert_lm/validation.mjs +2 -2
  81. package/batteries/llm/litert_lm.cjs +1 -1
  82. package/batteries/llm/litert_lm.mjs +1 -1
  83. package/batteries/llm/ollama/adapter.cjs +6 -6
  84. package/batteries/llm/ollama/adapter.mjs +6 -6
  85. package/batteries/llm/ollama/helpers.cjs +4 -4
  86. package/batteries/llm/ollama/helpers.mjs +4 -4
  87. package/batteries/llm/ollama/validation.cjs +2 -2
  88. package/batteries/llm/ollama/validation.mjs +2 -2
  89. package/batteries/llm/ollama.cjs +2 -2
  90. package/batteries/llm/ollama.mjs +2 -2
  91. package/batteries/llm/openai_chat_completions/adapter.cjs +6 -6
  92. package/batteries/llm/openai_chat_completions/adapter.mjs +6 -6
  93. package/batteries/llm/openai_chat_completions/helpers.cjs +4 -4
  94. package/batteries/llm/openai_chat_completions/helpers.mjs +4 -4
  95. package/batteries/llm/openai_chat_completions/validation.cjs +2 -2
  96. package/batteries/llm/openai_chat_completions/validation.mjs +2 -2
  97. package/batteries/llm/openai_chat_completions.cjs +2 -2
  98. package/batteries/llm/openai_chat_completions.mjs +2 -2
  99. package/batteries/llm/openai_responses/adapter.cjs +6 -6
  100. package/batteries/llm/openai_responses/adapter.mjs +6 -6
  101. package/batteries/llm/openai_responses/helpers.cjs +3 -3
  102. package/batteries/llm/openai_responses/helpers.mjs +3 -3
  103. package/batteries/llm/openai_responses/validation.cjs +2 -2
  104. package/batteries/llm/openai_responses/validation.mjs +2 -2
  105. package/batteries/llm/openai_responses.cjs +2 -2
  106. package/batteries/llm/openai_responses.mjs +2 -2
  107. package/batteries/llm/transformers_js/adapter.cjs +6 -6
  108. package/batteries/llm/transformers_js/adapter.mjs +6 -6
  109. package/batteries/llm/transformers_js/helpers.cjs +3 -3
  110. package/batteries/llm/transformers_js/helpers.mjs +3 -3
  111. package/batteries/llm/transformers_js/validation.cjs +2 -2
  112. package/batteries/llm/transformers_js/validation.mjs +2 -2
  113. package/batteries/llm/transformers_js.cjs +1 -1
  114. package/batteries/llm/transformers_js.mjs +1 -1
  115. package/batteries/llm/webllm_chat_completions/adapter.cjs +6 -6
  116. package/batteries/llm/webllm_chat_completions/adapter.mjs +6 -6
  117. package/batteries/llm/webllm_chat_completions/helpers.cjs +1 -1
  118. package/batteries/llm/webllm_chat_completions/helpers.mjs +1 -1
  119. package/batteries/llm/webllm_chat_completions/validation.cjs +2 -2
  120. package/batteries/llm/webllm_chat_completions/validation.mjs +2 -2
  121. package/batteries/llm/webllm_chat_completions.cjs +2 -2
  122. package/batteries/llm/webllm_chat_completions.mjs +2 -2
  123. package/batteries/llm.cjs +2 -2
  124. package/batteries/llm.mjs +2 -2
  125. package/batteries/media/forge.cjs +4 -4
  126. package/batteries/media/forge.mjs +4 -4
  127. package/batteries/media/lint.cjs +1 -1
  128. package/batteries/media/lint.mjs +1 -1
  129. package/batteries/orchestration/dispatch_reasoner.cjs +4 -4
  130. package/batteries/orchestration/dispatch_reasoner.mjs +4 -4
  131. package/batteries/orchestration/executor.cjs +2 -2
  132. package/batteries/orchestration/executor.mjs +2 -2
  133. package/batteries/orchestration/forge.cjs +4 -4
  134. package/batteries/orchestration/forge.mjs +4 -4
  135. package/batteries/orchestration/validation.cjs +2 -2
  136. package/batteries/orchestration/validation.mjs +2 -2
  137. package/batteries/orchestration.cjs +4 -4
  138. package/batteries/orchestration.mjs +4 -4
  139. package/batteries/sandbox/js.cjs +1 -1
  140. package/batteries/sandbox/js.mjs +1 -1
  141. package/batteries/sandbox/tools.cjs +1 -1
  142. package/batteries/sandbox/tools.mjs +1 -1
  143. package/batteries/sandbox.cjs +3 -3
  144. package/batteries/sandbox.mjs +3 -3
  145. package/batteries/skills.cjs +9 -9
  146. package/batteries/skills.mjs +9 -9
  147. package/batteries/tools/_shared.cjs +1 -1
  148. package/batteries/tools/_shared.mjs +1 -1
  149. package/batteries/tools/color.cjs +2 -2
  150. package/batteries/tools/color.mjs +2 -2
  151. package/batteries/tools/comparison.cjs +2 -2
  152. package/batteries/tools/comparison.mjs +2 -2
  153. package/batteries/tools/data_structure.cjs +2 -2
  154. package/batteries/tools/data_structure.mjs +2 -2
  155. package/batteries/tools/datetime_extended.cjs +3 -3
  156. package/batteries/tools/datetime_extended.mjs +3 -3
  157. package/batteries/tools/datetime_math.cjs +3 -3
  158. package/batteries/tools/datetime_math.mjs +3 -3
  159. package/batteries/tools/encoding.cjs +2 -2
  160. package/batteries/tools/encoding.mjs +2 -2
  161. package/batteries/tools/formatting.cjs +2 -2
  162. package/batteries/tools/formatting.mjs +2 -2
  163. package/batteries/tools/geo_basics.cjs +2 -2
  164. package/batteries/tools/geo_basics.mjs +2 -2
  165. package/batteries/tools/math.cjs +2 -2
  166. package/batteries/tools/math.mjs +2 -2
  167. package/batteries/tools/memory.cjs +5 -5
  168. package/batteries/tools/memory.mjs +5 -5
  169. package/batteries/tools/parsing.cjs +3 -3
  170. package/batteries/tools/parsing.mjs +3 -3
  171. package/batteries/tools/retrievables.cjs +5 -5
  172. package/batteries/tools/retrievables.mjs +5 -5
  173. package/batteries/tools/scrapper.cjs +1 -1
  174. package/batteries/tools/scrapper.mjs +1 -1
  175. package/batteries/tools/searxng.cjs +1 -1
  176. package/batteries/tools/searxng.mjs +1 -1
  177. package/batteries/tools/standing_instructions.cjs +3 -3
  178. package/batteries/tools/standing_instructions.mjs +3 -3
  179. package/batteries/tools/statistics.cjs +3 -3
  180. package/batteries/tools/statistics.mjs +3 -3
  181. package/batteries/tools/string_processing.cjs +2 -2
  182. package/batteries/tools/string_processing.mjs +2 -2
  183. package/batteries/tools/structured_data.cjs +2 -2
  184. package/batteries/tools/structured_data.mjs +2 -2
  185. package/batteries/tools/text_analysis.cjs +3 -3
  186. package/batteries/tools/text_analysis.mjs +3 -3
  187. package/batteries/tools/text_comparison.cjs +2 -2
  188. package/batteries/tools/text_comparison.mjs +2 -2
  189. package/batteries/tools/time.cjs +3 -3
  190. package/batteries/tools/time.mjs +3 -3
  191. package/batteries/tools/unit_conversion.cjs +2 -2
  192. package/batteries/tools/unit_conversion.mjs +2 -2
  193. package/batteries/tools.cjs +2 -2
  194. package/batteries/tools.mjs +2 -2
  195. package/batteries/validation.cjs +4 -4
  196. package/batteries/validation.mjs +4 -4
  197. package/batteries/vector/conformance.cjs +59 -0
  198. package/batteries/vector/conformance.cjs.map +1 -1
  199. package/batteries/vector/conformance.mjs +59 -0
  200. package/batteries/vector/conformance.mjs.map +1 -1
  201. package/batteries/vector/lancedb.cjs +1 -1
  202. package/batteries/vector/lancedb.cjs.map +1 -1
  203. package/batteries/vector/lancedb.mjs +1 -1
  204. package/batteries/vector/lancedb.mjs.map +1 -1
  205. package/batteries/vector/retrievable.cjs +1 -1
  206. package/batteries/vector/retrievable.mjs +1 -1
  207. package/batteries/vector/sqlite_vec/index.d.ts +1 -1
  208. package/batteries/vector/sqlite_vec.cjs +19 -30
  209. package/batteries/vector/sqlite_vec.cjs.map +1 -1
  210. package/batteries/vector/sqlite_vec.mjs +20 -31
  211. package/batteries/vector/sqlite_vec.mjs.map +1 -1
  212. package/batteries.cjs +4 -4
  213. package/batteries.mjs +4 -4
  214. package/{chat_common-B7fvGTKv.cjs → chat_common-CNayzS1V.cjs} +2 -2
  215. package/{chat_common-B7fvGTKv.cjs.map → chat_common-CNayzS1V.cjs.map} +1 -1
  216. package/{chat_common-CeoHwSsz.mjs → chat_common-DKw8FDol.mjs} +2 -2
  217. package/{chat_common-CeoHwSsz.mjs.map → chat_common-DKw8FDol.mjs.map} +1 -1
  218. package/claude-code-cli-wrapper.cjs +104 -22
  219. package/claude-code-cli-wrapper.cjs.map +1 -1
  220. package/claude-code-cli-wrapper.mjs +104 -22
  221. package/claude-code-cli-wrapper.mjs.map +1 -1
  222. package/{common-7gCR3zQu.cjs → common-CqetsItm.cjs} +7 -7
  223. package/{common-7gCR3zQu.cjs.map → common-CqetsItm.cjs.map} +1 -1
  224. package/{common-B5maOoF_.mjs → common-oI1niK9Z.mjs} +7 -7
  225. package/{common-B5maOoF_.mjs.map → common-oI1niK9Z.mjs.map} +1 -1
  226. package/common.cjs +7 -7
  227. package/common.mjs +7 -7
  228. package/{dispatch_runner-DO7jo_gl.mjs → dispatch_runner-BZDXUe0G.mjs} +5 -5
  229. package/{dispatch_runner-DO7jo_gl.mjs.map → dispatch_runner-BZDXUe0G.mjs.map} +1 -1
  230. package/{dispatch_runner-FlriTyoq.cjs → dispatch_runner-DDUxXJC3.cjs} +5 -5
  231. package/{dispatch_runner-FlriTyoq.cjs.map → dispatch_runner-DDUxXJC3.cjs.map} +1 -1
  232. package/dispatch_runner.cjs +1 -1
  233. package/dispatch_runner.mjs +1 -1
  234. package/{ecmascript-bgwVyZG1.mjs → ecmascript-B9Z2C24U.mjs} +3 -3
  235. package/{ecmascript-bgwVyZG1.mjs.map → ecmascript-B9Z2C24U.mjs.map} +1 -1
  236. package/{ecmascript-BydSB03Y.cjs → ecmascript-BYfmjg7y.cjs} +3 -3
  237. package/{ecmascript-BydSB03Y.cjs.map → ecmascript-BYfmjg7y.cjs.map} +1 -1
  238. package/eslint/rules.cjs +1 -1
  239. package/eslint/rules.mjs +1 -1
  240. package/eslint.cjs +2 -2
  241. package/eslint.mjs +2 -2
  242. package/exceptions.cjs +1 -1
  243. package/exceptions.mjs +1 -1
  244. package/forge.cjs +2 -2
  245. package/forge.mjs +2 -2
  246. package/guards.cjs +7 -7
  247. package/guards.mjs +7 -7
  248. package/{helpers-Dcp4CrOz.cjs → helpers-DPlh_7Bn.cjs} +3 -3
  249. package/{helpers-Dcp4CrOz.cjs.map → helpers-DPlh_7Bn.cjs.map} +1 -1
  250. package/{helpers-C1T0DV8Q.mjs → helpers-mbn_EsDV.mjs} +3 -3
  251. package/{helpers-C1T0DV8Q.mjs.map → helpers-mbn_EsDV.mjs.map} +1 -1
  252. package/index.cjs +11 -11
  253. package/index.mjs +11 -11
  254. package/mcp/adk-docs-corpus.json +2 -2
  255. package/package.json +425 -425
  256. package/{retrievable-BBwiACtw.cjs → retrievable-Bq-O3k6r.cjs} +3 -3
  257. package/{retrievable-BBwiACtw.cjs.map → retrievable-Bq-O3k6r.cjs.map} +1 -1
  258. package/{retrievable-CrGKx4sc.mjs → retrievable-CXTAUuuv.mjs} +3 -3
  259. package/{retrievable-CrGKx4sc.mjs.map → retrievable-CXTAUuuv.mjs.map} +1 -1
  260. package/{scrapper-BibKj8qE.cjs → scrapper-B8xG9lTD.cjs} +4 -4
  261. package/{scrapper-BibKj8qE.cjs.map → scrapper-B8xG9lTD.cjs.map} +1 -1
  262. package/{scrapper-_JzgbLG5.mjs → scrapper-DiQ5FfDL.mjs} +4 -4
  263. package/{scrapper-_JzgbLG5.mjs.map → scrapper-DiQ5FfDL.mjs.map} +1 -1
  264. package/{searxng-D-nA2gEI.mjs → searxng-BFTJ6rTE.mjs} +4 -4
  265. package/{searxng-D-nA2gEI.mjs.map → searxng-BFTJ6rTE.mjs.map} +1 -1
  266. package/{searxng-C6xrLW-v.cjs → searxng-EJhWRnIE.cjs} +4 -4
  267. package/{searxng-C6xrLW-v.cjs.map → searxng-EJhWRnIE.cjs.map} +1 -1
  268. package/server.json +2 -2
  269. package/skills/adk-assembly/SKILL.md +2 -2
  270. package/{spooled_artifact-BLgbGT8b.cjs → spooled_artifact---DmicFW.cjs} +384 -384
  271. package/spooled_artifact---DmicFW.cjs.map +1 -0
  272. package/{spooled_artifact-BkOe4-j-.mjs → spooled_artifact-Bw_7swIp.mjs} +385 -385
  273. package/spooled_artifact-Bw_7swIp.mjs.map +1 -0
  274. package/spooled_artifact.cjs +2 -2
  275. package/spooled_artifact.mjs +2 -2
  276. package/{spooled_markdown_artifact-8zdejfcJ.cjs → spooled_markdown_artifact-Rjzj-kyh.cjs} +3 -3
  277. package/{spooled_markdown_artifact-8zdejfcJ.cjs.map → spooled_markdown_artifact-Rjzj-kyh.cjs.map} +1 -1
  278. package/{spooled_markdown_artifact-Dnl-lCGv.mjs → spooled_markdown_artifact-vd6eNtIP.mjs} +3 -3
  279. package/{spooled_markdown_artifact-Dnl-lCGv.mjs.map → spooled_markdown_artifact-vd6eNtIP.mjs.map} +1 -1
  280. package/{thought-BrQt6HAb.mjs → thought-CxSaF6Xf.mjs} +3 -3
  281. package/{thought-BrQt6HAb.mjs.map → thought-CxSaF6Xf.mjs.map} +1 -1
  282. package/{thought-COeZTQ-B.cjs → thought-Dp3sMWW6.cjs} +3 -3
  283. package/{thought-COeZTQ-B.cjs.map → thought-Dp3sMWW6.cjs.map} +1 -1
  284. package/{tokenizable-DDIdXbJN.cjs → tokenizable-BcodRtoW.cjs} +29 -29
  285. package/{tokenizable-DDIdXbJN.cjs.map → tokenizable-BcodRtoW.cjs.map} +1 -1
  286. package/{tokenizable-xPXj1AQX.mjs → tokenizable-VB4Av8GF.mjs} +30 -30
  287. package/{tokenizable-xPXj1AQX.mjs.map → tokenizable-VB4Av8GF.mjs.map} +1 -1
  288. package/{tool_call-JbjeUpRK.cjs → tool_call-B9px4W9k.cjs} +4 -4
  289. package/{tool_call-JbjeUpRK.cjs.map → tool_call-B9px4W9k.cjs.map} +1 -1
  290. package/{tool_call-8crmZsO8.mjs → tool_call-Cfaj-6zK.mjs} +4 -4
  291. package/{tool_call-8crmZsO8.mjs.map → tool_call-Cfaj-6zK.mjs.map} +1 -1
  292. package/{tools-BC4NJqZD.mjs → tools-D0MBDh2R.mjs} +6 -6
  293. package/{tools-BC4NJqZD.mjs.map → tools-D0MBDh2R.mjs.map} +1 -1
  294. package/{tools-D4mdzi0a.cjs → tools-aNbi6JyA.cjs} +6 -6
  295. package/{tools-D4mdzi0a.cjs.map → tools-aNbi6JyA.cjs.map} +1 -1
  296. package/{toon-BA9Q7NHG.mjs → toon-BSTQnHed.mjs} +5 -5
  297. package/{toon-BA9Q7NHG.mjs.map → toon-BSTQnHed.mjs.map} +1 -1
  298. package/{toon-b-0pFKZ8.cjs → toon-DmLwZ6H1.cjs} +5 -5
  299. package/{toon-b-0pFKZ8.cjs.map → toon-DmLwZ6H1.cjs.map} +1 -1
  300. package/{turn_runner-BmVXcBZ2.mjs → turn_runner-BHHD6h77.mjs} +5 -5
  301. package/{turn_runner-BmVXcBZ2.mjs.map → turn_runner-BHHD6h77.mjs.map} +1 -1
  302. package/{turn_runner-DX-3s3ih.cjs → turn_runner-Dj7JsUTQ.cjs} +5 -5
  303. package/{turn_runner-DX-3s3ih.cjs.map → turn_runner-Dj7JsUTQ.cjs.map} +1 -1
  304. package/turn_runner.cjs +1 -1
  305. package/turn_runner.mjs +1 -1
  306. package/{xml-B1JIGLpU.mjs → xml-CYseDztH.mjs} +5 -5
  307. package/{xml-B1JIGLpU.mjs.map → xml-CYseDztH.mjs.map} +1 -1
  308. package/{xml-fvwTsoRv.cjs → xml-zJul8jMQ.cjs} +5 -5
  309. package/{xml-fvwTsoRv.cjs.map → xml-zJul8jMQ.cjs.map} +1 -1
  310. package/{yaml-1mP4uxdf.mjs → yaml-BPBoiOjx.mjs} +5 -5
  311. package/{yaml-1mP4uxdf.mjs.map → yaml-BPBoiOjx.mjs.map} +1 -1
  312. package/{yaml-DPUTumla.cjs → yaml-D0PzoRAr.cjs} +5 -5
  313. package/{yaml-DPUTumla.cjs.map → yaml-D0PzoRAr.cjs.map} +1 -1
  314. package/artifact_methods-7KatxCSd.cjs +0 -1
  315. package/artifact_methods-q52ETLwI.mjs +0 -1
  316. package/spooled_artifact-BLgbGT8b.cjs.map +0 -1
  317. package/spooled_artifact-BkOe4-j-.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"scrapper-_JzgbLG5.mjs","names":[],"sources":["../src/batteries/tools/scrapper/exceptions.ts","../src/batteries/tools/scrapper/shared.ts","../src/batteries/tools/scrapper/index.ts"],"sourcesContent":["/**\n * Battery-scoped exception constructors for the Scrapper web-extraction tool.\n *\n * @remarks\n * Battery-scoped exception classes owned by the Scrapper tool battery (not the ADK core). Minted\n * via `createException` from `@nhtio/adk/factories` and re-exported from the battery's barrel\n * (`@nhtio/adk/batteries/tools/scrapper`). This file intentionally carries **no** `@module` tag:\n * it is an internal sibling relative-imported by `index.ts`, so it does not mint its own\n * `…/scrapper/exceptions` entrypoint (whose `exceptions` leaf basename would collide with sibling\n * batteries under the `vite-plugin-dts` rolled-up `.d.ts` rule).\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when a Scrapper factory receives invalid configuration.\n *\n * @remarks\n * Fatal: config bugs (missing/unparseable `instanceUrl`, a bad `artifact` resolver, or an async\n * resolver passed to a `*Sync` factory) fail loud at factory-call time rather than at first scrape.\n */\nexport const E_INVALID_SCRAPPER_CONFIG = createException<[string]>(\n 'E_INVALID_SCRAPPER_CONFIG',\n 'Invalid Scrapper tool config: %s',\n 'E_INVALID_SCRAPPER_CONFIG',\n 529,\n true\n)\n","/**\n * Internal core shared by both Scrapper verbs (article / links).\n *\n * @remarks\n * No `@module` tag — this is a sibling of `index.ts`, relative-imported, not its own entrypoint.\n * Houses the per-parameter disposition machinery (schema building from `fixed`/`defaults`), the\n * snake→kebab wire mapping, the request/response contexts, and the `fetch`+pipeline handler core.\n * Generic harness helpers (artifact/header resolution, pipeline runners) come from `../_shared`.\n */\n\nimport { Tool } from '@nhtio/adk/forge'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport { Middleware } from '@nhtio/middleware'\nimport { E_INVALID_SCRAPPER_CONFIG } from './exceptions'\nimport {\n resolveHeaders,\n makeShortCircuit,\n isShortCircuit,\n runInputPipeline,\n runOutputPipeline,\n runToolGate,\n type ToolGateFn,\n type ToolHeaders,\n type ToolHeadersResolver,\n type SpooledArtifactCtor,\n type MiddlewareFn,\n} from '../_shared'\nimport type { Schema } from '@nhtio/validation'\nimport type { NextFn } from '@nhtio/middleware'\n\nconst DEFAULT_REQUEST_TIMEOUT = 65_000\n\n/** Throw the battery-scoped config error. */\nexport const failConfig = (reason: string): never => {\n throw new E_INVALID_SCRAPPER_CONFIG([reason])\n}\n\n// ── Parameter specs (per-parameter disposition) ──────────────────────────────\n\n/** The wire type of a Scrapper query parameter — controls serialisation. */\nexport type ScrapperParamType = 'string' | 'number' | 'boolean'\n\n/**\n * One curated, model-facing Scrapper parameter: its snake_case key (used in the model schema and\n * in `fixed`/`defaults`), its kebab-case wire name, its type, the base validator, and a description.\n */\nexport interface ScrapperParamSpec {\n /** snake_case key as seen by the model and in `config.fixed` / `config.defaults`. */\n key: string\n /** kebab-case name sent to the Scrapper API. */\n wire: string\n /** Wire type, controlling string/number/boolean serialisation. */\n type: ScrapperParamType\n /** Base `@nhtio/validation` schema (no `.required()`/`.default()`/`.optional()` applied yet). */\n schema: Schema\n /** Human-readable description surfaced to the model. */\n description: string\n}\n\n/** Serialise a parameter value to its wire string. */\nconst toWire = (value: unknown): string => String(value)\n\n/**\n * Build the model-facing input schema from a verb's param specs and the factory's disposition.\n * `url` is always required. A `fixed` param is omitted (the model can't set it); a `defaults` param\n * gets `.default(value)`; everything else is `.optional()`.\n */\nexport const buildScrapperSchema = (\n specs: ScrapperParamSpec[],\n fixed: Record<string, unknown> | undefined,\n defaults: Record<string, unknown> | undefined,\n extra: Record<string, Schema> = {}\n): Schema => {\n const shape: Record<string, Schema> = {\n url: validator.string().required().description('The absolute URL of the page to load.'),\n }\n for (const spec of specs) {\n if (fixed && spec.key in fixed) continue // pinned → not model-visible\n let sch = spec.schema\n if (defaults && spec.key in defaults) {\n sch = sch.default(defaults[spec.key] as never)\n } else {\n sch = sch.optional()\n }\n shape[spec.key] = sch.description(spec.description)\n }\n return validator.object({ ...shape, ...extra })\n}\n\n/**\n * Assemble the wire-kebab query params for one request: each spec's value is `fixed` (if pinned)\n * else the validated model/default value; then `fixedQuery` raw passthrough is layered on. `url`\n * is handled separately (it is the search target, never pinned).\n *\n * @remarks\n * An empty string (`''`) is deliberately treated the same as `undefined`/`null` and never\n * forwarded — this is what lets the schema's `.allow('')` on optional string specs actually\n * mean \"don't set this,\" instead of sending e.g. `user-agent=` to the wire. This applies\n * generically to every spec (no per-spec-type branching), including a `fixed`-pinned value that\n * happens to be `''` — an explicitly pinned empty string still means \"don't send it.\"\n */\nexport const buildWireParams = (\n args: Record<string, unknown>,\n specs: ScrapperParamSpec[],\n fixed: Record<string, unknown> | undefined,\n fixedQuery: Record<string, string> | undefined\n): Record<string, string> => {\n const out: Record<string, string> = {}\n for (const spec of specs) {\n const value = fixed && spec.key in fixed ? fixed[spec.key] : args[spec.key]\n const isEmptyString = typeof value === 'string' && value.length === 0\n if (value !== undefined && value !== null && !isEmptyString) out[spec.wire] = toWire(value)\n }\n for (const [k, v] of Object.entries(fixedQuery ?? {})) out[k] = v\n return out\n}\n\n// ── Contexts ─────────────────────────────────────────────────────────────────\n\n/**\n * Mutable context handed to each input-pipeline stage **before** the HTTP request is sent.\n * Identical for both verbs.\n */\nexport interface ScrapperRequestContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The target page URL (the `url` argument). Mutable. */\n url: string\n /** Wire-kebab query params (everything except `url`). Mutable. */\n params: Record<string, string>\n /** Resolved request headers sent to the SCRAPPER INSTANCE (auth). Mutable. */\n headers: ToolHeaders\n /** The Scrapper instance base URL (read-only). */\n readonly instanceUrl: string\n /** Cross-stage scratch space; also carried onto the response context. */\n readonly stash: Map<string, unknown>\n /** Skip the fetch and return `result` verbatim as the tool's output (e.g. a cache hit). */\n shortCircuit(result: string): void\n}\n\n/**\n * Mutable context handed to each output-pipeline stage **after** the response JSON is parsed.\n *\n * @typeParam R - The verb's normalised result type (article object or links payload).\n */\nexport interface ScrapperResponseContext<R> {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The request context as it was sent (post-input-pipeline). */\n readonly request: ScrapperRequestContext\n /** The parsed Scrapper JSON body. Mutable (used when `format` is `raw`). */\n raw: unknown\n /** The normalised result. Mutable — reshape, redact, enrich. */\n result: R\n /** The effective payload shape for this call. */\n format: 'normalized' | 'raw'\n /** When set, used verbatim as the tool's output (overrides serialisation). */\n output?: string\n /** Cross-stage scratch space; carried over from the request context. */\n readonly stash: Map<string, unknown>\n}\n\n/** An input-pipeline stage. Onion middleware over {@link ScrapperRequestContext}. */\nexport type ScrapperInputMiddlewareFn = (\n ctx: ScrapperRequestContext,\n next: NextFn\n) => void | Promise<void>\n\n/** An output-pipeline stage over a verb's {@link ScrapperResponseContext}. */\nexport type ScrapperOutputMiddlewareFn<R> = (\n ctx: ScrapperResponseContext<R>,\n next: NextFn\n) => void | Promise<void>\n\n// ── Config (shared shape; `artifact` variant supplied by the verb factory) ────\n\n/** Configuration common to every Scrapper factory. `A` is the accepted `artifact` resolver type. */\nexport interface ScrapperBaseConfig<P, R, A> {\n /** Base URL of the Scrapper instance, e.g. `https://scrapper.example.org`. Required. */\n instanceUrl: string\n /** Headers sent to the Scrapper INSTANCE for auth (X-API-Key / Basic) — static or resolver. */\n headers?: ToolHeaders | ToolHeadersResolver\n /** The tool's own `fetch` AbortController timeout in ms. Default `65_000` (> Scrapper's 60s browser default). */\n requestTimeoutMs?: number\n /** Output shape. `normalized`/`raw` pin it; `either` (default) exposes a `format` arg to the model. */\n resultFormat?: 'normalized' | 'raw' | 'either'\n /** Spool-artifact resolver for the output. Default `() => SpooledJsonArtifact`. */\n artifact?: A\n /** Tool name override. */\n name?: string\n /** Tool description override. */\n description?: string\n /** Pinned params — sent always, removed from the model schema. */\n fixed?: Partial<P>\n /** Model-overridable default param values. */\n defaults?: Partial<P>\n /** Raw, un-modeled wire params (kebab keys) — always sent, never model-visible. Keeps the battery generic. */\n fixedQuery?: Record<string, string>\n /**\n * Optional per-call gate run before the HTTP request — the seam for human-approval/RBAC\n * flows built on `ctx.waitFor` (the ADK gates primitive). Throwing aborts the call through\n * the standard tool-error path. Scraping reaches the network on the agent's behalf, which\n * makes every call a candidate for gating.\n */\n gate?: ToolGateFn\n /** Stages run before the HTTP request. See {@link ScrapperRequestContext}. */\n inputPipeline?: ScrapperInputMiddlewareFn[]\n /** Stages run after the response is parsed. See {@link ScrapperResponseContext}. */\n outputPipeline?: ScrapperOutputMiddlewareFn<R>[]\n}\n\n/** Verb-specific wiring passed to {@link assembleScrapperTool}. */\nexport interface ScrapperVerb<R> {\n /** Scrapper endpoint path, e.g. `/api/article`. */\n endpoint: string\n /** The curated param specs for this verb. */\n specs: ScrapperParamSpec[]\n /** Default tool name (`scrapper_article` / `scrapper_links`). */\n defaultName: string\n /** Default tool description. */\n defaultDescription: string\n /** Map a parsed Scrapper body to the verb's normalised result. */\n normalize: (body: Record<string, unknown>) => R\n}\n\n/** Parse a Scrapper error body (`{ detail: [{ msg }] }`) into a single message, best-effort. */\nconst parseScrapperError = (body: unknown, status: number, statusText: string): string => {\n if (body && typeof body === 'object' && Array.isArray((body as { detail?: unknown }).detail)) {\n const detail = (body as { detail: Array<{ msg?: unknown; loc?: unknown }> }).detail\n const msgs = detail\n .map((d) => {\n const loc = Array.isArray(d.loc) ? d.loc.join('.') : undefined\n const msg = typeof d.msg === 'string' ? d.msg : undefined\n if (msg && loc) return `${loc}: ${msg}`\n return msg ?? loc\n })\n .filter((m): m is string => typeof m === 'string')\n if (msgs.length) return msgs.join('; ')\n }\n return `HTTP ${status} ${statusText}`.trim()\n}\n\n/**\n * Build a configured Scrapper {@link Tool} from validated config + an already-resolved sync\n * artifact constructor. Shared by every verb and by both the async and sync factories.\n */\nexport const assembleScrapperTool = <P, R>(\n verb: ScrapperVerb<R>,\n config: ScrapperBaseConfig<P, R, unknown>,\n instanceUrl: string,\n artifactConstructor: () => SpooledArtifactCtor\n): Tool => {\n const requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT\n const resultFormat = config.resultFormat ?? 'either'\n const toolName = config.name ?? verb.defaultName\n const fixed = config.fixed as Record<string, unknown> | undefined\n const defaults = config.defaults as Record<string, unknown> | undefined\n\n const inputMw = new Middleware<MiddlewareFn<ScrapperRequestContext>>()\n for (const fn of config.inputPipeline ?? []) inputMw.add(fn)\n const outputMw = new Middleware<MiddlewareFn<ScrapperResponseContext<R>>>()\n for (const fn of config.outputPipeline ?? [])\n outputMw.add(fn as MiddlewareFn<ScrapperResponseContext<R>>)\n const hasInput = (config.inputPipeline ?? []).length > 0\n const hasOutput = (config.outputPipeline ?? []).length > 0\n\n // Build the schema, conditionally exposing `format` only when the factory is neutral.\n const extra: Record<string, Schema> =\n resultFormat === 'either'\n ? {\n format: validator\n .string()\n .valid('normalized', 'raw')\n .default('normalized')\n .description('Output shape: \"normalized\" (trimmed) or \"raw\" (full Scrapper JSON).'),\n }\n : {}\n const inputSchema = buildScrapperSchema(verb.specs, fixed, defaults, extra)\n\n return new Tool({\n name: toolName,\n description: config.description ?? verb.defaultDescription,\n inputSchema,\n artifactConstructor,\n handler: async (args, handlerCtx) => {\n const a = args as Record<string, unknown> & { url: string; format?: 'normalized' | 'raw' }\n await runToolGate(config.gate, handlerCtx, toolName, args)\n try {\n const format: 'normalized' | 'raw' =\n resultFormat === 'either' ? (a.format ?? 'normalized') : resultFormat\n\n const headers: ToolHeaders = {\n Accept: 'application/json',\n ...(await resolveHeaders(config.headers)),\n }\n\n const params = buildWireParams(a, verb.specs, fixed, config.fixedQuery)\n const stash = new Map<string, unknown>()\n\n const requestCtx: ScrapperRequestContext = {\n toolName,\n url: a.url,\n params,\n headers,\n instanceUrl,\n stash,\n shortCircuit: makeShortCircuit(),\n }\n\n if (hasInput) {\n const short = await runInputPipeline(inputMw, requestCtx, 'Scrapper')\n if (short !== undefined) return short\n }\n\n const url = new URL(verb.endpoint, instanceUrl + '/')\n url.searchParams.set('url', requestCtx.url)\n for (const [k, v] of Object.entries(requestCtx.params)) url.searchParams.set(k, v)\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), requestTimeoutMs)\n let response: Response\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: requestCtx.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (!response.ok) {\n let body: unknown\n try {\n body = await response.json()\n } catch {\n body = undefined\n }\n return `Error: Scrapper request failed — ${parseScrapperError(body, response.status, response.statusText)}.`\n }\n\n const body = (await response.json()) as Record<string, unknown>\n\n const responseCtx: ScrapperResponseContext<R> = {\n toolName,\n request: requestCtx,\n raw: body,\n result: verb.normalize(body),\n format,\n stash,\n }\n\n if (hasOutput) await runOutputPipeline(outputMw, responseCtx, 'Scrapper')\n\n if (typeof responseCtx.output === 'string') return responseCtx.output\n if (responseCtx.format === 'raw') return JSON.stringify(responseCtx.raw, null, 2)\n return JSON.stringify(responseCtx.result, null, 2)\n } catch (err) {\n if (isShortCircuit(err)) return err.result\n return `Error: ${isError(err) ? err.message : String(err)}`\n }\n },\n })\n}\n\n/** Validate `instanceUrl` and return the trailing-slash-normalised base. */\nexport const validateScrapperInstanceUrl = (config: { instanceUrl?: string }): string => {\n if (typeof config?.instanceUrl !== 'string' || config.instanceUrl.trim() === '') {\n failConfig('instanceUrl is required')\n }\n try {\n new URL(config.instanceUrl as string)\n } catch {\n failConfig(`instanceUrl is not a valid URL: ${config.instanceUrl}`)\n }\n return (config.instanceUrl as string).replace(/\\/+$/, '')\n}\n","/**\n * Factories for configured Scrapper web-extraction tools (article + links).\n *\n * @module @nhtio/adk/batteries/tools/scrapper\n *\n * @remarks\n * [Scrapper](https://github.com/amerkurev/scrapper) is a self-hosted service that loads a page in a\n * real headless browser and extracts either the readable article (`/api/article`) or the page's\n * links (`/api/links`). It gives an agent browser-grade reading power — JS-rendered pages a\n * renderless fetcher can't see — but as a **stateless** HTTP call: each request runs in a fresh\n * incognito context, stores no session or credentials, and shares nothing with any other call.\n *\n * Like the SearXNG battery, this exports **factories** (not ready-made `Tool` constants), because a\n * scrape tool needs per-deployment config (instance URL + custom auth headers). Two verbs, each with\n * an async factory ({@link createScrapperArticleTool} / {@link createScrapperLinksTool}, accepting a\n * dynamic-import `artifact` resolver) and a sync variant ({@link createScrapperArticleToolSync} /\n * {@link createScrapperLinksToolSync}). Because these are factories, they MUST NOT be bulk-registered\n * via `Object.values(batteries)` — call one, then register the returned tool.\n *\n * @see https://github.com/amerkurev/scrapper\n */\n\nimport { validator } from '@nhtio/validation'\nimport { SpooledJsonArtifact } from '@nhtio/adk/spooled_artifact'\nimport { resolveArtifact, resolveArtifactSync } from '../_shared'\nimport {\n failConfig,\n validateScrapperInstanceUrl,\n assembleScrapperTool,\n type ScrapperBaseConfig,\n type ScrapperParamSpec,\n type ScrapperVerb,\n} from './shared'\nimport type { Tool } from '@nhtio/adk/forge'\nimport type { ArtifactResolver, SyncArtifactResolver } from '../_shared'\n\nexport { E_INVALID_SCRAPPER_CONFIG } from './exceptions'\nexport type {\n ScrapperRequestContext,\n ScrapperResponseContext,\n ScrapperInputMiddlewareFn,\n ScrapperOutputMiddlewareFn,\n} from './shared'\n\n// ── Param sets ───────────────────────────────────────────────────────────────\n\n/** Model-facing params common to both verbs (snake_case; mapped to kebab on the wire). */\nexport interface ScrapperCommonParams {\n /** Return a cached result when available instead of re-scraping. */\n cache?: boolean\n /** Capture a screenshot; the result carries a `screenshotUri`. */\n screenshot?: boolean\n /** Run in an incognito browser context (no persisted browsing data). Default true upstream. */\n incognito?: boolean\n /** Browser navigation timeout in ms (`0` disables). Distinct from the tool's own fetch timeout. */\n timeout?: number\n /** When navigation is considered finished. */\n wait_until?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit'\n /** Wait this many ms after load before parsing. */\n sleep?: number\n /** Scroll down N pixels for lazy-loading pages. Requires a positive `sleep`. */\n scroll_down?: number\n /** Emulated device, e.g. `Desktop Chrome`. Overrides individual viewport/UA settings. */\n device?: string\n /** Explicit user-agent (prefer `device`). */\n user_agent?: string\n /** Extra headers the SCRAPER's browser sends to the TARGET site, `K:v;K2:v2` (NOT instance auth). */\n extra_http_headers?: string\n /** Upstream proxy, e.g. `http://host:3128` or `socks5://host:1080`. */\n proxy_server?: string\n}\n\n/** Model-facing params for `/api/article`. */\nexport interface ScrapperArticleParams extends ScrapperCommonParams {\n /** Populate `fullContent` with the page's full HTML. */\n full_content?: boolean\n}\n\n/** Model-facing params for `/api/links`. */\nexport interface ScrapperLinksParams extends ScrapperCommonParams {\n /** Median link-text length threshold for the link parser. */\n text_len_threshold?: number\n /** Median words-per-link threshold for the link parser. */\n words_threshold?: number\n}\n\nconst commonSpecs: ScrapperParamSpec[] = [\n {\n key: 'cache',\n wire: 'cache',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Return a cached result when available instead of re-scraping.',\n },\n {\n key: 'screenshot',\n wire: 'screenshot',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Capture a screenshot; the result carries a screenshotUri.',\n },\n {\n key: 'incognito',\n wire: 'incognito',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Run in an incognito browser context (no persisted data).',\n },\n {\n key: 'timeout',\n wire: 'timeout',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Browser navigation timeout in ms (0 disables).',\n },\n {\n key: 'wait_until',\n wire: 'wait-until',\n type: 'string',\n schema: validator.string().valid('load', 'domcontentloaded', 'networkidle', 'commit'),\n description: 'When navigation is considered finished.',\n },\n {\n key: 'sleep',\n wire: 'sleep',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Wait this many ms after load before parsing.',\n },\n {\n key: 'scroll_down',\n wire: 'scroll-down',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Scroll down N pixels for lazy-loading pages (requires a positive sleep).',\n },\n {\n key: 'device',\n wire: 'device',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Emulated device, e.g. \"Desktop Chrome\". Omit or send an empty string to leave it unset.',\n },\n {\n key: 'user_agent',\n wire: 'user-agent',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Explicit user-agent (prefer device). Omit or send an empty string to leave it unset.',\n },\n {\n key: 'extra_http_headers',\n wire: 'extra-http-headers',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Extra headers the scraper sends to the TARGET site, formatted \"K:v;K2:v2\". Omit or send an empty string to send none.',\n },\n {\n key: 'proxy_server',\n wire: 'proxy-server',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Upstream proxy, e.g. \"http://host:3128\" or \"socks5://host:1080\". Omit or send an empty string to use no proxy.',\n },\n]\n\nconst articleSpecs: ScrapperParamSpec[] = [\n ...commonSpecs,\n {\n key: 'full_content',\n wire: 'full-content',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Populate fullContent with the page full HTML.',\n },\n]\n\nconst linksSpecs: ScrapperParamSpec[] = [\n ...commonSpecs,\n {\n key: 'text_len_threshold',\n wire: 'text-len-threshold',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Median link-text length threshold for the link parser.',\n },\n {\n key: 'words_threshold',\n wire: 'words-threshold',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Median words-per-link threshold for the link parser.',\n },\n]\n\n// ── Normalised result shapes ─────────────────────────────────────────────────\n\n/** A normalised Scrapper article (loose/nullable upstream). */\nexport interface ScrapperArticle {\n /** The page URL the article was extracted from. */\n url?: string\n /** Article title. */\n title?: string\n /** Author / byline metadata. */\n byline?: string\n /** Short excerpt or description of the article. */\n excerpt?: string\n /** Name of the site the article came from. */\n siteName?: string\n /** Detected content language. */\n lang?: string\n /** Character count of the extracted article text. */\n length?: number\n /** Publication time, when the page exposed one. */\n publishedTime?: string\n /** Scrapper's own date field for the result. */\n date?: string\n /** Article text with HTML stripped. */\n textContent?: string\n /** Processed article HTML; present when the caller requested it. */\n content?: string\n /** Full page HTML; present only when `full_content` was set. */\n fullContent?: string\n /** Screenshot URI; present only when `screenshot` was set. */\n screenshotUri?: string\n}\n\n/** A single link from `/api/links` (verified live: `{ url, text }`). */\nexport interface ScrapperLink {\n /** The link's target URL. */\n url?: string\n /** The link's anchor text. */\n text?: string\n}\n\n/** A normalised Scrapper links payload. */\nexport interface ScrapperLinks {\n /** The page URL the links were collected from. */\n url?: string\n /** The page title. */\n title?: string\n /** The page's domain. */\n domain?: string\n /** Scrapper's own date field for the result. */\n date?: string\n /** The collected links, each `{ url, text }`. */\n links: ScrapperLink[]\n /** Screenshot URI; present only when `screenshot` was set. */\n screenshotUri?: string\n}\n\nconst str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)\nconst num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)\n\nconst normalizeArticle = (body: Record<string, unknown>): ScrapperArticle => {\n const out: ScrapperArticle = {}\n out.url = str(body.url)\n out.title = str(body.title)\n out.byline = str(body.byline)\n out.excerpt = str(body.excerpt)\n out.siteName = str(body.siteName)\n out.lang = str(body.lang)\n out.length = num(body.length)\n out.publishedTime = str(body.publishedTime)\n out.date = str(body.date)\n out.textContent = str(body.textContent)\n if (str(body.content)) out.content = str(body.content)\n if (str(body.fullContent)) out.fullContent = str(body.fullContent)\n if (str(body.screenshotUri)) out.screenshotUri = str(body.screenshotUri)\n // Drop undefined keys so the serialised payload stays tight.\n return JSON.parse(JSON.stringify(out)) as ScrapperArticle\n}\n\nconst normalizeLinks = (body: Record<string, unknown>): ScrapperLinks => {\n const rawLinks = Array.isArray(body.links) ? body.links : []\n const links: ScrapperLink[] = rawLinks.map((l) => {\n const r = (l ?? {}) as Record<string, unknown>\n const item: ScrapperLink = {}\n if (str(r.url)) item.url = str(r.url)\n if (str(r.text)) item.text = str(r.text)\n return item\n })\n const out: ScrapperLinks = { links }\n if (str(body.url)) out.url = str(body.url)\n if (str(body.title)) out.title = str(body.title)\n if (str(body.domain)) out.domain = str(body.domain)\n if (str(body.date)) out.date = str(body.date)\n if (str(body.screenshotUri)) out.screenshotUri = str(body.screenshotUri)\n return out\n}\n\nconst articleVerb: ScrapperVerb<ScrapperArticle> = {\n endpoint: '/api/article',\n specs: articleSpecs,\n defaultName: 'scrapper_article',\n defaultDescription:\n 'Load a web page in a real (headless) browser and extract its readable article — title, ' +\n 'byline, text content, and metadata. Renders JavaScript-heavy pages a plain fetch cannot. ' +\n 'Each call is stateless (fresh incognito context, no stored session).',\n normalize: normalizeArticle,\n}\n\nconst linksVerb: ScrapperVerb<ScrapperLinks> = {\n endpoint: '/api/links',\n specs: linksSpecs,\n defaultName: 'scrapper_links',\n defaultDescription:\n 'Load a web page in a real (headless) browser and collect its article/navigation links ' +\n '(each { url, text }). Renders JavaScript-heavy index pages a plain fetch cannot. ' +\n 'Each call is stateless (fresh incognito context, no stored session).',\n normalize: normalizeLinks,\n}\n\n// ── Config aliases ────────────────────────────────────────────────────────────\n\nexport type { ScrapperBaseConfig } from './shared'\n\n/** Async-factory config for `/api/article` (full `artifact` resolver, incl. dynamic import). */\nexport type ScrapperArticleConfig = ScrapperBaseConfig<\n ScrapperArticleParams,\n ScrapperArticle,\n ArtifactResolver\n>\n/** Sync-factory config for `/api/article` (`artifact` narrowed to the sync subset). */\nexport type ScrapperArticleConfigSync = ScrapperBaseConfig<\n ScrapperArticleParams,\n ScrapperArticle,\n SyncArtifactResolver\n>\n/** Async-factory config for `/api/links`. */\nexport type ScrapperLinksConfig = ScrapperBaseConfig<\n ScrapperLinksParams,\n ScrapperLinks,\n ArtifactResolver\n>\n/** Sync-factory config for `/api/links`. */\nexport type ScrapperLinksConfigSync = ScrapperBaseConfig<\n ScrapperLinksParams,\n ScrapperLinks,\n SyncArtifactResolver\n>\n\nconst defaultArtifact = () => SpooledJsonArtifact\n\n// ── Factories ──────────────────────────────────────────────────────────────────\n\n/**\n * Create a configured Scrapper **article** {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * Async because `artifact` may be an async / dynamic-import resolver, which must resolve to the sync\n * `() => Ctor` `Tool.artifactConstructor` requires before the tool is built. For the common case,\n * use {@link createScrapperArticleToolSync} and skip the `await`.\n *\n * @warning\n * Two distinct \"headers\": `config.headers` authenticates to the Scrapper *instance*; the\n * `extra_http_headers` *parameter* is what the scraper's browser sends to the *target site* — do not\n * conflate them. Also note `scroll_down` requires a positive `sleep`, and `resultUri`/`screenshotUri`\n * are instance-relative and may come back `http://` even over HTTPS — do not assume they match\n * `instanceUrl`.\n *\n * @param config - Instance URL, instance-auth headers, output policy, `artifact` resolver,\n * per-parameter disposition (`fixed`/`defaults`/`fixedQuery`), and middleware pipelines.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createScrapperArticleTool = async (config: ScrapperArticleConfig): Promise<Tool> => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = await resolveArtifact(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(articleVerb, config, instanceUrl, artifact)\n}\n\n/**\n * Synchronous {@link createScrapperArticleTool} — `artifact` narrowed to the sync subset.\n *\n * @param config - Same as {@link createScrapperArticleTool}, with a sync-only `artifact`.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createScrapperArticleToolSync = (config: ScrapperArticleConfigSync): Tool => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = resolveArtifactSync(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(articleVerb, config, instanceUrl, artifact)\n}\n\n/**\n * Create a configured Scrapper **links** {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * See {@link createScrapperArticleTool} for the two-headers caveat and the async rationale. Each\n * `links` item is `{ url, text }`.\n *\n * @param config - Instance URL, instance-auth headers, output policy, `artifact` resolver,\n * per-parameter disposition, and middleware pipelines.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createScrapperLinksTool = async (config: ScrapperLinksConfig): Promise<Tool> => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = await resolveArtifact(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(linksVerb, config, instanceUrl, artifact)\n}\n\n/**\n * Synchronous {@link createScrapperLinksTool} — `artifact` narrowed to the sync subset.\n *\n * @param config - Same as {@link createScrapperLinksTool}, with a sync-only `artifact`.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createScrapperLinksToolSync = (config: ScrapperLinksConfigSync): Tool => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = resolveArtifactSync(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(linksVerb, config, instanceUrl, artifact)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"scrapper-DiQ5FfDL.mjs","names":[],"sources":["../src/batteries/tools/scrapper/exceptions.ts","../src/batteries/tools/scrapper/shared.ts","../src/batteries/tools/scrapper/index.ts"],"sourcesContent":["/**\n * Battery-scoped exception constructors for the Scrapper web-extraction tool.\n *\n * @remarks\n * Battery-scoped exception classes owned by the Scrapper tool battery (not the ADK core). Minted\n * via `createException` from `@nhtio/adk/factories` and re-exported from the battery's barrel\n * (`@nhtio/adk/batteries/tools/scrapper`). This file intentionally carries **no** `@module` tag:\n * it is an internal sibling relative-imported by `index.ts`, so it does not mint its own\n * `…/scrapper/exceptions` entrypoint (whose `exceptions` leaf basename would collide with sibling\n * batteries under the `vite-plugin-dts` rolled-up `.d.ts` rule).\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when a Scrapper factory receives invalid configuration.\n *\n * @remarks\n * Fatal: config bugs (missing/unparseable `instanceUrl`, a bad `artifact` resolver, or an async\n * resolver passed to a `*Sync` factory) fail loud at factory-call time rather than at first scrape.\n */\nexport const E_INVALID_SCRAPPER_CONFIG = createException<[string]>(\n 'E_INVALID_SCRAPPER_CONFIG',\n 'Invalid Scrapper tool config: %s',\n 'E_INVALID_SCRAPPER_CONFIG',\n 529,\n true\n)\n","/**\n * Internal core shared by both Scrapper verbs (article / links).\n *\n * @remarks\n * No `@module` tag — this is a sibling of `index.ts`, relative-imported, not its own entrypoint.\n * Houses the per-parameter disposition machinery (schema building from `fixed`/`defaults`), the\n * snake→kebab wire mapping, the request/response contexts, and the `fetch`+pipeline handler core.\n * Generic harness helpers (artifact/header resolution, pipeline runners) come from `../_shared`.\n */\n\nimport { Tool } from '@nhtio/adk/forge'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport { Middleware } from '@nhtio/middleware'\nimport { E_INVALID_SCRAPPER_CONFIG } from './exceptions'\nimport {\n resolveHeaders,\n makeShortCircuit,\n isShortCircuit,\n runInputPipeline,\n runOutputPipeline,\n runToolGate,\n type ToolGateFn,\n type ToolHeaders,\n type ToolHeadersResolver,\n type SpooledArtifactCtor,\n type MiddlewareFn,\n} from '../_shared'\nimport type { Schema } from '@nhtio/validation'\nimport type { NextFn } from '@nhtio/middleware'\n\nconst DEFAULT_REQUEST_TIMEOUT = 65_000\n\n/** Throw the battery-scoped config error. */\nexport const failConfig = (reason: string): never => {\n throw new E_INVALID_SCRAPPER_CONFIG([reason])\n}\n\n// ── Parameter specs (per-parameter disposition) ──────────────────────────────\n\n/** The wire type of a Scrapper query parameter — controls serialisation. */\nexport type ScrapperParamType = 'string' | 'number' | 'boolean'\n\n/**\n * One curated, model-facing Scrapper parameter: its snake_case key (used in the model schema and\n * in `fixed`/`defaults`), its kebab-case wire name, its type, the base validator, and a description.\n */\nexport interface ScrapperParamSpec {\n /** snake_case key as seen by the model and in `config.fixed` / `config.defaults`. */\n key: string\n /** kebab-case name sent to the Scrapper API. */\n wire: string\n /** Wire type, controlling string/number/boolean serialisation. */\n type: ScrapperParamType\n /** Base `@nhtio/validation` schema (no `.required()`/`.default()`/`.optional()` applied yet). */\n schema: Schema\n /** Human-readable description surfaced to the model. */\n description: string\n}\n\n/** Serialise a parameter value to its wire string. */\nconst toWire = (value: unknown): string => String(value)\n\n/**\n * Build the model-facing input schema from a verb's param specs and the factory's disposition.\n * `url` is always required. A `fixed` param is omitted (the model can't set it); a `defaults` param\n * gets `.default(value)`; everything else is `.optional()`.\n */\nexport const buildScrapperSchema = (\n specs: ScrapperParamSpec[],\n fixed: Record<string, unknown> | undefined,\n defaults: Record<string, unknown> | undefined,\n extra: Record<string, Schema> = {}\n): Schema => {\n const shape: Record<string, Schema> = {\n url: validator.string().required().description('The absolute URL of the page to load.'),\n }\n for (const spec of specs) {\n if (fixed && spec.key in fixed) continue // pinned → not model-visible\n let sch = spec.schema\n if (defaults && spec.key in defaults) {\n sch = sch.default(defaults[spec.key] as never)\n } else {\n sch = sch.optional()\n }\n shape[spec.key] = sch.description(spec.description)\n }\n return validator.object({ ...shape, ...extra })\n}\n\n/**\n * Assemble the wire-kebab query params for one request: each spec's value is `fixed` (if pinned)\n * else the validated model/default value; then `fixedQuery` raw passthrough is layered on. `url`\n * is handled separately (it is the search target, never pinned).\n *\n * @remarks\n * An empty string (`''`) is deliberately treated the same as `undefined`/`null` and never\n * forwarded — this is what lets the schema's `.allow('')` on optional string specs actually\n * mean \"don't set this,\" instead of sending e.g. `user-agent=` to the wire. This applies\n * generically to every spec (no per-spec-type branching), including a `fixed`-pinned value that\n * happens to be `''` — an explicitly pinned empty string still means \"don't send it.\"\n */\nexport const buildWireParams = (\n args: Record<string, unknown>,\n specs: ScrapperParamSpec[],\n fixed: Record<string, unknown> | undefined,\n fixedQuery: Record<string, string> | undefined\n): Record<string, string> => {\n const out: Record<string, string> = {}\n for (const spec of specs) {\n const value = fixed && spec.key in fixed ? fixed[spec.key] : args[spec.key]\n const isEmptyString = typeof value === 'string' && value.length === 0\n if (value !== undefined && value !== null && !isEmptyString) out[spec.wire] = toWire(value)\n }\n for (const [k, v] of Object.entries(fixedQuery ?? {})) out[k] = v\n return out\n}\n\n// ── Contexts ─────────────────────────────────────────────────────────────────\n\n/**\n * Mutable context handed to each input-pipeline stage **before** the HTTP request is sent.\n * Identical for both verbs.\n */\nexport interface ScrapperRequestContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The target page URL (the `url` argument). Mutable. */\n url: string\n /** Wire-kebab query params (everything except `url`). Mutable. */\n params: Record<string, string>\n /** Resolved request headers sent to the SCRAPPER INSTANCE (auth). Mutable. */\n headers: ToolHeaders\n /** The Scrapper instance base URL (read-only). */\n readonly instanceUrl: string\n /** Cross-stage scratch space; also carried onto the response context. */\n readonly stash: Map<string, unknown>\n /** Skip the fetch and return `result` verbatim as the tool's output (e.g. a cache hit). */\n shortCircuit(result: string): void\n}\n\n/**\n * Mutable context handed to each output-pipeline stage **after** the response JSON is parsed.\n *\n * @typeParam R - The verb's normalised result type (article object or links payload).\n */\nexport interface ScrapperResponseContext<R> {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The request context as it was sent (post-input-pipeline). */\n readonly request: ScrapperRequestContext\n /** The parsed Scrapper JSON body. Mutable (used when `format` is `raw`). */\n raw: unknown\n /** The normalised result. Mutable — reshape, redact, enrich. */\n result: R\n /** The effective payload shape for this call. */\n format: 'normalized' | 'raw'\n /** When set, used verbatim as the tool's output (overrides serialisation). */\n output?: string\n /** Cross-stage scratch space; carried over from the request context. */\n readonly stash: Map<string, unknown>\n}\n\n/** An input-pipeline stage. Onion middleware over {@link ScrapperRequestContext}. */\nexport type ScrapperInputMiddlewareFn = (\n ctx: ScrapperRequestContext,\n next: NextFn\n) => void | Promise<void>\n\n/** An output-pipeline stage over a verb's {@link ScrapperResponseContext}. */\nexport type ScrapperOutputMiddlewareFn<R> = (\n ctx: ScrapperResponseContext<R>,\n next: NextFn\n) => void | Promise<void>\n\n// ── Config (shared shape; `artifact` variant supplied by the verb factory) ────\n\n/** Configuration common to every Scrapper factory. `A` is the accepted `artifact` resolver type. */\nexport interface ScrapperBaseConfig<P, R, A> {\n /** Base URL of the Scrapper instance, e.g. `https://scrapper.example.org`. Required. */\n instanceUrl: string\n /** Headers sent to the Scrapper INSTANCE for auth (X-API-Key / Basic) — static or resolver. */\n headers?: ToolHeaders | ToolHeadersResolver\n /** The tool's own `fetch` AbortController timeout in ms. Default `65_000` (> Scrapper's 60s browser default). */\n requestTimeoutMs?: number\n /** Output shape. `normalized`/`raw` pin it; `either` (default) exposes a `format` arg to the model. */\n resultFormat?: 'normalized' | 'raw' | 'either'\n /** Spool-artifact resolver for the output. Default `() => SpooledJsonArtifact`. */\n artifact?: A\n /** Tool name override. */\n name?: string\n /** Tool description override. */\n description?: string\n /** Pinned params — sent always, removed from the model schema. */\n fixed?: Partial<P>\n /** Model-overridable default param values. */\n defaults?: Partial<P>\n /** Raw, un-modeled wire params (kebab keys) — always sent, never model-visible. Keeps the battery generic. */\n fixedQuery?: Record<string, string>\n /**\n * Optional per-call gate run before the HTTP request — the seam for human-approval/RBAC\n * flows built on `ctx.waitFor` (the ADK gates primitive). Throwing aborts the call through\n * the standard tool-error path. Scraping reaches the network on the agent's behalf, which\n * makes every call a candidate for gating.\n */\n gate?: ToolGateFn\n /** Stages run before the HTTP request. See {@link ScrapperRequestContext}. */\n inputPipeline?: ScrapperInputMiddlewareFn[]\n /** Stages run after the response is parsed. See {@link ScrapperResponseContext}. */\n outputPipeline?: ScrapperOutputMiddlewareFn<R>[]\n}\n\n/** Verb-specific wiring passed to {@link assembleScrapperTool}. */\nexport interface ScrapperVerb<R> {\n /** Scrapper endpoint path, e.g. `/api/article`. */\n endpoint: string\n /** The curated param specs for this verb. */\n specs: ScrapperParamSpec[]\n /** Default tool name (`scrapper_article` / `scrapper_links`). */\n defaultName: string\n /** Default tool description. */\n defaultDescription: string\n /** Map a parsed Scrapper body to the verb's normalised result. */\n normalize: (body: Record<string, unknown>) => R\n}\n\n/** Parse a Scrapper error body (`{ detail: [{ msg }] }`) into a single message, best-effort. */\nconst parseScrapperError = (body: unknown, status: number, statusText: string): string => {\n if (body && typeof body === 'object' && Array.isArray((body as { detail?: unknown }).detail)) {\n const detail = (body as { detail: Array<{ msg?: unknown; loc?: unknown }> }).detail\n const msgs = detail\n .map((d) => {\n const loc = Array.isArray(d.loc) ? d.loc.join('.') : undefined\n const msg = typeof d.msg === 'string' ? d.msg : undefined\n if (msg && loc) return `${loc}: ${msg}`\n return msg ?? loc\n })\n .filter((m): m is string => typeof m === 'string')\n if (msgs.length) return msgs.join('; ')\n }\n return `HTTP ${status} ${statusText}`.trim()\n}\n\n/**\n * Build a configured Scrapper {@link Tool} from validated config + an already-resolved sync\n * artifact constructor. Shared by every verb and by both the async and sync factories.\n */\nexport const assembleScrapperTool = <P, R>(\n verb: ScrapperVerb<R>,\n config: ScrapperBaseConfig<P, R, unknown>,\n instanceUrl: string,\n artifactConstructor: () => SpooledArtifactCtor\n): Tool => {\n const requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT\n const resultFormat = config.resultFormat ?? 'either'\n const toolName = config.name ?? verb.defaultName\n const fixed = config.fixed as Record<string, unknown> | undefined\n const defaults = config.defaults as Record<string, unknown> | undefined\n\n const inputMw = new Middleware<MiddlewareFn<ScrapperRequestContext>>()\n for (const fn of config.inputPipeline ?? []) inputMw.add(fn)\n const outputMw = new Middleware<MiddlewareFn<ScrapperResponseContext<R>>>()\n for (const fn of config.outputPipeline ?? [])\n outputMw.add(fn as MiddlewareFn<ScrapperResponseContext<R>>)\n const hasInput = (config.inputPipeline ?? []).length > 0\n const hasOutput = (config.outputPipeline ?? []).length > 0\n\n // Build the schema, conditionally exposing `format` only when the factory is neutral.\n const extra: Record<string, Schema> =\n resultFormat === 'either'\n ? {\n format: validator\n .string()\n .valid('normalized', 'raw')\n .default('normalized')\n .description('Output shape: \"normalized\" (trimmed) or \"raw\" (full Scrapper JSON).'),\n }\n : {}\n const inputSchema = buildScrapperSchema(verb.specs, fixed, defaults, extra)\n\n return new Tool({\n name: toolName,\n description: config.description ?? verb.defaultDescription,\n inputSchema,\n artifactConstructor,\n handler: async (args, handlerCtx) => {\n const a = args as Record<string, unknown> & { url: string; format?: 'normalized' | 'raw' }\n await runToolGate(config.gate, handlerCtx, toolName, args)\n try {\n const format: 'normalized' | 'raw' =\n resultFormat === 'either' ? (a.format ?? 'normalized') : resultFormat\n\n const headers: ToolHeaders = {\n Accept: 'application/json',\n ...(await resolveHeaders(config.headers)),\n }\n\n const params = buildWireParams(a, verb.specs, fixed, config.fixedQuery)\n const stash = new Map<string, unknown>()\n\n const requestCtx: ScrapperRequestContext = {\n toolName,\n url: a.url,\n params,\n headers,\n instanceUrl,\n stash,\n shortCircuit: makeShortCircuit(),\n }\n\n if (hasInput) {\n const short = await runInputPipeline(inputMw, requestCtx, 'Scrapper')\n if (short !== undefined) return short\n }\n\n const url = new URL(verb.endpoint, instanceUrl + '/')\n url.searchParams.set('url', requestCtx.url)\n for (const [k, v] of Object.entries(requestCtx.params)) url.searchParams.set(k, v)\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), requestTimeoutMs)\n let response: Response\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: requestCtx.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (!response.ok) {\n let body: unknown\n try {\n body = await response.json()\n } catch {\n body = undefined\n }\n return `Error: Scrapper request failed — ${parseScrapperError(body, response.status, response.statusText)}.`\n }\n\n const body = (await response.json()) as Record<string, unknown>\n\n const responseCtx: ScrapperResponseContext<R> = {\n toolName,\n request: requestCtx,\n raw: body,\n result: verb.normalize(body),\n format,\n stash,\n }\n\n if (hasOutput) await runOutputPipeline(outputMw, responseCtx, 'Scrapper')\n\n if (typeof responseCtx.output === 'string') return responseCtx.output\n if (responseCtx.format === 'raw') return JSON.stringify(responseCtx.raw, null, 2)\n return JSON.stringify(responseCtx.result, null, 2)\n } catch (err) {\n if (isShortCircuit(err)) return err.result\n return `Error: ${isError(err) ? err.message : String(err)}`\n }\n },\n })\n}\n\n/** Validate `instanceUrl` and return the trailing-slash-normalised base. */\nexport const validateScrapperInstanceUrl = (config: { instanceUrl?: string }): string => {\n if (typeof config?.instanceUrl !== 'string' || config.instanceUrl.trim() === '') {\n failConfig('instanceUrl is required')\n }\n try {\n new URL(config.instanceUrl as string)\n } catch {\n failConfig(`instanceUrl is not a valid URL: ${config.instanceUrl}`)\n }\n return (config.instanceUrl as string).replace(/\\/+$/, '')\n}\n","/**\n * Factories for configured Scrapper web-extraction tools (article + links).\n *\n * @module @nhtio/adk/batteries/tools/scrapper\n *\n * @remarks\n * [Scrapper](https://github.com/amerkurev/scrapper) is a self-hosted service that loads a page in a\n * real headless browser and extracts either the readable article (`/api/article`) or the page's\n * links (`/api/links`). It gives an agent browser-grade reading power — JS-rendered pages a\n * renderless fetcher can't see — but as a **stateless** HTTP call: each request runs in a fresh\n * incognito context, stores no session or credentials, and shares nothing with any other call.\n *\n * Like the SearXNG battery, this exports **factories** (not ready-made `Tool` constants), because a\n * scrape tool needs per-deployment config (instance URL + custom auth headers). Two verbs, each with\n * an async factory ({@link createScrapperArticleTool} / {@link createScrapperLinksTool}, accepting a\n * dynamic-import `artifact` resolver) and a sync variant ({@link createScrapperArticleToolSync} /\n * {@link createScrapperLinksToolSync}). Because these are factories, they MUST NOT be bulk-registered\n * via `Object.values(batteries)` — call one, then register the returned tool.\n *\n * @see https://github.com/amerkurev/scrapper\n */\n\nimport { validator } from '@nhtio/validation'\nimport { SpooledJsonArtifact } from '@nhtio/adk/spooled_artifact'\nimport { resolveArtifact, resolveArtifactSync } from '../_shared'\nimport {\n failConfig,\n validateScrapperInstanceUrl,\n assembleScrapperTool,\n type ScrapperBaseConfig,\n type ScrapperParamSpec,\n type ScrapperVerb,\n} from './shared'\nimport type { Tool } from '@nhtio/adk/forge'\nimport type { ArtifactResolver, SyncArtifactResolver } from '../_shared'\n\nexport { E_INVALID_SCRAPPER_CONFIG } from './exceptions'\nexport type {\n ScrapperRequestContext,\n ScrapperResponseContext,\n ScrapperInputMiddlewareFn,\n ScrapperOutputMiddlewareFn,\n} from './shared'\n\n// ── Param sets ───────────────────────────────────────────────────────────────\n\n/** Model-facing params common to both verbs (snake_case; mapped to kebab on the wire). */\nexport interface ScrapperCommonParams {\n /** Return a cached result when available instead of re-scraping. */\n cache?: boolean\n /** Capture a screenshot; the result carries a `screenshotUri`. */\n screenshot?: boolean\n /** Run in an incognito browser context (no persisted browsing data). Default true upstream. */\n incognito?: boolean\n /** Browser navigation timeout in ms (`0` disables). Distinct from the tool's own fetch timeout. */\n timeout?: number\n /** When navigation is considered finished. */\n wait_until?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit'\n /** Wait this many ms after load before parsing. */\n sleep?: number\n /** Scroll down N pixels for lazy-loading pages. Requires a positive `sleep`. */\n scroll_down?: number\n /** Emulated device, e.g. `Desktop Chrome`. Overrides individual viewport/UA settings. */\n device?: string\n /** Explicit user-agent (prefer `device`). */\n user_agent?: string\n /** Extra headers the SCRAPER's browser sends to the TARGET site, `K:v;K2:v2` (NOT instance auth). */\n extra_http_headers?: string\n /** Upstream proxy, e.g. `http://host:3128` or `socks5://host:1080`. */\n proxy_server?: string\n}\n\n/** Model-facing params for `/api/article`. */\nexport interface ScrapperArticleParams extends ScrapperCommonParams {\n /** Populate `fullContent` with the page's full HTML. */\n full_content?: boolean\n}\n\n/** Model-facing params for `/api/links`. */\nexport interface ScrapperLinksParams extends ScrapperCommonParams {\n /** Median link-text length threshold for the link parser. */\n text_len_threshold?: number\n /** Median words-per-link threshold for the link parser. */\n words_threshold?: number\n}\n\nconst commonSpecs: ScrapperParamSpec[] = [\n {\n key: 'cache',\n wire: 'cache',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Return a cached result when available instead of re-scraping.',\n },\n {\n key: 'screenshot',\n wire: 'screenshot',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Capture a screenshot; the result carries a screenshotUri.',\n },\n {\n key: 'incognito',\n wire: 'incognito',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Run in an incognito browser context (no persisted data).',\n },\n {\n key: 'timeout',\n wire: 'timeout',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Browser navigation timeout in ms (0 disables).',\n },\n {\n key: 'wait_until',\n wire: 'wait-until',\n type: 'string',\n schema: validator.string().valid('load', 'domcontentloaded', 'networkidle', 'commit'),\n description: 'When navigation is considered finished.',\n },\n {\n key: 'sleep',\n wire: 'sleep',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Wait this many ms after load before parsing.',\n },\n {\n key: 'scroll_down',\n wire: 'scroll-down',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Scroll down N pixels for lazy-loading pages (requires a positive sleep).',\n },\n {\n key: 'device',\n wire: 'device',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Emulated device, e.g. \"Desktop Chrome\". Omit or send an empty string to leave it unset.',\n },\n {\n key: 'user_agent',\n wire: 'user-agent',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Explicit user-agent (prefer device). Omit or send an empty string to leave it unset.',\n },\n {\n key: 'extra_http_headers',\n wire: 'extra-http-headers',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Extra headers the scraper sends to the TARGET site, formatted \"K:v;K2:v2\". Omit or send an empty string to send none.',\n },\n {\n key: 'proxy_server',\n wire: 'proxy-server',\n type: 'string',\n schema: validator.string().allow(''),\n description:\n 'Upstream proxy, e.g. \"http://host:3128\" or \"socks5://host:1080\". Omit or send an empty string to use no proxy.',\n },\n]\n\nconst articleSpecs: ScrapperParamSpec[] = [\n ...commonSpecs,\n {\n key: 'full_content',\n wire: 'full-content',\n type: 'boolean',\n schema: validator.boolean(),\n description: 'Populate fullContent with the page full HTML.',\n },\n]\n\nconst linksSpecs: ScrapperParamSpec[] = [\n ...commonSpecs,\n {\n key: 'text_len_threshold',\n wire: 'text-len-threshold',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Median link-text length threshold for the link parser.',\n },\n {\n key: 'words_threshold',\n wire: 'words-threshold',\n type: 'number',\n schema: validator.number().min(0),\n description: 'Median words-per-link threshold for the link parser.',\n },\n]\n\n// ── Normalised result shapes ─────────────────────────────────────────────────\n\n/** A normalised Scrapper article (loose/nullable upstream). */\nexport interface ScrapperArticle {\n /** The page URL the article was extracted from. */\n url?: string\n /** Article title. */\n title?: string\n /** Author / byline metadata. */\n byline?: string\n /** Short excerpt or description of the article. */\n excerpt?: string\n /** Name of the site the article came from. */\n siteName?: string\n /** Detected content language. */\n lang?: string\n /** Character count of the extracted article text. */\n length?: number\n /** Publication time, when the page exposed one. */\n publishedTime?: string\n /** Scrapper's own date field for the result. */\n date?: string\n /** Article text with HTML stripped. */\n textContent?: string\n /** Processed article HTML; present when the caller requested it. */\n content?: string\n /** Full page HTML; present only when `full_content` was set. */\n fullContent?: string\n /** Screenshot URI; present only when `screenshot` was set. */\n screenshotUri?: string\n}\n\n/** A single link from `/api/links` (verified live: `{ url, text }`). */\nexport interface ScrapperLink {\n /** The link's target URL. */\n url?: string\n /** The link's anchor text. */\n text?: string\n}\n\n/** A normalised Scrapper links payload. */\nexport interface ScrapperLinks {\n /** The page URL the links were collected from. */\n url?: string\n /** The page title. */\n title?: string\n /** The page's domain. */\n domain?: string\n /** Scrapper's own date field for the result. */\n date?: string\n /** The collected links, each `{ url, text }`. */\n links: ScrapperLink[]\n /** Screenshot URI; present only when `screenshot` was set. */\n screenshotUri?: string\n}\n\nconst str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined)\nconst num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined)\n\nconst normalizeArticle = (body: Record<string, unknown>): ScrapperArticle => {\n const out: ScrapperArticle = {}\n out.url = str(body.url)\n out.title = str(body.title)\n out.byline = str(body.byline)\n out.excerpt = str(body.excerpt)\n out.siteName = str(body.siteName)\n out.lang = str(body.lang)\n out.length = num(body.length)\n out.publishedTime = str(body.publishedTime)\n out.date = str(body.date)\n out.textContent = str(body.textContent)\n if (str(body.content)) out.content = str(body.content)\n if (str(body.fullContent)) out.fullContent = str(body.fullContent)\n if (str(body.screenshotUri)) out.screenshotUri = str(body.screenshotUri)\n // Drop undefined keys so the serialised payload stays tight.\n return JSON.parse(JSON.stringify(out)) as ScrapperArticle\n}\n\nconst normalizeLinks = (body: Record<string, unknown>): ScrapperLinks => {\n const rawLinks = Array.isArray(body.links) ? body.links : []\n const links: ScrapperLink[] = rawLinks.map((l) => {\n const r = (l ?? {}) as Record<string, unknown>\n const item: ScrapperLink = {}\n if (str(r.url)) item.url = str(r.url)\n if (str(r.text)) item.text = str(r.text)\n return item\n })\n const out: ScrapperLinks = { links }\n if (str(body.url)) out.url = str(body.url)\n if (str(body.title)) out.title = str(body.title)\n if (str(body.domain)) out.domain = str(body.domain)\n if (str(body.date)) out.date = str(body.date)\n if (str(body.screenshotUri)) out.screenshotUri = str(body.screenshotUri)\n return out\n}\n\nconst articleVerb: ScrapperVerb<ScrapperArticle> = {\n endpoint: '/api/article',\n specs: articleSpecs,\n defaultName: 'scrapper_article',\n defaultDescription:\n 'Load a web page in a real (headless) browser and extract its readable article — title, ' +\n 'byline, text content, and metadata. Renders JavaScript-heavy pages a plain fetch cannot. ' +\n 'Each call is stateless (fresh incognito context, no stored session).',\n normalize: normalizeArticle,\n}\n\nconst linksVerb: ScrapperVerb<ScrapperLinks> = {\n endpoint: '/api/links',\n specs: linksSpecs,\n defaultName: 'scrapper_links',\n defaultDescription:\n 'Load a web page in a real (headless) browser and collect its article/navigation links ' +\n '(each { url, text }). Renders JavaScript-heavy index pages a plain fetch cannot. ' +\n 'Each call is stateless (fresh incognito context, no stored session).',\n normalize: normalizeLinks,\n}\n\n// ── Config aliases ────────────────────────────────────────────────────────────\n\nexport type { ScrapperBaseConfig } from './shared'\n\n/** Async-factory config for `/api/article` (full `artifact` resolver, incl. dynamic import). */\nexport type ScrapperArticleConfig = ScrapperBaseConfig<\n ScrapperArticleParams,\n ScrapperArticle,\n ArtifactResolver\n>\n/** Sync-factory config for `/api/article` (`artifact` narrowed to the sync subset). */\nexport type ScrapperArticleConfigSync = ScrapperBaseConfig<\n ScrapperArticleParams,\n ScrapperArticle,\n SyncArtifactResolver\n>\n/** Async-factory config for `/api/links`. */\nexport type ScrapperLinksConfig = ScrapperBaseConfig<\n ScrapperLinksParams,\n ScrapperLinks,\n ArtifactResolver\n>\n/** Sync-factory config for `/api/links`. */\nexport type ScrapperLinksConfigSync = ScrapperBaseConfig<\n ScrapperLinksParams,\n ScrapperLinks,\n SyncArtifactResolver\n>\n\nconst defaultArtifact = () => SpooledJsonArtifact\n\n// ── Factories ──────────────────────────────────────────────────────────────────\n\n/**\n * Create a configured Scrapper **article** {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * Async because `artifact` may be an async / dynamic-import resolver, which must resolve to the sync\n * `() => Ctor` `Tool.artifactConstructor` requires before the tool is built. For the common case,\n * use {@link createScrapperArticleToolSync} and skip the `await`.\n *\n * @warning\n * Two distinct \"headers\": `config.headers` authenticates to the Scrapper *instance*; the\n * `extra_http_headers` *parameter* is what the scraper's browser sends to the *target site* — do not\n * conflate them. Also note `scroll_down` requires a positive `sleep`, and `resultUri`/`screenshotUri`\n * are instance-relative and may come back `http://` even over HTTPS — do not assume they match\n * `instanceUrl`.\n *\n * @param config - Instance URL, instance-auth headers, output policy, `artifact` resolver,\n * per-parameter disposition (`fixed`/`defaults`/`fixedQuery`), and middleware pipelines.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createScrapperArticleTool = async (config: ScrapperArticleConfig): Promise<Tool> => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = await resolveArtifact(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(articleVerb, config, instanceUrl, artifact)\n}\n\n/**\n * Synchronous {@link createScrapperArticleTool} — `artifact` narrowed to the sync subset.\n *\n * @param config - Same as {@link createScrapperArticleTool}, with a sync-only `artifact`.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createScrapperArticleToolSync = (config: ScrapperArticleConfigSync): Tool => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = resolveArtifactSync(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(articleVerb, config, instanceUrl, artifact)\n}\n\n/**\n * Create a configured Scrapper **links** {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * See {@link createScrapperArticleTool} for the two-headers caveat and the async rationale. Each\n * `links` item is `{ url, text }`.\n *\n * @param config - Instance URL, instance-auth headers, output policy, `artifact` resolver,\n * per-parameter disposition, and middleware pipelines.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createScrapperLinksTool = async (config: ScrapperLinksConfig): Promise<Tool> => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = await resolveArtifact(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(linksVerb, config, instanceUrl, artifact)\n}\n\n/**\n * Synchronous {@link createScrapperLinksTool} — `artifact` narrowed to the sync subset.\n *\n * @param config - Same as {@link createScrapperLinksTool}, with a sync-only `artifact`.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SCRAPPER_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createScrapperLinksToolSync = (config: ScrapperLinksConfigSync): Tool => {\n const instanceUrl = validateScrapperInstanceUrl(config)\n const artifact = resolveArtifactSync(config.artifact ?? defaultArtifact, failConfig)\n return assembleScrapperTool(linksVerb, config, instanceUrl, artifact)\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { t as createException } from "./exceptions-DRYLe-J4.mjs";
2
2
  import { t as isError } from "./guards-BEpY7lBI.mjs";
3
- import { f as Tool } from "./spooled_artifact-BkOe4-j-.mjs";
4
- import { n as SpooledJsonArtifact } from "./spooled_markdown_artifact-Dnl-lCGv.mjs";
5
- import "./factories.mjs";
3
+ import { p as Tool } from "./spooled_artifact-Bw_7swIp.mjs";
4
+ import { n as SpooledJsonArtifact } from "./spooled_markdown_artifact-vd6eNtIP.mjs";
6
5
  import "./forge.mjs";
6
+ import "./factories.mjs";
7
7
  import "./guards.mjs";
8
8
  import "./spooled_artifact.mjs";
9
9
  import { isShortCircuit, makeShortCircuit, resolveArtifact, resolveArtifactSync, resolveHeaders, runInputPipeline, runOutputPipeline, runToolGate } from "./batteries/tools/_shared.mjs";
@@ -245,4 +245,4 @@ var buildNormalisedPayload = (ctx) => {
245
245
  //#endregion
246
246
  export { createSearxngSearchToolSync as n, E_INVALID_SEARXNG_CONFIG as r, createSearxngSearchTool as t };
247
247
 
248
- //# sourceMappingURL=searxng-D-nA2gEI.mjs.map
248
+ //# sourceMappingURL=searxng-BFTJ6rTE.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"searxng-D-nA2gEI.mjs","names":[],"sources":["../src/batteries/tools/searxng/exceptions.ts","../src/batteries/tools/searxng/index.ts"],"sourcesContent":["/**\n * Battery-scoped exception constructors for the SearXNG search tool.\n *\n * @remarks\n * Battery-scoped exception classes owned by the SearXNG tool battery (not the ADK core). Minted\n * via `createException` from `@nhtio/adk/factories` and re-exported from the battery's barrel\n * (`@nhtio/adk/batteries/tools/searxng`). This file intentionally carries **no** `@module` tag:\n * it is an internal sibling relative-imported by `index.ts`, so it does not mint its own\n * `…/searxng/exceptions` entrypoint (whose `exceptions` leaf basename would collide with sibling\n * batteries under the `vite-plugin-dts` rolled-up `.d.ts` rule).\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when {@link createSearxngSearchTool} receives invalid configuration.\n *\n * @remarks\n * Fatal: config bugs (missing or unparseable `instanceUrl`) fail loud at factory-call time\n * rather than at the first search.\n */\nexport const E_INVALID_SEARXNG_CONFIG = createException<[string]>(\n 'E_INVALID_SEARXNG_CONFIG',\n 'Invalid SearXNG tool config: %s',\n 'E_INVALID_SEARXNG_CONFIG',\n 529,\n true\n)\n","/**\n * Factory for a configured SearXNG metasearch tool.\n *\n * @module @nhtio/adk/batteries/tools/searxng\n *\n * @remarks\n * Unlike the other bundled tool categories — every one of which exports a ready-made,\n * stateless `Tool` constant — the SearXNG battery exports **factories**,\n * {@link createSearxngSearchTool} (async) and {@link createSearxngSearchToolSync}. A search tool\n * has to talk to a *specific* SearXNG instance, usually behind custom authentication, so it needs\n * per-deployment configuration (a base URL and headers) that cannot be baked in at module load.\n *\n * Because this module exports factories rather than `Tool` instances, they MUST NOT be\n * bulk-registered via `Object.values(batteries)`. Call a factory first, then register the\n * returned tool: `new ToolRegistry([await createSearxngSearchTool({ instanceUrl })])`.\n *\n * @see https://docs.searxng.org/dev/search_api.html\n */\n\nimport { Tool } from '@nhtio/adk/forge'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport { Middleware } from '@nhtio/middleware'\nimport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\nimport { SpooledJsonArtifact } from '@nhtio/adk/spooled_artifact'\nimport {\n resolveHeaders,\n resolveArtifact,\n resolveArtifactSync,\n makeShortCircuit,\n isShortCircuit,\n runInputPipeline,\n runOutputPipeline,\n runToolGate,\n type ToolGateFn,\n type ArtifactResolver,\n type SyncArtifactResolver,\n type SpooledArtifactCtor,\n type MiddlewareFn,\n} from '../_shared'\nimport type { Schema } from '@nhtio/validation'\nimport type { NextFn } from '@nhtio/middleware'\n\nexport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\n\n/** A static set of request headers (used for custom authentication). */\nexport type SearxngHeaders = Record<string, string>\n\n/**\n * A resolver returning request headers, sync or async. Use this form when the auth token is\n * refreshable — the resolver runs on every search, so a fresh token can be minted per call.\n */\nexport type SearxngHeadersResolver = () => SearxngHeaders | Promise<SearxngHeaders>\n\n/** The output shape the tool serialises. `either` lets the model pick per call. */\nexport type SearxngResultFormat = 'normalized' | 'raw' | 'either'\n\n/**\n * A single normalised SearXNG result. SearXNG result items are deliberately untyped upstream,\n * so every field except a best-effort `title`/`url` is optional.\n */\nexport interface SearxngResult {\n /** Result title, when the source engine provided one. */\n title?: string\n /** Result URL, when the source engine provided one. */\n url?: string\n /** Snippet / summary text for the result. */\n content?: string\n /** The SearXNG engine that produced this result (e.g. `google`, `duckduckgo`). */\n engine?: string\n /** Relevance score as reported by SearXNG (higher is more relevant). */\n score?: number\n /** Publication date, when the source engine exposed one (ISO-ish string, engine-dependent). */\n publishedDate?: string\n}\n\n/**\n * Mutable context handed to each input-pipeline stage **before** the HTTP request is sent.\n *\n * @remarks\n * Stages mutate this in place (onion `(ctx, next)` style) to adjust the outgoing request —\n * inject or rotate auth headers, force a language, rewrite the query — or call\n * {@link SearxngRequestContext.shortCircuit} to skip the fetch entirely (e.g. a cache hit).\n */\nexport interface SearxngRequestContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The search query. Mutable. */\n query: string\n /** Extra SearXNG query parameters (`categories`, `engines`, `language`, …). Mutable. */\n params: Record<string, string>\n /** Resolved request headers. Mutable — inject, redact, or rotate auth here. */\n headers: SearxngHeaders\n /** The target instance base URL (read-only). */\n readonly instanceUrl: string\n /** Cross-stage scratch space; also carried onto the response context. */\n readonly stash: Map<string, unknown>\n /** Skip the fetch and return `result` verbatim as the tool's output. */\n shortCircuit(result: string): void\n}\n\n/**\n * Mutable context handed to each output-pipeline stage **after** the response JSON is parsed.\n *\n * @remarks\n * Stages reshape, redact, enrich, or re-rank {@link SearxngResponseContext.results}, mutate the\n * raw body, or set {@link SearxngResponseContext.output} to override the serialised string\n * verbatim (e.g. to render markdown that matches a markdown `artifact` resolver).\n */\nexport interface SearxngResponseContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The request context as it was sent (post-input-pipeline). */\n readonly request: SearxngRequestContext\n /** The parsed SearXNG JSON body. Mutable (used when `format` is `raw`). */\n raw: unknown\n /** The normalised result list. Mutable — filter, redact, or re-rank. */\n results: SearxngResult[]\n /** The effective payload shape for this call. */\n format: 'normalized' | 'raw'\n /** When set, used verbatim as the tool's output (overrides serialisation). */\n output?: string\n /** Cross-stage scratch space; carried over from the request context. */\n readonly stash: Map<string, unknown>\n}\n\n/** An input-pipeline stage. Onion middleware over {@link SearxngRequestContext}. */\nexport type SearxngInputMiddlewareFn = (\n ctx: SearxngRequestContext,\n next: NextFn\n) => void | Promise<void>\n\n/** An output-pipeline stage. Onion middleware over {@link SearxngResponseContext}. */\nexport type SearxngOutputMiddlewareFn = (\n ctx: SearxngResponseContext,\n next: NextFn\n) => void | Promise<void>\n\n/**\n * Configuration for {@link createSearxngSearchTool} (async) and\n * {@link createSearxngSearchToolSync} (sync — `artifact` narrowed to the sync subset).\n *\n * @typeParam A - The {@link ArtifactResolver} variant accepted: the full resolver (async factory)\n * or the sync subset ({@link createSearxngSearchToolSync}).\n */\nexport interface SearxngToolConfig<A = ArtifactResolver> {\n /** Base URL of the SearXNG instance, e.g. `https://searx.example.org`. Required. */\n instanceUrl: string\n /** Custom request headers — a static object or a (sync/async) resolver for refreshable auth. */\n headers?: SearxngHeaders | SearxngHeadersResolver\n /** Request timeout in milliseconds. Default `10_000`. */\n timeout?: number\n /**\n * Output shape. `normalized`/`raw` pin the shape (the model cannot change it); `either`\n * (default) exposes a `format` argument so the model chooses per call.\n */\n resultFormat?: SearxngResultFormat\n /** Tool name. Default `searxng_search`. */\n name?: string\n /** Tool description override. */\n description?: string\n /**\n * Spool-artifact resolver for the tool's output. Default `() => SpooledJsonArtifact`. Accepts a\n * constructor, a sync resolver, or — via {@link createSearxngSearchTool} — an async /\n * dynamic-import resolver. Pass `() => SpooledMarkdownArtifact` (paired with an output stage that\n * renders markdown into `ctx.output`) or `() => SpooledArtifact` for plain text.\n */\n artifact?: A\n /**\n * Optional per-call gate run before the HTTP request — the seam for human-approval/RBAC\n * flows built on `ctx.waitFor` (the ADK gates primitive). Throwing aborts the call through\n * the standard tool-error path. Search queries reach the network on the agent's behalf,\n * which makes every call a candidate for gating.\n */\n gate?: ToolGateFn\n /** Stages run before the HTTP request. See {@link SearxngRequestContext}. */\n inputPipeline?: SearxngInputMiddlewareFn[]\n /** Stages run after the response is parsed. See {@link SearxngResponseContext}. */\n outputPipeline?: SearxngOutputMiddlewareFn[]\n}\n\nconst DEFAULT_NAME = 'searxng_search'\nconst DEFAULT_TIMEOUT = 10_000\nconst DEFAULT_DESCRIPTION =\n 'Search the web via a SearXNG metasearch instance. Returns aggregated results (title, url, ' +\n 'snippet, source engine) plus any answers, infoboxes, and suggestions.'\n\n/** Normalise a loose SearXNG result item into a {@link SearxngResult}. */\nconst normaliseResult = (raw: unknown): SearxngResult => {\n const r = (raw ?? {}) as Record<string, unknown>\n const out: SearxngResult = {}\n if (typeof r.title === 'string') out.title = r.title\n if (typeof r.url === 'string') out.url = r.url\n if (typeof r.content === 'string') out.content = r.content\n if (typeof r.engine === 'string') out.engine = r.engine\n if (typeof r.score === 'number') out.score = r.score\n if (typeof r.publishedDate === 'string') out.publishedDate = r.publishedDate\n return out\n}\n\nconst fail = (reason: string): never => {\n throw new E_INVALID_SEARXNG_CONFIG([reason])\n}\n\n/** Validate `instanceUrl` and return the trailing-slash-normalised base. */\nconst validateInstanceUrl = (config: { instanceUrl?: string }): string => {\n if (typeof config?.instanceUrl !== 'string' || config.instanceUrl.trim() === '') {\n fail('instanceUrl is required')\n }\n try {\n new URL(config.instanceUrl as string)\n } catch {\n fail(`instanceUrl is not a valid URL: ${config.instanceUrl}`)\n }\n return (config.instanceUrl as string).replace(/\\/+$/, '')\n}\n\n/**\n * Assemble the `Tool` from validated config + an already-resolved sync artifact constructor.\n * Shared by both the async and sync factories — they differ only in how `artifact` is resolved.\n */\nconst assembleTool = (\n config: SearxngToolConfig<unknown>,\n instanceUrl: string,\n artifactConstructor: () => SpooledArtifactCtor\n): Tool => {\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n const resultFormat: SearxngResultFormat = config.resultFormat ?? 'either'\n const toolName = config.name ?? DEFAULT_NAME\n\n // Stages are fixed at factory time; build the Middleware shells once. A fresh `.runner()` is\n // minted per invocation inside the handler (runners are single-use).\n const inputMw = new Middleware<MiddlewareFn<SearxngRequestContext>>()\n for (const fn of config.inputPipeline ?? []) inputMw.add(fn)\n const outputMw = new Middleware<MiddlewareFn<SearxngResponseContext>>()\n for (const fn of config.outputPipeline ?? []) outputMw.add(fn)\n const hasInput = (config.inputPipeline ?? []).length > 0\n const hasOutput = (config.outputPipeline ?? []).length > 0\n\n // Build the input schema, conditionally exposing `format` only when the factory is neutral.\n const schemaShape: Record<string, Schema> = {\n query: validator.string().required().description('The search query.'),\n categories: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG categories (e.g. \"general,news\"). Omit or send an empty string to search every category.'\n ),\n engines: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG engines (e.g. \"google,duckduckgo\"). Omit or send an empty string to search every engine.'\n ),\n language: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Language code (e.g. \"en\", \"de\"). Omit or send an empty string to search all languages.'\n ),\n pageno: validator.number().min(1).default(1).description('Result page number (1-based).'),\n time_range: validator\n .string()\n .valid('day', 'month', 'year')\n .optional()\n .description('Restrict results to a time range.'),\n safesearch: validator\n .number()\n .valid(0, 1, 2)\n .optional()\n .description('Safe-search level: 0 (off), 1 (moderate), 2 (strict).'),\n }\n if (resultFormat === 'either') {\n schemaShape.format = validator\n .string()\n .valid('normalized', 'raw')\n .default('normalized')\n .description('Output shape: \"normalized\" (trimmed) or \"raw\" (full SearXNG JSON).')\n }\n const inputSchema = validator.object(schemaShape)\n\n return new Tool({\n name: toolName,\n description: config.description ?? DEFAULT_DESCRIPTION,\n inputSchema,\n artifactConstructor,\n handler: async (args, handlerCtx) => {\n await runToolGate(config.gate, handlerCtx, toolName, args)\n const a = args as {\n query: string\n categories?: string\n engines?: string\n language?: string\n pageno?: number\n time_range?: string\n safesearch?: number\n format?: 'normalized' | 'raw'\n }\n\n try {\n // 1. Effective output format: a pinned factory format wins; else the model's choice.\n const format: 'normalized' | 'raw' =\n resultFormat === 'either' ? (a.format ?? 'normalized') : resultFormat\n\n // 2. Resolve headers, merged over portable defaults (caller headers win).\n const headers: SearxngHeaders = {\n 'Accept': 'application/json',\n 'User-Agent': 'adk-searxng-tool',\n ...(await resolveHeaders(config.headers)),\n }\n\n // 3. Assemble the SearXNG query params from validated args.\n const params: Record<string, string> = {}\n if (a.categories) params.categories = a.categories\n if (a.engines) params.engines = a.engines\n if (a.language) params.language = a.language\n if (a.pageno && a.pageno !== 1) params.pageno = String(a.pageno)\n if (a.time_range) params.time_range = a.time_range\n if (typeof a.safesearch === 'number') params.safesearch = String(a.safesearch)\n\n const stash = new Map<string, unknown>()\n\n // 4. Run the input pipeline (fresh runner) over a mutable request context.\n const requestCtx: SearxngRequestContext = {\n toolName,\n query: a.query,\n params,\n headers,\n instanceUrl,\n stash,\n shortCircuit: makeShortCircuit(),\n }\n\n if (hasInput) {\n const short = await runInputPipeline(inputMw, requestCtx, 'SearXNG')\n if (short !== undefined) return short\n }\n\n // 5. Build the request URL from the (possibly mutated) query + params.\n const url = new URL('/search', instanceUrl + '/')\n url.searchParams.set('q', requestCtx.query)\n for (const [k, v] of Object.entries(requestCtx.params)) url.searchParams.set(k, v)\n url.searchParams.set('format', 'json')\n\n // 6. Fetch with an AbortController timeout.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeout)\n let response: Response\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: requestCtx.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (!response.ok) {\n if (response.status === 403) {\n return (\n `Error: SearXNG returned 403 Forbidden. The instance likely has the JSON output ` +\n `format disabled — enable it under \"search.formats: [json]\" in its settings.yml.`\n )\n }\n return `Error: SearXNG returned HTTP ${response.status} ${response.statusText}.`\n }\n\n const body = (await response.json()) as Record<string, unknown>\n\n // 7. Run the output pipeline (fresh runner) over a mutable response context.\n const responseCtx: SearxngResponseContext = {\n toolName,\n request: requestCtx,\n raw: body,\n results: Array.isArray(body.results) ? body.results.map(normaliseResult) : [],\n format,\n stash,\n }\n\n if (hasOutput) await runOutputPipeline(outputMw, responseCtx, 'SearXNG')\n\n // 8. Serialise and return.\n if (typeof responseCtx.output === 'string') return responseCtx.output\n if (responseCtx.format === 'raw') return JSON.stringify(responseCtx.raw, null, 2)\n return JSON.stringify(buildNormalisedPayload(responseCtx), null, 2)\n } catch (err) {\n if (isShortCircuit(err)) return err.result\n return `Error: ${isError(err) ? err.message : String(err)}`\n }\n },\n })\n}\n\n/**\n * Create a configured SearXNG search {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * Async because `artifact` may be an async / dynamic-import resolver, which must be resolved to the\n * sync `() => Ctor` that `Tool.artifactConstructor` requires before the tool is built (the\n * wrap-site invokes it synchronously). For the common case where you reference the artifact class\n * directly, use {@link createSearxngSearchToolSync} and skip the `await`.\n *\n * The handler always requests `format=json`. Note that SearXNG ships with JSON output\n * **disabled** by default (it is abused by bots); an instance that has not enabled\n * `search.formats: [json]` in its `settings.yml` answers with HTTP 403, which the tool returns\n * as a graceful `Error:` string naming the setting.\n *\n * @warning\n * Do not trust the `number_of_results` field for a result count — SearXNG frequently reports `0`\n * in JSON output even when `results` is non-empty. This is a long-standing upstream quirk, not a\n * tool defect (see {@link https://github.com/searxng/searxng/issues/2987 | searxng#2987} and\n * {@link https://github.com/searxng/searxng/issues/2457 | searxng#2457}). The tool passes the\n * field through verbatim; use `results.length` as the authoritative count.\n *\n * @param config - The instance URL, optional custom headers, output-format policy, `artifact`\n * resolver, and input/output middleware pipelines. See {@link SearxngToolConfig}.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createSearxngSearchTool = async (\n config: SearxngToolConfig<ArtifactResolver>\n): Promise<Tool> => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = await resolveArtifact(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/**\n * Synchronous {@link createSearxngSearchTool} — the ergonomic common path.\n *\n * @remarks\n * `artifact` is narrowed to the sync subset (a constructor or a sync resolver). Passing an async\n * resolver is a compile-time type error and a runtime {@link E_INVALID_SEARXNG_CONFIG}; for\n * dynamic-import resolvers use the async {@link createSearxngSearchTool}. See its docs for the\n * `number_of_results` caveat and 403/JSON-disabled behaviour.\n *\n * @param config - Same as {@link SearxngToolConfig}, with `artifact` restricted to the sync subset.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createSearxngSearchToolSync = (\n config: SearxngToolConfig<SyncArtifactResolver>\n): Tool => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = resolveArtifactSync(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/** Assemble the trimmed normalised payload, dropping empty aggregate arrays. */\nconst buildNormalisedPayload = (ctx: SearxngResponseContext): Record<string, unknown> => {\n const raw = (ctx.raw ?? {}) as Record<string, unknown>\n const payload: Record<string, unknown> = {\n query: typeof raw.query === 'string' ? raw.query : ctx.request.query,\n results: ctx.results,\n }\n // NOTE: SearXNG's `number_of_results` is frequently `0` in JSON output even when `results`\n // is non-empty — a long-standing upstream quirk, not a tool bug (see\n // https://github.com/searxng/searxng/issues/2987 and\n // https://github.com/searxng/searxng/issues/2457). We pass through whatever the instance\n // reports; treat `results.length` as the authoritative count, not this field.\n if (typeof raw.number_of_results === 'number') payload.number_of_results = raw.number_of_results\n for (const key of ['answers', 'infoboxes', 'suggestions', 'corrections'] as const) {\n const value = raw[key]\n if (Array.isArray(value) && value.length > 0) payload[key] = value\n }\n return payload\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"searxng-BFTJ6rTE.mjs","names":[],"sources":["../src/batteries/tools/searxng/exceptions.ts","../src/batteries/tools/searxng/index.ts"],"sourcesContent":["/**\n * Battery-scoped exception constructors for the SearXNG search tool.\n *\n * @remarks\n * Battery-scoped exception classes owned by the SearXNG tool battery (not the ADK core). Minted\n * via `createException` from `@nhtio/adk/factories` and re-exported from the battery's barrel\n * (`@nhtio/adk/batteries/tools/searxng`). This file intentionally carries **no** `@module` tag:\n * it is an internal sibling relative-imported by `index.ts`, so it does not mint its own\n * `…/searxng/exceptions` entrypoint (whose `exceptions` leaf basename would collide with sibling\n * batteries under the `vite-plugin-dts` rolled-up `.d.ts` rule).\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when {@link createSearxngSearchTool} receives invalid configuration.\n *\n * @remarks\n * Fatal: config bugs (missing or unparseable `instanceUrl`) fail loud at factory-call time\n * rather than at the first search.\n */\nexport const E_INVALID_SEARXNG_CONFIG = createException<[string]>(\n 'E_INVALID_SEARXNG_CONFIG',\n 'Invalid SearXNG tool config: %s',\n 'E_INVALID_SEARXNG_CONFIG',\n 529,\n true\n)\n","/**\n * Factory for a configured SearXNG metasearch tool.\n *\n * @module @nhtio/adk/batteries/tools/searxng\n *\n * @remarks\n * Unlike the other bundled tool categories — every one of which exports a ready-made,\n * stateless `Tool` constant — the SearXNG battery exports **factories**,\n * {@link createSearxngSearchTool} (async) and {@link createSearxngSearchToolSync}. A search tool\n * has to talk to a *specific* SearXNG instance, usually behind custom authentication, so it needs\n * per-deployment configuration (a base URL and headers) that cannot be baked in at module load.\n *\n * Because this module exports factories rather than `Tool` instances, they MUST NOT be\n * bulk-registered via `Object.values(batteries)`. Call a factory first, then register the\n * returned tool: `new ToolRegistry([await createSearxngSearchTool({ instanceUrl })])`.\n *\n * @see https://docs.searxng.org/dev/search_api.html\n */\n\nimport { Tool } from '@nhtio/adk/forge'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport { Middleware } from '@nhtio/middleware'\nimport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\nimport { SpooledJsonArtifact } from '@nhtio/adk/spooled_artifact'\nimport {\n resolveHeaders,\n resolveArtifact,\n resolveArtifactSync,\n makeShortCircuit,\n isShortCircuit,\n runInputPipeline,\n runOutputPipeline,\n runToolGate,\n type ToolGateFn,\n type ArtifactResolver,\n type SyncArtifactResolver,\n type SpooledArtifactCtor,\n type MiddlewareFn,\n} from '../_shared'\nimport type { Schema } from '@nhtio/validation'\nimport type { NextFn } from '@nhtio/middleware'\n\nexport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\n\n/** A static set of request headers (used for custom authentication). */\nexport type SearxngHeaders = Record<string, string>\n\n/**\n * A resolver returning request headers, sync or async. Use this form when the auth token is\n * refreshable — the resolver runs on every search, so a fresh token can be minted per call.\n */\nexport type SearxngHeadersResolver = () => SearxngHeaders | Promise<SearxngHeaders>\n\n/** The output shape the tool serialises. `either` lets the model pick per call. */\nexport type SearxngResultFormat = 'normalized' | 'raw' | 'either'\n\n/**\n * A single normalised SearXNG result. SearXNG result items are deliberately untyped upstream,\n * so every field except a best-effort `title`/`url` is optional.\n */\nexport interface SearxngResult {\n /** Result title, when the source engine provided one. */\n title?: string\n /** Result URL, when the source engine provided one. */\n url?: string\n /** Snippet / summary text for the result. */\n content?: string\n /** The SearXNG engine that produced this result (e.g. `google`, `duckduckgo`). */\n engine?: string\n /** Relevance score as reported by SearXNG (higher is more relevant). */\n score?: number\n /** Publication date, when the source engine exposed one (ISO-ish string, engine-dependent). */\n publishedDate?: string\n}\n\n/**\n * Mutable context handed to each input-pipeline stage **before** the HTTP request is sent.\n *\n * @remarks\n * Stages mutate this in place (onion `(ctx, next)` style) to adjust the outgoing request —\n * inject or rotate auth headers, force a language, rewrite the query — or call\n * {@link SearxngRequestContext.shortCircuit} to skip the fetch entirely (e.g. a cache hit).\n */\nexport interface SearxngRequestContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The search query. Mutable. */\n query: string\n /** Extra SearXNG query parameters (`categories`, `engines`, `language`, …). Mutable. */\n params: Record<string, string>\n /** Resolved request headers. Mutable — inject, redact, or rotate auth here. */\n headers: SearxngHeaders\n /** The target instance base URL (read-only). */\n readonly instanceUrl: string\n /** Cross-stage scratch space; also carried onto the response context. */\n readonly stash: Map<string, unknown>\n /** Skip the fetch and return `result` verbatim as the tool's output. */\n shortCircuit(result: string): void\n}\n\n/**\n * Mutable context handed to each output-pipeline stage **after** the response JSON is parsed.\n *\n * @remarks\n * Stages reshape, redact, enrich, or re-rank {@link SearxngResponseContext.results}, mutate the\n * raw body, or set {@link SearxngResponseContext.output} to override the serialised string\n * verbatim (e.g. to render markdown that matches a markdown `artifact` resolver).\n */\nexport interface SearxngResponseContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The request context as it was sent (post-input-pipeline). */\n readonly request: SearxngRequestContext\n /** The parsed SearXNG JSON body. Mutable (used when `format` is `raw`). */\n raw: unknown\n /** The normalised result list. Mutable — filter, redact, or re-rank. */\n results: SearxngResult[]\n /** The effective payload shape for this call. */\n format: 'normalized' | 'raw'\n /** When set, used verbatim as the tool's output (overrides serialisation). */\n output?: string\n /** Cross-stage scratch space; carried over from the request context. */\n readonly stash: Map<string, unknown>\n}\n\n/** An input-pipeline stage. Onion middleware over {@link SearxngRequestContext}. */\nexport type SearxngInputMiddlewareFn = (\n ctx: SearxngRequestContext,\n next: NextFn\n) => void | Promise<void>\n\n/** An output-pipeline stage. Onion middleware over {@link SearxngResponseContext}. */\nexport type SearxngOutputMiddlewareFn = (\n ctx: SearxngResponseContext,\n next: NextFn\n) => void | Promise<void>\n\n/**\n * Configuration for {@link createSearxngSearchTool} (async) and\n * {@link createSearxngSearchToolSync} (sync — `artifact` narrowed to the sync subset).\n *\n * @typeParam A - The {@link ArtifactResolver} variant accepted: the full resolver (async factory)\n * or the sync subset ({@link createSearxngSearchToolSync}).\n */\nexport interface SearxngToolConfig<A = ArtifactResolver> {\n /** Base URL of the SearXNG instance, e.g. `https://searx.example.org`. Required. */\n instanceUrl: string\n /** Custom request headers — a static object or a (sync/async) resolver for refreshable auth. */\n headers?: SearxngHeaders | SearxngHeadersResolver\n /** Request timeout in milliseconds. Default `10_000`. */\n timeout?: number\n /**\n * Output shape. `normalized`/`raw` pin the shape (the model cannot change it); `either`\n * (default) exposes a `format` argument so the model chooses per call.\n */\n resultFormat?: SearxngResultFormat\n /** Tool name. Default `searxng_search`. */\n name?: string\n /** Tool description override. */\n description?: string\n /**\n * Spool-artifact resolver for the tool's output. Default `() => SpooledJsonArtifact`. Accepts a\n * constructor, a sync resolver, or — via {@link createSearxngSearchTool} — an async /\n * dynamic-import resolver. Pass `() => SpooledMarkdownArtifact` (paired with an output stage that\n * renders markdown into `ctx.output`) or `() => SpooledArtifact` for plain text.\n */\n artifact?: A\n /**\n * Optional per-call gate run before the HTTP request — the seam for human-approval/RBAC\n * flows built on `ctx.waitFor` (the ADK gates primitive). Throwing aborts the call through\n * the standard tool-error path. Search queries reach the network on the agent's behalf,\n * which makes every call a candidate for gating.\n */\n gate?: ToolGateFn\n /** Stages run before the HTTP request. See {@link SearxngRequestContext}. */\n inputPipeline?: SearxngInputMiddlewareFn[]\n /** Stages run after the response is parsed. See {@link SearxngResponseContext}. */\n outputPipeline?: SearxngOutputMiddlewareFn[]\n}\n\nconst DEFAULT_NAME = 'searxng_search'\nconst DEFAULT_TIMEOUT = 10_000\nconst DEFAULT_DESCRIPTION =\n 'Search the web via a SearXNG metasearch instance. Returns aggregated results (title, url, ' +\n 'snippet, source engine) plus any answers, infoboxes, and suggestions.'\n\n/** Normalise a loose SearXNG result item into a {@link SearxngResult}. */\nconst normaliseResult = (raw: unknown): SearxngResult => {\n const r = (raw ?? {}) as Record<string, unknown>\n const out: SearxngResult = {}\n if (typeof r.title === 'string') out.title = r.title\n if (typeof r.url === 'string') out.url = r.url\n if (typeof r.content === 'string') out.content = r.content\n if (typeof r.engine === 'string') out.engine = r.engine\n if (typeof r.score === 'number') out.score = r.score\n if (typeof r.publishedDate === 'string') out.publishedDate = r.publishedDate\n return out\n}\n\nconst fail = (reason: string): never => {\n throw new E_INVALID_SEARXNG_CONFIG([reason])\n}\n\n/** Validate `instanceUrl` and return the trailing-slash-normalised base. */\nconst validateInstanceUrl = (config: { instanceUrl?: string }): string => {\n if (typeof config?.instanceUrl !== 'string' || config.instanceUrl.trim() === '') {\n fail('instanceUrl is required')\n }\n try {\n new URL(config.instanceUrl as string)\n } catch {\n fail(`instanceUrl is not a valid URL: ${config.instanceUrl}`)\n }\n return (config.instanceUrl as string).replace(/\\/+$/, '')\n}\n\n/**\n * Assemble the `Tool` from validated config + an already-resolved sync artifact constructor.\n * Shared by both the async and sync factories — they differ only in how `artifact` is resolved.\n */\nconst assembleTool = (\n config: SearxngToolConfig<unknown>,\n instanceUrl: string,\n artifactConstructor: () => SpooledArtifactCtor\n): Tool => {\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n const resultFormat: SearxngResultFormat = config.resultFormat ?? 'either'\n const toolName = config.name ?? DEFAULT_NAME\n\n // Stages are fixed at factory time; build the Middleware shells once. A fresh `.runner()` is\n // minted per invocation inside the handler (runners are single-use).\n const inputMw = new Middleware<MiddlewareFn<SearxngRequestContext>>()\n for (const fn of config.inputPipeline ?? []) inputMw.add(fn)\n const outputMw = new Middleware<MiddlewareFn<SearxngResponseContext>>()\n for (const fn of config.outputPipeline ?? []) outputMw.add(fn)\n const hasInput = (config.inputPipeline ?? []).length > 0\n const hasOutput = (config.outputPipeline ?? []).length > 0\n\n // Build the input schema, conditionally exposing `format` only when the factory is neutral.\n const schemaShape: Record<string, Schema> = {\n query: validator.string().required().description('The search query.'),\n categories: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG categories (e.g. \"general,news\"). Omit or send an empty string to search every category.'\n ),\n engines: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG engines (e.g. \"google,duckduckgo\"). Omit or send an empty string to search every engine.'\n ),\n language: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Language code (e.g. \"en\", \"de\"). Omit or send an empty string to search all languages.'\n ),\n pageno: validator.number().min(1).default(1).description('Result page number (1-based).'),\n time_range: validator\n .string()\n .valid('day', 'month', 'year')\n .optional()\n .description('Restrict results to a time range.'),\n safesearch: validator\n .number()\n .valid(0, 1, 2)\n .optional()\n .description('Safe-search level: 0 (off), 1 (moderate), 2 (strict).'),\n }\n if (resultFormat === 'either') {\n schemaShape.format = validator\n .string()\n .valid('normalized', 'raw')\n .default('normalized')\n .description('Output shape: \"normalized\" (trimmed) or \"raw\" (full SearXNG JSON).')\n }\n const inputSchema = validator.object(schemaShape)\n\n return new Tool({\n name: toolName,\n description: config.description ?? DEFAULT_DESCRIPTION,\n inputSchema,\n artifactConstructor,\n handler: async (args, handlerCtx) => {\n await runToolGate(config.gate, handlerCtx, toolName, args)\n const a = args as {\n query: string\n categories?: string\n engines?: string\n language?: string\n pageno?: number\n time_range?: string\n safesearch?: number\n format?: 'normalized' | 'raw'\n }\n\n try {\n // 1. Effective output format: a pinned factory format wins; else the model's choice.\n const format: 'normalized' | 'raw' =\n resultFormat === 'either' ? (a.format ?? 'normalized') : resultFormat\n\n // 2. Resolve headers, merged over portable defaults (caller headers win).\n const headers: SearxngHeaders = {\n 'Accept': 'application/json',\n 'User-Agent': 'adk-searxng-tool',\n ...(await resolveHeaders(config.headers)),\n }\n\n // 3. Assemble the SearXNG query params from validated args.\n const params: Record<string, string> = {}\n if (a.categories) params.categories = a.categories\n if (a.engines) params.engines = a.engines\n if (a.language) params.language = a.language\n if (a.pageno && a.pageno !== 1) params.pageno = String(a.pageno)\n if (a.time_range) params.time_range = a.time_range\n if (typeof a.safesearch === 'number') params.safesearch = String(a.safesearch)\n\n const stash = new Map<string, unknown>()\n\n // 4. Run the input pipeline (fresh runner) over a mutable request context.\n const requestCtx: SearxngRequestContext = {\n toolName,\n query: a.query,\n params,\n headers,\n instanceUrl,\n stash,\n shortCircuit: makeShortCircuit(),\n }\n\n if (hasInput) {\n const short = await runInputPipeline(inputMw, requestCtx, 'SearXNG')\n if (short !== undefined) return short\n }\n\n // 5. Build the request URL from the (possibly mutated) query + params.\n const url = new URL('/search', instanceUrl + '/')\n url.searchParams.set('q', requestCtx.query)\n for (const [k, v] of Object.entries(requestCtx.params)) url.searchParams.set(k, v)\n url.searchParams.set('format', 'json')\n\n // 6. Fetch with an AbortController timeout.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeout)\n let response: Response\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: requestCtx.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (!response.ok) {\n if (response.status === 403) {\n return (\n `Error: SearXNG returned 403 Forbidden. The instance likely has the JSON output ` +\n `format disabled — enable it under \"search.formats: [json]\" in its settings.yml.`\n )\n }\n return `Error: SearXNG returned HTTP ${response.status} ${response.statusText}.`\n }\n\n const body = (await response.json()) as Record<string, unknown>\n\n // 7. Run the output pipeline (fresh runner) over a mutable response context.\n const responseCtx: SearxngResponseContext = {\n toolName,\n request: requestCtx,\n raw: body,\n results: Array.isArray(body.results) ? body.results.map(normaliseResult) : [],\n format,\n stash,\n }\n\n if (hasOutput) await runOutputPipeline(outputMw, responseCtx, 'SearXNG')\n\n // 8. Serialise and return.\n if (typeof responseCtx.output === 'string') return responseCtx.output\n if (responseCtx.format === 'raw') return JSON.stringify(responseCtx.raw, null, 2)\n return JSON.stringify(buildNormalisedPayload(responseCtx), null, 2)\n } catch (err) {\n if (isShortCircuit(err)) return err.result\n return `Error: ${isError(err) ? err.message : String(err)}`\n }\n },\n })\n}\n\n/**\n * Create a configured SearXNG search {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * Async because `artifact` may be an async / dynamic-import resolver, which must be resolved to the\n * sync `() => Ctor` that `Tool.artifactConstructor` requires before the tool is built (the\n * wrap-site invokes it synchronously). For the common case where you reference the artifact class\n * directly, use {@link createSearxngSearchToolSync} and skip the `await`.\n *\n * The handler always requests `format=json`. Note that SearXNG ships with JSON output\n * **disabled** by default (it is abused by bots); an instance that has not enabled\n * `search.formats: [json]` in its `settings.yml` answers with HTTP 403, which the tool returns\n * as a graceful `Error:` string naming the setting.\n *\n * @warning\n * Do not trust the `number_of_results` field for a result count — SearXNG frequently reports `0`\n * in JSON output even when `results` is non-empty. This is a long-standing upstream quirk, not a\n * tool defect (see {@link https://github.com/searxng/searxng/issues/2987 | searxng#2987} and\n * {@link https://github.com/searxng/searxng/issues/2457 | searxng#2457}). The tool passes the\n * field through verbatim; use `results.length` as the authoritative count.\n *\n * @param config - The instance URL, optional custom headers, output-format policy, `artifact`\n * resolver, and input/output middleware pipelines. See {@link SearxngToolConfig}.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createSearxngSearchTool = async (\n config: SearxngToolConfig<ArtifactResolver>\n): Promise<Tool> => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = await resolveArtifact(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/**\n * Synchronous {@link createSearxngSearchTool} — the ergonomic common path.\n *\n * @remarks\n * `artifact` is narrowed to the sync subset (a constructor or a sync resolver). Passing an async\n * resolver is a compile-time type error and a runtime {@link E_INVALID_SEARXNG_CONFIG}; for\n * dynamic-import resolvers use the async {@link createSearxngSearchTool}. See its docs for the\n * `number_of_results` caveat and 403/JSON-disabled behaviour.\n *\n * @param config - Same as {@link SearxngToolConfig}, with `artifact` restricted to the sync subset.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createSearxngSearchToolSync = (\n config: SearxngToolConfig<SyncArtifactResolver>\n): Tool => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = resolveArtifactSync(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/** Assemble the trimmed normalised payload, dropping empty aggregate arrays. */\nconst buildNormalisedPayload = (ctx: SearxngResponseContext): Record<string, unknown> => {\n const raw = (ctx.raw ?? {}) as Record<string, unknown>\n const payload: Record<string, unknown> = {\n query: typeof raw.query === 'string' ? raw.query : ctx.request.query,\n results: ctx.results,\n }\n // NOTE: SearXNG's `number_of_results` is frequently `0` in JSON output even when `results`\n // is non-empty — a long-standing upstream quirk, not a tool bug (see\n // https://github.com/searxng/searxng/issues/2987 and\n // https://github.com/searxng/searxng/issues/2457). We pass through whatever the instance\n // reports; treat `results.length` as the authoritative count, not this field.\n if (typeof raw.number_of_results === 'number') payload.number_of_results = raw.number_of_results\n for (const key of ['answers', 'infoboxes', 'suggestions', 'corrections'] as const) {\n const value = raw[key]\n if (Array.isArray(value) && value.length > 0) payload[key] = value\n }\n return payload\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  const require_exceptions = require("./exceptions-DrHnAGQz.cjs");
2
2
  const require_guards = require("./guards-5-XmJgD2.cjs");
3
- const require_spooled_artifact = require("./spooled_artifact-BLgbGT8b.cjs");
4
- const require_spooled_markdown_artifact = require("./spooled_markdown_artifact-8zdejfcJ.cjs");
5
- require("./factories.cjs");
3
+ const require_spooled_artifact = require("./spooled_artifact---DmicFW.cjs");
4
+ const require_spooled_markdown_artifact = require("./spooled_markdown_artifact-Rjzj-kyh.cjs");
6
5
  require("./forge.cjs");
6
+ require("./factories.cjs");
7
7
  require("./guards.cjs");
8
8
  require("./spooled_artifact.cjs");
9
9
  const require_batteries_tools__shared = require("./batteries/tools/_shared.cjs");
@@ -262,4 +262,4 @@ Object.defineProperty(exports, "createSearxngSearchToolSync", {
262
262
  }
263
263
  });
264
264
 
265
- //# sourceMappingURL=searxng-C6xrLW-v.cjs.map
265
+ //# sourceMappingURL=searxng-EJhWRnIE.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"searxng-C6xrLW-v.cjs","names":[],"sources":["../src/batteries/tools/searxng/exceptions.ts","../src/batteries/tools/searxng/index.ts"],"sourcesContent":["/**\n * Battery-scoped exception constructors for the SearXNG search tool.\n *\n * @remarks\n * Battery-scoped exception classes owned by the SearXNG tool battery (not the ADK core). Minted\n * via `createException` from `@nhtio/adk/factories` and re-exported from the battery's barrel\n * (`@nhtio/adk/batteries/tools/searxng`). This file intentionally carries **no** `@module` tag:\n * it is an internal sibling relative-imported by `index.ts`, so it does not mint its own\n * `…/searxng/exceptions` entrypoint (whose `exceptions` leaf basename would collide with sibling\n * batteries under the `vite-plugin-dts` rolled-up `.d.ts` rule).\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when {@link createSearxngSearchTool} receives invalid configuration.\n *\n * @remarks\n * Fatal: config bugs (missing or unparseable `instanceUrl`) fail loud at factory-call time\n * rather than at the first search.\n */\nexport const E_INVALID_SEARXNG_CONFIG = createException<[string]>(\n 'E_INVALID_SEARXNG_CONFIG',\n 'Invalid SearXNG tool config: %s',\n 'E_INVALID_SEARXNG_CONFIG',\n 529,\n true\n)\n","/**\n * Factory for a configured SearXNG metasearch tool.\n *\n * @module @nhtio/adk/batteries/tools/searxng\n *\n * @remarks\n * Unlike the other bundled tool categories — every one of which exports a ready-made,\n * stateless `Tool` constant — the SearXNG battery exports **factories**,\n * {@link createSearxngSearchTool} (async) and {@link createSearxngSearchToolSync}. A search tool\n * has to talk to a *specific* SearXNG instance, usually behind custom authentication, so it needs\n * per-deployment configuration (a base URL and headers) that cannot be baked in at module load.\n *\n * Because this module exports factories rather than `Tool` instances, they MUST NOT be\n * bulk-registered via `Object.values(batteries)`. Call a factory first, then register the\n * returned tool: `new ToolRegistry([await createSearxngSearchTool({ instanceUrl })])`.\n *\n * @see https://docs.searxng.org/dev/search_api.html\n */\n\nimport { Tool } from '@nhtio/adk/forge'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport { Middleware } from '@nhtio/middleware'\nimport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\nimport { SpooledJsonArtifact } from '@nhtio/adk/spooled_artifact'\nimport {\n resolveHeaders,\n resolveArtifact,\n resolveArtifactSync,\n makeShortCircuit,\n isShortCircuit,\n runInputPipeline,\n runOutputPipeline,\n runToolGate,\n type ToolGateFn,\n type ArtifactResolver,\n type SyncArtifactResolver,\n type SpooledArtifactCtor,\n type MiddlewareFn,\n} from '../_shared'\nimport type { Schema } from '@nhtio/validation'\nimport type { NextFn } from '@nhtio/middleware'\n\nexport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\n\n/** A static set of request headers (used for custom authentication). */\nexport type SearxngHeaders = Record<string, string>\n\n/**\n * A resolver returning request headers, sync or async. Use this form when the auth token is\n * refreshable — the resolver runs on every search, so a fresh token can be minted per call.\n */\nexport type SearxngHeadersResolver = () => SearxngHeaders | Promise<SearxngHeaders>\n\n/** The output shape the tool serialises. `either` lets the model pick per call. */\nexport type SearxngResultFormat = 'normalized' | 'raw' | 'either'\n\n/**\n * A single normalised SearXNG result. SearXNG result items are deliberately untyped upstream,\n * so every field except a best-effort `title`/`url` is optional.\n */\nexport interface SearxngResult {\n /** Result title, when the source engine provided one. */\n title?: string\n /** Result URL, when the source engine provided one. */\n url?: string\n /** Snippet / summary text for the result. */\n content?: string\n /** The SearXNG engine that produced this result (e.g. `google`, `duckduckgo`). */\n engine?: string\n /** Relevance score as reported by SearXNG (higher is more relevant). */\n score?: number\n /** Publication date, when the source engine exposed one (ISO-ish string, engine-dependent). */\n publishedDate?: string\n}\n\n/**\n * Mutable context handed to each input-pipeline stage **before** the HTTP request is sent.\n *\n * @remarks\n * Stages mutate this in place (onion `(ctx, next)` style) to adjust the outgoing request —\n * inject or rotate auth headers, force a language, rewrite the query — or call\n * {@link SearxngRequestContext.shortCircuit} to skip the fetch entirely (e.g. a cache hit).\n */\nexport interface SearxngRequestContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The search query. Mutable. */\n query: string\n /** Extra SearXNG query parameters (`categories`, `engines`, `language`, …). Mutable. */\n params: Record<string, string>\n /** Resolved request headers. Mutable — inject, redact, or rotate auth here. */\n headers: SearxngHeaders\n /** The target instance base URL (read-only). */\n readonly instanceUrl: string\n /** Cross-stage scratch space; also carried onto the response context. */\n readonly stash: Map<string, unknown>\n /** Skip the fetch and return `result` verbatim as the tool's output. */\n shortCircuit(result: string): void\n}\n\n/**\n * Mutable context handed to each output-pipeline stage **after** the response JSON is parsed.\n *\n * @remarks\n * Stages reshape, redact, enrich, or re-rank {@link SearxngResponseContext.results}, mutate the\n * raw body, or set {@link SearxngResponseContext.output} to override the serialised string\n * verbatim (e.g. to render markdown that matches a markdown `artifact` resolver).\n */\nexport interface SearxngResponseContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The request context as it was sent (post-input-pipeline). */\n readonly request: SearxngRequestContext\n /** The parsed SearXNG JSON body. Mutable (used when `format` is `raw`). */\n raw: unknown\n /** The normalised result list. Mutable — filter, redact, or re-rank. */\n results: SearxngResult[]\n /** The effective payload shape for this call. */\n format: 'normalized' | 'raw'\n /** When set, used verbatim as the tool's output (overrides serialisation). */\n output?: string\n /** Cross-stage scratch space; carried over from the request context. */\n readonly stash: Map<string, unknown>\n}\n\n/** An input-pipeline stage. Onion middleware over {@link SearxngRequestContext}. */\nexport type SearxngInputMiddlewareFn = (\n ctx: SearxngRequestContext,\n next: NextFn\n) => void | Promise<void>\n\n/** An output-pipeline stage. Onion middleware over {@link SearxngResponseContext}. */\nexport type SearxngOutputMiddlewareFn = (\n ctx: SearxngResponseContext,\n next: NextFn\n) => void | Promise<void>\n\n/**\n * Configuration for {@link createSearxngSearchTool} (async) and\n * {@link createSearxngSearchToolSync} (sync — `artifact` narrowed to the sync subset).\n *\n * @typeParam A - The {@link ArtifactResolver} variant accepted: the full resolver (async factory)\n * or the sync subset ({@link createSearxngSearchToolSync}).\n */\nexport interface SearxngToolConfig<A = ArtifactResolver> {\n /** Base URL of the SearXNG instance, e.g. `https://searx.example.org`. Required. */\n instanceUrl: string\n /** Custom request headers — a static object or a (sync/async) resolver for refreshable auth. */\n headers?: SearxngHeaders | SearxngHeadersResolver\n /** Request timeout in milliseconds. Default `10_000`. */\n timeout?: number\n /**\n * Output shape. `normalized`/`raw` pin the shape (the model cannot change it); `either`\n * (default) exposes a `format` argument so the model chooses per call.\n */\n resultFormat?: SearxngResultFormat\n /** Tool name. Default `searxng_search`. */\n name?: string\n /** Tool description override. */\n description?: string\n /**\n * Spool-artifact resolver for the tool's output. Default `() => SpooledJsonArtifact`. Accepts a\n * constructor, a sync resolver, or — via {@link createSearxngSearchTool} — an async /\n * dynamic-import resolver. Pass `() => SpooledMarkdownArtifact` (paired with an output stage that\n * renders markdown into `ctx.output`) or `() => SpooledArtifact` for plain text.\n */\n artifact?: A\n /**\n * Optional per-call gate run before the HTTP request — the seam for human-approval/RBAC\n * flows built on `ctx.waitFor` (the ADK gates primitive). Throwing aborts the call through\n * the standard tool-error path. Search queries reach the network on the agent's behalf,\n * which makes every call a candidate for gating.\n */\n gate?: ToolGateFn\n /** Stages run before the HTTP request. See {@link SearxngRequestContext}. */\n inputPipeline?: SearxngInputMiddlewareFn[]\n /** Stages run after the response is parsed. See {@link SearxngResponseContext}. */\n outputPipeline?: SearxngOutputMiddlewareFn[]\n}\n\nconst DEFAULT_NAME = 'searxng_search'\nconst DEFAULT_TIMEOUT = 10_000\nconst DEFAULT_DESCRIPTION =\n 'Search the web via a SearXNG metasearch instance. Returns aggregated results (title, url, ' +\n 'snippet, source engine) plus any answers, infoboxes, and suggestions.'\n\n/** Normalise a loose SearXNG result item into a {@link SearxngResult}. */\nconst normaliseResult = (raw: unknown): SearxngResult => {\n const r = (raw ?? {}) as Record<string, unknown>\n const out: SearxngResult = {}\n if (typeof r.title === 'string') out.title = r.title\n if (typeof r.url === 'string') out.url = r.url\n if (typeof r.content === 'string') out.content = r.content\n if (typeof r.engine === 'string') out.engine = r.engine\n if (typeof r.score === 'number') out.score = r.score\n if (typeof r.publishedDate === 'string') out.publishedDate = r.publishedDate\n return out\n}\n\nconst fail = (reason: string): never => {\n throw new E_INVALID_SEARXNG_CONFIG([reason])\n}\n\n/** Validate `instanceUrl` and return the trailing-slash-normalised base. */\nconst validateInstanceUrl = (config: { instanceUrl?: string }): string => {\n if (typeof config?.instanceUrl !== 'string' || config.instanceUrl.trim() === '') {\n fail('instanceUrl is required')\n }\n try {\n new URL(config.instanceUrl as string)\n } catch {\n fail(`instanceUrl is not a valid URL: ${config.instanceUrl}`)\n }\n return (config.instanceUrl as string).replace(/\\/+$/, '')\n}\n\n/**\n * Assemble the `Tool` from validated config + an already-resolved sync artifact constructor.\n * Shared by both the async and sync factories — they differ only in how `artifact` is resolved.\n */\nconst assembleTool = (\n config: SearxngToolConfig<unknown>,\n instanceUrl: string,\n artifactConstructor: () => SpooledArtifactCtor\n): Tool => {\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n const resultFormat: SearxngResultFormat = config.resultFormat ?? 'either'\n const toolName = config.name ?? DEFAULT_NAME\n\n // Stages are fixed at factory time; build the Middleware shells once. A fresh `.runner()` is\n // minted per invocation inside the handler (runners are single-use).\n const inputMw = new Middleware<MiddlewareFn<SearxngRequestContext>>()\n for (const fn of config.inputPipeline ?? []) inputMw.add(fn)\n const outputMw = new Middleware<MiddlewareFn<SearxngResponseContext>>()\n for (const fn of config.outputPipeline ?? []) outputMw.add(fn)\n const hasInput = (config.inputPipeline ?? []).length > 0\n const hasOutput = (config.outputPipeline ?? []).length > 0\n\n // Build the input schema, conditionally exposing `format` only when the factory is neutral.\n const schemaShape: Record<string, Schema> = {\n query: validator.string().required().description('The search query.'),\n categories: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG categories (e.g. \"general,news\"). Omit or send an empty string to search every category.'\n ),\n engines: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG engines (e.g. \"google,duckduckgo\"). Omit or send an empty string to search every engine.'\n ),\n language: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Language code (e.g. \"en\", \"de\"). Omit or send an empty string to search all languages.'\n ),\n pageno: validator.number().min(1).default(1).description('Result page number (1-based).'),\n time_range: validator\n .string()\n .valid('day', 'month', 'year')\n .optional()\n .description('Restrict results to a time range.'),\n safesearch: validator\n .number()\n .valid(0, 1, 2)\n .optional()\n .description('Safe-search level: 0 (off), 1 (moderate), 2 (strict).'),\n }\n if (resultFormat === 'either') {\n schemaShape.format = validator\n .string()\n .valid('normalized', 'raw')\n .default('normalized')\n .description('Output shape: \"normalized\" (trimmed) or \"raw\" (full SearXNG JSON).')\n }\n const inputSchema = validator.object(schemaShape)\n\n return new Tool({\n name: toolName,\n description: config.description ?? DEFAULT_DESCRIPTION,\n inputSchema,\n artifactConstructor,\n handler: async (args, handlerCtx) => {\n await runToolGate(config.gate, handlerCtx, toolName, args)\n const a = args as {\n query: string\n categories?: string\n engines?: string\n language?: string\n pageno?: number\n time_range?: string\n safesearch?: number\n format?: 'normalized' | 'raw'\n }\n\n try {\n // 1. Effective output format: a pinned factory format wins; else the model's choice.\n const format: 'normalized' | 'raw' =\n resultFormat === 'either' ? (a.format ?? 'normalized') : resultFormat\n\n // 2. Resolve headers, merged over portable defaults (caller headers win).\n const headers: SearxngHeaders = {\n 'Accept': 'application/json',\n 'User-Agent': 'adk-searxng-tool',\n ...(await resolveHeaders(config.headers)),\n }\n\n // 3. Assemble the SearXNG query params from validated args.\n const params: Record<string, string> = {}\n if (a.categories) params.categories = a.categories\n if (a.engines) params.engines = a.engines\n if (a.language) params.language = a.language\n if (a.pageno && a.pageno !== 1) params.pageno = String(a.pageno)\n if (a.time_range) params.time_range = a.time_range\n if (typeof a.safesearch === 'number') params.safesearch = String(a.safesearch)\n\n const stash = new Map<string, unknown>()\n\n // 4. Run the input pipeline (fresh runner) over a mutable request context.\n const requestCtx: SearxngRequestContext = {\n toolName,\n query: a.query,\n params,\n headers,\n instanceUrl,\n stash,\n shortCircuit: makeShortCircuit(),\n }\n\n if (hasInput) {\n const short = await runInputPipeline(inputMw, requestCtx, 'SearXNG')\n if (short !== undefined) return short\n }\n\n // 5. Build the request URL from the (possibly mutated) query + params.\n const url = new URL('/search', instanceUrl + '/')\n url.searchParams.set('q', requestCtx.query)\n for (const [k, v] of Object.entries(requestCtx.params)) url.searchParams.set(k, v)\n url.searchParams.set('format', 'json')\n\n // 6. Fetch with an AbortController timeout.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeout)\n let response: Response\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: requestCtx.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (!response.ok) {\n if (response.status === 403) {\n return (\n `Error: SearXNG returned 403 Forbidden. The instance likely has the JSON output ` +\n `format disabled — enable it under \"search.formats: [json]\" in its settings.yml.`\n )\n }\n return `Error: SearXNG returned HTTP ${response.status} ${response.statusText}.`\n }\n\n const body = (await response.json()) as Record<string, unknown>\n\n // 7. Run the output pipeline (fresh runner) over a mutable response context.\n const responseCtx: SearxngResponseContext = {\n toolName,\n request: requestCtx,\n raw: body,\n results: Array.isArray(body.results) ? body.results.map(normaliseResult) : [],\n format,\n stash,\n }\n\n if (hasOutput) await runOutputPipeline(outputMw, responseCtx, 'SearXNG')\n\n // 8. Serialise and return.\n if (typeof responseCtx.output === 'string') return responseCtx.output\n if (responseCtx.format === 'raw') return JSON.stringify(responseCtx.raw, null, 2)\n return JSON.stringify(buildNormalisedPayload(responseCtx), null, 2)\n } catch (err) {\n if (isShortCircuit(err)) return err.result\n return `Error: ${isError(err) ? err.message : String(err)}`\n }\n },\n })\n}\n\n/**\n * Create a configured SearXNG search {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * Async because `artifact` may be an async / dynamic-import resolver, which must be resolved to the\n * sync `() => Ctor` that `Tool.artifactConstructor` requires before the tool is built (the\n * wrap-site invokes it synchronously). For the common case where you reference the artifact class\n * directly, use {@link createSearxngSearchToolSync} and skip the `await`.\n *\n * The handler always requests `format=json`. Note that SearXNG ships with JSON output\n * **disabled** by default (it is abused by bots); an instance that has not enabled\n * `search.formats: [json]` in its `settings.yml` answers with HTTP 403, which the tool returns\n * as a graceful `Error:` string naming the setting.\n *\n * @warning\n * Do not trust the `number_of_results` field for a result count — SearXNG frequently reports `0`\n * in JSON output even when `results` is non-empty. This is a long-standing upstream quirk, not a\n * tool defect (see {@link https://github.com/searxng/searxng/issues/2987 | searxng#2987} and\n * {@link https://github.com/searxng/searxng/issues/2457 | searxng#2457}). The tool passes the\n * field through verbatim; use `results.length` as the authoritative count.\n *\n * @param config - The instance URL, optional custom headers, output-format policy, `artifact`\n * resolver, and input/output middleware pipelines. See {@link SearxngToolConfig}.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createSearxngSearchTool = async (\n config: SearxngToolConfig<ArtifactResolver>\n): Promise<Tool> => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = await resolveArtifact(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/**\n * Synchronous {@link createSearxngSearchTool} — the ergonomic common path.\n *\n * @remarks\n * `artifact` is narrowed to the sync subset (a constructor or a sync resolver). Passing an async\n * resolver is a compile-time type error and a runtime {@link E_INVALID_SEARXNG_CONFIG}; for\n * dynamic-import resolvers use the async {@link createSearxngSearchTool}. See its docs for the\n * `number_of_results` caveat and 403/JSON-disabled behaviour.\n *\n * @param config - Same as {@link SearxngToolConfig}, with `artifact` restricted to the sync subset.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createSearxngSearchToolSync = (\n config: SearxngToolConfig<SyncArtifactResolver>\n): Tool => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = resolveArtifactSync(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/** Assemble the trimmed normalised payload, dropping empty aggregate arrays. */\nconst buildNormalisedPayload = (ctx: SearxngResponseContext): Record<string, unknown> => {\n const raw = (ctx.raw ?? {}) as Record<string, unknown>\n const payload: Record<string, unknown> = {\n query: typeof raw.query === 'string' ? raw.query : ctx.request.query,\n results: ctx.results,\n }\n // NOTE: SearXNG's `number_of_results` is frequently `0` in JSON output even when `results`\n // is non-empty — a long-standing upstream quirk, not a tool bug (see\n // https://github.com/searxng/searxng/issues/2987 and\n // https://github.com/searxng/searxng/issues/2457). We pass through whatever the instance\n // reports; treat `results.length` as the authoritative count, not this field.\n if (typeof raw.number_of_results === 'number') payload.number_of_results = raw.number_of_results\n for (const key of ['answers', 'infoboxes', 'suggestions', 'corrections'] as const) {\n const value = raw[key]\n if (Array.isArray(value) && value.length > 0) payload[key] = value\n }\n return payload\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"searxng-EJhWRnIE.cjs","names":[],"sources":["../src/batteries/tools/searxng/exceptions.ts","../src/batteries/tools/searxng/index.ts"],"sourcesContent":["/**\n * Battery-scoped exception constructors for the SearXNG search tool.\n *\n * @remarks\n * Battery-scoped exception classes owned by the SearXNG tool battery (not the ADK core). Minted\n * via `createException` from `@nhtio/adk/factories` and re-exported from the battery's barrel\n * (`@nhtio/adk/batteries/tools/searxng`). This file intentionally carries **no** `@module` tag:\n * it is an internal sibling relative-imported by `index.ts`, so it does not mint its own\n * `…/searxng/exceptions` entrypoint (whose `exceptions` leaf basename would collide with sibling\n * batteries under the `vite-plugin-dts` rolled-up `.d.ts` rule).\n */\n\nimport { createException } from '@nhtio/adk/factories'\n\n/**\n * Thrown when {@link createSearxngSearchTool} receives invalid configuration.\n *\n * @remarks\n * Fatal: config bugs (missing or unparseable `instanceUrl`) fail loud at factory-call time\n * rather than at the first search.\n */\nexport const E_INVALID_SEARXNG_CONFIG = createException<[string]>(\n 'E_INVALID_SEARXNG_CONFIG',\n 'Invalid SearXNG tool config: %s',\n 'E_INVALID_SEARXNG_CONFIG',\n 529,\n true\n)\n","/**\n * Factory for a configured SearXNG metasearch tool.\n *\n * @module @nhtio/adk/batteries/tools/searxng\n *\n * @remarks\n * Unlike the other bundled tool categories — every one of which exports a ready-made,\n * stateless `Tool` constant — the SearXNG battery exports **factories**,\n * {@link createSearxngSearchTool} (async) and {@link createSearxngSearchToolSync}. A search tool\n * has to talk to a *specific* SearXNG instance, usually behind custom authentication, so it needs\n * per-deployment configuration (a base URL and headers) that cannot be baked in at module load.\n *\n * Because this module exports factories rather than `Tool` instances, they MUST NOT be\n * bulk-registered via `Object.values(batteries)`. Call a factory first, then register the\n * returned tool: `new ToolRegistry([await createSearxngSearchTool({ instanceUrl })])`.\n *\n * @see https://docs.searxng.org/dev/search_api.html\n */\n\nimport { Tool } from '@nhtio/adk/forge'\nimport { isError } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport { Middleware } from '@nhtio/middleware'\nimport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\nimport { SpooledJsonArtifact } from '@nhtio/adk/spooled_artifact'\nimport {\n resolveHeaders,\n resolveArtifact,\n resolveArtifactSync,\n makeShortCircuit,\n isShortCircuit,\n runInputPipeline,\n runOutputPipeline,\n runToolGate,\n type ToolGateFn,\n type ArtifactResolver,\n type SyncArtifactResolver,\n type SpooledArtifactCtor,\n type MiddlewareFn,\n} from '../_shared'\nimport type { Schema } from '@nhtio/validation'\nimport type { NextFn } from '@nhtio/middleware'\n\nexport { E_INVALID_SEARXNG_CONFIG } from './exceptions'\n\n/** A static set of request headers (used for custom authentication). */\nexport type SearxngHeaders = Record<string, string>\n\n/**\n * A resolver returning request headers, sync or async. Use this form when the auth token is\n * refreshable — the resolver runs on every search, so a fresh token can be minted per call.\n */\nexport type SearxngHeadersResolver = () => SearxngHeaders | Promise<SearxngHeaders>\n\n/** The output shape the tool serialises. `either` lets the model pick per call. */\nexport type SearxngResultFormat = 'normalized' | 'raw' | 'either'\n\n/**\n * A single normalised SearXNG result. SearXNG result items are deliberately untyped upstream,\n * so every field except a best-effort `title`/`url` is optional.\n */\nexport interface SearxngResult {\n /** Result title, when the source engine provided one. */\n title?: string\n /** Result URL, when the source engine provided one. */\n url?: string\n /** Snippet / summary text for the result. */\n content?: string\n /** The SearXNG engine that produced this result (e.g. `google`, `duckduckgo`). */\n engine?: string\n /** Relevance score as reported by SearXNG (higher is more relevant). */\n score?: number\n /** Publication date, when the source engine exposed one (ISO-ish string, engine-dependent). */\n publishedDate?: string\n}\n\n/**\n * Mutable context handed to each input-pipeline stage **before** the HTTP request is sent.\n *\n * @remarks\n * Stages mutate this in place (onion `(ctx, next)` style) to adjust the outgoing request —\n * inject or rotate auth headers, force a language, rewrite the query — or call\n * {@link SearxngRequestContext.shortCircuit} to skip the fetch entirely (e.g. a cache hit).\n */\nexport interface SearxngRequestContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The search query. Mutable. */\n query: string\n /** Extra SearXNG query parameters (`categories`, `engines`, `language`, …). Mutable. */\n params: Record<string, string>\n /** Resolved request headers. Mutable — inject, redact, or rotate auth here. */\n headers: SearxngHeaders\n /** The target instance base URL (read-only). */\n readonly instanceUrl: string\n /** Cross-stage scratch space; also carried onto the response context. */\n readonly stash: Map<string, unknown>\n /** Skip the fetch and return `result` verbatim as the tool's output. */\n shortCircuit(result: string): void\n}\n\n/**\n * Mutable context handed to each output-pipeline stage **after** the response JSON is parsed.\n *\n * @remarks\n * Stages reshape, redact, enrich, or re-rank {@link SearxngResponseContext.results}, mutate the\n * raw body, or set {@link SearxngResponseContext.output} to override the serialised string\n * verbatim (e.g. to render markdown that matches a markdown `artifact` resolver).\n */\nexport interface SearxngResponseContext {\n /** The tool's name (read-only). */\n readonly toolName: string\n /** The request context as it was sent (post-input-pipeline). */\n readonly request: SearxngRequestContext\n /** The parsed SearXNG JSON body. Mutable (used when `format` is `raw`). */\n raw: unknown\n /** The normalised result list. Mutable — filter, redact, or re-rank. */\n results: SearxngResult[]\n /** The effective payload shape for this call. */\n format: 'normalized' | 'raw'\n /** When set, used verbatim as the tool's output (overrides serialisation). */\n output?: string\n /** Cross-stage scratch space; carried over from the request context. */\n readonly stash: Map<string, unknown>\n}\n\n/** An input-pipeline stage. Onion middleware over {@link SearxngRequestContext}. */\nexport type SearxngInputMiddlewareFn = (\n ctx: SearxngRequestContext,\n next: NextFn\n) => void | Promise<void>\n\n/** An output-pipeline stage. Onion middleware over {@link SearxngResponseContext}. */\nexport type SearxngOutputMiddlewareFn = (\n ctx: SearxngResponseContext,\n next: NextFn\n) => void | Promise<void>\n\n/**\n * Configuration for {@link createSearxngSearchTool} (async) and\n * {@link createSearxngSearchToolSync} (sync — `artifact` narrowed to the sync subset).\n *\n * @typeParam A - The {@link ArtifactResolver} variant accepted: the full resolver (async factory)\n * or the sync subset ({@link createSearxngSearchToolSync}).\n */\nexport interface SearxngToolConfig<A = ArtifactResolver> {\n /** Base URL of the SearXNG instance, e.g. `https://searx.example.org`. Required. */\n instanceUrl: string\n /** Custom request headers — a static object or a (sync/async) resolver for refreshable auth. */\n headers?: SearxngHeaders | SearxngHeadersResolver\n /** Request timeout in milliseconds. Default `10_000`. */\n timeout?: number\n /**\n * Output shape. `normalized`/`raw` pin the shape (the model cannot change it); `either`\n * (default) exposes a `format` argument so the model chooses per call.\n */\n resultFormat?: SearxngResultFormat\n /** Tool name. Default `searxng_search`. */\n name?: string\n /** Tool description override. */\n description?: string\n /**\n * Spool-artifact resolver for the tool's output. Default `() => SpooledJsonArtifact`. Accepts a\n * constructor, a sync resolver, or — via {@link createSearxngSearchTool} — an async /\n * dynamic-import resolver. Pass `() => SpooledMarkdownArtifact` (paired with an output stage that\n * renders markdown into `ctx.output`) or `() => SpooledArtifact` for plain text.\n */\n artifact?: A\n /**\n * Optional per-call gate run before the HTTP request — the seam for human-approval/RBAC\n * flows built on `ctx.waitFor` (the ADK gates primitive). Throwing aborts the call through\n * the standard tool-error path. Search queries reach the network on the agent's behalf,\n * which makes every call a candidate for gating.\n */\n gate?: ToolGateFn\n /** Stages run before the HTTP request. See {@link SearxngRequestContext}. */\n inputPipeline?: SearxngInputMiddlewareFn[]\n /** Stages run after the response is parsed. See {@link SearxngResponseContext}. */\n outputPipeline?: SearxngOutputMiddlewareFn[]\n}\n\nconst DEFAULT_NAME = 'searxng_search'\nconst DEFAULT_TIMEOUT = 10_000\nconst DEFAULT_DESCRIPTION =\n 'Search the web via a SearXNG metasearch instance. Returns aggregated results (title, url, ' +\n 'snippet, source engine) plus any answers, infoboxes, and suggestions.'\n\n/** Normalise a loose SearXNG result item into a {@link SearxngResult}. */\nconst normaliseResult = (raw: unknown): SearxngResult => {\n const r = (raw ?? {}) as Record<string, unknown>\n const out: SearxngResult = {}\n if (typeof r.title === 'string') out.title = r.title\n if (typeof r.url === 'string') out.url = r.url\n if (typeof r.content === 'string') out.content = r.content\n if (typeof r.engine === 'string') out.engine = r.engine\n if (typeof r.score === 'number') out.score = r.score\n if (typeof r.publishedDate === 'string') out.publishedDate = r.publishedDate\n return out\n}\n\nconst fail = (reason: string): never => {\n throw new E_INVALID_SEARXNG_CONFIG([reason])\n}\n\n/** Validate `instanceUrl` and return the trailing-slash-normalised base. */\nconst validateInstanceUrl = (config: { instanceUrl?: string }): string => {\n if (typeof config?.instanceUrl !== 'string' || config.instanceUrl.trim() === '') {\n fail('instanceUrl is required')\n }\n try {\n new URL(config.instanceUrl as string)\n } catch {\n fail(`instanceUrl is not a valid URL: ${config.instanceUrl}`)\n }\n return (config.instanceUrl as string).replace(/\\/+$/, '')\n}\n\n/**\n * Assemble the `Tool` from validated config + an already-resolved sync artifact constructor.\n * Shared by both the async and sync factories — they differ only in how `artifact` is resolved.\n */\nconst assembleTool = (\n config: SearxngToolConfig<unknown>,\n instanceUrl: string,\n artifactConstructor: () => SpooledArtifactCtor\n): Tool => {\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n const resultFormat: SearxngResultFormat = config.resultFormat ?? 'either'\n const toolName = config.name ?? DEFAULT_NAME\n\n // Stages are fixed at factory time; build the Middleware shells once. A fresh `.runner()` is\n // minted per invocation inside the handler (runners are single-use).\n const inputMw = new Middleware<MiddlewareFn<SearxngRequestContext>>()\n for (const fn of config.inputPipeline ?? []) inputMw.add(fn)\n const outputMw = new Middleware<MiddlewareFn<SearxngResponseContext>>()\n for (const fn of config.outputPipeline ?? []) outputMw.add(fn)\n const hasInput = (config.inputPipeline ?? []).length > 0\n const hasOutput = (config.outputPipeline ?? []).length > 0\n\n // Build the input schema, conditionally exposing `format` only when the factory is neutral.\n const schemaShape: Record<string, Schema> = {\n query: validator.string().required().description('The search query.'),\n categories: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG categories (e.g. \"general,news\"). Omit or send an empty string to search every category.'\n ),\n engines: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Comma-separated SearXNG engines (e.g. \"google,duckduckgo\"). Omit or send an empty string to search every engine.'\n ),\n language: validator\n .string()\n .optional()\n .allow('')\n .description(\n 'Language code (e.g. \"en\", \"de\"). Omit or send an empty string to search all languages.'\n ),\n pageno: validator.number().min(1).default(1).description('Result page number (1-based).'),\n time_range: validator\n .string()\n .valid('day', 'month', 'year')\n .optional()\n .description('Restrict results to a time range.'),\n safesearch: validator\n .number()\n .valid(0, 1, 2)\n .optional()\n .description('Safe-search level: 0 (off), 1 (moderate), 2 (strict).'),\n }\n if (resultFormat === 'either') {\n schemaShape.format = validator\n .string()\n .valid('normalized', 'raw')\n .default('normalized')\n .description('Output shape: \"normalized\" (trimmed) or \"raw\" (full SearXNG JSON).')\n }\n const inputSchema = validator.object(schemaShape)\n\n return new Tool({\n name: toolName,\n description: config.description ?? DEFAULT_DESCRIPTION,\n inputSchema,\n artifactConstructor,\n handler: async (args, handlerCtx) => {\n await runToolGate(config.gate, handlerCtx, toolName, args)\n const a = args as {\n query: string\n categories?: string\n engines?: string\n language?: string\n pageno?: number\n time_range?: string\n safesearch?: number\n format?: 'normalized' | 'raw'\n }\n\n try {\n // 1. Effective output format: a pinned factory format wins; else the model's choice.\n const format: 'normalized' | 'raw' =\n resultFormat === 'either' ? (a.format ?? 'normalized') : resultFormat\n\n // 2. Resolve headers, merged over portable defaults (caller headers win).\n const headers: SearxngHeaders = {\n 'Accept': 'application/json',\n 'User-Agent': 'adk-searxng-tool',\n ...(await resolveHeaders(config.headers)),\n }\n\n // 3. Assemble the SearXNG query params from validated args.\n const params: Record<string, string> = {}\n if (a.categories) params.categories = a.categories\n if (a.engines) params.engines = a.engines\n if (a.language) params.language = a.language\n if (a.pageno && a.pageno !== 1) params.pageno = String(a.pageno)\n if (a.time_range) params.time_range = a.time_range\n if (typeof a.safesearch === 'number') params.safesearch = String(a.safesearch)\n\n const stash = new Map<string, unknown>()\n\n // 4. Run the input pipeline (fresh runner) over a mutable request context.\n const requestCtx: SearxngRequestContext = {\n toolName,\n query: a.query,\n params,\n headers,\n instanceUrl,\n stash,\n shortCircuit: makeShortCircuit(),\n }\n\n if (hasInput) {\n const short = await runInputPipeline(inputMw, requestCtx, 'SearXNG')\n if (short !== undefined) return short\n }\n\n // 5. Build the request URL from the (possibly mutated) query + params.\n const url = new URL('/search', instanceUrl + '/')\n url.searchParams.set('q', requestCtx.query)\n for (const [k, v] of Object.entries(requestCtx.params)) url.searchParams.set(k, v)\n url.searchParams.set('format', 'json')\n\n // 6. Fetch with an AbortController timeout.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeout)\n let response: Response\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: requestCtx.headers,\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n\n if (!response.ok) {\n if (response.status === 403) {\n return (\n `Error: SearXNG returned 403 Forbidden. The instance likely has the JSON output ` +\n `format disabled — enable it under \"search.formats: [json]\" in its settings.yml.`\n )\n }\n return `Error: SearXNG returned HTTP ${response.status} ${response.statusText}.`\n }\n\n const body = (await response.json()) as Record<string, unknown>\n\n // 7. Run the output pipeline (fresh runner) over a mutable response context.\n const responseCtx: SearxngResponseContext = {\n toolName,\n request: requestCtx,\n raw: body,\n results: Array.isArray(body.results) ? body.results.map(normaliseResult) : [],\n format,\n stash,\n }\n\n if (hasOutput) await runOutputPipeline(outputMw, responseCtx, 'SearXNG')\n\n // 8. Serialise and return.\n if (typeof responseCtx.output === 'string') return responseCtx.output\n if (responseCtx.format === 'raw') return JSON.stringify(responseCtx.raw, null, 2)\n return JSON.stringify(buildNormalisedPayload(responseCtx), null, 2)\n } catch (err) {\n if (isShortCircuit(err)) return err.result\n return `Error: ${isError(err) ? err.message : String(err)}`\n }\n },\n })\n}\n\n/**\n * Create a configured SearXNG search {@link Tool} (async — accepts a dynamic-import `artifact`).\n *\n * @remarks\n * Async because `artifact` may be an async / dynamic-import resolver, which must be resolved to the\n * sync `() => Ctor` that `Tool.artifactConstructor` requires before the tool is built (the\n * wrap-site invokes it synchronously). For the common case where you reference the artifact class\n * directly, use {@link createSearxngSearchToolSync} and skip the `await`.\n *\n * The handler always requests `format=json`. Note that SearXNG ships with JSON output\n * **disabled** by default (it is abused by bots); an instance that has not enabled\n * `search.formats: [json]` in its `settings.yml` answers with HTTP 403, which the tool returns\n * as a graceful `Error:` string naming the setting.\n *\n * @warning\n * Do not trust the `number_of_results` field for a result count — SearXNG frequently reports `0`\n * in JSON output even when `results` is non-empty. This is a long-standing upstream quirk, not a\n * tool defect (see {@link https://github.com/searxng/searxng/issues/2987 | searxng#2987} and\n * {@link https://github.com/searxng/searxng/issues/2457 | searxng#2457}). The tool passes the\n * field through verbatim; use `results.length` as the authoritative count.\n *\n * @param config - The instance URL, optional custom headers, output-format policy, `artifact`\n * resolver, and input/output middleware pipelines. See {@link SearxngToolConfig}.\n * @returns A promise of a `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid.\n */\nexport const createSearxngSearchTool = async (\n config: SearxngToolConfig<ArtifactResolver>\n): Promise<Tool> => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = await resolveArtifact(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/**\n * Synchronous {@link createSearxngSearchTool} — the ergonomic common path.\n *\n * @remarks\n * `artifact` is narrowed to the sync subset (a constructor or a sync resolver). Passing an async\n * resolver is a compile-time type error and a runtime {@link E_INVALID_SEARXNG_CONFIG}; for\n * dynamic-import resolvers use the async {@link createSearxngSearchTool}. See its docs for the\n * `number_of_results` caveat and 403/JSON-disabled behaviour.\n *\n * @param config - Same as {@link SearxngToolConfig}, with `artifact` restricted to the sync subset.\n * @returns A `Tool` ready to register in a `ToolRegistry`.\n * @throws {@link E_INVALID_SEARXNG_CONFIG} when `instanceUrl` or `artifact` is invalid (incl. an async resolver).\n */\nexport const createSearxngSearchToolSync = (\n config: SearxngToolConfig<SyncArtifactResolver>\n): Tool => {\n const instanceUrl = validateInstanceUrl(config)\n const artifactConstructor = resolveArtifactSync(\n config.artifact ?? (() => SpooledJsonArtifact),\n fail\n )\n return assembleTool(config, instanceUrl, artifactConstructor)\n}\n\n/** Assemble the trimmed normalised payload, dropping empty aggregate arrays. */\nconst buildNormalisedPayload = (ctx: SearxngResponseContext): Record<string, unknown> => {\n const raw = (ctx.raw ?? {}) as Record<string, unknown>\n const payload: Record<string, unknown> = {\n query: typeof raw.query === 'string' ? raw.query : ctx.request.query,\n results: ctx.results,\n }\n // NOTE: SearXNG's `number_of_results` is frequently `0` in JSON output even when `results`\n // is non-empty — a long-standing upstream quirk, not a tool bug (see\n // https://github.com/searxng/searxng/issues/2987 and\n // https://github.com/searxng/searxng/issues/2457). We pass through whatever the instance\n // reports; treat `results.length` as the authoritative count, not this field.\n if (typeof raw.number_of_results === 'number') payload.number_of_results = raw.number_of_results\n for (const key of ['answers', 'infoboxes', 'suggestions', 'corrections'] as const) {\n const value = raw[key]\n if (Array.isArray(value) && value.length > 0) payload[key] = value\n }\n return payload\n}\n"],"mappings":"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"}
package/server.json CHANGED
@@ -3,12 +3,12 @@
3
3
  "name": "io.nht/adk-assembly",
4
4
  "title": "ADK Assembly",
5
5
  "description": "Opinionated Agent Development Kit — bring-your-own infrastructure, opt-in batteries.",
6
- "version": "1.20260924.0",
6
+ "version": "1.20260928.0",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@nhtio/adk",
11
- "version": "1.20260924.0",
11
+ "version": "1.20260928.0",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  }
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: "Requires TypeScript/JavaScript project using @nhtio/adk; examples assume Node 20+ or compatible browser/runtime."
6
6
  metadata:
7
7
  package: "@nhtio/adk"
8
- version: "1.20260924.0"
8
+ version: "1.20260928.0"
9
9
  author: "Jak Giveon <jak@nht.io>"
10
10
  copyright: "© 2025-present New Horizon Technology LTD"
11
11
  ---
@@ -17,7 +17,7 @@ Use this skill to help users build or review an `@nhtio/adk` assembly: the appli
17
17
  ## Package Metadata
18
18
 
19
19
  - Package: `@nhtio/adk`
20
- - Version: `1.20260924.0`
20
+ - Version: `1.20260928.0`
21
21
  - License: MIT
22
22
  - Author: Jak Giveon <jak@nht.io>
23
23
  - Copyright: © 2025-present New Horizon Technology LTD