@stonerzju/opencode 1.2.16-offline.1

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 (496) hide show
  1. package/AGENTS.md +10 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/opencode +179 -0
  6. package/bunfig.toml +7 -0
  7. package/drizzle.config.ts +10 -0
  8. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  9. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  10. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  11. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  12. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  13. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  14. package/migration/20260225215848_workspace/migration.sql +7 -0
  15. package/migration/20260225215848_workspace/snapshot.json +959 -0
  16. package/package.json +140 -0
  17. package/package.json.bak +140 -0
  18. package/parsers-config.ts +254 -0
  19. package/script/build.ts +224 -0
  20. package/script/check-migrations.ts +16 -0
  21. package/script/postinstall.mjs +131 -0
  22. package/script/publish.ts +181 -0
  23. package/script/schema.ts +63 -0
  24. package/script/seed-e2e.ts +50 -0
  25. package/src/acp/README.md +174 -0
  26. package/src/acp/agent.ts +1741 -0
  27. package/src/acp/session.ts +116 -0
  28. package/src/acp/types.ts +23 -0
  29. package/src/agent/agent.ts +339 -0
  30. package/src/agent/generate.txt +75 -0
  31. package/src/agent/prompt/compaction.txt +14 -0
  32. package/src/agent/prompt/explore.txt +18 -0
  33. package/src/agent/prompt/summary.txt +11 -0
  34. package/src/agent/prompt/title.txt +44 -0
  35. package/src/auth/index.ts +68 -0
  36. package/src/bun/index.ts +131 -0
  37. package/src/bun/registry.ts +50 -0
  38. package/src/bus/bus-event.ts +43 -0
  39. package/src/bus/global.ts +10 -0
  40. package/src/bus/index.ts +105 -0
  41. package/src/cli/bootstrap.ts +17 -0
  42. package/src/cli/cmd/acp.ts +70 -0
  43. package/src/cli/cmd/agent.ts +257 -0
  44. package/src/cli/cmd/auth.ts +449 -0
  45. package/src/cli/cmd/cmd.ts +7 -0
  46. package/src/cli/cmd/db.ts +118 -0
  47. package/src/cli/cmd/debug/agent.ts +167 -0
  48. package/src/cli/cmd/debug/config.ts +16 -0
  49. package/src/cli/cmd/debug/file.ts +97 -0
  50. package/src/cli/cmd/debug/index.ts +48 -0
  51. package/src/cli/cmd/debug/lsp.ts +52 -0
  52. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  53. package/src/cli/cmd/debug/scrap.ts +16 -0
  54. package/src/cli/cmd/debug/skill.ts +16 -0
  55. package/src/cli/cmd/debug/snapshot.ts +52 -0
  56. package/src/cli/cmd/export.ts +88 -0
  57. package/src/cli/cmd/generate.ts +38 -0
  58. package/src/cli/cmd/github.ts +1631 -0
  59. package/src/cli/cmd/import.ts +170 -0
  60. package/src/cli/cmd/mcp.ts +754 -0
  61. package/src/cli/cmd/models.ts +77 -0
  62. package/src/cli/cmd/pr.ts +112 -0
  63. package/src/cli/cmd/run.ts +625 -0
  64. package/src/cli/cmd/serve.ts +31 -0
  65. package/src/cli/cmd/session.ts +156 -0
  66. package/src/cli/cmd/stats.ts +410 -0
  67. package/src/cli/cmd/tui/app.tsx +845 -0
  68. package/src/cli/cmd/tui/attach.ts +88 -0
  69. package/src/cli/cmd/tui/component/border.tsx +21 -0
  70. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  71. package/src/cli/cmd/tui/component/dialog-command.tsx +147 -0
  72. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  73. package/src/cli/cmd/tui/component/dialog-model.tsx +165 -0
  74. package/src/cli/cmd/tui/component/dialog-provider.tsx +259 -0
  75. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  76. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  77. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  78. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  79. package/src/cli/cmd/tui/component/dialog-status.tsx +167 -0
  80. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  81. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  82. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  83. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
  84. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  85. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  86. package/src/cli/cmd/tui/component/prompt/index.tsx +1155 -0
  87. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  88. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  89. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  90. package/src/cli/cmd/tui/component/tips.tsx +152 -0
  91. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  92. package/src/cli/cmd/tui/context/args.tsx +15 -0
  93. package/src/cli/cmd/tui/context/directory.ts +13 -0
  94. package/src/cli/cmd/tui/context/exit.tsx +53 -0
  95. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  96. package/src/cli/cmd/tui/context/keybind.tsx +102 -0
  97. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  98. package/src/cli/cmd/tui/context/local.tsx +406 -0
  99. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  100. package/src/cli/cmd/tui/context/route.tsx +46 -0
  101. package/src/cli/cmd/tui/context/sdk.tsx +101 -0
  102. package/src/cli/cmd/tui/context/sync.tsx +488 -0
  103. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  104. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  105. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  106. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  107. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  108. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  109. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  110. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  111. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  112. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  113. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  114. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  115. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  116. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  117. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  118. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  119. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  120. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  121. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  122. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  123. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  124. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  125. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  126. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  127. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  128. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  129. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  130. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  131. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  132. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  133. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  134. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  135. package/src/cli/cmd/tui/context/theme.tsx +1152 -0
  136. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  137. package/src/cli/cmd/tui/event.ts +48 -0
  138. package/src/cli/cmd/tui/routes/home.tsx +145 -0
  139. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  140. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  141. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  142. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  143. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  144. package/src/cli/cmd/tui/routes/session/header.tsx +135 -0
  145. package/src/cli/cmd/tui/routes/session/index.tsx +2219 -0
  146. package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
  147. package/src/cli/cmd/tui/routes/session/question.tsx +466 -0
  148. package/src/cli/cmd/tui/routes/session/sidebar.tsx +321 -0
  149. package/src/cli/cmd/tui/thread.ts +199 -0
  150. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  151. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +85 -0
  152. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +207 -0
  153. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  154. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +80 -0
  155. package/src/cli/cmd/tui/ui/dialog-select.tsx +401 -0
  156. package/src/cli/cmd/tui/ui/dialog.tsx +182 -0
  157. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  158. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  159. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  160. package/src/cli/cmd/tui/util/clipboard.ts +164 -0
  161. package/src/cli/cmd/tui/util/editor.ts +33 -0
  162. package/src/cli/cmd/tui/util/selection.ts +25 -0
  163. package/src/cli/cmd/tui/util/signal.ts +7 -0
  164. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  165. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  166. package/src/cli/cmd/tui/win32.ts +129 -0
  167. package/src/cli/cmd/tui/worker.ts +157 -0
  168. package/src/cli/cmd/uninstall.ts +356 -0
  169. package/src/cli/cmd/upgrade.ts +73 -0
  170. package/src/cli/cmd/web.ts +81 -0
  171. package/src/cli/cmd/workspace-serve.ts +16 -0
  172. package/src/cli/error.ts +57 -0
  173. package/src/cli/logo.ts +6 -0
  174. package/src/cli/network.ts +60 -0
  175. package/src/cli/ui.ts +116 -0
  176. package/src/cli/upgrade.ts +25 -0
  177. package/src/command/index.ts +150 -0
  178. package/src/command/template/initialize.txt +10 -0
  179. package/src/command/template/review.txt +101 -0
  180. package/src/config/config.ts +1408 -0
  181. package/src/config/markdown.ts +99 -0
  182. package/src/config/migrate-tui-config.ts +155 -0
  183. package/src/config/paths.ts +174 -0
  184. package/src/config/tui-schema.ts +34 -0
  185. package/src/config/tui.ts +118 -0
  186. package/src/control/control.sql.ts +22 -0
  187. package/src/control/index.ts +67 -0
  188. package/src/control-plane/adaptors/index.ts +10 -0
  189. package/src/control-plane/adaptors/types.ts +7 -0
  190. package/src/control-plane/adaptors/worktree.ts +26 -0
  191. package/src/control-plane/config.ts +10 -0
  192. package/src/control-plane/session-proxy-middleware.ts +46 -0
  193. package/src/control-plane/sse.ts +66 -0
  194. package/src/control-plane/workspace-server/routes.ts +33 -0
  195. package/src/control-plane/workspace-server/server.ts +24 -0
  196. package/src/control-plane/workspace.sql.ts +12 -0
  197. package/src/control-plane/workspace.ts +160 -0
  198. package/src/env/index.ts +28 -0
  199. package/src/file/ignore.ts +82 -0
  200. package/src/file/index.ts +646 -0
  201. package/src/file/ripgrep.ts +372 -0
  202. package/src/file/time.ts +71 -0
  203. package/src/file/watcher.ts +128 -0
  204. package/src/flag/flag.ts +109 -0
  205. package/src/format/formatter.ts +395 -0
  206. package/src/format/index.ts +140 -0
  207. package/src/global/index.ts +54 -0
  208. package/src/id/id.ts +84 -0
  209. package/src/ide/index.ts +76 -0
  210. package/src/index.ts +210 -0
  211. package/src/installation/index.ts +266 -0
  212. package/src/lsp/client.ts +251 -0
  213. package/src/lsp/index.ts +485 -0
  214. package/src/lsp/language.ts +120 -0
  215. package/src/lsp/server.ts +2142 -0
  216. package/src/mcp/auth.ts +130 -0
  217. package/src/mcp/index.ts +937 -0
  218. package/src/mcp/oauth-callback.ts +200 -0
  219. package/src/mcp/oauth-provider.ts +176 -0
  220. package/src/patch/index.ts +680 -0
  221. package/src/permission/arity.ts +163 -0
  222. package/src/permission/index.ts +210 -0
  223. package/src/permission/next.ts +286 -0
  224. package/src/plugin/codex.ts +624 -0
  225. package/src/plugin/copilot.ts +327 -0
  226. package/src/plugin/index.ts +143 -0
  227. package/src/project/bootstrap.ts +33 -0
  228. package/src/project/instance.ts +114 -0
  229. package/src/project/project.sql.ts +15 -0
  230. package/src/project/project.ts +441 -0
  231. package/src/project/state.ts +70 -0
  232. package/src/project/vcs.ts +76 -0
  233. package/src/provider/auth.ts +147 -0
  234. package/src/provider/error.ts +189 -0
  235. package/src/provider/models.ts +146 -0
  236. package/src/provider/provider.ts +1338 -0
  237. package/src/provider/sdk/copilot/README.md +5 -0
  238. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +164 -0
  239. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  240. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +17 -0
  241. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  242. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +780 -0
  243. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  244. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  245. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +87 -0
  246. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  247. package/src/provider/sdk/copilot/index.ts +2 -0
  248. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  249. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +303 -0
  250. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  251. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  252. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  253. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +207 -0
  254. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1732 -0
  255. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +177 -0
  256. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  257. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +88 -0
  258. package/src/provider/sdk/copilot/responses/tool/file-search.ts +128 -0
  259. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +115 -0
  260. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +65 -0
  261. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +104 -0
  262. package/src/provider/sdk/copilot/responses/tool/web-search.ts +103 -0
  263. package/src/provider/transform.ts +955 -0
  264. package/src/pty/index.ts +324 -0
  265. package/src/question/index.ts +171 -0
  266. package/src/scheduler/index.ts +61 -0
  267. package/src/server/error.ts +36 -0
  268. package/src/server/event.ts +7 -0
  269. package/src/server/mdns.ts +60 -0
  270. package/src/server/routes/config.ts +92 -0
  271. package/src/server/routes/experimental.ts +270 -0
  272. package/src/server/routes/file.ts +197 -0
  273. package/src/server/routes/global.ts +185 -0
  274. package/src/server/routes/mcp.ts +225 -0
  275. package/src/server/routes/permission.ts +68 -0
  276. package/src/server/routes/project.ts +82 -0
  277. package/src/server/routes/provider.ts +165 -0
  278. package/src/server/routes/pty.ts +200 -0
  279. package/src/server/routes/question.ts +98 -0
  280. package/src/server/routes/session.ts +974 -0
  281. package/src/server/routes/tui.ts +379 -0
  282. package/src/server/routes/workspace.ts +104 -0
  283. package/src/server/server.ts +623 -0
  284. package/src/session/compaction.ts +261 -0
  285. package/src/session/index.ts +877 -0
  286. package/src/session/instruction.ts +192 -0
  287. package/src/session/llm.ts +279 -0
  288. package/src/session/message-v2.ts +899 -0
  289. package/src/session/message.ts +189 -0
  290. package/src/session/processor.ts +421 -0
  291. package/src/session/prompt/anthropic-20250930.txt +166 -0
  292. package/src/session/prompt/anthropic.txt +105 -0
  293. package/src/session/prompt/beast.txt +147 -0
  294. package/src/session/prompt/build-switch.txt +5 -0
  295. package/src/session/prompt/codex_header.txt +79 -0
  296. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  297. package/src/session/prompt/gemini.txt +155 -0
  298. package/src/session/prompt/max-steps.txt +16 -0
  299. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  300. package/src/session/prompt/plan.txt +26 -0
  301. package/src/session/prompt/qwen.txt +109 -0
  302. package/src/session/prompt/trinity.txt +97 -0
  303. package/src/session/prompt.ts +1959 -0
  304. package/src/session/retry.ts +101 -0
  305. package/src/session/revert.ts +138 -0
  306. package/src/session/session.sql.ts +88 -0
  307. package/src/session/status.ts +76 -0
  308. package/src/session/summary.ts +161 -0
  309. package/src/session/system.ts +54 -0
  310. package/src/session/todo.ts +56 -0
  311. package/src/share/share-next.ts +210 -0
  312. package/src/share/share.sql.ts +13 -0
  313. package/src/shell/shell.ts +68 -0
  314. package/src/skill/discovery.ts +98 -0
  315. package/src/skill/index.ts +1 -0
  316. package/src/skill/skill.ts +189 -0
  317. package/src/snapshot/index.ts +297 -0
  318. package/src/sql.d.ts +4 -0
  319. package/src/storage/db.ts +155 -0
  320. package/src/storage/json-migration.ts +425 -0
  321. package/src/storage/schema.sql.ts +10 -0
  322. package/src/storage/schema.ts +5 -0
  323. package/src/storage/storage.ts +220 -0
  324. package/src/tool/apply_patch.ts +281 -0
  325. package/src/tool/apply_patch.txt +33 -0
  326. package/src/tool/bash.ts +274 -0
  327. package/src/tool/bash.txt +115 -0
  328. package/src/tool/batch.ts +181 -0
  329. package/src/tool/batch.txt +24 -0
  330. package/src/tool/codesearch.ts +132 -0
  331. package/src/tool/codesearch.txt +12 -0
  332. package/src/tool/edit.ts +654 -0
  333. package/src/tool/edit.txt +10 -0
  334. package/src/tool/external-directory.ts +32 -0
  335. package/src/tool/glob.ts +78 -0
  336. package/src/tool/glob.txt +6 -0
  337. package/src/tool/grep.ts +156 -0
  338. package/src/tool/grep.txt +8 -0
  339. package/src/tool/invalid.ts +17 -0
  340. package/src/tool/ls.ts +121 -0
  341. package/src/tool/ls.txt +1 -0
  342. package/src/tool/lsp.ts +97 -0
  343. package/src/tool/lsp.txt +19 -0
  344. package/src/tool/multiedit.ts +46 -0
  345. package/src/tool/multiedit.txt +41 -0
  346. package/src/tool/plan-enter.txt +14 -0
  347. package/src/tool/plan-exit.txt +13 -0
  348. package/src/tool/plan.ts +131 -0
  349. package/src/tool/question.ts +33 -0
  350. package/src/tool/question.txt +10 -0
  351. package/src/tool/read.ts +293 -0
  352. package/src/tool/read.txt +14 -0
  353. package/src/tool/registry.ts +173 -0
  354. package/src/tool/skill.ts +123 -0
  355. package/src/tool/task.ts +165 -0
  356. package/src/tool/task.txt +60 -0
  357. package/src/tool/todo.ts +53 -0
  358. package/src/tool/todoread.txt +14 -0
  359. package/src/tool/todowrite.txt +167 -0
  360. package/src/tool/tool.ts +89 -0
  361. package/src/tool/truncation.ts +107 -0
  362. package/src/tool/webfetch.ts +206 -0
  363. package/src/tool/webfetch.txt +13 -0
  364. package/src/tool/websearch.ts +150 -0
  365. package/src/tool/websearch.txt +14 -0
  366. package/src/tool/write.ts +84 -0
  367. package/src/tool/write.txt +8 -0
  368. package/src/util/abort.ts +35 -0
  369. package/src/util/archive.ts +16 -0
  370. package/src/util/color.ts +19 -0
  371. package/src/util/context.ts +25 -0
  372. package/src/util/defer.ts +12 -0
  373. package/src/util/eventloop.ts +20 -0
  374. package/src/util/filesystem.ts +189 -0
  375. package/src/util/fn.ts +11 -0
  376. package/src/util/format.ts +20 -0
  377. package/src/util/git.ts +35 -0
  378. package/src/util/glob.ts +34 -0
  379. package/src/util/iife.ts +3 -0
  380. package/src/util/keybind.ts +103 -0
  381. package/src/util/lazy.ts +23 -0
  382. package/src/util/locale.ts +81 -0
  383. package/src/util/lock.ts +98 -0
  384. package/src/util/log.ts +182 -0
  385. package/src/util/process.ts +126 -0
  386. package/src/util/proxied.ts +3 -0
  387. package/src/util/queue.ts +32 -0
  388. package/src/util/rpc.ts +66 -0
  389. package/src/util/scrap.ts +10 -0
  390. package/src/util/signal.ts +12 -0
  391. package/src/util/timeout.ts +14 -0
  392. package/src/util/token.ts +7 -0
  393. package/src/util/wildcard.ts +59 -0
  394. package/src/worktree/index.ts +643 -0
  395. package/sst-env.d.ts +10 -0
  396. package/test/AGENTS.md +81 -0
  397. package/test/acp/agent-interface.test.ts +51 -0
  398. package/test/acp/event-subscription.test.ts +683 -0
  399. package/test/agent/agent.test.ts +689 -0
  400. package/test/bun.test.ts +53 -0
  401. package/test/cli/github-action.test.ts +197 -0
  402. package/test/cli/github-remote.test.ts +80 -0
  403. package/test/cli/import.test.ts +38 -0
  404. package/test/cli/plugin-auth-picker.test.ts +120 -0
  405. package/test/cli/tui/transcript.test.ts +322 -0
  406. package/test/config/agent-color.test.ts +71 -0
  407. package/test/config/config.test.ts +1886 -0
  408. package/test/config/fixtures/empty-frontmatter.md +4 -0
  409. package/test/config/fixtures/frontmatter.md +28 -0
  410. package/test/config/fixtures/markdown-header.md +11 -0
  411. package/test/config/fixtures/no-frontmatter.md +1 -0
  412. package/test/config/fixtures/weird-model-id.md +13 -0
  413. package/test/config/markdown.test.ts +228 -0
  414. package/test/config/tui.test.ts +510 -0
  415. package/test/control-plane/session-proxy-middleware.test.ts +147 -0
  416. package/test/control-plane/sse.test.ts +56 -0
  417. package/test/control-plane/workspace-server-sse.test.ts +65 -0
  418. package/test/control-plane/workspace-sync.test.ts +97 -0
  419. package/test/file/ignore.test.ts +10 -0
  420. package/test/file/index.test.ts +394 -0
  421. package/test/file/path-traversal.test.ts +198 -0
  422. package/test/file/ripgrep.test.ts +39 -0
  423. package/test/file/time.test.ts +361 -0
  424. package/test/fixture/db.ts +11 -0
  425. package/test/fixture/fixture.ts +45 -0
  426. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  427. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  428. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  429. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  430. package/test/fixture/skills/index.json +6 -0
  431. package/test/ide/ide.test.ts +82 -0
  432. package/test/keybind.test.ts +421 -0
  433. package/test/lsp/client.test.ts +95 -0
  434. package/test/mcp/headers.test.ts +153 -0
  435. package/test/mcp/oauth-browser.test.ts +249 -0
  436. package/test/memory/abort-leak.test.ts +136 -0
  437. package/test/patch/patch.test.ts +348 -0
  438. package/test/permission/arity.test.ts +33 -0
  439. package/test/permission/next.test.ts +689 -0
  440. package/test/permission-task.test.ts +319 -0
  441. package/test/plugin/auth-override.test.ts +44 -0
  442. package/test/plugin/codex.test.ts +123 -0
  443. package/test/preload.ts +80 -0
  444. package/test/project/project.test.ts +348 -0
  445. package/test/project/worktree-remove.test.ts +65 -0
  446. package/test/provider/amazon-bedrock.test.ts +446 -0
  447. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  448. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  449. package/test/provider/gitlab-duo.test.ts +262 -0
  450. package/test/provider/provider.test.ts +2220 -0
  451. package/test/provider/transform.test.ts +2353 -0
  452. package/test/pty/pty-output-isolation.test.ts +140 -0
  453. package/test/question/question.test.ts +300 -0
  454. package/test/scheduler.test.ts +73 -0
  455. package/test/server/global-session-list.test.ts +89 -0
  456. package/test/server/session-list.test.ts +90 -0
  457. package/test/server/session-select.test.ts +78 -0
  458. package/test/session/compaction.test.ts +423 -0
  459. package/test/session/instruction.test.ts +170 -0
  460. package/test/session/llm.test.ts +667 -0
  461. package/test/session/message-v2.test.ts +924 -0
  462. package/test/session/prompt.test.ts +211 -0
  463. package/test/session/retry.test.ts +188 -0
  464. package/test/session/revert-compact.test.ts +285 -0
  465. package/test/session/session.test.ts +71 -0
  466. package/test/session/structured-output-integration.test.ts +233 -0
  467. package/test/session/structured-output.test.ts +385 -0
  468. package/test/skill/discovery.test.ts +110 -0
  469. package/test/skill/skill.test.ts +388 -0
  470. package/test/snapshot/snapshot.test.ts +1180 -0
  471. package/test/storage/json-migration.test.ts +846 -0
  472. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  473. package/test/tool/apply_patch.test.ts +566 -0
  474. package/test/tool/bash.test.ts +402 -0
  475. package/test/tool/edit.test.ts +496 -0
  476. package/test/tool/external-directory.test.ts +127 -0
  477. package/test/tool/fixtures/large-image.png +0 -0
  478. package/test/tool/fixtures/models-api.json +38413 -0
  479. package/test/tool/grep.test.ts +110 -0
  480. package/test/tool/question.test.ts +107 -0
  481. package/test/tool/read.test.ts +504 -0
  482. package/test/tool/registry.test.ts +122 -0
  483. package/test/tool/skill.test.ts +112 -0
  484. package/test/tool/truncation.test.ts +160 -0
  485. package/test/tool/webfetch.test.ts +100 -0
  486. package/test/tool/write.test.ts +348 -0
  487. package/test/util/filesystem.test.ts +443 -0
  488. package/test/util/format.test.ts +59 -0
  489. package/test/util/glob.test.ts +164 -0
  490. package/test/util/iife.test.ts +36 -0
  491. package/test/util/lazy.test.ts +50 -0
  492. package/test/util/lock.test.ts +72 -0
  493. package/test/util/process.test.ts +59 -0
  494. package/test/util/timeout.test.ts +21 -0
  495. package/test/util/wildcard.test.ts +90 -0
  496. package/tsconfig.json +16 -0
@@ -0,0 +1,877 @@
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 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 { Database, NotFoundError, eq, and, or, gte, isNull, desc, like, inArray, lt } from "../storage/db"
14
+ import type { SQL } from "../storage/db"
15
+ import { SessionTable, MessageTable, PartTable } from "./session.sql"
16
+ import { ProjectTable } from "../project/project.sql"
17
+ import { Storage } from "@/storage/storage"
18
+ import { Log } from "../util/log"
19
+ import { MessageV2 } from "./message-v2"
20
+ import { Instance } from "../project/instance"
21
+ import { SessionPrompt } from "./prompt"
22
+ import { fn } from "@/util/fn"
23
+ import { Command } from "../command"
24
+ import { Snapshot } from "@/snapshot"
25
+
26
+ import type { Provider } from "@/provider/provider"
27
+ import { PermissionNext } from "@/permission/next"
28
+ import { Global } from "@/global"
29
+ import type { LanguageModelV2Usage } from "@ai-sdk/provider"
30
+ import { iife } from "@/util/iife"
31
+
32
+ export namespace Session {
33
+ const log = Log.create({ service: "session" })
34
+
35
+ const parentTitlePrefix = "New session - "
36
+ const childTitlePrefix = "Child session - "
37
+
38
+ function createDefaultTitle(isChild = false) {
39
+ return (isChild ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString()
40
+ }
41
+
42
+ export function isDefaultTitle(title: string) {
43
+ return new RegExp(
44
+ `^(${parentTitlePrefix}|${childTitlePrefix})\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$`,
45
+ ).test(title)
46
+ }
47
+
48
+ type SessionRow = typeof SessionTable.$inferSelect
49
+
50
+ export function fromRow(row: SessionRow): Info {
51
+ const summary =
52
+ row.summary_additions !== null || row.summary_deletions !== null || row.summary_files !== null
53
+ ? {
54
+ additions: row.summary_additions ?? 0,
55
+ deletions: row.summary_deletions ?? 0,
56
+ files: row.summary_files ?? 0,
57
+ diffs: row.summary_diffs ?? undefined,
58
+ }
59
+ : undefined
60
+ const share = row.share_url ? { url: row.share_url } : undefined
61
+ const revert = row.revert ?? undefined
62
+ return {
63
+ id: row.id,
64
+ slug: row.slug,
65
+ projectID: row.project_id,
66
+ directory: row.directory,
67
+ parentID: row.parent_id ?? undefined,
68
+ title: row.title,
69
+ version: row.version,
70
+ summary,
71
+ share,
72
+ revert,
73
+ permission: row.permission ?? undefined,
74
+ time: {
75
+ created: row.time_created,
76
+ updated: row.time_updated,
77
+ compacting: row.time_compacting ?? undefined,
78
+ archived: row.time_archived ?? undefined,
79
+ },
80
+ }
81
+ }
82
+
83
+ export function toRow(info: Info) {
84
+ return {
85
+ id: info.id,
86
+ project_id: info.projectID,
87
+ parent_id: info.parentID,
88
+ slug: info.slug,
89
+ directory: info.directory,
90
+ title: info.title,
91
+ version: info.version,
92
+ share_url: info.share?.url,
93
+ summary_additions: info.summary?.additions,
94
+ summary_deletions: info.summary?.deletions,
95
+ summary_files: info.summary?.files,
96
+ summary_diffs: info.summary?.diffs,
97
+ revert: info.revert ?? null,
98
+ permission: info.permission,
99
+ time_created: info.time.created,
100
+ time_updated: info.time.updated,
101
+ time_compacting: info.time.compacting,
102
+ time_archived: info.time.archived,
103
+ }
104
+ }
105
+
106
+ function getForkedTitle(title: string): string {
107
+ const match = title.match(/^(.+) \(fork #(\d+)\)$/)
108
+ if (match) {
109
+ const base = match[1]
110
+ const num = parseInt(match[2], 10)
111
+ return `${base} (fork #${num + 1})`
112
+ }
113
+ return `${title} (fork #1)`
114
+ }
115
+
116
+ export const Info = z
117
+ .object({
118
+ id: Identifier.schema("session"),
119
+ slug: z.string(),
120
+ projectID: z.string(),
121
+ directory: z.string(),
122
+ parentID: Identifier.schema("session").optional(),
123
+ summary: z
124
+ .object({
125
+ additions: z.number(),
126
+ deletions: z.number(),
127
+ files: z.number(),
128
+ diffs: Snapshot.FileDiff.array().optional(),
129
+ })
130
+ .optional(),
131
+ share: z
132
+ .object({
133
+ url: z.string(),
134
+ })
135
+ .optional(),
136
+ title: z.string(),
137
+ version: z.string(),
138
+ time: z.object({
139
+ created: z.number(),
140
+ updated: z.number(),
141
+ compacting: z.number().optional(),
142
+ archived: z.number().optional(),
143
+ }),
144
+ permission: PermissionNext.Ruleset.optional(),
145
+ revert: z
146
+ .object({
147
+ messageID: z.string(),
148
+ partID: z.string().optional(),
149
+ snapshot: z.string().optional(),
150
+ diff: z.string().optional(),
151
+ })
152
+ .optional(),
153
+ })
154
+ .meta({
155
+ ref: "Session",
156
+ })
157
+ export type Info = z.output<typeof Info>
158
+
159
+ export const ProjectInfo = z
160
+ .object({
161
+ id: z.string(),
162
+ name: z.string().optional(),
163
+ worktree: z.string(),
164
+ })
165
+ .meta({
166
+ ref: "ProjectSummary",
167
+ })
168
+ export type ProjectInfo = z.output<typeof ProjectInfo>
169
+
170
+ export const GlobalInfo = Info.extend({
171
+ project: ProjectInfo.nullable(),
172
+ }).meta({
173
+ ref: "GlobalSession",
174
+ })
175
+ export type GlobalInfo = z.output<typeof GlobalInfo>
176
+
177
+ export const Event = {
178
+ Created: BusEvent.define(
179
+ "session.created",
180
+ z.object({
181
+ info: Info,
182
+ }),
183
+ ),
184
+ Updated: BusEvent.define(
185
+ "session.updated",
186
+ z.object({
187
+ info: Info,
188
+ }),
189
+ ),
190
+ Deleted: BusEvent.define(
191
+ "session.deleted",
192
+ z.object({
193
+ info: Info,
194
+ }),
195
+ ),
196
+ Diff: BusEvent.define(
197
+ "session.diff",
198
+ z.object({
199
+ sessionID: z.string(),
200
+ diff: Snapshot.FileDiff.array(),
201
+ }),
202
+ ),
203
+ Error: BusEvent.define(
204
+ "session.error",
205
+ z.object({
206
+ sessionID: z.string().optional(),
207
+ error: MessageV2.Assistant.shape.error,
208
+ }),
209
+ ),
210
+ }
211
+
212
+ export const create = fn(
213
+ z
214
+ .object({
215
+ parentID: Identifier.schema("session").optional(),
216
+ title: z.string().optional(),
217
+ permission: Info.shape.permission,
218
+ })
219
+ .optional(),
220
+ async (input) => {
221
+ return createNext({
222
+ parentID: input?.parentID,
223
+ directory: Instance.directory,
224
+ title: input?.title,
225
+ permission: input?.permission,
226
+ })
227
+ },
228
+ )
229
+
230
+ export const fork = fn(
231
+ z.object({
232
+ sessionID: Identifier.schema("session"),
233
+ messageID: Identifier.schema("message").optional(),
234
+ }),
235
+ async (input) => {
236
+ const original = await get(input.sessionID)
237
+ if (!original) throw new Error("session not found")
238
+ const title = getForkedTitle(original.title)
239
+ const session = await createNext({
240
+ directory: Instance.directory,
241
+ title,
242
+ })
243
+ const msgs = await messages({ sessionID: input.sessionID })
244
+ const idMap = new Map<string, string>()
245
+
246
+ for (const msg of msgs) {
247
+ if (input.messageID && msg.info.id >= input.messageID) break
248
+ const newID = Identifier.ascending("message")
249
+ idMap.set(msg.info.id, newID)
250
+
251
+ const parentID = msg.info.role === "assistant" && msg.info.parentID ? idMap.get(msg.info.parentID) : undefined
252
+ const cloned = await updateMessage({
253
+ ...msg.info,
254
+ sessionID: session.id,
255
+ id: newID,
256
+ ...(parentID && { parentID }),
257
+ })
258
+
259
+ for (const part of msg.parts) {
260
+ await updatePart({
261
+ ...part,
262
+ id: Identifier.ascending("part"),
263
+ messageID: cloned.id,
264
+ sessionID: session.id,
265
+ })
266
+ }
267
+ }
268
+ return session
269
+ },
270
+ )
271
+
272
+ export const touch = fn(Identifier.schema("session"), async (sessionID) => {
273
+ const now = Date.now()
274
+ Database.use((db) => {
275
+ const row = db
276
+ .update(SessionTable)
277
+ .set({ time_updated: now })
278
+ .where(eq(SessionTable.id, sessionID))
279
+ .returning()
280
+ .get()
281
+ if (!row) throw new NotFoundError({ message: `Session not found: ${sessionID}` })
282
+ const info = fromRow(row)
283
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
284
+ })
285
+ })
286
+
287
+ export async function createNext(input: {
288
+ id?: string
289
+ title?: string
290
+ parentID?: string
291
+ directory: string
292
+ permission?: PermissionNext.Ruleset
293
+ }) {
294
+ const result: Info = {
295
+ id: Identifier.descending("session", input.id),
296
+ slug: Slug.create(),
297
+ version: Installation.VERSION,
298
+ projectID: Instance.project.id,
299
+ directory: input.directory,
300
+ parentID: input.parentID,
301
+ title: input.title ?? createDefaultTitle(!!input.parentID),
302
+ permission: input.permission,
303
+ time: {
304
+ created: Date.now(),
305
+ updated: Date.now(),
306
+ },
307
+ }
308
+ log.info("created", result)
309
+ Database.use((db) => {
310
+ db.insert(SessionTable).values(toRow(result)).run()
311
+ Database.effect(() =>
312
+ Bus.publish(Event.Created, {
313
+ info: result,
314
+ }),
315
+ )
316
+ })
317
+ const cfg = await Config.get()
318
+ if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
319
+ share(result.id).catch(() => {
320
+ // Silently ignore sharing errors during session creation
321
+ })
322
+ Bus.publish(Event.Updated, {
323
+ info: result,
324
+ })
325
+ return result
326
+ }
327
+
328
+ export function plan(input: { slug: string; time: { created: number } }) {
329
+ const base = Instance.project.vcs
330
+ ? path.join(Instance.worktree, ".opencode", "plans")
331
+ : path.join(Global.Path.data, "plans")
332
+ return path.join(base, [input.time.created, input.slug].join("-") + ".md")
333
+ }
334
+
335
+ export const get = fn(Identifier.schema("session"), async (id) => {
336
+ const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
337
+ if (!row) throw new NotFoundError({ message: `Session not found: ${id}` })
338
+ return fromRow(row)
339
+ })
340
+
341
+ export const share = fn(Identifier.schema("session"), async (id) => {
342
+ const cfg = await Config.get()
343
+ if (cfg.share === "disabled") {
344
+ throw new Error("Sharing is disabled in configuration")
345
+ }
346
+ const { ShareNext } = await import("@/share/share-next")
347
+ const share = await ShareNext.create(id)
348
+ Database.use((db) => {
349
+ const row = db.update(SessionTable).set({ share_url: share.url }).where(eq(SessionTable.id, id)).returning().get()
350
+ if (!row) throw new NotFoundError({ message: `Session not found: ${id}` })
351
+ const info = fromRow(row)
352
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
353
+ })
354
+ return share
355
+ })
356
+
357
+ export const unshare = fn(Identifier.schema("session"), async (id) => {
358
+ // Use ShareNext to remove the share (same as share function uses ShareNext to create)
359
+ const { ShareNext } = await import("@/share/share-next")
360
+ await ShareNext.remove(id)
361
+ Database.use((db) => {
362
+ const row = db.update(SessionTable).set({ share_url: null }).where(eq(SessionTable.id, id)).returning().get()
363
+ if (!row) throw new NotFoundError({ message: `Session not found: ${id}` })
364
+ const info = fromRow(row)
365
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
366
+ })
367
+ })
368
+
369
+ export const setTitle = fn(
370
+ z.object({
371
+ sessionID: Identifier.schema("session"),
372
+ title: z.string(),
373
+ }),
374
+ async (input) => {
375
+ return Database.use((db) => {
376
+ const row = db
377
+ .update(SessionTable)
378
+ .set({ title: input.title })
379
+ .where(eq(SessionTable.id, input.sessionID))
380
+ .returning()
381
+ .get()
382
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
383
+ const info = fromRow(row)
384
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
385
+ return info
386
+ })
387
+ },
388
+ )
389
+
390
+ export const setArchived = fn(
391
+ z.object({
392
+ sessionID: Identifier.schema("session"),
393
+ time: z.number().optional(),
394
+ }),
395
+ async (input) => {
396
+ return Database.use((db) => {
397
+ const row = db
398
+ .update(SessionTable)
399
+ .set({ time_archived: input.time })
400
+ .where(eq(SessionTable.id, input.sessionID))
401
+ .returning()
402
+ .get()
403
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
404
+ const info = fromRow(row)
405
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
406
+ return info
407
+ })
408
+ },
409
+ )
410
+
411
+ export const setPermission = fn(
412
+ z.object({
413
+ sessionID: Identifier.schema("session"),
414
+ permission: PermissionNext.Ruleset,
415
+ }),
416
+ async (input) => {
417
+ return Database.use((db) => {
418
+ const row = db
419
+ .update(SessionTable)
420
+ .set({ permission: input.permission, time_updated: Date.now() })
421
+ .where(eq(SessionTable.id, input.sessionID))
422
+ .returning()
423
+ .get()
424
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
425
+ const info = fromRow(row)
426
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
427
+ return info
428
+ })
429
+ },
430
+ )
431
+
432
+ export const setRevert = fn(
433
+ z.object({
434
+ sessionID: Identifier.schema("session"),
435
+ revert: Info.shape.revert,
436
+ summary: Info.shape.summary,
437
+ }),
438
+ async (input) => {
439
+ return Database.use((db) => {
440
+ const row = db
441
+ .update(SessionTable)
442
+ .set({
443
+ revert: input.revert ?? null,
444
+ summary_additions: input.summary?.additions,
445
+ summary_deletions: input.summary?.deletions,
446
+ summary_files: input.summary?.files,
447
+ time_updated: Date.now(),
448
+ })
449
+ .where(eq(SessionTable.id, input.sessionID))
450
+ .returning()
451
+ .get()
452
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
453
+ const info = fromRow(row)
454
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
455
+ return info
456
+ })
457
+ },
458
+ )
459
+
460
+ export const clearRevert = fn(Identifier.schema("session"), async (sessionID) => {
461
+ return Database.use((db) => {
462
+ const row = db
463
+ .update(SessionTable)
464
+ .set({
465
+ revert: null,
466
+ time_updated: Date.now(),
467
+ })
468
+ .where(eq(SessionTable.id, sessionID))
469
+ .returning()
470
+ .get()
471
+ if (!row) throw new NotFoundError({ message: `Session not found: ${sessionID}` })
472
+ const info = fromRow(row)
473
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
474
+ return info
475
+ })
476
+ })
477
+
478
+ export const setSummary = fn(
479
+ z.object({
480
+ sessionID: Identifier.schema("session"),
481
+ summary: Info.shape.summary,
482
+ }),
483
+ async (input) => {
484
+ return Database.use((db) => {
485
+ const row = db
486
+ .update(SessionTable)
487
+ .set({
488
+ summary_additions: input.summary?.additions,
489
+ summary_deletions: input.summary?.deletions,
490
+ summary_files: input.summary?.files,
491
+ time_updated: Date.now(),
492
+ })
493
+ .where(eq(SessionTable.id, input.sessionID))
494
+ .returning()
495
+ .get()
496
+ if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` })
497
+ const info = fromRow(row)
498
+ Database.effect(() => Bus.publish(Event.Updated, { info }))
499
+ return info
500
+ })
501
+ },
502
+ )
503
+
504
+ export const diff = fn(Identifier.schema("session"), async (sessionID) => {
505
+ try {
506
+ return await Storage.read<Snapshot.FileDiff[]>(["session_diff", sessionID])
507
+ } catch {
508
+ return []
509
+ }
510
+ })
511
+
512
+ export const messages = fn(
513
+ z.object({
514
+ sessionID: Identifier.schema("session"),
515
+ limit: z.number().optional(),
516
+ }),
517
+ async (input) => {
518
+ const result = [] as MessageV2.WithParts[]
519
+ for await (const msg of MessageV2.stream(input.sessionID)) {
520
+ if (input.limit && result.length >= input.limit) break
521
+ result.push(msg)
522
+ }
523
+ result.reverse()
524
+ return result
525
+ },
526
+ )
527
+
528
+ export function* list(input?: {
529
+ directory?: string
530
+ roots?: boolean
531
+ start?: number
532
+ search?: string
533
+ limit?: number
534
+ }) {
535
+ const project = Instance.project
536
+ const conditions = [eq(SessionTable.project_id, project.id)]
537
+
538
+ if (input?.directory) {
539
+ conditions.push(eq(SessionTable.directory, input.directory))
540
+ }
541
+ if (input?.roots) {
542
+ conditions.push(isNull(SessionTable.parent_id))
543
+ }
544
+ if (input?.start) {
545
+ conditions.push(gte(SessionTable.time_updated, input.start))
546
+ }
547
+ if (input?.search) {
548
+ conditions.push(like(SessionTable.title, `%${input.search}%`))
549
+ }
550
+
551
+ const limit = input?.limit ?? 100
552
+
553
+ const rows = Database.use((db) =>
554
+ db
555
+ .select()
556
+ .from(SessionTable)
557
+ .where(and(...conditions))
558
+ .orderBy(desc(SessionTable.time_updated))
559
+ .limit(limit)
560
+ .all(),
561
+ )
562
+ for (const row of rows) {
563
+ yield fromRow(row)
564
+ }
565
+ }
566
+
567
+ export function* listGlobal(input?: {
568
+ directory?: string
569
+ roots?: boolean
570
+ start?: number
571
+ cursor?: number
572
+ search?: string
573
+ limit?: number
574
+ archived?: boolean
575
+ }) {
576
+ const conditions: SQL[] = []
577
+
578
+ if (input?.directory) {
579
+ conditions.push(eq(SessionTable.directory, input.directory))
580
+ }
581
+ if (input?.roots) {
582
+ conditions.push(isNull(SessionTable.parent_id))
583
+ }
584
+ if (input?.start) {
585
+ conditions.push(gte(SessionTable.time_updated, input.start))
586
+ }
587
+ if (input?.cursor) {
588
+ conditions.push(lt(SessionTable.time_updated, input.cursor))
589
+ }
590
+ if (input?.search) {
591
+ conditions.push(like(SessionTable.title, `%${input.search}%`))
592
+ }
593
+ if (!input?.archived) {
594
+ conditions.push(isNull(SessionTable.time_archived))
595
+ }
596
+
597
+ const limit = input?.limit ?? 100
598
+
599
+ const rows = Database.use((db) => {
600
+ const query =
601
+ conditions.length > 0
602
+ ? db
603
+ .select()
604
+ .from(SessionTable)
605
+ .where(and(...conditions))
606
+ : db.select().from(SessionTable)
607
+ return query.orderBy(desc(SessionTable.time_updated), desc(SessionTable.id)).limit(limit).all()
608
+ })
609
+
610
+ const ids = [...new Set(rows.map((row) => row.project_id))]
611
+ const projects = new Map<string, ProjectInfo>()
612
+
613
+ if (ids.length > 0) {
614
+ const items = Database.use((db) =>
615
+ db
616
+ .select({ id: ProjectTable.id, name: ProjectTable.name, worktree: ProjectTable.worktree })
617
+ .from(ProjectTable)
618
+ .where(inArray(ProjectTable.id, ids))
619
+ .all(),
620
+ )
621
+ for (const item of items) {
622
+ projects.set(item.id, {
623
+ id: item.id,
624
+ name: item.name ?? undefined,
625
+ worktree: item.worktree,
626
+ })
627
+ }
628
+ }
629
+
630
+ for (const row of rows) {
631
+ const project = projects.get(row.project_id) ?? null
632
+ yield { ...fromRow(row), project }
633
+ }
634
+ }
635
+
636
+ export const children = fn(Identifier.schema("session"), async (parentID) => {
637
+ const project = Instance.project
638
+ const rows = Database.use((db) =>
639
+ db
640
+ .select()
641
+ .from(SessionTable)
642
+ .where(and(eq(SessionTable.project_id, project.id), eq(SessionTable.parent_id, parentID)))
643
+ .all(),
644
+ )
645
+ return rows.map(fromRow)
646
+ })
647
+
648
+ export const remove = fn(Identifier.schema("session"), async (sessionID) => {
649
+ const project = Instance.project
650
+ try {
651
+ const session = await get(sessionID)
652
+ for (const child of await children(sessionID)) {
653
+ await remove(child.id)
654
+ }
655
+ await unshare(sessionID).catch(() => {})
656
+ // CASCADE delete handles messages and parts automatically
657
+ Database.use((db) => {
658
+ db.delete(SessionTable).where(eq(SessionTable.id, sessionID)).run()
659
+ Database.effect(() =>
660
+ Bus.publish(Event.Deleted, {
661
+ info: session,
662
+ }),
663
+ )
664
+ })
665
+ } catch (e) {
666
+ log.error(e)
667
+ }
668
+ })
669
+
670
+ export const updateMessage = fn(MessageV2.Info, async (msg) => {
671
+ const time_created = msg.time.created
672
+ const { id, sessionID, ...data } = msg
673
+ Database.use((db) => {
674
+ db.insert(MessageTable)
675
+ .values({
676
+ id,
677
+ session_id: sessionID,
678
+ time_created,
679
+ data,
680
+ })
681
+ .onConflictDoUpdate({ target: MessageTable.id, set: { data } })
682
+ .run()
683
+ Database.effect(() =>
684
+ Bus.publish(MessageV2.Event.Updated, {
685
+ info: msg,
686
+ }),
687
+ )
688
+ })
689
+ return msg
690
+ })
691
+
692
+ export const removeMessage = fn(
693
+ z.object({
694
+ sessionID: Identifier.schema("session"),
695
+ messageID: Identifier.schema("message"),
696
+ }),
697
+ async (input) => {
698
+ // CASCADE delete handles parts automatically
699
+ Database.use((db) => {
700
+ db.delete(MessageTable)
701
+ .where(and(eq(MessageTable.id, input.messageID), eq(MessageTable.session_id, input.sessionID)))
702
+ .run()
703
+ Database.effect(() =>
704
+ Bus.publish(MessageV2.Event.Removed, {
705
+ sessionID: input.sessionID,
706
+ messageID: input.messageID,
707
+ }),
708
+ )
709
+ })
710
+ return input.messageID
711
+ },
712
+ )
713
+
714
+ export const removePart = fn(
715
+ z.object({
716
+ sessionID: Identifier.schema("session"),
717
+ messageID: Identifier.schema("message"),
718
+ partID: Identifier.schema("part"),
719
+ }),
720
+ async (input) => {
721
+ Database.use((db) => {
722
+ db.delete(PartTable)
723
+ .where(and(eq(PartTable.id, input.partID), eq(PartTable.session_id, input.sessionID)))
724
+ .run()
725
+ Database.effect(() =>
726
+ Bus.publish(MessageV2.Event.PartRemoved, {
727
+ sessionID: input.sessionID,
728
+ messageID: input.messageID,
729
+ partID: input.partID,
730
+ }),
731
+ )
732
+ })
733
+ return input.partID
734
+ },
735
+ )
736
+
737
+ const UpdatePartInput = MessageV2.Part
738
+
739
+ export const updatePart = fn(UpdatePartInput, async (part) => {
740
+ const { id, messageID, sessionID, ...data } = part
741
+ const time = Date.now()
742
+ Database.use((db) => {
743
+ db.insert(PartTable)
744
+ .values({
745
+ id,
746
+ message_id: messageID,
747
+ session_id: sessionID,
748
+ time_created: time,
749
+ data,
750
+ })
751
+ .onConflictDoUpdate({ target: PartTable.id, set: { data } })
752
+ .run()
753
+ Database.effect(() =>
754
+ Bus.publish(MessageV2.Event.PartUpdated, {
755
+ part,
756
+ }),
757
+ )
758
+ })
759
+ return part
760
+ })
761
+
762
+ export const updatePartDelta = fn(
763
+ z.object({
764
+ sessionID: z.string(),
765
+ messageID: z.string(),
766
+ partID: z.string(),
767
+ field: z.string(),
768
+ delta: z.string(),
769
+ }),
770
+ async (input) => {
771
+ Bus.publish(MessageV2.Event.PartDelta, input)
772
+ },
773
+ )
774
+
775
+ export const getUsage = fn(
776
+ z.object({
777
+ model: z.custom<Provider.Model>(),
778
+ usage: z.custom<LanguageModelV2Usage>(),
779
+ metadata: z.custom<ProviderMetadata>().optional(),
780
+ }),
781
+ (input) => {
782
+ const safe = (value: number) => {
783
+ if (!Number.isFinite(value)) return 0
784
+ return value
785
+ }
786
+ const inputTokens = safe(input.usage.inputTokens ?? 0)
787
+ const outputTokens = safe(input.usage.outputTokens ?? 0)
788
+ const reasoningTokens = safe(input.usage.reasoningTokens ?? 0)
789
+
790
+ const cacheReadInputTokens = safe(input.usage.cachedInputTokens ?? 0)
791
+ const cacheWriteInputTokens = safe(
792
+ (input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
793
+ // @ts-expect-error
794
+ input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
795
+ // @ts-expect-error
796
+ input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
797
+ 0) as number,
798
+ )
799
+
800
+ // OpenRouter provides inputTokens as the total count of input tokens (including cached).
801
+ // AFAIK other providers (OpenRouter/OpenAI/Gemini etc.) do it the same way e.g. vercel/ai#8794 (comment)
802
+ // Anthropic does it differently though - inputTokens doesn't include cached tokens.
803
+ // It looks like OpenCode's cost calculation assumes all providers return inputTokens the same way Anthropic does (I'm guessing getUsage logic was originally implemented with anthropic), so it's causing incorrect cost calculation for OpenRouter and others.
804
+ const excludesCachedTokens = !!(input.metadata?.["anthropic"] || input.metadata?.["bedrock"])
805
+ const adjustedInputTokens = safe(
806
+ excludesCachedTokens ? inputTokens : inputTokens - cacheReadInputTokens - cacheWriteInputTokens,
807
+ )
808
+
809
+ const total = iife(() => {
810
+ // Anthropic doesn't provide total_tokens, also ai sdk will vastly undercount if we
811
+ // don't compute from components
812
+ if (
813
+ input.model.api.npm === "@ai-sdk/anthropic" ||
814
+ input.model.api.npm === "@ai-sdk/amazon-bedrock" ||
815
+ input.model.api.npm === "@ai-sdk/google-vertex/anthropic"
816
+ ) {
817
+ return adjustedInputTokens + outputTokens + cacheReadInputTokens + cacheWriteInputTokens
818
+ }
819
+ return input.usage.totalTokens
820
+ })
821
+
822
+ const tokens = {
823
+ total,
824
+ input: adjustedInputTokens,
825
+ output: outputTokens,
826
+ reasoning: reasoningTokens,
827
+ cache: {
828
+ write: cacheWriteInputTokens,
829
+ read: cacheReadInputTokens,
830
+ },
831
+ }
832
+
833
+ const costInfo =
834
+ input.model.cost?.experimentalOver200K && tokens.input + tokens.cache.read > 200_000
835
+ ? input.model.cost.experimentalOver200K
836
+ : input.model.cost
837
+ return {
838
+ cost: safe(
839
+ new Decimal(0)
840
+ .add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000))
841
+ .add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000))
842
+ .add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000))
843
+ .add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000))
844
+ // TODO: update models.dev to have better pricing model, for now:
845
+ // charge reasoning tokens at the same rate as output tokens
846
+ .add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000))
847
+ .toNumber(),
848
+ ),
849
+ tokens,
850
+ }
851
+ },
852
+ )
853
+
854
+ export class BusyError extends Error {
855
+ constructor(public readonly sessionID: string) {
856
+ super(`Session ${sessionID} is busy`)
857
+ }
858
+ }
859
+
860
+ export const initialize = fn(
861
+ z.object({
862
+ sessionID: Identifier.schema("session"),
863
+ modelID: z.string(),
864
+ providerID: z.string(),
865
+ messageID: Identifier.schema("message"),
866
+ }),
867
+ async (input) => {
868
+ await SessionPrompt.command({
869
+ sessionID: input.sessionID,
870
+ messageID: input.messageID,
871
+ model: input.providerID + "/" + input.modelID,
872
+ command: Command.Default.INIT,
873
+ arguments: "",
874
+ })
875
+ },
876
+ )
877
+ }