@openhands/agent-canvas 1.0.0-alpha.9 → 1.0.0-beta.2
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 +2 -2
- package/bin/agent-canvas.mjs +22 -2
- package/build/assets/{QueryClientProvider-B7kl84Kj.js → QueryClientProvider-CkGuhXg-.js} +1 -1
- package/build/assets/{Trans-1j65oy9O.js → Trans-Cvm_-SMi.js} +1 -1
- package/build/assets/acp-providers-CbiRekh9.js +1 -0
- package/build/assets/{acp-route-guard-CQTmeJwM.js → acp-route-guard-B2yoBZ_4.js} +1 -1
- package/build/assets/{active-backend-context-TVbjnvmP.js → active-backend-context-cCM1vYYZ.js} +1 -1
- package/build/assets/add-backend-modal-DIUQzMPa.js +1 -0
- package/build/assets/agent-server-client-options-Bc5ZorQZ.js +1 -0
- package/build/assets/agent-server-compatibility-BlkUsrX2.js +1 -0
- package/build/assets/agent-server-conversation-service.api-DFvqqEDo.js +5 -0
- package/build/assets/{agent-settings-B247S9G3.js → agent-settings-CnGSCmK8.js} +1 -1
- package/build/assets/{alert-banner-BWoqueRw.js → alert-banner-DtzAX654.js} +1 -1
- package/build/assets/{analytics-consent-form-modal-C7sXfxRh.js → analytics-consent-form-modal-CHZ3I37v.js} +1 -1
- package/build/assets/api-key-entry-screen-B2gynaCp.js +1 -0
- package/build/assets/{app-settings-BVeSaty9.js → app-settings-Db9ITeJH.js} +1 -1
- package/build/assets/{automation-detail-R-99FUce.js → automation-detail-Di7EOIZD.js} +1 -1
- package/build/assets/{automations-list-Dfu2c-_D.js → automations-list-IsIWdDiw.js} +1 -1
- package/build/assets/{backend-form-modal-DxYjqqAK.js → backend-form-modal-Dnk33xA_.js} +1 -1
- package/build/assets/{backend-synced-settings-badge-nAfiUWvM.js → backend-synced-settings-badge-Dc6c7GT4.js} +1 -1
- package/build/assets/{base-modal-CQRvRHu1.js → base-modal-_dYTw1ri.js} +1 -1
- package/build/assets/{brand-button-C2nEKopC.js → brand-button-Br7f0kZJ.js} +1 -1
- package/build/assets/{browser-HrYc5Gce.js → browser-D810xUYt.js} +2 -2
- package/build/assets/browser-store-Couc4S5D.js +1 -0
- package/build/assets/browser-tab-B-aIqXRl.js +1 -0
- package/build/assets/{checkmark-BJJrZUF8.js → checkmark-DL7acQA7.js} +1 -1
- package/build/assets/{chevron-left-small-CSh-sE9L.js → chevron-left-small-CVWf8TI6.js} +1 -1
- package/build/assets/{circle-plus-check-toggle-qs8Va1cC.js → circle-plus-check-toggle-P7ZZToV4.js} +1 -1
- package/build/assets/{clock-ZR4Kn-_Y.js → clock-BRjCgHTc.js} +1 -1
- package/build/assets/{close-BdmyeRqS.js → close-B5LROHR3.js} +1 -1
- package/build/assets/{combobox-caret-B53O9Hsq.js → combobox-caret-to1O8irE.js} +1 -1
- package/build/assets/{condenser-settings-A35V3yng.js → condenser-settings-wnEKhBof.js} +1 -1
- package/build/assets/{confirmation-modal-C9-La0h3.js → confirmation-modal-Dau3w_sa.js} +1 -1
- package/build/assets/{context-menu-list-item-Buu9nc0q.js → context-menu-list-item-CWNFpuiC.js} +1 -1
- package/build/assets/conversation-HlncOV7n.js +19 -0
- package/build/assets/conversation-MtnkpqA9.js +1 -0
- package/build/assets/conversation-panel-DxnM6tRe.js +1 -0
- package/build/assets/{conversation-service.api--f8WglOC.js → conversation-service.api-nb5W1PqR.js} +1 -1
- package/build/assets/{conversation-tab-empty-state-D8dNvo-V.js → conversation-tab-empty-state-DyssnnWa.js} +1 -1
- package/build/assets/conversation-websocket-context-C8_PkGLi.js +3 -0
- package/build/assets/{copy-C7Ti2d8C.js → copy-DYgmUdIw.js} +1 -1
- package/build/assets/{custom-toast-handlers-BOc3qeQ7.js → custom-toast-handlers-C-SZFmto.js} +1 -1
- package/build/assets/declaration-BNMqORFE.js +1 -0
- package/build/assets/{device-verify-CMusn8nX.js → device-verify-DqDlphsG.js} +1 -1
- package/build/assets/{dist-DZHSA2e6.js → dist-C6t0EXL7.js} +1 -1
- package/build/assets/{edit-automation-modal-Dnjxbjn7.js → edit-automation-modal-BGzR3nfZ.js} +1 -1
- package/build/assets/{ellipsis-button-ugUATsNo.js → ellipsis-button-ZyLMPURn.js} +1 -1
- package/build/assets/{entry.client-CqqXOSvd.js → entry.client-1VMHpktY.js} +2 -2
- package/build/assets/{enum-filter-dropdown-1vpOGySB.js → enum-filter-dropdown-CEgCdu4A.js} +1 -1
- package/build/assets/{environment-switch-overlay-CTCTQikP.js → environment-switch-overlay-XL8yCGP6.js} +1 -1
- package/build/assets/{extensions-hub-BSUseHVF.js → extensions-hub-C651jsVh.js} +1 -1
- package/build/assets/{extensions-navigation-CT1kc1u_.js → extensions-navigation-BYR8Giqq.js} +1 -1
- package/build/assets/files-tab-BhnLgimi.js +1 -0
- package/build/assets/{folder-0WSMImNX.js → folder-ZZJVGgd7.js} +1 -1
- package/build/assets/{git-control-bar-branch-button-C8u5rzjc.js → git-control-bar-branch-button-M34A5_vX.js} +2 -2
- package/build/assets/{git-provider-icon-D-a-rcLm.js → git-provider-icon-D5dCNy-k.js} +1 -1
- package/build/assets/home-CYQv7yc_.js +1 -0
- package/build/assets/{i18n-DjAGhTis.js → i18n-CTohRuoO.js} +1 -1
- package/build/assets/install-server-modal-f31_CLrW.js +1 -0
- package/build/assets/{launch-B2mbfOSm.js → launch-DHEUYn2A.js} +1 -1
- package/build/assets/{lesson-plan-DRYG5SLI.js → lesson-plan-dH5Bj0pN.js} +1 -1
- package/build/assets/{link-external-C9d6Fo3x.js → link-external-D2POYx4c.js} +1 -1
- package/build/assets/{llm-client-ChQzg4wX.js → llm-client-DaH1TuyR.js} +1 -1
- package/build/assets/llm-settings-Bql-vydt.js +1 -0
- package/build/assets/llm-settings-C_tal6Ds.js +1 -0
- package/build/assets/{loading-spinner-C04FGh14.js → loading-spinner-BPtYORNK.js} +1 -1
- package/build/assets/{manage-backends-modal-s22zCdEW.js → manage-backends-modal-l7RkKfwX.js} +1 -1
- package/build/assets/{manage-workspaces-modal-C5EuW8m1.js → manage-workspaces-modal-DhKF_8z3.js} +1 -1
- package/build/assets/manifest-6b31008e.js +1 -0
- package/build/assets/{markdown-renderer-CEX4Becj.js → markdown-renderer-DMzf2i4x.js} +1 -1
- package/build/assets/mcp-D2onbwVk.js +9 -0
- package/build/assets/{messages-6aOyUu3r.js → messages-BMzyOW2V.js} +1 -1
- package/build/assets/{modal-backdrop-DTYGVmOR.js → modal-backdrop-BAbgYsqB.js} +1 -1
- package/build/assets/{modal-body-YElmM1dV.js → modal-body-BI6Ru2Qr.js} +1 -1
- package/build/assets/{modal-close-button-C_GpQt9F.js → modal-close-button-t1Gh3qmL.js} +1 -1
- package/build/assets/{model-selector-DeMmw-Xa.js → model-selector-SM9IUz-q.js} +1 -1
- package/build/assets/{mutation-Cz7N4XAo.js → mutation-D0OogFCz.js} +1 -1
- package/build/assets/{navigation-context-DeIPtGPp.js → navigation-context-D0YWpT8d.js} +1 -1
- package/build/assets/{navigation-link-C9JD4PYD.js → navigation-link-Cn7KP3c5.js} +1 -1
- package/build/assets/{openhands-logo-CI5Fhn1W.js → openhands-logo-CnrF6LKb.js} +1 -1
- package/build/assets/{option-service.api-DsI1UW7N.js → option-service.api-KvY_mZMY.js} +1 -1
- package/build/assets/{organization-service.api-COwMPFg5.js → organization-service.api-DzYTHTYC.js} +1 -1
- package/build/assets/{path-utils-BVbe598W.js → path-utils-YohAYyMv.js} +1 -1
- package/build/assets/{plan-components-DEjMuDDG.js → plan-components-atxXCF0R.js} +1 -1
- package/build/assets/{planner-tab-bN6r1G-1.js → planner-tab-CFc-hV07.js} +1 -1
- package/build/assets/{profiles-client-BGkKEV9j.js → profiles-client-D6IkTJof.js} +1 -1
- package/build/assets/{providers-DXvCAN_u.js → providers-Bx6EfrzZ.js} +1 -1
- package/build/assets/{proxy-CurRmrqf.js → proxy-CxydCnis.js} +1 -1
- package/build/assets/{query-client-config-Ba7qAAoO.js → query-client-config-B7u9asM0.js} +1 -1
- package/build/assets/{recommended-automations-launcher-mJhK6Atl.js → recommended-automations-launcher-sgvfU62c.js} +3 -3
- package/build/assets/root-E6pcRIYp.js +2 -0
- package/build/assets/{root-layout-BjVwHmta.js → root-layout-DVepR4To.js} +2 -2
- package/build/assets/sdk-section-page-DOIKvwSL.js +1 -0
- package/build/assets/{sdk-settings-schema-QBYH-ONX.js → sdk-settings-schema-DsUf9wu1.js} +1 -1
- package/build/assets/{search-Cq_cFrDt.js → search-27Owlc3A.js} +1 -1
- package/build/assets/{secrets-service-Bwd5DeUs.js → secrets-service-BsnKFc2x.js} +1 -1
- package/build/assets/secrets-settings-Bz_UohPJ.js +1 -0
- package/build/assets/{server-client-C3mC8Hl3.js → server-client-DyAQ3NZ_.js} +1 -1
- package/build/assets/{settings-D7E2U5tK.js → settings-BYkVX7vW.js} +1 -1
- package/build/assets/{settings-client-CwjfwoiB.js → settings-client-C73C7IgV.js} +1 -1
- package/build/assets/{settings-dropdown-input-VwAXNrOb.js → settings-dropdown-input-BJYvGdg-.js} +1 -1
- package/build/assets/{settings-gear-BJwWR1ej.js → settings-gear-C77PgE_O.js} +1 -1
- package/build/assets/{settings-index-J-3BNR0W.js → settings-index-Dz0BmdJD.js} +1 -1
- package/build/assets/{settings-input-DBywAnA7.js → settings-input-Bn7F5C75.js} +1 -1
- package/build/assets/{settings-list-classes-BOS092DR.js → settings-list-classes-Bf80tWtc.js} +1 -1
- package/build/assets/{settings-modal-B8vgWDTe.js → settings-modal-Brzgh5Yw.js} +1 -1
- package/build/assets/{settings-section-header-context-32x6WTyL.js → settings-section-header-context-BgZe5YkE.js} +1 -1
- package/build/assets/{settings-service.api-FvJGK45W.js → settings-service.api-CZ3uWx4v.js} +1 -1
- package/build/assets/{settings-switch-DTKmHC8F.js → settings-switch-BeIKrWms.js} +1 -1
- package/build/assets/{shared-conversation-EZV0FRIf.js → shared-conversation-DChOdb0t.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-BnbzzpQl.js → sidebar-mobile-menu-toggle-BWuf4PRH.js} +1 -1
- package/build/assets/{sidebar-nav-link-CnWoZcwc.js → sidebar-nav-link-BGjiJq-4.js} +1 -1
- package/build/assets/{skill-card-pill-row-tZ599jli.js → skill-card-pill-row-DF1axQCG.js} +1 -1
- package/build/assets/{skills-ZyAO5dyK.js → skills-ChIKZPK4.js} +1 -1
- package/build/assets/{skills-plugins-BSRz041I.js → skills-plugins-CcI_19lM.js} +1 -1
- package/build/assets/{skills-settings-CG2hu34D.js → skills-settings-DlA5hlXw.js} +1 -1
- package/build/assets/{status-CsatcFbK.js → status-hp6M6E7E.js} +1 -1
- package/build/assets/{styled-tooltip-CS3mB_1X.js → styled-tooltip-CBzrri6o.js} +1 -1
- package/build/assets/{switch-skeleton-C-CfhYYV.js → switch-skeleton-DnC9wLp7.js} +1 -1
- package/build/assets/{task-list-tab-465DDju0.js → task-list-tab-DUJn1sgz.js} +1 -1
- package/build/assets/{terminal-CcgBEVnC.js → terminal-CRf9S0Z2.js} +1 -1
- package/build/assets/{terminal-LNa-iU5c.js → terminal-RmuaSdhJ.js} +1 -1
- package/build/assets/{toggle-switch-k-IZCDbt.js → toggle-switch-Pvyp2RAN.js} +1 -1
- package/build/assets/{typography-vVUMoNUg.js → typography-gpuWmrQO.js} +1 -1
- package/build/assets/{u-check-circle-DplbarS5.js → u-check-circle-IUIfACQQ.js} +1 -1
- package/build/assets/{u-check-circle-half-yDuiSZHC.js → u-check-circle-half-C1YxB6py.js} +1 -1
- package/build/assets/{u-circuit-C9tYkpeK.js → u-circuit-BmVikJHu.js} +1 -1
- package/build/assets/{u-edit-KAUlufD8.js → u-edit-CFvXHqZk.js} +1 -1
- package/build/assets/use-active-conversation-Db3IWSPK.js +1 -0
- package/build/assets/{use-agent-settings-schema-Bvp5UzV8.js → use-agent-settings-schema-33Un7UF2.js} +1 -1
- package/build/assets/{use-agent-state-D2C9SeGw.js → use-agent-state-Bn8vS5sY.js} +1 -1
- package/build/assets/{use-cloud-current-user-id-DWVar4st.js → use-cloud-current-user-id-CvkXFnTT.js} +1 -1
- package/build/assets/use-config-Co1O8-Ey.js +1 -0
- package/build/assets/{use-create-conversation-BEZg__Vv.js → use-create-conversation-CKS3EAHu.js} +1 -1
- package/build/assets/use-get-secrets-DuhdIA59.js +1 -0
- package/build/assets/{use-handle-plan-click-uOpew2LO.js → use-handle-plan-click-C9zJpK8A.js} +1 -1
- package/build/assets/use-is-authed-BggE5wPj.js +1 -0
- package/build/assets/{use-is-creating-conversation-DhDeeWfA.js → use-is-creating-conversation-BZ5hB_Bg.js} +1 -1
- package/build/assets/{use-launch-skill-in-chat-DVGPFrbI.js → use-launch-skill-in-chat-fNN_xGZG.js} +1 -1
- package/build/assets/{use-llm-profiles-O4a9V6RC.js → use-llm-profiles-DDOol3gK.js} +1 -1
- package/build/assets/use-runtime-is-ready-CQCE3xZC.js +1 -0
- package/build/assets/{use-save-settings-CEEKSTWG.js → use-save-settings-VUrj_QNG.js} +1 -1
- package/build/assets/{use-settings-DQ7Oo1Hj.js → use-settings-DQIZmIov.js} +1 -1
- package/build/assets/{use-settings-nav-items-YmrXrjn9.js → use-settings-nav-items-1ZvovKSr.js} +1 -1
- package/build/assets/{use-skills-BIvlWblA.js → use-skills-DAMLFjKU.js} +1 -1
- package/build/assets/use-unified-vscode-url-sZt29HrC.js +1 -0
- package/build/assets/use-user-conversation-DfgEB6RW.js +1 -0
- package/build/assets/{useMutation-B4OUESdw.js → useMutation-DqrumCWD.js} +1 -1
- package/build/assets/{useTranslation-CpIcQBq6.js → useTranslation-DCOdSSMl.js} +1 -1
- package/build/assets/{utils-D-HX7JCe.js → utils-i18rdUj2.js} +1 -1
- package/build/assets/v4-CNn21NXa.js +1 -0
- package/build/assets/{vendor~browser-Dr71AdrG.js → vendor~browser-BNjNhjFU.js} +1 -1
- package/build/assets/{vendor~browser-tab-BiVxfjJo.js → vendor~browser-tab-BgwV1mxF.js} +1 -1
- package/build/assets/{vendor~conversation-panel~conversation-BlCIz9XQ.js → vendor~conversation-panel~conversation-a9SyrrhV.js} +1 -1
- package/build/assets/{vendor~files-tab-DtLR-QD9.js → vendor~files-tab-BGKayPiK.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-Ds9quNZ9.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-smY2r837.js} +1 -1
- package/build/assets/{vendor~home~mcp~automations-list-C5PoHCy6.js → vendor~home~mcp~automations-list-Ccy2I0KU.js} +1 -1
- package/build/assets/{vendor~home~mcp~automations-list-BUBGGGYz.js → vendor~home~mcp~automations-list-DoPfwaXj.js} +1 -1
- package/build/assets/{vendor~home~mcp~llm-settings~agent-settings~condenser-settings~verification-settings~app-se~ocm3mykx-CGlZoBKa.js → vendor~home~mcp~llm-settings~agent-settings~condenser-settings~verification-settings~app-se~ocm3mykx-DbfELDJu.js} +2 -2
- package/build/assets/{vendor~home~mcp~llm-settings~agent-settings~condenser-settings~verification-settings~app-se~ocm3mykx-DE11mPxp.js → vendor~home~mcp~llm-settings~agent-settings~condenser-settings~verification-settings~app-se~ocm3mykx-Z3nsiNNq.js} +1 -1
- package/build/assets/{vendor~launch-Dg--Ssk6.js → vendor~launch-vdeRTWFu.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation~shared-conversation-DrXgiSCq.js → vendor~root-layout~conversation-panel~conversation~shared-conversation-DW31UyBp.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~skil~iguv7bgw-8b8V5bfO.js → vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~skil~iguv7bgw-BkQGKpye.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~skil~iguv7bgw-Dy7L6fMG.js → vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~skil~iguv7bgw-DzIXV3Ui.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~skil~k776hupu-D40EXhZx.js → vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~skil~k776hupu-Bbs7UJ5U.js} +2 -2
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~launch~extensions-hub~skills-settin~dp08i1qy-CHrEOFl6.js → vendor~root-layout~home~conversation-panel~conversation~launch~extensions-hub~skills-settin~dp08i1qy-DTwbEEcX.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~shared-conversation~alert-banner~pl~rqjteh0a-BP1SKG0F.js → vendor~root-layout~home~conversation-panel~conversation~shared-conversation~alert-banner~pl~rqjteh0a-d2oallMa.js} +1 -1
- package/build/assets/{vendor~root~root-layout~home~conversation-panel~conversation~launch~extensions-hub~skills-s~kyz9p27j-CyUbhpbm.js → vendor~root~root-layout~home~conversation-panel~conversation~launch~extensions-hub~skills-s~f2l2lr17-CDXvdvb2.js} +1 -1
- package/build/assets/{verification-settings-BtlTiHP8.js → verification-settings-CsbvQcYS.js} +1 -1
- package/build/assets/{vscode-tab-B0vdh9gU.js → vscode-tab-Zb-QbTuV.js} +1 -1
- package/build/assets/{waiting-for-runtime-message-DWPl_Yby.js → waiting-for-runtime-message-CntjExbU.js} +1 -1
- package/build/assets/{x-mark-CWI0f9yI.js → x-mark-CrpjscNc.js} +1 -1
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +7 -0
- package/build/locales/ca/openhands.json +7 -0
- package/build/locales/de/openhands.json +7 -0
- package/build/locales/en/openhands.json +7 -0
- package/build/locales/es/openhands.json +7 -0
- package/build/locales/fr/openhands.json +7 -0
- package/build/locales/it/openhands.json +7 -0
- package/build/locales/ja/openhands.json +7 -0
- package/build/locales/ko-KR/openhands.json +7 -0
- package/build/locales/no/openhands.json +7 -0
- package/build/locales/pt/openhands.json +7 -0
- package/build/locales/tr/openhands.json +7 -0
- package/build/locales/uk/openhands.json +7 -0
- package/build/locales/zh-CN/openhands.json +7 -0
- package/build/locales/zh-TW/openhands.json +7 -0
- package/config/defaults.json +0 -4
- package/dist/api/agent-server-adapter.cjs +1 -1
- package/dist/api/agent-server-adapter.cjs.map +1 -1
- package/dist/api/agent-server-adapter.js +1 -1
- package/dist/api/agent-server-adapter.js.map +1 -1
- package/dist/api/agent-server-compatibility.cjs +1 -1
- package/dist/api/agent-server-compatibility.cjs.map +1 -1
- package/dist/api/agent-server-compatibility.d.ts +16 -0
- package/dist/api/agent-server-compatibility.js +31 -20
- package/dist/api/agent-server-compatibility.js.map +1 -1
- package/dist/api/agent-server-config.cjs +1 -1
- package/dist/api/agent-server-config.cjs.map +1 -1
- package/dist/api/agent-server-config.d.ts +45 -0
- package/dist/api/agent-server-config.js +49 -21
- package/dist/api/agent-server-config.js.map +1 -1
- package/dist/api/backend-registry/storage.cjs +1 -1
- package/dist/api/backend-registry/storage.cjs.map +1 -1
- package/dist/api/backend-registry/storage.js +34 -32
- package/dist/api/backend-registry/storage.js.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +5 -4
- package/dist/api/conversation-service/agent-server-conversation-service.api.js +70 -76
- package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
- package/dist/components/features/backends/api-key-entry-screen.d.ts +10 -0
- package/dist/components/features/backends/backend-form-modal.cjs +1 -1
- package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
- package/dist/components/features/backends/backend-form-modal.d.ts +23 -2
- package/dist/components/features/backends/backend-form-modal.js +43 -38
- package/dist/components/features/backends/backend-form-modal.js.map +1 -1
- package/dist/components/features/browser/browser.cjs +1 -1
- package/dist/components/features/browser/browser.cjs.map +1 -1
- package/dist/components/features/browser/browser.js +10 -16
- package/dist/components/features/browser/browser.js.map +1 -1
- package/dist/components/features/mcp-page/install-server-modal.cjs +1 -1
- package/dist/components/features/mcp-page/install-server-modal.cjs.map +1 -1
- package/dist/components/features/mcp-page/install-server-modal.js +123 -116
- package/dist/components/features/mcp-page/install-server-modal.js.map +1 -1
- package/dist/components/features/mcp-page/installed-server-card.cjs +1 -1
- package/dist/components/features/mcp-page/installed-server-card.cjs.map +1 -1
- package/dist/components/features/mcp-page/installed-server-card.js +40 -40
- package/dist/components/features/mcp-page/installed-server-card.js.map +1 -1
- package/dist/components/features/mcp-page/marketplace-card.cjs +1 -1
- package/dist/components/features/mcp-page/marketplace-card.cjs.map +1 -1
- package/dist/components/features/mcp-page/marketplace-card.js +2 -3
- package/dist/components/features/mcp-page/marketplace-card.js.map +1 -1
- package/dist/components/features/mcp-page/marketplace-section.cjs +1 -1
- package/dist/components/features/mcp-page/marketplace-section.cjs.map +1 -1
- package/dist/components/features/mcp-page/marketplace-section.js +21 -21
- package/dist/components/features/mcp-page/marketplace-section.js.map +1 -1
- package/dist/components/features/onboarding/steps/setup-acp-secrets-step.d.ts +27 -0
- package/dist/components/features/settings/llm-profiles/llm-settings-local-view.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/llm-settings-local-view.js +2 -0
- package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs +1 -1
- package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs.map +1 -1
- package/dist/components/features/settings/sdk-settings/sdk-section-page.d.ts +10 -1
- package/dist/components/features/settings/sdk-settings/sdk-section-page.js +87 -84
- package/dist/components/features/settings/sdk-settings/sdk-section-page.js.map +1 -1
- package/dist/constants/acp-providers.cjs +1 -1
- package/dist/constants/acp-providers.cjs.map +1 -1
- package/dist/constants/acp-providers.d.ts +25 -0
- package/dist/constants/acp-providers.js +1 -0
- package/dist/constants/acp-providers.js.map +1 -1
- package/dist/contexts/conversation-websocket-context.cjs +3 -3
- package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
- package/dist/contexts/conversation-websocket-context.js +136 -132
- package/dist/contexts/conversation-websocket-context.js.map +1 -1
- package/dist/hooks/chat/use-model-interceptor.cjs.map +1 -1
- package/dist/hooks/chat/use-model-interceptor.js.map +1 -1
- package/dist/hooks/mutation/use-switch-llm-profile.cjs.map +1 -1
- package/dist/hooks/mutation/use-switch-llm-profile.d.ts +1 -1
- package/dist/hooks/mutation/use-switch-llm-profile.js.map +1 -1
- package/dist/hooks/query/use-config.cjs +1 -1
- package/dist/hooks/query/use-config.cjs.map +1 -1
- package/dist/hooks/query/use-config.js +10 -10
- package/dist/hooks/query/use-config.js.map +1 -1
- package/dist/hooks/query/use-local-git-info.cjs +3 -3
- package/dist/hooks/query/use-local-git-info.cjs.map +1 -1
- package/dist/hooks/query/use-local-git-info.js +24 -25
- package/dist/hooks/query/use-local-git-info.js.map +1 -1
- package/dist/i18n/declaration.cjs +1 -1
- package/dist/i18n/declaration.cjs.map +1 -1
- package/dist/i18n/declaration.d.ts +7 -0
- package/dist/i18n/declaration.js +1 -1
- package/dist/i18n/declaration.js.map +1 -1
- package/dist/i18n/translation.cjs +2 -2
- package/dist/i18n/translation.cjs.map +1 -1
- package/dist/i18n/translation.js +119 -0
- package/dist/i18n/translation.js.map +1 -1
- package/dist/locales/ar/openhands.json +7 -0
- package/dist/locales/ca/openhands.json +7 -0
- package/dist/locales/de/openhands.json +7 -0
- package/dist/locales/en/openhands.json +7 -0
- package/dist/locales/es/openhands.json +7 -0
- package/dist/locales/fr/openhands.json +7 -0
- package/dist/locales/it/openhands.json +7 -0
- package/dist/locales/ja/openhands.json +7 -0
- package/dist/locales/ko-KR/openhands.json +7 -0
- package/dist/locales/no/openhands.json +7 -0
- package/dist/locales/pt/openhands.json +7 -0
- package/dist/locales/tr/openhands.json +7 -0
- package/dist/locales/uk/openhands.json +7 -0
- package/dist/locales/zh-CN/openhands.json +7 -0
- package/dist/locales/zh-TW/openhands.json +7 -0
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/routes/mcp.cjs +1 -1
- package/dist/routes/mcp.cjs.map +1 -1
- package/dist/routes/mcp.js +64 -64
- package/dist/routes/mcp.js.map +1 -1
- package/dist/stores/browser-store.cjs +1 -1
- package/dist/stores/browser-store.cjs.map +1 -1
- package/dist/stores/browser-store.js +1 -1
- package/dist/stores/browser-store.js.map +1 -1
- package/dist/utils/mcp-marketplace-utils.cjs +1 -1
- package/dist/utils/mcp-marketplace-utils.cjs.map +1 -1
- package/dist/utils/mcp-marketplace-utils.d.ts +13 -22
- package/dist/utils/mcp-marketplace-utils.js +46 -28
- package/dist/utils/mcp-marketplace-utils.js.map +1 -1
- package/dist/utils/sdk-settings-schema.cjs +1 -1
- package/dist/utils/sdk-settings-schema.cjs.map +1 -1
- package/dist/utils/sdk-settings-schema.d.ts +1 -0
- package/dist/utils/sdk-settings-schema.js +1 -1
- package/dist/utils/sdk-settings-schema.js.map +1 -1
- package/package.json +1 -1
- package/scripts/dev-safe.mjs +59 -40
- package/scripts/dev-static.mjs +2 -3
- package/scripts/dev-with-automation.mjs +75 -19
- package/scripts/static-server.mjs +77 -35
- package/tools/canvas_ui_tool.py +4 -0
- package/build/assets/acp-providers-DauuOsW9.js +0 -1
- package/build/assets/add-backend-modal-FsnpTTgO.js +0 -1
- package/build/assets/agent-server-client-options-DT2GP6VJ.js +0 -1
- package/build/assets/agent-server-compatibility-2aOx5iWd.js +0 -1
- package/build/assets/agent-server-conversation-service.api-BZmUqtiO.js +0 -5
- package/build/assets/browser-store-C3AqxAO7.js +0 -1
- package/build/assets/browser-tab-B_BuTvrO.js +0 -1
- package/build/assets/conversation--ldUK72N.js +0 -19
- package/build/assets/conversation-eNrhH94O.js +0 -1
- package/build/assets/conversation-panel-B49Jpqpb.js +0 -1
- package/build/assets/conversation-websocket-context-BW68-J8o.js +0 -3
- package/build/assets/declaration-D378OjpZ.js +0 -1
- package/build/assets/files-tab-CQHdWpQt.js +0 -1
- package/build/assets/home-DD0GroCu.js +0 -1
- package/build/assets/install-server-modal-z5VaHeXd.js +0 -1
- package/build/assets/llm-settings-BEyqixPI.js +0 -1
- package/build/assets/llm-settings-BdiaGFbg.js +0 -1
- package/build/assets/manifest-9d1c34fb.js +0 -1
- package/build/assets/mcp-C06YssEI.js +0 -9
- package/build/assets/root-3t9rxEpE.js +0 -2
- package/build/assets/sdk-section-page-CJW0G04-.js +0 -1
- package/build/assets/secrets-settings-MLXqOtX2.js +0 -1
- package/build/assets/use-active-conversation-DS5j9R4q.js +0 -1
- package/build/assets/use-config-BSu_53GL.js +0 -1
- package/build/assets/use-conversation-id-DajhCn2A.js +0 -1
- package/build/assets/use-is-authed-hXC8vxgT.js +0 -1
- package/build/assets/use-runtime-is-ready-pGSbPddC.js +0 -1
- package/build/assets/use-unified-vscode-url-wAMzv8Sn.js +0 -1
- package/build/assets/use-user-conversation-B_zDoSeh.js +0 -1
- /package/build/assets/{automation-XLxhq3I8.js → automation-IdgZq6ZK.js} +0 -0
- /package/build/assets/{common-SMkEaBSr.js → common-DR1t-EeP.js} +0 -0
- /package/build/assets/{conversation-state-store-Bc0slAjL.js → conversation-state-store-u5jepov0.js} +0 -0
- /package/build/assets/{dist-yMQV8IUk.js → dist-BxBP7tFD.js} +0 -0
- /package/build/assets/{git-status-mapper-BI8FyUVp.js → git-status-mapper-DnL9OC8_.js} +0 -0
- /package/build/assets/{handle-capture-consent-BfZATzpI.js → handle-capture-consent-3XrjZ8wi.js} +0 -0
- /package/build/assets/{iconBase-C7N9pPOs.js → iconBase-DE30Zj_-.js} +0 -0
- /package/build/assets/{settings-D5am1n6X.js → settings-D_H-qsRm.js} +0 -0
- /package/build/assets/{settings-like-page-layout-classes-Bn-M9oOa.js → settings-like-page-layout-classes-I0BDBEoq.js} +0 -0
- /package/build/assets/{settings-utils-BsvSU3OM.js → settings-utils-B6Nl07io.js} +0 -0
- /package/build/assets/{sidebar-store-cOeaKmIm.js → sidebar-store-Uy3v0AOV.js} +0 -0
- /package/build/assets/{use-breakpoint-B86yKT9n.js → use-breakpoint-DbJ6FkQ-.js} +0 -0
- /package/build/assets/{use-click-outside-element-835W9pC6.js → use-click-outside-element-DffgWWoZ.js} +0 -0
- /package/build/assets/{use-task-list-DDeNHprj.js → use-task-list-CLJbuJgM.js} +0 -0
- /package/build/assets/{vendor~browser-BpdPBhgZ.js → vendor~browser-DDiZgqD3.js} +0 -0
- /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-Df7_G0zR.js → vendor~conversation-panel~conversation~alert-banner-DbvX3OcM.js} +0 -0
- /package/build/assets/{vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~b4cctr4k-B7YVdv1X.js → vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~g56ukk6u-DsSvIDZQ.js} +0 -0
- /package/build/assets/{vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~i9dbt75i-CI82Did1.js → vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~hkqzh1hb-BZ0HXuHD.js} +0 -0
- /package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~launch~extensions-hub~skills-settin~pfbaerbd-zhv9fooy.js → vendor~root-layout~home~conversation-panel~conversation~launch~extensions-hub~skills-settin~ninslayh-D9P8e98a.js} +0 -0
- /package/build/assets/{vendor~terminal-BUxzHKcC.js → vendor~terminal-DUrOWGFE.js} +0 -0
- /package/build/assets/{vscode-url-helper-jesbpos5.js → vscode-url-helper-Cwy1A62q.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-marketplace-utils.js","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationTransport as MarketplaceTemplate,\n} from \"@openhands/extensions/integrations\";\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Get the default transport template from an integration catalog entry.\n * Integrations may have multiple connection options; we use the default\n * one (or the first if no default is specified). Only MCP-backed options\n * have a `transport` field.\n */\nexport function getDefaultTemplate(\n entry: MarketplaceEntry,\n): MarketplaceTemplate | undefined {\n const option =\n entry.connectionOptions.find(\n (o) => o.id === entry.defaultConnectionOptionId,\n ) ?? entry.connectionOptions[0];\n return option?.transport;\n}\n\n/**\n * Get the stdio (API key-based) transport template from an integration entry.\n * Many integrations have multiple connection options (e.g., OAuth + stdio).\n * Since OAuth isn't implemented in the UI yet, the install modal should use\n * this function to get the stdio-based option that can be configured with\n * API keys/tokens.\n *\n * Falls back to getDefaultTemplate if no stdio option exists.\n */\nexport function getInstallableTemplate(\n entry: MarketplaceEntry,\n): MarketplaceTemplate | undefined {\n // First, try to find a stdio option (API key-based, what we can actually install)\n const stdioOption = entry.connectionOptions.find(\n (o) => o.transport?.kind === \"stdio\",\n );\n if (stdioOption?.transport) return stdioOption.transport;\n\n // Fall back to the default template (could be shttp/sse with api_key)\n return getDefaultTemplate(entry);\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n template: MarketplaceTemplate,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n if (template.kind === \"shttp\") {\n const tplUrl = template.url;\n if (!tplUrl) return null;\n return (\n servers.find(\n (s) => s.type === \"shttp\" && !!s.url && urlsMatch(s.url, tplUrl),\n ) ?? null\n );\n }\n\n if (template.kind === \"sse\") {\n const tplUrl = template.url;\n if (!tplUrl) return null;\n return (\n servers.find(\n (s) => s.type === \"sse\" && !!s.url && urlsMatch(s.url, tplUrl),\n ) ?? null\n );\n }\n\n // stdio: match on the registered server name.\n return (\n servers.find((s) => s.type === \"stdio\" && s.name === template.serverName) ??\n null\n );\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n *\n * Since an entry may have multiple connection options (e.g., OAuth + stdio),\n * we check ALL templates in the entry's connectionOptions, not just the default.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check all connection options, not just the default\n for (const option of entry.connectionOptions) {\n const tpl = option.transport;\n if (!tpl) continue;\n if (tpl.kind === \"stdio\") {\n if (server.type === \"stdio\" && server.name === tpl.serverName)\n return true;\n }\n // Reuse the same loose URL match as `findInstalledMatch` so a\n // server whose URL was normalized by the backend (trailing slash\n // stripped, query string dropped, etc.) still gets paired with\n // its catalog tile — otherwise the installed-servers list would\n // render the generic icon while the marketplace shows the\n // entry as installed, which is confusing.\n if (tpl.kind === \"shttp\") {\n if (server.type === \"shttp\" && urlsMatch(server.url, tpl.url))\n return true;\n }\n if (tpl.kind === \"sse\") {\n if (server.type === \"sse\" && urlsMatch(server.url, tpl.url))\n return true;\n }\n }\n return false;\n });\n}\n"],"mappings":";AAMA,IAAM,KAAU,MAA4B;AAC1C,KAAI;AACF,SAAO,IAAI,IAAI,EAAI;SACb;AACN,SAAO;;;AAeX,SAAgB,EAAU,GAAY,GAAqB;CACzD,IAAM,IAAO,OAAO,KAAM,WAAW,IAAI,IACnC,IAAO,OAAO,KAAM,WAAW,IAAI;AACzC,KAAI,CAAC,KAAQ,CAAC,EAAM,QAAO;CAC3B,IAAM,IAAO,EAAO,EAAK,EACnB,IAAQ,EAAO,EAAK;AAI1B,QAHI,CAAC,KAAQ,CAAC,IACL,EAAK,QAAQ,QAAQ,GAAG,KAAK,EAAK,QAAQ,QAAQ,GAAG,GAG5D,EAAK,aAAa,EAAM,YACxB,EAAK,SAAS,EAAM,QACpB,EAAK,SAAS,QAAQ,QAAQ,GAAG,KAAK,EAAM,SAAS,QAAQ,QAAQ,GAAG;;AAU5E,SAAgB,EACd,GACiC;AAKjC,SAHE,EAAM,kBAAkB,MACrB,MAAM,EAAE,OAAO,EAAM,0BACvB,IAAI,EAAM,kBAAkB,KAChB;;AAYjB,SAAgB,EACd,GACiC;CAEjC,IAAM,IAAc,EAAM,kBAAkB,MACzC,MAAM,EAAE,WAAW,SAAS,QAC9B;AAID,QAHI,GAAa,YAAkB,EAAY,YAGxC,EAAmB,EAAM;;AAuClC,SAAgB,EACd,GACA,GACS;AAGT,QAFI,CAAC,EAAM,uBAAuB,EAAM,wBAAwB,QACvD,KACF,EAAM,wBAAwB;;AAGvC,SAAS,EAAU,GAAuB;AACxC,QAAO,EAAM,MAAM,CAAC,aAAa;;AAQnC,SAAgB,EACd,GACoB;AACpB,QAAO,EACJ,KAAK,GAAO,OAAW;EAAE;EAAO;EAAO,EAAE,CACzC,MAAM,GAAG,OAEL,EAAE,MAAM,kBAAkB,MAAM,EAAE,MAAM,kBAAkB,MACtC,EAAE,QAAQ,EAAE,MACnC,CACD,KAAK,EAAE,eAAY,EAAM;;AAU9B,SAAgB,EACd,GACA,GACS;CACT,IAAM,IAAI,EAAU,EAAS;AAU7B,QATK,IACY;EACf,EAAM;EACN,EAAM;EACN,EAAM;EACN,GAAI,EAAM,YAAY,EAAE;EACzB,CACE,KAAK,IAAI,CACT,aACI,CAAS,SAAS,EAAE,GATZ;;AAmBjB,SAAgB,EACd,GACA,GACA,GACS;CACT,IAAM,IAAI,EAAU,EAAS;AAgB7B,QAfK,IACY;EACf,EAAO;EACP,UAAU,IAAS,EAAO,OAAO,KAAA;EACjC,aAAa,IAAS,EAAO,UAAU,KAAA;EACvC,UAAU,IAAS,EAAO,MAAM,KAAK,IAAI,GAAG,KAAA;EAC5C,SAAS,IAAS,EAAO,MAAM,KAAA;EAC/B,GAAc;EACd,GAAc;EACd,GAAc;EACd,GAAI,GAAc,YAAY,EAAE;EACjC,CACE,OAAO,QAAQ,CACf,KAAK,IAAI,CACT,aACI,CAAS,SAAS,EAAE,GAfZ;;AA0BjB,SAAgB,EACd,GACA,GAC8B;AAC9B,QAAO,EAAQ,MAAM,MAAU;AAE7B,OAAK,IAAM,KAAU,EAAM,mBAAmB;GAC5C,IAAM,IAAM,EAAO;AACd,aACD,EAAI,SAAS,WACX,EAAO,SAAS,WAAW,EAAO,SAAS,EAAI,cASjD,EAAI,SAAS,WACX,EAAO,SAAS,WAAW,EAAU,EAAO,KAAK,EAAI,IAAI,IAG3D,EAAI,SAAS,SACX,EAAO,SAAS,SAAS,EAAU,EAAO,KAAK,EAAI,IAAI,EACzD,QAAO;;AAGb,SAAO;GACP"}
|
|
1
|
+
{"version":3,"file":"mcp-marketplace-utils.js","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationConnectionOption,\n IntegrationTransport,\n} from \"@openhands/extensions/integrations\";\n\nexport type { MarketplaceEntry };\n\nexport type McpMarketplaceConnectionOption = IntegrationConnectionOption & {\n provider: \"mcp\";\n transport: IntegrationTransport;\n};\n\nexport function getMcpConnectionOptions(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption[] {\n return entry.connectionOptions.filter(\n (option): option is McpMarketplaceConnectionOption =>\n option.provider === \"mcp\" && !!option.transport,\n );\n}\n\nexport function getDefaultMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n return (\n options.find((option) => option.id === entry.defaultConnectionOptionId) ??\n options[0]\n );\n}\n\nfunction isLocallyInstallableMcpOption(\n option: McpMarketplaceConnectionOption,\n): boolean {\n // The local install modal writes static MCP server config. OAuth options\n // describe hosted redirect flows, so prefer an API/stdio fallback when one\n // exists and leave OAuth as the default connection for hosted integrations.\n return option.auth.strategy !== \"oauth2\";\n}\n\nexport function getInstallableMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n const defaultOption = options.find(\n (option) => option.id === entry.defaultConnectionOptionId,\n );\n if (defaultOption && isLocallyInstallableMcpOption(defaultOption)) {\n return defaultOption;\n }\n return options.find(isLocallyInstallableMcpOption);\n}\n\nexport function getDefaultMcpTransport(\n entry: MarketplaceEntry,\n): IntegrationTransport | undefined {\n return getDefaultMcpConnectionOption(entry)?.transport;\n}\n\nexport function getMcpMarketplaceCatalog(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog.filter((entry) => !!getDefaultMcpConnectionOption(entry));\n}\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n transport: IntegrationTransport,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n return (\n servers.find((server) => transportMatchesServer(transport, server)) ?? null\n );\n}\n\nexport function findInstalledEntryMatch(\n entry: MarketplaceEntry,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n for (const option of getMcpConnectionOptions(entry)) {\n const match = findInstalledMatch(option.transport, servers);\n if (match) return match;\n }\n return null;\n}\n\nfunction transportMatchesServer(\n transport: IntegrationTransport,\n server: MCPServerConfig,\n): boolean {\n if (transport.kind === \"shttp\") {\n const tplUrl = transport.url;\n return (\n server.type === \"shttp\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n if (transport.kind === \"sse\") {\n const tplUrl = transport.url;\n return (\n server.type === \"sse\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n // stdio: match on the registered server name.\n return server.type === \"stdio\" && server.name === transport.serverName;\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check every MCP option rather than only the default. Some unified\n // integration entries default to OAuth-hosted MCP while still exposing\n // an API/stdio option; existing installed servers should match either.\n return getMcpConnectionOptions(entry).some((option) =>\n transportMatchesServer(option.transport, server),\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`./sdk-settings-field-metadata.cjs`);var t=new Set([`llm.model`,`llm.api_key`,`llm.base_url`]),n={basic:new Set([`critical`]),advanced:new Set([`critical`,`major`]),all:new Set([`critical`,`major`,`minor`])};function r(e){return!!e&&Array.isArray(e.sections)}function i(e){return r(e)?e.sections.flatMap(e=>e.fields):[]}function a(e,t){if(!e)return;let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function o(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e+=1){let t=r[e];(i[t]==null||typeof i[t]!=`object`||Array.isArray(i[t]))&&(i[t]={}),i=i[t]}i[r[r.length-1]]=n}function s(e,t,n=`agent_settings`){return a(e[n],t)??null}function c(e,t){return s(e,t,`agent_settings`)}function l(e){return e.choices.length>0}function u(e){return e.prominence===`critical`}function d(e){return e.prominence===`minor`}function f(e,t){let n=t??e.default;return l(e)?n==null?``:String(n):e.value_type===`boolean`?!!(n??!1):n==null?``:e.value_type===`array`||e.value_type===`object`?JSON.stringify(n,null,2):String(n)}function p(e,t){if(t===void 0)return null;if(e.value_type===`boolean`){if(typeof t==`string`){if(t===`true`)return!0;if(t===`false`)return!1}return t===null?null:!!t}if(e.value_type===`integer`||e.value_type===`number`){if(t===``||t===null)return null;let e=typeof t==`number`?t:Number(String(t));return Number.isNaN(e)?null:e}if(e.value_type===`array`||e.value_type===`object`){if(t===null||e.value_type===`object`&&typeof t==`object`&&!Array.isArray(t)&&Object.keys(t).length===0)return null;if(typeof t==`string`){let n=t.trim();if(!n)return null;try{let t=JSON.parse(n);return e.value_type===`object`&&typeof t==`object`&&t&&!Array.isArray(t)&&Object.keys(t).length===0?null:JSON.stringify(t)}catch{return n}}return JSON.stringify(t)}return t===null?null:String(t)}function m(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);return r?Object.fromEntries(i(r).map(t=>[t.key,f(t,s(e,t.key,n))])):{}}function h(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);if(!r)return`basic`;let a=!1,o=!1;for(let t of i(r))u(t)||p(t,s(e,t.key,n)??t.default??null)!==p(t,t.default??null)&&(d(t)?a=!0:o=!0);return a?`all`:o?`advanced`:`basic`}function g(e,t){return e.depends_on.every(e=>t[e]===!0)}function _(e){if(typeof e==`boolean`)return e;let t=e.trim().toLowerCase();if(!t)return null;if(t===`true`)return!0;if(t===`false`)return!1;throw Error(`Expected a boolean value, received: ${e}`)}function v(t,n){if(t.value_type===`boolean`)return _(n);if(t.value_type===`integer`||t.value_type===`number`){let r=String(n).trim();if(!r)return null;let i=Number(r);if(Number.isNaN(i))throw Error(`Expected a numeric value, received: ${r}`);if(t.value_type===`integer`&&!Number.isInteger(i))throw Error(`Expected an integer value, received: ${r}`);let a=e.getSettingsFieldConstraints(t.key);if(a?.min!=null&&i<a.min)throw Error(`${t.label} must be at least ${a.min}`);if(a?.max!=null&&i>a.max)throw Error(`${t.label} must be at most ${a.max}`);return i}if(t.value_type===`array`||t.value_type===`object`){let e=String(n).trim();if(!e)return null;let r;try{r=JSON.parse(e)}catch{throw Error(`Invalid JSON for ${t.label}`)}if(t.value_type===`array`){if(!Array.isArray(r))throw Error(`${t.label} must be a JSON array`);return r}if(r===null||Array.isArray(r)||typeof r!=`object`)throw Error(`${t.label} must be a JSON object`);return r}let r=String(n);return r===``&&!t.secret?null:r}function y(e,t,n){let r={};for(let a of i(e))n[a.key]&&o(r,a.key,v(a,t[a.key]));return r}function b(e,t){return n[t].has(e.prominence)}function x(e,t,n,r){let a=y(e,t,n);for(let t of i(e))b(t,r)||o(a,t.key,t.default??null);return a}function S(e,n,i,a=t){return r(e)?e.sections.map(e=>({...e,fields:e.fields.filter(e=>!a.has(e.key)&&b(e,i)&&g(e,n))})).filter(e=>e.fields.length>0):[]}function C(e){return e?i(e).some(e=>e.prominence===`major`):!1}function w(e){return e?i(e).some(e=>e.prominence===`minor`):!1}exports.buildInitialSettingsFormValues=m,exports.buildSdkSettingsPayloadForView=x,exports.getAgentSettingValue=c,exports.getVisibleSettingsSections=S,exports.hasAdvancedSettings=C,exports.hasMinorSettings=w,exports.inferInitialView=h,exports.isValidSettingsSchema=r;
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`./sdk-settings-field-metadata.cjs`);var t=new Set([`llm.model`,`llm.api_key`,`llm.base_url`]),n={basic:new Set([`critical`]),advanced:new Set([`critical`,`major`]),all:new Set([`critical`,`major`,`minor`])};function r(e){return!!e&&Array.isArray(e.sections)}function i(e){return r(e)?e.sections.flatMap(e=>e.fields):[]}function a(e,t){if(!e)return;let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function o(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e+=1){let t=r[e];(i[t]==null||typeof i[t]!=`object`||Array.isArray(i[t]))&&(i[t]={}),i=i[t]}i[r[r.length-1]]=n}function s(e,t,n=`agent_settings`){return a(e[n],t)??null}function c(e,t){return s(e,t,`agent_settings`)}function l(e){return e.choices.length>0}function u(e){return e.prominence===`critical`}function d(e){return e.prominence===`minor`}function f(e,t){let n=t??e.default;return l(e)?n==null?``:String(n):e.value_type===`boolean`?!!(n??!1):n==null?``:e.value_type===`array`||e.value_type===`object`?JSON.stringify(n,null,2):String(n)}function p(e,t){if(t===void 0)return null;if(e.value_type===`boolean`){if(typeof t==`string`){if(t===`true`)return!0;if(t===`false`)return!1}return t===null?null:!!t}if(e.value_type===`integer`||e.value_type===`number`){if(t===``||t===null)return null;let e=typeof t==`number`?t:Number(String(t));return Number.isNaN(e)?null:e}if(e.value_type===`array`||e.value_type===`object`){if(t===null||e.value_type===`object`&&typeof t==`object`&&!Array.isArray(t)&&Object.keys(t).length===0)return null;if(typeof t==`string`){let n=t.trim();if(!n)return null;try{let t=JSON.parse(n);return e.value_type===`object`&&typeof t==`object`&&t&&!Array.isArray(t)&&Object.keys(t).length===0?null:JSON.stringify(t)}catch{return n}}return JSON.stringify(t)}return t===null?null:String(t)}function m(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);return r?Object.fromEntries(i(r).map(t=>[t.key,f(t,s(e,t.key,n))])):{}}function h(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);if(!r)return`basic`;let a=!1,o=!1;for(let t of i(r))u(t)||p(t,s(e,t.key,n)??t.default??null)!==p(t,t.default??null)&&(d(t)?a=!0:o=!0);return a?`all`:o?`advanced`:`basic`}function g(e,t){return e.depends_on.every(e=>t[e]===!0)}function _(e){if(typeof e==`boolean`)return e;let t=e.trim().toLowerCase();if(!t)return null;if(t===`true`)return!0;if(t===`false`)return!1;throw Error(`Expected a boolean value, received: ${e}`)}function v(t,n){if(t.value_type===`boolean`)return _(n);if(t.value_type===`integer`||t.value_type===`number`){let r=String(n).trim();if(!r)return null;let i=Number(r);if(Number.isNaN(i))throw Error(`Expected a numeric value, received: ${r}`);if(t.value_type===`integer`&&!Number.isInteger(i))throw Error(`Expected an integer value, received: ${r}`);let a=e.getSettingsFieldConstraints(t.key);if(a?.min!=null&&i<a.min)throw Error(`${t.label} must be at least ${a.min}`);if(a?.max!=null&&i>a.max)throw Error(`${t.label} must be at most ${a.max}`);return i}if(t.value_type===`array`||t.value_type===`object`){let e=String(n).trim();if(!e)return null;let r;try{r=JSON.parse(e)}catch{throw Error(`Invalid JSON for ${t.label}`)}if(t.value_type===`array`){if(!Array.isArray(r))throw Error(`${t.label} must be a JSON array`);return r}if(r===null||Array.isArray(r)||typeof r!=`object`)throw Error(`${t.label} must be a JSON object`);return r}let r=String(n);return r===``&&!t.secret?null:r}function y(e,t,n){let r={};for(let a of i(e))n[a.key]&&o(r,a.key,v(a,t[a.key]));return r}function b(e,t){return n[t].has(e.prominence)}function x(e,t,n,r){let a=y(e,t,n);for(let t of i(e))b(t,r)||o(a,t.key,t.default??null);return a}function S(e,n,i,a=t){return r(e)?e.sections.map(e=>({...e,fields:e.fields.filter(e=>!a.has(e.key)&&b(e,i)&&g(e,n))})).filter(e=>e.fields.length>0):[]}function C(e){return e?i(e).some(e=>e.prominence===`major`):!1}function w(e){return e?i(e).some(e=>e.prominence===`minor`):!1}exports.buildInitialSettingsFormValues=m,exports.buildSdkSettingsPayload=y,exports.buildSdkSettingsPayloadForView=x,exports.getAgentSettingValue=c,exports.getVisibleSettingsSections=S,exports.hasAdvancedSettings=C,exports.hasMinorSettings=w,exports.inferInitialView=h,exports.isValidSettingsSchema=r;
|
|
2
2
|
//# sourceMappingURL=sdk-settings-schema.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-settings-schema.cjs","names":[],"sources":["../../src/utils/sdk-settings-schema.ts"],"sourcesContent":["import {\n SettingProminence,\n Settings,\n SettingsFieldSchema,\n SettingsSchema,\n SettingsSectionSchema,\n SettingsValue,\n} from \"#/types/settings\";\nimport { getSettingsFieldConstraints } from \"#/utils/sdk-settings-field-metadata\";\n\nexport type SettingsFormValues = Record<string, string | boolean>;\nexport type SettingsDirtyState = Record<string, boolean>;\nexport type SdkSettingsPayload = Record<string, SettingsValue>;\nexport type SettingsValueSource = \"agent_settings\" | \"conversation_settings\";\n\nexport type SettingsView = \"basic\" | \"advanced\" | \"all\";\n\n/** Fields that are rendered by purpose-built components instead of the\n * generic `SchemaField` renderer. */\nexport const SPECIALLY_RENDERED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n]);\n\n/** Prominence tiers visible at each view level. */\nconst VIEW_PROMINENCES: Record<SettingsView, Set<SettingProminence>> = {\n basic: new Set<SettingProminence>([\"critical\"]),\n advanced: new Set<SettingProminence>([\"critical\", \"major\"]),\n all: new Set<SettingProminence>([\"critical\", \"major\", \"minor\"]),\n};\n\n/**\n * True when `schema` looks like a usable `SettingsSchema` — i.e. an\n * object with an array `sections` field. Guards every helper in this\n * module against malformed/empty schema responses (e.g. when the\n * frontend ends up pointing at a host that does not actually serve\n * `/api/settings/agent-schema`, such as an unconfigured Vercel preview\n * origin that returns the React Router SPA shell for arbitrary\n * `/api/*` paths). Without this check, `schema.sections.filter(...)`\n * inside `SdkSectionPage` blows up with\n * `Cannot read properties of undefined (reading 'filter')` and React\n * Router escalates to a full-screen error page.\n */\nexport function isValidSettingsSchema(\n schema: SettingsSchema | null | undefined,\n): schema is SettingsSchema {\n return !!schema && Array.isArray((schema as SettingsSchema).sections);\n}\n\nfunction getSchemaFields(schema: SettingsSchema): SettingsFieldSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections.flatMap((section) => section.fields);\n}\n\n/** Traverse a nested object using a dotted key path (e.g. \"llm.model\"). */\nfunction lookupDotted(\n obj: Record<string, unknown> | null | undefined,\n key: string,\n): unknown {\n if (!obj) return undefined;\n const parts = key.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Set a value in a nested object at a dotted key path (e.g. \"llm.model\"). */\nfunction setDotted(\n obj: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n const parts = key.split(\".\");\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i += 1) {\n const part = parts[i];\n if (\n current[part] == null ||\n typeof current[part] !== \"object\" ||\n Array.isArray(current[part])\n ) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\nexport function getSettingValue(\n settings: Settings,\n key: string,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsValue {\n return (lookupDotted(settings[source] as Record<string, unknown>, key) ??\n null) as SettingsValue;\n}\n\nexport function getAgentSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"agent_settings\");\n}\n\nexport function getConversationSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"conversation_settings\");\n}\n\nfunction isChoiceField(field: SettingsFieldSchema): boolean {\n return field.choices.length > 0;\n}\n\nfunction isCriticalField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"critical\";\n}\n\nfunction isMinorField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"minor\";\n}\n\nfunction normalizeFieldValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): string | boolean {\n const resolvedValue = rawValue ?? field.default;\n\n if (isChoiceField(field)) {\n return resolvedValue === null || resolvedValue === undefined\n ? \"\"\n : String(resolvedValue);\n }\n\n if (field.value_type === \"boolean\") {\n return Boolean(resolvedValue ?? false);\n }\n\n if (resolvedValue === null || resolvedValue === undefined) {\n return \"\";\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n return JSON.stringify(resolvedValue, null, 2);\n }\n\n return String(resolvedValue);\n}\n\nfunction normalizeComparableValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): boolean | number | string | null {\n if (rawValue === undefined) {\n return null;\n }\n\n if (field.value_type === \"boolean\") {\n if (typeof rawValue === \"string\") {\n if (rawValue === \"true\") {\n return true;\n }\n if (rawValue === \"false\") {\n return false;\n }\n }\n if (rawValue === null) {\n return null;\n }\n return Boolean(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n if (rawValue === \"\" || rawValue === null) {\n return null;\n }\n\n const parsedValue =\n typeof rawValue === \"number\" ? rawValue : Number(String(rawValue));\n return Number.isNaN(parsedValue) ? null : parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n if (rawValue === null) {\n return null;\n }\n\n // Treat empty objects as null so that serializer artifacts\n // (e.g. mcp_config: {} vs schema default null) don't trigger\n // a spurious view escalation in inferInitialView.\n if (\n field.value_type === \"object\" &&\n typeof rawValue === \"object\" &&\n !Array.isArray(rawValue) &&\n Object.keys(rawValue as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n\n if (typeof rawValue === \"string\") {\n const trimmedValue = rawValue.trim();\n if (!trimmedValue) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(trimmedValue);\n // Also normalise stringified empty objects\n if (\n field.value_type === \"object\" &&\n parsed !== null &&\n typeof parsed === \"object\" &&\n !Array.isArray(parsed) &&\n Object.keys(parsed as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n return JSON.stringify(parsed);\n } catch {\n return trimmedValue;\n }\n }\n\n return JSON.stringify(rawValue);\n }\n\n if (rawValue === null) {\n return null;\n }\n\n return String(rawValue);\n}\n\nexport function buildInitialSettingsFormValues(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsFormValues {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return {};\n }\n\n return Object.fromEntries(\n getSchemaFields(schema).map((field) => [\n field.key,\n normalizeFieldValue(field, getSettingValue(settings, field.key, source)),\n ]),\n );\n}\n\nexport function inferInitialView(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsView {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return \"basic\";\n }\n\n let hasMinorOverride = false;\n let hasMajorOverride = false;\n\n for (const field of getSchemaFields(schema)) {\n if (!isCriticalField(field)) {\n const currentValue = getSettingValue(settings, field.key, source);\n const isDifferent =\n normalizeComparableValue(\n field,\n currentValue ?? field.default ?? null,\n ) !== normalizeComparableValue(field, field.default ?? null);\n\n if (isDifferent) {\n if (isMinorField(field)) {\n hasMinorOverride = true;\n } else {\n hasMajorOverride = true;\n }\n }\n }\n }\n\n if (hasMinorOverride) return \"all\";\n if (hasMajorOverride) return \"advanced\";\n return \"basic\";\n}\n\n/** Determine which view tier to default to based on whether the user has\n * overridden any non-critical settings. */\nexport function hasAdvancedSettingsOverrides(settings: Settings): boolean {\n return inferInitialView(settings) !== \"basic\";\n}\n\nexport function isSettingsFieldVisible(\n field: SettingsFieldSchema,\n values: SettingsFormValues,\n): boolean {\n return field.depends_on.every((dependency) => values[dependency] === true);\n}\n\nfunction parseBooleanFieldValue(rawValue: string | boolean): boolean | null {\n if (typeof rawValue === \"boolean\") {\n return rawValue;\n }\n\n const normalizedValue = rawValue.trim().toLowerCase();\n if (!normalizedValue) {\n return null;\n }\n if (normalizedValue === \"true\") {\n return true;\n }\n if (normalizedValue === \"false\") {\n return false;\n }\n\n throw new Error(`Expected a boolean value, received: ${rawValue}`);\n}\n\nfunction coerceFieldValue(\n field: SettingsFieldSchema,\n rawValue: string | boolean,\n): SettingsValue {\n if (field.value_type === \"boolean\") {\n return parseBooleanFieldValue(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n const parsedValue = Number(stringValue);\n if (Number.isNaN(parsedValue)) {\n throw new Error(`Expected a numeric value, received: ${stringValue}`);\n }\n if (field.value_type === \"integer\" && !Number.isInteger(parsedValue)) {\n throw new Error(`Expected an integer value, received: ${stringValue}`);\n }\n\n const constraints = getSettingsFieldConstraints(field.key);\n if (constraints?.min != null && parsedValue < constraints.min) {\n throw new Error(`${field.label} must be at least ${constraints.min}`);\n }\n if (constraints?.max != null && parsedValue > constraints.max) {\n throw new Error(`${field.label} must be at most ${constraints.max}`);\n }\n\n return parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n let parsedValue: unknown;\n try {\n parsedValue = JSON.parse(stringValue);\n } catch {\n throw new Error(`Invalid JSON for ${field.label}`);\n }\n\n if (field.value_type === \"array\") {\n if (!Array.isArray(parsedValue)) {\n throw new Error(`${field.label} must be a JSON array`);\n }\n return parsedValue as SettingsValue[];\n }\n\n if (\n parsedValue === null ||\n Array.isArray(parsedValue) ||\n typeof parsedValue !== \"object\"\n ) {\n throw new Error(`${field.label} must be a JSON object`);\n }\n\n return parsedValue as { [key: string]: SettingsValue };\n }\n\n const stringValue = String(rawValue);\n if (stringValue === \"\" && !field.secret) {\n return null;\n }\n\n return stringValue;\n}\n\nexport function buildSdkSettingsPayload(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n): SdkSettingsPayload {\n const payload: Record<string, unknown> = {};\n\n for (const field of getSchemaFields(schema)) {\n if (dirty[field.key]) {\n setDotted(payload, field.key, coerceFieldValue(field, values[field.key]));\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\nfunction isFieldVisibleInView(\n field: SettingsFieldSchema,\n view: SettingsView,\n): boolean {\n return VIEW_PROMINENCES[view].has(field.prominence);\n}\n\nexport function buildSdkSettingsPayloadForView(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n view: SettingsView,\n): SdkSettingsPayload {\n const payload = buildSdkSettingsPayload(schema, values, dirty) as Record<\n string,\n unknown\n >;\n\n for (const field of getSchemaFields(schema)) {\n if (!isFieldVisibleInView(field, view)) {\n setDotted(payload, field.key, field.default ?? null);\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\n/** Return sections with fields filtered for the current view tier.\n * Specially-rendered fields are excluded from the generic list. */\nexport function getVisibleSettingsSections(\n schema: SettingsSchema,\n values: SettingsFormValues,\n view: SettingsView,\n excludeKeys: Set<string> = SPECIALLY_RENDERED_KEYS,\n): SettingsSectionSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections\n .map((section) => ({\n ...section,\n fields: section.fields.filter(\n (field) =>\n !excludeKeys.has(field.key) &&\n isFieldVisibleInView(field, view) &&\n isSettingsFieldVisible(field, values),\n ),\n }))\n .filter((section) => section.fields.length > 0);\n}\n\n/** Whether the schema has any fields visible in the \"advanced\" tier. */\nexport function hasAdvancedSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"major\");\n}\n\n/** Whether the schema has any \"minor\" prominence fields. */\nexport function hasMinorSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"minor\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdk-settings-schema.cjs","names":[],"sources":["../../src/utils/sdk-settings-schema.ts"],"sourcesContent":["import {\n SettingProminence,\n Settings,\n SettingsFieldSchema,\n SettingsSchema,\n SettingsSectionSchema,\n SettingsValue,\n} from \"#/types/settings\";\nimport { getSettingsFieldConstraints } from \"#/utils/sdk-settings-field-metadata\";\n\nexport type SettingsFormValues = Record<string, string | boolean>;\nexport type SettingsDirtyState = Record<string, boolean>;\nexport type SdkSettingsPayload = Record<string, SettingsValue>;\nexport type SettingsValueSource = \"agent_settings\" | \"conversation_settings\";\n\nexport type SettingsView = \"basic\" | \"advanced\" | \"all\";\n\n/** Fields that are rendered by purpose-built components instead of the\n * generic `SchemaField` renderer. */\nexport const SPECIALLY_RENDERED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n]);\n\n/** Prominence tiers visible at each view level. */\nconst VIEW_PROMINENCES: Record<SettingsView, Set<SettingProminence>> = {\n basic: new Set<SettingProminence>([\"critical\"]),\n advanced: new Set<SettingProminence>([\"critical\", \"major\"]),\n all: new Set<SettingProminence>([\"critical\", \"major\", \"minor\"]),\n};\n\n/**\n * True when `schema` looks like a usable `SettingsSchema` — i.e. an\n * object with an array `sections` field. Guards every helper in this\n * module against malformed/empty schema responses (e.g. when the\n * frontend ends up pointing at a host that does not actually serve\n * `/api/settings/agent-schema`, such as an unconfigured Vercel preview\n * origin that returns the React Router SPA shell for arbitrary\n * `/api/*` paths). Without this check, `schema.sections.filter(...)`\n * inside `SdkSectionPage` blows up with\n * `Cannot read properties of undefined (reading 'filter')` and React\n * Router escalates to a full-screen error page.\n */\nexport function isValidSettingsSchema(\n schema: SettingsSchema | null | undefined,\n): schema is SettingsSchema {\n return !!schema && Array.isArray((schema as SettingsSchema).sections);\n}\n\nfunction getSchemaFields(schema: SettingsSchema): SettingsFieldSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections.flatMap((section) => section.fields);\n}\n\n/** Traverse a nested object using a dotted key path (e.g. \"llm.model\"). */\nfunction lookupDotted(\n obj: Record<string, unknown> | null | undefined,\n key: string,\n): unknown {\n if (!obj) return undefined;\n const parts = key.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Set a value in a nested object at a dotted key path (e.g. \"llm.model\"). */\nfunction setDotted(\n obj: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n const parts = key.split(\".\");\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i += 1) {\n const part = parts[i];\n if (\n current[part] == null ||\n typeof current[part] !== \"object\" ||\n Array.isArray(current[part])\n ) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\nexport function getSettingValue(\n settings: Settings,\n key: string,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsValue {\n return (lookupDotted(settings[source] as Record<string, unknown>, key) ??\n null) as SettingsValue;\n}\n\nexport function getAgentSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"agent_settings\");\n}\n\nexport function getConversationSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"conversation_settings\");\n}\n\nfunction isChoiceField(field: SettingsFieldSchema): boolean {\n return field.choices.length > 0;\n}\n\nfunction isCriticalField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"critical\";\n}\n\nfunction isMinorField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"minor\";\n}\n\nexport function normalizeFieldValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): string | boolean {\n const resolvedValue = rawValue ?? field.default;\n\n if (isChoiceField(field)) {\n return resolvedValue === null || resolvedValue === undefined\n ? \"\"\n : String(resolvedValue);\n }\n\n if (field.value_type === \"boolean\") {\n return Boolean(resolvedValue ?? false);\n }\n\n if (resolvedValue === null || resolvedValue === undefined) {\n return \"\";\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n return JSON.stringify(resolvedValue, null, 2);\n }\n\n return String(resolvedValue);\n}\n\nfunction normalizeComparableValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): boolean | number | string | null {\n if (rawValue === undefined) {\n return null;\n }\n\n if (field.value_type === \"boolean\") {\n if (typeof rawValue === \"string\") {\n if (rawValue === \"true\") {\n return true;\n }\n if (rawValue === \"false\") {\n return false;\n }\n }\n if (rawValue === null) {\n return null;\n }\n return Boolean(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n if (rawValue === \"\" || rawValue === null) {\n return null;\n }\n\n const parsedValue =\n typeof rawValue === \"number\" ? rawValue : Number(String(rawValue));\n return Number.isNaN(parsedValue) ? null : parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n if (rawValue === null) {\n return null;\n }\n\n // Treat empty objects as null so that serializer artifacts\n // (e.g. mcp_config: {} vs schema default null) don't trigger\n // a spurious view escalation in inferInitialView.\n if (\n field.value_type === \"object\" &&\n typeof rawValue === \"object\" &&\n !Array.isArray(rawValue) &&\n Object.keys(rawValue as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n\n if (typeof rawValue === \"string\") {\n const trimmedValue = rawValue.trim();\n if (!trimmedValue) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(trimmedValue);\n // Also normalise stringified empty objects\n if (\n field.value_type === \"object\" &&\n parsed !== null &&\n typeof parsed === \"object\" &&\n !Array.isArray(parsed) &&\n Object.keys(parsed as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n return JSON.stringify(parsed);\n } catch {\n return trimmedValue;\n }\n }\n\n return JSON.stringify(rawValue);\n }\n\n if (rawValue === null) {\n return null;\n }\n\n return String(rawValue);\n}\n\nexport function buildInitialSettingsFormValues(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsFormValues {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return {};\n }\n\n return Object.fromEntries(\n getSchemaFields(schema).map((field) => [\n field.key,\n normalizeFieldValue(field, getSettingValue(settings, field.key, source)),\n ]),\n );\n}\n\nexport function inferInitialView(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsView {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return \"basic\";\n }\n\n let hasMinorOverride = false;\n let hasMajorOverride = false;\n\n for (const field of getSchemaFields(schema)) {\n if (!isCriticalField(field)) {\n const currentValue = getSettingValue(settings, field.key, source);\n const isDifferent =\n normalizeComparableValue(\n field,\n currentValue ?? field.default ?? null,\n ) !== normalizeComparableValue(field, field.default ?? null);\n\n if (isDifferent) {\n if (isMinorField(field)) {\n hasMinorOverride = true;\n } else {\n hasMajorOverride = true;\n }\n }\n }\n }\n\n if (hasMinorOverride) return \"all\";\n if (hasMajorOverride) return \"advanced\";\n return \"basic\";\n}\n\n/** Determine which view tier to default to based on whether the user has\n * overridden any non-critical settings. */\nexport function hasAdvancedSettingsOverrides(settings: Settings): boolean {\n return inferInitialView(settings) !== \"basic\";\n}\n\nexport function isSettingsFieldVisible(\n field: SettingsFieldSchema,\n values: SettingsFormValues,\n): boolean {\n return field.depends_on.every((dependency) => values[dependency] === true);\n}\n\nfunction parseBooleanFieldValue(rawValue: string | boolean): boolean | null {\n if (typeof rawValue === \"boolean\") {\n return rawValue;\n }\n\n const normalizedValue = rawValue.trim().toLowerCase();\n if (!normalizedValue) {\n return null;\n }\n if (normalizedValue === \"true\") {\n return true;\n }\n if (normalizedValue === \"false\") {\n return false;\n }\n\n throw new Error(`Expected a boolean value, received: ${rawValue}`);\n}\n\nfunction coerceFieldValue(\n field: SettingsFieldSchema,\n rawValue: string | boolean,\n): SettingsValue {\n if (field.value_type === \"boolean\") {\n return parseBooleanFieldValue(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n const parsedValue = Number(stringValue);\n if (Number.isNaN(parsedValue)) {\n throw new Error(`Expected a numeric value, received: ${stringValue}`);\n }\n if (field.value_type === \"integer\" && !Number.isInteger(parsedValue)) {\n throw new Error(`Expected an integer value, received: ${stringValue}`);\n }\n\n const constraints = getSettingsFieldConstraints(field.key);\n if (constraints?.min != null && parsedValue < constraints.min) {\n throw new Error(`${field.label} must be at least ${constraints.min}`);\n }\n if (constraints?.max != null && parsedValue > constraints.max) {\n throw new Error(`${field.label} must be at most ${constraints.max}`);\n }\n\n return parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n let parsedValue: unknown;\n try {\n parsedValue = JSON.parse(stringValue);\n } catch {\n throw new Error(`Invalid JSON for ${field.label}`);\n }\n\n if (field.value_type === \"array\") {\n if (!Array.isArray(parsedValue)) {\n throw new Error(`${field.label} must be a JSON array`);\n }\n return parsedValue as SettingsValue[];\n }\n\n if (\n parsedValue === null ||\n Array.isArray(parsedValue) ||\n typeof parsedValue !== \"object\"\n ) {\n throw new Error(`${field.label} must be a JSON object`);\n }\n\n return parsedValue as { [key: string]: SettingsValue };\n }\n\n const stringValue = String(rawValue);\n if (stringValue === \"\" && !field.secret) {\n return null;\n }\n\n return stringValue;\n}\n\nexport function buildSdkSettingsPayload(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n): SdkSettingsPayload {\n const payload: Record<string, unknown> = {};\n\n for (const field of getSchemaFields(schema)) {\n if (dirty[field.key]) {\n setDotted(payload, field.key, coerceFieldValue(field, values[field.key]));\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\nfunction isFieldVisibleInView(\n field: SettingsFieldSchema,\n view: SettingsView,\n): boolean {\n return VIEW_PROMINENCES[view].has(field.prominence);\n}\n\nexport function buildSdkSettingsPayloadForView(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n view: SettingsView,\n): SdkSettingsPayload {\n const payload = buildSdkSettingsPayload(schema, values, dirty) as Record<\n string,\n unknown\n >;\n\n for (const field of getSchemaFields(schema)) {\n if (!isFieldVisibleInView(field, view)) {\n setDotted(payload, field.key, field.default ?? null);\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\n/** Return sections with fields filtered for the current view tier.\n * Specially-rendered fields are excluded from the generic list. */\nexport function getVisibleSettingsSections(\n schema: SettingsSchema,\n values: SettingsFormValues,\n view: SettingsView,\n excludeKeys: Set<string> = SPECIALLY_RENDERED_KEYS,\n): SettingsSectionSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections\n .map((section) => ({\n ...section,\n fields: section.fields.filter(\n (field) =>\n !excludeKeys.has(field.key) &&\n isFieldVisibleInView(field, view) &&\n isSettingsFieldVisible(field, values),\n ),\n }))\n .filter((section) => section.fields.length > 0);\n}\n\n/** Whether the schema has any fields visible in the \"advanced\" tier. */\nexport function hasAdvancedSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"major\");\n}\n\n/** Whether the schema has any \"minor\" prominence fields. */\nexport function hasMinorSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"minor\");\n}\n"],"mappings":"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"}
|
|
@@ -23,6 +23,7 @@ export declare function isValidSettingsSchema(schema: SettingsSchema | null | un
|
|
|
23
23
|
export declare function getSettingValue(settings: Settings, key: string, source?: SettingsValueSource): SettingsValue;
|
|
24
24
|
export declare function getAgentSettingValue(settings: Settings, key: string): SettingsValue;
|
|
25
25
|
export declare function getConversationSettingValue(settings: Settings, key: string): SettingsValue;
|
|
26
|
+
export declare function normalizeFieldValue(field: SettingsFieldSchema, rawValue: unknown): string | boolean;
|
|
26
27
|
export declare function buildInitialSettingsFormValues(settings: Settings, schemaOverride?: SettingsSchema | null, source?: SettingsValueSource): SettingsFormValues;
|
|
27
28
|
export declare function inferInitialView(settings: Settings, schemaOverride?: SettingsSchema | null, source?: SettingsValueSource): SettingsView;
|
|
28
29
|
/** Determine which view tier to default to based on whether the user has
|
|
@@ -165,6 +165,6 @@ function w(e) {
|
|
|
165
165
|
return e ? i(e).some((e) => e.prominence === "minor") : !1;
|
|
166
166
|
}
|
|
167
167
|
//#endregion
|
|
168
|
-
export { m as buildInitialSettingsFormValues, x as buildSdkSettingsPayloadForView, c as getAgentSettingValue, S as getVisibleSettingsSections, C as hasAdvancedSettings, w as hasMinorSettings, h as inferInitialView, r as isValidSettingsSchema };
|
|
168
|
+
export { m as buildInitialSettingsFormValues, y as buildSdkSettingsPayload, x as buildSdkSettingsPayloadForView, c as getAgentSettingValue, S as getVisibleSettingsSections, C as hasAdvancedSettings, w as hasMinorSettings, h as inferInitialView, r as isValidSettingsSchema };
|
|
169
169
|
|
|
170
170
|
//# sourceMappingURL=sdk-settings-schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-settings-schema.js","names":[],"sources":["../../src/utils/sdk-settings-schema.ts"],"sourcesContent":["import {\n SettingProminence,\n Settings,\n SettingsFieldSchema,\n SettingsSchema,\n SettingsSectionSchema,\n SettingsValue,\n} from \"#/types/settings\";\nimport { getSettingsFieldConstraints } from \"#/utils/sdk-settings-field-metadata\";\n\nexport type SettingsFormValues = Record<string, string | boolean>;\nexport type SettingsDirtyState = Record<string, boolean>;\nexport type SdkSettingsPayload = Record<string, SettingsValue>;\nexport type SettingsValueSource = \"agent_settings\" | \"conversation_settings\";\n\nexport type SettingsView = \"basic\" | \"advanced\" | \"all\";\n\n/** Fields that are rendered by purpose-built components instead of the\n * generic `SchemaField` renderer. */\nexport const SPECIALLY_RENDERED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n]);\n\n/** Prominence tiers visible at each view level. */\nconst VIEW_PROMINENCES: Record<SettingsView, Set<SettingProminence>> = {\n basic: new Set<SettingProminence>([\"critical\"]),\n advanced: new Set<SettingProminence>([\"critical\", \"major\"]),\n all: new Set<SettingProminence>([\"critical\", \"major\", \"minor\"]),\n};\n\n/**\n * True when `schema` looks like a usable `SettingsSchema` — i.e. an\n * object with an array `sections` field. Guards every helper in this\n * module against malformed/empty schema responses (e.g. when the\n * frontend ends up pointing at a host that does not actually serve\n * `/api/settings/agent-schema`, such as an unconfigured Vercel preview\n * origin that returns the React Router SPA shell for arbitrary\n * `/api/*` paths). Without this check, `schema.sections.filter(...)`\n * inside `SdkSectionPage` blows up with\n * `Cannot read properties of undefined (reading 'filter')` and React\n * Router escalates to a full-screen error page.\n */\nexport function isValidSettingsSchema(\n schema: SettingsSchema | null | undefined,\n): schema is SettingsSchema {\n return !!schema && Array.isArray((schema as SettingsSchema).sections);\n}\n\nfunction getSchemaFields(schema: SettingsSchema): SettingsFieldSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections.flatMap((section) => section.fields);\n}\n\n/** Traverse a nested object using a dotted key path (e.g. \"llm.model\"). */\nfunction lookupDotted(\n obj: Record<string, unknown> | null | undefined,\n key: string,\n): unknown {\n if (!obj) return undefined;\n const parts = key.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Set a value in a nested object at a dotted key path (e.g. \"llm.model\"). */\nfunction setDotted(\n obj: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n const parts = key.split(\".\");\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i += 1) {\n const part = parts[i];\n if (\n current[part] == null ||\n typeof current[part] !== \"object\" ||\n Array.isArray(current[part])\n ) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\nexport function getSettingValue(\n settings: Settings,\n key: string,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsValue {\n return (lookupDotted(settings[source] as Record<string, unknown>, key) ??\n null) as SettingsValue;\n}\n\nexport function getAgentSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"agent_settings\");\n}\n\nexport function getConversationSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"conversation_settings\");\n}\n\nfunction isChoiceField(field: SettingsFieldSchema): boolean {\n return field.choices.length > 0;\n}\n\nfunction isCriticalField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"critical\";\n}\n\nfunction isMinorField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"minor\";\n}\n\nfunction normalizeFieldValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): string | boolean {\n const resolvedValue = rawValue ?? field.default;\n\n if (isChoiceField(field)) {\n return resolvedValue === null || resolvedValue === undefined\n ? \"\"\n : String(resolvedValue);\n }\n\n if (field.value_type === \"boolean\") {\n return Boolean(resolvedValue ?? false);\n }\n\n if (resolvedValue === null || resolvedValue === undefined) {\n return \"\";\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n return JSON.stringify(resolvedValue, null, 2);\n }\n\n return String(resolvedValue);\n}\n\nfunction normalizeComparableValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): boolean | number | string | null {\n if (rawValue === undefined) {\n return null;\n }\n\n if (field.value_type === \"boolean\") {\n if (typeof rawValue === \"string\") {\n if (rawValue === \"true\") {\n return true;\n }\n if (rawValue === \"false\") {\n return false;\n }\n }\n if (rawValue === null) {\n return null;\n }\n return Boolean(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n if (rawValue === \"\" || rawValue === null) {\n return null;\n }\n\n const parsedValue =\n typeof rawValue === \"number\" ? rawValue : Number(String(rawValue));\n return Number.isNaN(parsedValue) ? null : parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n if (rawValue === null) {\n return null;\n }\n\n // Treat empty objects as null so that serializer artifacts\n // (e.g. mcp_config: {} vs schema default null) don't trigger\n // a spurious view escalation in inferInitialView.\n if (\n field.value_type === \"object\" &&\n typeof rawValue === \"object\" &&\n !Array.isArray(rawValue) &&\n Object.keys(rawValue as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n\n if (typeof rawValue === \"string\") {\n const trimmedValue = rawValue.trim();\n if (!trimmedValue) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(trimmedValue);\n // Also normalise stringified empty objects\n if (\n field.value_type === \"object\" &&\n parsed !== null &&\n typeof parsed === \"object\" &&\n !Array.isArray(parsed) &&\n Object.keys(parsed as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n return JSON.stringify(parsed);\n } catch {\n return trimmedValue;\n }\n }\n\n return JSON.stringify(rawValue);\n }\n\n if (rawValue === null) {\n return null;\n }\n\n return String(rawValue);\n}\n\nexport function buildInitialSettingsFormValues(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsFormValues {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return {};\n }\n\n return Object.fromEntries(\n getSchemaFields(schema).map((field) => [\n field.key,\n normalizeFieldValue(field, getSettingValue(settings, field.key, source)),\n ]),\n );\n}\n\nexport function inferInitialView(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsView {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return \"basic\";\n }\n\n let hasMinorOverride = false;\n let hasMajorOverride = false;\n\n for (const field of getSchemaFields(schema)) {\n if (!isCriticalField(field)) {\n const currentValue = getSettingValue(settings, field.key, source);\n const isDifferent =\n normalizeComparableValue(\n field,\n currentValue ?? field.default ?? null,\n ) !== normalizeComparableValue(field, field.default ?? null);\n\n if (isDifferent) {\n if (isMinorField(field)) {\n hasMinorOverride = true;\n } else {\n hasMajorOverride = true;\n }\n }\n }\n }\n\n if (hasMinorOverride) return \"all\";\n if (hasMajorOverride) return \"advanced\";\n return \"basic\";\n}\n\n/** Determine which view tier to default to based on whether the user has\n * overridden any non-critical settings. */\nexport function hasAdvancedSettingsOverrides(settings: Settings): boolean {\n return inferInitialView(settings) !== \"basic\";\n}\n\nexport function isSettingsFieldVisible(\n field: SettingsFieldSchema,\n values: SettingsFormValues,\n): boolean {\n return field.depends_on.every((dependency) => values[dependency] === true);\n}\n\nfunction parseBooleanFieldValue(rawValue: string | boolean): boolean | null {\n if (typeof rawValue === \"boolean\") {\n return rawValue;\n }\n\n const normalizedValue = rawValue.trim().toLowerCase();\n if (!normalizedValue) {\n return null;\n }\n if (normalizedValue === \"true\") {\n return true;\n }\n if (normalizedValue === \"false\") {\n return false;\n }\n\n throw new Error(`Expected a boolean value, received: ${rawValue}`);\n}\n\nfunction coerceFieldValue(\n field: SettingsFieldSchema,\n rawValue: string | boolean,\n): SettingsValue {\n if (field.value_type === \"boolean\") {\n return parseBooleanFieldValue(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n const parsedValue = Number(stringValue);\n if (Number.isNaN(parsedValue)) {\n throw new Error(`Expected a numeric value, received: ${stringValue}`);\n }\n if (field.value_type === \"integer\" && !Number.isInteger(parsedValue)) {\n throw new Error(`Expected an integer value, received: ${stringValue}`);\n }\n\n const constraints = getSettingsFieldConstraints(field.key);\n if (constraints?.min != null && parsedValue < constraints.min) {\n throw new Error(`${field.label} must be at least ${constraints.min}`);\n }\n if (constraints?.max != null && parsedValue > constraints.max) {\n throw new Error(`${field.label} must be at most ${constraints.max}`);\n }\n\n return parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n let parsedValue: unknown;\n try {\n parsedValue = JSON.parse(stringValue);\n } catch {\n throw new Error(`Invalid JSON for ${field.label}`);\n }\n\n if (field.value_type === \"array\") {\n if (!Array.isArray(parsedValue)) {\n throw new Error(`${field.label} must be a JSON array`);\n }\n return parsedValue as SettingsValue[];\n }\n\n if (\n parsedValue === null ||\n Array.isArray(parsedValue) ||\n typeof parsedValue !== \"object\"\n ) {\n throw new Error(`${field.label} must be a JSON object`);\n }\n\n return parsedValue as { [key: string]: SettingsValue };\n }\n\n const stringValue = String(rawValue);\n if (stringValue === \"\" && !field.secret) {\n return null;\n }\n\n return stringValue;\n}\n\nexport function buildSdkSettingsPayload(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n): SdkSettingsPayload {\n const payload: Record<string, unknown> = {};\n\n for (const field of getSchemaFields(schema)) {\n if (dirty[field.key]) {\n setDotted(payload, field.key, coerceFieldValue(field, values[field.key]));\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\nfunction isFieldVisibleInView(\n field: SettingsFieldSchema,\n view: SettingsView,\n): boolean {\n return VIEW_PROMINENCES[view].has(field.prominence);\n}\n\nexport function buildSdkSettingsPayloadForView(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n view: SettingsView,\n): SdkSettingsPayload {\n const payload = buildSdkSettingsPayload(schema, values, dirty) as Record<\n string,\n unknown\n >;\n\n for (const field of getSchemaFields(schema)) {\n if (!isFieldVisibleInView(field, view)) {\n setDotted(payload, field.key, field.default ?? null);\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\n/** Return sections with fields filtered for the current view tier.\n * Specially-rendered fields are excluded from the generic list. */\nexport function getVisibleSettingsSections(\n schema: SettingsSchema,\n values: SettingsFormValues,\n view: SettingsView,\n excludeKeys: Set<string> = SPECIALLY_RENDERED_KEYS,\n): SettingsSectionSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections\n .map((section) => ({\n ...section,\n fields: section.fields.filter(\n (field) =>\n !excludeKeys.has(field.key) &&\n isFieldVisibleInView(field, view) &&\n isSettingsFieldVisible(field, values),\n ),\n }))\n .filter((section) => section.fields.length > 0);\n}\n\n/** Whether the schema has any fields visible in the \"advanced\" tier. */\nexport function hasAdvancedSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"major\");\n}\n\n/** Whether the schema has any \"minor\" prominence fields. */\nexport function hasMinorSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"minor\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdk-settings-schema.js","names":[],"sources":["../../src/utils/sdk-settings-schema.ts"],"sourcesContent":["import {\n SettingProminence,\n Settings,\n SettingsFieldSchema,\n SettingsSchema,\n SettingsSectionSchema,\n SettingsValue,\n} from \"#/types/settings\";\nimport { getSettingsFieldConstraints } from \"#/utils/sdk-settings-field-metadata\";\n\nexport type SettingsFormValues = Record<string, string | boolean>;\nexport type SettingsDirtyState = Record<string, boolean>;\nexport type SdkSettingsPayload = Record<string, SettingsValue>;\nexport type SettingsValueSource = \"agent_settings\" | \"conversation_settings\";\n\nexport type SettingsView = \"basic\" | \"advanced\" | \"all\";\n\n/** Fields that are rendered by purpose-built components instead of the\n * generic `SchemaField` renderer. */\nexport const SPECIALLY_RENDERED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n]);\n\n/** Prominence tiers visible at each view level. */\nconst VIEW_PROMINENCES: Record<SettingsView, Set<SettingProminence>> = {\n basic: new Set<SettingProminence>([\"critical\"]),\n advanced: new Set<SettingProminence>([\"critical\", \"major\"]),\n all: new Set<SettingProminence>([\"critical\", \"major\", \"minor\"]),\n};\n\n/**\n * True when `schema` looks like a usable `SettingsSchema` — i.e. an\n * object with an array `sections` field. Guards every helper in this\n * module against malformed/empty schema responses (e.g. when the\n * frontend ends up pointing at a host that does not actually serve\n * `/api/settings/agent-schema`, such as an unconfigured Vercel preview\n * origin that returns the React Router SPA shell for arbitrary\n * `/api/*` paths). Without this check, `schema.sections.filter(...)`\n * inside `SdkSectionPage` blows up with\n * `Cannot read properties of undefined (reading 'filter')` and React\n * Router escalates to a full-screen error page.\n */\nexport function isValidSettingsSchema(\n schema: SettingsSchema | null | undefined,\n): schema is SettingsSchema {\n return !!schema && Array.isArray((schema as SettingsSchema).sections);\n}\n\nfunction getSchemaFields(schema: SettingsSchema): SettingsFieldSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections.flatMap((section) => section.fields);\n}\n\n/** Traverse a nested object using a dotted key path (e.g. \"llm.model\"). */\nfunction lookupDotted(\n obj: Record<string, unknown> | null | undefined,\n key: string,\n): unknown {\n if (!obj) return undefined;\n const parts = key.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Set a value in a nested object at a dotted key path (e.g. \"llm.model\"). */\nfunction setDotted(\n obj: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n const parts = key.split(\".\");\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i += 1) {\n const part = parts[i];\n if (\n current[part] == null ||\n typeof current[part] !== \"object\" ||\n Array.isArray(current[part])\n ) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\nexport function getSettingValue(\n settings: Settings,\n key: string,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsValue {\n return (lookupDotted(settings[source] as Record<string, unknown>, key) ??\n null) as SettingsValue;\n}\n\nexport function getAgentSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"agent_settings\");\n}\n\nexport function getConversationSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"conversation_settings\");\n}\n\nfunction isChoiceField(field: SettingsFieldSchema): boolean {\n return field.choices.length > 0;\n}\n\nfunction isCriticalField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"critical\";\n}\n\nfunction isMinorField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"minor\";\n}\n\nexport function normalizeFieldValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): string | boolean {\n const resolvedValue = rawValue ?? field.default;\n\n if (isChoiceField(field)) {\n return resolvedValue === null || resolvedValue === undefined\n ? \"\"\n : String(resolvedValue);\n }\n\n if (field.value_type === \"boolean\") {\n return Boolean(resolvedValue ?? false);\n }\n\n if (resolvedValue === null || resolvedValue === undefined) {\n return \"\";\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n return JSON.stringify(resolvedValue, null, 2);\n }\n\n return String(resolvedValue);\n}\n\nfunction normalizeComparableValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): boolean | number | string | null {\n if (rawValue === undefined) {\n return null;\n }\n\n if (field.value_type === \"boolean\") {\n if (typeof rawValue === \"string\") {\n if (rawValue === \"true\") {\n return true;\n }\n if (rawValue === \"false\") {\n return false;\n }\n }\n if (rawValue === null) {\n return null;\n }\n return Boolean(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n if (rawValue === \"\" || rawValue === null) {\n return null;\n }\n\n const parsedValue =\n typeof rawValue === \"number\" ? rawValue : Number(String(rawValue));\n return Number.isNaN(parsedValue) ? null : parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n if (rawValue === null) {\n return null;\n }\n\n // Treat empty objects as null so that serializer artifacts\n // (e.g. mcp_config: {} vs schema default null) don't trigger\n // a spurious view escalation in inferInitialView.\n if (\n field.value_type === \"object\" &&\n typeof rawValue === \"object\" &&\n !Array.isArray(rawValue) &&\n Object.keys(rawValue as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n\n if (typeof rawValue === \"string\") {\n const trimmedValue = rawValue.trim();\n if (!trimmedValue) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(trimmedValue);\n // Also normalise stringified empty objects\n if (\n field.value_type === \"object\" &&\n parsed !== null &&\n typeof parsed === \"object\" &&\n !Array.isArray(parsed) &&\n Object.keys(parsed as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n return JSON.stringify(parsed);\n } catch {\n return trimmedValue;\n }\n }\n\n return JSON.stringify(rawValue);\n }\n\n if (rawValue === null) {\n return null;\n }\n\n return String(rawValue);\n}\n\nexport function buildInitialSettingsFormValues(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsFormValues {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return {};\n }\n\n return Object.fromEntries(\n getSchemaFields(schema).map((field) => [\n field.key,\n normalizeFieldValue(field, getSettingValue(settings, field.key, source)),\n ]),\n );\n}\n\nexport function inferInitialView(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsView {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return \"basic\";\n }\n\n let hasMinorOverride = false;\n let hasMajorOverride = false;\n\n for (const field of getSchemaFields(schema)) {\n if (!isCriticalField(field)) {\n const currentValue = getSettingValue(settings, field.key, source);\n const isDifferent =\n normalizeComparableValue(\n field,\n currentValue ?? field.default ?? null,\n ) !== normalizeComparableValue(field, field.default ?? null);\n\n if (isDifferent) {\n if (isMinorField(field)) {\n hasMinorOverride = true;\n } else {\n hasMajorOverride = true;\n }\n }\n }\n }\n\n if (hasMinorOverride) return \"all\";\n if (hasMajorOverride) return \"advanced\";\n return \"basic\";\n}\n\n/** Determine which view tier to default to based on whether the user has\n * overridden any non-critical settings. */\nexport function hasAdvancedSettingsOverrides(settings: Settings): boolean {\n return inferInitialView(settings) !== \"basic\";\n}\n\nexport function isSettingsFieldVisible(\n field: SettingsFieldSchema,\n values: SettingsFormValues,\n): boolean {\n return field.depends_on.every((dependency) => values[dependency] === true);\n}\n\nfunction parseBooleanFieldValue(rawValue: string | boolean): boolean | null {\n if (typeof rawValue === \"boolean\") {\n return rawValue;\n }\n\n const normalizedValue = rawValue.trim().toLowerCase();\n if (!normalizedValue) {\n return null;\n }\n if (normalizedValue === \"true\") {\n return true;\n }\n if (normalizedValue === \"false\") {\n return false;\n }\n\n throw new Error(`Expected a boolean value, received: ${rawValue}`);\n}\n\nfunction coerceFieldValue(\n field: SettingsFieldSchema,\n rawValue: string | boolean,\n): SettingsValue {\n if (field.value_type === \"boolean\") {\n return parseBooleanFieldValue(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n const parsedValue = Number(stringValue);\n if (Number.isNaN(parsedValue)) {\n throw new Error(`Expected a numeric value, received: ${stringValue}`);\n }\n if (field.value_type === \"integer\" && !Number.isInteger(parsedValue)) {\n throw new Error(`Expected an integer value, received: ${stringValue}`);\n }\n\n const constraints = getSettingsFieldConstraints(field.key);\n if (constraints?.min != null && parsedValue < constraints.min) {\n throw new Error(`${field.label} must be at least ${constraints.min}`);\n }\n if (constraints?.max != null && parsedValue > constraints.max) {\n throw new Error(`${field.label} must be at most ${constraints.max}`);\n }\n\n return parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n let parsedValue: unknown;\n try {\n parsedValue = JSON.parse(stringValue);\n } catch {\n throw new Error(`Invalid JSON for ${field.label}`);\n }\n\n if (field.value_type === \"array\") {\n if (!Array.isArray(parsedValue)) {\n throw new Error(`${field.label} must be a JSON array`);\n }\n return parsedValue as SettingsValue[];\n }\n\n if (\n parsedValue === null ||\n Array.isArray(parsedValue) ||\n typeof parsedValue !== \"object\"\n ) {\n throw new Error(`${field.label} must be a JSON object`);\n }\n\n return parsedValue as { [key: string]: SettingsValue };\n }\n\n const stringValue = String(rawValue);\n if (stringValue === \"\" && !field.secret) {\n return null;\n }\n\n return stringValue;\n}\n\nexport function buildSdkSettingsPayload(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n): SdkSettingsPayload {\n const payload: Record<string, unknown> = {};\n\n for (const field of getSchemaFields(schema)) {\n if (dirty[field.key]) {\n setDotted(payload, field.key, coerceFieldValue(field, values[field.key]));\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\nfunction isFieldVisibleInView(\n field: SettingsFieldSchema,\n view: SettingsView,\n): boolean {\n return VIEW_PROMINENCES[view].has(field.prominence);\n}\n\nexport function buildSdkSettingsPayloadForView(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n view: SettingsView,\n): SdkSettingsPayload {\n const payload = buildSdkSettingsPayload(schema, values, dirty) as Record<\n string,\n unknown\n >;\n\n for (const field of getSchemaFields(schema)) {\n if (!isFieldVisibleInView(field, view)) {\n setDotted(payload, field.key, field.default ?? null);\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\n/** Return sections with fields filtered for the current view tier.\n * Specially-rendered fields are excluded from the generic list. */\nexport function getVisibleSettingsSections(\n schema: SettingsSchema,\n values: SettingsFormValues,\n view: SettingsView,\n excludeKeys: Set<string> = SPECIALLY_RENDERED_KEYS,\n): SettingsSectionSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections\n .map((section) => ({\n ...section,\n fields: section.fields.filter(\n (field) =>\n !excludeKeys.has(field.key) &&\n isFieldVisibleInView(field, view) &&\n isSettingsFieldVisible(field, values),\n ),\n }))\n .filter((section) => section.fields.length > 0);\n}\n\n/** Whether the schema has any fields visible in the \"advanced\" tier. */\nexport function hasAdvancedSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"major\");\n}\n\n/** Whether the schema has any \"minor\" prominence fields. */\nexport function hasMinorSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"minor\");\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/scripts/dev-safe.mjs
CHANGED
|
@@ -42,7 +42,6 @@ const LOCAL_AGENT_SERVER_SUBDIRS = [
|
|
|
42
42
|
"openhands-tools",
|
|
43
43
|
"openhands-workspace",
|
|
44
44
|
];
|
|
45
|
-
const DEFAULT_SECRET_KEY = SHARED_DEFAULTS.defaults.secretKey;
|
|
46
45
|
const DEFAULT_AGENT_SERVER_VERSION = SHARED_DEFAULTS.versions.agentServer;
|
|
47
46
|
const FRONTEND_REQUIRED_BINS = ["cross-env", "react-router"];
|
|
48
47
|
|
|
@@ -54,20 +53,36 @@ export function generateRandomApiKey() {
|
|
|
54
53
|
return randomBytes(32).toString("hex");
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
// Where the auto-generated
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
56
|
+
// Where the auto-generated API key is persisted so it stays stable across
|
|
57
|
+
// `npm run dev` restarts. Keeping the key stable means the value baked into
|
|
58
|
+
// the frontend (VITE_SESSION_API_KEY) and the persisted backend-registry entry
|
|
59
|
+
// (`openhands-backends` localStorage) stay in sync without users needing to
|
|
60
|
+
// set anything in `.env`.
|
|
62
61
|
//
|
|
63
62
|
// To rotate the key, delete this file. To pin a key explicitly, export
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
export const DEFAULT_SESSION_API_KEY_PATH = path.join(
|
|
63
|
+
// LOCAL_BACKEND_API_KEY — it takes precedence over the persisted file.
|
|
64
|
+
export const DEFAULT_API_KEY_PATH = path.join(
|
|
67
65
|
homedir(),
|
|
68
66
|
".openhands",
|
|
69
67
|
"agent-canvas",
|
|
70
|
-
"
|
|
68
|
+
"api-key.txt",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/** @deprecated Use DEFAULT_API_KEY_PATH */
|
|
72
|
+
export const DEFAULT_SESSION_API_KEY_PATH = DEFAULT_API_KEY_PATH;
|
|
73
|
+
|
|
74
|
+
// Where the OH_SECRET_KEY is persisted so dev mode and Docker mode share the
|
|
75
|
+
// same encryption key when both use ~/.openhands as their state directory.
|
|
76
|
+
// docker/entrypoint.sh reads and writes this same file, so whichever mode runs
|
|
77
|
+
// first generates the key and the other picks it up automatically.
|
|
78
|
+
//
|
|
79
|
+
// To rotate the key, delete this file and restart both modes. To pin a key
|
|
80
|
+
// explicitly, export OH_SECRET_KEY — that takes precedence over the file.
|
|
81
|
+
export const DEFAULT_SECRET_KEY_PATH = path.join(
|
|
82
|
+
homedir(),
|
|
83
|
+
".openhands",
|
|
84
|
+
"agent-canvas",
|
|
85
|
+
"secret-key.txt",
|
|
71
86
|
);
|
|
72
87
|
|
|
73
88
|
// Cache so repeated lookups within a single process return the same key,
|
|
@@ -75,22 +90,29 @@ export const DEFAULT_SESSION_API_KEY_PATH = path.join(
|
|
|
75
90
|
const persistedApiKeyCache = new Map();
|
|
76
91
|
|
|
77
92
|
/**
|
|
78
|
-
* Load the persisted default
|
|
79
|
-
*
|
|
93
|
+
* Load the persisted default API key, generating + persisting one if the file
|
|
94
|
+
* doesn't exist yet.
|
|
80
95
|
*
|
|
81
96
|
* Best-effort: if the file can't be written (e.g. read-only home dir), we
|
|
82
97
|
* fall back to an in-memory key for this process so dev still works -- the
|
|
83
98
|
* key just won't survive a restart.
|
|
84
99
|
*
|
|
85
100
|
* @param {string} filePath - Where to read/write the key.
|
|
86
|
-
* @returns {string} The (hex)
|
|
101
|
+
* @returns {string} The (hex) API key.
|
|
87
102
|
*/
|
|
88
|
-
export function
|
|
89
|
-
filePath =
|
|
103
|
+
export function getOrCreatePersistedApiKeyFile(
|
|
104
|
+
filePath = DEFAULT_API_KEY_PATH,
|
|
90
105
|
) {
|
|
91
106
|
return getOrCreatePersistedApiKey(filePath, "session");
|
|
92
107
|
}
|
|
93
108
|
|
|
109
|
+
/** @deprecated Use getOrCreatePersistedApiKeyFile */
|
|
110
|
+
export function getOrCreatePersistedSessionApiKey(
|
|
111
|
+
filePath = DEFAULT_API_KEY_PATH,
|
|
112
|
+
) {
|
|
113
|
+
return getOrCreatePersistedApiKeyFile(filePath);
|
|
114
|
+
}
|
|
115
|
+
|
|
94
116
|
/**
|
|
95
117
|
* Load a persisted default API key, generating + persisting one if the file
|
|
96
118
|
* doesn't exist yet.
|
|
@@ -568,26 +590,26 @@ function buildConfigFromPorts(ports, cwd, env) {
|
|
|
568
590
|
);
|
|
569
591
|
const conversationsPath = path.join(stateDir, "conversations");
|
|
570
592
|
const workspacesPath = path.join(stateDir, "workspaces");
|
|
571
|
-
// Use provided secret key or
|
|
572
|
-
|
|
573
|
-
//
|
|
574
|
-
// ~/.openhands/
|
|
575
|
-
|
|
593
|
+
// Use provided secret key, or read/generate one persisted to
|
|
594
|
+
// ~/.openhands/agent-canvas/secret-key.txt. Persisting ensures dev mode
|
|
595
|
+
// and Docker mode share the same encryption key when they mount the same
|
|
596
|
+
// ~/.openhands directory (docker/entrypoint.sh reads/writes the same file).
|
|
597
|
+
const secretKeyPath =
|
|
598
|
+
env.OH_SECRET_KEY_PATH || DEFAULT_SECRET_KEY_PATH;
|
|
599
|
+
const secretKey =
|
|
600
|
+
env.OH_SECRET_KEY || getOrCreatePersistedApiKey(secretKeyPath, "secret");
|
|
601
|
+
// Use the user-provided LOCAL_BACKEND_API_KEY or fall back to a key
|
|
602
|
+
// persisted to ~/.openhands/agent-canvas/api-key.txt. Persisting on disk
|
|
603
|
+
// keeps the agent-server, the Vite-baked VITE_SESSION_API_KEY, and any
|
|
576
604
|
// `openhands-backends` localStorage entries the frontend has cached all
|
|
577
605
|
// pointing at the same value across dev restarts.
|
|
578
606
|
//
|
|
579
|
-
//
|
|
580
|
-
// - SESSION_API_KEY: Common name
|
|
581
|
-
// - OH_SESSION_API_KEYS_0: Used by agent-server V1 config
|
|
582
|
-
// - VITE_SESSION_API_KEY: Used by frontend config
|
|
607
|
+
// LOCAL_BACKEND_API_KEY is the single user-facing env var for the API key.
|
|
583
608
|
// OH_SESSION_API_KEY_PATH overrides the persisted file path (used by tests).
|
|
584
|
-
const persistedKeyPath =
|
|
585
|
-
env.OH_SESSION_API_KEY_PATH || DEFAULT_SESSION_API_KEY_PATH;
|
|
609
|
+
const persistedKeyPath = env.OH_SESSION_API_KEY_PATH || DEFAULT_API_KEY_PATH;
|
|
586
610
|
const sessionApiKey =
|
|
587
|
-
env.
|
|
588
|
-
|
|
589
|
-
env.VITE_SESSION_API_KEY ||
|
|
590
|
-
getOrCreatePersistedSessionApiKey(persistedKeyPath);
|
|
611
|
+
env.LOCAL_BACKEND_API_KEY ||
|
|
612
|
+
getOrCreatePersistedApiKeyFile(persistedKeyPath);
|
|
591
613
|
|
|
592
614
|
// Host directory containing Agent-Canvas-specific Python tools (e.g. the
|
|
593
615
|
// canvas_ui tool). Added to OH_EXTRA_PYTHON_PATH below so the agent-server
|
|
@@ -899,16 +921,13 @@ async function main() {
|
|
|
899
921
|
|
|
900
922
|
const secretKeySource = process.env.OH_SECRET_KEY
|
|
901
923
|
? "custom (from OH_SECRET_KEY)"
|
|
902
|
-
:
|
|
903
|
-
|
|
904
|
-
const sessionKeySource =
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
: `persisted (${
|
|
910
|
-
process.env.OH_SESSION_API_KEY_PATH || DEFAULT_SESSION_API_KEY_PATH
|
|
911
|
-
})`;
|
|
924
|
+
: `persisted (${process.env.OH_SECRET_KEY_PATH || DEFAULT_SECRET_KEY_PATH})`;
|
|
925
|
+
|
|
926
|
+
const sessionKeySource = process.env.LOCAL_BACKEND_API_KEY
|
|
927
|
+
? "custom (from LOCAL_BACKEND_API_KEY)"
|
|
928
|
+
: `persisted (${
|
|
929
|
+
process.env.OH_SESSION_API_KEY_PATH || DEFAULT_API_KEY_PATH
|
|
930
|
+
})`;
|
|
912
931
|
|
|
913
932
|
console.log(`- agent-server: ${agentServerCmd.source}`);
|
|
914
933
|
console.log(`- backend: ${config.backendBaseUrl}`);
|
package/scripts/dev-static.mjs
CHANGED
|
@@ -388,9 +388,8 @@ function startStaticServer(config) {
|
|
|
388
388
|
"0.0.0.0",
|
|
389
389
|
"--port",
|
|
390
390
|
String(config.vitePort),
|
|
391
|
-
// Inject the
|
|
392
|
-
//
|
|
393
|
-
// into the bundle at publish time.
|
|
391
|
+
// Inject the API key so the pre-built frontend can authenticate
|
|
392
|
+
// to the agent-server without a baked-in VITE_SESSION_API_KEY.
|
|
394
393
|
...(config.sessionApiKey
|
|
395
394
|
? ["--session-api-key", config.sessionApiKey]
|
|
396
395
|
: []),
|