@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
|
@@ -1,41 +1,14 @@
|
|
|
1
1
|
import { OpenCode } from '@opencode/client';
|
|
2
2
|
|
|
3
3
|
// Directory-scoped OpenCode provider catalog for small-model resolution.
|
|
4
|
-
// Never contacts models.dev — source of truth is
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Never contacts models.dev — source of truth is provider.list + model.list,
|
|
5
|
+
// the same connected catalog the LLM gateway checks before generating.
|
|
6
|
+
// ModelInfo.id is the external model id; ModelInfo.modelID is internal.
|
|
7
|
+
// OpenCode failure is an explicit error, never a substitute catalog.
|
|
7
8
|
|
|
8
9
|
const CATALOG_TTL_MS = 30_000;
|
|
9
10
|
const CATALOG_TIMEOUT_MS = 8_000;
|
|
10
11
|
|
|
11
|
-
// Minimal catalog when OpenCode is unreachable. OpenAI OAuth and Copilot use
|
|
12
|
-
// fixed utility models outside the catalog; Google/Anthropic need family
|
|
13
|
-
// entries so the family-priority scan can still pick a small model.
|
|
14
|
-
const MINIMAL_FALLBACK_CATALOG = Object.freeze({
|
|
15
|
-
google: Object.freeze({
|
|
16
|
-
id: 'google',
|
|
17
|
-
name: 'Google',
|
|
18
|
-
models: Object.freeze({
|
|
19
|
-
'gemini-2.5-flash': Object.freeze({
|
|
20
|
-
id: 'gemini-2.5-flash',
|
|
21
|
-
family: 'gemini-flash',
|
|
22
|
-
release_date: '2025-06-01',
|
|
23
|
-
}),
|
|
24
|
-
}),
|
|
25
|
-
}),
|
|
26
|
-
anthropic: Object.freeze({
|
|
27
|
-
id: 'anthropic',
|
|
28
|
-
name: 'Anthropic',
|
|
29
|
-
models: Object.freeze({
|
|
30
|
-
'claude-haiku-4-5': Object.freeze({
|
|
31
|
-
id: 'claude-haiku-4-5',
|
|
32
|
-
family: 'claude-haiku',
|
|
33
|
-
release_date: '2025-10-01',
|
|
34
|
-
}),
|
|
35
|
-
}),
|
|
36
|
-
}),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
12
|
const normalizeDirectoryKey = (directory) => {
|
|
40
13
|
if (typeof directory !== 'string') return '';
|
|
41
14
|
const trimmed = directory.trim();
|
|
@@ -46,6 +19,18 @@ const normalizeDirectoryKey = (directory) => {
|
|
|
46
19
|
|
|
47
20
|
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
48
21
|
|
|
22
|
+
// v2 ModelInfo.cost is a tier array. Ranking still reads cost.input / cost.output.
|
|
23
|
+
const projectCatalogCost = (cost) => {
|
|
24
|
+
const entry = Array.isArray(cost)
|
|
25
|
+
? (cost.find((item) => isRecord(item) && !isRecord(item.tier)) || cost.find((item) => isRecord(item)))
|
|
26
|
+
: (isRecord(cost) ? cost : null);
|
|
27
|
+
if (!isRecord(entry)) return undefined;
|
|
28
|
+
const projected = Object.create(null);
|
|
29
|
+
if (Number.isFinite(entry.input)) projected.input = entry.input;
|
|
30
|
+
if (Number.isFinite(entry.output)) projected.output = entry.output;
|
|
31
|
+
return Object.keys(projected).length > 0 ? projected : undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
49
34
|
// Compose official v2 provider.list + model.list into the SDK catalog shape
|
|
50
35
|
// consumed by toSmallModelCatalog. Missing arrays are failure, not empty success.
|
|
51
36
|
const composeV2ProviderCatalogSource = (providers, models) => {
|
|
@@ -55,9 +40,11 @@ const composeV2ProviderCatalogSource = (providers, models) => {
|
|
|
55
40
|
for (const model of models) {
|
|
56
41
|
if (!isRecord(model)) continue;
|
|
57
42
|
const providerID = typeof model.providerID === 'string' ? model.providerID : '';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
// ModelInfo.id is the external model id used in generate/session refs.
|
|
44
|
+
// ModelInfo.modelID is a separate internal field and must not be preferred.
|
|
45
|
+
const modelID = typeof model.id === 'string' && model.id
|
|
46
|
+
? model.id
|
|
47
|
+
: (typeof model.modelID === 'string' ? model.modelID : '');
|
|
61
48
|
if (!providerID || !modelID) continue;
|
|
62
49
|
if (!modelsByProvider.has(providerID)) modelsByProvider.set(providerID, Object.create(null));
|
|
63
50
|
const entry = { id: modelID };
|
|
@@ -68,6 +55,8 @@ const composeV2ProviderCatalogSource = (providers, models) => {
|
|
|
68
55
|
if (Number.isFinite(model.limit.output)) limit.output = model.limit.output;
|
|
69
56
|
if (Object.keys(limit).length > 0) entry.limit = limit;
|
|
70
57
|
}
|
|
58
|
+
const cost = projectCatalogCost(model.cost);
|
|
59
|
+
if (cost) entry.cost = cost;
|
|
71
60
|
if (isRecord(model.api) && typeof model.api.url === 'string' && model.api.url.trim()) {
|
|
72
61
|
entry.api = { url: model.api.url.trim() };
|
|
73
62
|
}
|
|
@@ -166,7 +155,7 @@ export function createModelCatalogLoader({
|
|
|
166
155
|
timeoutMs = CATALOG_TIMEOUT_MS,
|
|
167
156
|
fetchImpl = globalThis.fetch.bind(globalThis),
|
|
168
157
|
}) {
|
|
169
|
-
/** @type {Map<string, { catalog: object, cachedAt: number,
|
|
158
|
+
/** @type {Map<string, { catalog: object | null, cachedAt: number, inflight: Promise<object> | null }>} */
|
|
170
159
|
const buckets = new Map();
|
|
171
160
|
|
|
172
161
|
const fetchOpenCodeCatalog = async (directory) => {
|
|
@@ -174,7 +163,7 @@ export function createModelCatalogLoader({
|
|
|
174
163
|
const client = OpenCode.make({
|
|
175
164
|
baseUrl,
|
|
176
165
|
headers: getOpenCodeAuthHeaders(),
|
|
177
|
-
fetch: (request) => fetchImpl(request, { signal: AbortSignal.timeout(timeoutMs) }),
|
|
166
|
+
fetch: (request, init) => fetchImpl(request, { ...init, signal: AbortSignal.timeout(timeoutMs) }),
|
|
178
167
|
});
|
|
179
168
|
const location = directory ? { directory } : undefined;
|
|
180
169
|
const request = location ? { location } : undefined;
|
|
@@ -194,8 +183,8 @@ export function createModelCatalogLoader({
|
|
|
194
183
|
};
|
|
195
184
|
|
|
196
185
|
/**
|
|
197
|
-
* Load (or serve cached) directory-scoped catalog.
|
|
198
|
-
*
|
|
186
|
+
* Load (or serve cached) directory-scoped catalog. Failures are not cached
|
|
187
|
+
* and reject with statusCode 502 so callers never resolve against a guess.
|
|
199
188
|
*/
|
|
200
189
|
const getModelCatalog = async (directory) => {
|
|
201
190
|
const key = normalizeDirectoryKey(directory);
|
|
@@ -206,7 +195,7 @@ export function createModelCatalogLoader({
|
|
|
206
195
|
}
|
|
207
196
|
|
|
208
197
|
if (!bucket) {
|
|
209
|
-
bucket = { catalog: null, cachedAt: 0,
|
|
198
|
+
bucket = { catalog: null, cachedAt: 0, inflight: null };
|
|
210
199
|
buckets.set(key, bucket);
|
|
211
200
|
}
|
|
212
201
|
|
|
@@ -216,18 +205,12 @@ export function createModelCatalogLoader({
|
|
|
216
205
|
const catalog = await fetchOpenCodeCatalog(key || undefined);
|
|
217
206
|
bucket.catalog = catalog;
|
|
218
207
|
bucket.cachedAt = Date.now();
|
|
219
|
-
bucket.source = 'opencode';
|
|
220
208
|
return catalog;
|
|
221
209
|
} catch (error) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
error?.message || error,
|
|
210
|
+
throw Object.assign(
|
|
211
|
+
new Error(`OpenCode provider catalog is unavailable (${error?.message || error})`),
|
|
212
|
+
{ statusCode: 502 },
|
|
226
213
|
);
|
|
227
|
-
bucket.catalog = MINIMAL_FALLBACK_CATALOG;
|
|
228
|
-
bucket.cachedAt = Date.now();
|
|
229
|
-
bucket.source = 'fallback';
|
|
230
|
-
return MINIMAL_FALLBACK_CATALOG;
|
|
231
214
|
} finally {
|
|
232
215
|
bucket.inflight = null;
|
|
233
216
|
}
|
|
@@ -242,8 +225,5 @@ export function createModelCatalogLoader({
|
|
|
242
225
|
getCatalogProvider,
|
|
243
226
|
/** @internal test helpers */
|
|
244
227
|
_normalizeDirectoryKey: normalizeDirectoryKey,
|
|
245
|
-
_minimalFallbackCatalog: MINIMAL_FALLBACK_CATALOG,
|
|
246
228
|
};
|
|
247
229
|
}
|
|
248
|
-
|
|
249
|
-
export { MINIMAL_FALLBACK_CATALOG };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createModelCatalogLoader, toSmallModelCatalog
|
|
2
|
+
import { createModelCatalogLoader, toSmallModelCatalog } from './catalog.js';
|
|
3
3
|
|
|
4
4
|
describe('toSmallModelCatalog', () => {
|
|
5
5
|
it('keeps family, release_date, limit, cost, model.api.url, and provider name', () => {
|
|
@@ -45,13 +45,15 @@ describe('toSmallModelCatalog', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
describe('createModelCatalogLoader', () => {
|
|
48
|
-
it('
|
|
48
|
+
it('rejects with 502 when OpenCode is unreachable and does not cache the failure', async () => {
|
|
49
|
+
let calls = 0;
|
|
49
50
|
const loader = createModelCatalogLoader({
|
|
50
51
|
buildOpenCodeUrl: () => 'http://127.0.0.1:9/',
|
|
51
52
|
getOpenCodeAuthHeaders: () => ({}),
|
|
52
53
|
ttlMs: 30_000,
|
|
53
54
|
timeoutMs: 50,
|
|
54
55
|
fetchImpl: async () => {
|
|
56
|
+
calls += 1;
|
|
55
57
|
throw new Error('network down');
|
|
56
58
|
},
|
|
57
59
|
});
|
|
@@ -59,13 +61,34 @@ describe('createModelCatalogLoader', () => {
|
|
|
59
61
|
const a = loader.getModelCatalog('/proj');
|
|
60
62
|
const b = loader.getModelCatalog('/proj/');
|
|
61
63
|
// Single-flight + directory key normalization: same bucket for trailing slash.
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
const results = await Promise.allSettled([a, b]);
|
|
65
|
+
for (const result of results) {
|
|
66
|
+
expect(result.status).toBe('rejected');
|
|
67
|
+
expect(result.reason.statusCode).toBe(502);
|
|
68
|
+
expect(result.reason.message).toMatch(/provider catalog is unavailable/);
|
|
69
|
+
}
|
|
70
|
+
const callsAfterFirst = calls;
|
|
71
|
+
await expect(loader.getModelCatalog('/proj')).rejects.toMatchObject({ statusCode: 502 });
|
|
72
|
+
expect(calls).toBeGreaterThan(callsAfterFirst);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('forwards OpenCode auth headers through the fetch wrapper', async () => {
|
|
76
|
+
const seen = [];
|
|
77
|
+
const loader = createModelCatalogLoader({
|
|
78
|
+
buildOpenCodeUrl: () => 'http://127.0.0.1:4096/',
|
|
79
|
+
getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic test' }),
|
|
80
|
+
fetchImpl: async (url, init) => {
|
|
81
|
+
seen.push(new Headers(init?.headers).get('authorization'));
|
|
82
|
+
return new Response(JSON.stringify({ data: [] }), {
|
|
83
|
+
status: 200,
|
|
84
|
+
headers: { 'content-type': 'application/json' },
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await loader.getModelCatalog('/proj');
|
|
90
|
+
expect(seen.length).toBeGreaterThan(0);
|
|
91
|
+
expect(seen.every((value) => value === 'Basic test')).toBe(true);
|
|
69
92
|
});
|
|
70
93
|
|
|
71
94
|
it('composes official v2 provider.list + model.list into the small-model catalog', async () => {
|
|
@@ -99,7 +122,6 @@ describe('createModelCatalogLoader', () => {
|
|
|
99
122
|
});
|
|
100
123
|
|
|
101
124
|
const catalog = await loader.getModelCatalog('/proj');
|
|
102
|
-
expect(catalog).not.toBe(MINIMAL_FALLBACK_CATALOG);
|
|
103
125
|
expect(catalog.google).toEqual({
|
|
104
126
|
id: 'google',
|
|
105
127
|
name: 'Google',
|
|
@@ -115,6 +137,39 @@ describe('createModelCatalogLoader', () => {
|
|
|
115
137
|
});
|
|
116
138
|
});
|
|
117
139
|
|
|
140
|
+
it('keys models by ModelInfo.id and keeps the base tier of array cost', async () => {
|
|
141
|
+
const loader = createModelCatalogLoader({
|
|
142
|
+
buildOpenCodeUrl: () => 'http://127.0.0.1:4096/',
|
|
143
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
144
|
+
fetchImpl: async (url) => {
|
|
145
|
+
const href = String(url);
|
|
146
|
+
const data = href.includes('/api/model')
|
|
147
|
+
? [{
|
|
148
|
+
id: 'gpt-5.4-mini',
|
|
149
|
+
modelID: 'internal-pack',
|
|
150
|
+
providerID: 'openai',
|
|
151
|
+
name: 'GPT-5.4 Mini',
|
|
152
|
+
cost: [
|
|
153
|
+
{ tier: { type: 'context', size: 200000 }, input: 9, output: 9 },
|
|
154
|
+
{ input: 1, output: 2 },
|
|
155
|
+
],
|
|
156
|
+
}]
|
|
157
|
+
: [{ id: 'openai', name: 'OpenAI', package: '@ai-sdk/openai' }];
|
|
158
|
+
return new Response(JSON.stringify({ data }), {
|
|
159
|
+
status: 200,
|
|
160
|
+
headers: { 'content-type': 'application/json' },
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const catalog = await loader.getModelCatalog('/proj');
|
|
166
|
+
expect(catalog.openai?.models?.['gpt-5.4-mini']).toEqual({
|
|
167
|
+
id: 'gpt-5.4-mini',
|
|
168
|
+
cost: { input: 1, output: 2 },
|
|
169
|
+
});
|
|
170
|
+
expect(catalog.openai?.models?.['internal-pack']).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
118
173
|
it('normalizes directory keys so trailing slashes share a bucket', () => {
|
|
119
174
|
const loader = createModelCatalogLoader({
|
|
120
175
|
buildOpenCodeUrl: () => 'http://127.0.0.1:9/',
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const TEST_TIMEOUT_MS = 15_000;
|
|
2
2
|
const MODELS_TIMEOUT_MS = 8_000;
|
|
3
|
+
const GENERATE_TIMEOUT_MS = 60_000;
|
|
4
|
+
// Generous default: thinking models that can't be switched off spend part of
|
|
5
|
+
// this budget on reasoning before the actual answer.
|
|
6
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 4_000;
|
|
3
7
|
|
|
4
8
|
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
9
|
|
|
@@ -96,6 +100,70 @@ export async function testCustomSummaryApi({
|
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Summary AI custom mode: one non-streaming chat completion against the
|
|
105
|
+
* user's own OpenAI-compatible endpoint. The token never leaves this process.
|
|
106
|
+
*/
|
|
107
|
+
export async function generateCustomSummaryText({
|
|
108
|
+
baseURL,
|
|
109
|
+
apiToken,
|
|
110
|
+
modelID,
|
|
111
|
+
prompt,
|
|
112
|
+
system,
|
|
113
|
+
maxOutputTokens,
|
|
114
|
+
fetchImpl = globalThis.fetch.bind(globalThis),
|
|
115
|
+
}) {
|
|
116
|
+
const parsedBaseURL = parseCustomApiBaseURL(baseURL);
|
|
117
|
+
if (!parsedBaseURL) {
|
|
118
|
+
throw Object.assign(new Error('Custom summary API Base URL is invalid'), { statusCode: 400 });
|
|
119
|
+
}
|
|
120
|
+
const response = await fetchImpl(`${parsedBaseURL}/chat/completions`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
Accept: 'application/json',
|
|
125
|
+
Authorization: `Bearer ${apiToken}`,
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
model: modelID,
|
|
129
|
+
messages: [
|
|
130
|
+
...(system ? [{ role: 'system', content: system }] : []),
|
|
131
|
+
{ role: 'user', content: prompt },
|
|
132
|
+
],
|
|
133
|
+
max_tokens: Number(maxOutputTokens) > 0 ? Number(maxOutputTokens) : DEFAULT_MAX_OUTPUT_TOKENS,
|
|
134
|
+
stream: false,
|
|
135
|
+
}),
|
|
136
|
+
signal: AbortSignal.timeout(GENERATE_TIMEOUT_MS),
|
|
137
|
+
});
|
|
138
|
+
const bodyText = await response.text().catch(() => '');
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const snippet = bodyText ? `: ${bodyText.slice(0, 300)}` : '';
|
|
141
|
+
throw new Error(`Custom summary API request failed with ${response.status}${snippet}`);
|
|
142
|
+
}
|
|
143
|
+
const payload = parseJson(bodyText);
|
|
144
|
+
const message = payload?.choices?.[0]?.message;
|
|
145
|
+
// Providers disagree on the content shape: plain string, an array of typed
|
|
146
|
+
// parts, or (thinking models) empty content with the budget spent on
|
|
147
|
+
// reasoning_content.
|
|
148
|
+
let text = '';
|
|
149
|
+
if (typeof message?.content === 'string') {
|
|
150
|
+
text = message.content;
|
|
151
|
+
} else if (Array.isArray(message?.content)) {
|
|
152
|
+
text = message.content.map((part) => (typeof part?.text === 'string' ? part.text : '')).join('');
|
|
153
|
+
}
|
|
154
|
+
if (!text.trim() && typeof message?.reasoning_content === 'string' && message.reasoning_content.trim()) {
|
|
155
|
+
const finishReason = payload?.choices?.[0]?.finish_reason;
|
|
156
|
+
throw new Error(
|
|
157
|
+
'Custom summary API spent the output budget on reasoning and returned no answer'
|
|
158
|
+
+ (finishReason ? ` (finish_reason: ${finishReason})` : ''),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (!text.trim()) {
|
|
162
|
+
throw new Error('Custom summary API returned no message content');
|
|
163
|
+
}
|
|
164
|
+
return text;
|
|
165
|
+
}
|
|
166
|
+
|
|
99
167
|
export async function listCustomSummaryModels({
|
|
100
168
|
baseURL,
|
|
101
169
|
apiToken,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import {
|
|
3
3
|
classifyCustomSummaryApiFailure,
|
|
4
|
+
generateCustomSummaryText,
|
|
4
5
|
listCustomSummaryModels,
|
|
5
6
|
parseCustomApiBaseURL,
|
|
6
7
|
testCustomSummaryApi,
|
|
@@ -99,3 +100,66 @@ describe('listCustomSummaryModels', () => {
|
|
|
99
100
|
})).toEqual([]);
|
|
100
101
|
});
|
|
101
102
|
});
|
|
103
|
+
|
|
104
|
+
describe('generateCustomSummaryText', () => {
|
|
105
|
+
const completion = (message, extra = {}) => new Response(JSON.stringify({
|
|
106
|
+
choices: [{ message, ...extra }],
|
|
107
|
+
}), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
108
|
+
|
|
109
|
+
it('posts an OpenAI-compatible chat completion and returns the text', async () => {
|
|
110
|
+
const fetchImpl = vi.fn(async () => completion({ role: 'assistant', content: 'feat: add retries' }));
|
|
111
|
+
const text = await generateCustomSummaryText({
|
|
112
|
+
baseURL: 'https://summary.example.test/v1/',
|
|
113
|
+
apiToken: 'token',
|
|
114
|
+
modelID: 'summary-model',
|
|
115
|
+
prompt: 'Diff',
|
|
116
|
+
system: 'Return a commit subject.',
|
|
117
|
+
maxOutputTokens: 64,
|
|
118
|
+
fetchImpl,
|
|
119
|
+
});
|
|
120
|
+
expect(text).toBe('feat: add retries');
|
|
121
|
+
const [url, init] = fetchImpl.mock.calls[0];
|
|
122
|
+
expect(url).toBe('https://summary.example.test/v1/chat/completions');
|
|
123
|
+
expect(init.method).toBe('POST');
|
|
124
|
+
expect(JSON.parse(init.body)).toEqual({
|
|
125
|
+
model: 'summary-model',
|
|
126
|
+
messages: [
|
|
127
|
+
{ role: 'system', content: 'Return a commit subject.' },
|
|
128
|
+
{ role: 'user', content: 'Diff' },
|
|
129
|
+
],
|
|
130
|
+
max_tokens: 64,
|
|
131
|
+
stream: false,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('joins array content parts', async () => {
|
|
136
|
+
const fetchImpl = vi.fn(async () => completion({ content: [{ type: 'text', text: 'a' }, { type: 'text', text: 'b' }] }));
|
|
137
|
+
await expect(generateCustomSummaryText({
|
|
138
|
+
baseURL: 'https://summary.example.test/v1', apiToken: 't', modelID: 'm', prompt: 'p', fetchImpl,
|
|
139
|
+
})).resolves.toBe('ab');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('rejects invalid base URLs before any request', async () => {
|
|
143
|
+
const fetchImpl = vi.fn();
|
|
144
|
+
await expect(generateCustomSummaryText({
|
|
145
|
+
baseURL: 'ftp://nope', apiToken: 't', modelID: 'm', prompt: 'p', fetchImpl,
|
|
146
|
+
})).rejects.toMatchObject({ statusCode: 400 });
|
|
147
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('surfaces HTTP failures, reasoning-only replies, and empty content as errors', async () => {
|
|
151
|
+
const input = { baseURL: 'https://summary.example.test/v1', apiToken: 't', modelID: 'm', prompt: 'p' };
|
|
152
|
+
await expect(generateCustomSummaryText({
|
|
153
|
+
...input,
|
|
154
|
+
fetchImpl: async () => new Response('rate limited', { status: 429 }),
|
|
155
|
+
})).rejects.toThrow(/failed with 429/);
|
|
156
|
+
await expect(generateCustomSummaryText({
|
|
157
|
+
...input,
|
|
158
|
+
fetchImpl: async () => completion({ content: '', reasoning_content: 'thinking…' }, { finish_reason: 'length' }),
|
|
159
|
+
})).rejects.toThrow(/reasoning.*finish_reason: length/);
|
|
160
|
+
await expect(generateCustomSummaryText({
|
|
161
|
+
...input,
|
|
162
|
+
fetchImpl: async () => completion({ content: '' }),
|
|
163
|
+
})).rejects.toThrow(/no message content/);
|
|
164
|
+
});
|
|
165
|
+
});
|