@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.4

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 (287) hide show
  1. package/README.md +15 -11
  2. package/bin/cli-opencode.test.js +4 -4
  3. package/bin/cli.js +1 -1
  4. package/bin/lib/cli-startup.js +1 -1
  5. package/bin/lib/cli-startup.test.js +18 -4
  6. package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
  7. package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
  10. package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
  11. package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
  12. package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
  16. package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
  20. package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
  21. package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
  22. package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
  24. package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
  27. package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
  28. package/dist/assets/context-menu-B2LXJgXL.js +1 -0
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
  31. package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
  33. package/dist/assets/es-CN3E_zXj.js +15 -0
  34. package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
  36. package/dist/assets/fr-9zZvQZD2.js +13 -0
  37. package/dist/assets/gitApi-yuWg9BN4.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
  39. package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
  40. package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
  41. package/dist/assets/ja-Dnjk_82N.js +15 -0
  42. package/dist/assets/ko-BxCEE99C.js +15 -0
  43. package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
  44. package/dist/assets/main-DiL6w6KF.js +169 -0
  45. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  46. package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
  47. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  48. package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
  49. package/dist/assets/miniChat-CwtUaElU.js +2 -0
  50. package/dist/assets/mobile-DB-6--ws.js +2 -0
  51. package/dist/assets/multirun-TAsxFI8e.js +4 -0
  52. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
  53. package/dist/assets/pl-CNNsWnPM.js +15 -0
  54. package/dist/assets/platform-C13fMVEs.js +6 -0
  55. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  56. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  57. package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
  58. package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
  59. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  60. package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
  61. package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
  62. package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
  63. package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
  64. package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
  65. package/dist/assets/src-ubCWXZhR.css +2 -0
  66. package/dist/assets/store-DLCiZaA6.js +15 -0
  67. package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
  68. package/dist/assets/textarea-BBOlRI9O.js +1 -0
  69. package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
  70. package/dist/assets/uk-rwQrxeYM.js +15 -0
  71. package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
  72. package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
  73. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  74. package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
  75. package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
  76. package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
  77. package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
  78. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  79. package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
  80. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  81. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
  83. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  84. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  87. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  88. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  89. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  90. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  91. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  92. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  99. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  100. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  101. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  102. package/dist/index.html +13 -13
  103. package/dist/mini-chat.html +13 -13
  104. package/dist/mobile.html +13 -13
  105. package/package.json +1 -1
  106. package/server/index.js +327 -62
  107. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  108. package/server/lib/assistants/contracts.js +11 -1
  109. package/server/lib/assistants/projection-history.test.js +265 -18
  110. package/server/lib/assistants/routes.js +6 -0
  111. package/server/lib/assistants/service.js +417 -231
  112. package/server/lib/assistants/service.test.js +392 -148
  113. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  114. package/server/lib/conversations/service.js +1 -0
  115. package/server/lib/conversations/service.test.js +16 -0
  116. package/server/lib/conversations/validation.js +11 -1
  117. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +49 -4
  120. package/server/lib/event-stream/reasoning-projection.js +16 -1
  121. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  122. package/server/lib/event-stream/runtime.js +57 -20
  123. package/server/lib/event-stream/runtime.test.js +112 -0
  124. package/server/lib/llm/DOCUMENTATION.md +33 -9
  125. package/server/lib/llm/catalog.js +1 -1
  126. package/server/lib/llm/completions.js +5 -1
  127. package/server/lib/llm/completions.test.js +3 -3
  128. package/server/lib/llm/generate.js +237 -145
  129. package/server/lib/llm/generate.test.js +201 -46
  130. package/server/lib/llm/routes.js +2 -0
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/notifications/runtime.js +23 -5
  133. package/server/lib/notifications/runtime.test.js +11 -0
  134. package/server/lib/opencode/DOCUMENTATION.md +85 -17
  135. package/server/lib/opencode/agent-document.d.ts +28 -0
  136. package/server/lib/opencode/agent-document.js +280 -0
  137. package/server/lib/opencode/agent-document.test.js +137 -0
  138. package/server/lib/opencode/agents.js +96 -0
  139. package/server/lib/opencode/auth-state-runtime.js +5 -0
  140. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  141. package/server/lib/opencode/config-entity-routes.js +42 -99
  142. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  143. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  144. package/server/lib/opencode/ensure-cli.js +1 -2
  145. package/server/lib/opencode/env-runtime.js +40 -26
  146. package/server/lib/opencode/env-runtime.test.js +10 -17
  147. package/server/lib/opencode/feature-routes-runtime.js +43 -4
  148. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  149. package/server/lib/opencode/http-server-connections.js +98 -0
  150. package/server/lib/opencode/http-server-connections.test.js +321 -0
  151. package/server/lib/opencode/lifecycle.js +349 -39
  152. package/server/lib/opencode/lifecycle.test.js +322 -0
  153. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  154. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  155. package/server/lib/opencode/openchamber-routes.js +7 -1
  156. package/server/lib/opencode/opencode2-pin.js +4 -4
  157. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  158. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  159. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  160. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  161. package/server/lib/opencode/plugin-routes.js +3 -18
  162. package/server/lib/opencode/plugin-routes.test.js +9 -9
  163. package/server/lib/opencode/provider-catalog.js +58 -12
  164. package/server/lib/opencode/provider-catalog.test.js +48 -0
  165. package/server/lib/opencode/proxy.js +252 -46
  166. package/server/lib/opencode/proxy.test.js +235 -2
  167. package/server/lib/opencode/routes.js +296 -62
  168. package/server/lib/opencode/routes.test.js +278 -12
  169. package/server/lib/opencode/runtime-contract.js +401 -0
  170. package/server/lib/opencode/runtime-contract.test.js +214 -0
  171. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  172. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  173. package/server/lib/opencode/server-utils-runtime.js +33 -0
  174. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  175. package/server/lib/opencode/shared-service.js +147 -0
  176. package/server/lib/opencode/shared-service.test.js +93 -0
  177. package/server/lib/opencode/shared.js +3 -3
  178. package/server/lib/opencode/shutdown-runtime.js +15 -6
  179. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  180. package/server/lib/opencode/skill-routes.js +20 -33
  181. package/server/lib/opencode/skill-routes.test.js +35 -2
  182. package/server/lib/opencode/skills.test.js +15 -0
  183. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  184. package/server/lib/opencode/static-routes-runtime.js +3 -1
  185. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  186. package/server/lib/opencode/v2-client.js +14 -3
  187. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  188. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  189. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  190. package/server/lib/permission-auto-accept/runtime.js +14 -7
  191. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  192. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  193. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
  194. package/server/lib/scheduled-tasks/runtime.js +52 -4
  195. package/server/lib/scheduled-tasks/runtime.test.js +80 -1
  196. package/server/lib/session-goal/DOCUMENTATION.md +113 -13
  197. package/server/lib/session-goal/routes.js +9 -0
  198. package/server/lib/session-goal/runtime.js +1579 -117
  199. package/server/lib/session-goal/runtime.test.js +1453 -84
  200. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  201. package/server/lib/session-index/event-ingest.js +89 -16
  202. package/server/lib/session-index/event-ingest.test.js +73 -0
  203. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  204. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  205. package/server/lib/session-index/routes.js +63 -10
  206. package/server/lib/session-index/routes.test.js +8 -8
  207. package/server/lib/session-index/service.js +7 -1
  208. package/server/lib/session-index/sync-runtime.js +161 -34
  209. package/server/lib/session-index/sync-runtime.test.js +155 -1
  210. package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
  211. package/server/lib/session-metadata/routes.js +108 -9
  212. package/server/lib/session-metadata/routes.test.js +47 -2
  213. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  214. package/server/lib/session-metadata/session-archive.js +654 -0
  215. package/server/lib/session-metadata/session-archive.test.js +359 -0
  216. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  217. package/server/lib/session-metadata/session-metadata-store.js +212 -72
  218. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  219. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  220. package/server/lib/session-metadata/session-projection.js +255 -0
  221. package/server/lib/session-metadata/session-projection.test.js +64 -0
  222. package/server/lib/session-metadata/system-session.js +55 -0
  223. package/server/lib/session-metadata/system-session.test.js +55 -0
  224. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  225. package/server/lib/session-title/native-runtime.test.js +167 -0
  226. package/server/lib/session-title/runtime.js +67 -42
  227. package/server/lib/session-title/session-access.js +46 -0
  228. package/server/lib/session-title/session-access.test.js +91 -0
  229. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  230. package/server/lib/session-turn-pages/routes.js +17 -102
  231. package/server/lib/session-turn-pages/routes.test.js +281 -0
  232. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  233. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  234. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  235. package/server/lib/small-model/catalog.js +31 -51
  236. package/server/lib/small-model/catalog.test.js +65 -10
  237. package/server/lib/small-model/custom-api.js +68 -0
  238. package/server/lib/small-model/custom-api.test.js +64 -0
  239. package/server/lib/small-model/index.js +124 -240
  240. package/server/lib/small-model/resolve.js +35 -120
  241. package/server/lib/small-model/resolve.test.js +20 -110
  242. package/server/lib/small-model/routes.js +1 -3
  243. package/server/lib/small-model/summary-settings.test.js +157 -253
  244. package/server/opencode-proxy.test.js +2 -0
  245. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  246. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  247. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  248. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  249. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  250. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  251. package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
  252. package/dist/assets/context-menu-jGtH6fR9.js +0 -1
  253. package/dist/assets/es-c52LTHHe.js +0 -15
  254. package/dist/assets/fr-PisFbUE8.js +0 -13
  255. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  256. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  257. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  258. package/dist/assets/ko-ClFys5o2.js +0 -15
  259. package/dist/assets/main-Dn9rpsgl.js +0 -169
  260. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  261. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  262. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  263. package/dist/assets/multirun-BvSG94mW.js +0 -4
  264. package/dist/assets/pl-CnaKWN5F.js +0 -15
  265. package/dist/assets/platform-CWTIp02q.js +0 -4
  266. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  267. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  268. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  269. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  270. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  271. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  272. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  273. package/dist/assets/src-BzX5UI-c.css +0 -2
  274. package/dist/assets/store-DJczmczv.js +0 -15
  275. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  276. package/dist/assets/uk-CaX0iqkB.js +0 -15
  277. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  278. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  279. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  280. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  281. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  282. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  283. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  284. package/server/lib/small-model/call.js +0 -427
  285. package/server/lib/small-model/call.test.js +0 -382
  286. package/server/lib/small-model/opencode-session.js +0 -183
  287. package/server/lib/small-model/opencode-session.test.js +0 -147
@@ -1,382 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
-
3
- // readConfig reads merged opencode config layers from disk; mock it so each
4
- // test controls the provider config without touching the filesystem. call.js
5
- // imports only readConfig from shared.js, so the rest of that module is left
6
- // untouched for this file.
7
- vi.mock('../opencode/shared.js', () => ({
8
- readConfig: vi.fn(),
9
- }));
10
-
11
- const { callSmallModel } = await import('./call.js');
12
- const { readConfig } = await import('../opencode/shared.js');
13
-
14
- // Minimal catalog fragment used by the catalog-based base URL resolution case.
15
- // Base URL comes from model.api.url (OpenCode SDK shape), not provider.api.
16
- const CATALOG = {
17
- mistral: {
18
- id: 'mistral',
19
- name: 'Mistral',
20
- models: {
21
- 'mistral-small-latest': {
22
- id: 'mistral-small-latest',
23
- api: { url: 'https://api.mistral.ai/v1' },
24
- },
25
- },
26
- },
27
- };
28
-
29
- const ok = (content) => ({
30
- ok: true,
31
- status: 200,
32
- json: async () => ({
33
- choices: [{ message: { content }, finish_reason: 'stop' }],
34
- }),
35
- text: async () => JSON.stringify({
36
- choices: [{ message: { content }, finish_reason: 'stop' }],
37
- }),
38
- });
39
-
40
- const lastCall = (mock) => {
41
- const [url, init] = mock.mock.calls.at(-1);
42
- return { url: String(url), init };
43
- };
44
-
45
- // Regression coverage for the small-model dispatch to custom OpenAI-compatible
46
- // providers — credential and endpoint resolution, precedence, and non-leakage.
47
- describe('callSmallModel — custom provider config', () => {
48
- let fetchMock;
49
- let originalFetch;
50
-
51
- beforeEach(() => {
52
- fetchMock = vi.fn();
53
- originalFetch = globalThis.fetch;
54
- globalThis.fetch = fetchMock;
55
- readConfig.mockReset();
56
- });
57
-
58
- afterEach(() => {
59
- globalThis.fetch = originalFetch;
60
- });
61
-
62
- describe('config-supplied credentials (no auth.json entry)', () => {
63
- it('calls a custom summary API without an OpenCode provider login', async () => {
64
- fetchMock.mockResolvedValue(ok('summary'));
65
-
66
- const text = await callSmallModel({
67
- auth: {},
68
- catalog: {},
69
- workingDirectory: '/proj',
70
- providerID: 'custom',
71
- modelID: 'summary-model',
72
- prompt: 'Summarize this diff.',
73
- custom: {
74
- baseURL: 'https://summary.example.test/v1',
75
- apiToken: 'summary-token',
76
- },
77
- });
78
-
79
- expect(text).toBe('summary');
80
- const { url, init } = lastCall(fetchMock);
81
- expect(url).toBe('https://summary.example.test/v1/chat/completions');
82
- expect(init.headers.Authorization).toBe('Bearer summary-token');
83
- expect(JSON.stringify(init.body)).not.toContain('summary-token');
84
- });
85
-
86
- it('uses apiKey and baseURL from provider config when no auth.json entry exists', async () => {
87
- readConfig.mockReturnValue({
88
- provider: {
89
- custom: {
90
- options: { apiKey: 'test-key', baseURL: 'https://proxy.example.test/v1' },
91
- },
92
- },
93
- });
94
- fetchMock.mockResolvedValue(ok('hello'));
95
-
96
- const text = await callSmallModel({
97
- auth: {},
98
- catalog: {},
99
- workingDirectory: '/proj',
100
- providerID: 'custom',
101
- modelID: 'gpt-4o-mini',
102
- prompt: 'hi',
103
- });
104
-
105
- expect(text).toBe('hello');
106
- expect(fetchMock).toHaveBeenCalledTimes(1);
107
-
108
- const { url, init } = lastCall(fetchMock);
109
- // Config baseURL is used, never the hardcoded OpenAI endpoint.
110
- expect(url).toBe('https://proxy.example.test/v1/chat/completions');
111
- expect(url).not.toContain('api.openai.com');
112
- // Config apiKey becomes the bearer credential.
113
- expect(init.headers.Authorization).toBe('Bearer test-key');
114
- });
115
-
116
- it('trims a trailing slash from the configured baseURL', async () => {
117
- readConfig.mockReturnValue({
118
- provider: {
119
- custom: { options: { apiKey: 'k', baseURL: 'https://proxy.example.test/v1/' } },
120
- },
121
- });
122
- fetchMock.mockResolvedValue(ok('ok'));
123
-
124
- await callSmallModel({
125
- auth: {},
126
- catalog: {},
127
- workingDirectory: '/proj',
128
- providerID: 'custom',
129
- modelID: 'gpt-4o-mini',
130
- prompt: 'hi',
131
- });
132
-
133
- expect(lastCall(fetchMock).url).toBe('https://proxy.example.test/v1/chat/completions');
134
- });
135
-
136
- it('throws "No OpenCode login found for provider" when neither auth.json nor config apiKey exists', async () => {
137
- readConfig.mockReturnValue({
138
- provider: { custom: { options: { baseURL: 'https://proxy.example.test/v1' } } },
139
- });
140
-
141
- await expect(callSmallModel({
142
- auth: {},
143
- catalog: {},
144
- workingDirectory: '/proj',
145
- providerID: 'custom',
146
- modelID: 'gpt-4o-mini',
147
- prompt: 'hi',
148
- })).rejects.toThrow('No OpenCode login found for provider "custom"');
149
-
150
- // The credential gate fires before any network call.
151
- expect(fetchMock).not.toHaveBeenCalled();
152
- });
153
-
154
- it('treats a blank/whitespace apiKey in config as absent', async () => {
155
- readConfig.mockReturnValue({
156
- provider: {
157
- custom: { options: { apiKey: ' ', baseURL: 'https://proxy.example.test/v1' } },
158
- },
159
- });
160
-
161
- await expect(callSmallModel({
162
- auth: {},
163
- catalog: {},
164
- workingDirectory: '/proj',
165
- providerID: 'custom',
166
- modelID: 'gpt-4o-mini',
167
- prompt: 'hi',
168
- })).rejects.toThrow('No OpenCode login found for provider "custom"');
169
- });
170
- });
171
-
172
- describe('resolution order when auth.json is also present', () => {
173
- it('uses the auth.json credential with the config baseURL', async () => {
174
- readConfig.mockReturnValue({
175
- provider: { custom: { options: { baseURL: 'https://proxy.example.test/v1' } } },
176
- });
177
- fetchMock.mockResolvedValue(ok('done'));
178
-
179
- const text = await callSmallModel({
180
- auth: { custom: { type: 'api', key: 'authjson-key' } },
181
- catalog: {},
182
- workingDirectory: '/proj',
183
- providerID: 'custom',
184
- modelID: 'gpt-4o-mini',
185
- prompt: 'hi',
186
- });
187
-
188
- expect(text).toBe('done');
189
- const { url, init } = lastCall(fetchMock);
190
- expect(url).toBe('https://proxy.example.test/v1/chat/completions');
191
- expect(init.headers.Authorization).toBe('Bearer authjson-key');
192
- });
193
-
194
- it('prefers the config apiKey over an auth.json credential when both are present (matches OpenCode)', async () => {
195
- readConfig.mockReturnValue({
196
- provider: {
197
- custom: { options: { apiKey: 'config-key', baseURL: 'https://proxy.example.test/v1' } },
198
- },
199
- });
200
- fetchMock.mockResolvedValue(ok('done'));
201
-
202
- await callSmallModel({
203
- auth: { custom: { type: 'api', key: 'authjson-key' } },
204
- catalog: {},
205
- workingDirectory: '/proj',
206
- providerID: 'custom',
207
- modelID: 'gpt-4o-mini',
208
- prompt: 'hi',
209
- });
210
-
211
- // OpenCode's resolveSDK reads options.apiKey first and only falls back to
212
- // auth.json's key when config has none — so the config key wins and the
213
- // auth.json credential must never be sent.
214
- expect(lastCall(fetchMock).init.headers.Authorization).toBe('Bearer config-key');
215
- expect(JSON.stringify(fetchMock.mock.calls[0][1])).not.toContain('authjson-key');
216
- });
217
- });
218
-
219
- describe('openai provider custom baseURL override', () => {
220
- it('respects provider.openai.options.baseURL over the hardcoded OpenAI endpoint', async () => {
221
- readConfig.mockReturnValue({
222
- provider: { openai: { options: { baseURL: 'https://gateway.example.test/v1' } } },
223
- });
224
- fetchMock.mockResolvedValue(ok('ok'));
225
-
226
- await callSmallModel({
227
- auth: { openai: { type: 'api', key: 'sk-openai' } },
228
- catalog: {},
229
- workingDirectory: '/proj',
230
- providerID: 'openai',
231
- modelID: 'gpt-4o-mini',
232
- prompt: 'hi',
233
- });
234
-
235
- const { url, init } = lastCall(fetchMock);
236
- expect(url).toBe('https://gateway.example.test/v1/chat/completions');
237
- expect(url).not.toContain('api.openai.com');
238
- expect(init.headers.Authorization).toBe('Bearer sk-openai');
239
- });
240
-
241
- it('falls back to https://api.openai.com/v1 when no openai baseURL override is configured', async () => {
242
- readConfig.mockReturnValue({});
243
- fetchMock.mockResolvedValue(ok('ok'));
244
-
245
- await callSmallModel({
246
- auth: { openai: { type: 'api', key: 'sk-openai' } },
247
- catalog: {},
248
- workingDirectory: '/proj',
249
- providerID: 'openai',
250
- modelID: 'gpt-4o-mini',
251
- prompt: 'hi',
252
- });
253
-
254
- expect(lastCall(fetchMock).url).toBe('https://api.openai.com/v1/chat/completions');
255
- });
256
-
257
- it('still requires a credential: a baseURL alone does not authenticate openai', async () => {
258
- readConfig.mockReturnValue({
259
- provider: { openai: { options: { baseURL: 'https://gateway.example.test/v1' } } },
260
- });
261
-
262
- await expect(callSmallModel({
263
- auth: {},
264
- catalog: {},
265
- workingDirectory: '/proj',
266
- providerID: 'openai',
267
- modelID: 'gpt-4o-mini',
268
- prompt: 'hi',
269
- })).rejects.toThrow('No OpenCode login found for provider "openai"');
270
- });
271
- });
272
-
273
- describe('catalog-based base URL (no config override)', () => {
274
- it('uses the model api.url from the catalog when no config baseURL is set', async () => {
275
- readConfig.mockReturnValue({});
276
- fetchMock.mockResolvedValue(ok('ok'));
277
-
278
- await callSmallModel({
279
- auth: { mistral: { type: 'api', key: 'mistral-key' } },
280
- catalog: CATALOG,
281
- workingDirectory: '/proj',
282
- providerID: 'mistral',
283
- modelID: 'mistral-small-latest',
284
- prompt: 'hi',
285
- });
286
-
287
- const { url, init } = lastCall(fetchMock);
288
- expect(url).toBe('https://api.mistral.ai/v1/chat/completions');
289
- expect(init.headers.Authorization).toBe('Bearer mistral-key');
290
- });
291
-
292
- it('prefers config baseURL over model api.url', async () => {
293
- readConfig.mockReturnValue({
294
- provider: { mistral: { options: { baseURL: 'https://gateway.example.test/v1' } } },
295
- });
296
- fetchMock.mockResolvedValue(ok('ok'));
297
-
298
- await callSmallModel({
299
- auth: { mistral: { type: 'api', key: 'mistral-key' } },
300
- catalog: CATALOG,
301
- workingDirectory: '/proj',
302
- providerID: 'mistral',
303
- modelID: 'mistral-small-latest',
304
- prompt: 'hi',
305
- });
306
-
307
- expect(lastCall(fetchMock).url).toBe('https://gateway.example.test/v1/chat/completions');
308
- });
309
-
310
- it('throws when a non-openai provider has no model api.url and no config baseURL', async () => {
311
- readConfig.mockReturnValue({});
312
-
313
- await expect(callSmallModel({
314
- auth: { custom: { type: 'api', key: 'k' } },
315
- catalog: {},
316
- workingDirectory: '/proj',
317
- providerID: 'custom',
318
- modelID: 'gpt-4o-mini',
319
- prompt: 'hi',
320
- })).rejects.toThrow('Provider "custom" has no known API base URL');
321
- });
322
- });
323
-
324
- describe('config-supplied key does not leak', () => {
325
- // The config-supplied key must stay in-memory: never copied into catalog
326
- // metadata, the response, or the request body.
327
- it('does not mutate the catalog or echo the key in the request/response', async () => {
328
- const catalog = { custom: { id: 'custom', models: {} } };
329
- const catalogBefore = JSON.parse(JSON.stringify(catalog));
330
- readConfig.mockReturnValue({
331
- provider: {
332
- custom: { options: { apiKey: 'test-key', baseURL: 'https://proxy.example.test/v1' } },
333
- },
334
- });
335
- fetchMock.mockResolvedValue(ok('the answer'));
336
-
337
- const text = await callSmallModel({
338
- auth: {},
339
- catalog,
340
- workingDirectory: '/proj',
341
- providerID: 'custom',
342
- modelID: 'gpt-4o-mini',
343
- prompt: 'hi',
344
- });
345
-
346
- // Response text is exactly the model output — no credential echoed back.
347
- expect(text).toBe('the answer');
348
- // Catalog object left untouched (key stays in-memory only).
349
- expect(catalog).toEqual(catalogBefore);
350
-
351
- const { url, init } = lastCall(fetchMock);
352
- // The key rides only in the Authorization header.
353
- expect(url).not.toContain('test-key');
354
- const body = JSON.parse(init.body);
355
- expect(JSON.stringify(body)).not.toContain('test-key');
356
- });
357
- });
358
-
359
- describe('merged config layers', () => {
360
- it('reads the provider config for the supplied working directory', async () => {
361
- readConfig.mockReturnValue({
362
- provider: {
363
- custom: { options: { apiKey: 'test-key', baseURL: 'https://proxy.example.test/v1' } },
364
- },
365
- });
366
- fetchMock.mockResolvedValue(ok('ok'));
367
-
368
- await callSmallModel({
369
- auth: {},
370
- catalog: {},
371
- workingDirectory: '/path/to/project',
372
- providerID: 'custom',
373
- modelID: 'gpt-4o-mini',
374
- prompt: 'hi',
375
- });
376
-
377
- // readConfig merges global + project-scoped layers for this directory;
378
- // confirm callSmallModel passes the working directory straight through.
379
- expect(readConfig).toHaveBeenCalledWith('/path/to/project');
380
- });
381
- });
382
- });
@@ -1,183 +0,0 @@
1
- import fs from 'fs';
2
- import fsPromises from 'fs/promises';
3
- import os from 'os';
4
- import path from 'path';
5
- import { OpenCode } from '@opencode/client';
6
-
7
- // Temporary OpenCode path for providers that lack a dedicated small-model
8
- // adapter (plugin providers, region/credential-chain, …).
9
- // Auth, endpoint rewrite, and token refresh stay inside the OpenCode runtime.
10
- //
11
- // Prefer official `generate.text` (no session). The deny-all agent markdown in
12
- // the lazy temp directory remains available if a future caller needs a session
13
- // workspace, but pure-text small-model calls must not open coding sessions.
14
-
15
- export const SMALL_MODEL_AGENT_NAME = 'openchamber-smallmodel';
16
- export const SESSION_SETTLE_TIMEOUT_MS = 60_000;
17
-
18
- const AGENT_MARKDOWN = `---
19
- mode: primary
20
- hidden: true
21
- permissions:
22
- - action: "*"
23
- resource: "*"
24
- effect: deny
25
- ---
26
-
27
- You are a utility text generator. Reply with only the requested text. Do not use tools.
28
- `;
29
-
30
- /** @type {string | null} */
31
- let tempDirectory = null;
32
- /** @type {Promise<string> | null} */
33
- let tempDirectoryInflight = null;
34
-
35
- const clientErrorMessage = (error, fallback) => {
36
- if (typeof error?.message === 'string' && error.message.trim()) return error.message;
37
- return fallback;
38
- };
39
-
40
- const ensureTempDirectory = async () => {
41
- if (tempDirectory) return tempDirectory;
42
- if (!tempDirectoryInflight) {
43
- tempDirectoryInflight = (async () => {
44
- const root = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'openchamber-smallmodel-'));
45
- const agentDir = path.join(root, '.opencode', 'agent');
46
- await fsPromises.mkdir(agentDir, { recursive: true });
47
- await fsPromises.writeFile(
48
- path.join(agentDir, `${SMALL_MODEL_AGENT_NAME}.md`),
49
- AGENT_MARKDOWN,
50
- 'utf8',
51
- );
52
- tempDirectory = root;
53
- return root;
54
- })().finally(() => {
55
- tempDirectoryInflight = null;
56
- });
57
- }
58
- return tempDirectoryInflight;
59
- };
60
-
61
- const createClient = ({ buildOpenCodeUrl, getOpenCodeAuthHeaders }) => {
62
- const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
63
- return OpenCode.make({
64
- baseUrl,
65
- headers: getOpenCodeAuthHeaders(),
66
- });
67
- };
68
-
69
- /**
70
- * Generate text via official OpenCode `generate.text`.
71
- * Errors are never masked as empty success.
72
- *
73
- * @param {{
74
- * buildOpenCodeUrl: (pathname: string, search?: string) => string,
75
- * getOpenCodeAuthHeaders: () => Record<string, string>,
76
- * providerID: string,
77
- * modelID: string,
78
- * prompt: string,
79
- * system?: string,
80
- * purpose?: string,
81
- * directory?: string,
82
- * settleTimeoutMs?: number,
83
- * }} options
84
- */
85
- export async function generateViaOpenCodeSession({
86
- buildOpenCodeUrl,
87
- getOpenCodeAuthHeaders,
88
- providerID,
89
- modelID,
90
- prompt,
91
- system,
92
- purpose: _purpose,
93
- directory,
94
- settleTimeoutMs = SESSION_SETTLE_TIMEOUT_MS,
95
- }) {
96
- if (typeof prompt !== 'string' || !prompt.trim()) {
97
- throw new Error('OpenCode small-model session requires a prompt');
98
- }
99
- if (typeof providerID !== 'string' || !providerID.trim() || typeof modelID !== 'string' || !modelID.trim()) {
100
- throw new Error('OpenCode small-model session requires providerID and modelID');
101
- }
102
-
103
- const budgetMs = Number(settleTimeoutMs) > 0 ? Number(settleTimeoutMs) : SESSION_SETTLE_TIMEOUT_MS;
104
- // Keep the deny-all agent workspace warm for isolation invariants / future
105
- // session needs, but pure text uses generate.text (no coding session).
106
- await ensureTempDirectory();
107
-
108
- const client = createClient({
109
- buildOpenCodeUrl,
110
- getOpenCodeAuthHeaders,
111
- });
112
-
113
- const locationDirectory = typeof directory === 'string' && directory.trim()
114
- ? directory.trim()
115
- : null;
116
- const location = locationDirectory ? { directory: locationDirectory } : undefined;
117
-
118
- const fullPrompt = [
119
- typeof system === 'string' && system.trim() ? system.trim() : '',
120
- prompt.trim(),
121
- ].filter(Boolean).join('\n\n');
122
-
123
- const controller = new AbortController();
124
- const timeout = setTimeout(() => {
125
- controller.abort(new Error(`OpenCode small-model generate timed out after ${budgetMs}ms`));
126
- }, budgetMs);
127
-
128
- try {
129
- let result;
130
- try {
131
- result = await client.generate.text({
132
- ...(location ? { location } : {}),
133
- prompt: fullPrompt,
134
- model: { id: modelID, providerID },
135
- }, { signal: controller.signal });
136
- } catch (error) {
137
- if (controller.signal.aborted) {
138
- throw controller.signal.reason instanceof Error
139
- ? controller.signal.reason
140
- : new Error(`OpenCode small-model generate timed out after ${budgetMs}ms`);
141
- }
142
- throw new Error(
143
- `OpenCode small-model generate.text failed: ${clientErrorMessage(error, 'generate.text failed')}`,
144
- );
145
- }
146
-
147
- const text = typeof result?.text === 'string' ? result.text : '';
148
- if (!text.trim()) {
149
- throw new Error('OpenCode small-model generate.text returned no assistant text');
150
- }
151
- return text;
152
- } finally {
153
- clearTimeout(timeout);
154
- }
155
- }
156
-
157
- /** Best-effort cleanup of the lazy temp directory (agent markdown + empty root). */
158
- export async function stop() {
159
- const dir = tempDirectory;
160
- tempDirectory = null;
161
- tempDirectoryInflight = null;
162
- if (!dir) return;
163
- try {
164
- await fsPromises.rm(dir, { recursive: true, force: true });
165
- } catch (error) {
166
- // Sync fallback if async rm races a partially-created tree.
167
- try {
168
- fs.rmSync(dir, { recursive: true, force: true });
169
- } catch {
170
- console.warn('[small-model] failed to remove temp directory:', error?.message || error);
171
- }
172
- }
173
- }
174
-
175
- /** @internal test helpers */
176
- export const _test = {
177
- getTempDirectory: () => tempDirectory,
178
- resetTempDirectory: () => {
179
- tempDirectory = null;
180
- tempDirectoryInflight = null;
181
- },
182
- AGENT_MARKDOWN,
183
- };