@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
package/src/ripgrep.ts ADDED
@@ -0,0 +1,281 @@
1
+ export * as Ripgrep from "./ripgrep"
2
+
3
+ import { Context, Effect, Fiber, Layer, Schema, Stream } from "effect"
4
+ import { ChildProcess } from "effect/unstable/process"
5
+ import { Entry, Match } from "@neurocode-ai/schema/filesystem"
6
+ import { makeGlobalNode } from "./effect/app-node"
7
+ import { AppProcess, collectStream, waitForAbort } from "./process"
8
+ import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
9
+ import { RipgrepBinary } from "./ripgrep/binary"
10
+
11
+ /**
12
+ * Small core-owned ripgrep execution adapter. It deliberately exposes raw
13
+ * process-oriented rows, not model text or permission behavior. Search maps
14
+ * these rows into filesystem results; leaf tools own
15
+ * presentation and permission prompts.
16
+ */
17
+
18
+ const ERROR_BYTES = 8 * 1024
19
+ const MAX_RECORD_BYTES = 64 * 1024
20
+ const MAX_SUBMATCHES = 100
21
+
22
+ const RawMatch = Schema.Struct({
23
+ type: Schema.Literal("match"),
24
+ data: Schema.Struct({
25
+ path: Schema.Struct({ text: Schema.String }),
26
+ lines: Schema.Struct({ text: Schema.String }),
27
+ line_number: PositiveInt,
28
+ absolute_offset: NonNegativeInt,
29
+ submatches: Schema.Array(
30
+ Schema.Struct({
31
+ match: Schema.Struct({ text: Schema.String }),
32
+ start: NonNegativeInt,
33
+ end: NonNegativeInt,
34
+ }),
35
+ ),
36
+ }),
37
+ })
38
+
39
+ type RawMatchData = (typeof RawMatch.Type)["data"]
40
+
41
+ export class Error extends Schema.TaggedErrorClass<Error>()("Ripgrep.Error", {
42
+ message: Schema.String,
43
+ cause: Schema.optional(Schema.Defect()),
44
+ }) {}
45
+
46
+ export class InvalidPatternError extends Schema.TaggedErrorClass<InvalidPatternError>()("Ripgrep.InvalidPatternError", {
47
+ pattern: Schema.String,
48
+ message: Schema.String,
49
+ }) {}
50
+
51
+ export interface FindInput {
52
+ readonly cwd: string
53
+ readonly pattern: string
54
+ readonly limit: number
55
+ readonly hidden?: boolean
56
+ readonly follow?: boolean
57
+ readonly signal?: AbortSignal
58
+ readonly onEntry?: (entry: Entry) => Effect.Effect<void>
59
+ }
60
+
61
+ export interface GlobInput {
62
+ readonly cwd: string
63
+ readonly pattern: string
64
+ readonly limit: number
65
+ readonly hidden?: boolean
66
+ readonly follow?: boolean
67
+ readonly signal?: AbortSignal
68
+ }
69
+
70
+ export interface GrepInput {
71
+ readonly cwd: string
72
+ readonly pattern: string
73
+ readonly file?: string
74
+ readonly include?: string
75
+ readonly limit: number
76
+ readonly signal?: AbortSignal
77
+ }
78
+
79
+ export interface Interface {
80
+ readonly find: (input: FindInput) => Effect.Effect<readonly Entry[], Error>
81
+ readonly glob: (input: GlobInput) => Effect.Effect<readonly Entry[], Error>
82
+ readonly grep: (input: GrepInput) => Effect.Effect<readonly Match[], Error | InvalidPatternError>
83
+ }
84
+
85
+ export class Service extends Context.Service<Service, Interface>()("@neurocode/v2/Ripgrep") {}
86
+
87
+ const failure = (message: string, cause?: unknown) => new Error({ message, cause })
88
+
89
+ const isInvalidPattern = (stderr: string) =>
90
+ stderr.includes("regex parse error") || stderr.includes("error parsing regex")
91
+
92
+ const layer = Layer.effect(
93
+ Service,
94
+ Effect.gen(function* () {
95
+ const process = yield* AppProcess.Service
96
+ const binary = yield* RipgrepBinary.Service
97
+
98
+ const run = <A>(input: {
99
+ readonly cwd: string
100
+ readonly args: string[]
101
+ readonly limit: number
102
+ readonly signal?: AbortSignal
103
+ readonly parse: (line: string) => Effect.Effect<A | undefined, Error>
104
+ readonly pattern?: string
105
+ readonly onItem?: (item: A) => Effect.Effect<void>
106
+ }) => {
107
+ const program = Effect.scoped(
108
+ Effect.gen(function* () {
109
+ const handle = yield* process.spawn(
110
+ ChildProcess.make(yield* binary.filepath, input.args, { cwd: input.cwd, extendEnv: true, stdin: "ignore" }),
111
+ )
112
+ const stderrFiber = yield* collectStream(handle.stderr, ERROR_BYTES).pipe(
113
+ Effect.map((output) => output.buffer.toString("utf8")),
114
+ Effect.forkScoped,
115
+ )
116
+ let observed = 0
117
+ const rows = yield* Stream.decodeText(handle.stdout).pipe(
118
+ Stream.splitLines,
119
+ Stream.filter((line) => line.length > 0),
120
+ Stream.mapEffect(input.parse),
121
+ Stream.filter((row): row is A => row !== undefined),
122
+ Stream.tap((row) => {
123
+ if (!input.onItem || observed++ >= input.limit) return Effect.void
124
+ return input.onItem(row)
125
+ }),
126
+ Stream.take(input.limit + 1),
127
+ Stream.runCollect,
128
+ Effect.map((chunk) => [...chunk]),
129
+ )
130
+ const truncated = rows.length > input.limit
131
+ if (truncated) return { items: rows.slice(0, input.limit), truncated, partial: false }
132
+
133
+ const code = yield* handle.exitCode
134
+ const stderr = yield* Fiber.join(stderrFiber)
135
+ if (input.pattern && code === 2 && isInvalidPattern(stderr)) {
136
+ return yield* new InvalidPatternError({ pattern: input.pattern, message: stderr.trim() })
137
+ }
138
+ if (code !== 0 && code !== 1 && code !== 2) {
139
+ return yield* failure(stderr.trim() || `ripgrep failed with code ${code}`)
140
+ }
141
+ return { items: code === 1 ? [] : rows, truncated: false, partial: code === 2 }
142
+ }),
143
+ )
144
+ const abortable = input.signal ? program.pipe(Effect.raceFirst(waitForAbort(input.signal))) : program
145
+ return abortable.pipe(
146
+ Effect.mapError((cause) =>
147
+ cause instanceof Error || cause instanceof InvalidPatternError
148
+ ? cause
149
+ : failure("ripgrep execution failed", cause),
150
+ ),
151
+ )
152
+ }
153
+
154
+ return Service.of({
155
+ glob: (input) =>
156
+ run<string>({
157
+ cwd: input.cwd,
158
+ limit: input.limit,
159
+ signal: input.signal,
160
+ args: [
161
+ "--no-config",
162
+ "--files",
163
+ ...(input.hidden ? ["--hidden"] : []),
164
+ ...(input.follow ? ["--follow"] : []),
165
+ `--glob=${input.pattern}`,
166
+ "--glob=!**/.git/**",
167
+ ".",
168
+ ],
169
+ parse: (line) =>
170
+ Effect.succeed(
171
+ line
172
+ .replace(/^(?:\.[\\/])+/u, "")
173
+ .replace(/^[\\/]+/u, "")
174
+ .replaceAll("\\", "/"),
175
+ ),
176
+ }).pipe(
177
+ Effect.map((result) =>
178
+ result.items.map((relative) =>
179
+ Entry.make({
180
+ path: RelativePath.make(relative),
181
+ type: "file",
182
+ }),
183
+ ),
184
+ ),
185
+ Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
186
+ ),
187
+ find: (input) =>
188
+ run<Entry>({
189
+ cwd: input.cwd,
190
+ limit: input.limit,
191
+ signal: input.signal,
192
+ args: [
193
+ "--no-config",
194
+ "--files",
195
+ ...(input.hidden ? ["--hidden"] : []),
196
+ ...(input.follow ? ["--follow"] : []),
197
+ ...(input.pattern === "*" ? [] : [`--glob=${input.pattern}`]),
198
+ "--glob=!**/.git/**",
199
+ ".",
200
+ ],
201
+ parse: (line) => {
202
+ const relative = line
203
+ .replace(/^(?:\.[\\/])+/u, "")
204
+ .replace(/^[\\/]+/u, "")
205
+ .replaceAll("\\", "/")
206
+ return Effect.succeed(
207
+ Entry.make({
208
+ path: RelativePath.make(relative),
209
+ type: "file",
210
+ }),
211
+ )
212
+ },
213
+ onItem: input.onEntry,
214
+ }).pipe(
215
+ Effect.map((result) => result.items),
216
+ Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
217
+ ),
218
+ grep: (input) =>
219
+ run<RawMatchData>({
220
+ ...input,
221
+ args: [
222
+ "--no-config",
223
+ "--json",
224
+ "--hidden",
225
+ "--no-messages",
226
+ ...(input.include ? [`--glob=${input.include}`] : []),
227
+ "--glob=!**/.git/**",
228
+ "--",
229
+ input.pattern,
230
+ input.file ?? ".",
231
+ ],
232
+ parse: (line) =>
233
+ (Buffer.byteLength(line, "utf8") > MAX_RECORD_BYTES
234
+ ? Effect.fail(failure(`Ripgrep JSON record exceeded ${MAX_RECORD_BYTES} bytes`))
235
+ : Effect.try({
236
+ try: () => JSON.parse(line) as unknown,
237
+ catch: (cause) => failure("Invalid ripgrep JSON output", cause),
238
+ })
239
+ ).pipe(
240
+ Effect.flatMap((json) => {
241
+ if (!json || typeof json !== "object" || !("type" in json) || json.type !== "match")
242
+ return Effect.succeed(undefined)
243
+ return Schema.decodeUnknownEffect(RawMatch)(json).pipe(
244
+ Effect.map((match) => ({
245
+ ...match.data,
246
+ path: { text: match.data.path.text.replace(/^\.[\\/]/, "") },
247
+ submatches: match.data.submatches.slice(0, MAX_SUBMATCHES),
248
+ })),
249
+ Effect.mapError((cause) => failure("Invalid ripgrep match output", cause)),
250
+ )
251
+ }),
252
+ ),
253
+ }).pipe(
254
+ Effect.map((result) =>
255
+ result.items.map((match) => {
256
+ const relative = match.path.text
257
+ .replace(/^(?:\.[\\/])+/u, "")
258
+ .replace(/^[\\/]+/u, "")
259
+ .replaceAll("\\", "/")
260
+ return Match.make({
261
+ entry: Entry.make({
262
+ path: RelativePath.make(relative),
263
+ type: "file",
264
+ }),
265
+ line: match.line_number,
266
+ offset: match.absolute_offset,
267
+ text: match.lines.text.length > 2_000 ? match.lines.text.slice(0, 2_000) + "..." : match.lines.text,
268
+ submatches: match.submatches.map((submatch) => ({
269
+ text: submatch.match.text,
270
+ start: submatch.start,
271
+ end: submatch.end,
272
+ })),
273
+ })
274
+ }),
275
+ ),
276
+ ),
277
+ })
278
+ }),
279
+ )
280
+
281
+ export const node = makeGlobalNode({ service: Service, layer: layer, deps: [RipgrepBinary.node, AppProcess.node] })
package/src/schema.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { Schema } from "effect"
2
+ import {
3
+ AbsolutePath,
4
+ DateTimeUtcFromMillis,
5
+ NonNegativeInt,
6
+ optional,
7
+ PositiveInt,
8
+ RelativePath,
9
+ statics,
10
+ } from "@neurocode-ai/schema/schema"
11
+
12
+ export { AbsolutePath, DateTimeUtcFromMillis, NonNegativeInt, optional, PositiveInt, RelativePath, statics }
13
+
14
+ /**
15
+ * Strip `readonly` from a nested type. Stand-in for `effect`'s `Types.DeepMutable`
16
+ * until `effect:core/x228my` ("Types.DeepMutable widens unknown to `{}`") lands.
17
+ *
18
+ * The upstream version falls through `unknown` into `{ -readonly [K in keyof T]: ... }`
19
+ * where `keyof unknown = never`, so `unknown` collapses to `{}`. This local
20
+ * version gates the object branch on `extends object` (which `unknown` does
21
+ * not) so `unknown` passes through untouched.
22
+ *
23
+ * Primitive bailout matches upstream — without it, branded strings like
24
+ * `string & Brand<"SessionID">` fall into the object branch and get their
25
+ * prototype methods walked.
26
+ *
27
+ * Tuple branch preserves readonly tuples (e.g. `ConfigPlugin.Spec`'s
28
+ * `readonly [string, Options]`); the general array branch would otherwise
29
+ * widen them to unbounded arrays.
30
+ */
31
+ // eslint-disable-next-line @typescript-eslint/ban-types
32
+ export type DeepMutable<T> = T extends string | number | boolean | bigint | symbol | Function
33
+ ? T
34
+ : T extends readonly [unknown, ...unknown[]]
35
+ ? { -readonly [K in keyof T]: DeepMutable<T[K]> }
36
+ : T extends readonly (infer U)[]
37
+ ? DeepMutable<U>[]
38
+ : T extends object
39
+ ? { -readonly [K in keyof T]: DeepMutable<T[K]> }
40
+ : T
41
+
42
+ /**
43
+ * Nominal wrapper for scalar types. The class itself is a valid schema —
44
+ * pass it directly to `Schema.decode`, `Schema.decodeEffect`, etc.
45
+ *
46
+ * Overrides `~type.make` on the derived `Schema.Opaque` so `Schema.Schema.Type`
47
+ * of a field using this newtype resolves to `Self` rather than the underlying
48
+ * branded phantom. Without that override, passing a class instance to code
49
+ * typed against `Schema.Schema.Type<FieldSchema>` would require a cast even
50
+ * though the values are structurally equivalent at runtime.
51
+ *
52
+ * @example
53
+ * class QuestionID extends Newtype<QuestionID>()("QuestionID", Schema.String) {
54
+ * static make(id: string): QuestionID {
55
+ * return this.make(id)
56
+ * }
57
+ * }
58
+ *
59
+ * Schema.decodeEffect(QuestionID)(input)
60
+ */
61
+ export function Newtype<Self>() {
62
+ return <const Tag extends string, S extends Schema.Top>(tag: Tag, schema: S) => {
63
+ abstract class Base {
64
+ declare readonly _newtype: Tag
65
+
66
+ static make(value: Schema.Schema.Type<S>): Self {
67
+ return value as unknown as Self
68
+ }
69
+ }
70
+
71
+ Object.setPrototypeOf(Base, schema)
72
+
73
+ return Base as unknown as (abstract new (_: never) => { readonly _newtype: Tag }) & {
74
+ readonly make: (value: Schema.Schema.Type<S>) => Self
75
+ } & Omit<Schema.Opaque<Self, S, {}>, "make" | "~type.make"> & {
76
+ readonly "~type.make": Self
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,241 @@
1
+ export * as SessionCompaction from "./compaction"
2
+
3
+ import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@neurocode-ai/llm"
4
+ import { DateTime, Effect, Stream } from "effect"
5
+ import type { Config } from "../config"
6
+ import type { EventV2 } from "../event"
7
+ import { SessionEvent } from "./event"
8
+ import { SessionMessage } from "./message"
9
+ import { SessionSchema } from "./schema"
10
+ import { Token } from "../util/token"
11
+
12
+ const DEFAULT_BUFFER = 20_000
13
+ const DEFAULT_KEEP_TOKENS = 8_000
14
+ const TOOL_OUTPUT_MAX_CHARS = 2_000
15
+ const SUMMARY_OUTPUT_TOKENS = 4_096
16
+ const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
17
+ <template>
18
+ ## Objective
19
+ - [one or two brief sentences describing what the user is trying to accomplish]
20
+
21
+ ## Important Details
22
+ - [constraints/preferences, decisions and why, important facts/assumptions, exact context needed to continue, or "(none)"]
23
+
24
+ ## Work State
25
+ ### Completed
26
+ - [finished work, verified facts, or changes made; otherwise "(none)"]
27
+
28
+ ### Active
29
+ - [current work, partial changes, or investigation state; otherwise "(none)"]
30
+
31
+ ### Blocked
32
+ - [blockers, failing commands, or unknowns; otherwise "(none)"]
33
+
34
+ ## Next Move
35
+ 1. [immediate concrete action, or "(none)"]
36
+ 2. [next action if known, or "(none)"]
37
+
38
+ ## Relevant Files
39
+ - [file or directory path: why it matters, or "(none)"]
40
+ </template>
41
+
42
+ Rules:
43
+ - Keep every section, even when empty.
44
+ - Use terse bullets, not prose paragraphs.
45
+ - Preserve exact file paths, symbols, commands, error strings, URLs, and identifiers when known.
46
+ - Do not mention the summary process or that context was compacted.`
47
+
48
+ type Entry = {
49
+ readonly seq: number
50
+ readonly message: SessionMessage.Message
51
+ }
52
+
53
+ type Settings = {
54
+ readonly auto: boolean
55
+ readonly buffer: number
56
+ readonly tokens: number
57
+ }
58
+
59
+ type Dependencies = {
60
+ readonly events: EventV2.Interface
61
+ readonly llm: {
62
+ readonly stream: (request: LLMRequest) => Stream.Stream<LLMEvent, LLMError>
63
+ }
64
+ readonly config: readonly Config.Entry[]
65
+ }
66
+
67
+ type Input = {
68
+ readonly sessionID: SessionSchema.ID
69
+ readonly entries: readonly Entry[]
70
+ readonly model: Model
71
+ readonly request: LLMRequest
72
+ }
73
+
74
+ const estimate = (value: unknown) => Token.estimate(JSON.stringify(value))
75
+
76
+ const truncate = (value: string) =>
77
+ value.length <= TOOL_OUTPUT_MAX_CHARS ? value : `${value.slice(0, TOOL_OUTPUT_MAX_CHARS)}\n[truncated]`
78
+
79
+ export const serializeToolContent = (content: SessionMessage.ToolStateCompleted["content"]) =>
80
+ content
81
+ .map((item) =>
82
+ item.type === "text" ? item.text : `[Attached ${item.mime}${item.name === undefined ? "" : `: ${item.name}`}]`,
83
+ )
84
+ .join("\n")
85
+
86
+ const serialize = (message: SessionMessage.Message) => {
87
+ if (message.type === "user") {
88
+ const files = message.files?.map((file) => `[Attached ${file.mime}: ${file.name ?? file.uri}]`) ?? []
89
+ return [`[User]: ${message.text}`, ...files].join("\n")
90
+ }
91
+ if (message.type === "assistant") {
92
+ return message.content
93
+ .flatMap((part) => {
94
+ if (part.type === "text") return [`[Assistant]: ${part.text}`]
95
+ if (part.type === "reasoning") return part.text ? [`[Assistant reasoning]: ${part.text}`] : []
96
+ const input = typeof part.state.input === "string" ? part.state.input : JSON.stringify(part.state.input)
97
+ if (part.state.status === "completed")
98
+ return [
99
+ `[Assistant tool call]: ${part.name}(${input})`,
100
+ `[Tool result]: ${truncate(serializeToolContent(part.state.content))}`,
101
+ ]
102
+ if (part.state.status === "error")
103
+ return [`[Assistant tool call]: ${part.name}(${input})`, `[Tool error]: ${part.state.error.message}`]
104
+ return [`[Assistant tool call]: ${part.name}(${input})`]
105
+ })
106
+ .join("\n")
107
+ }
108
+ if (message.type === "system") return `[System update]: ${message.text}`
109
+ if (message.type === "synthetic") return `[Synthetic context]: ${message.text}`
110
+ if (message.type === "shell") return `[Shell]: ${message.command}\n${truncate(message.output)}`
111
+ return ""
112
+ }
113
+
114
+ const settings = (documents: readonly Config.Entry[]) => {
115
+ const configured = documents
116
+ .filter((entry): entry is Config.Document => entry.type === "document")
117
+ .flatMap((entry) => (entry.info.compaction ? [entry.info.compaction] : []))
118
+ return configured.reduce<Settings>(
119
+ (result, current) => ({
120
+ auto: current.auto ?? result.auto,
121
+ buffer: current.buffer ?? result.buffer,
122
+ tokens: current.keep?.tokens ?? result.tokens,
123
+ }),
124
+ { auto: true, buffer: DEFAULT_BUFFER, tokens: DEFAULT_KEEP_TOKENS },
125
+ )
126
+ }
127
+
128
+ const select = (
129
+ entries: readonly Entry[],
130
+ tokens: number,
131
+ ): { readonly head: string; readonly recent: string } | undefined => {
132
+ const conversation = entries
133
+ .filter((entry) => entry.message.type !== "compaction")
134
+ .map((entry) => serialize(entry.message))
135
+ .filter(Boolean)
136
+ if (conversation.length === 0) return
137
+ let total = 0
138
+ let split = conversation.length
139
+ let splitPrefix = ""
140
+ let splitSuffix = ""
141
+ for (let index = conversation.length - 1; index >= 0; index--) {
142
+ const next = total + Token.estimate(conversation[index])
143
+ if (next > tokens) {
144
+ const remaining = Math.max(0, tokens - total) * 4
145
+ if (remaining > 0) {
146
+ splitPrefix = conversation[index].slice(0, -remaining)
147
+ splitSuffix = conversation[index].slice(-remaining)
148
+ split = index + 1
149
+ }
150
+ break
151
+ }
152
+ total = next
153
+ split = index
154
+ }
155
+ return {
156
+ head: [...conversation.slice(0, split), splitPrefix].filter(Boolean).join("\n\n"),
157
+ recent: [splitSuffix, ...conversation.slice(split)].filter(Boolean).join("\n\n"),
158
+ }
159
+ }
160
+
161
+ export const buildPrompt = (input: { readonly previousSummary?: string; readonly context: readonly string[] }) =>
162
+ [
163
+ input.previousSummary
164
+ ? `Update the anchored summary below using the conversation history above.\nPreserve still-true details, remove stale details, and merge in the new facts.\n<previous-summary>\n${input.previousSummary}\n</previous-summary>`
165
+ : "Create a new anchored summary from the conversation history.",
166
+ SUMMARY_TEMPLATE,
167
+ ...input.context,
168
+ ].join("\n\n")
169
+
170
+ export const make = (dependencies: Dependencies) => {
171
+ const config = settings(dependencies.config)
172
+ const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: Input) {
173
+ const context = input.model.route.defaults.limits?.context
174
+ if (context === undefined || context <= 0) return false
175
+ const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
176
+ const selected = select(input.entries, config.tokens)
177
+ const previousSummary = input.entries.find((entry) => entry.message.type === "compaction")?.message
178
+ if (!selected || (selected.head.length === 0 && previousSummary?.type !== "compaction")) return false
179
+ const summaryPrompt = buildPrompt({
180
+ previousSummary: previousSummary?.type === "compaction" ? previousSummary.summary : undefined,
181
+ context: [previousSummary?.type === "compaction" ? previousSummary.recent : "", selected.head].filter(Boolean),
182
+ })
183
+ const summaryOutput = Math.min(output || SUMMARY_OUTPUT_TOKENS, SUMMARY_OUTPUT_TOKENS)
184
+ if (Token.estimate(summaryPrompt) > context - summaryOutput) return false
185
+ const messageID = SessionMessage.ID.create()
186
+ yield* dependencies.events.publish(SessionEvent.Compaction.Started, {
187
+ sessionID: input.sessionID,
188
+ messageID,
189
+ timestamp: yield* DateTime.now,
190
+ reason: "auto",
191
+ })
192
+
193
+ const chunks: string[] = []
194
+ let failed = false
195
+ const summarized = yield* dependencies.llm
196
+ .stream(
197
+ LLM.request({
198
+ model: input.model,
199
+ messages: [Message.user(summaryPrompt)],
200
+ tools: [],
201
+ generation: { maxTokens: summaryOutput },
202
+ }),
203
+ )
204
+ .pipe(
205
+ Stream.runForEach((event) => {
206
+ if (LLMEvent.is.providerError(event)) failed = true
207
+ if (LLMEvent.is.textDelta(event)) chunks.push(event.text)
208
+ return Effect.void
209
+ }),
210
+ Effect.as(true),
211
+ Effect.catchTag("LLM.Error", () => Effect.succeed(false)),
212
+ )
213
+ const summary = chunks.join("")
214
+ if (!summarized || failed || !summary.trim()) return false
215
+ yield* dependencies.events.publish(SessionEvent.Compaction.Ended, {
216
+ sessionID: input.sessionID,
217
+ messageID,
218
+ timestamp: yield* DateTime.now,
219
+ reason: "auto",
220
+ text: summary,
221
+ recent: selected.recent,
222
+ })
223
+ return true
224
+ })
225
+ const compactIfNeeded = Effect.fn("SessionCompaction.compactIfNeeded")(function* (input: Input) {
226
+ if (!config.auto) return false
227
+ const context = input.model.route.defaults.limits?.context
228
+ if (context === undefined || context <= 0) return false
229
+ const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
230
+ if (
231
+ estimate({ system: input.request.system, messages: input.request.messages, tools: input.request.tools }) <=
232
+ context - Math.max(output, config.buffer)
233
+ )
234
+ return false
235
+ return yield* compactAfterOverflow(input)
236
+ })
237
+ return {
238
+ compactIfNeeded,
239
+ compactAfterOverflow,
240
+ }
241
+ }