@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
@@ -0,0 +1,281 @@
1
+ /**
2
+ * OpenCode v2 SessionMessageInfo → Host `{ info, parts }` projection.
3
+ *
4
+ * Shared by the web session-turn-pages routes and the VS Code turn-page
5
+ * bridge so both runtimes expose the same external record contract.
6
+ * Pure: no I/O, no logging.
7
+ */
8
+
9
+ /**
10
+ * Native SessionMessageInfo carrier fields that become `parts`.
11
+ * Must not remain on `info` after normalize — otherwise
12
+ * includeReasoning=false can strip parts while leaking content/text.
13
+ */
14
+ const MESSAGE_PART_CARRIER_KEYS = new Set(['content', 'text', 'files', 'parts', 'agents', 'skills']);
15
+
16
+ /**
17
+ * v2 session-lifecycle / control rows. OpenCode lists them alongside chat
18
+ * turns; projecting `type`→`role` made cold reload paint empty Assistant
19
+ * headers (FINAL-GATE hard reload). Align with UI session-projection-api.
20
+ */
21
+ const CONTROL_MESSAGE_TYPES = new Set([
22
+ 'idle',
23
+ 'model-switched',
24
+ 'agent-selected',
25
+ 'agent-switched',
26
+ 'location-switched',
27
+ ]);
28
+
29
+ const CHAT_MESSAGE_TYPES = new Set([
30
+ 'user',
31
+ 'assistant',
32
+ 'system',
33
+ 'synthetic',
34
+ 'compaction',
35
+ ]);
36
+
37
+ const asNonEmptyString = (value) => (
38
+ typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined
39
+ );
40
+
41
+ const stripMessagePartCarriers = (value) => {
42
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
43
+ let changed = false;
44
+ const next = {};
45
+ for (const [key, field] of Object.entries(value)) {
46
+ if (MESSAGE_PART_CARRIER_KEYS.has(key)) {
47
+ changed = true;
48
+ continue;
49
+ }
50
+ next[key] = field;
51
+ }
52
+ return changed ? next : value;
53
+ };
54
+
55
+ /**
56
+ * True for v2 control rows that must never reach ChatMessage props.
57
+ * Accepts raw SessionMessageInfo or already-projected `{ info, parts }`.
58
+ */
59
+ const isControlSessionMessage = (entry) => {
60
+ if (!entry || typeof entry !== 'object') return false;
61
+ const info = entry.info && typeof entry.info === 'object' ? entry.info : entry;
62
+ const type = asNonEmptyString(info.type);
63
+ const role = asNonEmptyString(info.role) ?? asNonEmptyString(info.clientRole);
64
+ if (type && CONTROL_MESSAGE_TYPES.has(type)) return true;
65
+ if (role && CONTROL_MESSAGE_TYPES.has(role)) return true;
66
+ return false;
67
+ };
68
+
69
+ /**
70
+ * Project SessionMessageInfo body fields into host `parts`.
71
+ *
72
+ * v2 assistants put body in `content[]`. Users put body in top-level `text`.
73
+ * Never expand both: the same string in `text` and `content[].text` produced
74
+ * duplicate text parts (FINAL-GATE follow-up cold props).
75
+ */
76
+ const projectV2Parts = (entry) => {
77
+ if (Array.isArray(entry?.parts)) return entry.parts;
78
+ const messageID = typeof entry?.id === 'string' ? entry.id : '';
79
+ const sessionID = typeof entry?.sessionID === 'string' ? entry.sessionID : '';
80
+ const parts = [];
81
+ const content = Array.isArray(entry?.content) ? entry.content : null;
82
+ const hasContent = Boolean(content && content.length > 0);
83
+
84
+ if (hasContent) {
85
+ let textOrdinal = 0;
86
+ let reasoningOrdinal = 0;
87
+ content.forEach((item, index) => {
88
+ if (!item || typeof item !== 'object') return;
89
+ if (item.type === 'text') {
90
+ const partID = asNonEmptyString(item.id) || `${messageID}:text:${textOrdinal}`;
91
+ textOrdinal += 1;
92
+ parts.push({
93
+ id: partID,
94
+ sessionID,
95
+ messageID,
96
+ type: 'text',
97
+ text: typeof item.text === 'string' ? item.text : '',
98
+ });
99
+ return;
100
+ }
101
+ if (item.type === 'reasoning') {
102
+ const partID = asNonEmptyString(item.id) || `${messageID}:reasoning:${reasoningOrdinal}`;
103
+ reasoningOrdinal += 1;
104
+ parts.push({
105
+ id: partID,
106
+ sessionID,
107
+ messageID,
108
+ type: 'reasoning',
109
+ text: typeof item.text === 'string' ? item.text : '',
110
+ });
111
+ return;
112
+ }
113
+ if (item.type === 'tool') {
114
+ const partID = asNonEmptyString(item.id) || `${messageID}:tool:${index}`;
115
+ parts.push({
116
+ id: partID,
117
+ sessionID,
118
+ messageID,
119
+ type: 'tool',
120
+ tool: item.name,
121
+ callID: item.id,
122
+ state: item.state ?? {},
123
+ });
124
+ }
125
+ });
126
+ } else if (typeof entry?.text === 'string' && entry.text) {
127
+ // User / synthetic body when content[] is absent.
128
+ parts.push({
129
+ id: `${messageID}:text:0`,
130
+ sessionID,
131
+ messageID,
132
+ type: 'text',
133
+ text: entry.text,
134
+ });
135
+ }
136
+
137
+ if (Array.isArray(entry?.files)) {
138
+ entry.files.forEach((file, index) => {
139
+ parts.push({
140
+ id: `${messageID}:file:${index}`,
141
+ sessionID,
142
+ messageID,
143
+ type: 'file',
144
+ mime: file?.mime || 'application/octet-stream',
145
+ url: file?.uri ?? file?.url,
146
+ ...(file?.name ? { filename: file.name } : {}),
147
+ });
148
+ });
149
+ }
150
+ return parts;
151
+ };
152
+
153
+ /**
154
+ * Build clean chat `info` from a native SessionMessageInfo row.
155
+ * Does not spread wire carriers (`type`, `rawFinish`, `outcome`, `content`).
156
+ */
157
+ const projectNativeSessionInfo = (entry, role) => {
158
+ const info = {
159
+ id: entry.id,
160
+ ...(asNonEmptyString(entry.sessionID) ? { sessionID: entry.sessionID } : {}),
161
+ role,
162
+ time: entry.time && typeof entry.time === 'object' ? entry.time : { created: 0 },
163
+ };
164
+
165
+ const agent = asNonEmptyString(entry.agent) ?? asNonEmptyString(entry.mode);
166
+ if (agent) info.agent = agent;
167
+ if (asNonEmptyString(entry.mode) && entry.mode !== agent) info.mode = entry.mode;
168
+
169
+ const model = entry.model && typeof entry.model === 'object' && !Array.isArray(entry.model)
170
+ ? entry.model
171
+ : null;
172
+ const modelID = asNonEmptyString(model?.id)
173
+ ?? asNonEmptyString(model?.modelID)
174
+ ?? asNonEmptyString(entry.modelID);
175
+ const providerID = asNonEmptyString(model?.providerID) ?? asNonEmptyString(entry.providerID);
176
+ const variant = asNonEmptyString(model?.variant) ?? asNonEmptyString(entry.variant);
177
+ if (modelID) info.modelID = modelID;
178
+ if (providerID) info.providerID = providerID;
179
+ if (variant) info.variant = variant;
180
+ if (modelID || providerID || variant) {
181
+ info.model = {
182
+ ...(providerID ? { providerID } : {}),
183
+ ...(modelID ? { modelID } : {}),
184
+ ...(variant ? { variant } : {}),
185
+ };
186
+ }
187
+
188
+ const finish = asNonEmptyString(entry.finish) ?? asNonEmptyString(entry.rawFinish);
189
+ if (finish) info.finish = finish;
190
+
191
+ if (entry.error && typeof entry.error === 'object' && !Array.isArray(entry.error)) {
192
+ info.error = {
193
+ type: asNonEmptyString(entry.error.type) ?? 'error',
194
+ message: typeof entry.error.message === 'string' ? entry.error.message : '',
195
+ ...(typeof entry.error.status === 'number' ? { status: entry.error.status } : {}),
196
+ };
197
+ }
198
+
199
+ if (typeof entry.cost === 'number' && Number.isFinite(entry.cost)) info.cost = entry.cost;
200
+ if (entry.tokens && typeof entry.tokens === 'object' && !Array.isArray(entry.tokens)) {
201
+ info.tokens = entry.tokens;
202
+ }
203
+ if (asNonEmptyString(entry.parentID)) info.parentID = entry.parentID;
204
+ if (entry.summary != null) info.summary = entry.summary;
205
+ if (role === 'compaction') info.clientRole = 'compaction';
206
+ if (role === 'system') info.clientRole = 'system';
207
+ if (role === 'synthetic') {
208
+ // Synthetic rows render as user-authored system injections in the UI.
209
+ info.role = 'user';
210
+ info.clientRole = 'system';
211
+ }
212
+
213
+ return info;
214
+ };
215
+
216
+ /**
217
+ * Project SessionMessageInfo (or an already-projected {info, parts} row)
218
+ * into the host HTTP DTO. External route contract stays {info, parts}.
219
+ * Returns `null` for v2 control rows (idle / model-switched / …).
220
+ */
221
+ export const projectSessionMessage = (entry) => {
222
+ if (isControlSessionMessage(entry)) return null;
223
+
224
+ if (entry && typeof entry === 'object' && entry.info && typeof entry.info === 'object') {
225
+ if (isControlSessionMessage(entry.info)) return null;
226
+ const stripped = stripMessagePartCarriers(entry.info);
227
+ // Already-projected chat rows: keep shape, drop leftover wire carriers.
228
+ const info = { ...stripped };
229
+ delete info.type;
230
+ delete info.rawFinish;
231
+ delete info.outcome;
232
+ // Normalize nested model.id → modelID when a prior path left wire shape.
233
+ if (info.model && typeof info.model === 'object' && !Array.isArray(info.model)) {
234
+ const modelID = asNonEmptyString(info.model.modelID)
235
+ ?? asNonEmptyString(info.model.id)
236
+ ?? asNonEmptyString(info.modelID);
237
+ const providerID = asNonEmptyString(info.model.providerID) ?? asNonEmptyString(info.providerID);
238
+ const variant = asNonEmptyString(info.model.variant) ?? asNonEmptyString(info.variant);
239
+ if (modelID) info.modelID = modelID;
240
+ if (providerID) info.providerID = providerID;
241
+ info.model = {
242
+ ...(providerID ? { providerID } : {}),
243
+ ...(modelID ? { modelID } : {}),
244
+ ...(variant ? { variant } : {}),
245
+ };
246
+ }
247
+ const role = asNonEmptyString(info.role) ?? asNonEmptyString(info.clientRole);
248
+ if (!role || CONTROL_MESSAGE_TYPES.has(role)) return null;
249
+ return {
250
+ info,
251
+ parts: Array.isArray(entry.parts) ? entry.parts : [],
252
+ };
253
+ }
254
+
255
+ if (!entry || typeof entry !== 'object' || typeof entry.id !== 'string' || entry.id.length === 0) {
256
+ return entry == null ? null : entry;
257
+ }
258
+
259
+ const type = asNonEmptyString(entry.type);
260
+ if (type && CONTROL_MESSAGE_TYPES.has(type)) return null;
261
+ if (type && !CHAT_MESSAGE_TYPES.has(type)) return null;
262
+
263
+ let role;
264
+ if (type === 'user' || type === 'assistant' || type === 'system' || type === 'compaction' || type === 'synthetic') {
265
+ role = type;
266
+ } else if (asNonEmptyString(entry.role) === 'user' || asNonEmptyString(entry.role) === 'assistant') {
267
+ role = entry.role;
268
+ } else {
269
+ return null;
270
+ }
271
+
272
+ return {
273
+ info: projectNativeSessionInfo(entry, role),
274
+ parts: projectV2Parts(entry),
275
+ };
276
+ };
277
+
278
+ export const projectSessionMessageRecords = (records) =>
279
+ (Array.isArray(records) ? records : [])
280
+ .map(projectSessionMessage)
281
+ .filter((record) => record != null && typeof record === 'object' && record.info);
@@ -1,143 +1,103 @@
1
1
  # Small Model
2
2
 
3
- Server-side LLM calls that reuse the user's existing OpenCode provider logins.
4
- Dedicated adapters still read `~/.local/share/opencode/auth.json`. Plugin and
5
- other non-dedicated providers are listed from OpenCode's connected catalog
6
- (`provider.list()` ∩ `config.providers`, same as `loadConnectedCatalog`).
7
- OpenCode uses a "small model" internally
3
+ Server-side utility LLM calls (commit messages, session titles, goal audits,
4
+ scheduled-task distill, TTS summaries, PR descriptions) routed through the
5
+ user's connected OpenCode providers. OpenCode uses a "small model" internally
8
6
  (titles, summaries) but does not expose it through the SDK or plugins — this
9
- module replicates that mechanism as an OpenChamber runtime API.
7
+ module replicates the selection as an OpenChamber runtime API and sends every
8
+ OpenCode-backed call through the Assistant LLM gateway
9
+ (`llm/completions.js` `createChatCompletion`).
10
10
 
11
11
  ## Security boundary
12
12
 
13
- Credentials never leave the server process. The client sends only a prompt;
14
- auth resolution, OAuth refresh, and provider dispatch all happen server-side
15
- (either in dedicated adapters or inside the OpenCode runtime for the session
16
- path). Routes live under `/api/*` and are gated by the ui-auth middleware like
17
- every other runtime API.
13
+ Credentials never leave OpenCode. This module does not read `auth.json`, does
14
+ not refresh OAuth tokens, and does not call provider APIs directly. The client
15
+ sends only a prompt; model resolution happens server-side and generation runs
16
+ inside the OpenCode runtime. The only direct HTTP call is the user-configured
17
+ custom Summary AI endpoint, with the token the user saved for it. Routes live
18
+ under `/api/*` and are gated by the ui-auth middleware like every other
19
+ runtime API.
18
20
 
19
21
  ## Files
20
22
 
21
23
  - `index.js` — `createSmallModelService(dependencies)` factory. Production
22
24
  obtains a single lazy instance from `server/index.js` (`getSmallModelService`)
23
25
  and injects it into feature routes, session assist/title/goal, and scheduled
24
- tasks. Tests may call the factory with a mock `getModelCatalog`. Returns
25
- `stop()` to remove the lazy temp directory used by the session path (wired
26
- from `server/index.js` `stop()`).
27
- - `opencode-session.js` — fallback for non-dedicated providers. Lazily prepares
28
- `mkdtemp('openchamber-smallmodel-')` with a hidden deny-all agent markdown
29
- (isolation invariant), then calls official `@opencode/client`
30
- `generate.text({ location?, prompt, model: { id, providerID } })`. No coding
31
- session / `promptAsync` / `tool.ids` path. Empty text is never success.
32
- - `resolve.js` — model selection, mirroring OpenCode's `getSmallModel` chain:
26
+ tasks. Tests may inject `getModelCatalog`, `openCodeClientFactory`, and
27
+ `createChatCompletion`. The host also injects `persistSessionMetadata` /
28
+ `onSystemSessionPersisted` so gateway throwaway sessions stay out of the
29
+ sidebar index.
30
+ - `resolve.js` — model selection over the connected catalog only, mirroring
31
+ OpenCode's `getSmallModel` chain:
33
32
  0. OpenChamber's own settings override (Settings → Sessions → Small Model):
34
33
  when `smallModelUseDefault` is `false`, `smallModelOverride`
35
34
  (`provider/model`) outranks everything below. Sanitized in
36
35
  `settings-helpers.js` (server), `persistence.ts` (client), and
37
36
  `bridge-settings-runtime.ts` (VS Code).
38
37
  1. `small_model` from the merged OpenCode config layers (`provider/model`).
39
- 2. Family-priority scan (`gemini-flash` → `gpt-nano` → `claude-haiku`)
40
- **within the session's provider first** (`preferredProviderID`, like
41
- OpenCode resolves within the current provider), then keyword tiers on that
42
- provider's catalog, then the session model, then other authenticated
43
- providers' catalog families, then keyword+cost default across providers,
44
- then the four minimal hardcoded candidates.
45
- 3. GitHub Copilot hidden utility models (`gpt-*-nano/mini`) — these never
46
- appear in the catalog, so they participate as the `gpt-nano` family entry
47
- and as a final utility fallback.
48
- 4. Session model (`preferredModelID`) when the session provider has auth but
49
- no small family/keyword match.
50
- Auth-type constraints for the four dedicated providers: OpenAI OAuth →
51
- hardcoded codex-small; OpenAI API key → catalog families; Anthropic/Google
52
- require API keys; Copilot supports auth aliases (`copilot` / `github-copilot`).
53
-
54
- **Default ranking (no explicit config):** candidates are catalog models from
55
- usable auth entries. Keyword tiers (tokenized id / known family):
56
- `flash` → `nano` → `haiku` → `mini` → `lite` → `turbo` → `instant` →
57
- `small` → `chat`. Within a tier: `cost.input` ascending (missing cost last),
58
- then newer `release_date`.
38
+ 2. When the session's provider (`preferredProviderID`) is in the connected
39
+ catalog: family-priority scan (`gemini-flash` → `gpt-nano` →
40
+ `claude-haiku`), then keyword tiers on that provider, then the session
41
+ model itself.
42
+ 3. Otherwise the other connected providers: family scan, then keyword+cost
43
+ default across providers.
44
+ 4. Nothing connected → `null` (routes answer 404).
45
+
46
+ **Keyword ranking:** tokenized id / known family tiers `flash` → `nano` →
47
+ `haiku` → `mini` → `lite` → `turbo` → `instant` → `small` → `chat`. Within a
48
+ tier: `cost.input` ascending (missing cost last), then newer `release_date`.
59
49
  - Input clamp: the prompt is truncated to the resolved model's catalog
60
50
  `limit.context` (minus an output reserve, ~4 chars/token estimate;
61
- conservative default when the model is not in the catalog). Truncation is
62
- reported as `inputTruncated: true` in the response.
63
- - `call.js` — dedicated wire formats and per-provider auth for the four
64
- adapters (replicating OpenCode plugin auth loaders):
65
- - **GitHub Copilot**: OpenAI-compatible `/chat/completions` on
66
- `https://api.githubcopilot.com` (or `copilot-api.<enterprise>`) with the
67
- stored device-OAuth token as the bearer — no token exchange, no expiry.
68
- - **OpenAI OAuth (ChatGPT plan)**: streaming Responses API on
69
- `https://chatgpt.com/backend-api/codex/responses` with
70
- `ChatGPT-Account-Id`; expired tokens are refreshed against
71
- `auth.openai.com` (single-flight) and written back to `auth.json`.
72
- - **Anthropic** (`type: api`): `/v1/messages` with `x-api-key`.
73
- - **Google** (`type: api`): `generateContent` with `x-goog-api-key`.
74
- - Custom Summary AI mode: OpenAI-compatible `/chat/completions` against the
75
- user-supplied base URL + token.
76
- Generic `api.url` chat-completions for arbitrary providers is **not** used
77
- for dispatch anymore (plugin providers need runtime rewrite); those providers
78
- take the OpenCode session path instead. The helper remains in `call.js` for
79
- the dedicated adapters and custom mode.
51
+ conservative default when the model has no limit or for the custom API).
52
+ Truncation is reported as `inputTruncated: true` in the response.
80
53
  - `catalog.js` — directory-scoped provider catalog via official
81
- `@opencode-ai/sdk/v2` `client.config.providers()`. Base URL/auth come from
82
- the composition root (`buildOpenCodeUrl` / `getOpenCodeAuthHeaders`).
83
- Per-directory short TTL (~30s) with single-flight. OpenCode failure returns
84
- an **explicit** minimal fallback catalog (OpenAI OAuth / GitHub Copilot /
85
- Google API / Anthropic API candidates only) — never an authoritative empty
86
- map. Raw small-model fields kept: `family`, `release_date`, `limit`,
54
+ `@opencode/client` `provider.list` + `model.list`. Model ids are `ModelInfo.id`
55
+ (external generate/session id). `ModelInfo.modelID` is internal and is not
56
+ preferred. v2 `cost` arrays keep the base tier's `input` / `output`. Base
57
+ URL/auth come from the composition root (`buildOpenCodeUrl` /
58
+ `getOpenCodeAuthHeaders`); the client's custom `fetch` must forward the
59
+ `init` argument (headers, method, body) or OpenCode answers 401.
60
+ Per-directory short TTL (~30s) with single-flight. OpenCode failure rejects
61
+ with `statusCode: 502` and is not cached — never a substitute or empty
62
+ catalog. Raw small-model fields kept: `family`, `release_date`, `limit`,
87
63
  `cost.input` / `cost.output`, `model.api.url`, provider `name`. Client-safe
88
64
  provider catalog projection remains in `opencode/provider-catalog.js` for
89
65
  `/api/config/catalog/providers`.
90
- - `routes.js` — `GET /api/small-model` (resolution preview),
66
+ - `routes.js` — `GET /api/small-model` (resolution preview; query
67
+ `directory`, `providerID`, `modelID`),
91
68
  `POST /api/small-model/generate` (`{ prompt, system?, maxOutputTokens?,
92
- model?, directory? }` → `{ text, providerID, modelID, source }`),
93
- `POST /api/small-model/test` (custom OpenAI-compatible probe → `{ ok, code? }`),
94
- and `POST /api/small-model/custom-models` (best-effort `{baseURL}/models`
95
- suggestions → `{ models }`). The preview response includes `callableModels`,
96
- the Provider/model allowlist consumed by Settings → Summary AI. Callable
97
- lists receive the same `directory` as describe/generate so they share the
98
- directory catalog.
99
- - `custom-api.js` — custom Summary AI probe and model listing. Failure codes
100
- are `incomplete`, `token` (401/403), `model` (404 / `model_not_found`), or
101
- `baseURL` (network / non-OpenAI-compatible). Tokens are never logged.
69
+ model?, directory?, preferredProviderID?, preferredModelID?,
70
+ restrictToPreferredProvider?, purpose? }` → `{ text, providerID, modelID,
71
+ source }`), `POST /api/small-model/test` (custom OpenAI-compatible probe →
72
+ `{ ok, code? }`), and `POST /api/small-model/custom-models` (best-effort
73
+ `{baseURL}/models` suggestions → `{ models }`).
74
+ - `custom-api.js` — custom Summary AI generation
75
+ (`generateCustomSummaryText`: OpenAI-compatible `/chat/completions`), probe,
76
+ and model listing. Probe failure codes are `incomplete`, `token` (401/403),
77
+ `model` (404 / `model_not_found`), or `baseURL` (network /
78
+ non-OpenAI-compatible). Reasoning-only or empty replies are errors, never
79
+ blank success. Tokens are never logged.
102
80
 
103
81
  ## Dispatch
104
82
 
105
- | Mode / provider | Path |
83
+ | Mode / purpose | Path |
106
84
  |---|---|
107
- | Summary AI `custom` | Direct OpenAI-compatible call (`call.js`) |
108
- | `openai` / `anthropic` / `google` / `github-copilot` with usable dedicated auth | Direct dedicated adapter (`call.js`) |
109
- | Any other usable auth + catalog model (plugin providers, …) | Temporary OpenCode session (`opencode-session.js`) |
110
-
111
- Callable Settings lists:
112
- - Dedicated adapters (`openai`, `anthropic`, `google`, `github-copilot`) stay
113
- gated by usable `auth.json` entries and the existing small-model whitelist
114
- (OpenAI OAuth → `gpt-5.4-mini`; Copilot → `gpt-5.4-nano`).
115
- - Every other provider is taken from the connected catalog (plugin / env
116
- credentials that OpenCode reports as connected), intersected with catalog
117
- model ids. No `api.url` gate. If the connected catalog request fails, the
118
- list falls back to usable `auth.json` plugin entries rather than pretending
119
- none exist.
120
- Copilot auth aliases merge to `github-copilot`.
121
-
122
- ## OpenCode session lifecycle
123
-
124
- 1. Lazy singleton temp directory under `os.tmpdir()` /
125
- `openchamber-smallmodel-*` with `.opencode/agent/openchamber-smallmodel.md`
126
- (`hidden: true`, `permissions: [{ action: "*", resource: "*", effect: deny }]`).
127
- 2. `session.create` with title `[small-model] <purpose>`, agent name, and
128
- `metadata.openchamber.smallModel = { purpose }` (sidebar invisibility also
129
- depends on a parallel filter lane for that marker).
130
- 3. Immediate `session.update({ time: { archived } })` (same pattern as
131
- assistants / scheduled tasks) so ordinary lists never flash the session.
132
- 4. `session.promptAsync` with `{ model: { providerID, modelID }, agent, system?,
133
- parts: [{ type: 'text', text, synthetic: false }] }`.
134
- 5. Poll `session.status` + `session.messages` until idle assistant completed
135
- (budget `SESSION_SETTLE_TIMEOUT_MS` = 60s). Network / timeout / empty text
136
- all throw with context — never an empty success.
137
- 6. `finally`: best-effort `session.delete`. Delete failure is logged and does
138
- not erase a successful text result.
139
- 7. Server `stop()` calls `smallModelServiceInstance.stop()` to `rm` the temp
140
- directory.
85
+ | Summary AI `custom` | User's OpenAI-compatible endpoint (`custom-api.js`) |
86
+ | Summary AI provider mode (`commit` / `session-title`) | Saved model, else OpenCode `model.default` → LLM gateway |
87
+ | Every other purpose | `resolve.js` over the connected catalog → LLM gateway |
88
+
89
+ The LLM gateway checks the model against the connected catalog (`no_provider`
90
+ otherwise) and runs `session.generate` in a throwaway session owned by the
91
+ hidden deny-all `openchamber-text` agent in the shared LLM temp directory
92
+ (see `llm/DOCUMENTATION.md`). One model call with the session's provider
93
+ context, no tool loop, no messages written; the session is removed afterwards.
94
+ `restrictToPreferredProvider` forbids resolution from switching away from the
95
+ session's provider unless the model came from an explicit choice (settings
96
+ override, OpenCode config, or request `model`).
97
+
98
+ Summary AI provider mode never uses small-model resolution. A saved model that
99
+ is not in the connected catalog fails with the gateway's `no_provider` error
100
+ instead of silently switching models.
141
101
 
142
102
  ### Skills / clean context boundary
143
103
 
@@ -147,53 +107,47 @@ by `Permission.evaluate("skill", skill.id, agent.permissions)` — a deny-all
147
107
  `action: "*"` rule therefore excludes every skill from the available list, so
148
108
  the guidance renders as "No skills are currently available." Skill **body**
149
109
  content is only injected when the skill tool runs; deny-all also blocks that
150
- tool. There is no separate agent/directory flag to disable skill guidance
151
- beyond permissions. Global `~/.agents/skills` may still be *discovered* by the
152
- OpenCode instance, but they are not listed or loadable under this agent.
153
- Project-level skills are absent because the temp directory has none.
110
+ tool. Global `~/.agents/skills` may still be *discovered* by the OpenCode
111
+ instance, but they are not listed or loadable under this agent. Project-level
112
+ skills are absent because the temp directory has none.
154
113
 
155
114
  ## Registration
156
115
 
157
116
  `server/index.js` creates one lazy `getSmallModelService` and injects
158
- `{ buildOpenCodeUrl, getOpenCodeAuthHeaders }` into the factory (and into
159
- session assist/title/goal, scheduled tasks, and `createFeatureRoutesRuntime`).
160
- Feature routes no longer import `small-model/index.js` top-level exports
161
- directly.
117
+ `{ buildOpenCodeUrl, getOpenCodeAuthHeaders, persistSessionMetadata,
118
+ onSystemSessionPersisted }` into the factory (and the service into session
119
+ assist/title/goal, scheduled tasks, and `createFeatureRoutesRuntime`). Server
120
+ `stop()` calls `stopLlmTempDirectory()` to remove the shared LLM temp root.
162
121
 
163
122
  ## Summary AI settings
164
123
 
165
124
  Commit-message generation and session-title refresh pass `purpose: 'commit'`
166
125
  or `purpose: 'session-title'` to `generateSmallModelText`. Settings → Summary
167
- AI can select an authenticated OpenCode provider/model or a custom
168
- OpenAI-compatible `baseURL`, model ID, and API token. Custom mode stores the
169
- model id in `summaryCustomModelID` (legacy `summaryModelID` is still read when
170
- that field is absent). Custom mode is enabled only when base URL, model ID,
171
- and API token are all present. A custom token stays in the server settings
172
- file; settings read responses expose only `hasSummaryCustomAPIToken`. Settings
173
- can probe the custom API (`POST /api/small-model/test`) and load model
174
- suggestions from `{baseURL}/models` without blocking free-form entry.
126
+ AI selects an OpenCode provider/model with the shared `ModelSelector` and the
127
+ same connected provider list as the chat model picker and Settings → Defaults
128
+ (`useConfigStore` providers; no allowlist), or a custom OpenAI-compatible
129
+ `baseURL`, model ID, and API token. Custom mode stores the model id in
130
+ `summaryCustomModelID` (legacy `summaryModelID` is still read when that field
131
+ is absent). Custom mode is enabled only when base URL, model ID, and API token
132
+ are all present. A custom token stays in the server settings file; settings
133
+ read responses expose only `hasSummaryCustomAPIToken`. Settings can probe the
134
+ custom API (`POST /api/small-model/test`) and load model suggestions from
135
+ `{baseURL}/models` without blocking free-form entry.
175
136
 
176
137
  `summaryCommitPrompt` and `summarySessionTitlePrompt` replace the respective
177
- call's system prompt when non-empty. With no persisted provider choice, summary
178
- calls use the same effective default shown by Settings: authenticated OpenAI
179
- when available, otherwise the first callable provider/model. This effective
180
- default remains authoritative when the active session uses another provider.
138
+ call's system prompt when non-empty. With no persisted provider choice (the
139
+ picker shows "OpenCode default model"), summary calls use OpenCode's
140
+ `model.default`. No default model is an explicit 404, not a fallback guess.
141
+ The chosen or default model stays authoritative when the active session uses
142
+ another provider.
181
143
 
182
144
  ## Known limitations
183
145
 
184
- - OpenCode's free models (`opencode/big-pickle`, `*-free`) work without a
185
- token only through OpenCode's own server — direct calls are rejected, and
186
- piggybacking on their subsidized infra is out of bounds by design. Every
187
- resolution step therefore requires a usable auth entry for the provider:
188
- a session on an unauthenticated `opencode` provider falls through to the
189
- global scan (or a clean 404 on a vanilla setup with no logins).
190
-
191
- - Anthropic OAuth (Claude Pro/Max) entries are not supported on the dedicated
192
- adapter — OpenCode itself keeps those outside `auth.json` in this generation;
193
- only `type: api` keys work for Anthropic direct calls. Plugin / other auth
194
- shapes that OpenCode can already run may still succeed via the session path.
195
- - Responses from the codex backend are collected from the SSE stream; the
196
- endpoint itself is non-streaming by design (small utility calls).
197
- - The server never requests models.dev. When OpenCode is down, only the four
198
- minimal hardcoded candidates remain available for resolution; session-path
199
- generation still requires a live OpenCode runtime.
146
+ - Provider behavior is OpenCode's: whatever fails in a normal OpenCode session
147
+ fails here with the same upstream error (for example OpenCode's free
148
+ `opencode` tier rejecting non-session utility use, a plugin provider
149
+ returning an unexpected status, or an invalid provider key).
150
+ - The first call after server start (or after the temp root is recreated)
151
+ waits for OpenCode to load the fresh location's config; agent verification
152
+ polls up to 5s, so cold calls take noticeably longer than warm ones.
153
+ - `session.generate` returns a full reply; utility calls have no streaming.