@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.
- package/README.md +15 -11
- package/bin/cli-opencode.test.js +4 -4
- package/bin/cli.js +1 -1
- package/bin/lib/cli-startup.js +1 -1
- package/bin/lib/cli-startup.test.js +18 -4
- package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
- package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
- package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
- package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
- package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
- package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
- package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
- package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
- package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
- package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
- package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
- package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
- package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
- package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
- package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
- package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
- package/dist/assets/context-menu-B2LXJgXL.js +1 -0
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
- package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
- package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
- package/dist/assets/es-CN3E_zXj.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
- package/dist/assets/fr-9zZvQZD2.js +13 -0
- package/dist/assets/gitApi-yuWg9BN4.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
- package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
- package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
- package/dist/assets/ja-Dnjk_82N.js +15 -0
- package/dist/assets/ko-BxCEE99C.js +15 -0
- package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
- package/dist/assets/main-DiL6w6KF.js +169 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
- package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
- package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
- package/dist/assets/miniChat-CwtUaElU.js +2 -0
- package/dist/assets/mobile-DB-6--ws.js +2 -0
- package/dist/assets/multirun-TAsxFI8e.js +4 -0
- package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
- package/dist/assets/pl-CNNsWnPM.js +15 -0
- package/dist/assets/platform-C13fMVEs.js +6 -0
- package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
- package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
- package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
- package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
- package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
- package/dist/assets/src-ubCWXZhR.css +2 -0
- package/dist/assets/store-DLCiZaA6.js +15 -0
- package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
- package/dist/assets/textarea-BBOlRI9O.js +1 -0
- package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
- package/dist/assets/uk-rwQrxeYM.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
- package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
- package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
- package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
- package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
- package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
- package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
- package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
- package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
- package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
- package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
- package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
- package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
- package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
- package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
- package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
- package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +1 -1
- package/server/index.js +327 -62
- package/server/lib/assistants/DOCUMENTATION.md +14 -9
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +265 -18
- package/server/lib/assistants/routes.js +6 -0
- package/server/lib/assistants/service.js +417 -231
- package/server/lib/assistants/service.test.js +392 -148
- package/server/lib/conversations/DOCUMENTATION.md +1 -1
- package/server/lib/conversations/service.js +1 -0
- package/server/lib/conversations/service.test.js +16 -0
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +49 -4
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +33 -9
- package/server/lib/llm/catalog.js +1 -1
- package/server/lib/llm/completions.js +5 -1
- package/server/lib/llm/completions.test.js +3 -3
- package/server/lib/llm/generate.js +237 -145
- package/server/lib/llm/generate.test.js +201 -46
- package/server/lib/llm/routes.js +2 -0
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/notifications/runtime.js +23 -5
- package/server/lib/notifications/runtime.test.js +11 -0
- package/server/lib/opencode/DOCUMENTATION.md +85 -17
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +42 -99
- package/server/lib/opencode/config-entity-routes.test.js +107 -0
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.js +1 -2
- package/server/lib/opencode/env-runtime.js +40 -26
- package/server/lib/opencode/env-runtime.test.js +10 -17
- package/server/lib/opencode/feature-routes-runtime.js +43 -4
- package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +349 -39
- package/server/lib/opencode/lifecycle.test.js +322 -0
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +4 -4
- package/server/lib/opencode/opencode2-pin.test.js +1 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +252 -46
- package/server/lib/opencode/proxy.test.js +235 -2
- package/server/lib/opencode/routes.js +296 -62
- package/server/lib/opencode/routes.test.js +278 -12
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +33 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +20 -33
- package/server/lib/opencode/skill-routes.test.js +35 -2
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/v2-client.js +14 -3
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
- package/server/lib/scheduled-tasks/runtime.js +52 -4
- package/server/lib/scheduled-tasks/runtime.test.js +80 -1
- package/server/lib/session-goal/DOCUMENTATION.md +113 -13
- package/server/lib/session-goal/routes.js +9 -0
- package/server/lib/session-goal/runtime.js +1579 -117
- package/server/lib/session-goal/runtime.test.js +1453 -84
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +161 -34
- package/server/lib/session-index/sync-runtime.test.js +155 -1
- package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
- package/server/lib/session-metadata/routes.js +108 -9
- package/server/lib/session-metadata/routes.test.js +47 -2
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
- package/server/lib/session-metadata/session-metadata-store.js +212 -72
- package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +255 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +67 -42
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
- package/server/lib/session-turn-pages/routes.js +17 -102
- package/server/lib/session-turn-pages/routes.test.js +281 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -147
- package/server/lib/small-model/catalog.js +31 -51
- package/server/lib/small-model/catalog.test.js +65 -10
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +124 -240
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +2 -0
- package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
- package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
- package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
- package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
- package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
- package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
- package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
- package/dist/assets/context-menu-jGtH6fR9.js +0 -1
- package/dist/assets/es-c52LTHHe.js +0 -15
- package/dist/assets/fr-PisFbUE8.js +0 -13
- package/dist/assets/gitApi-ButwzQ0N.js +0 -9
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
- package/dist/assets/ja-C8Ikun3Z.js +0 -15
- package/dist/assets/ko-ClFys5o2.js +0 -15
- package/dist/assets/main-Dn9rpsgl.js +0 -169
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/miniChat-BpjxVqgg.js +0 -2
- package/dist/assets/mobile-nMJCaNq1.js +0 -2
- package/dist/assets/multirun-BvSG94mW.js +0 -4
- package/dist/assets/pl-CnaKWN5F.js +0 -15
- package/dist/assets/platform-CWTIp02q.js +0 -4
- package/dist/assets/projectMeta-DOe49dM_.js +0 -3
- package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
- package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
- package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
- package/dist/assets/src-BzX5UI-c.css +0 -2
- package/dist/assets/store-DJczmczv.js +0 -15
- package/dist/assets/textarea-3XR8sUfy.js +0 -1
- package/dist/assets/uk-CaX0iqkB.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
- package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/zh-CN-20MYEjha.js +0 -15
- package/dist/assets/zh-TW-DiN04qTw.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -183
- 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
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
25
|
-
`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 /
|
|
83
|
+
| Mode / purpose | Path |
|
|
106
84
|
|---|---|
|
|
107
|
-
| Summary AI `custom` |
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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.
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
default
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
-
|
|
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.
|