opencode-v2 1.1.53

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 (439) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +84 -0
  5. package/bunfig.toml +5 -0
  6. package/package.json +126 -0
  7. package/parsers-config.ts +253 -0
  8. package/script/build.ts +193 -0
  9. package/script/postinstall.mjs +125 -0
  10. package/script/publish.ts +181 -0
  11. package/script/schema.ts +47 -0
  12. package/script/seed-e2e.ts +50 -0
  13. package/src/acp/README.md +164 -0
  14. package/src/acp/agent.ts +1676 -0
  15. package/src/acp/session.ts +117 -0
  16. package/src/acp/types.ts +23 -0
  17. package/src/agent/agent.ts +414 -0
  18. package/src/agent/generate.txt +75 -0
  19. package/src/agent/prompt/compaction.txt +12 -0
  20. package/src/agent/prompt/explore.txt +18 -0
  21. package/src/agent/prompt/summary.txt +11 -0
  22. package/src/agent/prompt/title.txt +44 -0
  23. package/src/auth/index.ts +70 -0
  24. package/src/bun/index.ts +137 -0
  25. package/src/bun/registry.ts +48 -0
  26. package/src/bus/bus-event.ts +43 -0
  27. package/src/bus/global.ts +10 -0
  28. package/src/bus/index.ts +105 -0
  29. package/src/cli/bootstrap.ts +17 -0
  30. package/src/cli/cmd/acp.ts +70 -0
  31. package/src/cli/cmd/agent.ts +257 -0
  32. package/src/cli/cmd/auth.ts +400 -0
  33. package/src/cli/cmd/cmd.ts +7 -0
  34. package/src/cli/cmd/debug/agent.ts +167 -0
  35. package/src/cli/cmd/debug/config.ts +16 -0
  36. package/src/cli/cmd/debug/file.ts +97 -0
  37. package/src/cli/cmd/debug/index.ts +48 -0
  38. package/src/cli/cmd/debug/lsp.ts +52 -0
  39. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  40. package/src/cli/cmd/debug/scrap.ts +16 -0
  41. package/src/cli/cmd/debug/skill.ts +16 -0
  42. package/src/cli/cmd/debug/snapshot.ts +52 -0
  43. package/src/cli/cmd/export.ts +88 -0
  44. package/src/cli/cmd/generate.ts +38 -0
  45. package/src/cli/cmd/github.ts +1540 -0
  46. package/src/cli/cmd/import.ts +147 -0
  47. package/src/cli/cmd/mcp.ts +755 -0
  48. package/src/cli/cmd/models.ts +77 -0
  49. package/src/cli/cmd/pr.ts +112 -0
  50. package/src/cli/cmd/run.ts +617 -0
  51. package/src/cli/cmd/serve.ts +20 -0
  52. package/src/cli/cmd/session.ts +135 -0
  53. package/src/cli/cmd/stats.ts +426 -0
  54. package/src/cli/cmd/tui/app.tsx +801 -0
  55. package/src/cli/cmd/tui/attach.ts +52 -0
  56. package/src/cli/cmd/tui/component/border.tsx +21 -0
  57. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  58. package/src/cli/cmd/tui/component/dialog-command.tsx +148 -0
  59. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  60. package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
  61. package/src/cli/cmd/tui/component/dialog-provider.tsx +266 -0
  62. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  63. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  64. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  65. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  66. package/src/cli/cmd/tui/component/dialog-status.tsx +177 -0
  67. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  68. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  69. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  70. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +666 -0
  71. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  72. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  73. package/src/cli/cmd/tui/component/prompt/index.tsx +1132 -0
  74. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  75. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  76. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  77. package/src/cli/cmd/tui/component/tips.tsx +153 -0
  78. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  79. package/src/cli/cmd/tui/context/args.tsx +15 -0
  80. package/src/cli/cmd/tui/context/directory.ts +13 -0
  81. package/src/cli/cmd/tui/context/exit.tsx +52 -0
  82. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  83. package/src/cli/cmd/tui/context/keybind.tsx +100 -0
  84. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  85. package/src/cli/cmd/tui/context/local.tsx +409 -0
  86. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  87. package/src/cli/cmd/tui/context/route.tsx +46 -0
  88. package/src/cli/cmd/tui/context/sdk.tsx +101 -0
  89. package/src/cli/cmd/tui/context/sync.tsx +470 -0
  90. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  91. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  92. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  95. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  96. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  97. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  98. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  99. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  100. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  101. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  102. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  103. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  104. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  105. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  106. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  107. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  108. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  109. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  110. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  111. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  112. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  113. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  114. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  115. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  116. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  117. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  118. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  119. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  120. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  121. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  122. package/src/cli/cmd/tui/context/theme.tsx +1152 -0
  123. package/src/cli/cmd/tui/event.ts +48 -0
  124. package/src/cli/cmd/tui/routes/home.tsx +140 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  126. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  128. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  129. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  130. package/src/cli/cmd/tui/routes/session/header.tsx +142 -0
  131. package/src/cli/cmd/tui/routes/session/index.tsx +2126 -0
  132. package/src/cli/cmd/tui/routes/session/permission.tsx +508 -0
  133. package/src/cli/cmd/tui/routes/session/question.tsx +466 -0
  134. package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
  135. package/src/cli/cmd/tui/thread.ts +175 -0
  136. package/src/cli/cmd/tui/ui/dialog-alert.tsx +68 -0
  137. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +93 -0
  138. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +215 -0
  139. package/src/cli/cmd/tui/ui/dialog-help.tsx +49 -0
  140. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +88 -0
  141. package/src/cli/cmd/tui/ui/dialog-select.tsx +399 -0
  142. package/src/cli/cmd/tui/ui/dialog.tsx +167 -0
  143. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  144. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  145. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  146. package/src/cli/cmd/tui/util/clipboard.ts +159 -0
  147. package/src/cli/cmd/tui/util/editor.ts +32 -0
  148. package/src/cli/cmd/tui/util/signal.ts +7 -0
  149. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  150. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  151. package/src/cli/cmd/tui/worker.ts +152 -0
  152. package/src/cli/cmd/uninstall.ts +357 -0
  153. package/src/cli/cmd/upgrade.ts +73 -0
  154. package/src/cli/cmd/web.ts +81 -0
  155. package/src/cli/error.ts +57 -0
  156. package/src/cli/logo.ts +6 -0
  157. package/src/cli/network.ts +60 -0
  158. package/src/cli/ui.ts +113 -0
  159. package/src/cli/upgrade.ts +25 -0
  160. package/src/command/index.ts +150 -0
  161. package/src/command/template/initialize.txt +10 -0
  162. package/src/command/template/review.txt +99 -0
  163. package/src/config/config.ts +1477 -0
  164. package/src/config/markdown.ts +98 -0
  165. package/src/env/index.ts +28 -0
  166. package/src/file/ignore.ts +83 -0
  167. package/src/file/index.ts +583 -0
  168. package/src/file/ripgrep.ts +375 -0
  169. package/src/file/time.ts +69 -0
  170. package/src/file/watcher.ts +127 -0
  171. package/src/flag/flag.ts +97 -0
  172. package/src/format/formatter.ts +366 -0
  173. package/src/format/index.ts +137 -0
  174. package/src/global/index.ts +55 -0
  175. package/src/id/id.ts +83 -0
  176. package/src/ide/index.ts +76 -0
  177. package/src/index.ts +159 -0
  178. package/src/installation/index.ts +246 -0
  179. package/src/lsp/client.ts +252 -0
  180. package/src/lsp/index.ts +485 -0
  181. package/src/lsp/language.ts +119 -0
  182. package/src/lsp/server.ts +2046 -0
  183. package/src/mcp/auth.ts +132 -0
  184. package/src/mcp/index.ts +934 -0
  185. package/src/mcp/oauth-callback.ts +200 -0
  186. package/src/mcp/oauth-provider.ts +154 -0
  187. package/src/patch/index.ts +680 -0
  188. package/src/permission/arity.ts +163 -0
  189. package/src/permission/index.ts +210 -0
  190. package/src/permission/next.ts +280 -0
  191. package/src/plugin/codex.ts +624 -0
  192. package/src/plugin/copilot.ts +327 -0
  193. package/src/plugin/index.ts +138 -0
  194. package/src/project/bootstrap.ts +35 -0
  195. package/src/project/instance.ts +114 -0
  196. package/src/project/project.ts +371 -0
  197. package/src/project/state.ts +70 -0
  198. package/src/project/vcs.ts +76 -0
  199. package/src/provider/auth.ts +147 -0
  200. package/src/provider/models.ts +133 -0
  201. package/src/provider/provider.ts +1262 -0
  202. package/src/provider/sdk/copilot/README.md +5 -0
  203. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +164 -0
  204. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  205. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +17 -0
  206. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  207. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +780 -0
  208. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  209. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  210. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +87 -0
  211. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  212. package/src/provider/sdk/copilot/index.ts +2 -0
  213. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  214. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +303 -0
  215. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  216. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  217. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  218. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +207 -0
  219. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1732 -0
  220. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +177 -0
  221. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  222. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +88 -0
  223. package/src/provider/sdk/copilot/responses/tool/file-search.ts +128 -0
  224. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +115 -0
  225. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +65 -0
  226. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +104 -0
  227. package/src/provider/sdk/copilot/responses/tool/web-search.ts +103 -0
  228. package/src/provider/transform.ts +828 -0
  229. package/src/pty/index.ts +250 -0
  230. package/src/question/index.ts +171 -0
  231. package/src/scheduler/index.ts +61 -0
  232. package/src/server/error.ts +36 -0
  233. package/src/server/event.ts +7 -0
  234. package/src/server/mdns.ts +60 -0
  235. package/src/server/routes/config.ts +92 -0
  236. package/src/server/routes/experimental.ts +208 -0
  237. package/src/server/routes/file.ts +197 -0
  238. package/src/server/routes/global.ts +183 -0
  239. package/src/server/routes/mcp.ts +225 -0
  240. package/src/server/routes/permission.ts +68 -0
  241. package/src/server/routes/project.ts +82 -0
  242. package/src/server/routes/provider.ts +165 -0
  243. package/src/server/routes/pty.ts +169 -0
  244. package/src/server/routes/question.ts +98 -0
  245. package/src/server/routes/session.ts +939 -0
  246. package/src/server/routes/tui.ts +379 -0
  247. package/src/server/server.ts +613 -0
  248. package/src/session/compaction.ts +226 -0
  249. package/src/session/index.ts +524 -0
  250. package/src/session/instruction.ts +197 -0
  251. package/src/session/llm.ts +289 -0
  252. package/src/session/message-v2.ts +802 -0
  253. package/src/session/message.ts +189 -0
  254. package/src/session/processor.ts +407 -0
  255. package/src/session/prompt/agent.txt +43 -0
  256. package/src/session/prompt/anthropic-20250930.txt +166 -0
  257. package/src/session/prompt/anthropic.txt +105 -0
  258. package/src/session/prompt/beast.txt +147 -0
  259. package/src/session/prompt/build-switch.txt +5 -0
  260. package/src/session/prompt/codex_header.txt +79 -0
  261. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  262. package/src/session/prompt/gemini.txt +155 -0
  263. package/src/session/prompt/max-steps.txt +16 -0
  264. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  265. package/src/session/prompt/plan.txt +26 -0
  266. package/src/session/prompt/qwen.txt +109 -0
  267. package/src/session/prompt/research.txt +81 -0
  268. package/src/session/prompt/trinity.txt +97 -0
  269. package/src/session/prompt.ts +1952 -0
  270. package/src/session/retry.ts +97 -0
  271. package/src/session/revert.ts +121 -0
  272. package/src/session/status.ts +76 -0
  273. package/src/session/summary.ts +217 -0
  274. package/src/session/system.ts +54 -0
  275. package/src/session/todo.ts +37 -0
  276. package/src/share/share-next.ts +200 -0
  277. package/src/share/share.ts +92 -0
  278. package/src/shell/shell.ts +67 -0
  279. package/src/skill/discovery.ts +97 -0
  280. package/src/skill/index.ts +1 -0
  281. package/src/skill/skill.ts +188 -0
  282. package/src/snapshot/index.ts +255 -0
  283. package/src/storage/storage.ts +227 -0
  284. package/src/tool/agent-enter.txt +1 -0
  285. package/src/tool/agent-exit.txt +1 -0
  286. package/src/tool/agent.ts +237 -0
  287. package/src/tool/apply_patch.ts +281 -0
  288. package/src/tool/apply_patch.txt +33 -0
  289. package/src/tool/bash.ts +269 -0
  290. package/src/tool/bash.txt +115 -0
  291. package/src/tool/batch.ts +175 -0
  292. package/src/tool/batch.txt +24 -0
  293. package/src/tool/chat-enter.txt +15 -0
  294. package/src/tool/chat-exit.txt +7 -0
  295. package/src/tool/chat.ts +217 -0
  296. package/src/tool/codesearch.ts +132 -0
  297. package/src/tool/codesearch.txt +12 -0
  298. package/src/tool/edit.ts +655 -0
  299. package/src/tool/edit.txt +10 -0
  300. package/src/tool/external-directory.ts +32 -0
  301. package/src/tool/glob.ts +78 -0
  302. package/src/tool/glob.txt +6 -0
  303. package/src/tool/grep.ts +147 -0
  304. package/src/tool/grep.txt +8 -0
  305. package/src/tool/invalid.ts +17 -0
  306. package/src/tool/ls.ts +121 -0
  307. package/src/tool/ls.txt +1 -0
  308. package/src/tool/lsp.ts +96 -0
  309. package/src/tool/lsp.txt +19 -0
  310. package/src/tool/multiedit.ts +46 -0
  311. package/src/tool/multiedit.txt +41 -0
  312. package/src/tool/plan-enter.txt +14 -0
  313. package/src/tool/plan-exit.txt +13 -0
  314. package/src/tool/plan.ts +130 -0
  315. package/src/tool/question.ts +33 -0
  316. package/src/tool/question.txt +10 -0
  317. package/src/tool/read.ts +211 -0
  318. package/src/tool/read.txt +12 -0
  319. package/src/tool/registry.ts +167 -0
  320. package/src/tool/research-enter.txt +1 -0
  321. package/src/tool/research-exit.txt +1 -0
  322. package/src/tool/research.ts +134 -0
  323. package/src/tool/skill.ts +123 -0
  324. package/src/tool/task.ts +165 -0
  325. package/src/tool/task.txt +60 -0
  326. package/src/tool/todo.ts +53 -0
  327. package/src/tool/todoread.txt +14 -0
  328. package/src/tool/todowrite.txt +167 -0
  329. package/src/tool/tool.ts +89 -0
  330. package/src/tool/truncation.ts +106 -0
  331. package/src/tool/webfetch.ts +186 -0
  332. package/src/tool/webfetch.txt +13 -0
  333. package/src/tool/websearch.ts +150 -0
  334. package/src/tool/websearch.txt +14 -0
  335. package/src/tool/write.ts +85 -0
  336. package/src/tool/write.txt +8 -0
  337. package/src/util/abort.ts +35 -0
  338. package/src/util/archive.ts +16 -0
  339. package/src/util/color.ts +19 -0
  340. package/src/util/context.ts +25 -0
  341. package/src/util/defer.ts +12 -0
  342. package/src/util/eventloop.ts +20 -0
  343. package/src/util/filesystem.ts +93 -0
  344. package/src/util/fn.ts +11 -0
  345. package/src/util/format.ts +20 -0
  346. package/src/util/iife.ts +3 -0
  347. package/src/util/keybind.ts +103 -0
  348. package/src/util/lazy.ts +18 -0
  349. package/src/util/locale.ts +81 -0
  350. package/src/util/lock.ts +98 -0
  351. package/src/util/log.ts +180 -0
  352. package/src/util/proxied.ts +3 -0
  353. package/src/util/queue.ts +32 -0
  354. package/src/util/rpc.ts +66 -0
  355. package/src/util/scrap.ts +10 -0
  356. package/src/util/signal.ts +12 -0
  357. package/src/util/timeout.ts +14 -0
  358. package/src/util/token.ts +7 -0
  359. package/src/util/wildcard.ts +56 -0
  360. package/src/worktree/index.ts +574 -0
  361. package/sst-env.d.ts +9 -0
  362. package/test/acp/agent-interface.test.ts +51 -0
  363. package/test/acp/event-subscription.test.ts +436 -0
  364. package/test/agent/agent.test.ts +675 -0
  365. package/test/bun.test.ts +53 -0
  366. package/test/cli/github-action.test.ts +161 -0
  367. package/test/cli/github-remote.test.ts +80 -0
  368. package/test/cli/import.test.ts +38 -0
  369. package/test/cli/tui/transcript.test.ts +322 -0
  370. package/test/config/agent-color.test.ts +71 -0
  371. package/test/config/config.test.ts +1802 -0
  372. package/test/config/fixtures/empty-frontmatter.md +4 -0
  373. package/test/config/fixtures/frontmatter.md +28 -0
  374. package/test/config/fixtures/markdown-header.md +11 -0
  375. package/test/config/fixtures/no-frontmatter.md +1 -0
  376. package/test/config/fixtures/weird-model-id.md +13 -0
  377. package/test/config/markdown.test.ts +228 -0
  378. package/test/file/ignore.test.ts +10 -0
  379. package/test/file/path-traversal.test.ts +198 -0
  380. package/test/file/ripgrep.test.ts +39 -0
  381. package/test/fixture/fixture.ts +45 -0
  382. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  383. package/test/ide/ide.test.ts +82 -0
  384. package/test/keybind.test.ts +421 -0
  385. package/test/lsp/client.test.ts +95 -0
  386. package/test/mcp/headers.test.ts +153 -0
  387. package/test/mcp/oauth-browser.test.ts +249 -0
  388. package/test/memory/abort-leak.test.ts +136 -0
  389. package/test/patch/patch.test.ts +348 -0
  390. package/test/permission/arity.test.ts +33 -0
  391. package/test/permission/next.test.ts +690 -0
  392. package/test/permission-task.test.ts +319 -0
  393. package/test/plugin/auth-override.test.ts +44 -0
  394. package/test/plugin/codex.test.ts +123 -0
  395. package/test/preload.ts +63 -0
  396. package/test/project/project.test.ts +120 -0
  397. package/test/provider/amazon-bedrock.test.ts +445 -0
  398. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  399. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  400. package/test/provider/gitlab-duo.test.ts +262 -0
  401. package/test/provider/provider.test.ts +2129 -0
  402. package/test/provider/transform.test.ts +2022 -0
  403. package/test/question/question.test.ts +300 -0
  404. package/test/scheduler.test.ts +73 -0
  405. package/test/server/session-list.test.ts +39 -0
  406. package/test/server/session-select.test.ts +78 -0
  407. package/test/session/compaction.test.ts +293 -0
  408. package/test/session/instruction.test.ts +170 -0
  409. package/test/session/llm.test.ts +691 -0
  410. package/test/session/message-v2.test.ts +786 -0
  411. package/test/session/prompt-missing-file.test.ts +53 -0
  412. package/test/session/prompt-special-chars.test.ts +56 -0
  413. package/test/session/prompt-variant.test.ts +60 -0
  414. package/test/session/retry.test.ts +179 -0
  415. package/test/session/revert-compact.test.ts +285 -0
  416. package/test/session/session.test.ts +71 -0
  417. package/test/skill/discovery.test.ts +60 -0
  418. package/test/skill/skill.test.ts +388 -0
  419. package/test/snapshot/snapshot.test.ts +1040 -0
  420. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  421. package/test/tool/apply_patch.test.ts +559 -0
  422. package/test/tool/bash.test.ts +399 -0
  423. package/test/tool/external-directory.test.ts +127 -0
  424. package/test/tool/fixtures/large-image.png +0 -0
  425. package/test/tool/fixtures/models-api.json +38413 -0
  426. package/test/tool/grep.test.ts +110 -0
  427. package/test/tool/question.test.ts +107 -0
  428. package/test/tool/read.test.ts +358 -0
  429. package/test/tool/registry.test.ts +122 -0
  430. package/test/tool/skill.test.ts +112 -0
  431. package/test/tool/truncation.test.ts +159 -0
  432. package/test/util/filesystem.test.ts +39 -0
  433. package/test/util/format.test.ts +59 -0
  434. package/test/util/iife.test.ts +36 -0
  435. package/test/util/lazy.test.ts +50 -0
  436. package/test/util/lock.test.ts +72 -0
  437. package/test/util/timeout.test.ts +21 -0
  438. package/test/util/wildcard.test.ts +75 -0
  439. package/tsconfig.json +16 -0
@@ -0,0 +1,524 @@
1
+ import { Slug } from "@opencode-ai/util/slug"
2
+ import path from "path"
3
+ import { BusEvent } from "@/bus/bus-event"
4
+ import { Bus } from "@/bus"
5
+ import { Decimal } from "decimal.js"
6
+ import z from "zod"
7
+ import { type LanguageModelUsage, type ProviderMetadata } from "ai"
8
+ import { Config } from "../config/config"
9
+ import { Flag } from "../flag/flag"
10
+ import { Identifier } from "../id/id"
11
+ import { Installation } from "../installation"
12
+
13
+ import { Storage } from "../storage/storage"
14
+ import { Log } from "../util/log"
15
+ import { MessageV2 } from "./message-v2"
16
+ import { Instance } from "../project/instance"
17
+ import { SessionPrompt } from "./prompt"
18
+ import { fn } from "@/util/fn"
19
+ import { Command } from "../command"
20
+ import { Snapshot } from "@/snapshot"
21
+
22
+ import type { Provider } from "@/provider/provider"
23
+ import { PermissionNext } from "@/permission/next"
24
+ import { Global } from "@/global"
25
+
26
+ export namespace Session {
27
+ const log = Log.create({ service: "session" })
28
+
29
+ const parentTitlePrefix = "New session - "
30
+ const childTitlePrefix = "Child session - "
31
+
32
+ function createDefaultTitle(isChild = false) {
33
+ return (isChild ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString()
34
+ }
35
+
36
+ export function isDefaultTitle(title: string) {
37
+ return new RegExp(
38
+ `^(${parentTitlePrefix}|${childTitlePrefix})\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$`,
39
+ ).test(title)
40
+ }
41
+
42
+ function getForkedTitle(title: string): string {
43
+ const match = title.match(/^(.+) \(fork #(\d+)\)$/)
44
+ if (match) {
45
+ const base = match[1]
46
+ const num = parseInt(match[2], 10)
47
+ return `${base} (fork #${num + 1})`
48
+ }
49
+ return `${title} (fork #1)`
50
+ }
51
+
52
+ export const Info = z
53
+ .object({
54
+ id: Identifier.schema("session"),
55
+ slug: z.string(),
56
+ projectID: z.string(),
57
+ directory: z.string(),
58
+ parentID: Identifier.schema("session").optional(),
59
+ summary: z
60
+ .object({
61
+ additions: z.number(),
62
+ deletions: z.number(),
63
+ files: z.number(),
64
+ diffs: Snapshot.FileDiff.array().optional(),
65
+ })
66
+ .optional(),
67
+ share: z
68
+ .object({
69
+ url: z.string(),
70
+ })
71
+ .optional(),
72
+ title: z.string(),
73
+ version: z.string(),
74
+ time: z.object({
75
+ created: z.number(),
76
+ updated: z.number(),
77
+ compacting: z.number().optional(),
78
+ archived: z.number().optional(),
79
+ }),
80
+ permission: PermissionNext.Ruleset.optional(),
81
+ revert: z
82
+ .object({
83
+ messageID: z.string(),
84
+ partID: z.string().optional(),
85
+ snapshot: z.string().optional(),
86
+ diff: z.string().optional(),
87
+ })
88
+ .optional(),
89
+ })
90
+ .meta({
91
+ ref: "Session",
92
+ })
93
+ export type Info = z.output<typeof Info>
94
+
95
+ export const ShareInfo = z
96
+ .object({
97
+ secret: z.string(),
98
+ url: z.string(),
99
+ })
100
+ .meta({
101
+ ref: "SessionShare",
102
+ })
103
+ export type ShareInfo = z.output<typeof ShareInfo>
104
+
105
+ export const Event = {
106
+ Created: BusEvent.define(
107
+ "session.created",
108
+ z.object({
109
+ info: Info,
110
+ }),
111
+ ),
112
+ Updated: BusEvent.define(
113
+ "session.updated",
114
+ z.object({
115
+ info: Info,
116
+ }),
117
+ ),
118
+ Deleted: BusEvent.define(
119
+ "session.deleted",
120
+ z.object({
121
+ info: Info,
122
+ }),
123
+ ),
124
+ Diff: BusEvent.define(
125
+ "session.diff",
126
+ z.object({
127
+ sessionID: z.string(),
128
+ diff: Snapshot.FileDiff.array(),
129
+ }),
130
+ ),
131
+ Error: BusEvent.define(
132
+ "session.error",
133
+ z.object({
134
+ sessionID: z.string().optional(),
135
+ error: MessageV2.Assistant.shape.error,
136
+ }),
137
+ ),
138
+ }
139
+
140
+ export const create = fn(
141
+ z
142
+ .object({
143
+ parentID: Identifier.schema("session").optional(),
144
+ title: z.string().optional(),
145
+ permission: Info.shape.permission,
146
+ })
147
+ .optional(),
148
+ async (input) => {
149
+ return createNext({
150
+ parentID: input?.parentID,
151
+ directory: Instance.directory,
152
+ title: input?.title,
153
+ permission: input?.permission,
154
+ })
155
+ },
156
+ )
157
+
158
+ export const fork = fn(
159
+ z.object({
160
+ sessionID: Identifier.schema("session"),
161
+ messageID: Identifier.schema("message").optional(),
162
+ }),
163
+ async (input) => {
164
+ const original = await get(input.sessionID)
165
+ if (!original) throw new Error("session not found")
166
+ const title = getForkedTitle(original.title)
167
+ const session = await createNext({
168
+ directory: Instance.directory,
169
+ title,
170
+ })
171
+ const msgs = await messages({ sessionID: input.sessionID })
172
+ const idMap = new Map<string, string>()
173
+
174
+ for (const msg of msgs) {
175
+ if (input.messageID && msg.info.id >= input.messageID) break
176
+ const newID = Identifier.ascending("message")
177
+ idMap.set(msg.info.id, newID)
178
+
179
+ const parentID = msg.info.role === "assistant" && msg.info.parentID ? idMap.get(msg.info.parentID) : undefined
180
+ const cloned = await updateMessage({
181
+ ...msg.info,
182
+ sessionID: session.id,
183
+ id: newID,
184
+ ...(parentID && { parentID }),
185
+ })
186
+
187
+ for (const part of msg.parts) {
188
+ await updatePart({
189
+ ...part,
190
+ id: Identifier.ascending("part"),
191
+ messageID: cloned.id,
192
+ sessionID: session.id,
193
+ })
194
+ }
195
+ }
196
+ return session
197
+ },
198
+ )
199
+
200
+ export const touch = fn(Identifier.schema("session"), async (sessionID) => {
201
+ await update(sessionID, (draft) => {
202
+ draft.time.updated = Date.now()
203
+ })
204
+ })
205
+
206
+ export async function createNext(input: {
207
+ id?: string
208
+ title?: string
209
+ parentID?: string
210
+ directory: string
211
+ permission?: PermissionNext.Ruleset
212
+ }) {
213
+ const result: Info = {
214
+ id: Identifier.descending("session", input.id),
215
+ slug: Slug.create(),
216
+ version: Installation.VERSION,
217
+ projectID: Instance.project.id,
218
+ directory: input.directory,
219
+ parentID: input.parentID,
220
+ title: input.title ?? createDefaultTitle(!!input.parentID),
221
+ permission: input.permission,
222
+ time: {
223
+ created: Date.now(),
224
+ updated: Date.now(),
225
+ },
226
+ }
227
+ log.info("created", result)
228
+ await Storage.write(["session", Instance.project.id, result.id], result)
229
+ Bus.publish(Event.Created, {
230
+ info: result,
231
+ })
232
+ const cfg = await Config.get()
233
+ if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
234
+ share(result.id)
235
+ .then((share) => {
236
+ update(result.id, (draft) => {
237
+ draft.share = share
238
+ })
239
+ })
240
+ .catch(() => {
241
+ // Silently ignore sharing errors during session creation
242
+ })
243
+ Bus.publish(Event.Updated, {
244
+ info: result,
245
+ })
246
+ return result
247
+ }
248
+
249
+ export function plan(input: { slug: string; time: { created: number } }) {
250
+ const base = Instance.project.vcs
251
+ ? path.join(Instance.worktree, ".opencode", "plans")
252
+ : path.join(Global.Path.data, "plans")
253
+ return path.join(base, [input.time.created, input.slug].join("-") + ".md")
254
+ }
255
+
256
+ export function research(input: { slug: string; time: { created: number } }) {
257
+ const base = Instance.project.vcs
258
+ ? path.join(Instance.worktree, ".opencode", "research")
259
+ : path.join(Global.Path.data, "research")
260
+ return path.join(base, [input.time.created, input.slug].join("-") + ".md")
261
+ }
262
+
263
+ export const get = fn(Identifier.schema("session"), async (id) => {
264
+ const read = await Storage.read<Info>(["session", Instance.project.id, id])
265
+ return read as Info
266
+ })
267
+
268
+ export const getShare = fn(Identifier.schema("session"), async (id) => {
269
+ return Storage.read<ShareInfo>(["share", id])
270
+ })
271
+
272
+ export const share = fn(Identifier.schema("session"), async (id) => {
273
+ const cfg = await Config.get()
274
+ if (cfg.share === "disabled") {
275
+ throw new Error("Sharing is disabled in configuration")
276
+ }
277
+ const { ShareNext } = await import("@/share/share-next")
278
+ const share = await ShareNext.create(id)
279
+ await update(
280
+ id,
281
+ (draft) => {
282
+ draft.share = {
283
+ url: share.url,
284
+ }
285
+ },
286
+ { touch: false },
287
+ )
288
+ return share
289
+ })
290
+
291
+ export const unshare = fn(Identifier.schema("session"), async (id) => {
292
+ // Use ShareNext to remove the share (same as share function uses ShareNext to create)
293
+ const { ShareNext } = await import("@/share/share-next")
294
+ await ShareNext.remove(id)
295
+ await update(
296
+ id,
297
+ (draft) => {
298
+ draft.share = undefined
299
+ },
300
+ { touch: false },
301
+ )
302
+ })
303
+
304
+ export async function update(id: string, editor: (session: Info) => void, options?: { touch?: boolean }) {
305
+ const project = Instance.project
306
+ const result = await Storage.update<Info>(["session", project.id, id], (draft) => {
307
+ editor(draft)
308
+ if (options?.touch !== false) {
309
+ draft.time.updated = Date.now()
310
+ }
311
+ })
312
+ Bus.publish(Event.Updated, {
313
+ info: result,
314
+ })
315
+ return result
316
+ }
317
+
318
+ export const diff = fn(Identifier.schema("session"), async (sessionID) => {
319
+ const diffs = await Storage.read<Snapshot.FileDiff[]>(["session_diff", sessionID])
320
+ return diffs ?? []
321
+ })
322
+
323
+ export const messages = fn(
324
+ z.object({
325
+ sessionID: Identifier.schema("session"),
326
+ limit: z.number().optional(),
327
+ }),
328
+ async (input) => {
329
+ const result = [] as MessageV2.WithParts[]
330
+ for await (const msg of MessageV2.stream(input.sessionID)) {
331
+ if (input.limit && result.length >= input.limit) break
332
+ result.push(msg)
333
+ }
334
+ result.reverse()
335
+ return result
336
+ },
337
+ )
338
+
339
+ export async function* list() {
340
+ const project = Instance.project
341
+ for (const item of await Storage.list(["session", project.id])) {
342
+ const session = await Storage.read<Info>(item).catch(() => undefined)
343
+ if (!session) continue
344
+ yield session
345
+ }
346
+ }
347
+
348
+ export const children = fn(Identifier.schema("session"), async (parentID) => {
349
+ const project = Instance.project
350
+ const result = [] as Session.Info[]
351
+ for (const item of await Storage.list(["session", project.id])) {
352
+ const session = await Storage.read<Info>(item).catch(() => undefined)
353
+ if (!session) continue
354
+ if (session.parentID !== parentID) continue
355
+ result.push(session)
356
+ }
357
+ return result
358
+ })
359
+
360
+ export const remove = fn(Identifier.schema("session"), async (sessionID) => {
361
+ const project = Instance.project
362
+ try {
363
+ const session = await get(sessionID)
364
+ for (const child of await children(sessionID)) {
365
+ await remove(child.id)
366
+ }
367
+ await unshare(sessionID).catch(() => {})
368
+ for (const msg of await Storage.list(["message", sessionID])) {
369
+ for (const part of await Storage.list(["part", msg.at(-1)!])) {
370
+ await Storage.remove(part)
371
+ }
372
+ await Storage.remove(msg)
373
+ }
374
+ await Storage.remove(["session", project.id, sessionID])
375
+ Bus.publish(Event.Deleted, {
376
+ info: session,
377
+ })
378
+ } catch (e) {
379
+ log.error(e)
380
+ }
381
+ })
382
+
383
+ export const updateMessage = fn(MessageV2.Info, async (msg) => {
384
+ await Storage.write(["message", msg.sessionID, msg.id], msg)
385
+ Bus.publish(MessageV2.Event.Updated, {
386
+ info: msg,
387
+ })
388
+ return msg
389
+ })
390
+
391
+ export const removeMessage = fn(
392
+ z.object({
393
+ sessionID: Identifier.schema("session"),
394
+ messageID: Identifier.schema("message"),
395
+ }),
396
+ async (input) => {
397
+ await Storage.remove(["message", input.sessionID, input.messageID])
398
+ Bus.publish(MessageV2.Event.Removed, {
399
+ sessionID: input.sessionID,
400
+ messageID: input.messageID,
401
+ })
402
+ return input.messageID
403
+ },
404
+ )
405
+
406
+ export const removePart = fn(
407
+ z.object({
408
+ sessionID: Identifier.schema("session"),
409
+ messageID: Identifier.schema("message"),
410
+ partID: Identifier.schema("part"),
411
+ }),
412
+ async (input) => {
413
+ await Storage.remove(["part", input.messageID, input.partID])
414
+ Bus.publish(MessageV2.Event.PartRemoved, {
415
+ sessionID: input.sessionID,
416
+ messageID: input.messageID,
417
+ partID: input.partID,
418
+ })
419
+ return input.partID
420
+ },
421
+ )
422
+
423
+ const UpdatePartInput = z.union([
424
+ MessageV2.Part,
425
+ z.object({
426
+ part: MessageV2.TextPart,
427
+ delta: z.string(),
428
+ }),
429
+ z.object({
430
+ part: MessageV2.ReasoningPart,
431
+ delta: z.string(),
432
+ }),
433
+ ])
434
+
435
+ export const updatePart = fn(UpdatePartInput, async (input) => {
436
+ const part = "delta" in input ? input.part : input
437
+ const delta = "delta" in input ? input.delta : undefined
438
+ await Storage.write(["part", part.messageID, part.id], part)
439
+ Bus.publish(MessageV2.Event.PartUpdated, {
440
+ part,
441
+ delta,
442
+ })
443
+ return part
444
+ })
445
+
446
+ export const getUsage = fn(
447
+ z.object({
448
+ model: z.custom<Provider.Model>(),
449
+ usage: z.custom<LanguageModelUsage>(),
450
+ metadata: z.custom<ProviderMetadata>().optional(),
451
+ }),
452
+ (input) => {
453
+ const cacheReadInputTokens = input.usage.cachedInputTokens ?? 0
454
+ const cacheWriteInputTokens = (input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
455
+ // @ts-expect-error
456
+ input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
457
+ // @ts-expect-error
458
+ input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
459
+ 0) as number
460
+
461
+ const excludesCachedTokens = !!(input.metadata?.["anthropic"] || input.metadata?.["bedrock"])
462
+ const adjustedInputTokens = excludesCachedTokens
463
+ ? (input.usage.inputTokens ?? 0)
464
+ : (input.usage.inputTokens ?? 0) - cacheReadInputTokens - cacheWriteInputTokens
465
+ const safe = (value: number) => {
466
+ if (!Number.isFinite(value)) return 0
467
+ return value
468
+ }
469
+
470
+ const tokens = {
471
+ input: safe(adjustedInputTokens),
472
+ output: safe(input.usage.outputTokens ?? 0),
473
+ reasoning: safe(input.usage?.reasoningTokens ?? 0),
474
+ cache: {
475
+ write: safe(cacheWriteInputTokens),
476
+ read: safe(cacheReadInputTokens),
477
+ },
478
+ }
479
+
480
+ const costInfo =
481
+ input.model.cost?.experimentalOver200K && tokens.input + tokens.cache.read > 200_000
482
+ ? input.model.cost.experimentalOver200K
483
+ : input.model.cost
484
+ return {
485
+ cost: safe(
486
+ new Decimal(0)
487
+ .add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000))
488
+ .add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000))
489
+ .add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000))
490
+ .add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000))
491
+ // TODO: update models.dev to have better pricing model, for now:
492
+ // charge reasoning tokens at the same rate as output tokens
493
+ .add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000))
494
+ .toNumber(),
495
+ ),
496
+ tokens,
497
+ }
498
+ },
499
+ )
500
+
501
+ export class BusyError extends Error {
502
+ constructor(public readonly sessionID: string) {
503
+ super(`Session ${sessionID} is busy`)
504
+ }
505
+ }
506
+
507
+ export const initialize = fn(
508
+ z.object({
509
+ sessionID: Identifier.schema("session"),
510
+ modelID: z.string(),
511
+ providerID: z.string(),
512
+ messageID: Identifier.schema("message"),
513
+ }),
514
+ async (input) => {
515
+ await SessionPrompt.command({
516
+ sessionID: input.sessionID,
517
+ messageID: input.messageID,
518
+ model: input.providerID + "/" + input.modelID,
519
+ command: Command.Default.INIT,
520
+ arguments: "",
521
+ })
522
+ },
523
+ )
524
+ }
@@ -0,0 +1,197 @@
1
+ import path from "path"
2
+ import os from "os"
3
+ import { Global } from "../global"
4
+ import { Filesystem } from "../util/filesystem"
5
+ import { Config } from "../config/config"
6
+ import { Instance } from "../project/instance"
7
+ import { Flag } from "@/flag/flag"
8
+ import { Log } from "../util/log"
9
+ import type { MessageV2 } from "./message-v2"
10
+
11
+ const log = Log.create({ service: "instruction" })
12
+
13
+ const FILES = [
14
+ "AGENTS.md",
15
+ "CLAUDE.md",
16
+ "CONTEXT.md", // deprecated
17
+ ]
18
+
19
+ function globalFiles() {
20
+ const files = []
21
+ if (Flag.OPENCODE_CONFIG_DIR) {
22
+ files.push(path.join(Flag.OPENCODE_CONFIG_DIR, "AGENTS.md"))
23
+ }
24
+ files.push(path.join(Global.Path.config, "AGENTS.md"))
25
+ if (!Flag.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT) {
26
+ files.push(path.join(os.homedir(), ".claude", "CLAUDE.md"))
27
+ }
28
+ return files
29
+ }
30
+
31
+ async function resolveRelative(instruction: string): Promise<string[]> {
32
+ if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
33
+ return Filesystem.globUp(instruction, Instance.directory, Instance.worktree).catch(() => [])
34
+ }
35
+ if (!Flag.OPENCODE_CONFIG_DIR) {
36
+ log.warn(
37
+ `Skipping relative instruction "${instruction}" - no OPENCODE_CONFIG_DIR set while project config is disabled`,
38
+ )
39
+ return []
40
+ }
41
+ return Filesystem.globUp(instruction, Flag.OPENCODE_CONFIG_DIR, Flag.OPENCODE_CONFIG_DIR).catch(() => [])
42
+ }
43
+
44
+ export namespace InstructionPrompt {
45
+ const state = Instance.state(() => {
46
+ return {
47
+ claims: new Map<string, Set<string>>(),
48
+ }
49
+ })
50
+
51
+ function isClaimed(messageID: string, filepath: string) {
52
+ const claimed = state().claims.get(messageID)
53
+ if (!claimed) return false
54
+ return claimed.has(filepath)
55
+ }
56
+
57
+ function claim(messageID: string, filepath: string) {
58
+ const current = state()
59
+ let claimed = current.claims.get(messageID)
60
+ if (!claimed) {
61
+ claimed = new Set()
62
+ current.claims.set(messageID, claimed)
63
+ }
64
+ claimed.add(filepath)
65
+ }
66
+
67
+ export function clear(messageID: string) {
68
+ state().claims.delete(messageID)
69
+ }
70
+
71
+ export async function systemPaths() {
72
+ const config = await Config.get()
73
+ const paths = new Set<string>()
74
+
75
+ if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
76
+ for (const file of FILES) {
77
+ const matches = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
78
+ if (matches.length > 0) {
79
+ matches.forEach((p) => {
80
+ paths.add(path.resolve(p))
81
+ })
82
+ break
83
+ }
84
+ }
85
+ }
86
+
87
+ for (const file of globalFiles()) {
88
+ if (await Bun.file(file).exists()) {
89
+ paths.add(path.resolve(file))
90
+ break
91
+ }
92
+ }
93
+
94
+ if (config.instructions) {
95
+ for (let instruction of config.instructions) {
96
+ if (instruction.startsWith("https://") || instruction.startsWith("http://")) continue
97
+ if (instruction.startsWith("~/")) {
98
+ instruction = path.join(os.homedir(), instruction.slice(2))
99
+ }
100
+ const matches = path.isAbsolute(instruction)
101
+ ? await Array.fromAsync(
102
+ new Bun.Glob(path.basename(instruction)).scan({
103
+ cwd: path.dirname(instruction),
104
+ absolute: true,
105
+ onlyFiles: true,
106
+ }),
107
+ ).catch(() => [])
108
+ : await resolveRelative(instruction)
109
+ matches.forEach((p) => {
110
+ paths.add(path.resolve(p))
111
+ })
112
+ }
113
+ }
114
+
115
+ return paths
116
+ }
117
+
118
+ export async function system() {
119
+ const config = await Config.get()
120
+ const paths = await systemPaths()
121
+
122
+ const files = Array.from(paths).map(async (p) => {
123
+ const content = await Bun.file(p)
124
+ .text()
125
+ .catch(() => "")
126
+ return content ? "Instructions from: " + p + "\n" + content : ""
127
+ })
128
+
129
+ const urls: string[] = []
130
+ if (config.instructions) {
131
+ for (const instruction of config.instructions) {
132
+ if (instruction.startsWith("https://") || instruction.startsWith("http://")) {
133
+ urls.push(instruction)
134
+ }
135
+ }
136
+ }
137
+ const fetches = urls.map((url) =>
138
+ fetch(url, { signal: AbortSignal.timeout(5000) })
139
+ .then((res) => (res.ok ? res.text() : ""))
140
+ .catch(() => "")
141
+ .then((x) => (x ? "Instructions from: " + url + "\n" + x : "")),
142
+ )
143
+
144
+ return Promise.all([...files, ...fetches]).then((result) => result.filter(Boolean))
145
+ }
146
+
147
+ export function loaded(messages: MessageV2.WithParts[]) {
148
+ const paths = new Set<string>()
149
+ for (const msg of messages) {
150
+ for (const part of msg.parts) {
151
+ if (part.type === "tool" && part.tool === "read" && part.state.status === "completed") {
152
+ if (part.state.time.compacted) continue
153
+ const loaded = part.state.metadata?.loaded
154
+ if (!loaded || !Array.isArray(loaded)) continue
155
+ for (const p of loaded) {
156
+ if (typeof p === "string") paths.add(p)
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return paths
162
+ }
163
+
164
+ export async function find(dir: string) {
165
+ for (const file of FILES) {
166
+ const filepath = path.resolve(path.join(dir, file))
167
+ if (await Bun.file(filepath).exists()) return filepath
168
+ }
169
+ }
170
+
171
+ export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: string) {
172
+ const system = await systemPaths()
173
+ const already = loaded(messages)
174
+ const results: { filepath: string; content: string }[] = []
175
+
176
+ const target = path.resolve(filepath)
177
+ let current = path.dirname(target)
178
+ const root = path.resolve(Instance.directory)
179
+
180
+ while (current.startsWith(root) && current !== root) {
181
+ const found = await find(current)
182
+
183
+ if (found && found !== target && !system.has(found) && !already.has(found) && !isClaimed(messageID, found)) {
184
+ claim(messageID, found)
185
+ const content = await Bun.file(found)
186
+ .text()
187
+ .catch(() => undefined)
188
+ if (content) {
189
+ results.push({ filepath: found, content: "Instructions from: " + found + "\n" + content })
190
+ }
191
+ }
192
+ current = path.dirname(current)
193
+ }
194
+
195
+ return results
196
+ }
197
+ }