@vantran-se/openchamber-web 1.24.3 → 2.0.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 (367) hide show
  1. package/README.md +2 -8
  2. package/bin/lib/commands-connect-url.js +3 -8
  3. package/bin/lib/commands-tunnel.js +3 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-BjJHrzmU.js → CodeMirrorCommentWidgets-ntXIbhV2.js} +1 -1
  5. package/dist/assets/DiffView-CjxUovbM.js +4 -0
  6. package/dist/assets/FilesView-DFWD0RQR.js +17 -0
  7. package/dist/assets/{GitView-CDOJy5Ay.js → GitView-BWa7e5vw.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-D3S0Rjic.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
  10. package/dist/assets/{JsonTreeView-D0C9nU7K.js → JsonTreeView-3sGwqexh.js} +1 -1
  11. package/dist/assets/LinearIssuesView-C6dpIdjw.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-Q7YR_tl4.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-DxDV56bL.js +12 -0
  14. package/dist/assets/OnboardingScreen-rK4Stg__.js +1 -0
  15. package/dist/assets/{PatchDiff-DnEhjXcC.js → PatchDiff-DS-IMU1j.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-DyxZVVVR.js → PierreDiffViewer-DW23KWKZ.js} +1 -1
  17. package/dist/assets/PlanView-BCk_VobP.js +5 -0
  18. package/dist/assets/PluginPane-BPP1dbck.js +3 -0
  19. package/dist/assets/SettingsView-kMmrpn43.js +29 -0
  20. package/dist/assets/SettingsWindow-BpvD6IYQ.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-BLttSS1X.js +1 -0
  22. package/dist/assets/ToolOutputDialog-CtqDqbyr.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-5FcKdyRy.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-0pyr_mHY.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
  25. package/dist/assets/{WalkthroughView-yNj4nOP5.js → WalkthroughView-lap5btCF.js} +2 -2
  26. package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
  27. package/dist/assets/{commitAndPush-DvJKa3Ks.js → commitAndPush-CwGJfJG-.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DxTBMuu-.js → computeVirtualFileMetrics-DW5jjX4n.js} +1 -1
  29. package/dist/assets/de-CndEg67T.js +10 -0
  30. package/dist/assets/es-DglvPlaC.js +16 -0
  31. package/dist/assets/fr-DejPCpbS.js +14 -0
  32. package/dist/assets/index-C2r64QfD.js +1 -0
  33. package/dist/assets/index-CLcM8HKx.css +1 -0
  34. package/dist/assets/index-CvLgnuNI.js +41 -0
  35. package/dist/assets/{isDiffPlainText-D7FkBMP6.js → isDiffPlainText-D47a6hMP.js} +1 -1
  36. package/dist/assets/{isFilePlainText-CL2vsrIZ.js → isFilePlainText-B-mGgeKl.js} +1 -1
  37. package/dist/assets/ja-CVpQawCd.js +16 -0
  38. package/dist/assets/ko-Bw446h9j.js +16 -0
  39. package/dist/assets/main-Bz4WWXbr.js +2 -0
  40. package/dist/assets/main-oreqDsOV.js +896 -0
  41. package/dist/assets/{markdownCore-Dh7WEsdM.js → markdownCore-D2e5sp9y.js} +4 -4
  42. package/dist/assets/miniChat-D1v2gsBI.js +2 -0
  43. package/dist/assets/mobile-DBj3lCf2.js +2 -0
  44. package/dist/assets/{patchFileDiff-Bsba8KFp.js → patchFileDiff-BpeUOV91.js} +3 -3
  45. package/dist/assets/pl-BYNlPdhm.js +16 -0
  46. package/dist/assets/pt-BR-CL-Yfv9H.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-DVtfEB2z.js +2 -0
  48. package/dist/assets/renderMobileApp-DFp9-xbG.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-CsIpUep1.js +27 -0
  50. package/dist/assets/scheduledTasksApi-B5OHVpPM.js +1 -0
  51. package/dist/assets/{shikiHighlight-8_3csmr6.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-CHo2fCgW.js → textMateThemeFromAppTheme-DGF2kG7g.js} +1 -1
  53. package/dist/assets/tr-DzmbxLy-.js +7 -0
  54. package/dist/assets/uk-C7vQnwro.js +16 -0
  55. package/dist/assets/useAppFontEffects-Ci3FEaPf.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-DkQ_gqpn.js → useInlineCommentController-CuUuakrt.js} +2 -2
  57. package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-CdzUmUyZ.js → vendor-codemirror-language-data-CQa7_1cr.js} +2 -2
  59. package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
  60. package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
  61. package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
  62. package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
  63. package/dist/assets/{web-components-B8IcVQAA.js → web-components-B6tKQyJN.js} +2 -2
  64. package/dist/assets/zh-CN-Cl0srS-z.js +16 -0
  65. package/dist/assets/zh-TW-DtII5bDb.js +16 -0
  66. package/dist/index.html +13 -7
  67. package/dist/mini-chat.html +5 -5
  68. package/dist/mobile.html +5 -5
  69. package/package.json +6 -5
  70. package/server/index.d.ts +1 -0
  71. package/server/index.js +246 -91
  72. package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
  73. package/server/lib/agent-tool/runtime.js +135 -54
  74. package/server/lib/agent-tool/runtime.test.js +155 -87
  75. package/server/lib/browser-control/DOCUMENTATION.md +37 -2
  76. package/server/lib/browser-control/provider.js +217 -0
  77. package/server/lib/browser-control/provider.test.js +251 -0
  78. package/server/lib/context-obligatory/runtime.js +61 -50
  79. package/server/lib/context-obligatory/runtime.test.js +62 -162
  80. package/server/lib/event-stream/DOCUMENTATION.md +12 -1
  81. package/server/lib/event-stream/delta-coalescer.js +0 -0
  82. package/server/lib/event-stream/delta-coalescer.test.js +68 -57
  83. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  84. package/server/lib/event-stream/global-hub.js +10 -1
  85. package/server/lib/event-stream/global-hub.test.js +27 -26
  86. package/server/lib/event-stream/index.js +6 -0
  87. package/server/lib/event-stream/protocol.js +26 -19
  88. package/server/lib/event-stream/protocol.test.js +18 -7
  89. package/server/lib/event-stream/rebind.test.js +5 -5
  90. package/server/lib/event-stream/runtime.test.js +17 -15
  91. package/server/lib/event-stream/translate-v2.js +313 -0
  92. package/server/lib/event-stream/translate-v2.test.js +209 -0
  93. package/server/lib/event-stream/upstream-reader.test.js +16 -16
  94. package/server/lib/fs/DOCUMENTATION.md +4 -2
  95. package/server/lib/fs/byte-range.js +35 -0
  96. package/server/lib/fs/byte-range.test.js +31 -0
  97. package/server/lib/fs/routes.js +47 -42
  98. package/server/lib/fs/routes.test.js +123 -18
  99. package/server/lib/git/service.js +12 -0
  100. package/server/lib/github/DOCUMENTATION.md +1 -1
  101. package/server/lib/github/pr-status.js +22 -2
  102. package/server/lib/github/pr-status.test.js +27 -14
  103. package/server/lib/guests/DOCUMENTATION.md +13 -0
  104. package/server/lib/guests/catalog.js +12 -0
  105. package/server/lib/guests/catalog.test.js +33 -0
  106. package/server/lib/guests/routes.js +10 -1
  107. package/server/lib/guests/service.js +192 -14
  108. package/server/lib/guests/service.test.js +201 -1
  109. package/server/lib/guests/surface.js +630 -0
  110. package/server/lib/guests/surface.test.js +486 -0
  111. package/server/lib/markdown-image-grants/routes.js +18 -5
  112. package/server/lib/markdown-image-grants/routes.test.js +9 -6
  113. package/server/lib/message-queue/DOCUMENTATION.md +33 -22
  114. package/server/lib/message-queue/runtime.js +155 -100
  115. package/server/lib/message-queue/runtime.test.js +168 -82
  116. package/server/lib/notifications/DOCUMENTATION.md +28 -0
  117. package/server/lib/notifications/control-stream.http.test.js +68 -0
  118. package/server/lib/notifications/emitter-runtime.js +15 -0
  119. package/server/lib/notifications/emitter-runtime.test.js +32 -0
  120. package/server/lib/notifications/push-runtime.js +5 -1
  121. package/server/lib/notifications/push-runtime.test.js +2 -2
  122. package/server/lib/notifications/routes.js +1 -1
  123. package/server/lib/notifications/runtime.js +115 -26
  124. package/server/lib/notifications/template-runtime.js +13 -15
  125. package/server/lib/notifications/template-runtime.test.js +16 -9
  126. package/server/lib/notifications/trigger-runtime.test.js +187 -0
  127. package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
  128. package/server/lib/openchamber-control/actions.js +3 -2
  129. package/server/lib/openchamber-control/error.js +1 -1
  130. package/server/lib/openchamber-control/file-open.js +59 -0
  131. package/server/lib/openchamber-control/file-open.test.js +57 -0
  132. package/server/lib/openchamber-control/routes.test.js +12 -0
  133. package/server/lib/openchamber-control/service.js +102 -49
  134. package/server/lib/openchamber-control/service.test.js +84 -44
  135. package/server/lib/openchamber-sessions/archive-store.js +224 -0
  136. package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
  137. package/server/lib/openchamber-sessions/opencode-client.js +22 -0
  138. package/server/lib/openchamber-sessions/routes.js +398 -328
  139. package/server/lib/openchamber-sessions/routes.test.js +777 -740
  140. package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
  141. package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
  142. package/server/lib/opencode/DOCUMENTATION.md +449 -29
  143. package/server/lib/opencode/agents.js +233 -327
  144. package/server/lib/opencode/auth.js +41 -44
  145. package/server/lib/opencode/auth.test.js +92 -0
  146. package/server/lib/opencode/cli-upgrade.d.ts +4 -0
  147. package/server/lib/opencode/cli-upgrade.js +18 -0
  148. package/server/lib/opencode/cli-upgrade.test.js +36 -0
  149. package/server/lib/opencode/commands.js +164 -145
  150. package/server/lib/opencode/compatibility.d.ts +16 -0
  151. package/server/lib/opencode/compatibility.js +88 -0
  152. package/server/lib/opencode/compatibility.test.js +62 -0
  153. package/server/lib/opencode/config-entity-routes.js +58 -29
  154. package/server/lib/opencode/config-entity-v2.test.js +290 -0
  155. package/server/lib/opencode/config-mutation-response.js +17 -7
  156. package/server/lib/opencode/config-mutation-response.test.js +4 -7
  157. package/server/lib/opencode/config-paths.test.js +2 -2
  158. package/server/lib/opencode/config-v2.d.ts +183 -0
  159. package/server/lib/opencode/config-v2.js +801 -0
  160. package/server/lib/opencode/config-v2.test.js +466 -0
  161. package/server/lib/opencode/credential-db.js +144 -0
  162. package/server/lib/opencode/credential-db.test.js +104 -0
  163. package/server/lib/opencode/env-runtime.js +48 -9
  164. package/server/lib/opencode/env-runtime.test.js +45 -0
  165. package/server/lib/opencode/feature-routes-runtime.js +15 -5
  166. package/server/lib/opencode/lifecycle-process.test.js +7 -4
  167. package/server/lib/opencode/lifecycle.js +85 -30
  168. package/server/lib/opencode/lifecycle.test.js +80 -8
  169. package/server/lib/opencode/login-shell-env.js +27 -0
  170. package/server/lib/opencode/managed-config-file.js +119 -0
  171. package/server/lib/opencode/managed-config-file.test.js +152 -0
  172. package/server/lib/opencode/managed-plugin-config.js +33 -13
  173. package/server/lib/opencode/mcp.js +43 -149
  174. package/server/lib/opencode/network-runtime.js +6 -7
  175. package/server/lib/opencode/openchamber-routes.test.js +2 -5
  176. package/server/lib/opencode/plugin-routes.js +3 -3
  177. package/server/lib/opencode/plugin-routes.test.js +6 -24
  178. package/server/lib/opencode/plugins.js +91 -40
  179. package/server/lib/opencode/plugins.test.js +28 -15
  180. package/server/lib/opencode/providers.js +63 -83
  181. package/server/lib/opencode/providers.test.js +128 -59
  182. package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
  183. package/server/lib/opencode/proxy.js +179 -129
  184. package/server/lib/opencode/pwa-manifest-routes.js +4 -2
  185. package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
  186. package/server/lib/opencode/response-envelope.js +14 -0
  187. package/server/lib/opencode/response-envelope.test.js +23 -0
  188. package/server/lib/opencode/routes-upgrade.test.js +73 -116
  189. package/server/lib/opencode/routes.js +121 -428
  190. package/server/lib/opencode/server-utils-runtime.js +10 -4
  191. package/server/lib/opencode/session-activity.js +78 -0
  192. package/server/lib/opencode/session-runtime.js +21 -10
  193. package/server/lib/opencode/session-runtime.test.js +9 -9
  194. package/server/lib/opencode/settings-helpers.js +9 -9
  195. package/server/lib/opencode/settings-helpers.test.js +7 -12
  196. package/server/lib/opencode/settings-registry.json +6 -8
  197. package/server/lib/opencode/settings-runtime.js +18 -1
  198. package/server/lib/opencode/settings-runtime.test.js +31 -2
  199. package/server/lib/opencode/shared.js +54 -22
  200. package/server/lib/opencode/shared.test.js +8 -4
  201. package/server/lib/opencode/shutdown-runtime.js +54 -7
  202. package/server/lib/opencode/shutdown-runtime.test.js +224 -30
  203. package/server/lib/opencode/skill-routes.js +23 -24
  204. package/server/lib/opencode/skills.js +7 -5
  205. package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
  206. package/server/lib/opencode/upgrade-capability.test.js +1 -1
  207. package/server/lib/opencode/v1-migration-topup.js +288 -0
  208. package/server/lib/opencode/v1-migration-topup.test.js +266 -0
  209. package/server/lib/opencode/v2-install.d.ts +8 -0
  210. package/server/lib/opencode/v2-install.js +158 -0
  211. package/server/lib/opencode/v2-install.test.js +124 -0
  212. package/server/lib/opencode/watcher.js +10 -3
  213. package/server/lib/opencode/watcher.test.js +17 -21
  214. package/server/lib/opencode/websearch-config.js +66 -0
  215. package/server/lib/package-manager.js +55 -68
  216. package/server/lib/package-manager.test.js +241 -75
  217. package/server/lib/permission-auto-accept/runtime.js +38 -22
  218. package/server/lib/permission-auto-accept/runtime.test.js +39 -17
  219. package/server/lib/quota/DOCUMENTATION.md +4 -0
  220. package/server/lib/quota/providers/xai.js +4 -4
  221. package/server/lib/relay/tunnel-host.js +4 -1
  222. package/server/lib/relay/tunnel-host.test.js +7 -0
  223. package/server/lib/routing/DOCUMENTATION.md +52 -21
  224. package/server/lib/routing/defaults.js +23 -6
  225. package/server/lib/routing/jev.js +17 -7
  226. package/server/lib/routing/routes.http.test.js +68 -33
  227. package/server/lib/routing/routes.js +65 -22
  228. package/server/lib/routing/runtime.js +145 -48
  229. package/server/lib/routing/runtime.test.js +79 -55
  230. package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
  231. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
  232. package/server/lib/scheduled-tasks/runtime.js +81 -74
  233. package/server/lib/scheduled-tasks/runtime.test.js +53 -1
  234. package/server/lib/session-assist/DOCUMENTATION.md +8 -3
  235. package/server/lib/session-assist/context.js +122 -46
  236. package/server/lib/session-assist/context.test.js +117 -49
  237. package/server/lib/session-assist/runtime.js +52 -22
  238. package/server/lib/session-assist/runtime.test.js +145 -173
  239. package/server/lib/session-goal/DOCUMENTATION.md +23 -15
  240. package/server/lib/session-goal/create.js +9 -12
  241. package/server/lib/session-goal/create.test.js +27 -28
  242. package/server/lib/session-goal/runtime.js +164 -77
  243. package/server/lib/session-goal/runtime.test.js +271 -519
  244. package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
  245. package/server/lib/session-knowledge/runtime.js +59 -41
  246. package/server/lib/session-knowledge/runtime.test.js +90 -20
  247. package/server/lib/small-model/DOCUMENTATION.md +154 -208
  248. package/server/lib/small-model/client.js +113 -0
  249. package/server/lib/small-model/index.js +296 -158
  250. package/server/lib/small-model/index.test.js +423 -256
  251. package/server/lib/small-model/routes.js +1 -1
  252. package/server/lib/spaces/DOCUMENTATION.md +807 -0
  253. package/server/lib/spaces/code-in-bait.js +185 -0
  254. package/server/lib/spaces/code-in.js +641 -0
  255. package/server/lib/spaces/code-in.test.js +1048 -0
  256. package/server/lib/spaces/errors.js +8 -0
  257. package/server/lib/spaces/exec-http.js +94 -0
  258. package/server/lib/spaces/gatekeeper-channel.js +202 -0
  259. package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
  260. package/server/lib/spaces/gatekeeper-program.cjs +914 -0
  261. package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
  262. package/server/lib/spaces/hardening.js +457 -0
  263. package/server/lib/spaces/hardening.test.js +368 -0
  264. package/server/lib/spaces/host-git.js +127 -0
  265. package/server/lib/spaces/host-git.test.js +137 -0
  266. package/server/lib/spaces/labels.js +234 -0
  267. package/server/lib/spaces/labels.test.js +219 -0
  268. package/server/lib/spaces/layout.js +184 -0
  269. package/server/lib/spaces/layout.test.js +30 -0
  270. package/server/lib/spaces/manager.js +82 -0
  271. package/server/lib/spaces/manager.test.js +114 -0
  272. package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
  273. package/server/lib/spaces/places/contract-suite.js +156 -0
  274. package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
  275. package/server/lib/spaces/places/contract.test.js +6 -0
  276. package/server/lib/spaces/places/docker-engine.js +126 -0
  277. package/server/lib/spaces/places/docker-live-support.js +202 -0
  278. package/server/lib/spaces/places/docker-tools.js +186 -0
  279. package/server/lib/spaces/places/docker.js +587 -0
  280. package/server/lib/spaces/places/docker.test.js +1610 -0
  281. package/server/lib/spaces/places/escape-suite.js +1018 -0
  282. package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
  283. package/server/lib/spaces/places/fake-docker.js +375 -0
  284. package/server/lib/spaces/places/memory-place.js +103 -0
  285. package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
  286. package/server/lib/spaces/places/registry.js +43 -0
  287. package/server/lib/spaces/places/registry.test.js +50 -0
  288. package/server/lib/spaces/places/server.docker.live.test.js +225 -0
  289. package/server/lib/spaces/run-command.js +261 -0
  290. package/server/lib/spaces/run-command.test.js +396 -0
  291. package/server/lib/spaces/space-server.js +190 -0
  292. package/server/lib/spaces/space-server.test.js +335 -0
  293. package/server/lib/spaces/tools-filler.js +82 -0
  294. package/server/lib/spaces/tools-filler.test.js +148 -0
  295. package/server/lib/spaces/tools-pack.js +49 -0
  296. package/server/lib/spaces/tools-pack.test.js +57 -0
  297. package/server/lib/spaces/tools.js +138 -0
  298. package/server/lib/spaces/tools.test.js +153 -0
  299. package/server/lib/tts/openai-sdk.js +11 -0
  300. package/server/lib/tts/service.js +7 -5
  301. package/server/lib/tts/stt.js +3 -2
  302. package/server/lib/tunnels/DOCUMENTATION.md +2 -0
  303. package/server/lib/tunnels/routes.js +7 -0
  304. package/server/lib/tunnels/routes.test.js +55 -0
  305. package/server/lib/ui-auth/ui-auth.js +38 -2
  306. package/server/lib/ui-auth/ui-auth.test.js +42 -0
  307. package/server/lib/ui-auth/ui-passkeys.js +12 -10
  308. package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
  309. package/server/lib/walkthrough/pull-request.js +61 -8
  310. package/server/lib/walkthrough/pull-request.test.js +54 -1
  311. package/server/lib/walkthrough/routes.js +20 -0
  312. package/server/lib/walkthrough/routes.test.js +19 -0
  313. package/server/opencode-proxy.test.js +190 -166
  314. package/server/opencode-worktree-readiness.test.js +5 -4
  315. package/dist/assets/DiffView-COVQhWGd.js +0 -4
  316. package/dist/assets/FilesView-B1V5JxFX.js +0 -13
  317. package/dist/assets/LinearIssuesView-D7FKg1XJ.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-BrFjAVEf.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-DWepTafh.js +0 -12
  320. package/dist/assets/OnboardingScreen-Dyxxiqdt.js +0 -1
  321. package/dist/assets/PlanView-LW2z_EAP.js +0 -5
  322. package/dist/assets/PluginPane-B4tKno2h.js +0 -3
  323. package/dist/assets/SettingsView-DEaMulN9.js +0 -29
  324. package/dist/assets/SettingsWindow-FLDJXOgQ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +0 -1
  326. package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CkR4msKP.js +0 -8
  328. package/dist/assets/appThemeRegistry-BuUKhbYQ.js +0 -1
  329. package/dist/assets/de-DgxgFyGG.js +0 -10
  330. package/dist/assets/es-BFsZj5F_.js +0 -16
  331. package/dist/assets/fr-jdZ5KKQi.js +0 -14
  332. package/dist/assets/index-CKHjPKbq.js +0 -1
  333. package/dist/assets/index-Cbci4i1W.js +0 -40
  334. package/dist/assets/index-DqaMWNrC.css +0 -1
  335. package/dist/assets/ja-C5kKrhO4.js +0 -16
  336. package/dist/assets/ko-DXDTT_0O.js +0 -16
  337. package/dist/assets/main-CZALtVek.js +0 -896
  338. package/dist/assets/main-CgXMBNvG.js +0 -2
  339. package/dist/assets/miniChat-CwTmDixi.js +0 -2
  340. package/dist/assets/mobile-CLf2vCtO.js +0 -2
  341. package/dist/assets/pl-D-7x4Y8Q.js +0 -16
  342. package/dist/assets/pt-BR-CSBZrHa8.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +0 -2
  344. package/dist/assets/renderMobileApp-V4hYd32H.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +0 -25
  346. package/dist/assets/scheduledTasksApi-DzjbLd8P.js +0 -1
  347. package/dist/assets/tr-Dr-oJ22L.js +0 -7
  348. package/dist/assets/uk-9Nx83UcI.js +0 -16
  349. package/dist/assets/useAppFontEffects-khZXpFL_.js +0 -7333
  350. package/dist/assets/useRootScrollLock-DdvHf9Pt.js +0 -2
  351. package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
  352. package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
  353. package/dist/assets/zh-CN-Bxy_Hj3r.js +0 -16
  354. package/dist/assets/zh-TW-0BB4gpHM.js +0 -16
  355. package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
  356. package/server/lib/routing/feature-flag.js +0 -11
  357. package/server/lib/small-model/call.js +0 -851
  358. package/server/lib/small-model/call.test.js +0 -1292
  359. package/server/lib/small-model/catalog.js +0 -13
  360. package/server/lib/small-model/resolve.js +0 -131
  361. package/server/lib/small-model/resolve.test.js +0 -197
  362. package/server/lib/small-model/runtime-providers.js +0 -164
  363. package/server/lib/small-model/runtime-providers.test.js +0 -118
  364. package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
  365. package/server/lib/system-prompt/runtime.js +0 -49
  366. package/server/lib/system-prompt/runtime.test.js +0 -76
  367. package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
@@ -1,229 +1,175 @@
1
1
  # Small Model
2
2
 
3
- Server-side direct LLM calls that reuse the user's existing OpenCode provider
4
- logins (`~/.local/share/opencode/auth.json`). OpenCode uses a "small model"
5
- internally (titles, summaries) but does not expose it through the SDK or
6
- plugins — this module replicates that mechanism as an OpenChamber runtime API.
3
+ Background LLM calls for OpenChamber's own features — session titles, goal
4
+ distillation, session assist, the changes walkthrough. Every call goes to the
5
+ running OpenCode through `POST /api/experimental/generate`; OpenChamber never talks to a
6
+ provider directly and never handles a provider credential.
7
7
 
8
8
  ## Security boundary
9
9
 
10
- Credentials never leave the server process. The client sends only a prompt;
11
- auth resolution, OAuth refresh, and provider dispatch all happen server-side.
12
- Routes live under `/api/*` and are gated by the ui-auth middleware like every
13
- other runtime API.
10
+ The client sends a prompt. This server forwards it to OpenCode, which owns the
11
+ credentials, the provider dispatch and the token refresh. Routes live under
12
+ `/api/*` and are gated by the ui-auth middleware like every other runtime API.
14
13
 
15
14
  ## Files
16
15
 
17
- - `index.js` — orchestration: `generateSmallModelText()` / `describeSmallModel()`.
18
- - `runtime-providers.js` — provider state that exists only inside the running
19
- OpenCode process. A plugin registers its provider from the `config` hook and
20
- supplies the credential from its `auth` loader, so neither reaches
21
- `opencode.json` nor `auth.json`; `GET /provider` is the only place they
22
- become visible. The module caches one snapshot (30s TTL, shared in-flight
23
- request) and answers `null` — never an empty provider list — when OpenCode is
24
- unreachable, so a momentary outage cannot retract providers. It is wired once
25
- from `server/index.js` and reset on OpenCode restart, which reloads plugins
26
- and can move their ports and keys.
27
- - `resolve.js` — model selection, mirroring OpenCode's `getSmallModel` chain:
28
- 0. OpenChamber's own settings override (Settings → Sessions → Small Model):
29
- when `smallModelUseDefault` is `false`, `smallModelOverride`
30
- (`provider/model`) outranks everything below. Sanitized in
31
- `settings-helpers.js` (server), `persistence.ts` (client), and
32
- `bridge-settings-runtime.ts` (VS Code).
33
- 1. `small_model` from the merged OpenCode config layers (`provider/model`).
34
- 2. Family-priority scan (`gemini-flash` → `gpt-nano` → `claude-haiku`)
35
- **within the session's provider first** (`preferredProviderID`, like
36
- OpenCode resolves within the current provider), then over the other
37
- providers with a usable auth entry, newest `release_date` first.
38
- 3. GitHub Copilot hidden utility models (`gpt-*-nano/mini`) — these never
39
- appear in the catalog, so they participate as the `gpt-nano` family entry
40
- and as a final utility fallback.
41
- 4. Last resort: the session's own model (`preferredModelID`) when no small
42
- model resolves anywhere — costlier, but always valid.
43
- - Input clamp: the prompt is measured against the resolved model's catalog
44
- `limit.context` (minus an output reserve, ~4 chars/token estimate;
45
- conservative default when the model is not in the catalog). `onOverflow`
46
- decides what an oversized prompt means:
47
- - `truncate` (default) clips the tail and reports `inputTruncated: true`.
48
- Correct for callers that degrade gracefully (summaries, commit messages).
49
- - `error` throws a `413` with `code: 'context-too-small'` plus
50
- `requiredChars`/`availableChars`. Correct for callers whose output would be
51
- quietly wrong on a clipped input, so they can ask the user for a roomier
52
- model instead of returning confident nonsense.
53
- - Structured output: pass `responseSchema` (a JSON Schema) to get
54
- schema-shaped JSON back as `text`. Wire support differs per format —
55
- `response_format: {type: 'json_schema'}` for OpenAI-compatible chat,
56
- `text.format` for the Responses API, a forced single tool call for the
57
- Anthropic messages API, and `generationConfig.responseSchema` for Google
58
- (whose OpenAPI-flavored dialect drops unknown JSON Schema keywords). The
59
- ChatGPT-plan codex backend has no equivalent and rejects a schema request
60
- with `code: 'structured-output-unsupported'` rather than silently returning
61
- prose.
62
- - Output budget: `maxOutputTokens` is capped at the catalog's `limit.output` for
63
- the model, and the **same number** is reserved from the input allowance. The
64
- two must not drift — a caller that asks for a large answer while the reserve
65
- stays at the default overruns the context, and the failure looks like a
66
- truncation bug rather than a budgeting one. `describeSmallModel` takes
67
- `outputReserveTokens` so readiness checks agree with what generation will do.
68
- It may be a **function** of `{ contextTokens, outputTokenLimit }` for callers
69
- that want as much answer room as the resolved model allows — they cannot name
70
- a number before knowing which model they got. The resolved value comes back as
71
- `outputTokens`, which is what the caller should then request, so the reserve
72
- and the request are the same number by construction.
73
- - Reasoning models can spend the entire output budget thinking and return
74
- nothing. That case (empty content with `finish_reason: 'length'`, or content
75
- empty while `reasoning_content` is populated) throws with
76
- `code: 'output-exhausted'` so callers can offer a different model instead of
77
- showing a transport error.
78
- - `timeoutMs` overrides the 60s default per call; `signal` lets a caller abort
79
- a request that is no longer wanted. Both apply to every wire format.
80
- - `describeSmallModel()` additionally reports `inputCharBudget`,
81
- `contextTokens`, `contextKnown`, `structuredOutput`, and `hasLogin`. The last
82
- is whether the resolved provider has a usable credential (`auth.json` or
83
- config `provider.<id>.options.apiKey`) — settings/config overrides can name a
84
- provider with none, and callers such as the walkthrough refuse before the
85
- request. `structuredOutput` is tri-state: `true`/`false` from the catalog,
86
- `null` when the catalog omits the field — which it does for roughly half of
87
- all models, aggregators and proxies especially. Callers must treat `null` as
88
- "try it", not "unsupported".
89
- - Missing credentials throw with `statusCode: 401` and
90
- `code: 'no-provider-login'` rather than a bare `Error`, so UI callers can show
91
- a blocker instead of a raw 500 message.
92
- - `call.js` — wire formats and per-provider auth, replicating OpenCode's
93
- plugin auth loaders:
94
- - OpenCode-hosted providers receive `x-opencode-session`. Session-backed
95
- features reuse the real OpenCode session id, walkthrough retries reuse the
96
- walkthrough cache key, and standalone one-shot actions receive a fresh
97
- opaque id for that generation.
98
- - **GitHub Copilot**: fetches the requested model's authenticated `/models`
99
- metadata from `https://api.githubcopilot.com` (or
100
- `copilot-api.<enterprise>`) and honors its advertised endpoint, preferring
101
- Anthropic-compatible `/v1/messages`, then OpenAI `/responses`, then
102
- `/chat/completions`. Models without `supported_endpoints` retain the legacy
103
- Chat Completions default; metadata, missing-model, and unsupported-endpoint
104
- failures are surfaced instead of guessing. The stored device-OAuth token is
105
- used as the bearer with no token exchange or expiry.
106
- - **OpenAI OAuth (ChatGPT plan)**: streaming Responses API on
107
- `https://chatgpt.com/backend-api/codex/responses` with
108
- `ChatGPT-Account-Id`; expired tokens are refreshed against
109
- `auth.openai.com` (single-flight) and written back to `auth.json`.
110
- - **Anthropic** (`type: api`): `/messages` with `x-api-key`, against
111
- `provider.anthropic.options.baseURL` when configured (used as-is, matching
112
- `@ai-sdk/anthropic` — no `/v1` is inserted) or `https://api.anthropic.com/v1`
113
- otherwise.
114
- - **Google** (`type: api`): `generateContent` with `x-goog-api-key`; Gemini 3
115
- uses `thinkingLevel`, Gemini 2.x uses `thinkingBudget: 0`, and all other
116
- models omit `thinkingConfig` entirely.
117
- - Everything else: OpenAI-compatible `/chat/completions` against the
118
- provider's base URL, resolved from (1) `provider.<id>.options.baseURL`
119
- in the OpenCode config, (2) the hardcoded `https://api.openai.com/v1`
120
- endpoint, (3) the selected model's endpoint OpenCode resolved at runtime,
121
- (4) the provider-level runtime endpoint, (5) the provider's `api` field
122
- from the models.dev catalog, or (6) `SDK_DEFAULT_BASE_URLS` — providers
123
- such as Groq, xAI, and Mistral keep their endpoint inside a dedicated AI
124
- SDK package, so neither the catalog nor OpenCode reports one. The
125
- credential follows
126
- the same shape: config `options.apiKey`, then the runtime credential, then
127
- the auth.json entry. `provider.<id>.options.headers` is sent with the
128
- request and overrides the bearer default, so gateways that authenticate on
129
- their own header work here exactly as they do in a chat turn. Configured API
130
- keys and header values honor OpenCode's `{env:NAME}` and `{file:path}`
131
- substitutions; file contents and resolved credentials remain server-side.
132
- - `thinking: { type: 'disabled' }` is sent in two cases, mirroring OpenCode's
133
- `transform.ts`. The GLM switch belongs to the endpoint, not the model
134
- family: it goes to a provider whose id contains `zai`/`zhipu` or whose
135
- base URL is on `api.z.ai`/`bigmodel.cn`, because the same GLM model served
136
- by another provider (OpenCode Go) rejects the unknown field with a 400.
137
- The MiniMax M3 switch goes only behind the `@ai-sdk/openai-compatible` and
138
- `@ai-sdk/anthropic` adapters (runtime model, then catalog model, then
139
- catalog provider `npm`); a provider that reports no adapter counts as
140
- OpenAI-compatible.
141
- - The runtime credential is refused for providers listed in
142
- `OWN_CREDENTIAL_HANDLING`. Their branches need the stored entry rather than
143
- a bearer token: the clearest case is the ChatGPT-plan `openai` login, whose
144
- runtime `options.apiKey` is an OAuth access token that `api.openai.com`
145
- answers with 401.
146
- - `[small-model:diagnostic]` logs record provider/model, input character
147
- counts, output budget, thinking toggle, HTTP/finish status, and
148
- content/reasoning lengths without logging prompts, response text, or
149
- credentials. Goal audit parsing similarly emits
150
- `[session-goal:diagnostic]` structural verdict metadata.
151
- - `catalog.js` — models.dev catalog via the shared in-process cache
152
- (`../opencode/models-metadata.js`, also serving
153
- `/api/openchamber/models-metadata`).
16
+ - `client.js` — the connection to the running OpenCode. `server/index.js` wires
17
+ it once with `buildOpenCodeUrl` and `getOpenCodeAuthHeaders`; the client is
18
+ built per call because the port and the server password both change across an
19
+ OpenCode restart. Scoping is the `x-opencode-directory` header, URI-encoded.
20
+ Also caches the model list for 30 seconds — a generation needs the model's
21
+ context and output limits, and a round trip per session title is the wrong
22
+ trade. An unreachable OpenCode keeps the previous answer rather than
23
+ retracting it.
24
+ - `index.js` — `generateSmallModelText()`, `describeSmallModel()`,
25
+ `listAuthenticatedProviders()`.
154
26
  - `routes.js` — `GET /api/small-model` (resolution preview) and
155
27
  `POST /api/small-model/generate` (`{ prompt, system?, maxOutputTokens?,
156
28
  model?, directory? }` → `{ text, providerID, modelID, source }`).
29
+ - `runtime-providers.js` — compatibility re-export so `server/index.js` keeps
30
+ resolving its import. Delete it once that import points at `client.js`.
31
+
32
+ ## Free tier and the session fallback
33
+
34
+ OpenCode's free zen models are listed as enabled without a login, but
35
+ `/api/experimental/generate` refuses them ("free tier can only be used in OpenCode") and
36
+ `/api/model/default` answers nothing, so on a fresh install with no provider
37
+ login `generateSmallModelText` throws `404`. What happens next is per feature:
38
+
39
+ - Commit message and PR description (`packages/ui/src/lib/gitApi.ts`) fall back
40
+ to `POST /api/session/:id/generate`, which runs on the open session's own
41
+ model with the session as context and does not touch its history. Verified
42
+ on OpenCode 2.0.2: the reply comes back and the message count stays at zero.
43
+ - Session renaming, the session goal and the walkthrough do NOT fall back —
44
+ feeding a whole session into a model to write a title or audit a goal is the
45
+ wrong cost. Their entry points read `available` from `GET /api/small-model`
46
+ (`packages/ui/src/stores/useSmallModelStore.ts`) and show a disabled control
47
+ with the reason; the walkthrough uses its own readiness (`no-model`).
48
+ - Session assist is server-side and simply does nothing when
49
+ `describeSmallModel` answers null.
50
+ - Notes summarization and spoken summaries keep the original text and silence
51
+ the 404.
52
+
53
+ ## Model resolution
54
+
55
+ Four things are decided here, in order:
56
+
57
+ 1. An explicit `model` on the request (`provider/model`) — `source: 'request'`.
58
+ 2. OpenChamber's settings override (Settings → Sessions → Small Model): when
59
+ `smallModelUseDefault` is `false`, `smallModelOverride` wins —
60
+ `source: 'settings'`.
61
+ 3. The small model of the session's provider (`preferredProviderID`) —
62
+ `source: 'session-provider-small'` — found by `pickSmallModelInProvider`:
63
+ the newest enabled, active, text-in text-out model of the first family in
64
+ `SMALL_MODEL_FAMILY_PRIORITY` (`gpt-luna`, `gemini-flash-lite`,
65
+ `gemini-flash`, `claude-haiku`, then `gpt-nano`, `gpt-mini`) that the
66
+ provider has. The first four are OpenCode's own list for its session
67
+ titles (`Catalog.model.small`), repeated here because OpenCode does not
68
+ expose it over HTTP; the last two are v1's additions so a provider with
69
+ only utility models (Copilot) still gets a cheap one. Families are
70
+ models.dev `family` values, not model ids (`gpt-luna` is the family of
71
+ `gpt-5.6-luna`); a model without one — a custom provider, a subscription
72
+ outside the catalog — gets its family read from its id (`familyOf`:
73
+ luna / flash-lite / flash / haiku / nano / mini). A caller that passes `restrictToPreferredProvider`
74
+ (session titles, the session goal, session assist, notes from a selection)
75
+ and finds none then takes the session's own model — `source:
76
+ 'session-model'`: costlier than a small model elsewhere, but never another
77
+ provider's subscription.
78
+ 4. The small model of any provider OpenCode can call, same family order,
79
+ newest first — `source: 'small'`. Where callers without a session (commit
80
+ messages, spoken summaries) usually land.
81
+ 5. Otherwise `GET /api/model/default` — `source: 'default'`. This is
82
+ OpenCode's default chat model, not a small one; it is the last resort.
83
+
84
+ Claude Code is refused unconditionally (`422`,
85
+ `code: 'small-model-provider-unsupported'`). A plugin can publish an
86
+ OpenAI-compatible endpoint for it, but that endpoint is a façade over the
87
+ Claude Agent SDK, which spawns the Claude Code CLI per request and spends the
88
+ user's Claude subscription rate limit. An available endpoint does not lift the
89
+ refusal — the cost is the reason, not the transport.
90
+
91
+ ## Prompt shape
92
+
93
+ `/api/experimental/generate` takes one prompt and no system message, so `system` leads the
94
+ prompt, separated by a blank line.
95
+
96
+ Input clamp: the prompt is measured against the resolved model's `limit.context`
97
+ as OpenCode reports it, minus an output reserve, at ~4 chars/token. A model
98
+ OpenCode does not list gets a conservative 64k default. `onOverflow` decides
99
+ what an oversized prompt means:
100
+
101
+ - `truncate` (default) clips the tail and reports `inputTruncated: true`.
102
+ Correct for callers that degrade gracefully (summaries, commit messages).
103
+ - `error` throws a `413` with `code: 'context-too-small'` plus
104
+ `requiredChars`/`availableChars`. Correct for callers whose output would be
105
+ quietly wrong on a clipped input, so they can ask the user for a roomier
106
+ model instead of returning confident nonsense.
107
+
108
+ Output budget: `maxOutputTokens` is capped at the model's `limit.output`, and
109
+ the **same number** is reserved from the input allowance. `/api/experimental/generate` takes
110
+ no output budget of its own, so this number only shapes the reserve — but the
111
+ two sides must stay equal or a caller that asks for a large answer overruns the
112
+ context and the failure looks like a truncation bug.
113
+ `describeSmallModel` takes `outputReserveTokens` so readiness checks agree with
114
+ what generation will do. It may be a **function** of
115
+ `{ contextTokens, outputTokenLimit }` for callers that want as much answer room
116
+ as the resolved model allows — they cannot name a number before knowing which
117
+ model they got. The resolved value comes back as `outputTokens`.
118
+
119
+ `timeoutMs` overrides the 60s default; `signal` aborts a request that is no
120
+ longer wanted. The timeout covers generation, retry waits and structured-output
121
+ retries together.
122
+
123
+ OpenCode 2 can reject an explicit model before its cold catalog finishes loading.
124
+ An `InvalidRequestError` whose message exactly names the selected model as
125
+ `Model unavailable: provider/model` gets one retry after 500 ms, with the same
126
+ model and prompt. Cancellation also stops the wait. Other errors are not retried.
127
+ If the model remains unavailable, the route returns 503 with
128
+ `code: 'small-model-unavailable'` and the model-specific reason. Commit and PR
129
+ generation own their error toast and suppress the shared request toast.
130
+
131
+ ## Structured output
132
+
133
+ `/api/experimental/generate` has no structured-output mode. `responseSchema` is therefore
134
+ emulated: the schema is appended to the prompt as "Reply with JSON matching this
135
+ schema and nothing else: …", and the reply is parsed here. A ```json fence is
136
+ stripped. An unparseable reply is retried **once** — a model that ignored the
137
+ shape often honours it on a second pass, and the alternative is failing a
138
+ walkthrough over a stray sentence of preamble. A second failure throws with
139
+ `code: 'structured-output-unsupported'` (`422`).
140
+
141
+ `describeSmallModel` reports `structuredOutput: null`, never `false`. The
142
+ capability is not knowable before the call, and callers must read `null` as
143
+ "try it".
157
144
 
158
145
  ## Which providers the pickers may offer
159
146
 
160
147
  `listAuthenticatedProviders()` answers one question for the Small Model and
161
- Changes Walkthrough pickers alike: which providers can this module actually
162
- call. One rule decides it, applied the same way to every provider — **a
163
- credential we are allowed to use, and an endpoint to send it to.** The
164
- auth.json scan as before, plus the credential and endpoint OpenCode resolved
165
- for a plugin provider.
166
-
167
- **opencode zen is excluded without a real login.** When the user has no zen
168
- credential, OpenCode substitutes the sentinel `options.apiKey = "public"` and
169
- trims its catalog to the free models. Those run on OpenCode's own subsidised
170
- infrastructure and are meant to be reached through OpenCode, so the sentinel is
171
- never accepted as a credential — see `ZEN_ANONYMOUS_API_KEY`.
172
-
173
- ### Why there is no capability probe
174
-
175
- A plugin may implement its whole integration inside `options.fetch` —
176
- rewriting the path, signing the request, translating the payload — and OpenCode
177
- cannot serialise a function. Such a provider advertises an ordinary base URL
178
- that answers nothing we know how to ask, and no reported field distinguishes it
179
- from a plain one.
180
-
181
- Asking the endpoint (`GET /models`) does identify that case correctly. It was
182
- measured against all 166 providers carrying an `api` URL in the models.dev
183
- catalog, and it also denies six of them — `cloudflare-workers-ai`,
184
- `infomaniak`, `iflowcn`, `inference`, `kuae-cloud-coding-plan`,
185
- `thinkingmachines` — which work fine and simply have no `/models` route. At a
186
- 3.6% false-negative rate on providers known to work, the probe removes more
187
- working models from the picker than broken ones, and a provider that silently
188
- vanishes explains nothing while one that fails on use says why.
189
-
190
- So availability stops at credential and endpoint, and the protocol verdict is
191
- left to the call. A provider whose protocol lives in a plugin's `fetch` stays
192
- selectable and fails when used — which is what it did before this resolution
193
- existed.
194
-
195
- Claude Code is refused unconditionally. A plugin can publish an
196
- OpenAI-compatible endpoint for it, but that endpoint is a façade over the
197
- Claude Agent SDK, which spawns the Claude Code CLI per request and spends the
198
- user's Claude subscription rate limit. Paying that for a session title or a
199
- summary is the wrong trade, so an available endpoint does not lift the
200
- refusal — the cost is the reason, not the transport.
148
+ Changes Walkthrough pickers: which providers OpenCode can call right now. A
149
+ provider counts when it has at least one enabled model in `GET /api/model` —
150
+ the same test OpenCode applies before letting a chat turn use it.
201
151
 
202
- The result is served as `authenticatedProviders` on `GET /api/small-model`.
203
- The field name predates the runtime resolution; it now means "callable", which
204
- is a superset of "has an auth.json entry".
152
+ `GET /api/provider` only contributes names. It comes back empty on setups where
153
+ models are perfectly usable, so an empty provider list is never read as "no
154
+ providers". Claude Code is removed from the result for the reason above.
155
+
156
+ The field is served as `authenticatedProviders` on `GET /api/small-model`. The
157
+ name predates this resolution; it now means "callable".
158
+
159
+ ## describeSmallModel
160
+
161
+ Reports which model would be used without calling it: `providerID`, `modelID`,
162
+ `source`, plus `inputCharBudget`, `contextTokens`, `contextKnown`,
163
+ `outputTokens`, `outputTokenLimit`, `structuredOutput` and `hasLogin`.
164
+
165
+ `hasLogin` is `false` when OpenCode lists the model as disabled — a settings
166
+ override can name a model there is no credential for, and the walkthrough
167
+ refuses before the user pays for a failed request. A model OpenCode does not
168
+ list at all is not evidence either way, so it counts as usable.
169
+
170
+ Returns `null` when OpenCode is not reachable.
205
171
 
206
172
  ## Registration
207
173
 
208
174
  Mounted lazily from `feature-routes-runtime.js` (same pattern as quota): the
209
175
  module is imported on first request, not at server startup.
210
-
211
- ## Known limitations
212
-
213
- - OpenCode's free models (`opencode/big-pickle`, `*-free`) work without a
214
- token only through OpenCode's own server — direct calls are rejected, and
215
- piggybacking on their subsidized infra is out of bounds by design. Every
216
- resolution step therefore requires a credential we are allowed to use:
217
- a session on an unauthenticated `opencode` provider falls through to the
218
- global scan (or a clean 404 on a vanilla setup with no logins). The runtime
219
- snapshot does not weaken this — OpenCode reports the sentinel
220
- `apiKey: "public"` for that state, and this module refuses to read it as a
221
- credential.
222
-
223
- - Anthropic OAuth (Claude Pro/Max) entries are not supported — OpenCode itself
224
- keeps those outside `auth.json` in this generation; only `type: api` keys
225
- work for Anthropic.
226
- - Amazon Bedrock, GitLab, Azure and other credential-chain providers are out
227
- of scope; they need more than a key/token (regions, resource names).
228
- - Responses from the codex backend are collected from the SSE stream; the
229
- endpoint itself is non-streaming by design (small utility calls).
@@ -0,0 +1,113 @@
1
+ import { OpenCode } from '@opencode/client';
2
+
3
+ // The running OpenCode instance is the only transport this module has. It is
4
+ // wired once from `server/index.js`, and re-read on every call because the
5
+ // port and the server password both move across an OpenCode restart.
6
+ let connection = null;
7
+
8
+ /**
9
+ * Wires this module to the running OpenCode instance. Pass `null` to detach.
10
+ * Until it is wired, every lookup answers "no client", which the callers
11
+ * report as "no small model available" rather than guessing.
12
+ */
13
+ export function configureOpenCodeRuntimeProviders(next) {
14
+ connection = next ?? null;
15
+ }
16
+
17
+ /**
18
+ * Drops anything cached about the running instance. An OpenCode restart can
19
+ * change the port, the password and the model list.
20
+ */
21
+ export function resetOpenCodeRuntimeProviders() {
22
+ modelCache = null;
23
+ modelCacheKey = '';
24
+ modelCacheAt = 0;
25
+ }
26
+
27
+ const stripTrailingSlash = (value) => value.replace(/\/+$/, '');
28
+
29
+ /**
30
+ * A `@opencode/client` bound to the running instance and scoped to
31
+ * `directory`, or `null` when OpenCode is not reachable yet.
32
+ *
33
+ * Built per call on purpose: `OpenCode.make` only closes over a base URL and
34
+ * headers, and both change when OpenCode restarts.
35
+ */
36
+ export function getSmallModelClient(directory) {
37
+ if (!connection) return null;
38
+ let baseUrl;
39
+ try {
40
+ baseUrl = stripTrailingSlash(connection.buildOpenCodeUrl('', ''));
41
+ } catch {
42
+ // The port is not known yet — OpenCode has not finished starting.
43
+ return null;
44
+ }
45
+ const headers = { ...connection.getOpenCodeAuthHeaders() };
46
+ if (typeof directory === 'string' && directory.trim()) {
47
+ headers['x-opencode-directory'] = encodeURIComponent(directory.trim());
48
+ }
49
+ return OpenCode.make({ baseUrl, headers });
50
+ }
51
+
52
+ const MODEL_CACHE_TTL_MS = 30_000;
53
+ let modelCache = null;
54
+ let modelCacheKey = '';
55
+ let modelCacheAt = 0;
56
+
57
+ const unwrap = (payload) => (Array.isArray(payload?.data) ? payload.data : []);
58
+
59
+ /**
60
+ * Every model the running OpenCode knows about for `directory`.
61
+ *
62
+ * Cached briefly: a generation needs the model's context and output limits,
63
+ * and paying a round trip for them on every title or summary would be the
64
+ * wrong trade. An empty array means "asked and got nothing", so callers fall
65
+ * back to conservative defaults rather than refusing.
66
+ */
67
+ export async function listModelInfos(client, directory) {
68
+ const key = typeof directory === 'string' ? directory : '';
69
+ if (modelCache && modelCacheKey === key && Date.now() - modelCacheAt < MODEL_CACHE_TTL_MS) {
70
+ return modelCache;
71
+ }
72
+ try {
73
+ const models = unwrap(await client.model.list());
74
+ modelCache = models;
75
+ modelCacheKey = key;
76
+ modelCacheAt = Date.now();
77
+ return models;
78
+ } catch {
79
+ // Keep the previous answer for this directory when there is one: a
80
+ // momentarily unreachable OpenCode must not retract model limits.
81
+ return modelCacheKey === key && modelCache ? modelCache : [];
82
+ }
83
+ }
84
+
85
+ export async function listProviderInfos(client) {
86
+ try {
87
+ return unwrap(await client.provider.list());
88
+ } catch {
89
+ return [];
90
+ }
91
+ }
92
+
93
+ /**
94
+ * The model OpenCode would pick on its own, or `null` when it cannot say.
95
+ */
96
+ export async function getDefaultModelInfo(client) {
97
+ try {
98
+ const payload = await client.model.default();
99
+ const info = payload?.data ?? payload;
100
+ return info && typeof info === 'object' && typeof info.id === 'string' ? info : null;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * The model entry for a `provider/model` reference. `modelID` and `id` are
108
+ * both accepted because OpenCode reports the same model under both.
109
+ */
110
+ export function findModelInfo(models, providerID, modelID) {
111
+ return models.find((model) => model?.providerID === providerID
112
+ && (model.id === modelID || model.modelID === modelID)) ?? null;
113
+ }