@openhands/agent-canvas 1.0.0-alpha.8 → 1.0.0-beta.1
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 +17 -6
- 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-g5-RZ0da.js → automation-detail-Di7EOIZD.js} +1 -1
- package/build/assets/{automations-list-DHoq_0MM.js → automations-list-IsIWdDiw.js} +1 -1
- package/build/assets/backend-form-modal-Dnk33xA_.js +1 -0
- 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-D810xUYt.js +5 -0
- 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-C8pYCyV6.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-D9uR9Blz.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-M34A5_vX.js +27 -0
- package/build/assets/{git-provider-icon-DYE9n7fs.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-hZ0ifhcV.js → launch-DHEUYn2A.js} +1 -1
- package/build/assets/{lesson-plan-DRYG5SLI.js → lesson-plan-dH5Bj0pN.js} +1 -1
- package/build/assets/{link-external-Df8J52xI.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-rYeyGx7j.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-9fee01b9.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-T2ewVkbp.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-CqJboYxo.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-BrntFmb1.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-BI9NhG8Y.js → recommended-automations-launcher-sgvfU62c.js} +3 -3
- package/build/assets/root-BXWU99D-.js +2 -0
- package/build/assets/{root-layout-BLjAEgle.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-a0QV8o99.js → shared-conversation-DChOdb0t.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-DTUNI1WQ.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-DOnMn9q1.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-Day9nhRT.js → task-list-tab-DUJn1sgz.js} +1 -1
- package/build/assets/{terminal-ro4SNjUU.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-DE5dlEXJ.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-DW7AGgLA.js → use-create-conversation-CKS3EAHu.js} +1 -1
- package/build/assets/{use-event-store-CQZCcVz-.js → use-event-store-BT_gV3ut.js} +1 -1
- package/build/assets/use-get-secrets-DuhdIA59.js +1 -0
- package/build/assets/{use-handle-plan-click-DpgEQDAV.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-D3-KXwQ0.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-DAMLFjKU.js +1 -0
- package/build/assets/{use-task-list-Bs90uF2N.js → use-task-list-CLJbuJgM.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-C0ShhiSU.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 +2 -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/api/skills-service.cjs +1 -1
- package/dist/api/skills-service.cjs.map +1 -1
- package/dist/api/skills-service.d.ts +1 -1
- package/dist/api/skills-service.js +2 -2
- package/dist/api/skills-service.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 +185 -173
- 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/conversation-panel/skills-modal.cjs +1 -1
- package/dist/components/features/conversation-panel/skills-modal.cjs.map +1 -1
- package/dist/components/features/conversation-panel/skills-modal.js +1 -1
- package/dist/components/features/conversation-panel/skills-modal.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 +177 -165
- 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/chat/use-slash-command.cjs +1 -1
- package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
- package/dist/hooks/chat/use-slash-command.js +1 -1
- package/dist/hooks/chat/use-slash-command.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-conversation-skills.cjs +2 -0
- package/dist/hooks/query/use-conversation-skills.cjs.map +1 -0
- package/dist/hooks/query/use-conversation-skills.d.ts +7 -0
- package/dist/hooks/query/use-conversation-skills.js +8 -0
- package/dist/hooks/query/use-conversation-skills.js.map +1 -0
- 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/hooks/query/use-skills.cjs +1 -1
- package/dist/hooks/query/use-skills.cjs.map +1 -1
- package/dist/hooks/query/use-skills.d.ts +6 -1
- package/dist/hooks/query/use-skills.js +3 -3
- package/dist/hooks/query/use-skills.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 +3 -3
- package/dist/package.js.map +1 -1
- package/dist/routes/conversation.cjs +1 -1
- package/dist/routes/conversation.cjs.map +1 -1
- package/dist/routes/conversation.js +61 -63
- package/dist/routes/conversation.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/stores/use-event-store.cjs +1 -1
- package/dist/stores/use-event-store.cjs.map +1 -1
- package/dist/stores/use-event-store.d.ts +22 -0
- package/dist/stores/use-event-store.js +9 -1
- package/dist/stores/use-event-store.js.map +1 -1
- package/dist/ui/context-menu.d.ts +1 -1
- package/dist/ui/help-link.d.ts +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 +3 -3
- package/scripts/dev-safe.mjs +94 -57
- package/scripts/dev-static.mjs +2 -3
- package/scripts/dev-with-automation.mjs +98 -67
- 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-KMmPQNZU.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-DSl9G5UR.js +0 -5
- package/build/assets/backend-form-modal-K6IMCr3p.js +0 -1
- package/build/assets/browser-DKG63inJ.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-BD5WemJI.js +0 -19
- package/build/assets/conversation-C47K62n8.js +0 -1
- package/build/assets/conversation-panel-Dn-S56Gk.js +0 -1
- package/build/assets/conversation-websocket-context-Ywrxd_9p.js +0 -3
- package/build/assets/declaration-D378OjpZ.js +0 -1
- package/build/assets/files-tab-B3A1NDlZ.js +0 -1
- package/build/assets/git-control-bar-branch-button-CcIpmyfM.js +0 -27
- package/build/assets/home-dIzxi5Dd.js +0 -1
- package/build/assets/install-server-modal-z5VaHeXd.js +0 -1
- package/build/assets/llm-settings-2036m7Wt.js +0 -1
- package/build/assets/llm-settings-CcHqGOYL.js +0 -1
- package/build/assets/manifest-97e839da.js +0 -1
- package/build/assets/mcp-C06YssEI.js +0 -9
- package/build/assets/root-BS1Td78t.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-D15D9GgR.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-XFbT16BD.js +0 -1
- package/build/assets/use-skills-Xe0vjPMt.js +0 -1
- package/build/assets/use-unified-vscode-url-BOsIOd-b.js +0 -1
- package/build/assets/use-user-conversation-Mc0mQgkl.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/{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":"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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openhands/agent-canvas",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Agent Canvas UI for OpenHands - run AI coding agents with a visual interface",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"@heroui/react": "2.8.10",
|
|
24
24
|
"@microlink/react-json-view": "1.31.20",
|
|
25
25
|
"@monaco-editor/react": "4.7.0",
|
|
26
|
-
"@openhands/extensions": "git+https://github.com/OpenHands/extensions.git#
|
|
27
|
-
"@openhands/typescript-client": "1.24.
|
|
26
|
+
"@openhands/extensions": "git+https://github.com/OpenHands/extensions.git#e14f740c59b4bfd7369d4bb6aea5eeb33dd05909",
|
|
27
|
+
"@openhands/typescript-client": "1.24.3",
|
|
28
28
|
"@react-router/node": "7.14.2",
|
|
29
29
|
"@react-router/serve": "7.14.2",
|
|
30
30
|
"@tailwindcss/vite": "4.2.4",
|
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.
|
|
@@ -210,6 +232,36 @@ function tryPort(port, host = "127.0.0.1") {
|
|
|
210
232
|
});
|
|
211
233
|
}
|
|
212
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Assert that all listed ports are available, throwing a descriptive error if
|
|
237
|
+
* any are already in use.
|
|
238
|
+
*
|
|
239
|
+
* Intended as a pre-flight check before spawning services so that a concurrent
|
|
240
|
+
* agent-canvas instance is detected immediately rather than silently starting
|
|
241
|
+
* on a different port.
|
|
242
|
+
*
|
|
243
|
+
* @param {Array<{name: string, port: number}>} portConfigs - Named port list
|
|
244
|
+
* @param {string} [host]
|
|
245
|
+
*/
|
|
246
|
+
export async function assertPortsFree(portConfigs, host = "127.0.0.1") {
|
|
247
|
+
const results = await Promise.all(
|
|
248
|
+
portConfigs.map(async ({ name, port }) => ({
|
|
249
|
+
name,
|
|
250
|
+
port,
|
|
251
|
+
free: await tryPort(port, host),
|
|
252
|
+
})),
|
|
253
|
+
);
|
|
254
|
+
const busy = results.filter(({ free }) => !free);
|
|
255
|
+
if (busy.length === 0) return;
|
|
256
|
+
|
|
257
|
+
const lines = busy.map(({ name, port }) => ` • ${name}: port ${port}`).join("\n");
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Cannot start: the following ports are already in use:\n\n${lines}\n\n` +
|
|
260
|
+
`Another agent-canvas instance may already be running.\n` +
|
|
261
|
+
`Stop it first, or override the port via environment variables (e.g. PORT=<other>).`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
213
265
|
/**
|
|
214
266
|
* Find multiple free ports at once, each preferring its specified default.
|
|
215
267
|
*
|
|
@@ -491,26 +543,14 @@ export async function buildSafeDevConfigAsync(
|
|
|
491
543
|
preferredBackendPort + 1,
|
|
492
544
|
);
|
|
493
545
|
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
{ name: "
|
|
497
|
-
{ name: "vscode",
|
|
546
|
+
// Fail fast if any required port is already in use.
|
|
547
|
+
await assertPortsFree([
|
|
548
|
+
{ name: "agent-server", port: preferredBackendPort },
|
|
549
|
+
{ name: "vscode", port: preferredVscodePort },
|
|
498
550
|
]);
|
|
499
551
|
|
|
500
|
-
// Log if we're using non-default ports
|
|
501
|
-
if (ports.backend !== preferredBackendPort) {
|
|
502
|
-
console.log(
|
|
503
|
-
` ℹ Port ${preferredBackendPort} busy, using ${ports.backend} for agent-server`,
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
if (ports.vscode !== preferredVscodePort) {
|
|
507
|
-
console.log(
|
|
508
|
-
` ℹ Port ${preferredVscodePort} busy, using ${ports.vscode} for vscode`,
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
552
|
return buildConfigFromPorts(
|
|
513
|
-
{ backendPort:
|
|
553
|
+
{ backendPort: preferredBackendPort, vscodePort: preferredVscodePort },
|
|
514
554
|
cwd,
|
|
515
555
|
env,
|
|
516
556
|
);
|
|
@@ -550,26 +590,26 @@ function buildConfigFromPorts(ports, cwd, env) {
|
|
|
550
590
|
);
|
|
551
591
|
const conversationsPath = path.join(stateDir, "conversations");
|
|
552
592
|
const workspacesPath = path.join(stateDir, "workspaces");
|
|
553
|
-
// Use provided secret key or
|
|
554
|
-
|
|
555
|
-
//
|
|
556
|
-
// ~/.openhands/
|
|
557
|
-
|
|
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
|
|
558
604
|
// `openhands-backends` localStorage entries the frontend has cached all
|
|
559
605
|
// pointing at the same value across dev restarts.
|
|
560
606
|
//
|
|
561
|
-
//
|
|
562
|
-
// - SESSION_API_KEY: Common name
|
|
563
|
-
// - OH_SESSION_API_KEYS_0: Used by agent-server V1 config
|
|
564
|
-
// - VITE_SESSION_API_KEY: Used by frontend config
|
|
607
|
+
// LOCAL_BACKEND_API_KEY is the single user-facing env var for the API key.
|
|
565
608
|
// OH_SESSION_API_KEY_PATH overrides the persisted file path (used by tests).
|
|
566
|
-
const persistedKeyPath =
|
|
567
|
-
env.OH_SESSION_API_KEY_PATH || DEFAULT_SESSION_API_KEY_PATH;
|
|
609
|
+
const persistedKeyPath = env.OH_SESSION_API_KEY_PATH || DEFAULT_API_KEY_PATH;
|
|
568
610
|
const sessionApiKey =
|
|
569
|
-
env.
|
|
570
|
-
|
|
571
|
-
env.VITE_SESSION_API_KEY ||
|
|
572
|
-
getOrCreatePersistedSessionApiKey(persistedKeyPath);
|
|
611
|
+
env.LOCAL_BACKEND_API_KEY ||
|
|
612
|
+
getOrCreatePersistedApiKeyFile(persistedKeyPath);
|
|
573
613
|
|
|
574
614
|
// Host directory containing Agent-Canvas-specific Python tools (e.g. the
|
|
575
615
|
// canvas_ui tool). Added to OH_EXTRA_PYTHON_PATH below so the agent-server
|
|
@@ -881,16 +921,13 @@ async function main() {
|
|
|
881
921
|
|
|
882
922
|
const secretKeySource = process.env.OH_SECRET_KEY
|
|
883
923
|
? "custom (from OH_SECRET_KEY)"
|
|
884
|
-
:
|
|
885
|
-
|
|
886
|
-
const sessionKeySource =
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
: `persisted (${
|
|
892
|
-
process.env.OH_SESSION_API_KEY_PATH || DEFAULT_SESSION_API_KEY_PATH
|
|
893
|
-
})`;
|
|
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
|
+
})`;
|
|
894
931
|
|
|
895
932
|
console.log(`- agent-server: ${agentServerCmd.source}`);
|
|
896
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
|
: []),
|