@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,13 +0,0 @@
1
- import { getModelsMetadata } from '../opencode/models-metadata.js';
2
-
3
- // The models.dev catalog is shared with the /api/openchamber/models-metadata
4
- // route through one in-process cache — no extra fetches, no cache files.
5
- export async function getModelCatalog() {
6
- const { metadata } = await getModelsMetadata();
7
- return metadata;
8
- }
9
-
10
- export function getCatalogProvider(catalog, providerID) {
11
- const entry = catalog?.[providerID];
12
- return entry && typeof entry === 'object' ? entry : null;
13
- }
@@ -1,131 +0,0 @@
1
- import { getCatalogProvider } from './catalog.js';
2
-
3
- // Mirrors OpenCode's getSmallModel fallback chain:
4
- // 1. `small_model` from the merged config layers ("provider/model").
5
- // 2. GitHub Copilot's hidden utility models when Copilot is logged in.
6
- // 3. Family-priority scan of the authenticated providers' catalog models.
7
- const FAMILY_PRIORITY = ['gemini-flash', 'gpt-nano', 'claude-haiku'];
8
- const COPILOT_UTILITY_MODELS = ['gpt-5.4-nano', 'gpt-4.1', 'gpt-4o', 'gpt-4o-mini'];
9
- // The ChatGPT-plan codex backend only accepts a small allowlist of models
10
- // (nano/API-key models are rejected with 400) — this is its cheapest one.
11
- const OPENAI_OAUTH_SMALL_MODEL = 'gpt-5.4-mini';
12
-
13
- const AUTH_PROVIDER_ALIASES = {
14
- 'github-copilot': ['github-copilot', 'copilot'],
15
- };
16
-
17
- export function getAuthEntryForProvider(auth, providerID) {
18
- const aliases = AUTH_PROVIDER_ALIASES[providerID] || [providerID];
19
- for (const alias of aliases) {
20
- const entry = auth?.[alias];
21
- if (entry && typeof entry === 'object') {
22
- return entry;
23
- }
24
- }
25
- return null;
26
- }
27
-
28
- export function isUsableAuthEntry(entry) {
29
- if (!entry || typeof entry !== 'object') return false;
30
- if (entry.type === 'api') return typeof entry.key === 'string' && entry.key.length > 0;
31
- if (entry.type === 'oauth') {
32
- return (typeof entry.access === 'string' && entry.access.length > 0)
33
- || (typeof entry.refresh === 'string' && entry.refresh.length > 0);
34
- }
35
- if (entry.type === 'wellknown') return typeof entry.token === 'string' && entry.token.length > 0;
36
- return false;
37
- }
38
-
39
- export function parseModelRef(value) {
40
- if (typeof value !== 'string') return null;
41
- const trimmed = value.trim();
42
- const slash = trimmed.indexOf('/');
43
- if (slash <= 0 || slash === trimmed.length - 1) return null;
44
- return {
45
- providerID: trimmed.slice(0, slash),
46
- modelID: trimmed.slice(slash + 1),
47
- };
48
- }
49
-
50
- const pickByFamily = (models, family) => {
51
- const matches = Object.values(models)
52
- .filter((model) => model && typeof model === 'object' && model.family === family);
53
- if (matches.length === 0) return null;
54
- matches.sort((a, b) => String(b.release_date || '').localeCompare(String(a.release_date || '')));
55
- return matches[0];
56
- };
57
-
58
- // Small-model candidates within ONE provider, by family priority. Copilot and
59
- // ChatGPT-plan OpenAI have fixed small models that never appear in the
60
- // catalog; everyone else is scanned through the catalog families.
61
- const pickWithinProvider = (providerID, auth, catalog, family) => {
62
- if (providerID === 'openai' && auth.openai?.type === 'oauth') {
63
- return family === 'gpt-nano'
64
- ? { providerID, modelID: OPENAI_OAUTH_SMALL_MODEL, source: 'codex-small' }
65
- : null;
66
- }
67
- if (providerID === 'github-copilot') {
68
- return family === 'gpt-nano'
69
- ? { providerID, modelID: COPILOT_UTILITY_MODELS[0], source: 'copilot-utility' }
70
- : null;
71
- }
72
- const provider = getCatalogProvider(catalog, providerID);
73
- if (!provider || !provider.models || typeof provider.models !== 'object') return null;
74
- const model = pickByFamily(provider.models, family);
75
- return model?.id ? { providerID, modelID: model.id, source: 'family-scan' } : null;
76
- };
77
-
78
- export function resolveSmallModel({ auth, catalog, settingsSmallModel, configSmallModel, preferredProviderID, preferredModelID }) {
79
- // OpenChamber's own setting (Settings → Sessions → Small Model override)
80
- // outranks everything, including the OpenCode config.
81
- const fromSettings = parseModelRef(settingsSmallModel);
82
- if (fromSettings) {
83
- return { ...fromSettings, source: 'settings' };
84
- }
85
-
86
- const explicit = parseModelRef(configSmallModel);
87
- if (explicit) {
88
- return { ...explicit, source: 'config' };
89
- }
90
-
91
- // Like OpenCode: when the caller has a session context, the utility call
92
- // stays on the session's provider. Scan its families for a small model,
93
- // otherwise run on the session's own model — never silently switch to a
94
- // different provider's subscription.
95
- const preferred = typeof preferredProviderID === 'string' && preferredProviderID
96
- ? preferredProviderID
97
- : null;
98
- if (preferred && isUsableAuthEntry(getAuthEntryForProvider(auth, preferred))) {
99
- for (const family of FAMILY_PRIORITY) {
100
- const match = pickWithinProvider(preferred, auth, catalog, family);
101
- if (match) return match;
102
- }
103
- if (typeof preferredModelID === 'string' && preferredModelID) {
104
- return { providerID: preferred, modelID: preferredModelID, source: 'session-model' };
105
- }
106
- }
107
-
108
- // No session context (or its provider has no usable login): scan all
109
- // authenticated providers by family priority.
110
- const authedProviders = Object.keys(auth || {}).filter((providerID) =>
111
- providerID !== preferred && isUsableAuthEntry(auth[providerID]));
112
-
113
- for (const family of FAMILY_PRIORITY) {
114
- for (const providerID of authedProviders) {
115
- const match = pickWithinProvider(providerID, auth, catalog, family);
116
- if (match) return match;
117
- }
118
- }
119
-
120
- // Copilot's utility fallback for legacy auth aliases the loop above missed.
121
- const copilotEntry = getAuthEntryForProvider(auth, 'github-copilot');
122
- if (isUsableAuthEntry(copilotEntry)) {
123
- return {
124
- providerID: 'github-copilot',
125
- modelID: COPILOT_UTILITY_MODELS[0],
126
- source: 'copilot-utility',
127
- };
128
- }
129
-
130
- return null;
131
- }
@@ -1,197 +0,0 @@
1
- import { describe, it, expect } from 'bun:test';
2
- import { resolveSmallModel, parseModelRef, isUsableAuthEntry } from './resolve.js';
3
-
4
- const catalog = {
5
- google: {
6
- id: 'google',
7
- models: {
8
- 'gemini-2.5-flash': { id: 'gemini-2.5-flash', family: 'gemini-flash', release_date: '2025-06-01' },
9
- 'gemini-2.0-flash': { id: 'gemini-2.0-flash', family: 'gemini-flash', release_date: '2024-12-01' },
10
- 'gemini-2.5-pro': { id: 'gemini-2.5-pro', family: 'gemini-pro', release_date: '2025-06-01' },
11
- },
12
- },
13
- anthropic: {
14
- id: 'anthropic',
15
- models: {
16
- 'claude-haiku-4-5': { id: 'claude-haiku-4-5', family: 'claude-haiku', release_date: '2025-10-01' },
17
- 'claude-sonnet-4-5': { id: 'claude-sonnet-4-5', family: 'claude-sonnet', release_date: '2025-09-01' },
18
- },
19
- },
20
- };
21
-
22
- describe('parseModelRef', () => {
23
- it('splits provider/model on the first slash', () => {
24
- expect(parseModelRef('anthropic/claude-haiku-4-5')).toEqual({
25
- providerID: 'anthropic',
26
- modelID: 'claude-haiku-4-5',
27
- });
28
- });
29
-
30
- it('keeps slashes inside the model id', () => {
31
- expect(parseModelRef('openrouter/google/gemini-2.5-flash')).toEqual({
32
- providerID: 'openrouter',
33
- modelID: 'google/gemini-2.5-flash',
34
- });
35
- });
36
-
37
- it('rejects values without a provider or model part', () => {
38
- expect(parseModelRef('anthropic/')).toBeNull();
39
- expect(parseModelRef('/model')).toBeNull();
40
- expect(parseModelRef('plain')).toBeNull();
41
- expect(parseModelRef(undefined)).toBeNull();
42
- });
43
- });
44
-
45
- describe('isUsableAuthEntry', () => {
46
- it('accepts api keys, oauth tokens, and wellknown tokens', () => {
47
- expect(isUsableAuthEntry({ type: 'api', key: 'sk-x' })).toBe(true);
48
- expect(isUsableAuthEntry({ type: 'oauth', access: 'a', refresh: 'r', expires: 0 })).toBe(true);
49
- expect(isUsableAuthEntry({ type: 'wellknown', key: 'k', token: 't' })).toBe(true);
50
- });
51
-
52
- it('rejects empty or malformed entries', () => {
53
- expect(isUsableAuthEntry({ type: 'api', key: '' })).toBe(false);
54
- expect(isUsableAuthEntry({ type: 'oauth' })).toBe(false);
55
- expect(isUsableAuthEntry(null)).toBe(false);
56
- });
57
- });
58
-
59
- describe('resolveSmallModel', () => {
60
- it('gives the OpenChamber settings override top priority', () => {
61
- const result = resolveSmallModel({
62
- auth: { anthropic: { type: 'api', key: 'sk-x' } },
63
- catalog,
64
- settingsSmallModel: 'anthropic/claude-haiku-4-5',
65
- configSmallModel: 'openai/gpt-4o-mini',
66
- preferredProviderID: 'anthropic',
67
- });
68
- expect(result).toEqual({ providerID: 'anthropic', modelID: 'claude-haiku-4-5', source: 'settings' });
69
- });
70
-
71
- it('prefers the configured small_model', () => {
72
- const result = resolveSmallModel({
73
- auth: { anthropic: { type: 'api', key: 'sk-x' } },
74
- catalog,
75
- configSmallModel: 'openai/gpt-4o-mini',
76
- });
77
- expect(result).toEqual({ providerID: 'openai', modelID: 'gpt-4o-mini', source: 'config' });
78
- });
79
-
80
- it('scans authenticated providers by family priority, newest first', () => {
81
- const result = resolveSmallModel({
82
- auth: {
83
- google: { type: 'api', key: 'g-key' },
84
- anthropic: { type: 'api', key: 'sk-x' },
85
- },
86
- catalog,
87
- configSmallModel: null,
88
- });
89
- expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.5-flash', source: 'family-scan' });
90
- });
91
-
92
- it('skips providers without a usable credential', () => {
93
- const result = resolveSmallModel({
94
- auth: {
95
- google: { type: 'api', key: '' },
96
- anthropic: { type: 'api', key: 'sk-x' },
97
- },
98
- catalog,
99
- configSmallModel: null,
100
- });
101
- expect(result).toEqual({ providerID: 'anthropic', modelID: 'claude-haiku-4-5', source: 'family-scan' });
102
- });
103
-
104
- it('falls back to Copilot utility models when only Copilot is logged in', () => {
105
- const result = resolveSmallModel({
106
- auth: { 'github-copilot': { type: 'oauth', access: 't', refresh: 't', expires: 0 } },
107
- catalog,
108
- configSmallModel: null,
109
- });
110
- expect(result?.providerID).toBe('github-copilot');
111
- expect(result?.source).toBe('copilot-utility');
112
- });
113
-
114
- it('returns null when nothing is authenticated', () => {
115
- expect(resolveSmallModel({ auth: {}, catalog, configSmallModel: null })).toBeNull();
116
- });
117
-
118
- it('prefers the session provider over other authenticated providers', () => {
119
- const result = resolveSmallModel({
120
- auth: {
121
- google: { type: 'api', key: 'g-key' },
122
- anthropic: { type: 'api', key: 'sk-x' },
123
- },
124
- catalog,
125
- configSmallModel: null,
126
- preferredProviderID: 'anthropic',
127
- });
128
- expect(result).toEqual({ providerID: 'anthropic', modelID: 'claude-haiku-4-5', source: 'family-scan' });
129
- });
130
-
131
- it('ignores a preferred provider without a usable login', () => {
132
- const result = resolveSmallModel({
133
- auth: { google: { type: 'api', key: 'g-key' } },
134
- catalog,
135
- configSmallModel: null,
136
- preferredProviderID: 'anthropic',
137
- });
138
- expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.5-flash', source: 'family-scan' });
139
- });
140
-
141
- it('never uses a session provider without a login (opencode free models)', () => {
142
- // Vanilla setups default the picker to opencode/big-pickle with no
143
- // opencode token — those free models only work through OpenCode itself
144
- // and must never be called directly, so the session context is ignored.
145
- const result = resolveSmallModel({
146
- auth: { openai: { type: 'oauth', access: 'a', refresh: 'r', expires: Date.now() + 60_000 } },
147
- catalog,
148
- configSmallModel: null,
149
- preferredProviderID: 'opencode',
150
- preferredModelID: 'big-pickle',
151
- });
152
- expect(result).toEqual({ providerID: 'openai', modelID: 'gpt-5.4-mini', source: 'codex-small' });
153
- });
154
-
155
- it('resolves nothing on a vanilla setup with no logins at all', () => {
156
- const result = resolveSmallModel({
157
- auth: {},
158
- catalog,
159
- configSmallModel: null,
160
- preferredProviderID: 'opencode',
161
- preferredModelID: 'big-pickle',
162
- });
163
- expect(result).toBeNull();
164
- });
165
-
166
- it('falls back to the session model instead of scanning other providers', () => {
167
- const result = resolveSmallModel({
168
- auth: {
169
- 'opencode-go': { type: 'api', key: 'oc-key' },
170
- openai: { type: 'oauth', access: 'a', refresh: 'r', expires: Date.now() + 60_000 },
171
- },
172
- catalog: {
173
- 'opencode-go': {
174
- id: 'opencode-go',
175
- models: {
176
- 'deepseek-v4-flash': { id: 'deepseek-v4-flash', family: 'deepseek-flash', release_date: '2026-01-01' },
177
- },
178
- },
179
- },
180
- configSmallModel: null,
181
- preferredProviderID: 'opencode-go',
182
- preferredModelID: 'deepseek-v4-flash',
183
- });
184
- expect(result).toEqual({ providerID: 'opencode-go', modelID: 'deepseek-v4-flash', source: 'session-model' });
185
- });
186
-
187
- it('falls back to the session model itself when nothing resolves', () => {
188
- const result = resolveSmallModel({
189
- auth: { mistral: { type: 'api', key: 'm-key' } },
190
- catalog,
191
- configSmallModel: null,
192
- preferredProviderID: 'mistral',
193
- preferredModelID: 'mistral-large-latest',
194
- });
195
- expect(result).toEqual({ providerID: 'mistral', modelID: 'mistral-large-latest', source: 'session-model' });
196
- });
197
- });
@@ -1,164 +0,0 @@
1
- // Provider state that exists only inside the running OpenCode process.
2
- //
3
- // A plugin registers its provider from the `config` hook and supplies the
4
- // credential from its `auth` loader, both at startup. Neither ends up in
5
- // `opencode.json` or `auth.json`, so a server that only reads files sees
6
- // nothing — which is why plugin-backed models used to fail here with
7
- // "has no known API base URL" while working fine in chat (#2666).
8
- //
9
- // `GET /provider` is where that state becomes visible. It reports, per
10
- // provider, the resolved `options.baseURL` and `options.apiKey`, and per model
11
- // the wire adapter (`api.npm`) and endpoint (`api.url`). The provider-level
12
- // endpoint remains only as a fallback when the selected model has no endpoint.
13
- //
14
- // What it does NOT report is `options.fetch`. OpenCode strips functions from
15
- // the response, and a plugin is free to put its whole protocol in there:
16
- // rewriting the path, signing the request, translating the payload. Such a
17
- // provider advertises a perfectly ordinary base URL that answers nothing we
18
- // know how to ask, and no field distinguishes the two.
19
- //
20
- // Asking the endpoint (`GET /models`) looked like the way to tell them apart,
21
- // and it does answer correctly for that case — but measured against the 166
22
- // providers in the models.dev catalog it also denies six that work fine and
23
- // simply have no `/models` route. A provider that vanishes from the picker
24
- // explains nothing; one that fails on use says why. So this module reports
25
- // what it knows and leaves the verdict to the call itself.
26
-
27
- const SNAPSHOT_TTL_MS = 30_000;
28
- const SNAPSHOT_TIMEOUT_MS = 5_000;
29
-
30
- // opencode zen hands out this sentinel instead of a key when the user has no
31
- // zen login, and trims its catalog to the free models. Those run on OpenCode's
32
- // own subsidised infrastructure and are meant to be reached through OpenCode,
33
- // not by us. Treating the sentinel as a credential would do exactly that, so
34
- // it is never accepted as one.
35
- export const ZEN_ANONYMOUS_API_KEY = 'public';
36
-
37
- let connection = null;
38
- let snapshot = null;
39
- let snapshotAt = 0;
40
- let inflight = null;
41
-
42
- /**
43
- * Wires this module to the running OpenCode instance. Called once at server
44
- * startup; pass `null` to detach. Until it is wired every lookup answers
45
- * "nothing known", which leaves the file-based resolution unchanged.
46
- */
47
- export function configureOpenCodeRuntimeProviders(next) {
48
- connection = next ?? null;
49
- resetOpenCodeRuntimeProviders();
50
- }
51
-
52
- /**
53
- * Drops every cached answer. OpenCode restarts reload plugins, which can
54
- * change ports, keys and the provider list itself.
55
- */
56
- export function resetOpenCodeRuntimeProviders() {
57
- snapshot = null;
58
- snapshotAt = 0;
59
- inflight = null;
60
- }
61
-
62
- /**
63
- * The boundary. Everything the `/provider` payload claims is checked here, so
64
- * the rest of this module and its callers work with settled values:
65
- * a credential we may use, provider and model endpoints, and whether the
66
- * provider is the anonymous zen case.
67
- *
68
- * The credential deliberately prefers `options.apiKey` over the `key` field:
69
- * for a plugin provider the former is what its auth loader produced and what
70
- * OpenCode itself sends, while `key` only carries env/auth.json values this
71
- * server can already read from disk.
72
- */
73
- function parseProviderListing(payload) {
74
- const providers = new Map();
75
- const connected = new Set();
76
- if (!payload || typeof payload !== 'object') return { providers, connected };
77
-
78
- const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
79
- const record = (value) => (value && typeof value === 'object' ? value : {});
80
- const endpoint = (value) => text(value)?.replace(/\/+$/, '') ?? null;
81
-
82
- for (const raw of Array.isArray(payload.all) ? payload.all : []) {
83
- const id = text(record(raw).id);
84
- if (!id) continue;
85
- const options = record(record(raw).options);
86
- const models = new Map();
87
- for (const [modelID, rawModel] of Object.entries(record(record(raw).models))) {
88
- const model = record(rawModel);
89
- const api = record(model.api);
90
- const modelURL = endpoint(api.url);
91
- const modelNpm = text(api.npm);
92
- models.set(modelID, { api: { url: modelURL, npm: modelNpm } });
93
- }
94
- const firstModel = models.values().next().value;
95
- const declaredKey = text(options.apiKey);
96
- providers.set(id, {
97
- id,
98
- source: text(record(raw).source),
99
- apiKey: declaredKey === ZEN_ANONYMOUS_API_KEY ? null : (declaredKey ?? text(record(raw).key)),
100
- baseURL: endpoint(options.baseURL) ?? firstModel?.api?.url ?? null,
101
- models,
102
- // True only for the zen-without-login case: a provider that is present
103
- // and usable through OpenCode, but that we must not call ourselves.
104
- anonymousZen: declaredKey === ZEN_ANONYMOUS_API_KEY,
105
- });
106
- }
107
-
108
- // Providers OpenCode considers usable right now. A provider can be present
109
- // in `all` (it is in the catalog) without any credential behind it.
110
- for (const raw of Array.isArray(payload.connected) ? payload.connected : []) {
111
- const id = text(raw);
112
- if (id) connected.add(id);
113
- }
114
-
115
- return { providers, connected };
116
- }
117
-
118
- const fetchSnapshot = async () => {
119
- const response = await fetch(connection.buildOpenCodeUrl('/provider', ''), {
120
- headers: { Accept: 'application/json', ...connection.getOpenCodeAuthHeaders() },
121
- signal: AbortSignal.timeout(SNAPSHOT_TIMEOUT_MS),
122
- });
123
- if (!response.ok) {
124
- throw new Error(`OpenCode provider listing failed with ${response.status}`);
125
- }
126
- return parseProviderListing(await response.json());
127
- };
128
-
129
- /**
130
- * The current runtime provider snapshot, or `null` when OpenCode cannot be
131
- * reached.
132
- *
133
- * `null` means "unknown", never "no providers": callers must fall back to
134
- * their file-based resolution rather than treat an unreachable OpenCode as an
135
- * empty provider list.
136
- */
137
- export async function getRuntimeProviderSnapshot() {
138
- if (!connection) return null;
139
- if (snapshot && Date.now() - snapshotAt < SNAPSHOT_TTL_MS) return snapshot;
140
- if (!inflight) {
141
- inflight = fetchSnapshot().finally(() => {
142
- inflight = null;
143
- });
144
- }
145
- try {
146
- snapshot = await inflight;
147
- snapshotAt = Date.now();
148
- return snapshot;
149
- } catch {
150
- // Keep serving the previous snapshot when there is one: a momentarily
151
- // unreachable OpenCode should not retract providers that were resolving a
152
- // second ago.
153
- return snapshot;
154
- }
155
- }
156
-
157
- /**
158
- * Runtime credential and endpoint for one provider, or `null` when OpenCode
159
- * knows nothing about it.
160
- */
161
- export async function getRuntimeProvider(providerID) {
162
- const current = await getRuntimeProviderSnapshot();
163
- return current?.providers.get(providerID) ?? null;
164
- }
@@ -1,118 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import {
4
- ZEN_ANONYMOUS_API_KEY,
5
- configureOpenCodeRuntimeProviders,
6
- getRuntimeProvider,
7
- getRuntimeProviderSnapshot,
8
- resetOpenCodeRuntimeProviders,
9
- } from './runtime-providers.js';
10
-
11
- const providerPayload = (overrides = {}) => ({
12
- all: [
13
- {
14
- id: 'llmapi',
15
- source: 'config',
16
- options: { apiKey: 'plugin-key', baseURL: 'https://api.llmapi.ai/v1/' },
17
- models: {
18
- 'claude-opus-4-8': { api: { id: 'claude-opus-4-8', url: '', npm: '@ai-sdk/anthropic' } },
19
- 'gpt-5.6-luna': { api: { id: 'gpt-5.6-luna', url: 'https://api.llmapi.ai/v1', npm: '@ai-sdk/openai' } },
20
- },
21
- },
22
- {
23
- id: 'opencode',
24
- source: 'custom',
25
- options: { apiKey: ZEN_ANONYMOUS_API_KEY },
26
- models: { 'free-model': { api: { id: 'free-model', url: 'https://opencode.ai/zen/v1', npm: '@ai-sdk/openai-compatible' } } },
27
- },
28
- {
29
- id: 'zai-coding-plan',
30
- source: 'api',
31
- key: 'auth-json-key',
32
- options: {},
33
- models: { 'glm-5': { api: { id: 'glm-5', url: 'https://api.z.ai/api/coding/paas/v4', npm: '@ai-sdk/openai-compatible' } } },
34
- },
35
- ],
36
- connected: ['llmapi', 'opencode', 'zai-coding-plan'],
37
- ...overrides,
38
- });
39
-
40
- describe('OpenCode runtime provider snapshot', () => {
41
- let fetchMock;
42
-
43
- beforeEach(() => {
44
- fetchMock = vi.fn(async () => new Response(JSON.stringify(providerPayload()), {
45
- status: 200,
46
- headers: { 'content-type': 'application/json' },
47
- }));
48
- vi.stubGlobal('fetch', fetchMock);
49
- configureOpenCodeRuntimeProviders({
50
- buildOpenCodeUrl: (pathname) => `http://127.0.0.1:4096${pathname}`,
51
- getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic test' }),
52
- });
53
- });
54
-
55
- afterEach(() => {
56
- configureOpenCodeRuntimeProviders(null);
57
- resetOpenCodeRuntimeProviders();
58
- vi.unstubAllGlobals();
59
- });
60
-
61
- it('reports the credential and endpoint a plugin registered at runtime', async () => {
62
- const provider = await getRuntimeProvider('llmapi');
63
-
64
- expect(provider).toMatchObject({ apiKey: 'plugin-key', baseURL: 'https://api.llmapi.ai/v1' });
65
- expect(provider.models.get('gpt-5.6-luna')).toEqual({
66
- api: { url: 'https://api.llmapi.ai/v1', npm: '@ai-sdk/openai' },
67
- });
68
- expect(fetchMock.mock.calls[0][0]).toBe('http://127.0.0.1:4096/provider');
69
- expect(fetchMock.mock.calls[0][1].headers).toMatchObject({ Authorization: 'Basic test' });
70
- });
71
-
72
- it('refuses the zen sentinel as a credential', async () => {
73
- const provider = await getRuntimeProvider('opencode');
74
-
75
- expect(provider.apiKey).toBeNull();
76
- expect(provider.anonymousZen).toBe(true);
77
- // The endpoint is still reported; only the credential is withheld.
78
- expect(provider.baseURL).toBe('https://opencode.ai/zen/v1');
79
- });
80
-
81
- it('falls back to the model endpoint when the provider carries no baseURL', async () => {
82
- expect((await getRuntimeProvider('zai-coding-plan')).baseURL).toBe('https://api.z.ai/api/coding/paas/v4');
83
- });
84
-
85
- it('serves one snapshot to concurrent callers instead of refetching', async () => {
86
- await Promise.all([getRuntimeProvider('llmapi'), getRuntimeProvider('opencode'), getRuntimeProvider('zai-coding-plan')]);
87
-
88
- expect(fetchMock).toHaveBeenCalledTimes(1);
89
- });
90
-
91
- it('answers "unknown" rather than "no providers" when OpenCode is unreachable', async () => {
92
- resetOpenCodeRuntimeProviders();
93
- fetchMock.mockRejectedValue(new Error('connection refused'));
94
-
95
- expect(await getRuntimeProviderSnapshot()).toBeNull();
96
- });
97
-
98
- it('keeps the previous snapshot when a later refresh fails', async () => {
99
- await getRuntimeProviderSnapshot();
100
- fetchMock.mockRejectedValue(new Error('connection refused'));
101
-
102
- // Past the snapshot TTL, so the next read genuinely attempts a refresh.
103
- vi.useFakeTimers();
104
- vi.setSystemTime(Date.now() + 60_000);
105
- const refreshed = await getRuntimeProviderSnapshot();
106
- vi.useRealTimers();
107
-
108
- expect(fetchMock).toHaveBeenCalledTimes(2);
109
- expect(refreshed.providers.has('llmapi')).toBe(true);
110
- });
111
-
112
- it('stays on file-based resolution until it is configured', async () => {
113
- configureOpenCodeRuntimeProviders(null);
114
-
115
- expect(await getRuntimeProvider('llmapi')).toBeNull();
116
- expect(fetchMock).not.toHaveBeenCalled();
117
- });
118
- });
@@ -1,36 +0,0 @@
1
- # Managed System Prompt Optimizer
2
-
3
- ## Purpose
4
-
5
- This module injects an opt-in OpenCode plugin only when OpenChamber launches
6
- and owns the OpenCode process and `optimizeSystemPrompt` is enabled. The plugin
7
- replaces OpenCode's built-in behavioral/provider prompt with a short identity
8
- while preserving the environment, project instructions, MCP instructions,
9
- skills, conversation history, and separately supplied tools.
10
-
11
- ## Runtime flow
12
-
13
- 1. Settings persist `optimizeSystemPrompt` in OpenChamber's `settings.json`.
14
- 2. The setting is applied when managed OpenCode restarts.
15
- 3. The runtime materializes the plugin under
16
- `<openchamber-data-dir>/system-prompt/` and appends its `file://` URL to
17
- `OPENCODE_CONFIG_CONTENT` without replacing existing plugin entries.
18
- 4. The plugin tracks the selected agent through `chat.message`. The transform
19
- runs only for sessions using the built-in `build` or `plan` agent.
20
- 5. The transform locates OpenCode's environment boundary and removes only the
21
- preceding text. If the boundary is absent, it leaves the prompt unchanged.
22
-
23
- ## Limitations
24
-
25
- OpenCode exposes the assembled prompt rather than structured sections. A custom
26
- prompt configured by overriding the `build` or `plan` agent occupies the same
27
- prefix as the built-in provider prompt, so the optimizer also removes that
28
- override. Other agents are never transformed. The setting is off by default.
29
-
30
- Plan-mode restrictions and build-mode transitions are not part of the removed
31
- prefix. OpenCode injects those as synthetic message reminders after system
32
- prompt transformation and separately enforces plan restrictions through tool
33
- permissions.
34
-
35
- The plugin is not injected for external OpenCode servers or VS Code's separate
36
- OpenCode lifecycle.