@openhands/agent-canvas 1.0.0-alpha.9 → 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.
Files changed (370) hide show
  1. package/README.md +2 -2
  2. package/bin/agent-canvas.mjs +22 -2
  3. package/build/assets/{QueryClientProvider-B7kl84Kj.js → QueryClientProvider-CkGuhXg-.js} +1 -1
  4. package/build/assets/{Trans-1j65oy9O.js → Trans-Cvm_-SMi.js} +1 -1
  5. package/build/assets/acp-providers-CbiRekh9.js +1 -0
  6. package/build/assets/{acp-route-guard-CQTmeJwM.js → acp-route-guard-B2yoBZ_4.js} +1 -1
  7. package/build/assets/{active-backend-context-TVbjnvmP.js → active-backend-context-cCM1vYYZ.js} +1 -1
  8. package/build/assets/add-backend-modal-DIUQzMPa.js +1 -0
  9. package/build/assets/agent-server-client-options-Bc5ZorQZ.js +1 -0
  10. package/build/assets/agent-server-compatibility-BlkUsrX2.js +1 -0
  11. package/build/assets/agent-server-conversation-service.api-DFvqqEDo.js +5 -0
  12. package/build/assets/{agent-settings-B247S9G3.js → agent-settings-CnGSCmK8.js} +1 -1
  13. package/build/assets/{alert-banner-BWoqueRw.js → alert-banner-DtzAX654.js} +1 -1
  14. package/build/assets/{analytics-consent-form-modal-C7sXfxRh.js → analytics-consent-form-modal-CHZ3I37v.js} +1 -1
  15. package/build/assets/api-key-entry-screen-B2gynaCp.js +1 -0
  16. package/build/assets/{app-settings-BVeSaty9.js → app-settings-Db9ITeJH.js} +1 -1
  17. package/build/assets/{automation-detail-R-99FUce.js → automation-detail-Di7EOIZD.js} +1 -1
  18. package/build/assets/{automations-list-Dfu2c-_D.js → automations-list-IsIWdDiw.js} +1 -1
  19. package/build/assets/{backend-form-modal-DxYjqqAK.js → backend-form-modal-Dnk33xA_.js} +1 -1
  20. package/build/assets/{backend-synced-settings-badge-nAfiUWvM.js → backend-synced-settings-badge-Dc6c7GT4.js} +1 -1
  21. package/build/assets/{base-modal-CQRvRHu1.js → base-modal-_dYTw1ri.js} +1 -1
  22. package/build/assets/{brand-button-C2nEKopC.js → brand-button-Br7f0kZJ.js} +1 -1
  23. package/build/assets/{browser-HrYc5Gce.js → browser-D810xUYt.js} +2 -2
  24. package/build/assets/browser-store-Couc4S5D.js +1 -0
  25. package/build/assets/browser-tab-B-aIqXRl.js +1 -0
  26. package/build/assets/{checkmark-BJJrZUF8.js → checkmark-DL7acQA7.js} +1 -1
  27. package/build/assets/{chevron-left-small-CSh-sE9L.js → chevron-left-small-CVWf8TI6.js} +1 -1
  28. package/build/assets/{circle-plus-check-toggle-qs8Va1cC.js → circle-plus-check-toggle-P7ZZToV4.js} +1 -1
  29. package/build/assets/{clock-ZR4Kn-_Y.js → clock-BRjCgHTc.js} +1 -1
  30. package/build/assets/{close-BdmyeRqS.js → close-B5LROHR3.js} +1 -1
  31. package/build/assets/{combobox-caret-B53O9Hsq.js → combobox-caret-to1O8irE.js} +1 -1
  32. package/build/assets/{condenser-settings-A35V3yng.js → condenser-settings-wnEKhBof.js} +1 -1
  33. package/build/assets/{confirmation-modal-C9-La0h3.js → confirmation-modal-Dau3w_sa.js} +1 -1
  34. package/build/assets/{context-menu-list-item-Buu9nc0q.js → context-menu-list-item-CWNFpuiC.js} +1 -1
  35. package/build/assets/conversation-HlncOV7n.js +19 -0
  36. package/build/assets/conversation-MtnkpqA9.js +1 -0
  37. package/build/assets/conversation-panel-DxnM6tRe.js +1 -0
  38. package/build/assets/{conversation-service.api--f8WglOC.js → conversation-service.api-nb5W1PqR.js} +1 -1
  39. package/build/assets/{conversation-tab-empty-state-D8dNvo-V.js → conversation-tab-empty-state-DyssnnWa.js} +1 -1
  40. package/build/assets/conversation-websocket-context-C8_PkGLi.js +3 -0
  41. package/build/assets/{copy-C7Ti2d8C.js → copy-DYgmUdIw.js} +1 -1
  42. package/build/assets/{custom-toast-handlers-BOc3qeQ7.js → custom-toast-handlers-C-SZFmto.js} +1 -1
  43. package/build/assets/declaration-BNMqORFE.js +1 -0
  44. package/build/assets/{device-verify-CMusn8nX.js → device-verify-DqDlphsG.js} +1 -1
  45. package/build/assets/{dist-DZHSA2e6.js → dist-C6t0EXL7.js} +1 -1
  46. package/build/assets/{edit-automation-modal-Dnjxbjn7.js → edit-automation-modal-BGzR3nfZ.js} +1 -1
  47. package/build/assets/{ellipsis-button-ugUATsNo.js → ellipsis-button-ZyLMPURn.js} +1 -1
  48. package/build/assets/{entry.client-CqqXOSvd.js → entry.client-1VMHpktY.js} +2 -2
  49. package/build/assets/{enum-filter-dropdown-1vpOGySB.js → enum-filter-dropdown-CEgCdu4A.js} +1 -1
  50. package/build/assets/{environment-switch-overlay-CTCTQikP.js → environment-switch-overlay-XL8yCGP6.js} +1 -1
  51. package/build/assets/{extensions-hub-BSUseHVF.js → extensions-hub-C651jsVh.js} +1 -1
  52. package/build/assets/{extensions-navigation-CT1kc1u_.js → extensions-navigation-BYR8Giqq.js} +1 -1
  53. package/build/assets/files-tab-BhnLgimi.js +1 -0
  54. package/build/assets/{folder-0WSMImNX.js → folder-ZZJVGgd7.js} +1 -1
  55. package/build/assets/{git-control-bar-branch-button-C8u5rzjc.js → git-control-bar-branch-button-M34A5_vX.js} +2 -2
  56. package/build/assets/{git-provider-icon-D-a-rcLm.js → git-provider-icon-D5dCNy-k.js} +1 -1
  57. package/build/assets/home-CYQv7yc_.js +1 -0
  58. package/build/assets/{i18n-DjAGhTis.js → i18n-CTohRuoO.js} +1 -1
  59. package/build/assets/install-server-modal-f31_CLrW.js +1 -0
  60. package/build/assets/{launch-B2mbfOSm.js → launch-DHEUYn2A.js} +1 -1
  61. package/build/assets/{lesson-plan-DRYG5SLI.js → lesson-plan-dH5Bj0pN.js} +1 -1
  62. package/build/assets/{link-external-C9d6Fo3x.js → link-external-D2POYx4c.js} +1 -1
  63. package/build/assets/{llm-client-ChQzg4wX.js → llm-client-DaH1TuyR.js} +1 -1
  64. package/build/assets/llm-settings-Bql-vydt.js +1 -0
  65. package/build/assets/llm-settings-C_tal6Ds.js +1 -0
  66. package/build/assets/{loading-spinner-C04FGh14.js → loading-spinner-BPtYORNK.js} +1 -1
  67. package/build/assets/{manage-backends-modal-s22zCdEW.js → manage-backends-modal-l7RkKfwX.js} +1 -1
  68. package/build/assets/{manage-workspaces-modal-C5EuW8m1.js → manage-workspaces-modal-DhKF_8z3.js} +1 -1
  69. package/build/assets/manifest-9fee01b9.js +1 -0
  70. package/build/assets/{markdown-renderer-CEX4Becj.js → markdown-renderer-DMzf2i4x.js} +1 -1
  71. package/build/assets/mcp-D2onbwVk.js +9 -0
  72. package/build/assets/{messages-6aOyUu3r.js → messages-BMzyOW2V.js} +1 -1
  73. package/build/assets/{modal-backdrop-DTYGVmOR.js → modal-backdrop-BAbgYsqB.js} +1 -1
  74. package/build/assets/{modal-body-YElmM1dV.js → modal-body-BI6Ru2Qr.js} +1 -1
  75. package/build/assets/{modal-close-button-C_GpQt9F.js → modal-close-button-t1Gh3qmL.js} +1 -1
  76. package/build/assets/{model-selector-DeMmw-Xa.js → model-selector-SM9IUz-q.js} +1 -1
  77. package/build/assets/{mutation-Cz7N4XAo.js → mutation-D0OogFCz.js} +1 -1
  78. package/build/assets/{navigation-context-DeIPtGPp.js → navigation-context-D0YWpT8d.js} +1 -1
  79. package/build/assets/{navigation-link-C9JD4PYD.js → navigation-link-Cn7KP3c5.js} +1 -1
  80. package/build/assets/{openhands-logo-CI5Fhn1W.js → openhands-logo-CnrF6LKb.js} +1 -1
  81. package/build/assets/{option-service.api-DsI1UW7N.js → option-service.api-KvY_mZMY.js} +1 -1
  82. package/build/assets/{organization-service.api-COwMPFg5.js → organization-service.api-DzYTHTYC.js} +1 -1
  83. package/build/assets/{path-utils-BVbe598W.js → path-utils-YohAYyMv.js} +1 -1
  84. package/build/assets/{plan-components-DEjMuDDG.js → plan-components-atxXCF0R.js} +1 -1
  85. package/build/assets/{planner-tab-bN6r1G-1.js → planner-tab-CFc-hV07.js} +1 -1
  86. package/build/assets/{profiles-client-BGkKEV9j.js → profiles-client-D6IkTJof.js} +1 -1
  87. package/build/assets/{providers-DXvCAN_u.js → providers-Bx6EfrzZ.js} +1 -1
  88. package/build/assets/{proxy-CurRmrqf.js → proxy-CxydCnis.js} +1 -1
  89. package/build/assets/{query-client-config-Ba7qAAoO.js → query-client-config-B7u9asM0.js} +1 -1
  90. package/build/assets/{recommended-automations-launcher-mJhK6Atl.js → recommended-automations-launcher-sgvfU62c.js} +3 -3
  91. package/build/assets/root-BXWU99D-.js +2 -0
  92. package/build/assets/{root-layout-BjVwHmta.js → root-layout-DVepR4To.js} +2 -2
  93. package/build/assets/sdk-section-page-DOIKvwSL.js +1 -0
  94. package/build/assets/{sdk-settings-schema-QBYH-ONX.js → sdk-settings-schema-DsUf9wu1.js} +1 -1
  95. package/build/assets/{search-Cq_cFrDt.js → search-27Owlc3A.js} +1 -1
  96. package/build/assets/{secrets-service-Bwd5DeUs.js → secrets-service-BsnKFc2x.js} +1 -1
  97. package/build/assets/secrets-settings-Bz_UohPJ.js +1 -0
  98. package/build/assets/{server-client-C3mC8Hl3.js → server-client-DyAQ3NZ_.js} +1 -1
  99. package/build/assets/{settings-D7E2U5tK.js → settings-BYkVX7vW.js} +1 -1
  100. package/build/assets/{settings-client-CwjfwoiB.js → settings-client-C73C7IgV.js} +1 -1
  101. package/build/assets/{settings-dropdown-input-VwAXNrOb.js → settings-dropdown-input-BJYvGdg-.js} +1 -1
  102. package/build/assets/{settings-gear-BJwWR1ej.js → settings-gear-C77PgE_O.js} +1 -1
  103. package/build/assets/{settings-index-J-3BNR0W.js → settings-index-Dz0BmdJD.js} +1 -1
  104. package/build/assets/{settings-input-DBywAnA7.js → settings-input-Bn7F5C75.js} +1 -1
  105. package/build/assets/{settings-list-classes-BOS092DR.js → settings-list-classes-Bf80tWtc.js} +1 -1
  106. package/build/assets/{settings-modal-B8vgWDTe.js → settings-modal-Brzgh5Yw.js} +1 -1
  107. package/build/assets/{settings-section-header-context-32x6WTyL.js → settings-section-header-context-BgZe5YkE.js} +1 -1
  108. package/build/assets/{settings-service.api-FvJGK45W.js → settings-service.api-CZ3uWx4v.js} +1 -1
  109. package/build/assets/{settings-switch-DTKmHC8F.js → settings-switch-BeIKrWms.js} +1 -1
  110. package/build/assets/{shared-conversation-EZV0FRIf.js → shared-conversation-DChOdb0t.js} +1 -1
  111. package/build/assets/{sidebar-mobile-menu-toggle-BnbzzpQl.js → sidebar-mobile-menu-toggle-BWuf4PRH.js} +1 -1
  112. package/build/assets/{sidebar-nav-link-CnWoZcwc.js → sidebar-nav-link-BGjiJq-4.js} +1 -1
  113. package/build/assets/{skill-card-pill-row-tZ599jli.js → skill-card-pill-row-DF1axQCG.js} +1 -1
  114. package/build/assets/{skills-ZyAO5dyK.js → skills-ChIKZPK4.js} +1 -1
  115. package/build/assets/{skills-plugins-BSRz041I.js → skills-plugins-CcI_19lM.js} +1 -1
  116. package/build/assets/{skills-settings-CG2hu34D.js → skills-settings-DlA5hlXw.js} +1 -1
  117. package/build/assets/{status-CsatcFbK.js → status-hp6M6E7E.js} +1 -1
  118. package/build/assets/{styled-tooltip-CS3mB_1X.js → styled-tooltip-CBzrri6o.js} +1 -1
  119. package/build/assets/{switch-skeleton-C-CfhYYV.js → switch-skeleton-DnC9wLp7.js} +1 -1
  120. package/build/assets/{task-list-tab-465DDju0.js → task-list-tab-DUJn1sgz.js} +1 -1
  121. package/build/assets/{terminal-CcgBEVnC.js → terminal-CRf9S0Z2.js} +1 -1
  122. package/build/assets/{terminal-LNa-iU5c.js → terminal-RmuaSdhJ.js} +1 -1
  123. package/build/assets/{toggle-switch-k-IZCDbt.js → toggle-switch-Pvyp2RAN.js} +1 -1
  124. package/build/assets/{typography-vVUMoNUg.js → typography-gpuWmrQO.js} +1 -1
  125. package/build/assets/{u-check-circle-DplbarS5.js → u-check-circle-IUIfACQQ.js} +1 -1
  126. package/build/assets/{u-check-circle-half-yDuiSZHC.js → u-check-circle-half-C1YxB6py.js} +1 -1
  127. package/build/assets/{u-circuit-C9tYkpeK.js → u-circuit-BmVikJHu.js} +1 -1
  128. package/build/assets/{u-edit-KAUlufD8.js → u-edit-CFvXHqZk.js} +1 -1
  129. package/build/assets/use-active-conversation-Db3IWSPK.js +1 -0
  130. package/build/assets/{use-agent-settings-schema-Bvp5UzV8.js → use-agent-settings-schema-33Un7UF2.js} +1 -1
  131. package/build/assets/{use-agent-state-D2C9SeGw.js → use-agent-state-Bn8vS5sY.js} +1 -1
  132. package/build/assets/{use-cloud-current-user-id-DWVar4st.js → use-cloud-current-user-id-CvkXFnTT.js} +1 -1
  133. package/build/assets/use-config-Co1O8-Ey.js +1 -0
  134. package/build/assets/{use-create-conversation-BEZg__Vv.js → use-create-conversation-CKS3EAHu.js} +1 -1
  135. package/build/assets/use-get-secrets-DuhdIA59.js +1 -0
  136. package/build/assets/{use-handle-plan-click-uOpew2LO.js → use-handle-plan-click-C9zJpK8A.js} +1 -1
  137. package/build/assets/use-is-authed-BggE5wPj.js +1 -0
  138. package/build/assets/{use-is-creating-conversation-DhDeeWfA.js → use-is-creating-conversation-BZ5hB_Bg.js} +1 -1
  139. package/build/assets/{use-launch-skill-in-chat-DVGPFrbI.js → use-launch-skill-in-chat-fNN_xGZG.js} +1 -1
  140. package/build/assets/{use-llm-profiles-O4a9V6RC.js → use-llm-profiles-DDOol3gK.js} +1 -1
  141. package/build/assets/use-runtime-is-ready-CQCE3xZC.js +1 -0
  142. package/build/assets/{use-save-settings-CEEKSTWG.js → use-save-settings-VUrj_QNG.js} +1 -1
  143. package/build/assets/{use-settings-DQ7Oo1Hj.js → use-settings-DQIZmIov.js} +1 -1
  144. package/build/assets/{use-settings-nav-items-YmrXrjn9.js → use-settings-nav-items-1ZvovKSr.js} +1 -1
  145. package/build/assets/{use-skills-BIvlWblA.js → use-skills-DAMLFjKU.js} +1 -1
  146. package/build/assets/use-unified-vscode-url-sZt29HrC.js +1 -0
  147. package/build/assets/use-user-conversation-DfgEB6RW.js +1 -0
  148. package/build/assets/{useMutation-B4OUESdw.js → useMutation-DqrumCWD.js} +1 -1
  149. package/build/assets/{useTranslation-CpIcQBq6.js → useTranslation-DCOdSSMl.js} +1 -1
  150. package/build/assets/{utils-D-HX7JCe.js → utils-i18rdUj2.js} +1 -1
  151. package/build/assets/v4-CNn21NXa.js +1 -0
  152. package/build/assets/{vendor~browser-Dr71AdrG.js → vendor~browser-BNjNhjFU.js} +1 -1
  153. package/build/assets/{vendor~browser-tab-BiVxfjJo.js → vendor~browser-tab-BgwV1mxF.js} +1 -1
  154. package/build/assets/{vendor~conversation-panel~conversation-BlCIz9XQ.js → vendor~conversation-panel~conversation-a9SyrrhV.js} +1 -1
  155. package/build/assets/{vendor~files-tab-DtLR-QD9.js → vendor~files-tab-BGKayPiK.js} +1 -1
  156. 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
  157. package/build/assets/{vendor~home~mcp~automations-list-C5PoHCy6.js → vendor~home~mcp~automations-list-Ccy2I0KU.js} +1 -1
  158. package/build/assets/{vendor~home~mcp~automations-list-BUBGGGYz.js → vendor~home~mcp~automations-list-DoPfwaXj.js} +1 -1
  159. 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
  160. 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
  161. package/build/assets/{vendor~launch-Dg--Ssk6.js → vendor~launch-vdeRTWFu.js} +1 -1
  162. 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
  163. 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
  164. 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
  165. 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
  166. 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
  167. 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
  168. 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
  169. package/build/assets/{verification-settings-BtlTiHP8.js → verification-settings-CsbvQcYS.js} +1 -1
  170. package/build/assets/{vscode-tab-B0vdh9gU.js → vscode-tab-Zb-QbTuV.js} +1 -1
  171. package/build/assets/{waiting-for-runtime-message-DWPl_Yby.js → waiting-for-runtime-message-CntjExbU.js} +1 -1
  172. package/build/assets/{x-mark-CWI0f9yI.js → x-mark-CrpjscNc.js} +1 -1
  173. package/build/index.html +4 -4
  174. package/build/locales/ar/openhands.json +7 -0
  175. package/build/locales/ca/openhands.json +7 -0
  176. package/build/locales/de/openhands.json +7 -0
  177. package/build/locales/en/openhands.json +7 -0
  178. package/build/locales/es/openhands.json +7 -0
  179. package/build/locales/fr/openhands.json +7 -0
  180. package/build/locales/it/openhands.json +7 -0
  181. package/build/locales/ja/openhands.json +7 -0
  182. package/build/locales/ko-KR/openhands.json +7 -0
  183. package/build/locales/no/openhands.json +7 -0
  184. package/build/locales/pt/openhands.json +7 -0
  185. package/build/locales/tr/openhands.json +7 -0
  186. package/build/locales/uk/openhands.json +7 -0
  187. package/build/locales/zh-CN/openhands.json +7 -0
  188. package/build/locales/zh-TW/openhands.json +7 -0
  189. package/config/defaults.json +0 -4
  190. package/dist/api/agent-server-adapter.cjs +1 -1
  191. package/dist/api/agent-server-adapter.cjs.map +1 -1
  192. package/dist/api/agent-server-adapter.js +1 -1
  193. package/dist/api/agent-server-adapter.js.map +1 -1
  194. package/dist/api/agent-server-compatibility.cjs +1 -1
  195. package/dist/api/agent-server-compatibility.cjs.map +1 -1
  196. package/dist/api/agent-server-compatibility.d.ts +16 -0
  197. package/dist/api/agent-server-compatibility.js +31 -20
  198. package/dist/api/agent-server-compatibility.js.map +1 -1
  199. package/dist/api/agent-server-config.cjs +1 -1
  200. package/dist/api/agent-server-config.cjs.map +1 -1
  201. package/dist/api/agent-server-config.d.ts +45 -0
  202. package/dist/api/agent-server-config.js +49 -21
  203. package/dist/api/agent-server-config.js.map +1 -1
  204. package/dist/api/backend-registry/storage.cjs +1 -1
  205. package/dist/api/backend-registry/storage.cjs.map +1 -1
  206. package/dist/api/backend-registry/storage.js +34 -32
  207. package/dist/api/backend-registry/storage.js.map +1 -1
  208. package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
  209. package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
  210. package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +5 -4
  211. package/dist/api/conversation-service/agent-server-conversation-service.api.js +70 -76
  212. package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
  213. package/dist/components/features/backends/api-key-entry-screen.d.ts +10 -0
  214. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  215. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  216. package/dist/components/features/backends/backend-form-modal.d.ts +23 -2
  217. package/dist/components/features/backends/backend-form-modal.js +43 -38
  218. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  219. package/dist/components/features/browser/browser.cjs +1 -1
  220. package/dist/components/features/browser/browser.cjs.map +1 -1
  221. package/dist/components/features/browser/browser.js +10 -16
  222. package/dist/components/features/browser/browser.js.map +1 -1
  223. package/dist/components/features/mcp-page/install-server-modal.cjs +1 -1
  224. package/dist/components/features/mcp-page/install-server-modal.cjs.map +1 -1
  225. package/dist/components/features/mcp-page/install-server-modal.js +123 -116
  226. package/dist/components/features/mcp-page/install-server-modal.js.map +1 -1
  227. package/dist/components/features/mcp-page/installed-server-card.cjs +1 -1
  228. package/dist/components/features/mcp-page/installed-server-card.cjs.map +1 -1
  229. package/dist/components/features/mcp-page/installed-server-card.js +40 -40
  230. package/dist/components/features/mcp-page/installed-server-card.js.map +1 -1
  231. package/dist/components/features/mcp-page/marketplace-card.cjs +1 -1
  232. package/dist/components/features/mcp-page/marketplace-card.cjs.map +1 -1
  233. package/dist/components/features/mcp-page/marketplace-card.js +2 -3
  234. package/dist/components/features/mcp-page/marketplace-card.js.map +1 -1
  235. package/dist/components/features/mcp-page/marketplace-section.cjs +1 -1
  236. package/dist/components/features/mcp-page/marketplace-section.cjs.map +1 -1
  237. package/dist/components/features/mcp-page/marketplace-section.js +21 -21
  238. package/dist/components/features/mcp-page/marketplace-section.js.map +1 -1
  239. package/dist/components/features/onboarding/steps/setup-acp-secrets-step.d.ts +27 -0
  240. package/dist/components/features/settings/llm-profiles/llm-settings-local-view.cjs +1 -1
  241. package/dist/components/features/settings/llm-profiles/llm-settings-local-view.js +2 -0
  242. package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs +1 -1
  243. package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs.map +1 -1
  244. package/dist/components/features/settings/sdk-settings/sdk-section-page.d.ts +10 -1
  245. package/dist/components/features/settings/sdk-settings/sdk-section-page.js +87 -84
  246. package/dist/components/features/settings/sdk-settings/sdk-section-page.js.map +1 -1
  247. package/dist/constants/acp-providers.cjs +1 -1
  248. package/dist/constants/acp-providers.cjs.map +1 -1
  249. package/dist/constants/acp-providers.d.ts +25 -0
  250. package/dist/constants/acp-providers.js +1 -0
  251. package/dist/constants/acp-providers.js.map +1 -1
  252. package/dist/contexts/conversation-websocket-context.cjs +3 -3
  253. package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
  254. package/dist/contexts/conversation-websocket-context.js +136 -132
  255. package/dist/contexts/conversation-websocket-context.js.map +1 -1
  256. package/dist/hooks/chat/use-model-interceptor.cjs.map +1 -1
  257. package/dist/hooks/chat/use-model-interceptor.js.map +1 -1
  258. package/dist/hooks/mutation/use-switch-llm-profile.cjs.map +1 -1
  259. package/dist/hooks/mutation/use-switch-llm-profile.d.ts +1 -1
  260. package/dist/hooks/mutation/use-switch-llm-profile.js.map +1 -1
  261. package/dist/hooks/query/use-config.cjs +1 -1
  262. package/dist/hooks/query/use-config.cjs.map +1 -1
  263. package/dist/hooks/query/use-config.js +10 -10
  264. package/dist/hooks/query/use-config.js.map +1 -1
  265. package/dist/hooks/query/use-local-git-info.cjs +3 -3
  266. package/dist/hooks/query/use-local-git-info.cjs.map +1 -1
  267. package/dist/hooks/query/use-local-git-info.js +24 -25
  268. package/dist/hooks/query/use-local-git-info.js.map +1 -1
  269. package/dist/i18n/declaration.cjs +1 -1
  270. package/dist/i18n/declaration.cjs.map +1 -1
  271. package/dist/i18n/declaration.d.ts +7 -0
  272. package/dist/i18n/declaration.js +1 -1
  273. package/dist/i18n/declaration.js.map +1 -1
  274. package/dist/i18n/translation.cjs +2 -2
  275. package/dist/i18n/translation.cjs.map +1 -1
  276. package/dist/i18n/translation.js +119 -0
  277. package/dist/i18n/translation.js.map +1 -1
  278. package/dist/locales/ar/openhands.json +7 -0
  279. package/dist/locales/ca/openhands.json +7 -0
  280. package/dist/locales/de/openhands.json +7 -0
  281. package/dist/locales/en/openhands.json +7 -0
  282. package/dist/locales/es/openhands.json +7 -0
  283. package/dist/locales/fr/openhands.json +7 -0
  284. package/dist/locales/it/openhands.json +7 -0
  285. package/dist/locales/ja/openhands.json +7 -0
  286. package/dist/locales/ko-KR/openhands.json +7 -0
  287. package/dist/locales/no/openhands.json +7 -0
  288. package/dist/locales/pt/openhands.json +7 -0
  289. package/dist/locales/tr/openhands.json +7 -0
  290. package/dist/locales/uk/openhands.json +7 -0
  291. package/dist/locales/zh-CN/openhands.json +7 -0
  292. package/dist/locales/zh-TW/openhands.json +7 -0
  293. package/dist/package.cjs +1 -1
  294. package/dist/package.cjs.map +1 -1
  295. package/dist/package.js +1 -1
  296. package/dist/package.js.map +1 -1
  297. package/dist/routes/mcp.cjs +1 -1
  298. package/dist/routes/mcp.cjs.map +1 -1
  299. package/dist/routes/mcp.js +64 -64
  300. package/dist/routes/mcp.js.map +1 -1
  301. package/dist/stores/browser-store.cjs +1 -1
  302. package/dist/stores/browser-store.cjs.map +1 -1
  303. package/dist/stores/browser-store.js +1 -1
  304. package/dist/stores/browser-store.js.map +1 -1
  305. package/dist/utils/mcp-marketplace-utils.cjs +1 -1
  306. package/dist/utils/mcp-marketplace-utils.cjs.map +1 -1
  307. package/dist/utils/mcp-marketplace-utils.d.ts +13 -22
  308. package/dist/utils/mcp-marketplace-utils.js +46 -28
  309. package/dist/utils/mcp-marketplace-utils.js.map +1 -1
  310. package/dist/utils/sdk-settings-schema.cjs +1 -1
  311. package/dist/utils/sdk-settings-schema.cjs.map +1 -1
  312. package/dist/utils/sdk-settings-schema.d.ts +1 -0
  313. package/dist/utils/sdk-settings-schema.js +1 -1
  314. package/dist/utils/sdk-settings-schema.js.map +1 -1
  315. package/package.json +1 -1
  316. package/scripts/dev-safe.mjs +59 -40
  317. package/scripts/dev-static.mjs +2 -3
  318. package/scripts/dev-with-automation.mjs +75 -19
  319. package/scripts/static-server.mjs +77 -35
  320. package/tools/canvas_ui_tool.py +4 -0
  321. package/build/assets/acp-providers-DauuOsW9.js +0 -1
  322. package/build/assets/add-backend-modal-FsnpTTgO.js +0 -1
  323. package/build/assets/agent-server-client-options-DT2GP6VJ.js +0 -1
  324. package/build/assets/agent-server-compatibility-2aOx5iWd.js +0 -1
  325. package/build/assets/agent-server-conversation-service.api-BZmUqtiO.js +0 -5
  326. package/build/assets/browser-store-C3AqxAO7.js +0 -1
  327. package/build/assets/browser-tab-B_BuTvrO.js +0 -1
  328. package/build/assets/conversation--ldUK72N.js +0 -19
  329. package/build/assets/conversation-eNrhH94O.js +0 -1
  330. package/build/assets/conversation-panel-B49Jpqpb.js +0 -1
  331. package/build/assets/conversation-websocket-context-BW68-J8o.js +0 -3
  332. package/build/assets/declaration-D378OjpZ.js +0 -1
  333. package/build/assets/files-tab-CQHdWpQt.js +0 -1
  334. package/build/assets/home-DD0GroCu.js +0 -1
  335. package/build/assets/install-server-modal-z5VaHeXd.js +0 -1
  336. package/build/assets/llm-settings-BEyqixPI.js +0 -1
  337. package/build/assets/llm-settings-BdiaGFbg.js +0 -1
  338. package/build/assets/manifest-9d1c34fb.js +0 -1
  339. package/build/assets/mcp-C06YssEI.js +0 -9
  340. package/build/assets/root-3t9rxEpE.js +0 -2
  341. package/build/assets/sdk-section-page-CJW0G04-.js +0 -1
  342. package/build/assets/secrets-settings-MLXqOtX2.js +0 -1
  343. package/build/assets/use-active-conversation-DS5j9R4q.js +0 -1
  344. package/build/assets/use-config-BSu_53GL.js +0 -1
  345. package/build/assets/use-conversation-id-DajhCn2A.js +0 -1
  346. package/build/assets/use-is-authed-hXC8vxgT.js +0 -1
  347. package/build/assets/use-runtime-is-ready-pGSbPddC.js +0 -1
  348. package/build/assets/use-unified-vscode-url-wAMzv8Sn.js +0 -1
  349. package/build/assets/use-user-conversation-B_zDoSeh.js +0 -1
  350. /package/build/assets/{automation-XLxhq3I8.js → automation-IdgZq6ZK.js} +0 -0
  351. /package/build/assets/{common-SMkEaBSr.js → common-DR1t-EeP.js} +0 -0
  352. /package/build/assets/{conversation-state-store-Bc0slAjL.js → conversation-state-store-u5jepov0.js} +0 -0
  353. /package/build/assets/{dist-yMQV8IUk.js → dist-BxBP7tFD.js} +0 -0
  354. /package/build/assets/{git-status-mapper-BI8FyUVp.js → git-status-mapper-DnL9OC8_.js} +0 -0
  355. /package/build/assets/{handle-capture-consent-BfZATzpI.js → handle-capture-consent-3XrjZ8wi.js} +0 -0
  356. /package/build/assets/{iconBase-C7N9pPOs.js → iconBase-DE30Zj_-.js} +0 -0
  357. /package/build/assets/{settings-D5am1n6X.js → settings-D_H-qsRm.js} +0 -0
  358. /package/build/assets/{settings-like-page-layout-classes-Bn-M9oOa.js → settings-like-page-layout-classes-I0BDBEoq.js} +0 -0
  359. /package/build/assets/{settings-utils-BsvSU3OM.js → settings-utils-B6Nl07io.js} +0 -0
  360. /package/build/assets/{sidebar-store-cOeaKmIm.js → sidebar-store-Uy3v0AOV.js} +0 -0
  361. /package/build/assets/{use-breakpoint-B86yKT9n.js → use-breakpoint-DbJ6FkQ-.js} +0 -0
  362. /package/build/assets/{use-click-outside-element-835W9pC6.js → use-click-outside-element-DffgWWoZ.js} +0 -0
  363. /package/build/assets/{use-task-list-DDeNHprj.js → use-task-list-CLJbuJgM.js} +0 -0
  364. /package/build/assets/{vendor~browser-BpdPBhgZ.js → vendor~browser-DDiZgqD3.js} +0 -0
  365. /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-Df7_G0zR.js → vendor~conversation-panel~conversation~alert-banner-DbvX3OcM.js} +0 -0
  366. /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
  367. /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
  368. /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
  369. /package/build/assets/{vendor~terminal-BUxzHKcC.js → vendor~terminal-DUrOWGFE.js} +0 -0
  370. /package/build/assets/{vscode-url-helper-jesbpos5.js → vscode-url-helper-Cwy1A62q.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-marketplace-utils.js","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationTransport as MarketplaceTemplate,\n} from \"@openhands/extensions/integrations\";\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Get the default transport template from an integration catalog entry.\n * Integrations may have multiple connection options; we use the default\n * one (or the first if no default is specified). Only MCP-backed options\n * have a `transport` field.\n */\nexport function getDefaultTemplate(\n entry: MarketplaceEntry,\n): MarketplaceTemplate | undefined {\n const option =\n entry.connectionOptions.find(\n (o) => o.id === entry.defaultConnectionOptionId,\n ) ?? entry.connectionOptions[0];\n return option?.transport;\n}\n\n/**\n * Get the stdio (API key-based) transport template from an integration entry.\n * Many integrations have multiple connection options (e.g., OAuth + stdio).\n * Since OAuth isn't implemented in the UI yet, the install modal should use\n * this function to get the stdio-based option that can be configured with\n * API keys/tokens.\n *\n * Falls back to getDefaultTemplate if no stdio option exists.\n */\nexport function getInstallableTemplate(\n entry: MarketplaceEntry,\n): MarketplaceTemplate | undefined {\n // First, try to find a stdio option (API key-based, what we can actually install)\n const stdioOption = entry.connectionOptions.find(\n (o) => o.transport?.kind === \"stdio\",\n );\n if (stdioOption?.transport) return stdioOption.transport;\n\n // Fall back to the default template (could be shttp/sse with api_key)\n return getDefaultTemplate(entry);\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n template: MarketplaceTemplate,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n if (template.kind === \"shttp\") {\n const tplUrl = template.url;\n if (!tplUrl) return null;\n return (\n servers.find(\n (s) => s.type === \"shttp\" && !!s.url && urlsMatch(s.url, tplUrl),\n ) ?? null\n );\n }\n\n if (template.kind === \"sse\") {\n const tplUrl = template.url;\n if (!tplUrl) return null;\n return (\n servers.find(\n (s) => s.type === \"sse\" && !!s.url && urlsMatch(s.url, tplUrl),\n ) ?? null\n );\n }\n\n // stdio: match on the registered server name.\n return (\n servers.find((s) => s.type === \"stdio\" && s.name === template.serverName) ??\n null\n );\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n *\n * Since an entry may have multiple connection options (e.g., OAuth + stdio),\n * we check ALL templates in the entry's connectionOptions, not just the default.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check all connection options, not just the default\n for (const option of entry.connectionOptions) {\n const tpl = option.transport;\n if (!tpl) continue;\n if (tpl.kind === \"stdio\") {\n if (server.type === \"stdio\" && server.name === tpl.serverName)\n return true;\n }\n // Reuse the same loose URL match as `findInstalledMatch` so a\n // server whose URL was normalized by the backend (trailing slash\n // stripped, query string dropped, etc.) still gets paired with\n // its catalog tile — otherwise the installed-servers list would\n // render the generic icon while the marketplace shows the\n // entry as installed, which is confusing.\n if (tpl.kind === \"shttp\") {\n if (server.type === \"shttp\" && urlsMatch(server.url, tpl.url))\n return true;\n }\n if (tpl.kind === \"sse\") {\n if (server.type === \"sse\" && urlsMatch(server.url, tpl.url))\n return true;\n }\n }\n return false;\n });\n}\n"],"mappings":";AAMA,IAAM,KAAU,MAA4B;AAC1C,KAAI;AACF,SAAO,IAAI,IAAI,EAAI;SACb;AACN,SAAO;;;AAeX,SAAgB,EAAU,GAAY,GAAqB;CACzD,IAAM,IAAO,OAAO,KAAM,WAAW,IAAI,IACnC,IAAO,OAAO,KAAM,WAAW,IAAI;AACzC,KAAI,CAAC,KAAQ,CAAC,EAAM,QAAO;CAC3B,IAAM,IAAO,EAAO,EAAK,EACnB,IAAQ,EAAO,EAAK;AAI1B,QAHI,CAAC,KAAQ,CAAC,IACL,EAAK,QAAQ,QAAQ,GAAG,KAAK,EAAK,QAAQ,QAAQ,GAAG,GAG5D,EAAK,aAAa,EAAM,YACxB,EAAK,SAAS,EAAM,QACpB,EAAK,SAAS,QAAQ,QAAQ,GAAG,KAAK,EAAM,SAAS,QAAQ,QAAQ,GAAG;;AAU5E,SAAgB,EACd,GACiC;AAKjC,SAHE,EAAM,kBAAkB,MACrB,MAAM,EAAE,OAAO,EAAM,0BACvB,IAAI,EAAM,kBAAkB,KAChB;;AAYjB,SAAgB,EACd,GACiC;CAEjC,IAAM,IAAc,EAAM,kBAAkB,MACzC,MAAM,EAAE,WAAW,SAAS,QAC9B;AAID,QAHI,GAAa,YAAkB,EAAY,YAGxC,EAAmB,EAAM;;AAuClC,SAAgB,EACd,GACA,GACS;AAGT,QAFI,CAAC,EAAM,uBAAuB,EAAM,wBAAwB,QACvD,KACF,EAAM,wBAAwB;;AAGvC,SAAS,EAAU,GAAuB;AACxC,QAAO,EAAM,MAAM,CAAC,aAAa;;AAQnC,SAAgB,EACd,GACoB;AACpB,QAAO,EACJ,KAAK,GAAO,OAAW;EAAE;EAAO;EAAO,EAAE,CACzC,MAAM,GAAG,OAEL,EAAE,MAAM,kBAAkB,MAAM,EAAE,MAAM,kBAAkB,MACtC,EAAE,QAAQ,EAAE,MACnC,CACD,KAAK,EAAE,eAAY,EAAM;;AAU9B,SAAgB,EACd,GACA,GACS;CACT,IAAM,IAAI,EAAU,EAAS;AAU7B,QATK,IACY;EACf,EAAM;EACN,EAAM;EACN,EAAM;EACN,GAAI,EAAM,YAAY,EAAE;EACzB,CACE,KAAK,IAAI,CACT,aACI,CAAS,SAAS,EAAE,GATZ;;AAmBjB,SAAgB,EACd,GACA,GACA,GACS;CACT,IAAM,IAAI,EAAU,EAAS;AAgB7B,QAfK,IACY;EACf,EAAO;EACP,UAAU,IAAS,EAAO,OAAO,KAAA;EACjC,aAAa,IAAS,EAAO,UAAU,KAAA;EACvC,UAAU,IAAS,EAAO,MAAM,KAAK,IAAI,GAAG,KAAA;EAC5C,SAAS,IAAS,EAAO,MAAM,KAAA;EAC/B,GAAc;EACd,GAAc;EACd,GAAc;EACd,GAAI,GAAc,YAAY,EAAE;EACjC,CACE,OAAO,QAAQ,CACf,KAAK,IAAI,CACT,aACI,CAAS,SAAS,EAAE,GAfZ;;AA0BjB,SAAgB,EACd,GACA,GAC8B;AAC9B,QAAO,EAAQ,MAAM,MAAU;AAE7B,OAAK,IAAM,KAAU,EAAM,mBAAmB;GAC5C,IAAM,IAAM,EAAO;AACd,aACD,EAAI,SAAS,WACX,EAAO,SAAS,WAAW,EAAO,SAAS,EAAI,cASjD,EAAI,SAAS,WACX,EAAO,SAAS,WAAW,EAAU,EAAO,KAAK,EAAI,IAAI,IAG3D,EAAI,SAAS,SACX,EAAO,SAAS,SAAS,EAAU,EAAO,KAAK,EAAI,IAAI,EACzD,QAAO;;AAGb,SAAO;GACP"}
1
+ {"version":3,"file":"mcp-marketplace-utils.js","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationConnectionOption,\n IntegrationTransport,\n} from \"@openhands/extensions/integrations\";\n\nexport type { MarketplaceEntry };\n\nexport type McpMarketplaceConnectionOption = IntegrationConnectionOption & {\n provider: \"mcp\";\n transport: IntegrationTransport;\n};\n\nexport function getMcpConnectionOptions(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption[] {\n return entry.connectionOptions.filter(\n (option): option is McpMarketplaceConnectionOption =>\n option.provider === \"mcp\" && !!option.transport,\n );\n}\n\nexport function getDefaultMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n return (\n options.find((option) => option.id === entry.defaultConnectionOptionId) ??\n options[0]\n );\n}\n\nfunction isLocallyInstallableMcpOption(\n option: McpMarketplaceConnectionOption,\n): boolean {\n // The local install modal writes static MCP server config. OAuth options\n // describe hosted redirect flows, so prefer an API/stdio fallback when one\n // exists and leave OAuth as the default connection for hosted integrations.\n return option.auth.strategy !== \"oauth2\";\n}\n\nexport function getInstallableMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n const defaultOption = options.find(\n (option) => option.id === entry.defaultConnectionOptionId,\n );\n if (defaultOption && isLocallyInstallableMcpOption(defaultOption)) {\n return defaultOption;\n }\n return options.find(isLocallyInstallableMcpOption);\n}\n\nexport function getDefaultMcpTransport(\n entry: MarketplaceEntry,\n): IntegrationTransport | undefined {\n return getDefaultMcpConnectionOption(entry)?.transport;\n}\n\nexport function getMcpMarketplaceCatalog(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog.filter((entry) => !!getDefaultMcpConnectionOption(entry));\n}\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n transport: IntegrationTransport,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n return (\n servers.find((server) => transportMatchesServer(transport, server)) ?? null\n );\n}\n\nexport function findInstalledEntryMatch(\n entry: MarketplaceEntry,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n for (const option of getMcpConnectionOptions(entry)) {\n const match = findInstalledMatch(option.transport, servers);\n if (match) return match;\n }\n return null;\n}\n\nfunction transportMatchesServer(\n transport: IntegrationTransport,\n server: MCPServerConfig,\n): boolean {\n if (transport.kind === \"shttp\") {\n const tplUrl = transport.url;\n return (\n server.type === \"shttp\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n if (transport.kind === \"sse\") {\n const tplUrl = transport.url;\n return (\n server.type === \"sse\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n // stdio: match on the registered server name.\n return server.type === \"stdio\" && server.name === transport.serverName;\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check every MCP option rather than only the default. Some unified\n // integration entries default to OAuth-hosted MCP while still exposing\n // an API/stdio option; existing installed servers should match either.\n return getMcpConnectionOptions(entry).some((option) =>\n transportMatchesServer(option.transport, server),\n );\n });\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`./sdk-settings-field-metadata.cjs`);var t=new Set([`llm.model`,`llm.api_key`,`llm.base_url`]),n={basic:new Set([`critical`]),advanced:new Set([`critical`,`major`]),all:new Set([`critical`,`major`,`minor`])};function r(e){return!!e&&Array.isArray(e.sections)}function i(e){return r(e)?e.sections.flatMap(e=>e.fields):[]}function a(e,t){if(!e)return;let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function o(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e+=1){let t=r[e];(i[t]==null||typeof i[t]!=`object`||Array.isArray(i[t]))&&(i[t]={}),i=i[t]}i[r[r.length-1]]=n}function s(e,t,n=`agent_settings`){return a(e[n],t)??null}function c(e,t){return s(e,t,`agent_settings`)}function l(e){return e.choices.length>0}function u(e){return e.prominence===`critical`}function d(e){return e.prominence===`minor`}function f(e,t){let n=t??e.default;return l(e)?n==null?``:String(n):e.value_type===`boolean`?!!(n??!1):n==null?``:e.value_type===`array`||e.value_type===`object`?JSON.stringify(n,null,2):String(n)}function p(e,t){if(t===void 0)return null;if(e.value_type===`boolean`){if(typeof t==`string`){if(t===`true`)return!0;if(t===`false`)return!1}return t===null?null:!!t}if(e.value_type===`integer`||e.value_type===`number`){if(t===``||t===null)return null;let e=typeof t==`number`?t:Number(String(t));return Number.isNaN(e)?null:e}if(e.value_type===`array`||e.value_type===`object`){if(t===null||e.value_type===`object`&&typeof t==`object`&&!Array.isArray(t)&&Object.keys(t).length===0)return null;if(typeof t==`string`){let n=t.trim();if(!n)return null;try{let t=JSON.parse(n);return e.value_type===`object`&&typeof t==`object`&&t&&!Array.isArray(t)&&Object.keys(t).length===0?null:JSON.stringify(t)}catch{return n}}return JSON.stringify(t)}return t===null?null:String(t)}function m(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);return r?Object.fromEntries(i(r).map(t=>[t.key,f(t,s(e,t.key,n))])):{}}function h(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);if(!r)return`basic`;let a=!1,o=!1;for(let t of i(r))u(t)||p(t,s(e,t.key,n)??t.default??null)!==p(t,t.default??null)&&(d(t)?a=!0:o=!0);return a?`all`:o?`advanced`:`basic`}function g(e,t){return e.depends_on.every(e=>t[e]===!0)}function _(e){if(typeof e==`boolean`)return e;let t=e.trim().toLowerCase();if(!t)return null;if(t===`true`)return!0;if(t===`false`)return!1;throw Error(`Expected a boolean value, received: ${e}`)}function v(t,n){if(t.value_type===`boolean`)return _(n);if(t.value_type===`integer`||t.value_type===`number`){let r=String(n).trim();if(!r)return null;let i=Number(r);if(Number.isNaN(i))throw Error(`Expected a numeric value, received: ${r}`);if(t.value_type===`integer`&&!Number.isInteger(i))throw Error(`Expected an integer value, received: ${r}`);let a=e.getSettingsFieldConstraints(t.key);if(a?.min!=null&&i<a.min)throw Error(`${t.label} must be at least ${a.min}`);if(a?.max!=null&&i>a.max)throw Error(`${t.label} must be at most ${a.max}`);return i}if(t.value_type===`array`||t.value_type===`object`){let e=String(n).trim();if(!e)return null;let r;try{r=JSON.parse(e)}catch{throw Error(`Invalid JSON for ${t.label}`)}if(t.value_type===`array`){if(!Array.isArray(r))throw Error(`${t.label} must be a JSON array`);return r}if(r===null||Array.isArray(r)||typeof r!=`object`)throw Error(`${t.label} must be a JSON object`);return r}let r=String(n);return r===``&&!t.secret?null:r}function y(e,t,n){let r={};for(let a of i(e))n[a.key]&&o(r,a.key,v(a,t[a.key]));return r}function b(e,t){return n[t].has(e.prominence)}function x(e,t,n,r){let a=y(e,t,n);for(let t of i(e))b(t,r)||o(a,t.key,t.default??null);return a}function S(e,n,i,a=t){return r(e)?e.sections.map(e=>({...e,fields:e.fields.filter(e=>!a.has(e.key)&&b(e,i)&&g(e,n))})).filter(e=>e.fields.length>0):[]}function C(e){return e?i(e).some(e=>e.prominence===`major`):!1}function w(e){return e?i(e).some(e=>e.prominence===`minor`):!1}exports.buildInitialSettingsFormValues=m,exports.buildSdkSettingsPayloadForView=x,exports.getAgentSettingValue=c,exports.getVisibleSettingsSections=S,exports.hasAdvancedSettings=C,exports.hasMinorSettings=w,exports.inferInitialView=h,exports.isValidSettingsSchema=r;
1
+ require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`./sdk-settings-field-metadata.cjs`);var t=new Set([`llm.model`,`llm.api_key`,`llm.base_url`]),n={basic:new Set([`critical`]),advanced:new Set([`critical`,`major`]),all:new Set([`critical`,`major`,`minor`])};function r(e){return!!e&&Array.isArray(e.sections)}function i(e){return r(e)?e.sections.flatMap(e=>e.fields):[]}function a(e,t){if(!e)return;let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function o(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e+=1){let t=r[e];(i[t]==null||typeof i[t]!=`object`||Array.isArray(i[t]))&&(i[t]={}),i=i[t]}i[r[r.length-1]]=n}function s(e,t,n=`agent_settings`){return a(e[n],t)??null}function c(e,t){return s(e,t,`agent_settings`)}function l(e){return e.choices.length>0}function u(e){return e.prominence===`critical`}function d(e){return e.prominence===`minor`}function f(e,t){let n=t??e.default;return l(e)?n==null?``:String(n):e.value_type===`boolean`?!!(n??!1):n==null?``:e.value_type===`array`||e.value_type===`object`?JSON.stringify(n,null,2):String(n)}function p(e,t){if(t===void 0)return null;if(e.value_type===`boolean`){if(typeof t==`string`){if(t===`true`)return!0;if(t===`false`)return!1}return t===null?null:!!t}if(e.value_type===`integer`||e.value_type===`number`){if(t===``||t===null)return null;let e=typeof t==`number`?t:Number(String(t));return Number.isNaN(e)?null:e}if(e.value_type===`array`||e.value_type===`object`){if(t===null||e.value_type===`object`&&typeof t==`object`&&!Array.isArray(t)&&Object.keys(t).length===0)return null;if(typeof t==`string`){let n=t.trim();if(!n)return null;try{let t=JSON.parse(n);return e.value_type===`object`&&typeof t==`object`&&t&&!Array.isArray(t)&&Object.keys(t).length===0?null:JSON.stringify(t)}catch{return n}}return JSON.stringify(t)}return t===null?null:String(t)}function m(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);return r?Object.fromEntries(i(r).map(t=>[t.key,f(t,s(e,t.key,n))])):{}}function h(e,t,n=`agent_settings`){let r=t??(n===`conversation_settings`?e.conversation_settings_schema:e.agent_settings_schema);if(!r)return`basic`;let a=!1,o=!1;for(let t of i(r))u(t)||p(t,s(e,t.key,n)??t.default??null)!==p(t,t.default??null)&&(d(t)?a=!0:o=!0);return a?`all`:o?`advanced`:`basic`}function g(e,t){return e.depends_on.every(e=>t[e]===!0)}function _(e){if(typeof e==`boolean`)return e;let t=e.trim().toLowerCase();if(!t)return null;if(t===`true`)return!0;if(t===`false`)return!1;throw Error(`Expected a boolean value, received: ${e}`)}function v(t,n){if(t.value_type===`boolean`)return _(n);if(t.value_type===`integer`||t.value_type===`number`){let r=String(n).trim();if(!r)return null;let i=Number(r);if(Number.isNaN(i))throw Error(`Expected a numeric value, received: ${r}`);if(t.value_type===`integer`&&!Number.isInteger(i))throw Error(`Expected an integer value, received: ${r}`);let a=e.getSettingsFieldConstraints(t.key);if(a?.min!=null&&i<a.min)throw Error(`${t.label} must be at least ${a.min}`);if(a?.max!=null&&i>a.max)throw Error(`${t.label} must be at most ${a.max}`);return i}if(t.value_type===`array`||t.value_type===`object`){let e=String(n).trim();if(!e)return null;let r;try{r=JSON.parse(e)}catch{throw Error(`Invalid JSON for ${t.label}`)}if(t.value_type===`array`){if(!Array.isArray(r))throw Error(`${t.label} must be a JSON array`);return r}if(r===null||Array.isArray(r)||typeof r!=`object`)throw Error(`${t.label} must be a JSON object`);return r}let r=String(n);return r===``&&!t.secret?null:r}function y(e,t,n){let r={};for(let a of i(e))n[a.key]&&o(r,a.key,v(a,t[a.key]));return r}function b(e,t){return n[t].has(e.prominence)}function x(e,t,n,r){let a=y(e,t,n);for(let t of i(e))b(t,r)||o(a,t.key,t.default??null);return a}function S(e,n,i,a=t){return r(e)?e.sections.map(e=>({...e,fields:e.fields.filter(e=>!a.has(e.key)&&b(e,i)&&g(e,n))})).filter(e=>e.fields.length>0):[]}function C(e){return e?i(e).some(e=>e.prominence===`major`):!1}function w(e){return e?i(e).some(e=>e.prominence===`minor`):!1}exports.buildInitialSettingsFormValues=m,exports.buildSdkSettingsPayload=y,exports.buildSdkSettingsPayloadForView=x,exports.getAgentSettingValue=c,exports.getVisibleSettingsSections=S,exports.hasAdvancedSettings=C,exports.hasMinorSettings=w,exports.inferInitialView=h,exports.isValidSettingsSchema=r;
2
2
  //# sourceMappingURL=sdk-settings-schema.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-settings-schema.cjs","names":[],"sources":["../../src/utils/sdk-settings-schema.ts"],"sourcesContent":["import {\n SettingProminence,\n Settings,\n SettingsFieldSchema,\n SettingsSchema,\n SettingsSectionSchema,\n SettingsValue,\n} from \"#/types/settings\";\nimport { getSettingsFieldConstraints } from \"#/utils/sdk-settings-field-metadata\";\n\nexport type SettingsFormValues = Record<string, string | boolean>;\nexport type SettingsDirtyState = Record<string, boolean>;\nexport type SdkSettingsPayload = Record<string, SettingsValue>;\nexport type SettingsValueSource = \"agent_settings\" | \"conversation_settings\";\n\nexport type SettingsView = \"basic\" | \"advanced\" | \"all\";\n\n/** Fields that are rendered by purpose-built components instead of the\n * generic `SchemaField` renderer. */\nexport const SPECIALLY_RENDERED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n]);\n\n/** Prominence tiers visible at each view level. */\nconst VIEW_PROMINENCES: Record<SettingsView, Set<SettingProminence>> = {\n basic: new Set<SettingProminence>([\"critical\"]),\n advanced: new Set<SettingProminence>([\"critical\", \"major\"]),\n all: new Set<SettingProminence>([\"critical\", \"major\", \"minor\"]),\n};\n\n/**\n * True when `schema` looks like a usable `SettingsSchema` — i.e. an\n * object with an array `sections` field. Guards every helper in this\n * module against malformed/empty schema responses (e.g. when the\n * frontend ends up pointing at a host that does not actually serve\n * `/api/settings/agent-schema`, such as an unconfigured Vercel preview\n * origin that returns the React Router SPA shell for arbitrary\n * `/api/*` paths). Without this check, `schema.sections.filter(...)`\n * inside `SdkSectionPage` blows up with\n * `Cannot read properties of undefined (reading 'filter')` and React\n * Router escalates to a full-screen error page.\n */\nexport function isValidSettingsSchema(\n schema: SettingsSchema | null | undefined,\n): schema is SettingsSchema {\n return !!schema && Array.isArray((schema as SettingsSchema).sections);\n}\n\nfunction getSchemaFields(schema: SettingsSchema): SettingsFieldSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections.flatMap((section) => section.fields);\n}\n\n/** Traverse a nested object using a dotted key path (e.g. \"llm.model\"). */\nfunction lookupDotted(\n obj: Record<string, unknown> | null | undefined,\n key: string,\n): unknown {\n if (!obj) return undefined;\n const parts = key.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\n/** Set a value in a nested object at a dotted key path (e.g. \"llm.model\"). */\nfunction setDotted(\n obj: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n const parts = key.split(\".\");\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i += 1) {\n const part = parts[i];\n if (\n current[part] == null ||\n typeof current[part] !== \"object\" ||\n Array.isArray(current[part])\n ) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n\nexport function getSettingValue(\n settings: Settings,\n key: string,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsValue {\n return (lookupDotted(settings[source] as Record<string, unknown>, key) ??\n null) as SettingsValue;\n}\n\nexport function getAgentSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"agent_settings\");\n}\n\nexport function getConversationSettingValue(\n settings: Settings,\n key: string,\n): SettingsValue {\n return getSettingValue(settings, key, \"conversation_settings\");\n}\n\nfunction isChoiceField(field: SettingsFieldSchema): boolean {\n return field.choices.length > 0;\n}\n\nfunction isCriticalField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"critical\";\n}\n\nfunction isMinorField(field: SettingsFieldSchema): boolean {\n return field.prominence === \"minor\";\n}\n\nfunction normalizeFieldValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): string | boolean {\n const resolvedValue = rawValue ?? field.default;\n\n if (isChoiceField(field)) {\n return resolvedValue === null || resolvedValue === undefined\n ? \"\"\n : String(resolvedValue);\n }\n\n if (field.value_type === \"boolean\") {\n return Boolean(resolvedValue ?? false);\n }\n\n if (resolvedValue === null || resolvedValue === undefined) {\n return \"\";\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n return JSON.stringify(resolvedValue, null, 2);\n }\n\n return String(resolvedValue);\n}\n\nfunction normalizeComparableValue(\n field: SettingsFieldSchema,\n rawValue: unknown,\n): boolean | number | string | null {\n if (rawValue === undefined) {\n return null;\n }\n\n if (field.value_type === \"boolean\") {\n if (typeof rawValue === \"string\") {\n if (rawValue === \"true\") {\n return true;\n }\n if (rawValue === \"false\") {\n return false;\n }\n }\n if (rawValue === null) {\n return null;\n }\n return Boolean(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n if (rawValue === \"\" || rawValue === null) {\n return null;\n }\n\n const parsedValue =\n typeof rawValue === \"number\" ? rawValue : Number(String(rawValue));\n return Number.isNaN(parsedValue) ? null : parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n if (rawValue === null) {\n return null;\n }\n\n // Treat empty objects as null so that serializer artifacts\n // (e.g. mcp_config: {} vs schema default null) don't trigger\n // a spurious view escalation in inferInitialView.\n if (\n field.value_type === \"object\" &&\n typeof rawValue === \"object\" &&\n !Array.isArray(rawValue) &&\n Object.keys(rawValue as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n\n if (typeof rawValue === \"string\") {\n const trimmedValue = rawValue.trim();\n if (!trimmedValue) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(trimmedValue);\n // Also normalise stringified empty objects\n if (\n field.value_type === \"object\" &&\n parsed !== null &&\n typeof parsed === \"object\" &&\n !Array.isArray(parsed) &&\n Object.keys(parsed as Record<string, unknown>).length === 0\n ) {\n return null;\n }\n return JSON.stringify(parsed);\n } catch {\n return trimmedValue;\n }\n }\n\n return JSON.stringify(rawValue);\n }\n\n if (rawValue === null) {\n return null;\n }\n\n return String(rawValue);\n}\n\nexport function buildInitialSettingsFormValues(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsFormValues {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return {};\n }\n\n return Object.fromEntries(\n getSchemaFields(schema).map((field) => [\n field.key,\n normalizeFieldValue(field, getSettingValue(settings, field.key, source)),\n ]),\n );\n}\n\nexport function inferInitialView(\n settings: Settings,\n schemaOverride?: SettingsSchema | null,\n source: SettingsValueSource = \"agent_settings\",\n): SettingsView {\n const schema =\n schemaOverride ??\n (source === \"conversation_settings\"\n ? settings.conversation_settings_schema\n : settings.agent_settings_schema);\n if (!schema) {\n return \"basic\";\n }\n\n let hasMinorOverride = false;\n let hasMajorOverride = false;\n\n for (const field of getSchemaFields(schema)) {\n if (!isCriticalField(field)) {\n const currentValue = getSettingValue(settings, field.key, source);\n const isDifferent =\n normalizeComparableValue(\n field,\n currentValue ?? field.default ?? null,\n ) !== normalizeComparableValue(field, field.default ?? null);\n\n if (isDifferent) {\n if (isMinorField(field)) {\n hasMinorOverride = true;\n } else {\n hasMajorOverride = true;\n }\n }\n }\n }\n\n if (hasMinorOverride) return \"all\";\n if (hasMajorOverride) return \"advanced\";\n return \"basic\";\n}\n\n/** Determine which view tier to default to based on whether the user has\n * overridden any non-critical settings. */\nexport function hasAdvancedSettingsOverrides(settings: Settings): boolean {\n return inferInitialView(settings) !== \"basic\";\n}\n\nexport function isSettingsFieldVisible(\n field: SettingsFieldSchema,\n values: SettingsFormValues,\n): boolean {\n return field.depends_on.every((dependency) => values[dependency] === true);\n}\n\nfunction parseBooleanFieldValue(rawValue: string | boolean): boolean | null {\n if (typeof rawValue === \"boolean\") {\n return rawValue;\n }\n\n const normalizedValue = rawValue.trim().toLowerCase();\n if (!normalizedValue) {\n return null;\n }\n if (normalizedValue === \"true\") {\n return true;\n }\n if (normalizedValue === \"false\") {\n return false;\n }\n\n throw new Error(`Expected a boolean value, received: ${rawValue}`);\n}\n\nfunction coerceFieldValue(\n field: SettingsFieldSchema,\n rawValue: string | boolean,\n): SettingsValue {\n if (field.value_type === \"boolean\") {\n return parseBooleanFieldValue(rawValue);\n }\n\n if (field.value_type === \"integer\" || field.value_type === \"number\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n const parsedValue = Number(stringValue);\n if (Number.isNaN(parsedValue)) {\n throw new Error(`Expected a numeric value, received: ${stringValue}`);\n }\n if (field.value_type === \"integer\" && !Number.isInteger(parsedValue)) {\n throw new Error(`Expected an integer value, received: ${stringValue}`);\n }\n\n const constraints = getSettingsFieldConstraints(field.key);\n if (constraints?.min != null && parsedValue < constraints.min) {\n throw new Error(`${field.label} must be at least ${constraints.min}`);\n }\n if (constraints?.max != null && parsedValue > constraints.max) {\n throw new Error(`${field.label} must be at most ${constraints.max}`);\n }\n\n return parsedValue;\n }\n\n if (field.value_type === \"array\" || field.value_type === \"object\") {\n const stringValue = String(rawValue).trim();\n if (!stringValue) {\n return null;\n }\n\n let parsedValue: unknown;\n try {\n parsedValue = JSON.parse(stringValue);\n } catch {\n throw new Error(`Invalid JSON for ${field.label}`);\n }\n\n if (field.value_type === \"array\") {\n if (!Array.isArray(parsedValue)) {\n throw new Error(`${field.label} must be a JSON array`);\n }\n return parsedValue as SettingsValue[];\n }\n\n if (\n parsedValue === null ||\n Array.isArray(parsedValue) ||\n typeof parsedValue !== \"object\"\n ) {\n throw new Error(`${field.label} must be a JSON object`);\n }\n\n return parsedValue as { [key: string]: SettingsValue };\n }\n\n const stringValue = String(rawValue);\n if (stringValue === \"\" && !field.secret) {\n return null;\n }\n\n return stringValue;\n}\n\nexport function buildSdkSettingsPayload(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n): SdkSettingsPayload {\n const payload: Record<string, unknown> = {};\n\n for (const field of getSchemaFields(schema)) {\n if (dirty[field.key]) {\n setDotted(payload, field.key, coerceFieldValue(field, values[field.key]));\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\nfunction isFieldVisibleInView(\n field: SettingsFieldSchema,\n view: SettingsView,\n): boolean {\n return VIEW_PROMINENCES[view].has(field.prominence);\n}\n\nexport function buildSdkSettingsPayloadForView(\n schema: SettingsSchema,\n values: SettingsFormValues,\n dirty: SettingsDirtyState,\n view: SettingsView,\n): SdkSettingsPayload {\n const payload = buildSdkSettingsPayload(schema, values, dirty) as Record<\n string,\n unknown\n >;\n\n for (const field of getSchemaFields(schema)) {\n if (!isFieldVisibleInView(field, view)) {\n setDotted(payload, field.key, field.default ?? null);\n }\n }\n\n return payload as SdkSettingsPayload;\n}\n\n/** Return sections with fields filtered for the current view tier.\n * Specially-rendered fields are excluded from the generic list. */\nexport function getVisibleSettingsSections(\n schema: SettingsSchema,\n values: SettingsFormValues,\n view: SettingsView,\n excludeKeys: Set<string> = SPECIALLY_RENDERED_KEYS,\n): SettingsSectionSchema[] {\n if (!isValidSettingsSchema(schema)) return [];\n return schema.sections\n .map((section) => ({\n ...section,\n fields: section.fields.filter(\n (field) =>\n !excludeKeys.has(field.key) &&\n isFieldVisibleInView(field, view) &&\n isSettingsFieldVisible(field, values),\n ),\n }))\n .filter((section) => section.fields.length > 0);\n}\n\n/** Whether the schema has any fields visible in the \"advanced\" tier. */\nexport function hasAdvancedSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"major\");\n}\n\n/** Whether the schema has any \"minor\" prominence fields. */\nexport function hasMinorSettings(schema: SettingsSchema | null): boolean {\n if (!schema) return false;\n return getSchemaFields(schema).some((f) => f.prominence === \"minor\");\n}\n"],"mappings":"kGAmBA,IAAa,EAA0B,IAAI,IAAI,CAC7C,YACA,cACA,eACD,CAAC,CAGI,EAAiE,CACrE,MAAO,IAAI,IAAuB,CAAC,WAAW,CAAC,CAC/C,SAAU,IAAI,IAAuB,CAAC,WAAY,QAAQ,CAAC,CAC3D,IAAK,IAAI,IAAuB,CAAC,WAAY,QAAS,QAAQ,CAAC,CAChE,CAcD,SAAgB,EACd,EAC0B,CAC1B,MAAO,CAAC,CAAC,GAAU,MAAM,QAAS,EAA0B,SAAS,CAGvE,SAAS,EAAgB,EAA+C,CAEtE,OADK,EAAsB,EAAO,CAC3B,EAAO,SAAS,QAAS,GAAY,EAAQ,OAAO,CADhB,EAAE,CAK/C,SAAS,EACP,EACA,EACS,CACT,GAAI,CAAC,EAAK,OACV,IAAM,EAAQ,EAAI,MAAM,IAAI,CACxB,EAAmB,EACvB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAuB,OAAO,GAAY,WAAtC,EAAgD,OACpD,EAAW,EAAoC,GAEjD,OAAO,EAIT,SAAS,EACP,EACA,EACA,EACM,CACN,IAAM,EAAQ,EAAI,MAAM,IAAI,CACxB,EAAmC,EACvC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAS,EAAG,GAAK,EAAG,CAC5C,IAAM,EAAO,EAAM,IAEjB,EAAQ,IAAS,MACjB,OAAO,EAAQ,IAAU,UACzB,MAAM,QAAQ,EAAQ,GAAM,IAE5B,EAAQ,GAAQ,EAAE,EAEpB,EAAU,EAAQ,GAEpB,EAAQ,EAAM,EAAM,OAAS,IAAM,EAGrC,SAAgB,EACd,EACA,EACA,EAA8B,iBACf,CACf,OAAQ,EAAa,EAAS,GAAoC,EAAI,EACpE,KAGJ,SAAgB,EACd,EACA,EACe,CACf,OAAO,EAAgB,EAAU,EAAK,iBAAiB,CAUzD,SAAS,EAAc,EAAqC,CAC1D,OAAO,EAAM,QAAQ,OAAS,EAGhC,SAAS,EAAgB,EAAqC,CAC5D,OAAO,EAAM,aAAe,WAG9B,SAAS,EAAa,EAAqC,CACzD,OAAO,EAAM,aAAe,QAG9B,SAAS,EACP,EACA,EACkB,CAClB,IAAM,EAAgB,GAAY,EAAM,QAoBxC,OAlBI,EAAc,EAAM,CACf,GAAkB,KACrB,GACA,OAAO,EAAc,CAGvB,EAAM,aAAe,UAChB,GAAQ,GAAiB,IAG9B,GAAkB,KACb,GAGL,EAAM,aAAe,SAAW,EAAM,aAAe,SAChD,KAAK,UAAU,EAAe,KAAM,EAAE,CAGxC,OAAO,EAAc,CAG9B,SAAS,EACP,EACA,EACkC,CAClC,GAAI,IAAa,IAAA,GACf,OAAO,KAGT,GAAI,EAAM,aAAe,UAAW,CAClC,GAAI,OAAO,GAAa,SAAU,CAChC,GAAI,IAAa,OACf,MAAO,GAET,GAAI,IAAa,QACf,MAAO,GAMX,OAHI,IAAa,KACR,KAEF,EAAQ,EAGjB,GAAI,EAAM,aAAe,WAAa,EAAM,aAAe,SAAU,CACnE,GAAI,IAAa,IAAM,IAAa,KAClC,OAAO,KAGT,IAAM,EACJ,OAAO,GAAa,SAAW,EAAW,OAAO,OAAO,EAAS,CAAC,CACpE,OAAO,OAAO,MAAM,EAAY,CAAG,KAAO,EAG5C,GAAI,EAAM,aAAe,SAAW,EAAM,aAAe,SAAU,CAQjE,GAPI,IAAa,MAQf,EAAM,aAAe,UACrB,OAAO,GAAa,UACpB,CAAC,MAAM,QAAQ,EAAS,EACxB,OAAO,KAAK,EAAoC,CAAC,SAAW,EAE5D,OAAO,KAGT,GAAI,OAAO,GAAa,SAAU,CAChC,IAAM,EAAe,EAAS,MAAM,CACpC,GAAI,CAAC,EACH,OAAO,KAET,GAAI,CACF,IAAM,EAAkB,KAAK,MAAM,EAAa,CAWhD,OARE,EAAM,aAAe,UAErB,OAAO,GAAW,UADlB,GAEA,CAAC,MAAM,QAAQ,EAAO,EACtB,OAAO,KAAK,EAAkC,CAAC,SAAW,EAEnD,KAEF,KAAK,UAAU,EAAO,MACvB,CACN,OAAO,GAIX,OAAO,KAAK,UAAU,EAAS,CAOjC,OAJI,IAAa,KACR,KAGF,OAAO,EAAS,CAGzB,SAAgB,EACd,EACA,EACA,EAA8B,iBACV,CACpB,IAAM,EACJ,IACC,IAAW,wBACR,EAAS,6BACT,EAAS,uBAKf,OAJK,EAIE,OAAO,YACZ,EAAgB,EAAO,CAAC,IAAK,GAAU,CACrC,EAAM,IACN,EAAoB,EAAO,EAAgB,EAAU,EAAM,IAAK,EAAO,CAAC,CACzE,CAAC,CACH,CARQ,EAAE,CAWb,SAAgB,EACd,EACA,EACA,EAA8B,iBAChB,CACd,IAAM,EACJ,IACC,IAAW,wBACR,EAAS,6BACT,EAAS,uBACf,GAAI,CAAC,EACH,MAAO,QAGT,IAAI,EAAmB,GACnB,EAAmB,GAEvB,IAAK,IAAM,KAAS,EAAgB,EAAO,CACpC,EAAgB,EAAM,EAGvB,EACE,EAHiB,EAAgB,EAAU,EAAM,IAAK,EAItD,EAAgB,EAAM,SAAW,KAClC,GAAK,EAAyB,EAAO,EAAM,SAAW,KAAK,GAGxD,EAAa,EAAM,CACrB,EAAmB,GAEnB,EAAmB,IAQ3B,OAFI,EAAyB,MACzB,EAAyB,WACtB,QAST,SAAgB,EACd,EACA,EACS,CACT,OAAO,EAAM,WAAW,MAAO,GAAe,EAAO,KAAgB,GAAK,CAG5E,SAAS,EAAuB,EAA4C,CAC1E,GAAI,OAAO,GAAa,UACtB,OAAO,EAGT,IAAM,EAAkB,EAAS,MAAM,CAAC,aAAa,CACrD,GAAI,CAAC,EACH,OAAO,KAET,GAAI,IAAoB,OACtB,MAAO,GAET,GAAI,IAAoB,QACtB,MAAO,GAGT,MAAU,MAAM,uCAAuC,IAAW,CAGpE,SAAS,EACP,EACA,EACe,CACf,GAAI,EAAM,aAAe,UACvB,OAAO,EAAuB,EAAS,CAGzC,GAAI,EAAM,aAAe,WAAa,EAAM,aAAe,SAAU,CACnE,IAAM,EAAc,OAAO,EAAS,CAAC,MAAM,CAC3C,GAAI,CAAC,EACH,OAAO,KAGT,IAAM,EAAc,OAAO,EAAY,CACvC,GAAI,OAAO,MAAM,EAAY,CAC3B,MAAU,MAAM,uCAAuC,IAAc,CAEvE,GAAI,EAAM,aAAe,WAAa,CAAC,OAAO,UAAU,EAAY,CAClE,MAAU,MAAM,wCAAwC,IAAc,CAGxE,IAAM,EAAc,EAAA,4BAA4B,EAAM,IAAI,CAC1D,GAAI,GAAa,KAAO,MAAQ,EAAc,EAAY,IACxD,MAAU,MAAM,GAAG,EAAM,MAAM,oBAAoB,EAAY,MAAM,CAEvE,GAAI,GAAa,KAAO,MAAQ,EAAc,EAAY,IACxD,MAAU,MAAM,GAAG,EAAM,MAAM,mBAAmB,EAAY,MAAM,CAGtE,OAAO,EAGT,GAAI,EAAM,aAAe,SAAW,EAAM,aAAe,SAAU,CACjE,IAAM,EAAc,OAAO,EAAS,CAAC,MAAM,CAC3C,GAAI,CAAC,EACH,OAAO,KAGT,IAAI,EACJ,GAAI,CACF,EAAc,KAAK,MAAM,EAAY,MAC/B,CACN,MAAU,MAAM,oBAAoB,EAAM,QAAQ,CAGpD,GAAI,EAAM,aAAe,QAAS,CAChC,GAAI,CAAC,MAAM,QAAQ,EAAY,CAC7B,MAAU,MAAM,GAAG,EAAM,MAAM,uBAAuB,CAExD,OAAO,EAGT,GACE,IAAgB,MAChB,MAAM,QAAQ,EAAY,EAC1B,OAAO,GAAgB,SAEvB,MAAU,MAAM,GAAG,EAAM,MAAM,wBAAwB,CAGzD,OAAO,EAGT,IAAM,EAAc,OAAO,EAAS,CAKpC,OAJI,IAAgB,IAAM,CAAC,EAAM,OACxB,KAGF,EAGT,SAAgB,EACd,EACA,EACA,EACoB,CACpB,IAAM,EAAmC,EAAE,CAE3C,IAAK,IAAM,KAAS,EAAgB,EAAO,CACrC,EAAM,EAAM,MACd,EAAU,EAAS,EAAM,IAAK,EAAiB,EAAO,EAAO,EAAM,KAAK,CAAC,CAI7E,OAAO,EAGT,SAAS,EACP,EACA,EACS,CACT,OAAO,EAAiB,GAAM,IAAI,EAAM,WAAW,CAGrD,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,IAAM,EAAU,EAAwB,EAAQ,EAAQ,EAAM,CAK9D,IAAK,IAAM,KAAS,EAAgB,EAAO,CACpC,EAAqB,EAAO,EAAK,EACpC,EAAU,EAAS,EAAM,IAAK,EAAM,SAAW,KAAK,CAIxD,OAAO,EAKT,SAAgB,EACd,EACA,EACA,EACA,EAA2B,EACF,CAEzB,OADK,EAAsB,EAAO,CAC3B,EAAO,SACX,IAAK,IAAa,CACjB,GAAG,EACH,OAAQ,EAAQ,OAAO,OACpB,GACC,CAAC,EAAY,IAAI,EAAM,IAAI,EAC3B,EAAqB,EAAO,EAAK,EACjC,EAAuB,EAAO,EAAO,CACxC,CACF,EAAE,CACF,OAAQ,GAAY,EAAQ,OAAO,OAAS,EAAE,CAXN,EAAE,CAe/C,SAAgB,EAAoB,EAAwC,CAE1E,OADK,EACE,EAAgB,EAAO,CAAC,KAAM,GAAM,EAAE,aAAe,QAAQ,CADhD,GAKtB,SAAgB,EAAiB,EAAwC,CAEvE,OADK,EACE,EAAgB,EAAO,CAAC,KAAM,GAAM,EAAE,aAAe,QAAQ,CADhD"}
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-alpha.9",
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,
@@ -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 default session API key is persisted so it stays
58
- // stable across `npm run dev` restarts. Keeping the key stable means the value
59
- // baked into the frontend (VITE_SESSION_API_KEY) and the persisted
60
- // backend-registry entry (`openhands-backends` localStorage) stay in sync
61
- // without users needing to set anything in `.env`.
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
- // SESSION_API_KEY (or OH_SESSION_API_KEYS_0 / VITE_SESSION_API_KEY) -- those
65
- // take precedence over the persisted file.
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
- "session-api-key.txt",
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 session API key, generating + persisting one if
79
- * the file doesn't exist yet.
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) session API key.
101
+ * @returns {string} The (hex) API key.
87
102
  */
88
- export function getOrCreatePersistedSessionApiKey(
89
- filePath = DEFAULT_SESSION_API_KEY_PATH,
103
+ export function getOrCreatePersistedApiKeyFile(
104
+ filePath = DEFAULT_API_KEY_PATH,
90
105
  ) {
91
106
  return getOrCreatePersistedApiKey(filePath, "session");
92
107
  }
93
108
 
109
+ /** @deprecated Use getOrCreatePersistedApiKeyFile */
110
+ export function getOrCreatePersistedSessionApiKey(
111
+ filePath = DEFAULT_API_KEY_PATH,
112
+ ) {
113
+ return getOrCreatePersistedApiKeyFile(filePath);
114
+ }
115
+
94
116
  /**
95
117
  * Load a persisted default API key, generating + persisting one if the file
96
118
  * doesn't exist yet.
@@ -568,26 +590,26 @@ function buildConfigFromPorts(ports, cwd, env) {
568
590
  );
569
591
  const conversationsPath = path.join(stateDir, "conversations");
570
592
  const workspacesPath = path.join(stateDir, "workspaces");
571
- // Use provided secret key or default for local development
572
- const secretKey = env.OH_SECRET_KEY || DEFAULT_SECRET_KEY;
573
- // Use provided session API key or fall back to a key persisted to
574
- // ~/.openhands/agent-canvas/session-api-key.txt. Persisting on disk keeps
575
- // the agent-server, the Vite-baked VITE_SESSION_API_KEY, and any
593
+ // Use provided secret key, or read/generate one persisted to
594
+ // ~/.openhands/agent-canvas/secret-key.txt. Persisting ensures dev mode
595
+ // and Docker mode share the same encryption key when they mount the same
596
+ // ~/.openhands directory (docker/entrypoint.sh reads/writes the same file).
597
+ const secretKeyPath =
598
+ env.OH_SECRET_KEY_PATH || DEFAULT_SECRET_KEY_PATH;
599
+ const secretKey =
600
+ env.OH_SECRET_KEY || getOrCreatePersistedApiKey(secretKeyPath, "secret");
601
+ // Use the user-provided LOCAL_BACKEND_API_KEY or fall back to a key
602
+ // persisted to ~/.openhands/agent-canvas/api-key.txt. Persisting on disk
603
+ // keeps the agent-server, the Vite-baked VITE_SESSION_API_KEY, and any
576
604
  // `openhands-backends` localStorage entries the frontend has cached all
577
605
  // pointing at the same value across dev restarts.
578
606
  //
579
- // Check multiple env vars that may be used:
580
- // - SESSION_API_KEY: Common name
581
- // - OH_SESSION_API_KEYS_0: Used by agent-server V1 config
582
- // - VITE_SESSION_API_KEY: Used by frontend config
607
+ // LOCAL_BACKEND_API_KEY is the single user-facing env var for the API key.
583
608
  // OH_SESSION_API_KEY_PATH overrides the persisted file path (used by tests).
584
- const persistedKeyPath =
585
- env.OH_SESSION_API_KEY_PATH || DEFAULT_SESSION_API_KEY_PATH;
609
+ const persistedKeyPath = env.OH_SESSION_API_KEY_PATH || DEFAULT_API_KEY_PATH;
586
610
  const sessionApiKey =
587
- env.SESSION_API_KEY ||
588
- env.OH_SESSION_API_KEYS_0 ||
589
- env.VITE_SESSION_API_KEY ||
590
- getOrCreatePersistedSessionApiKey(persistedKeyPath);
611
+ env.LOCAL_BACKEND_API_KEY ||
612
+ getOrCreatePersistedApiKeyFile(persistedKeyPath);
591
613
 
592
614
  // Host directory containing Agent-Canvas-specific Python tools (e.g. the
593
615
  // canvas_ui tool). Added to OH_EXTRA_PYTHON_PATH below so the agent-server
@@ -899,16 +921,13 @@ async function main() {
899
921
 
900
922
  const secretKeySource = process.env.OH_SECRET_KEY
901
923
  ? "custom (from OH_SECRET_KEY)"
902
- : "default (for local development)";
903
-
904
- const sessionKeySource =
905
- process.env.SESSION_API_KEY ||
906
- process.env.OH_SESSION_API_KEYS_0 ||
907
- process.env.VITE_SESSION_API_KEY
908
- ? "custom (from env)"
909
- : `persisted (${
910
- process.env.OH_SESSION_API_KEY_PATH || DEFAULT_SESSION_API_KEY_PATH
911
- })`;
924
+ : `persisted (${process.env.OH_SECRET_KEY_PATH || DEFAULT_SECRET_KEY_PATH})`;
925
+
926
+ const sessionKeySource = process.env.LOCAL_BACKEND_API_KEY
927
+ ? "custom (from LOCAL_BACKEND_API_KEY)"
928
+ : `persisted (${
929
+ process.env.OH_SESSION_API_KEY_PATH || DEFAULT_API_KEY_PATH
930
+ })`;
912
931
 
913
932
  console.log(`- agent-server: ${agentServerCmd.source}`);
914
933
  console.log(`- backend: ${config.backendBaseUrl}`);
@@ -388,9 +388,8 @@ function startStaticServer(config) {
388
388
  "0.0.0.0",
389
389
  "--port",
390
390
  String(config.vitePort),
391
- // Inject the runtime session key so the pre-built frontend can
392
- // authenticate to agent-server without VITE_SESSION_API_KEY being baked
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
  : []),