@neurocode-ai/core 1.18.8

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 (490) hide show
  1. package/bin/neurocode +199 -0
  2. package/bunfig.toml +2 -0
  3. package/drizzle.config.ts +10 -0
  4. package/package.json +131 -0
  5. package/schema.json +2071 -0
  6. package/script/fix-node-pty.ts +28 -0
  7. package/script/migration.ts +196 -0
  8. package/src/account/sql.ts +39 -0
  9. package/src/account.ts +101 -0
  10. package/src/agent.ts +111 -0
  11. package/src/aisdk.ts +235 -0
  12. package/src/background-job.ts +365 -0
  13. package/src/catalog.ts +301 -0
  14. package/src/command.ts +64 -0
  15. package/src/config/agent.ts +25 -0
  16. package/src/config/attachments.ts +15 -0
  17. package/src/config/command.ts +12 -0
  18. package/src/config/compaction.ts +15 -0
  19. package/src/config/experimental.ts +18 -0
  20. package/src/config/formatter.ts +12 -0
  21. package/src/config/lsp.ts +18 -0
  22. package/src/config/markdown.ts +36 -0
  23. package/src/config/mcp.ts +48 -0
  24. package/src/config/plugin/agent.ts +179 -0
  25. package/src/config/plugin/command.ts +83 -0
  26. package/src/config/plugin/external.ts +91 -0
  27. package/src/config/plugin/provider.ts +113 -0
  28. package/src/config/plugin/reference.ts +70 -0
  29. package/src/config/plugin/skill.ts +50 -0
  30. package/src/config/plugin.ts +13 -0
  31. package/src/config/provider.ts +71 -0
  32. package/src/config/reference.ts +22 -0
  33. package/src/config/tool-output.ts +9 -0
  34. package/src/config/watcher.ts +7 -0
  35. package/src/config.ts +227 -0
  36. package/src/control-plane/move-session.ts +148 -0
  37. package/src/control-plane/workspace.sql.ts +20 -0
  38. package/src/credential/sql.ts +14 -0
  39. package/src/credential.ts +138 -0
  40. package/src/cross-spawn-spawner.ts +507 -0
  41. package/src/data-migration.sql.ts +6 -0
  42. package/src/database/database.ts +57 -0
  43. package/src/database/migration/20260127222353_familiar_lady_ursula.ts +107 -0
  44. package/src/database/migration/20260211171708_add_project_commands.ts +11 -0
  45. package/src/database/migration/20260213144116_wakeful_the_professor.ts +23 -0
  46. package/src/database/migration/20260225215848_workspace.ts +19 -0
  47. package/src/database/migration/20260227213759_add_session_workspace_id.ts +12 -0
  48. package/src/database/migration/20260228203230_blue_harpoon.ts +30 -0
  49. package/src/database/migration/20260303231226_add_workspace_fields.ts +15 -0
  50. package/src/database/migration/20260309230000_move_org_to_state.ts +15 -0
  51. package/src/database/migration/20260312043431_session_message_cursor.ts +16 -0
  52. package/src/database/migration/20260323234822_events.ts +26 -0
  53. package/src/database/migration/20260410174513_workspace-name.ts +29 -0
  54. package/src/database/migration/20260413175956_chief_energizer.ts +24 -0
  55. package/src/database/migration/20260423070820_add_icon_url_override.ts +14 -0
  56. package/src/database/migration/20260427172553_slow_nightmare.ts +30 -0
  57. package/src/database/migration/20260428004200_add_session_path.ts +11 -0
  58. package/src/database/migration/20260501142318_next_venus.ts +12 -0
  59. package/src/database/migration/20260504145000_add_sync_owner.ts +11 -0
  60. package/src/database/migration/20260507164347_add_workspace_time.ts +11 -0
  61. package/src/database/migration/20260510033149_session_usage.ts +56 -0
  62. package/src/database/migration/20260511000411_data_migration_state.ts +16 -0
  63. package/src/database/migration/20260511173437_session-metadata.ts +16 -0
  64. package/src/database/migration/20260601010001_normalize_storage_paths.ts +22 -0
  65. package/src/database/migration/20260601202201_amazing_prowler.ts +11 -0
  66. package/src/database/migration/20260602002951_lowly_union_jack.ts +24 -0
  67. package/src/database/migration/20260602182828_add_project_directories.ts +20 -0
  68. package/src/database/migration/20260603001617_session_message_projection_indexes.ts +19 -0
  69. package/src/database/migration/20260603040000_session_message_projection_order.ts +19 -0
  70. package/src/database/migration/20260603141458_session_input_inbox.ts +25 -0
  71. package/src/database/migration/20260603160727_jittery_ezekiel_stane.ts +20 -0
  72. package/src/database/migration/20260604172448_event_sourced_session_input.ts +47 -0
  73. package/src/database/migration/20260605003541_add_session_context_snapshot.ts +21 -0
  74. package/src/database/migration/20260605042240_add_context_epoch_agent.ts +11 -0
  75. package/src/database/migration/20260611035744_credential.ts +25 -0
  76. package/src/database/migration/20260611192811_lush_chimera.ts +25 -0
  77. package/src/database/migration/20260612174303_project_dir_strategy.ts +29 -0
  78. package/src/database/migration/20260622142730_simplify_session_context_epoch.ts +13 -0
  79. package/src/database/migration/20260622170816_reset_v2_session_state.ts +17 -0
  80. package/src/database/migration/20260622202450_simplify_session_input.ts +17 -0
  81. package/src/database/migration.gen.ts +44 -0
  82. package/src/database/migration.ts +81 -0
  83. package/src/database/path.ts +91 -0
  84. package/src/database/schema.gen.ts +274 -0
  85. package/src/database/schema.sql.ts +10 -0
  86. package/src/database/sqlite.bun.ts +183 -0
  87. package/src/database/sqlite.node.ts +178 -0
  88. package/src/database/sqlite.ts +8 -0
  89. package/src/effect/app-node-builder.ts +23 -0
  90. package/src/effect/app-node-platform.ts +18 -0
  91. package/src/effect/app-node.ts +14 -0
  92. package/src/effect/dfdf +1 -0
  93. package/src/effect/keyed-mutex.ts +45 -0
  94. package/src/effect/layer-node.ts +333 -0
  95. package/src/effect/memo-map.ts +3 -0
  96. package/src/effect/runtime.ts +21 -0
  97. package/src/effect/service-use.ts +43 -0
  98. package/src/event/sql.ts +25 -0
  99. package/src/event.ts +638 -0
  100. package/src/file-mutation.ts +207 -0
  101. package/src/file.ts +6 -0
  102. package/src/filesystem/fff.bun.ts +140 -0
  103. package/src/filesystem/fff.node.ts +138 -0
  104. package/src/filesystem/ignore.ts +67 -0
  105. package/src/filesystem/protected.ts +53 -0
  106. package/src/filesystem/search.ts +239 -0
  107. package/src/filesystem/watcher.ts +140 -0
  108. package/src/filesystem.ts +118 -0
  109. package/src/flag/flag.ts +78 -0
  110. package/src/fs-util.ts +274 -0
  111. package/src/git.ts +987 -0
  112. package/src/github-copilot/README.md +5 -0
  113. package/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  114. package/src/github-copilot/chat/get-response-metadata.ts +15 -0
  115. package/src/github-copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  116. package/src/github-copilot/chat/openai-compatible-api-types.ts +64 -0
  117. package/src/github-copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  118. package/src/github-copilot/chat/openai-compatible-chat-options.ts +28 -0
  119. package/src/github-copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  120. package/src/github-copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  121. package/src/github-copilot/copilot-provider.ts +100 -0
  122. package/src/github-copilot/openai-compatible-error.ts +27 -0
  123. package/src/github-copilot/responses/convert-to-openai-responses-input.ts +335 -0
  124. package/src/github-copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  125. package/src/github-copilot/responses/openai-config.ts +18 -0
  126. package/src/github-copilot/responses/openai-error.ts +22 -0
  127. package/src/github-copilot/responses/openai-responses-api-types.ts +214 -0
  128. package/src/github-copilot/responses/openai-responses-language-model.ts +1770 -0
  129. package/src/github-copilot/responses/openai-responses-prepare-tools.ts +173 -0
  130. package/src/github-copilot/responses/openai-responses-settings.ts +1 -0
  131. package/src/github-copilot/responses/tool/code-interpreter.ts +87 -0
  132. package/src/github-copilot/responses/tool/file-search.ts +127 -0
  133. package/src/github-copilot/responses/tool/image-generation.ts +114 -0
  134. package/src/github-copilot/responses/tool/local-shell.ts +64 -0
  135. package/src/github-copilot/responses/tool/web-search-preview.ts +103 -0
  136. package/src/github-copilot/responses/tool/web-search.ts +102 -0
  137. package/src/global.ts +87 -0
  138. package/src/id/id.ts +47 -0
  139. package/src/image/photon.ts +94 -0
  140. package/src/image.ts +81 -0
  141. package/src/installation/version.ts +8 -0
  142. package/src/instruction-context.ts +101 -0
  143. package/src/integration/connection.ts +12 -0
  144. package/src/integration.ts +520 -0
  145. package/src/location-mutation.ts +162 -0
  146. package/src/location-service-map.ts +18 -0
  147. package/src/location-services.ts +115 -0
  148. package/src/location.ts +39 -0
  149. package/src/markdown.d.ts +4 -0
  150. package/src/model.ts +41 -0
  151. package/src/models-dev.ts +260 -0
  152. package/src/npm-config.ts +40 -0
  153. package/src/npm.ts +269 -0
  154. package/src/oauth/page.ts +276 -0
  155. package/src/observability/logging.ts +71 -0
  156. package/src/observability/otlp.ts +79 -0
  157. package/src/observability/shared.ts +1 -0
  158. package/src/observability.ts +24 -0
  159. package/src/patch.ts +197 -0
  160. package/src/permission/saved.ts +79 -0
  161. package/src/permission/sql.ts +20 -0
  162. package/src/permission.ts +310 -0
  163. package/src/plugin/agent.ts +206 -0
  164. package/src/plugin/command/initialize.txt +65 -0
  165. package/src/plugin/command/review.txt +100 -0
  166. package/src/plugin/command.ts +25 -0
  167. package/src/plugin/host.ts +219 -0
  168. package/src/plugin/internal.ts +153 -0
  169. package/src/plugin/layer-map.example.ts +94 -0
  170. package/src/plugin/models-dev.ts +183 -0
  171. package/src/plugin/promise.ts +93 -0
  172. package/src/plugin/provider/alibaba.ts +15 -0
  173. package/src/plugin/provider/amazon-bedrock.ts +126 -0
  174. package/src/plugin/provider/anthropic.ts +27 -0
  175. package/src/plugin/provider/azure.ts +82 -0
  176. package/src/plugin/provider/cerebras.ts +26 -0
  177. package/src/plugin/provider/cloudflare-ai-gateway.ts +81 -0
  178. package/src/plugin/provider/cloudflare-workers-ai.ts +81 -0
  179. package/src/plugin/provider/cohere.ts +15 -0
  180. package/src/plugin/provider/deepinfra.ts +15 -0
  181. package/src/plugin/provider/dynamic.ts +31 -0
  182. package/src/plugin/provider/gateway.ts +15 -0
  183. package/src/plugin/provider/github-copilot.ts +52 -0
  184. package/src/plugin/provider/gitlab.ts +65 -0
  185. package/src/plugin/provider/google-vertex.ts +173 -0
  186. package/src/plugin/provider/google.ts +15 -0
  187. package/src/plugin/provider/groq.ts +15 -0
  188. package/src/plugin/provider/kilo.ts +21 -0
  189. package/src/plugin/provider/llmgateway.ts +26 -0
  190. package/src/plugin/provider/mistral.ts +15 -0
  191. package/src/plugin/provider/nvidia.ts +22 -0
  192. package/src/plugin/provider/openai-compatible.ts +17 -0
  193. package/src/plugin/provider/openai.ts +292 -0
  194. package/src/plugin/provider/opencode.ts +311 -0
  195. package/src/plugin/provider/openrouter.ts +36 -0
  196. package/src/plugin/provider/perplexity.ts +15 -0
  197. package/src/plugin/provider/sap-ai-core.ts +46 -0
  198. package/src/plugin/provider/snowflake-cortex.ts +89 -0
  199. package/src/plugin/provider/togetherai.ts +15 -0
  200. package/src/plugin/provider/venice.ts +15 -0
  201. package/src/plugin/provider/vercel.ts +27 -0
  202. package/src/plugin/provider/xai.ts +22 -0
  203. package/src/plugin/provider/zenmux.ts +21 -0
  204. package/src/plugin/provider.ts +71 -0
  205. package/src/plugin/skill/customize-neurocode.md +452 -0
  206. package/src/plugin/skill.ts +31 -0
  207. package/src/plugin/variant.ts +39 -0
  208. package/src/plugin.ts +167 -0
  209. package/src/policy.ts +49 -0
  210. package/src/process.ts +261 -0
  211. package/src/project/copy-strategies.ts +35 -0
  212. package/src/project/copy.ts +292 -0
  213. package/src/project/directories.ts +158 -0
  214. package/src/project/schema.ts +16 -0
  215. package/src/project/sql.ts +35 -0
  216. package/src/project.ts +136 -0
  217. package/src/provider.ts +25 -0
  218. package/src/pty/protocol.ts +37 -0
  219. package/src/pty/pty.bun.ts +26 -0
  220. package/src/pty/pty.node.ts +29 -0
  221. package/src/pty/pty.ts +25 -0
  222. package/src/pty/schema.ts +1 -0
  223. package/src/pty/ticket.ts +56 -0
  224. package/src/pty.ts +318 -0
  225. package/src/public-event-manifest.ts +7 -0
  226. package/src/question.ts +153 -0
  227. package/src/reference/guidance.ts +69 -0
  228. package/src/reference.ts +125 -0
  229. package/src/repository-cache.ts +259 -0
  230. package/src/repository.ts +214 -0
  231. package/src/ripgrep/binary.ts +132 -0
  232. package/src/ripgrep.ts +281 -0
  233. package/src/schema.ts +79 -0
  234. package/src/session/compaction.ts +241 -0
  235. package/src/session/context-epoch.ts +174 -0
  236. package/src/session/error.ts +24 -0
  237. package/src/session/event.ts +2 -0
  238. package/src/session/execution/local.ts +46 -0
  239. package/src/session/execution.ts +34 -0
  240. package/src/session/history.ts +101 -0
  241. package/src/session/info.ts +50 -0
  242. package/src/session/input.ts +288 -0
  243. package/src/session/message-updater.ts +397 -0
  244. package/src/session/message.ts +2 -0
  245. package/src/session/projector.ts +458 -0
  246. package/src/session/prompt.ts +1 -0
  247. package/src/session/revert.ts +121 -0
  248. package/src/session/run-coordinator.ts +104 -0
  249. package/src/session/runner/index.ts +28 -0
  250. package/src/session/runner/llm.ts +432 -0
  251. package/src/session/runner/max-steps.ts +16 -0
  252. package/src/session/runner/model.ts +218 -0
  253. package/src/session/runner/publish-llm-event.ts +423 -0
  254. package/src/session/runner/to-llm-message.ts +171 -0
  255. package/src/session/schema.ts +9 -0
  256. package/src/session/sql.ts +176 -0
  257. package/src/session/store.ts +63 -0
  258. package/src/session/todo.ts +78 -0
  259. package/src/session.ts +486 -0
  260. package/src/share/sql.ts +13 -0
  261. package/src/shell.ts +226 -0
  262. package/src/skill/discovery.ts +213 -0
  263. package/src/skill/guidance.ts +76 -0
  264. package/src/skill.ts +132 -0
  265. package/src/snapshot.ts +266 -0
  266. package/src/state.ts +128 -0
  267. package/src/system-context/builtins.ts +50 -0
  268. package/src/system-context/index.ts +320 -0
  269. package/src/system-context/registry.ts +49 -0
  270. package/src/tool/AGENTS.md +59 -0
  271. package/src/tool/application-tools.ts +57 -0
  272. package/src/tool/apply-patch.ts +219 -0
  273. package/src/tool/bash.ts +207 -0
  274. package/src/tool/builtins.ts +48 -0
  275. package/src/tool/edit.ts +223 -0
  276. package/src/tool/glob.ts +105 -0
  277. package/src/tool/grep.ts +137 -0
  278. package/src/tool/http-body.ts +30 -0
  279. package/src/tool/question.ts +94 -0
  280. package/src/tool/read-filesystem.ts +366 -0
  281. package/src/tool/read.ts +117 -0
  282. package/src/tool/registry.ts +147 -0
  283. package/src/tool/skill.ts +109 -0
  284. package/src/tool/todowrite.ts +62 -0
  285. package/src/tool/tool.ts +162 -0
  286. package/src/tool/tools.ts +13 -0
  287. package/src/tool/webfetch.ts +218 -0
  288. package/src/tool/websearch.ts +260 -0
  289. package/src/tool/write.ts +101 -0
  290. package/src/tool-output-store.ts +211 -0
  291. package/src/util/array.ts +10 -0
  292. package/src/util/binary.ts +41 -0
  293. package/src/util/effect-flock.ts +284 -0
  294. package/src/util/encode.ts +51 -0
  295. package/src/util/error.ts +70 -0
  296. package/src/util/flock.ts +358 -0
  297. package/src/util/glob.ts +34 -0
  298. package/src/util/hash.ts +11 -0
  299. package/src/util/identifier.ts +1 -0
  300. package/src/util/iife.ts +3 -0
  301. package/src/util/lazy.ts +11 -0
  302. package/src/util/module.ts +10 -0
  303. package/src/util/path.ts +37 -0
  304. package/src/util/retry.ts +42 -0
  305. package/src/util/slug.ts +74 -0
  306. package/src/util/token.ts +5 -0
  307. package/src/util/which.ts +14 -0
  308. package/src/util/wildcard.ts +14 -0
  309. package/src/v1/config/agent.ts +89 -0
  310. package/src/v1/config/attachment.ts +25 -0
  311. package/src/v1/config/command.ts +13 -0
  312. package/src/v1/config/config.ts +192 -0
  313. package/src/v1/config/console-state.ts +16 -0
  314. package/src/v1/config/error.ts +39 -0
  315. package/src/v1/config/formatter.ts +13 -0
  316. package/src/v1/config/layout.ts +6 -0
  317. package/src/v1/config/lsp.ts +80 -0
  318. package/src/v1/config/mcp.ts +63 -0
  319. package/src/v1/config/migrate.ts +254 -0
  320. package/src/v1/config/permission.ts +50 -0
  321. package/src/v1/config/plugin.ts +9 -0
  322. package/src/v1/config/provider-options.ts +227 -0
  323. package/src/v1/config/provider.ts +121 -0
  324. package/src/v1/config/server.ts +19 -0
  325. package/src/v1/config/skills.ts +13 -0
  326. package/src/v1/permission.ts +33 -0
  327. package/src/v1/session.ts +68 -0
  328. package/src/v2-schema.ts +3 -0
  329. package/src/workspace.ts +6 -0
  330. package/sst-env.d.ts +10 -0
  331. package/test/agent.test.ts +131 -0
  332. package/test/application-tools.test.ts +287 -0
  333. package/test/background-job.test.ts +106 -0
  334. package/test/catalog.test.ts +353 -0
  335. package/test/command.test.ts +56 -0
  336. package/test/config/agent.test.ts +349 -0
  337. package/test/config/command.test.ts +83 -0
  338. package/test/config/config.test.ts +798 -0
  339. package/test/config/fixtures/plugin/directory-plugin.ts +13 -0
  340. package/test/config/plugin.test.ts +248 -0
  341. package/test/config/provider-options.test.ts +224 -0
  342. package/test/config/provider.test.ts +269 -0
  343. package/test/config/skill.test.ts +80 -0
  344. package/test/credential.test.ts +36 -0
  345. package/test/database-migration.test.ts +644 -0
  346. package/test/effect/cross-spawn-spawner.test.ts +426 -0
  347. package/test/effect/keyed-mutex.test.ts +73 -0
  348. package/test/effect/layer-node/layer-node-types.test.ts +143 -0
  349. package/test/effect/layer-node/layer-node.test.ts +261 -0
  350. package/test/effect/layer-node/node-build.test.ts +125 -0
  351. package/test/effect/observability.test.ts +109 -0
  352. package/test/event.test.ts +1124 -0
  353. package/test/file-mutation.test.ts +368 -0
  354. package/test/filesystem/filesystem.test.ts +387 -0
  355. package/test/filesystem/ignore.test.ts +10 -0
  356. package/test/filesystem/search.test.ts +44 -0
  357. package/test/filesystem/watcher.test.ts +266 -0
  358. package/test/fixture/effect-flock-worker.ts +60 -0
  359. package/test/fixture/flock-worker.ts +72 -0
  360. package/test/fixture/git.ts +49 -0
  361. package/test/fixture/location.ts +26 -0
  362. package/test/fixture/tmpdir.ts +25 -0
  363. package/test/fixtures/recordings/session-runner/openai-chat-streams-text.json +27 -0
  364. package/test/git.test.ts +164 -0
  365. package/test/github-copilot/convert-to-copilot-messages.test.ts +523 -0
  366. package/test/github-copilot/copilot-chat-model.test.ts +592 -0
  367. package/test/github-copilot/openai-responses-language-model.test.ts +206 -0
  368. package/test/global.test.ts +16 -0
  369. package/test/instruction-context.test.ts +323 -0
  370. package/test/integration.test.ts +349 -0
  371. package/test/legacy-event-schema.test.ts +16 -0
  372. package/test/lib/effect.ts +53 -0
  373. package/test/lib/tool.ts +20 -0
  374. package/test/location-filesystem.test.ts +69 -0
  375. package/test/location-layer.test.ts +226 -0
  376. package/test/location-mutation.test.ts +178 -0
  377. package/test/location.test.ts +42 -0
  378. package/test/model.test.ts +23 -0
  379. package/test/models.test.ts +290 -0
  380. package/test/move-session.test.ts +235 -0
  381. package/test/npm-config.test.ts +51 -0
  382. package/test/npm.test.ts +84 -0
  383. package/test/oauth-page.test.ts +15 -0
  384. package/test/patch.test.ts +68 -0
  385. package/test/permission.test.ts +315 -0
  386. package/test/plugin/command.test.ts +47 -0
  387. package/test/plugin/fixture.ts +52 -0
  388. package/test/plugin/fixtures/config-effect-plugin.ts +15 -0
  389. package/test/plugin/fixtures/config-promise-plugin.ts +13 -0
  390. package/test/plugin/fixtures/invalid-plugin.ts +1 -0
  391. package/test/plugin/fixtures/models-dev.json +14 -0
  392. package/test/plugin/fixtures/provider-factory.ts +9 -0
  393. package/test/plugin/host.ts +303 -0
  394. package/test/plugin/models-dev.test.ts +170 -0
  395. package/test/plugin/promise.test.ts +67 -0
  396. package/test/plugin/provider-alibaba.test.ts +92 -0
  397. package/test/plugin/provider-amazon-bedrock.test.ts +622 -0
  398. package/test/plugin/provider-anthropic.test.ts +94 -0
  399. package/test/plugin/provider-azure-cognitive-services.test.ts +199 -0
  400. package/test/plugin/provider-azure.test.ts +293 -0
  401. package/test/plugin/provider-cerebras.test.ts +136 -0
  402. package/test/plugin/provider-cloudflare-ai-gateway.test.ts +426 -0
  403. package/test/plugin/provider-cloudflare-workers-ai.test.ts +279 -0
  404. package/test/plugin/provider-cohere.test.ts +123 -0
  405. package/test/plugin/provider-deepinfra.test.ts +159 -0
  406. package/test/plugin/provider-dynamic.test.ts +184 -0
  407. package/test/plugin/provider-gateway.test.ts +115 -0
  408. package/test/plugin/provider-github-copilot.test.ts +278 -0
  409. package/test/plugin/provider-gitlab.test.ts +308 -0
  410. package/test/plugin/provider-google-vertex-anthropic.test.ts +264 -0
  411. package/test/plugin/provider-google-vertex.test.ts +387 -0
  412. package/test/plugin/provider-google.test.ts +80 -0
  413. package/test/plugin/provider-groq.test.ts +121 -0
  414. package/test/plugin/provider-kilo.test.ts +99 -0
  415. package/test/plugin/provider-llmgateway.test.ts +80 -0
  416. package/test/plugin/provider-mistral.test.ts +129 -0
  417. package/test/plugin/provider-nvidia.test.ts +97 -0
  418. package/test/plugin/provider-openai-compatible.test.ts +107 -0
  419. package/test/plugin/provider-openai.test.ts +176 -0
  420. package/test/plugin/provider-opencode.test.ts +423 -0
  421. package/test/plugin/provider-openrouter.test.ts +113 -0
  422. package/test/plugin/provider-perplexity.test.ts +122 -0
  423. package/test/plugin/provider-sap-ai-core.test.ts +159 -0
  424. package/test/plugin/provider-snowflake-cortex.test.ts +261 -0
  425. package/test/plugin/provider-togetherai.test.ts +129 -0
  426. package/test/plugin/provider-venice.test.ts +115 -0
  427. package/test/plugin/provider-vercel.test.ts +83 -0
  428. package/test/plugin/provider-xai.test.ts +125 -0
  429. package/test/plugin/provider-zenmux.test.ts +110 -0
  430. package/test/plugin/skill.test.ts +25 -0
  431. package/test/plugin/variant.test.ts +67 -0
  432. package/test/plugin.test.ts +71 -0
  433. package/test/policy.test.ts +85 -0
  434. package/test/preload.ts +5 -0
  435. package/test/process/process.test.ts +368 -0
  436. package/test/project-copy.test.ts +400 -0
  437. package/test/project-directories.test.ts +61 -0
  438. package/test/project.test.ts +221 -0
  439. package/test/provider-mistral.test.ts +282 -0
  440. package/test/provider-xai-responses.test.ts +32 -0
  441. package/test/pty/info-schema.test.ts +32 -0
  442. package/test/pty/protocol.test.ts +27 -0
  443. package/test/pty/pty-session.test.ts +240 -0
  444. package/test/pty/ticket.test.ts +62 -0
  445. package/test/question.test.ts +114 -0
  446. package/test/reference-guidance.test.ts +76 -0
  447. package/test/reference.test.ts +78 -0
  448. package/test/repository-cache.test.ts +153 -0
  449. package/test/repository.test.ts +71 -0
  450. package/test/ripgrep.test.ts +65 -0
  451. package/test/session-compaction.test.ts +27 -0
  452. package/test/session-create.test.ts +425 -0
  453. package/test/session-history.test.ts +165 -0
  454. package/test/session-projector.test.ts +533 -0
  455. package/test/session-prompt.test.ts +584 -0
  456. package/test/session-run-coordinator.test.ts +418 -0
  457. package/test/session-runner-message.test.ts +501 -0
  458. package/test/session-runner-model.test.ts +347 -0
  459. package/test/session-runner-recorded.test.ts +193 -0
  460. package/test/session-runner-tool-events.test.ts +136 -0
  461. package/test/session-runner-tool-registry.test.ts +452 -0
  462. package/test/session-runner.test.ts +3365 -0
  463. package/test/session-todo.test.ts +94 -0
  464. package/test/session-tool-progress.test.ts +158 -0
  465. package/test/shared-schema.test.ts +206 -0
  466. package/test/shell.test.ts +108 -0
  467. package/test/skill/guidance.test.ts +144 -0
  468. package/test/skill-discovery.test.ts +165 -0
  469. package/test/skill.test.ts +125 -0
  470. package/test/snapshot.test.ts +178 -0
  471. package/test/state.test.ts +115 -0
  472. package/test/system-context/builtins.test.ts +129 -0
  473. package/test/system-context/index.test.ts +307 -0
  474. package/test/system-context/registry.test.ts +114 -0
  475. package/test/tool-apply-patch.test.ts +436 -0
  476. package/test/tool-bash.test.ts +437 -0
  477. package/test/tool-edit.test.ts +434 -0
  478. package/test/tool-output-store.test.ts +247 -0
  479. package/test/tool-question.test.ts +162 -0
  480. package/test/tool-read-filesystem.test.ts +118 -0
  481. package/test/tool-read.test.ts +674 -0
  482. package/test/tool-skill.test.ts +149 -0
  483. package/test/tool-todowrite.test.ts +125 -0
  484. package/test/tool-webfetch.test.ts +281 -0
  485. package/test/tool-websearch.test.ts +316 -0
  486. package/test/tool-write.test.ts +305 -0
  487. package/test/util/effect-flock.test.ts +392 -0
  488. package/test/util/flock.test.ts +428 -0
  489. package/test/util/which.test.ts +100 -0
  490. package/tsconfig.json +7 -0
@@ -0,0 +1,109 @@
1
+ export * as SkillTool from "./skill"
2
+
3
+ import path from "path"
4
+ import { ToolFailure } from "@neurocode-ai/llm"
5
+ import { Effect, Layer, Schema } from "effect"
6
+ import { makeLocationNode } from "../effect/app-node"
7
+ import { FSUtil } from "../fs-util"
8
+ import { SkillV2 } from "../skill"
9
+ import { PermissionV2 } from "../permission"
10
+ import { ToolRegistry } from "./registry"
11
+ import { Tool } from "./tool"
12
+ import { Tools } from "./tools"
13
+
14
+ export const name = "skill"
15
+ const FILE_LIMIT = 10
16
+
17
+ export const Input = Schema.Struct({
18
+ name: Schema.String.annotate({ description: "The name of the skill from the available skills list" }),
19
+ })
20
+
21
+ export const Output = Schema.Struct({
22
+ name: Schema.String,
23
+ directory: Schema.String,
24
+ output: Schema.String,
25
+ })
26
+
27
+ export const description = [
28
+ "Load a specialized skill when the task at hand matches one of the available skills in the system context.",
29
+ "",
30
+ "Use this tool to inject the skill's instructions and resources into the current conversation. The output may contain detailed workflow guidance as well as references to scripts, files, etc. in the same directory as the skill.",
31
+ "",
32
+ "The skill name must match one of the available skills in the system context.",
33
+ ].join("\n")
34
+
35
+ export const toModelOutput = (skill: SkillV2.Info, files: ReadonlyArray<string>) => {
36
+ const directory = path.dirname(skill.location)
37
+ return [
38
+ `<skill_content name="${skill.name}">`,
39
+ `# Skill: ${skill.name}`,
40
+ "",
41
+ skill.content.trim(),
42
+ "",
43
+ `Base directory for this skill: ${directory}`,
44
+ "Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.",
45
+ "Note: file list is sampled.",
46
+ "",
47
+ "<skill_files>",
48
+ ...files.map((file) => `<file>${file}</file>`),
49
+ "</skill_files>",
50
+ "</skill_content>",
51
+ ].join("\n")
52
+ }
53
+
54
+ const unableToLoad = (name: string, error?: unknown) =>
55
+ new ToolFailure({ message: `Unable to load skill ${name}`, error })
56
+
57
+ const layer = Layer.effectDiscard(
58
+ Effect.gen(function* () {
59
+ const tools = yield* Tools.Service
60
+ const fs = yield* FSUtil.Service
61
+ const skills = yield* SkillV2.Service
62
+ const permission = yield* PermissionV2.Service
63
+ yield* tools
64
+ .register({
65
+ [name]: Tool.make({
66
+ description,
67
+ input: Input,
68
+ output: Output,
69
+ toModelOutput: ({ output }) => [{ type: "text", text: output.output }],
70
+ execute: (input, context) =>
71
+ Effect.gen(function* () {
72
+ const current = yield* skills.list()
73
+ const skill = current.find((skill) => skill.name === input.name)
74
+ if (!skill) return yield* unableToLoad(input.name)
75
+ return yield* Effect.gen(function* () {
76
+ yield* permission.assert({
77
+ action: name,
78
+ resources: [skill.name],
79
+ save: [skill.name],
80
+ sessionID: context.sessionID,
81
+ agent: context.agent,
82
+ source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
83
+ })
84
+ const directory = path.dirname(skill.location)
85
+ const files =
86
+ path.basename(skill.location) === "SKILL.md"
87
+ ? (yield* fs.glob("**/*", { cwd: directory, absolute: true, include: "file", dot: true }))
88
+ .filter((file) => path.basename(file) !== "SKILL.md")
89
+ .toSorted()
90
+ .slice(0, FILE_LIMIT)
91
+ : []
92
+ return {
93
+ name: skill.name,
94
+ directory,
95
+ output: toModelOutput(skill, files),
96
+ }
97
+ }).pipe(Effect.mapError((error) => unableToLoad(input.name, error)))
98
+ }),
99
+ }),
100
+ })
101
+ .pipe(Effect.orDie)
102
+ }),
103
+ )
104
+
105
+ export const node = makeLocationNode({
106
+ name: "tool/skill",
107
+ layer,
108
+ deps: [ToolRegistry.node, FSUtil.node, SkillV2.node, PermissionV2.node],
109
+ })
@@ -0,0 +1,62 @@
1
+ export * as TodoWriteTool from "./todowrite"
2
+
3
+ import { ToolFailure } from "@neurocode-ai/llm"
4
+ import { Effect, Layer, Schema } from "effect"
5
+ import { makeLocationNode } from "../effect/app-node"
6
+ import { PermissionV2 } from "../permission"
7
+ import { SessionTodo } from "../session/todo"
8
+ import { ToolRegistry } from "./registry"
9
+ import { Tool } from "./tool"
10
+ import { Tools } from "./tools"
11
+
12
+ export const name = "todowrite"
13
+
14
+ export const Input = Schema.Struct({
15
+ todos: Schema.Array(SessionTodo.Info).annotate({ description: "The updated todo list" }),
16
+ })
17
+
18
+ export const Output = Schema.Struct({
19
+ todos: Schema.Array(SessionTodo.Info),
20
+ })
21
+ export type Output = typeof Output.Type
22
+
23
+ export const toModelOutput = (output: Output) => JSON.stringify(output.todos, null, 2)
24
+
25
+ const layer = Layer.effectDiscard(
26
+ Effect.gen(function* () {
27
+ const tools = yield* Tools.Service
28
+ const todos = yield* SessionTodo.Service
29
+ const permission = yield* PermissionV2.Service
30
+
31
+ yield* tools
32
+ .register({
33
+ [name]: Tool.make({
34
+ description:
35
+ "Create and maintain a structured task list for the current coding session. Use it to track progress during multi-step work and keep todo statuses current.",
36
+ input: Input,
37
+ output: Output,
38
+ toModelOutput: ({ output }) => [{ type: "text", text: toModelOutput(output) }],
39
+ execute: (input, context) =>
40
+ Effect.gen(function* () {
41
+ yield* permission.assert({
42
+ action: name,
43
+ resources: ["*"],
44
+ save: ["*"],
45
+ sessionID: context.sessionID,
46
+ agent: context.agent,
47
+ source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
48
+ })
49
+ yield* todos.update({ sessionID: context.sessionID, todos: input.todos })
50
+ return { todos: input.todos }
51
+ }).pipe(Effect.mapError(() => new ToolFailure({ message: "Unable to update todos" }))),
52
+ }),
53
+ })
54
+ .pipe(Effect.orDie)
55
+ }),
56
+ )
57
+
58
+ export const node = makeLocationNode({
59
+ name: "tool/todowrite",
60
+ layer,
61
+ deps: [ToolRegistry.node, PermissionV2.node, SessionTodo.node],
62
+ })
@@ -0,0 +1,162 @@
1
+ export * as Tool from "./tool"
2
+
3
+ import { ToolDefinition, ToolFailure, ToolOutput, type ToolCall } from "@neurocode-ai/llm"
4
+ import { Effect, JsonSchema, Schema } from "effect"
5
+ import type { AgentV2 } from "../agent"
6
+ import type { SessionMessage } from "../session/message"
7
+ import type { SessionSchema } from "../session/schema"
8
+
9
+ export interface Context {
10
+ readonly sessionID: SessionSchema.ID
11
+ readonly agent: AgentV2.ID
12
+ readonly assistantMessageID: SessionMessage.ID
13
+ readonly toolCallID: string
14
+ }
15
+
16
+ export type SchemaType<A> = Schema.Codec<A, any, never, never>
17
+
18
+ declare const TypeId: unique symbol
19
+
20
+ export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
21
+ readonly [TypeId]: {
22
+ readonly _Input: Input
23
+ readonly _Output: Output
24
+ }
25
+ }
26
+
27
+ export type AnyTool = Definition<any, any>
28
+ export const Failure = ToolFailure
29
+ export type Failure = ToolFailure
30
+
31
+ export class RegistrationError extends Schema.TaggedErrorClass<RegistrationError>()("Tool.RegistrationError", {
32
+ name: Schema.String,
33
+ message: Schema.String,
34
+ }) {}
35
+
36
+ export type Content =
37
+ | { readonly type: "text"; readonly text: string }
38
+ | { readonly type: "file"; readonly data: string; readonly mime: string; readonly name?: string }
39
+
40
+ type Config<
41
+ Input extends SchemaType<any>,
42
+ Output extends SchemaType<any>,
43
+ Structured extends SchemaType<any> = Output,
44
+ > = {
45
+ readonly description: string
46
+ readonly input: Input
47
+ readonly output: Output
48
+ readonly structured?: Structured
49
+ readonly toStructuredOutput?: (input: {
50
+ readonly input: Schema.Schema.Type<Input>
51
+ readonly output: Output["Encoded"]
52
+ }) => Schema.Schema.Type<Structured>
53
+ readonly execute: (
54
+ input: Schema.Schema.Type<Input>,
55
+ context: Context,
56
+ ) => Effect.Effect<Schema.Schema.Type<Output>, ToolFailure>
57
+ readonly toModelOutput?: (input: {
58
+ readonly input: Schema.Schema.Type<Input>
59
+ readonly output: Output["Encoded"]
60
+ }) => ReadonlyArray<Content>
61
+ }
62
+
63
+ type Runtime = {
64
+ readonly permission?: string
65
+ readonly definition: (name: string) => ToolDefinition
66
+ readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, ToolFailure>
67
+ }
68
+
69
+ const runtimes = new WeakMap<AnyTool, Runtime>()
70
+
71
+ export function make<
72
+ Input extends SchemaType<any>,
73
+ Output extends SchemaType<any>,
74
+ Structured extends SchemaType<any> = Output,
75
+ >(config: Config<Input, Output, Structured>): Definition<Input, Structured> {
76
+ const tool = Object.freeze({}) as Definition<Input, Structured>
77
+ const definitions = new Map<string, ToolDefinition>()
78
+ runtimes.set(tool, {
79
+ definition: (name) => {
80
+ const cached = definitions.get(name)
81
+ if (cached) return cached
82
+ const definition = new ToolDefinition({
83
+ name,
84
+ description: config.description,
85
+ inputSchema: toJsonSchema(config.input),
86
+ outputSchema: toJsonSchema(config.structured ?? config.output),
87
+ })
88
+ definitions.set(name, definition)
89
+ return definition
90
+ },
91
+ settle: (call, context) =>
92
+ Schema.decodeUnknownEffect(config.input)(call.input).pipe(
93
+ Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })),
94
+ Effect.flatMap((input) =>
95
+ config.execute(input, context).pipe(
96
+ Effect.flatMap((output) =>
97
+ Schema.encodeEffect(config.output)(output).pipe(
98
+ Effect.flatMap((output) => {
99
+ if (!config.structured || !config.toStructuredOutput)
100
+ return Effect.succeed({ output, structured: output })
101
+ return Schema.encodeEffect(config.structured)(config.toStructuredOutput({ input, output })).pipe(
102
+ Effect.map((structured) => ({ output, structured })),
103
+ )
104
+ }),
105
+ Effect.mapError(
106
+ (error) =>
107
+ new ToolFailure({
108
+ message: `Tool returned an invalid value for its output schema: ${error.message}`,
109
+ }),
110
+ ),
111
+ ),
112
+ ),
113
+ Effect.map(({ output, structured }) => ({
114
+ structured,
115
+ content:
116
+ config.toModelOutput?.({ input, output }).map((part) =>
117
+ part.type === "text"
118
+ ? { type: "text" as const, text: part.text }
119
+ : {
120
+ type: "file" as const,
121
+ uri: `data:${part.mime};base64,${part.data}`,
122
+ mime: part.mime,
123
+ name: part.name,
124
+ },
125
+ ) ?? (typeof output === "string" ? [{ type: "text" as const, text: output }] : []),
126
+ })),
127
+ ),
128
+ ),
129
+ ),
130
+ })
131
+ return tool
132
+ }
133
+
134
+ export const validateName = (name: string) =>
135
+ /^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(name)
136
+ ? Effect.void
137
+ : Effect.fail(new RegistrationError({ name, message: `Invalid tool name: ${name}` }))
138
+
139
+ export const withPermission = <Input extends SchemaType<any>, Output extends SchemaType<any>>(
140
+ tool: Definition<Input, Output>,
141
+ permission: string,
142
+ ) => {
143
+ const decorated = Object.freeze({}) as Definition<Input, Output>
144
+ runtimes.set(decorated, { ...runtimeOf(tool), permission })
145
+ return decorated
146
+ }
147
+
148
+ export const permission = (tool: AnyTool, name: string) => runtimeOf(tool).permission ?? name
149
+ export const definition = (name: string, tool: AnyTool) => runtimeOf(tool).definition(name)
150
+ export const settle = (tool: AnyTool, call: ToolCall, context: Context) => runtimeOf(tool).settle(call, context)
151
+
152
+ function runtimeOf(tool: AnyTool) {
153
+ const runtime = runtimes.get(tool)
154
+ if (!runtime) throw new TypeError("Invalid Core Tool value")
155
+ return runtime
156
+ }
157
+
158
+ function toJsonSchema(schema: Schema.Top): JsonSchema.JsonSchema {
159
+ const document = Schema.toJsonSchemaDocument(schema)
160
+ if (Object.keys(document.definitions).length === 0) return document.schema
161
+ return { ...document.schema, $defs: document.definitions }
162
+ }
@@ -0,0 +1,13 @@
1
+ export * as Tools from "./tools"
2
+
3
+ import { Context, Effect, Scope } from "effect"
4
+ import { Tool } from "./tool"
5
+
6
+ export interface Interface {
7
+ readonly register: (
8
+ tools: Readonly<Record<string, Tool.AnyTool>>,
9
+ ) => Effect.Effect<void, Tool.RegistrationError, Scope.Scope>
10
+ }
11
+
12
+ /** Narrow registration-only Location capability. */
13
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/Tools") {}
@@ -0,0 +1,218 @@
1
+ export * as WebFetchTool from "./webfetch"
2
+
3
+ import { ToolFailure } from "@neurocode-ai/llm"
4
+ import { Duration, Effect, Layer, Schema } from "effect"
5
+ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
6
+ import { Parser } from "htmlparser2"
7
+ import TurndownService from "turndown"
8
+ import { makeLocationNode } from "../effect/app-node"
9
+ import { LayerNodePlatform } from "../effect/app-node-platform"
10
+ import { PermissionV2 } from "../permission"
11
+ import { collectBoundedResponseBody } from "./http-body"
12
+ import { ToolRegistry } from "./registry"
13
+ import { Tool } from "./tool"
14
+ import { Tools } from "./tools"
15
+
16
+ export const name = "webfetch"
17
+ export const MAX_RESPONSE_BYTES = 5 * 1024 * 1024
18
+ export const DEFAULT_TIMEOUT_SECONDS = 30
19
+ export const MAX_TIMEOUT_SECONDS = 120
20
+
21
+ export const description = `Fetch content from an HTTP or HTTPS URL and return it as text, markdown, or HTML. Markdown is the default.
22
+
23
+ Use a more targeted tool when one is available. This tool is read-only. Large text results may be replaced with a preview while the complete output is retained in managed storage.`
24
+
25
+ const Timeout = Schema.Number.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(MAX_TIMEOUT_SECONDS))
26
+
27
+ export const Input = Schema.Struct({
28
+ url: Schema.String.annotate({ description: "The HTTP or HTTPS URL to fetch content from" }),
29
+ format: Schema.Literals(["text", "markdown", "html"])
30
+ .annotate({ description: "The format to return the content in. Defaults to markdown." })
31
+ .pipe(Schema.withDecodingDefault(Effect.succeed("markdown" as const))),
32
+ timeout: Timeout.pipe(Schema.optional).annotate({
33
+ description: `Optional timeout in seconds (maximum: ${MAX_TIMEOUT_SECONDS})`,
34
+ }),
35
+ })
36
+
37
+ const Output = Schema.Struct({
38
+ url: Schema.String,
39
+ contentType: Schema.String,
40
+ format: Input.fields.format,
41
+ output: Schema.String,
42
+ })
43
+
44
+ type Format = (typeof Input.Type)["format"]
45
+
46
+ const acceptHeader = (format: Format) => {
47
+ switch (format) {
48
+ case "markdown":
49
+ return "text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1"
50
+ case "text":
51
+ return "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1"
52
+ case "html":
53
+ return "text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1"
54
+ }
55
+ return "*/*"
56
+ }
57
+
58
+ const headers = (format: Format, userAgent: string) => ({
59
+ "User-Agent": userAgent,
60
+ Accept: acceptHeader(format),
61
+ "Accept-Language": "en-US,en;q=0.9",
62
+ })
63
+
64
+ const browserUserAgent =
65
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
66
+
67
+ const isCloudflareChallenge = (error: unknown) => {
68
+ if (!error || typeof error !== "object" || !("reason" in error)) return false
69
+ const reason = error.reason
70
+ if (
71
+ !reason ||
72
+ typeof reason !== "object" ||
73
+ !("_tag" in reason) ||
74
+ reason._tag !== "StatusCodeError" ||
75
+ !("response" in reason)
76
+ )
77
+ return false
78
+ const response = reason.response as HttpClientResponse.HttpClientResponse
79
+ return response.status === 403 && response.headers["cf-mitigated"] === "challenge"
80
+ }
81
+
82
+ const request = (url: string, format: Format, userAgent = browserUserAgent) =>
83
+ HttpClientRequest.get(url).pipe(HttpClientRequest.setHeaders(headers(format, userAgent)))
84
+
85
+ const assertHttpUrl = (url: URL) => {
86
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("URL must use http:// or https://")
87
+ }
88
+
89
+ const execute = (http: HttpClient.HttpClient, url: string, format: Format, userAgent = browserUserAgent) =>
90
+ http.execute(request(url, format, userAgent)).pipe(Effect.flatMap(HttpClientResponse.filterStatusOk))
91
+
92
+ const collectBody = (response: HttpClientResponse.HttpClientResponse) =>
93
+ collectBoundedResponseBody(
94
+ response,
95
+ MAX_RESPONSE_BYTES,
96
+ () => new Error(`Response too large (exceeds ${MAX_RESPONSE_BYTES} byte limit)`),
97
+ )
98
+
99
+ const mimeFrom = (contentType: string) => contentType.split(";", 1)[0]?.trim().toLowerCase() ?? ""
100
+ const isImageAttachment = (mime: string) =>
101
+ mime.startsWith("image/") && mime !== "image/svg+xml" && mime !== "image/vnd.fastbidsheet"
102
+ const isTextualMime = (mime: string) =>
103
+ !mime ||
104
+ mime.startsWith("text/") ||
105
+ mime === "application/json" ||
106
+ mime.endsWith("+json") ||
107
+ mime === "application/xml" ||
108
+ mime.endsWith("+xml") ||
109
+ mime === "application/javascript" ||
110
+ mime === "application/x-javascript"
111
+ const convert = (content: string, contentType: string, format: Format) => {
112
+ if (!contentType.includes("text/html")) return content
113
+ if (format === "markdown") return convertHTMLToMarkdown(content)
114
+ if (format === "text") return extractTextFromHTML(content)
115
+ return content
116
+ }
117
+
118
+ const layer = Layer.effectDiscard(
119
+ Effect.gen(function* () {
120
+ const tools = yield* Tools.Service
121
+ const http = yield* HttpClient.HttpClient
122
+ const permission = yield* PermissionV2.Service
123
+
124
+ yield* tools
125
+ .register({
126
+ [name]: Tool.make({
127
+ description,
128
+ input: Input,
129
+ output: Output,
130
+ toModelOutput: ({ output }) => [{ type: "text", text: output.output }],
131
+ execute: (input, context) =>
132
+ Effect.gen(function* () {
133
+ yield* Effect.try({
134
+ try: () => assertHttpUrl(new URL(input.url)),
135
+ catch: (error) => error,
136
+ })
137
+
138
+ yield* permission.assert({
139
+ action: name,
140
+ resources: [input.url],
141
+ save: ["*"],
142
+ metadata: input,
143
+ sessionID: context.sessionID,
144
+ agent: context.agent,
145
+ source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
146
+ })
147
+
148
+ const { body, contentType } = yield* Effect.gen(function* () {
149
+ const response = yield* execute(http, input.url, input.format).pipe(
150
+ Effect.catchIf(isCloudflareChallenge, () => execute(http, input.url, input.format, "neurocode")),
151
+ )
152
+ const contentType = response.headers["content-type"] || ""
153
+ const mime = mimeFrom(contentType)
154
+ if (isImageAttachment(mime))
155
+ return yield* Effect.fail(new Error(`Unsupported fetched image content type: ${mime}`))
156
+ if (!isTextualMime(mime))
157
+ return yield* Effect.fail(new Error(`Unsupported fetched file content type: ${mime}`))
158
+ return { body: yield* collectBody(response), contentType }
159
+ }).pipe(
160
+ Effect.timeoutOrElse({
161
+ duration: Duration.seconds(input.timeout ?? DEFAULT_TIMEOUT_SECONDS),
162
+ orElse: () => Effect.fail(new Error("Request timed out")),
163
+ }),
164
+ )
165
+ const content = new TextDecoder().decode(body)
166
+ const output = yield* Effect.try({
167
+ try: () => convert(content, contentType, input.format),
168
+ catch: (error) => error,
169
+ })
170
+ return {
171
+ url: input.url,
172
+ contentType,
173
+ format: input.format,
174
+ output,
175
+ }
176
+ }).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to fetch ${input.url}` }))),
177
+ }),
178
+ })
179
+ .pipe(Effect.orDie)
180
+ }),
181
+ )
182
+
183
+ export const node = makeLocationNode({
184
+ name: "tool/webfetch",
185
+ layer,
186
+ deps: [ToolRegistry.node, PermissionV2.node, LayerNodePlatform.httpClient],
187
+ })
188
+
189
+ export function extractTextFromHTML(html: string) {
190
+ let text = ""
191
+ let skipDepth = 0
192
+ const parser = new Parser({
193
+ onopentag(name) {
194
+ if (skipDepth > 0 || ["script", "style", "noscript", "iframe", "object", "embed"].includes(name)) skipDepth++
195
+ },
196
+ ontext(input) {
197
+ if (skipDepth === 0) text += input
198
+ },
199
+ onclosetag() {
200
+ if (skipDepth > 0) skipDepth--
201
+ },
202
+ })
203
+ parser.write(html)
204
+ parser.end()
205
+ return text.trim()
206
+ }
207
+
208
+ export function convertHTMLToMarkdown(html: string) {
209
+ const turndown = new TurndownService({
210
+ headingStyle: "atx",
211
+ hr: "---",
212
+ bulletListMarker: "-",
213
+ codeBlockStyle: "fenced",
214
+ emDelimiter: "*",
215
+ })
216
+ turndown.remove(["script", "style", "meta", "link"])
217
+ return turndown.turndown(html)
218
+ }