@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,223 @@
1
+ /**
2
+ * Model-facing V2 exact-edit leaf. Relative paths resolve within the active
3
+ * Location. Absolute paths inside that Location are accepted, while explicit
4
+ * absolute external paths retain mutation capability through a separate
5
+ * external_directory approval before edit approval.
6
+ */
7
+ export * as EditTool from "./edit"
8
+
9
+ import { ToolFailure } from "@neurocode-ai/llm"
10
+ import { FileDiff } from "@neurocode-ai/schema/file-diff"
11
+ import { createTwoFilesPatch, diffLines } from "diff"
12
+ import { Effect, Layer, Schema } from "effect"
13
+ import { makeLocationNode } from "../effect/app-node"
14
+ import { FileMutation } from "../file-mutation"
15
+ import { FSUtil } from "../fs-util"
16
+ import { LocationMutation } from "../location-mutation"
17
+ import { PermissionV2 } from "../permission"
18
+ import { ToolRegistry } from "./registry"
19
+ import { Tool } from "./tool"
20
+ import { Tools } from "./tools"
21
+
22
+ export const name = "edit"
23
+
24
+ export const Input = Schema.Struct({
25
+ path: Schema.String.annotate({
26
+ description:
27
+ "File path to edit. Relative paths resolve within the active Location. Absolute paths inside that Location are accepted; external absolute paths require external_directory approval.",
28
+ }),
29
+ oldString: Schema.String.annotate({ description: "Exact text to replace" }),
30
+ newString: Schema.String.annotate({ description: "Replacement text, which must differ from oldString" }),
31
+ replaceAll: Schema.Boolean.pipe(Schema.optional).annotate({
32
+ description: "Replace all exact occurrences of oldString (default false)",
33
+ }),
34
+ })
35
+
36
+ export const Output = Schema.Struct({
37
+ files: Schema.Array(FileDiff.Info),
38
+ replacements: Schema.Number,
39
+ })
40
+ export type Output = typeof Output.Type
41
+
42
+ const normalizeLineEndings = (text: string) => text.replaceAll("\r\n", "\n")
43
+ const detectLineEnding = (text: string): "\n" | "\r\n" => (text.includes("\r\n") ? "\r\n" : "\n")
44
+ const convertToLineEnding = (text: string, ending: "\n" | "\r\n") =>
45
+ ending === "\n" ? normalizeLineEndings(text) : normalizeLineEndings(text).replaceAll("\n", "\r\n")
46
+
47
+ const splitBom = (text: string) =>
48
+ text.startsWith("\uFEFF") ? { bom: true, text: text.slice(1) } : { bom: false, text }
49
+ const joinBom = (text: string, bom: boolean) => (bom ? `\uFEFF${text}` : text)
50
+ const decodeUtf8 = (content: Uint8Array) => {
51
+ const bom = content[0] === 0xef && content[1] === 0xbb && content[2] === 0xbf
52
+ return { bom, content, text: new TextDecoder().decode(bom ? content.slice(3) : content) }
53
+ }
54
+
55
+ const countOccurrences = (content: string, search: string) => {
56
+ if (search === "") return content.length + 1
57
+ let count = 0
58
+ let offset = 0
59
+ while ((offset = content.indexOf(search, offset)) !== -1) {
60
+ count++
61
+ offset += search.length
62
+ }
63
+ return count
64
+ }
65
+
66
+ const previewLines = (value: string, prefix: "+" | "-") => {
67
+ const lines = normalizeLineEndings(value).split("\n")
68
+ const shown = lines.slice(0, 6).map((line) => `${prefix}${line.length > 240 ? `${line.slice(0, 240)}...` : line}`)
69
+ if (lines.length > shown.length) shown.push(`${prefix}...`)
70
+ return shown
71
+ }
72
+
73
+ export const toModelOutput = (output: Output, oldString: string, newString: string) =>
74
+ [
75
+ `Edited file successfully: ${output.files[0]?.file}`,
76
+ `Replacements: ${output.replacements}`,
77
+ "```diff",
78
+ ...previewLines(oldString, "-"),
79
+ ...previewLines(newString, "+"),
80
+ "```",
81
+ ].join("\n")
82
+
83
+ /** Deferred V2 edit behavior and UX integrations remain visible at the model-facing seam. */
84
+ // TODO: Port V1 fuzzy correction strategies only after exact-edit behavior is established: line-trimmed matching, block-anchor fallback, indentation correction, and similarity-threshold review.
85
+ // TODO: Add formatter integration after V2 formatter runtime exists.
86
+ // TODO: Publish watcher/file-edit events after V2 watcher integration exists.
87
+ // TODO: Add snapshots / undo after design exists.
88
+ // TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
89
+
90
+ const layer = Layer.effectDiscard(
91
+ Effect.gen(function* () {
92
+ const tools = yield* Tools.Service
93
+ const mutation = yield* LocationMutation.Service
94
+ const files = yield* FileMutation.Service
95
+ const fs = yield* FSUtil.Service
96
+ const permission = yield* PermissionV2.Service
97
+
98
+ yield* tools
99
+ .register({
100
+ [name]: Tool.withPermission(
101
+ Tool.make({
102
+ description:
103
+ "Replace exact text in one file. Relative paths resolve within the active Location. Absolute paths inside the Location are accepted. Explicit external absolute paths require external_directory approval before edit approval.",
104
+ input: Input,
105
+ output: Output,
106
+ toModelOutput: ({ input, output }) => [
107
+ { type: "text", text: toModelOutput(output, input.oldString, input.newString) },
108
+ ],
109
+ execute: (input, context) => {
110
+ const unableToEdit = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
111
+ effect.pipe(
112
+ Effect.mapError((error) =>
113
+ error instanceof FileMutation.StaleContentError
114
+ ? new ToolFailure({
115
+ message: "File changed after permission approval. Read it again before editing.",
116
+ })
117
+ : new ToolFailure({ message: `Unable to edit ${input.path}` }),
118
+ ),
119
+ )
120
+
121
+ return Effect.gen(function* () {
122
+ const permissionSource = {
123
+ type: "tool" as const,
124
+ messageID: context.assistantMessageID,
125
+ callID: context.toolCallID,
126
+ }
127
+ if (input.oldString === input.newString) {
128
+ return yield* new ToolFailure({
129
+ message: "No changes to apply: oldString and newString are identical.",
130
+ })
131
+ }
132
+ if (input.oldString === "") {
133
+ return yield* new ToolFailure({
134
+ message: "oldString must not be empty. Use write to create or overwrite a file.",
135
+ })
136
+ }
137
+
138
+ const target = yield* unableToEdit(mutation.resolve({ path: input.path, kind: "file" }))
139
+ const external = target.externalDirectory
140
+ if (external) {
141
+ yield* unableToEdit(
142
+ permission.assert({
143
+ ...LocationMutation.externalDirectoryPermission(external),
144
+ sessionID: context.sessionID,
145
+ agent: context.agent,
146
+ source: permissionSource,
147
+ }),
148
+ )
149
+ }
150
+
151
+ yield* unableToEdit(
152
+ permission.assert({
153
+ action: "edit",
154
+ resources: [target.resource],
155
+ save: ["*"],
156
+ sessionID: context.sessionID,
157
+ agent: context.agent,
158
+ source: permissionSource,
159
+ }),
160
+ )
161
+ const source = decodeUtf8(yield* unableToEdit(fs.readFile(target.canonical)))
162
+ const ending = detectLineEnding(source.text)
163
+ const oldString = convertToLineEnding(input.oldString, ending)
164
+ const newString = convertToLineEnding(input.newString, ending)
165
+ const replacements = countOccurrences(source.text, oldString)
166
+ if (replacements === 0) {
167
+ return yield* new ToolFailure({
168
+ message:
169
+ "Could not find oldString in the file. It must match exactly, including whitespace and indentation.",
170
+ })
171
+ }
172
+ if (replacements > 1 && input.replaceAll !== true) {
173
+ return yield* new ToolFailure({
174
+ message:
175
+ "Found multiple exact matches for oldString. Provide more surrounding context or set replaceAll to true.",
176
+ })
177
+ }
178
+
179
+ const replaced =
180
+ input.replaceAll === true
181
+ ? source.text.replaceAll(oldString, newString)
182
+ : source.text.replace(oldString, newString)
183
+ const counts = diffLines(source.text, replaced).reduce(
184
+ (result, item) => ({
185
+ additions: result.additions + (item.added ? (item.count ?? 0) : 0),
186
+ deletions: result.deletions + (item.removed ? (item.count ?? 0) : 0),
187
+ }),
188
+ { additions: 0, deletions: 0 },
189
+ )
190
+ const next = splitBom(replaced)
191
+ const result = yield* unableToEdit(
192
+ files.writeIfUnchanged({
193
+ target,
194
+ expected: source.content,
195
+ content: joinBom(next.text, source.bom || next.bom),
196
+ }),
197
+ )
198
+ return {
199
+ files: [
200
+ {
201
+ file: result.resource,
202
+ patch: createTwoFilesPatch(result.resource, result.resource, source.text, replaced),
203
+ status: "modified" as const,
204
+ ...counts,
205
+ },
206
+ ],
207
+ replacements,
208
+ } satisfies Output
209
+ })
210
+ },
211
+ }),
212
+ "edit",
213
+ ),
214
+ })
215
+ .pipe(Effect.orDie)
216
+ }),
217
+ )
218
+
219
+ export const node = makeLocationNode({
220
+ name: "tool/edit",
221
+ layer,
222
+ deps: [ToolRegistry.node, LocationMutation.node, FileMutation.node, FSUtil.node, PermissionV2.node],
223
+ })
@@ -0,0 +1,105 @@
1
+ export * as GlobTool from "./glob"
2
+
3
+ import { ToolFailure } from "@neurocode-ai/llm"
4
+ import { Effect, Layer, Schema } from "effect"
5
+ import path from "path"
6
+ import { makeLocationNode } from "../effect/app-node"
7
+ import { FileSystem } from "../filesystem"
8
+ import { Location } from "../location"
9
+ import { Ripgrep } from "../ripgrep"
10
+ import { RelativePath } from "../schema"
11
+ import { PermissionV2 } from "../permission"
12
+ import { ToolRegistry } from "./registry"
13
+ import { Tool } from "./tool"
14
+ import { Tools } from "./tools"
15
+
16
+ export const name = "glob"
17
+
18
+ export const Input = Schema.Struct({
19
+ pattern: FileSystem.GlobInput.fields.pattern.annotate({ description: "Glob pattern to match files against" }),
20
+ path: RelativePath.pipe(Schema.optional).annotate({
21
+ description: "Relative directory to search. Defaults to the active Location.",
22
+ }),
23
+ limit: FileSystem.GlobInput.fields.limit.annotate({
24
+ description: "Maximum results to return",
25
+ }),
26
+ })
27
+
28
+ export const Output = Schema.Array(FileSystem.Entry)
29
+ type ModelOutput = typeof Output.Encoded
30
+
31
+ /** Format raw search results into the concise line-oriented output models expect. */
32
+ export const toModelOutput = (output: ModelOutput) => {
33
+ const lines = output.length === 0 ? ["No files found"] : output.map((item) => item.path)
34
+ return lines.join("\n")
35
+ }
36
+
37
+ /** Glob leaf that defaults its filesystem root to the active Location. */
38
+ const layer = Layer.effectDiscard(
39
+ Effect.gen(function* () {
40
+ const tools = yield* Tools.Service
41
+ const ripgrep = yield* Ripgrep.Service
42
+ const location = yield* Location.Service
43
+ const permission = yield* PermissionV2.Service
44
+
45
+ yield* tools
46
+ .register({
47
+ [name]: Tool.make({
48
+ description:
49
+ "Find files by glob pattern within the active Location. Returns concise relative file resources. Use a relative path to narrow the search and limit to bound the result count.",
50
+ input: Input,
51
+ output: Output,
52
+ toModelOutput: ({ output }) => [
53
+ {
54
+ type: "text",
55
+ text: toModelOutput(
56
+ output.map((entry) => ({ ...entry, path: path.resolve(location.directory, entry.path) })),
57
+ ),
58
+ },
59
+ ],
60
+ execute: (input, context) =>
61
+ Effect.gen(function* () {
62
+ yield* permission.assert({
63
+ action: name,
64
+ resources: [input.pattern],
65
+ save: ["*"],
66
+ metadata: {
67
+ root: input.path ?? ".",
68
+ path: input.path,
69
+ limit: input.limit,
70
+ },
71
+ sessionID: context.sessionID,
72
+ agent: context.agent,
73
+ source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
74
+ })
75
+ const cwd = path.resolve(location.directory, input.path ?? ".")
76
+ return yield* ripgrep
77
+ .glob({
78
+ cwd,
79
+ pattern: input.pattern,
80
+ limit: input.limit ?? Number.MAX_SAFE_INTEGER,
81
+ })
82
+ .pipe(
83
+ Effect.map((result) =>
84
+ result.map((entry) =>
85
+ FileSystem.Entry.make({
86
+ ...entry,
87
+ path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
88
+ }),
89
+ ),
90
+ ),
91
+ )
92
+ }).pipe(
93
+ Effect.mapError(() => new ToolFailure({ message: `Unable to find files matching ${input.pattern}` })),
94
+ ),
95
+ }),
96
+ })
97
+ .pipe(Effect.orDie)
98
+ }),
99
+ )
100
+
101
+ export const node = makeLocationNode({
102
+ name: "tool/glob",
103
+ layer,
104
+ deps: [ToolRegistry.node, Ripgrep.node, Location.node, PermissionV2.node],
105
+ })
@@ -0,0 +1,137 @@
1
+ export * as GrepTool from "./grep"
2
+
3
+ import { ToolFailure } from "@neurocode-ai/llm"
4
+ import { Effect, Layer, Schema } from "effect"
5
+ import path from "path"
6
+ import { makeLocationNode } from "../effect/app-node"
7
+ import { FileSystem } from "../filesystem"
8
+ import { FSUtil } from "../fs-util"
9
+ import { Location } from "../location"
10
+ import { PermissionV2 } from "../permission"
11
+ import { Ripgrep } from "../ripgrep"
12
+ import { RelativePath } from "../schema"
13
+ import { ToolRegistry } from "./registry"
14
+ import { Tool } from "./tool"
15
+ import { Tools } from "./tools"
16
+
17
+ export const name = "grep"
18
+
19
+ export const Input = Schema.Struct({
20
+ pattern: FileSystem.GrepInput.fields.pattern.annotate({
21
+ description: "Regex pattern to search for in file contents",
22
+ }),
23
+ path: RelativePath.pipe(Schema.optional).annotate({
24
+ description: "Relative directory to search. Defaults to the active Location.",
25
+ }),
26
+ include: FileSystem.GrepInput.fields.include.annotate({
27
+ description: 'File glob to include in the search (for example, "*.js" or "*.{ts,tsx}")',
28
+ }),
29
+ limit: FileSystem.GrepInput.fields.limit.annotate({
30
+ description: "Maximum matches to return",
31
+ }),
32
+ })
33
+
34
+ export const Output = Schema.Array(FileSystem.Match)
35
+ type ModelOutput = typeof Output.Encoded
36
+
37
+ /** Format raw search matches into the familiar concise model output. */
38
+ export const toModelOutput = (output: ModelOutput) => {
39
+ const lines = output.length === 0 ? ["No files found"] : [`Found ${output.length} matches`]
40
+ let current = ""
41
+ for (const match of output) {
42
+ if (current !== match.entry.path) {
43
+ if (current) lines.push("")
44
+ current = match.entry.path
45
+ lines.push(`${match.entry.path}:`)
46
+ }
47
+ lines.push(` Line ${match.line}: ${match.text}`)
48
+ }
49
+ return lines.join("\n")
50
+ }
51
+
52
+ /** Grep leaf that defaults its filesystem root to the active Location. */
53
+ const layer = Layer.effectDiscard(
54
+ Effect.gen(function* () {
55
+ const tools = yield* Tools.Service
56
+ const fs = yield* FSUtil.Service
57
+ const ripgrep = yield* Ripgrep.Service
58
+ const location = yield* Location.Service
59
+ const permission = yield* PermissionV2.Service
60
+
61
+ yield* tools
62
+ .register({
63
+ [name]: Tool.make({
64
+ description:
65
+ "Search file contents by regular expression within the active Location or an absolute managed tool-output file. Use a path to narrow the search, include to filter files by glob, and limit to bound the match count. Returns concise file resources, line numbers, and bounded line previews.",
66
+ input: Input,
67
+ output: Output,
68
+ toModelOutput: ({ output }) => [
69
+ {
70
+ type: "text",
71
+ text: toModelOutput(
72
+ output.map((match) => ({
73
+ ...match,
74
+ entry: { ...match.entry, path: path.resolve(location.directory, match.entry.path) },
75
+ })),
76
+ ),
77
+ },
78
+ ],
79
+ execute: (input, context) =>
80
+ Effect.gen(function* () {
81
+ yield* permission.assert({
82
+ action: name,
83
+ resources: [input.pattern],
84
+ save: ["*"],
85
+ metadata: {
86
+ root: ".",
87
+ path: input.path,
88
+ include: input.include,
89
+ limit: input.limit,
90
+ },
91
+ sessionID: context.sessionID,
92
+ agent: context.agent,
93
+ source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
94
+ })
95
+ const target = path.resolve(location.directory, input.path ?? ".")
96
+ const info = yield* fs.stat(target).pipe(Effect.catch(() => Effect.succeed(undefined)))
97
+ return yield* ripgrep
98
+ .grep({
99
+ cwd: info?.type === "Directory" ? target : path.dirname(target),
100
+ pattern: input.pattern,
101
+ file: info?.type === "File" ? path.basename(target) : undefined,
102
+ include: input.include,
103
+ limit: input.limit ?? Number.MAX_SAFE_INTEGER,
104
+ })
105
+ .pipe(
106
+ Effect.map((result) =>
107
+ result.map((match) =>
108
+ FileSystem.Match.make({
109
+ ...match,
110
+ entry: FileSystem.Entry.make({
111
+ ...match.entry,
112
+ path: RelativePath.make(
113
+ path.relative(
114
+ location.directory,
115
+ path.resolve(
116
+ info?.type === "Directory" ? target : path.dirname(target),
117
+ match.entry.path,
118
+ ),
119
+ ),
120
+ ),
121
+ }),
122
+ }),
123
+ ),
124
+ ),
125
+ )
126
+ }).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to grep for ${input.pattern}` }))),
127
+ }),
128
+ })
129
+ .pipe(Effect.orDie)
130
+ }),
131
+ )
132
+
133
+ export const node = makeLocationNode({
134
+ name: "tool/grep",
135
+ layer,
136
+ deps: [ToolRegistry.node, FSUtil.node, Ripgrep.node, Location.node, PermissionV2.node],
137
+ })
@@ -0,0 +1,30 @@
1
+ import { Effect, Stream } from "effect"
2
+ import { HttpClientResponse } from "effect/unstable/http"
3
+
4
+ export const collectBoundedResponseBody = (
5
+ response: HttpClientResponse.HttpClientResponse,
6
+ maximumBytes: number,
7
+ tooLarge: () => Error,
8
+ ) =>
9
+ Effect.gen(function* () {
10
+ const contentLength = response.headers["content-length"]
11
+ const parsedSize = contentLength ? Number.parseInt(contentLength, 10) : undefined
12
+ const declaredSize =
13
+ parsedSize !== undefined && Number.isSafeInteger(parsedSize) && parsedSize >= 0 ? parsedSize : undefined
14
+ if (declaredSize !== undefined && declaredSize > maximumBytes) return yield* Effect.fail(tooLarge())
15
+ let body = Buffer.allocUnsafe(Math.min(maximumBytes, declaredSize || 64 * 1024))
16
+ let size = 0
17
+ yield* Stream.runForEach(response.stream, (chunk) => {
18
+ if (chunk.byteLength === 0) return Effect.void
19
+ if (size + chunk.byteLength > maximumBytes) return Effect.fail(tooLarge())
20
+ if (size + chunk.byteLength > body.byteLength) {
21
+ const grown = Buffer.allocUnsafe(Math.min(maximumBytes, Math.max(size + chunk.byteLength, body.byteLength * 2)))
22
+ body.copy(grown, 0, 0, size)
23
+ body = grown
24
+ }
25
+ body.set(chunk, size)
26
+ size += chunk.byteLength
27
+ return Effect.void
28
+ })
29
+ return body.subarray(0, size)
30
+ })
@@ -0,0 +1,94 @@
1
+ export * as QuestionTool from "./question"
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 { QuestionV2 } from "../question"
8
+ import { ToolRegistry } from "./registry"
9
+ import { Tool } from "./tool"
10
+ import { Tools } from "./tools"
11
+
12
+ export const name = "question"
13
+
14
+ export const description = `Use this tool when you need to ask the user questions during execution. This allows you to:
15
+ 1. Gather user preferences or requirements
16
+ 2. Clarify ambiguous instructions
17
+ 3. Get decisions on implementation choices as you work
18
+ 4. Offer choices to the user about what direction to take.
19
+
20
+ Usage notes:
21
+ - When \`custom\` is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options
22
+ - Answers are returned as arrays of labels; set \`multiple: true\` to allow selecting more than one
23
+ - If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label`
24
+
25
+ export const Input = Schema.Struct({
26
+ questions: Schema.Array(QuestionV2.Prompt).annotate({ description: "Questions to ask" }),
27
+ })
28
+
29
+ export const Output = Schema.Struct({
30
+ answers: Schema.Array(QuestionV2.Answer),
31
+ })
32
+ export type Output = typeof Output.Type
33
+
34
+ export const toModelOutput = (
35
+ questions: ReadonlyArray<QuestionV2.Prompt>,
36
+ answers: ReadonlyArray<QuestionV2.Answer>,
37
+ ) => {
38
+ const formatted = questions
39
+ .map(
40
+ (question, index) =>
41
+ `"${question.question}"="${answers[index]?.length ? answers[index].join(", ") : "Unanswered"}"`,
42
+ )
43
+ .join(", ")
44
+ return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
45
+ }
46
+
47
+ const layer = Layer.effectDiscard(
48
+ Effect.gen(function* () {
49
+ const tools = yield* Tools.Service
50
+ const question = yield* QuestionV2.Service
51
+ const permission = yield* PermissionV2.Service
52
+
53
+ yield* tools
54
+ .register({
55
+ [name]: Tool.make({
56
+ description,
57
+ input: Input,
58
+ output: Output,
59
+ toModelOutput: ({ input, output }) => [
60
+ { type: "text", text: toModelOutput(input.questions, output.answers) },
61
+ ],
62
+ execute: (input, context) =>
63
+ permission
64
+ .assert({
65
+ action: "question",
66
+ resources: ["*"],
67
+ sessionID: context.sessionID,
68
+ agent: context.agent,
69
+ source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
70
+ })
71
+ .pipe(
72
+ Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })),
73
+ Effect.andThen(
74
+ question
75
+ .ask({
76
+ sessionID: context.sessionID,
77
+ questions: input.questions,
78
+ tool: { messageID: context.assistantMessageID, callID: context.toolCallID },
79
+ })
80
+ .pipe(Effect.orDie),
81
+ ),
82
+ Effect.map((answers) => ({ answers })),
83
+ ),
84
+ }),
85
+ })
86
+ .pipe(Effect.orDie)
87
+ }),
88
+ )
89
+
90
+ export const node = makeLocationNode({
91
+ name: "tool/question",
92
+ layer,
93
+ deps: [ToolRegistry.node, PermissionV2.node, QuestionV2.node],
94
+ })