@openhands/agent-canvas 1.0.0-alpha.8 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (409) hide show
  1. package/README.md +17 -6
  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-g5-RZ0da.js → automation-detail-Di7EOIZD.js} +1 -1
  18. package/build/assets/{automations-list-DHoq_0MM.js → automations-list-IsIWdDiw.js} +1 -1
  19. package/build/assets/backend-form-modal-Dnk33xA_.js +1 -0
  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-D810xUYt.js +5 -0
  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-C8pYCyV6.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-D9uR9Blz.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-M34A5_vX.js +27 -0
  56. package/build/assets/{git-provider-icon-DYE9n7fs.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-hZ0ifhcV.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-Df8J52xI.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-rYeyGx7j.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-T2ewVkbp.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-CqJboYxo.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-BrntFmb1.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-BI9NhG8Y.js → recommended-automations-launcher-sgvfU62c.js} +3 -3
  91. package/build/assets/root-BXWU99D-.js +2 -0
  92. package/build/assets/{root-layout-BLjAEgle.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-a0QV8o99.js → shared-conversation-DChOdb0t.js} +1 -1
  111. package/build/assets/{sidebar-mobile-menu-toggle-DTUNI1WQ.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-DOnMn9q1.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-Day9nhRT.js → task-list-tab-DUJn1sgz.js} +1 -1
  121. package/build/assets/{terminal-ro4SNjUU.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-DE5dlEXJ.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-DW7AGgLA.js → use-create-conversation-CKS3EAHu.js} +1 -1
  135. package/build/assets/{use-event-store-CQZCcVz-.js → use-event-store-BT_gV3ut.js} +1 -1
  136. package/build/assets/use-get-secrets-DuhdIA59.js +1 -0
  137. package/build/assets/{use-handle-plan-click-DpgEQDAV.js → use-handle-plan-click-C9zJpK8A.js} +1 -1
  138. package/build/assets/use-is-authed-BggE5wPj.js +1 -0
  139. package/build/assets/{use-is-creating-conversation-DhDeeWfA.js → use-is-creating-conversation-BZ5hB_Bg.js} +1 -1
  140. package/build/assets/{use-launch-skill-in-chat-DVGPFrbI.js → use-launch-skill-in-chat-fNN_xGZG.js} +1 -1
  141. package/build/assets/{use-llm-profiles-D3-KXwQ0.js → use-llm-profiles-DDOol3gK.js} +1 -1
  142. package/build/assets/use-runtime-is-ready-CQCE3xZC.js +1 -0
  143. package/build/assets/{use-save-settings-CEEKSTWG.js → use-save-settings-VUrj_QNG.js} +1 -1
  144. package/build/assets/{use-settings-DQ7Oo1Hj.js → use-settings-DQIZmIov.js} +1 -1
  145. package/build/assets/{use-settings-nav-items-YmrXrjn9.js → use-settings-nav-items-1ZvovKSr.js} +1 -1
  146. package/build/assets/use-skills-DAMLFjKU.js +1 -0
  147. package/build/assets/{use-task-list-Bs90uF2N.js → use-task-list-CLJbuJgM.js} +1 -1
  148. package/build/assets/use-unified-vscode-url-sZt29HrC.js +1 -0
  149. package/build/assets/use-user-conversation-DfgEB6RW.js +1 -0
  150. package/build/assets/{useMutation-B4OUESdw.js → useMutation-DqrumCWD.js} +1 -1
  151. package/build/assets/{useTranslation-CpIcQBq6.js → useTranslation-DCOdSSMl.js} +1 -1
  152. package/build/assets/{utils-D-HX7JCe.js → utils-i18rdUj2.js} +1 -1
  153. package/build/assets/v4-CNn21NXa.js +1 -0
  154. package/build/assets/{vendor~browser-Dr71AdrG.js → vendor~browser-BNjNhjFU.js} +1 -1
  155. package/build/assets/{vendor~browser-tab-BiVxfjJo.js → vendor~browser-tab-BgwV1mxF.js} +1 -1
  156. package/build/assets/{vendor~conversation-panel~conversation-BlCIz9XQ.js → vendor~conversation-panel~conversation-a9SyrrhV.js} +1 -1
  157. package/build/assets/{vendor~files-tab-DtLR-QD9.js → vendor~files-tab-BGKayPiK.js} +1 -1
  158. 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
  159. package/build/assets/{vendor~home~mcp~automations-list-C5PoHCy6.js → vendor~home~mcp~automations-list-Ccy2I0KU.js} +1 -1
  160. package/build/assets/{vendor~home~mcp~automations-list-BUBGGGYz.js → vendor~home~mcp~automations-list-DoPfwaXj.js} +1 -1
  161. 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
  162. 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
  163. package/build/assets/{vendor~launch-Dg--Ssk6.js → vendor~launch-vdeRTWFu.js} +1 -1
  164. 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
  165. 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
  166. 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
  167. 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
  168. 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
  169. 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
  170. 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
  171. package/build/assets/{verification-settings-BtlTiHP8.js → verification-settings-CsbvQcYS.js} +1 -1
  172. package/build/assets/{vscode-tab-C0ShhiSU.js → vscode-tab-Zb-QbTuV.js} +1 -1
  173. package/build/assets/{waiting-for-runtime-message-DWPl_Yby.js → waiting-for-runtime-message-CntjExbU.js} +1 -1
  174. package/build/assets/{x-mark-CWI0f9yI.js → x-mark-CrpjscNc.js} +1 -1
  175. package/build/index.html +4 -4
  176. package/build/locales/ar/openhands.json +7 -0
  177. package/build/locales/ca/openhands.json +7 -0
  178. package/build/locales/de/openhands.json +7 -0
  179. package/build/locales/en/openhands.json +7 -0
  180. package/build/locales/es/openhands.json +7 -0
  181. package/build/locales/fr/openhands.json +7 -0
  182. package/build/locales/it/openhands.json +7 -0
  183. package/build/locales/ja/openhands.json +7 -0
  184. package/build/locales/ko-KR/openhands.json +7 -0
  185. package/build/locales/no/openhands.json +7 -0
  186. package/build/locales/pt/openhands.json +7 -0
  187. package/build/locales/tr/openhands.json +7 -0
  188. package/build/locales/uk/openhands.json +7 -0
  189. package/build/locales/zh-CN/openhands.json +7 -0
  190. package/build/locales/zh-TW/openhands.json +7 -0
  191. package/config/defaults.json +0 -4
  192. package/dist/api/agent-server-adapter.cjs +1 -1
  193. package/dist/api/agent-server-adapter.cjs.map +1 -1
  194. package/dist/api/agent-server-adapter.js +2 -1
  195. package/dist/api/agent-server-adapter.js.map +1 -1
  196. package/dist/api/agent-server-compatibility.cjs +1 -1
  197. package/dist/api/agent-server-compatibility.cjs.map +1 -1
  198. package/dist/api/agent-server-compatibility.d.ts +16 -0
  199. package/dist/api/agent-server-compatibility.js +31 -20
  200. package/dist/api/agent-server-compatibility.js.map +1 -1
  201. package/dist/api/agent-server-config.cjs +1 -1
  202. package/dist/api/agent-server-config.cjs.map +1 -1
  203. package/dist/api/agent-server-config.d.ts +45 -0
  204. package/dist/api/agent-server-config.js +49 -21
  205. package/dist/api/agent-server-config.js.map +1 -1
  206. package/dist/api/backend-registry/storage.cjs +1 -1
  207. package/dist/api/backend-registry/storage.cjs.map +1 -1
  208. package/dist/api/backend-registry/storage.js +34 -32
  209. package/dist/api/backend-registry/storage.js.map +1 -1
  210. package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
  211. package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
  212. package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +5 -4
  213. package/dist/api/conversation-service/agent-server-conversation-service.api.js +70 -76
  214. package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
  215. package/dist/api/skills-service.cjs +1 -1
  216. package/dist/api/skills-service.cjs.map +1 -1
  217. package/dist/api/skills-service.d.ts +1 -1
  218. package/dist/api/skills-service.js +2 -2
  219. package/dist/api/skills-service.js.map +1 -1
  220. package/dist/components/features/backends/api-key-entry-screen.d.ts +10 -0
  221. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  222. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  223. package/dist/components/features/backends/backend-form-modal.d.ts +23 -2
  224. package/dist/components/features/backends/backend-form-modal.js +185 -173
  225. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  226. package/dist/components/features/browser/browser.cjs +1 -1
  227. package/dist/components/features/browser/browser.cjs.map +1 -1
  228. package/dist/components/features/browser/browser.js +10 -16
  229. package/dist/components/features/browser/browser.js.map +1 -1
  230. package/dist/components/features/conversation-panel/skills-modal.cjs +1 -1
  231. package/dist/components/features/conversation-panel/skills-modal.cjs.map +1 -1
  232. package/dist/components/features/conversation-panel/skills-modal.js +1 -1
  233. package/dist/components/features/conversation-panel/skills-modal.js.map +1 -1
  234. package/dist/components/features/mcp-page/install-server-modal.cjs +1 -1
  235. package/dist/components/features/mcp-page/install-server-modal.cjs.map +1 -1
  236. package/dist/components/features/mcp-page/install-server-modal.js +123 -116
  237. package/dist/components/features/mcp-page/install-server-modal.js.map +1 -1
  238. package/dist/components/features/mcp-page/installed-server-card.cjs +1 -1
  239. package/dist/components/features/mcp-page/installed-server-card.cjs.map +1 -1
  240. package/dist/components/features/mcp-page/installed-server-card.js +40 -40
  241. package/dist/components/features/mcp-page/installed-server-card.js.map +1 -1
  242. package/dist/components/features/mcp-page/marketplace-card.cjs +1 -1
  243. package/dist/components/features/mcp-page/marketplace-card.cjs.map +1 -1
  244. package/dist/components/features/mcp-page/marketplace-card.js +2 -3
  245. package/dist/components/features/mcp-page/marketplace-card.js.map +1 -1
  246. package/dist/components/features/mcp-page/marketplace-section.cjs +1 -1
  247. package/dist/components/features/mcp-page/marketplace-section.cjs.map +1 -1
  248. package/dist/components/features/mcp-page/marketplace-section.js +21 -21
  249. package/dist/components/features/mcp-page/marketplace-section.js.map +1 -1
  250. package/dist/components/features/onboarding/steps/setup-acp-secrets-step.d.ts +27 -0
  251. package/dist/components/features/settings/llm-profiles/llm-settings-local-view.cjs +1 -1
  252. package/dist/components/features/settings/llm-profiles/llm-settings-local-view.js +2 -0
  253. package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs +1 -1
  254. package/dist/components/features/settings/sdk-settings/sdk-section-page.cjs.map +1 -1
  255. package/dist/components/features/settings/sdk-settings/sdk-section-page.d.ts +10 -1
  256. package/dist/components/features/settings/sdk-settings/sdk-section-page.js +87 -84
  257. package/dist/components/features/settings/sdk-settings/sdk-section-page.js.map +1 -1
  258. package/dist/constants/acp-providers.cjs +1 -1
  259. package/dist/constants/acp-providers.cjs.map +1 -1
  260. package/dist/constants/acp-providers.d.ts +25 -0
  261. package/dist/constants/acp-providers.js +1 -0
  262. package/dist/constants/acp-providers.js.map +1 -1
  263. package/dist/contexts/conversation-websocket-context.cjs +3 -3
  264. package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
  265. package/dist/contexts/conversation-websocket-context.js +177 -165
  266. package/dist/contexts/conversation-websocket-context.js.map +1 -1
  267. package/dist/hooks/chat/use-model-interceptor.cjs.map +1 -1
  268. package/dist/hooks/chat/use-model-interceptor.js.map +1 -1
  269. package/dist/hooks/chat/use-slash-command.cjs +1 -1
  270. package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
  271. package/dist/hooks/chat/use-slash-command.js +1 -1
  272. package/dist/hooks/chat/use-slash-command.js.map +1 -1
  273. package/dist/hooks/mutation/use-switch-llm-profile.cjs.map +1 -1
  274. package/dist/hooks/mutation/use-switch-llm-profile.d.ts +1 -1
  275. package/dist/hooks/mutation/use-switch-llm-profile.js.map +1 -1
  276. package/dist/hooks/query/use-config.cjs +1 -1
  277. package/dist/hooks/query/use-config.cjs.map +1 -1
  278. package/dist/hooks/query/use-config.js +10 -10
  279. package/dist/hooks/query/use-config.js.map +1 -1
  280. package/dist/hooks/query/use-conversation-skills.cjs +2 -0
  281. package/dist/hooks/query/use-conversation-skills.cjs.map +1 -0
  282. package/dist/hooks/query/use-conversation-skills.d.ts +7 -0
  283. package/dist/hooks/query/use-conversation-skills.js +8 -0
  284. package/dist/hooks/query/use-conversation-skills.js.map +1 -0
  285. package/dist/hooks/query/use-local-git-info.cjs +3 -3
  286. package/dist/hooks/query/use-local-git-info.cjs.map +1 -1
  287. package/dist/hooks/query/use-local-git-info.js +24 -25
  288. package/dist/hooks/query/use-local-git-info.js.map +1 -1
  289. package/dist/hooks/query/use-skills.cjs +1 -1
  290. package/dist/hooks/query/use-skills.cjs.map +1 -1
  291. package/dist/hooks/query/use-skills.d.ts +6 -1
  292. package/dist/hooks/query/use-skills.js +3 -3
  293. package/dist/hooks/query/use-skills.js.map +1 -1
  294. package/dist/i18n/declaration.cjs +1 -1
  295. package/dist/i18n/declaration.cjs.map +1 -1
  296. package/dist/i18n/declaration.d.ts +7 -0
  297. package/dist/i18n/declaration.js +1 -1
  298. package/dist/i18n/declaration.js.map +1 -1
  299. package/dist/i18n/translation.cjs +2 -2
  300. package/dist/i18n/translation.cjs.map +1 -1
  301. package/dist/i18n/translation.js +119 -0
  302. package/dist/i18n/translation.js.map +1 -1
  303. package/dist/locales/ar/openhands.json +7 -0
  304. package/dist/locales/ca/openhands.json +7 -0
  305. package/dist/locales/de/openhands.json +7 -0
  306. package/dist/locales/en/openhands.json +7 -0
  307. package/dist/locales/es/openhands.json +7 -0
  308. package/dist/locales/fr/openhands.json +7 -0
  309. package/dist/locales/it/openhands.json +7 -0
  310. package/dist/locales/ja/openhands.json +7 -0
  311. package/dist/locales/ko-KR/openhands.json +7 -0
  312. package/dist/locales/no/openhands.json +7 -0
  313. package/dist/locales/pt/openhands.json +7 -0
  314. package/dist/locales/tr/openhands.json +7 -0
  315. package/dist/locales/uk/openhands.json +7 -0
  316. package/dist/locales/zh-CN/openhands.json +7 -0
  317. package/dist/locales/zh-TW/openhands.json +7 -0
  318. package/dist/package.cjs +1 -1
  319. package/dist/package.cjs.map +1 -1
  320. package/dist/package.js +3 -3
  321. package/dist/package.js.map +1 -1
  322. package/dist/routes/conversation.cjs +1 -1
  323. package/dist/routes/conversation.cjs.map +1 -1
  324. package/dist/routes/conversation.js +61 -63
  325. package/dist/routes/conversation.js.map +1 -1
  326. package/dist/routes/mcp.cjs +1 -1
  327. package/dist/routes/mcp.cjs.map +1 -1
  328. package/dist/routes/mcp.js +64 -64
  329. package/dist/routes/mcp.js.map +1 -1
  330. package/dist/stores/browser-store.cjs +1 -1
  331. package/dist/stores/browser-store.cjs.map +1 -1
  332. package/dist/stores/browser-store.js +1 -1
  333. package/dist/stores/browser-store.js.map +1 -1
  334. package/dist/stores/use-event-store.cjs +1 -1
  335. package/dist/stores/use-event-store.cjs.map +1 -1
  336. package/dist/stores/use-event-store.d.ts +22 -0
  337. package/dist/stores/use-event-store.js +9 -1
  338. package/dist/stores/use-event-store.js.map +1 -1
  339. package/dist/ui/context-menu.d.ts +1 -1
  340. package/dist/ui/help-link.d.ts +1 -1
  341. package/dist/utils/mcp-marketplace-utils.cjs +1 -1
  342. package/dist/utils/mcp-marketplace-utils.cjs.map +1 -1
  343. package/dist/utils/mcp-marketplace-utils.d.ts +13 -22
  344. package/dist/utils/mcp-marketplace-utils.js +46 -28
  345. package/dist/utils/mcp-marketplace-utils.js.map +1 -1
  346. package/dist/utils/sdk-settings-schema.cjs +1 -1
  347. package/dist/utils/sdk-settings-schema.cjs.map +1 -1
  348. package/dist/utils/sdk-settings-schema.d.ts +1 -0
  349. package/dist/utils/sdk-settings-schema.js +1 -1
  350. package/dist/utils/sdk-settings-schema.js.map +1 -1
  351. package/package.json +3 -3
  352. package/scripts/dev-safe.mjs +94 -57
  353. package/scripts/dev-static.mjs +2 -3
  354. package/scripts/dev-with-automation.mjs +98 -67
  355. package/scripts/static-server.mjs +77 -35
  356. package/tools/canvas_ui_tool.py +4 -0
  357. package/build/assets/acp-providers-DauuOsW9.js +0 -1
  358. package/build/assets/add-backend-modal-KMmPQNZU.js +0 -1
  359. package/build/assets/agent-server-client-options-DT2GP6VJ.js +0 -1
  360. package/build/assets/agent-server-compatibility-2aOx5iWd.js +0 -1
  361. package/build/assets/agent-server-conversation-service.api-DSl9G5UR.js +0 -5
  362. package/build/assets/backend-form-modal-K6IMCr3p.js +0 -1
  363. package/build/assets/browser-DKG63inJ.js +0 -5
  364. package/build/assets/browser-store-C3AqxAO7.js +0 -1
  365. package/build/assets/browser-tab-B_BuTvrO.js +0 -1
  366. package/build/assets/conversation-BD5WemJI.js +0 -19
  367. package/build/assets/conversation-C47K62n8.js +0 -1
  368. package/build/assets/conversation-panel-Dn-S56Gk.js +0 -1
  369. package/build/assets/conversation-websocket-context-Ywrxd_9p.js +0 -3
  370. package/build/assets/declaration-D378OjpZ.js +0 -1
  371. package/build/assets/files-tab-B3A1NDlZ.js +0 -1
  372. package/build/assets/git-control-bar-branch-button-CcIpmyfM.js +0 -27
  373. package/build/assets/home-dIzxi5Dd.js +0 -1
  374. package/build/assets/install-server-modal-z5VaHeXd.js +0 -1
  375. package/build/assets/llm-settings-2036m7Wt.js +0 -1
  376. package/build/assets/llm-settings-CcHqGOYL.js +0 -1
  377. package/build/assets/manifest-97e839da.js +0 -1
  378. package/build/assets/mcp-C06YssEI.js +0 -9
  379. package/build/assets/root-BS1Td78t.js +0 -2
  380. package/build/assets/sdk-section-page-CJW0G04-.js +0 -1
  381. package/build/assets/secrets-settings-MLXqOtX2.js +0 -1
  382. package/build/assets/use-active-conversation-D15D9GgR.js +0 -1
  383. package/build/assets/use-config-BSu_53GL.js +0 -1
  384. package/build/assets/use-conversation-id-DajhCn2A.js +0 -1
  385. package/build/assets/use-is-authed-hXC8vxgT.js +0 -1
  386. package/build/assets/use-runtime-is-ready-XFbT16BD.js +0 -1
  387. package/build/assets/use-skills-Xe0vjPMt.js +0 -1
  388. package/build/assets/use-unified-vscode-url-BOsIOd-b.js +0 -1
  389. package/build/assets/use-user-conversation-Mc0mQgkl.js +0 -1
  390. /package/build/assets/{automation-XLxhq3I8.js → automation-IdgZq6ZK.js} +0 -0
  391. /package/build/assets/{common-SMkEaBSr.js → common-DR1t-EeP.js} +0 -0
  392. /package/build/assets/{conversation-state-store-Bc0slAjL.js → conversation-state-store-u5jepov0.js} +0 -0
  393. /package/build/assets/{dist-yMQV8IUk.js → dist-BxBP7tFD.js} +0 -0
  394. /package/build/assets/{git-status-mapper-BI8FyUVp.js → git-status-mapper-DnL9OC8_.js} +0 -0
  395. /package/build/assets/{handle-capture-consent-BfZATzpI.js → handle-capture-consent-3XrjZ8wi.js} +0 -0
  396. /package/build/assets/{iconBase-C7N9pPOs.js → iconBase-DE30Zj_-.js} +0 -0
  397. /package/build/assets/{settings-D5am1n6X.js → settings-D_H-qsRm.js} +0 -0
  398. /package/build/assets/{settings-like-page-layout-classes-Bn-M9oOa.js → settings-like-page-layout-classes-I0BDBEoq.js} +0 -0
  399. /package/build/assets/{settings-utils-BsvSU3OM.js → settings-utils-B6Nl07io.js} +0 -0
  400. /package/build/assets/{sidebar-store-cOeaKmIm.js → sidebar-store-Uy3v0AOV.js} +0 -0
  401. /package/build/assets/{use-breakpoint-B86yKT9n.js → use-breakpoint-DbJ6FkQ-.js} +0 -0
  402. /package/build/assets/{use-click-outside-element-835W9pC6.js → use-click-outside-element-DffgWWoZ.js} +0 -0
  403. /package/build/assets/{vendor~browser-BpdPBhgZ.js → vendor~browser-DDiZgqD3.js} +0 -0
  404. /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-Df7_G0zR.js → vendor~conversation-panel~conversation~alert-banner-DbvX3OcM.js} +0 -0
  405. /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
  406. /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
  407. /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
  408. /package/build/assets/{vendor~terminal-BUxzHKcC.js → vendor~terminal-DUrOWGFE.js} +0 -0
  409. /package/build/assets/{vscode-url-helper-jesbpos5.js → vscode-url-helper-Cwy1A62q.js} +0 -0
@@ -3,154 +3,157 @@ import { I18nKey as t } from "../../../../i18n/declaration.js";
3
3
  import { displayErrorToast as n, displaySuccessToast as ee } from "../../../../utils/custom-toast-handlers.js";
4
4
  import { AxiosError as r } from "../../../../node_modules/axios/index.js";
5
5
  import { useSettings as te } from "../../../../hooks/query/use-settings.js";
6
- import { Typography as ne } from "../../../../ui/typography.js";
7
- import { BrandButton as i } from "../brand-button.js";
6
+ import { Typography as i } from "../../../../ui/typography.js";
7
+ import { BrandButton as ne } from "../brand-button.js";
8
8
  import { useSaveSettings as re } from "../../../../hooks/mutation/use-save-settings.js";
9
9
  import { retrieveAxiosErrorMessage as ie } from "../../../../utils/retrieve-axios-error-message.js";
10
- import { useAgentSettingsSchema as ae, useConversationSettingsSchema as a } from "../../../../hooks/query/use-agent-settings-schema.js";
11
- import { LlmSettingsInputsSkeleton as o } from "../llm-settings/llm-settings-inputs-skeleton.js";
12
- import { buildInitialSettingsFormValues as s, buildSdkSettingsPayloadForView as c, getVisibleSettingsSections as l, hasAdvancedSettings as oe, hasMinorSettings as se, inferInitialView as ce, isValidSettingsSchema as le } from "../../../../utils/sdk-settings-schema.js";
13
- import { SchemaField as ue } from "./schema-field.js";
10
+ import { useAgentSettingsSchema as a, useConversationSettingsSchema as o } from "../../../../hooks/query/use-agent-settings-schema.js";
11
+ import { LlmSettingsInputsSkeleton as s } from "../llm-settings/llm-settings-inputs-skeleton.js";
12
+ import { buildInitialSettingsFormValues as c, buildSdkSettingsPayload as l, buildSdkSettingsPayloadForView as ae, getVisibleSettingsSections as oe, hasAdvancedSettings as se, hasMinorSettings as ce, inferInitialView as le, isValidSettingsSchema as ue } from "../../../../utils/sdk-settings-schema.js";
13
+ import { SchemaField as u } from "./schema-field.js";
14
14
  import { ViewToggle as de } from "./view-toggle.js";
15
- import u from "react";
16
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
15
+ import d from "react";
16
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
17
17
  //#region src/components/features/settings/sdk-settings/sdk-section-page.tsx
18
- var fe = /* @__PURE__ */ new Set(), p = {
18
+ var fe = /* @__PURE__ */ new Set(), m = {
19
19
  basic: 0,
20
20
  advanced: 1,
21
21
  all: 2
22
- }, pe = (e, t) => p[t] < p[e] ? t : e, me = (e, { showAdvanced: t, showAll: n }) => e === "all" ? n ? "all" : t ? "advanced" : "basic" : e === "advanced" ? t ? "advanced" : n ? "all" : "basic" : "basic", he = (e, t) => e instanceof r ? e.response?.status === 401 ? `${t} This agent server requires X-Session-API-Key. Set VITE_SESSION_API_KEY in the frontend to the same value used by the backend SESSION_API_KEY or OH_SESSION_API_KEYS_0.` : e.response?.status === 404 ? `${t} This backend does not expose /api/settings/* schema endpoints. Upgrade to a recent openhands-agent-server release.` : t : t;
23
- function m({ sectionKeys: r, excludeKeys: p = fe, scope: m = "personal", settingsSource: h = "agent_settings", header: g, extraDirty: _ = !1, buildPayload: v, onSaveSuccess: y, getInitialView: b, forceShowAdvancedView: ge = !1, allowAllView: _e = !0, initialValueOverrides: x, embedded: ve = !1, hideSaveButton: S = !1, onSaveControlChange: C, testId: w = "sdk-section-settings-screen" }) {
24
- let { t: T } = e("openhands"), { mutate: E, isPending: D } = re(m), { data: O, isLoading: k, isFetching: A } = te(m), j = ae(O?.agent_settings_schema), M = a(O?.conversation_settings_schema), N = h === "conversation_settings" ? M : j, P = N.data, F = N.isLoading, [I, L] = u.useState("basic"), [R, z] = u.useState({}), [B, V] = u.useState({}), H = u.useRef(!1), U = u.useMemo(() => JSON.stringify(r), [r]), W = u.useMemo(() => JSON.parse(U), [U]), G = u.useMemo(() => {
25
- if (!le(P)) return null;
26
- let e = new Set(W);
22
+ }, h = (e, t) => m[t] < m[e] ? t : e, pe = (e, { showAdvanced: t, showAll: n }) => e === "all" ? n ? "all" : t ? "advanced" : "basic" : e === "advanced" ? t ? "advanced" : n ? "all" : "basic" : "basic", me = (e, t) => e instanceof r ? e.response?.status === 401 ? `${t} This agent server requires X-Session-API-Key. Set VITE_SESSION_API_KEY in the frontend to the same value used by the backend SESSION_API_KEY or OH_SESSION_API_KEYS_0.` : e.response?.status === 404 ? `${t} This backend does not expose /api/settings/* schema endpoints. Upgrade to a recent openhands-agent-server release.` : t : t;
23
+ function g({ sectionKeys: r, excludeKeys: m = fe, scope: g = "personal", settingsSource: _ = "agent_settings", header: he, extraDirty: v = !1, buildPayload: y, onSaveSuccess: ge, getInitialView: b, forceShowAdvancedView: _e = !1, allowAllView: ve = !0, initialValueOverrides: x, embedded: ye = !1, hideSaveButton: S = !1, onSaveControlChange: C, testId: w = "sdk-section-settings-screen" }) {
24
+ let { t: T } = e("openhands"), { mutate: E, isPending: D } = re(g), { data: O, isLoading: k, isFetching: A } = te(g), j = a(O?.agent_settings_schema), M = o(O?.conversation_settings_schema), N = _ === "conversation_settings" ? M : j, P = N.data, be = N.isLoading, [F, I] = d.useState("basic"), [L, R] = d.useState({}), [z, B] = d.useState({}), V = d.useRef(!1), H = d.useMemo(() => JSON.stringify(r), [r]), U = d.useMemo(() => JSON.parse(H), [H]), W = d.useMemo(() => {
25
+ if (!ue(P)) return null;
26
+ let e = new Set(U);
27
27
  return {
28
28
  ...P,
29
29
  sections: P.sections.filter((t) => e.has(t.key))
30
30
  };
31
- }, [P, W]), K = ge || oe(G), q = _e && se(G), ye = u.useMemo(() => he(N.error, T(t.SETTINGS$SDK_SCHEMA_UNAVAILABLE)), [N.error, T]), be = u.useMemo(() => x ? JSON.stringify(x) : "", [x]), J = u.useMemo(() => {
32
- if (!O || !G) return null;
33
- let e = s(O, G, h);
31
+ }, [P, U]), G = _e || se(W), K = ve && ce(W), xe = d.useMemo(() => me(N.error, T(t.SETTINGS$SDK_SCHEMA_UNAVAILABLE)), [N.error, T]), Se = d.useMemo(() => x ? JSON.stringify(x) : "", [x]), q = d.useMemo(() => {
32
+ if (!O || !W) return null;
33
+ let e = c(O, W, _);
34
34
  return x ? {
35
35
  ...e,
36
36
  ...x
37
37
  } : e;
38
38
  }, [
39
39
  O,
40
- G,
41
- h,
42
- be
43
- ]), Y = u.useMemo(() => !O || !G ? null : me(b ? b(O, G) : ce(O, G, h), {
44
- showAdvanced: K,
45
- showAll: q
40
+ W,
41
+ _,
42
+ Se
43
+ ]), J = d.useMemo(() => !O || !W ? null : pe(b ? b(O, W) : le(O, W, _), {
44
+ showAdvanced: G,
45
+ showAll: K
46
46
  }), [
47
47
  O,
48
- G,
48
+ W,
49
49
  b,
50
- h,
51
- K,
52
- q
50
+ _,
51
+ G,
52
+ K
53
53
  ]);
54
- u.useEffect(() => {
55
- H.current = !1, L("basic"), z({}), V({});
54
+ d.useEffect(() => {
55
+ V.current = !1, I("basic"), R({}), B({});
56
56
  }, [
57
- m,
58
- h,
59
- U
60
- ]), u.useEffect(() => {
61
- !J || !Y || (z(J), V(x ? Object.fromEntries(Object.keys(x).map((e) => [e, !0])) : {}), L((e) => H.current ? pe(e, Y) : (H.current = !0, Y)));
62
- }, [J, Y]);
63
- let xe = u.useMemo(() => G ? l(G, R, I, p) : [], [
64
- G,
65
- R,
66
- I,
67
- p
68
- ]), X = u.useCallback((e, t) => {
69
- z((n) => ({
57
+ g,
58
+ _,
59
+ H
60
+ ]), d.useEffect(() => {
61
+ !q || !J || (R(q), B(x ? Object.fromEntries(Object.keys(x).map((e) => [e, !0])) : {}), I((e) => V.current ? h(e, J) : (V.current = !0, J)));
62
+ }, [q, J]);
63
+ let Ce = d.useMemo(() => W ? oe(W, L, F, m) : [], [
64
+ W,
65
+ L,
66
+ F,
67
+ m
68
+ ]), Y = d.useCallback((e, t) => {
69
+ R((n) => ({
70
70
  ...n,
71
71
  [e]: t
72
- })), V((t) => ({
72
+ })), B((t) => ({
73
73
  ...t,
74
74
  [e]: !0
75
75
  }));
76
- }, []), Se = u.useCallback((e) => {
76
+ }, []), we = d.useCallback((e) => {
77
77
  n(ie(e) || T(t.ERROR$GENERIC));
78
- }, [T]), Z = u.useRef(() => {}), Ce = u.useCallback(() => {
79
- Z.current();
80
- }, []), Q = () => {
81
- if (!G) return;
78
+ }, [T]), X = d.useRef(() => {}), Te = d.useCallback(() => {
79
+ X.current();
80
+ }, []), Z = d.useRef(() => ({})), Ee = d.useCallback(() => Z.current(), []), Q = () => {
81
+ if (!W) return;
82
82
  let e;
83
83
  try {
84
- let t = c(G, R, B, I), n;
85
- n = h === "conversation_settings" ? { conversation_settings_diff: t } : { agent_settings_diff: t }, e = v ? v(t, {
86
- values: R,
87
- dirty: B,
88
- view: I
84
+ let t = ae(W, L, z, F), n;
85
+ n = _ === "conversation_settings" ? { conversation_settings_diff: t } : { agent_settings_diff: t }, e = y ? y(t, {
86
+ values: L,
87
+ dirty: z,
88
+ view: F
89
89
  }) : n;
90
90
  } catch (e) {
91
91
  n(e instanceof Error ? e.message : T(t.ERROR$GENERIC));
92
92
  return;
93
93
  }
94
94
  Object.keys(e).length !== 0 && E(e, {
95
- onError: Se,
95
+ onError: we,
96
96
  onSuccess: () => {
97
- ee(T(t.SETTINGS$SAVED_WARNING)), V({}), y?.();
97
+ ee(T(t.SETTINGS$SAVED_WARNING)), B({}), ge?.();
98
98
  }
99
99
  });
100
100
  };
101
- Z.current = Q;
102
- let $ = Object.keys(B).length > 0 || _;
103
- return u.useEffect(() => {
101
+ X.current = Q, Z.current = () => W ? l(W, L, z) : {};
102
+ let $ = Object.keys(z).length > 0 || v;
103
+ return d.useEffect(() => {
104
104
  C && C({
105
- save: Ce,
105
+ save: Te,
106
106
  isSaving: D,
107
107
  isDirty: $,
108
- values: R
108
+ values: L,
109
+ view: F,
110
+ getDirtyPayload: Ee
109
111
  });
110
112
  }, [
111
113
  D,
112
114
  $,
113
- R
114
- ]), k || A || F ? /* @__PURE__ */ d(o, {}) : !G || G.sections.length === 0 ? /* @__PURE__ */ d(ne.Paragraph, {
115
+ L,
116
+ F
117
+ ]), k || A || be ? /* @__PURE__ */ f(s, {}) : !W || W.sections.length === 0 ? /* @__PURE__ */ f(i.Paragraph, {
115
118
  className: "text-tertiary-alt",
116
- children: ye
117
- }) : Object.keys(R).length === 0 ? /* @__PURE__ */ d(o, {}) : /* @__PURE__ */ f("div", {
119
+ children: xe
120
+ }) : Object.keys(L).length === 0 ? /* @__PURE__ */ f(s, {}) : /* @__PURE__ */ p("div", {
118
121
  "data-testid": w,
119
- className: ve ? "relative flex min-h-0 w-full flex-1 flex-col" : "relative w-full min-h-0",
120
- children: [/* @__PURE__ */ d(de, {
121
- view: I,
122
- setView: L,
123
- showAdvanced: K,
124
- showAll: q,
122
+ className: ye ? "relative flex min-h-0 w-full flex-1 flex-col" : "relative w-full min-h-0",
123
+ children: [/* @__PURE__ */ f(de, {
124
+ view: F,
125
+ setView: I,
126
+ showAdvanced: G,
127
+ showAll: K,
125
128
  isDisabled: !1
126
- }), /* @__PURE__ */ f("div", {
129
+ }), /* @__PURE__ */ p("div", {
127
130
  className: "flex flex-col gap-8",
128
131
  children: [
129
- g?.({
130
- values: R,
132
+ he?.({
133
+ values: L,
131
134
  isDisabled: !1,
132
- view: I,
133
- onChange: X
135
+ view: F,
136
+ onChange: Y
134
137
  }),
135
- xe.map((e, t) => /* @__PURE__ */ d("section", {
138
+ Ce.map((e, t) => /* @__PURE__ */ f("section", {
136
139
  className: "flex flex-col gap-4",
137
- children: /* @__PURE__ */ d("div", {
140
+ children: /* @__PURE__ */ f("div", {
138
141
  className: "flex flex-col gap-4",
139
- children: e.fields.map((e) => /* @__PURE__ */ d(ue, {
142
+ children: e.fields.map((e) => /* @__PURE__ */ f(u, {
140
143
  field: e,
141
- value: R[e.key],
144
+ value: L[e.key],
142
145
  isDisabled: !1,
143
- onChange: (t) => X(e.key, t)
146
+ onChange: (t) => Y(e.key, t)
144
147
  }, e.key))
145
148
  })
146
149
  }, `${e.key}-${t}`)),
147
- S ? null : /* @__PURE__ */ d("div", {
150
+ S ? null : /* @__PURE__ */ f("div", {
148
151
  className: "flex justify-start pt-2",
149
- children: /* @__PURE__ */ d(i, {
152
+ children: /* @__PURE__ */ f(ne, {
150
153
  testId: "save-button",
151
154
  type: "button",
152
155
  variant: "primary",
153
- isDisabled: D || Object.keys(B).length === 0 && !_,
156
+ isDisabled: D || Object.keys(z).length === 0 && !v,
154
157
  onClick: Q,
155
158
  children: T(D ? t.SETTINGS$SAVING : t.SETTINGS$SAVE_CHANGES)
156
159
  })
@@ -160,6 +163,6 @@ function m({ sectionKeys: r, excludeKeys: p = fe, scope: m = "personal", setting
160
163
  });
161
164
  }
162
165
  //#endregion
163
- export { m as SdkSectionPage };
166
+ export { g as SdkSectionPage };
164
167
 
165
168
  //# sourceMappingURL=sdk-section-page.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-section-page.js","names":[],"sources":["../../../../../src/components/features/settings/sdk-settings/sdk-section-page.tsx"],"sourcesContent":["import React from \"react\";\nimport { AxiosError } from \"axios\";\nimport { useTranslation } from \"react-i18next\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { LlmSettingsInputsSkeleton } from \"#/components/features/settings/llm-settings/llm-settings-inputs-skeleton\";\nimport { useSaveSettings } from \"#/hooks/mutation/use-save-settings\";\nimport {\n useAgentSettingsSchema,\n useConversationSettingsSchema,\n} from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Typography } from \"#/ui/typography\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\nimport { retrieveAxiosErrorMessage } from \"#/utils/retrieve-axios-error-message\";\nimport {\n buildInitialSettingsFormValues,\n buildSdkSettingsPayloadForView,\n getVisibleSettingsSections,\n hasAdvancedSettings,\n hasMinorSettings,\n inferInitialView,\n isValidSettingsSchema,\n SettingsDirtyState,\n SettingsFormValues,\n type SettingsValueSource,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { SchemaField } from \"./schema-field\";\nimport { ViewToggle } from \"./view-toggle\";\n\nconst EMPTY_EXCLUDE_KEYS = new Set<string>();\n\nconst VIEW_ORDER: Record<SettingsView, number> = {\n basic: 0,\n advanced: 1,\n all: 2,\n};\n\nconst getLessDetailedView = (\n currentView: SettingsView,\n nextView: SettingsView,\n): SettingsView =>\n VIEW_ORDER[nextView] < VIEW_ORDER[currentView] ? nextView : currentView;\n\nconst normalizeView = (\n view: SettingsView,\n {\n showAdvanced,\n showAll,\n }: {\n showAdvanced: boolean;\n showAll: boolean;\n },\n): SettingsView => {\n if (view === \"all\") {\n if (showAll) {\n return \"all\";\n }\n\n return showAdvanced ? \"advanced\" : \"basic\";\n }\n\n if (view === \"advanced\") {\n if (showAdvanced) {\n return \"advanced\";\n }\n\n return showAll ? \"all\" : \"basic\";\n }\n\n return \"basic\";\n};\n\nconst getSchemaUnavailableMessage = (\n error: unknown,\n fallbackMessage: string,\n): string => {\n if (!(error instanceof AxiosError)) {\n return fallbackMessage;\n }\n\n if (error.response?.status === 401) {\n return `${fallbackMessage} This agent server requires X-Session-API-Key. Set VITE_SESSION_API_KEY in the frontend to the same value used by the backend SESSION_API_KEY or OH_SESSION_API_KEYS_0.`;\n }\n\n if (error.response?.status === 404) {\n return `${fallbackMessage} This backend does not expose /api/settings/* schema endpoints. Upgrade to a recent openhands-agent-server release.`;\n }\n\n return fallbackMessage;\n};\n\nexport interface SdkSectionHeaderProps {\n values: SettingsFormValues;\n isDisabled: boolean;\n view: SettingsView;\n onChange: (key: string, value: string | boolean) => void;\n}\n\n/**\n * Snapshot of the page's save state, surfaced to the parent so it can\n * render its own Save/Next button (e.g. in onboarding) when\n * {@link SdkSectionPage}'s built-in button is hidden via\n * `hideSaveButton`.\n */\nexport interface SdkSectionSaveControl {\n /** Trigger a save of the currently-dirty fields. No-op while `isSaving` or `!isDirty`. */\n save: () => void;\n /** A save mutation is in flight. */\n isSaving: boolean;\n /** At least one field is dirty (or `extraDirty` was passed in). */\n isDirty: boolean;\n /** Current form values (for custom save flows). */\n values: SettingsFormValues;\n}\n\n/**\n * A generic SDK-schema–driven settings page that renders fields\n * from one or more schema sections.\n *\n * @param sectionKeys - which schema section(s) this page owns (e.g. [\"condenser\"])\n * @param excludeKeys - field keys to skip (rendered elsewhere by the caller)\n * @param header - optional render prop receiving shared state to render above fields\n * @param testId - data-testid for the page wrapper\n */\nexport function SdkSectionPage({\n sectionKeys,\n excludeKeys = EMPTY_EXCLUDE_KEYS,\n scope = \"personal\",\n settingsSource = \"agent_settings\",\n header,\n extraDirty = false,\n buildPayload,\n onSaveSuccess,\n getInitialView,\n forceShowAdvancedView = false,\n allowAllView = true,\n initialValueOverrides,\n embedded = false,\n hideSaveButton = false,\n onSaveControlChange,\n testId = \"sdk-section-settings-screen\",\n}: {\n sectionKeys: string[];\n excludeKeys?: Set<string>;\n scope?: SettingsScope;\n settingsSource?: SettingsValueSource;\n\n header?: (props: SdkSectionHeaderProps) => React.ReactNode;\n extraDirty?: boolean;\n buildPayload?: (\n payload: ReturnType<typeof buildSdkSettingsPayloadForView>,\n context: {\n values: SettingsFormValues;\n dirty: SettingsDirtyState;\n view: SettingsView;\n },\n ) => Record<string, unknown>;\n onSaveSuccess?: () => void;\n getInitialView?: (\n settings: Settings,\n filteredSchema: SettingsSchema,\n ) => SettingsView;\n forceShowAdvancedView?: boolean;\n allowAllView?: boolean;\n /**\n * Per-field initial value overrides that win over the values\n * derived from `useSettings`. The keys of each override are also\n * marked dirty on hydration so the user can save the form without\n * having to touch the prefilled fields. Useful when the page is\n * embedded in a flow that wants to nudge brand-new users toward a\n * particular default (e.g. onboarding pre-filling Anthropic/Opus).\n */\n initialValueOverrides?: SettingsFormValues;\n /**\n * When true, the Save button container is rendered inline (no\n * sticky positioning, no contrasting `bg-base` band) so the page\n * can be dropped into a modal/card without a hard footer break.\n */\n embedded?: boolean;\n /**\n * Suppress the built-in Save Changes button entirely. Pair with\n * {@link onSaveControlChange} to drive saving from a parent-rendered\n * action (e.g. an onboarding \"Next\" button).\n */\n hideSaveButton?: boolean;\n /**\n * Fires whenever the save state changes (a mutation starts/finishes,\n * dirty status flips). Provides a stable `save()` callback the\n * parent can wire to its own button. Useful when the form is\n * embedded in a custom flow and the built-in Save button is hidden.\n */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n testId?: string;\n}) {\n const { t } = useTranslation(\"openhands\");\n const { mutate: saveSettings, isPending } = useSaveSettings(scope);\n const { data: settings, isLoading, isFetching } = useSettings(scope);\n const agentSchemaQuery = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const conversationSchemaQuery = useConversationSettingsSchema(\n settings?.conversation_settings_schema,\n );\n const activeSchemaQuery =\n settingsSource === \"conversation_settings\"\n ? conversationSchemaQuery\n : agentSchemaQuery;\n const schema = activeSchemaQuery.data;\n const isSchemaLoading = activeSchemaQuery.isLoading;\n const isReadOnly = false;\n\n const [view, setView] = React.useState<SettingsView>(\"basic\");\n const [values, setValues] = React.useState<SettingsFormValues>({});\n const [dirty, setDirty] = React.useState<SettingsDirtyState>({});\n const hasHydratedViewRef = React.useRef(false);\n\n const sectionKeysSignature = React.useMemo(\n () => JSON.stringify(sectionKeys),\n [sectionKeys],\n );\n const stableSectionKeys = React.useMemo(\n () => JSON.parse(sectionKeysSignature) as string[],\n [sectionKeysSignature],\n );\n\n // Build a filtered schema containing only the requested sections.\n // `isValidSettingsSchema` guards against truthy-but-malformed schema\n // responses (e.g. when the deployment is pointed at a host that does\n // not serve `/api/settings/agent-schema` and returns an SPA shell\n // that parses into an object without a `sections` array). Without\n // the guard, `schema.sections.filter(...)` would throw and React\n // Router would escalate the crash to a full-screen error.\n const filteredSchema = React.useMemo(() => {\n if (!isValidSettingsSchema(schema)) return null;\n const sectionSet = new Set(stableSectionKeys);\n return {\n ...schema,\n sections: schema.sections.filter((s) => sectionSet.has(s.key)),\n };\n }, [schema, stableSectionKeys]);\n\n const showAdvanced =\n forceShowAdvancedView || hasAdvancedSettings(filteredSchema);\n const showAll = allowAllView && hasMinorSettings(filteredSchema);\n const schemaUnavailableMessage = React.useMemo(\n () =>\n getSchemaUnavailableMessage(\n activeSchemaQuery.error,\n t(I18nKey.SETTINGS$SDK_SCHEMA_UNAVAILABLE),\n ),\n [activeSchemaQuery.error, t],\n );\n\n const overridesSignature = React.useMemo(\n () => (initialValueOverrides ? JSON.stringify(initialValueOverrides) : \"\"),\n [initialValueOverrides],\n );\n\n const initialValues = React.useMemo(() => {\n if (!settings || !filteredSchema) return null;\n const base = buildInitialSettingsFormValues(\n settings,\n filteredSchema,\n settingsSource,\n );\n if (!initialValueOverrides) return base;\n return { ...base, ...initialValueOverrides };\n // overridesSignature keeps the memo reactive without depending on\n // a (potentially recreated) object reference each render.\n }, [settings, filteredSchema, settingsSource, overridesSignature]);\n\n const initialView = React.useMemo(() => {\n if (!settings || !filteredSchema) return null;\n\n const resolvedInitialView = getInitialView\n ? getInitialView(settings, filteredSchema)\n : inferInitialView(settings, filteredSchema, settingsSource);\n\n return normalizeView(resolvedInitialView, { showAdvanced, showAll });\n }, [\n settings,\n filteredSchema,\n getInitialView,\n settingsSource,\n showAdvanced,\n showAll,\n ]);\n\n React.useEffect(() => {\n hasHydratedViewRef.current = false;\n setView(\"basic\");\n setValues({});\n setDirty({});\n }, [scope, settingsSource, sectionKeysSignature]);\n\n React.useEffect(() => {\n if (!initialValues || !initialView) return;\n\n setValues(initialValues);\n // Override-supplied keys are pre-populated for the user, so mark\n // them dirty up-front; otherwise the Save button stays disabled\n // until the user touches a field, defeating the point of the\n // override.\n const overrideDirty: SettingsDirtyState = initialValueOverrides\n ? Object.fromEntries(\n Object.keys(initialValueOverrides).map((key) => [key, true]),\n )\n : {};\n setDirty(overrideDirty);\n setView((currentView) => {\n if (!hasHydratedViewRef.current) {\n hasHydratedViewRef.current = true;\n return initialView;\n }\n\n return getLessDetailedView(currentView, initialView);\n });\n // initialValueOverrides is intentionally tracked via\n // overridesSignature on initialValues; including the object ref\n // here would re-fire the effect every render.\n }, [initialValues, initialView]);\n\n const visibleSections = React.useMemo(() => {\n if (!filteredSchema) return [];\n return getVisibleSettingsSections(\n filteredSchema,\n values,\n view,\n excludeKeys,\n );\n }, [filteredSchema, values, view, excludeKeys]);\n\n const handleFieldChange = React.useCallback(\n (fieldKey: string, nextValue: string | boolean) => {\n setValues((prev) => ({ ...prev, [fieldKey]: nextValue }));\n setDirty((prev) => ({ ...prev, [fieldKey]: true }));\n },\n [],\n );\n\n const handleError = React.useCallback(\n (error: AxiosError) => {\n const msg = retrieveAxiosErrorMessage(error);\n displayErrorToast(msg || t(I18nKey.ERROR$GENERIC));\n },\n [t],\n );\n\n // Stable save callback so `onSaveControlChange` can hand a single\n // function reference to the parent across renders. The latest\n // closure is kept up to date via `handleSaveRef`.\n const handleSaveRef = React.useRef<() => void>(() => {});\n const stableSave = React.useCallback(() => {\n handleSaveRef.current();\n }, []);\n\n const handleSave = () => {\n if (!filteredSchema || isReadOnly) return;\n\n let payload: Record<string, unknown>;\n try {\n const basePayload = buildSdkSettingsPayloadForView(\n filteredSchema,\n values,\n dirty,\n view,\n );\n let defaultPayload: Record<string, unknown>;\n if (settingsSource === \"conversation_settings\") {\n defaultPayload = { conversation_settings_diff: basePayload };\n } else {\n defaultPayload = { agent_settings_diff: basePayload };\n }\n payload = buildPayload\n ? buildPayload(basePayload, { values, dirty, view })\n : defaultPayload;\n } catch (error) {\n displayErrorToast(\n error instanceof Error ? error.message : t(I18nKey.ERROR$GENERIC),\n );\n return;\n }\n\n if (Object.keys(payload).length === 0) return;\n\n saveSettings(payload, {\n onError: handleError,\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.SETTINGS$SAVED_WARNING));\n setDirty({});\n onSaveSuccess?.();\n },\n });\n };\n\n handleSaveRef.current = handleSave;\n\n // Surface save state to the parent. Hooks must run before any\n // conditional early-returns below, so this lives here rather than\n // alongside the JSX. The dependency list deliberately excludes\n // `stableSave` (it never changes) and `onSaveControlChange` (we\n // tolerate ref-instability of the callback to avoid spamming the\n // parent on every render).\n const saveControlIsDirty = Object.keys(dirty).length > 0 || extraDirty;\n React.useEffect(() => {\n if (!onSaveControlChange) return;\n onSaveControlChange({\n save: stableSave,\n isSaving: isPending,\n isDirty: saveControlIsDirty,\n values,\n });\n }, [isPending, saveControlIsDirty, values]);\n\n if (isLoading || isFetching || isSchemaLoading) {\n return <LlmSettingsInputsSkeleton />;\n }\n\n if (!filteredSchema || filteredSchema.sections.length === 0) {\n return (\n <Typography.Paragraph className=\"text-tertiary-alt\">\n {schemaUnavailableMessage}\n </Typography.Paragraph>\n );\n }\n\n if (Object.keys(values).length === 0) return <LlmSettingsInputsSkeleton />;\n\n // Scrolling is owned by the settings shell (or onboarding wrapper), not a\n // nested scroll region. Save actions are inline after the last field.\n const bodyClassName = \"flex flex-col gap-8\";\n\n return (\n <div\n data-testid={testId}\n className={\n embedded\n ? \"relative flex min-h-0 w-full flex-1 flex-col\"\n : \"relative w-full min-h-0\"\n }\n >\n <ViewToggle\n view={view}\n setView={setView}\n showAdvanced={showAdvanced}\n showAll={showAll}\n isDisabled={isReadOnly}\n />\n\n <div className={bodyClassName}>\n {header?.({\n values,\n isDisabled: isReadOnly,\n view,\n onChange: handleFieldChange,\n })}\n\n {visibleSections.map((section, sectionIndex) => (\n <section\n key={`${section.key}-${sectionIndex}`}\n className=\"flex flex-col gap-4\"\n >\n <div className=\"flex flex-col gap-4\">\n {section.fields.map((field) => (\n <SchemaField\n key={field.key}\n field={field}\n value={values[field.key]}\n isDisabled={isReadOnly}\n onChange={(nextValue) =>\n handleFieldChange(field.key, nextValue)\n }\n />\n ))}\n </div>\n </section>\n ))}\n\n {!isReadOnly && !hideSaveButton ? (\n <div className=\"flex justify-start pt-2\">\n <BrandButton\n testId=\"save-button\"\n type=\"button\"\n variant=\"primary\"\n isDisabled={\n isPending || (Object.keys(dirty).length === 0 && !extraDirty)\n }\n onClick={handleSave}\n >\n {isPending\n ? t(I18nKey.SETTINGS$SAVING)\n : t(I18nKey.SETTINGS$SAVE_CHANGES)}\n </BrandButton>\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sdk-section-page.js","names":[],"sources":["../../../../../src/components/features/settings/sdk-settings/sdk-section-page.tsx"],"sourcesContent":["import React from \"react\";\nimport { AxiosError } from \"axios\";\nimport { useTranslation } from \"react-i18next\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { LlmSettingsInputsSkeleton } from \"#/components/features/settings/llm-settings/llm-settings-inputs-skeleton\";\nimport { useSaveSettings } from \"#/hooks/mutation/use-save-settings\";\nimport {\n useAgentSettingsSchema,\n useConversationSettingsSchema,\n} from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Typography } from \"#/ui/typography\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\nimport { retrieveAxiosErrorMessage } from \"#/utils/retrieve-axios-error-message\";\nimport {\n buildInitialSettingsFormValues,\n buildSdkSettingsPayload,\n buildSdkSettingsPayloadForView,\n getVisibleSettingsSections,\n hasAdvancedSettings,\n hasMinorSettings,\n inferInitialView,\n isValidSettingsSchema,\n SettingsDirtyState,\n SettingsFormValues,\n type SettingsValueSource,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { SchemaField } from \"./schema-field\";\nimport { ViewToggle } from \"./view-toggle\";\n\nconst EMPTY_EXCLUDE_KEYS = new Set<string>();\n\nconst VIEW_ORDER: Record<SettingsView, number> = {\n basic: 0,\n advanced: 1,\n all: 2,\n};\n\nconst getLessDetailedView = (\n currentView: SettingsView,\n nextView: SettingsView,\n): SettingsView =>\n VIEW_ORDER[nextView] < VIEW_ORDER[currentView] ? nextView : currentView;\n\nconst normalizeView = (\n view: SettingsView,\n {\n showAdvanced,\n showAll,\n }: {\n showAdvanced: boolean;\n showAll: boolean;\n },\n): SettingsView => {\n if (view === \"all\") {\n if (showAll) {\n return \"all\";\n }\n\n return showAdvanced ? \"advanced\" : \"basic\";\n }\n\n if (view === \"advanced\") {\n if (showAdvanced) {\n return \"advanced\";\n }\n\n return showAll ? \"all\" : \"basic\";\n }\n\n return \"basic\";\n};\n\nconst getSchemaUnavailableMessage = (\n error: unknown,\n fallbackMessage: string,\n): string => {\n if (!(error instanceof AxiosError)) {\n return fallbackMessage;\n }\n\n if (error.response?.status === 401) {\n return `${fallbackMessage} This agent server requires X-Session-API-Key. Set VITE_SESSION_API_KEY in the frontend to the same value used by the backend SESSION_API_KEY or OH_SESSION_API_KEYS_0.`;\n }\n\n if (error.response?.status === 404) {\n return `${fallbackMessage} This backend does not expose /api/settings/* schema endpoints. Upgrade to a recent openhands-agent-server release.`;\n }\n\n return fallbackMessage;\n};\n\nexport interface SdkSectionHeaderProps {\n values: SettingsFormValues;\n isDisabled: boolean;\n view: SettingsView;\n onChange: (key: string, value: string | boolean) => void;\n}\n\n/**\n * Snapshot of the page's save state, surfaced to the parent so it can\n * render its own Save/Next button (e.g. in onboarding) when\n * {@link SdkSectionPage}'s built-in button is hidden via\n * `hideSaveButton`.\n */\nexport interface SdkSectionSaveControl {\n /** Trigger a save of the currently-dirty fields. No-op while `isSaving` or `!isDirty`. */\n save: () => void;\n /** A save mutation is in flight. */\n isSaving: boolean;\n /** At least one field is dirty (or `extraDirty` was passed in). */\n isDirty: boolean;\n /** Current form values (for custom save flows). */\n values: SettingsFormValues;\n /** The active view tier (basic/advanced/all) the form is rendering. */\n view: SettingsView;\n /**\n * Returns the coerced, dirty-only payload as a nested object\n * (e.g. `{ llm: { temperature: 0.7 } }`). Lets a custom save flow persist\n * exactly the fields the user changed, with proper types, without\n * re-implementing schema-driven coercion. Throws if a field fails coercion.\n */\n getDirtyPayload: () => Record<string, unknown>;\n}\n\n/**\n * A generic SDK-schema–driven settings page that renders fields\n * from one or more schema sections.\n *\n * @param sectionKeys - which schema section(s) this page owns (e.g. [\"condenser\"])\n * @param excludeKeys - field keys to skip (rendered elsewhere by the caller)\n * @param header - optional render prop receiving shared state to render above fields\n * @param testId - data-testid for the page wrapper\n */\nexport function SdkSectionPage({\n sectionKeys,\n excludeKeys = EMPTY_EXCLUDE_KEYS,\n scope = \"personal\",\n settingsSource = \"agent_settings\",\n header,\n extraDirty = false,\n buildPayload,\n onSaveSuccess,\n getInitialView,\n forceShowAdvancedView = false,\n allowAllView = true,\n initialValueOverrides,\n embedded = false,\n hideSaveButton = false,\n onSaveControlChange,\n testId = \"sdk-section-settings-screen\",\n}: {\n sectionKeys: string[];\n excludeKeys?: Set<string>;\n scope?: SettingsScope;\n settingsSource?: SettingsValueSource;\n\n header?: (props: SdkSectionHeaderProps) => React.ReactNode;\n extraDirty?: boolean;\n buildPayload?: (\n payload: ReturnType<typeof buildSdkSettingsPayloadForView>,\n context: {\n values: SettingsFormValues;\n dirty: SettingsDirtyState;\n view: SettingsView;\n },\n ) => Record<string, unknown>;\n onSaveSuccess?: () => void;\n getInitialView?: (\n settings: Settings,\n filteredSchema: SettingsSchema,\n ) => SettingsView;\n forceShowAdvancedView?: boolean;\n allowAllView?: boolean;\n /**\n * Per-field initial value overrides that win over the values\n * derived from `useSettings`. The keys of each override are also\n * marked dirty on hydration so the user can save the form without\n * having to touch the prefilled fields. Useful when the page is\n * embedded in a flow that wants to nudge brand-new users toward a\n * particular default (e.g. onboarding pre-filling OpenHands/Opus).\n */\n initialValueOverrides?: SettingsFormValues;\n /**\n * When true, the Save button container is rendered inline (no\n * sticky positioning, no contrasting `bg-base` band) so the page\n * can be dropped into a modal/card without a hard footer break.\n */\n embedded?: boolean;\n /**\n * Suppress the built-in Save Changes button entirely. Pair with\n * {@link onSaveControlChange} to drive saving from a parent-rendered\n * action (e.g. an onboarding \"Next\" button).\n */\n hideSaveButton?: boolean;\n /**\n * Fires whenever the save state changes (a mutation starts/finishes,\n * dirty status flips). Provides a stable `save()` callback the\n * parent can wire to its own button. Useful when the form is\n * embedded in a custom flow and the built-in Save button is hidden.\n */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n testId?: string;\n}) {\n const { t } = useTranslation(\"openhands\");\n const { mutate: saveSettings, isPending } = useSaveSettings(scope);\n const { data: settings, isLoading, isFetching } = useSettings(scope);\n const agentSchemaQuery = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const conversationSchemaQuery = useConversationSettingsSchema(\n settings?.conversation_settings_schema,\n );\n const activeSchemaQuery =\n settingsSource === \"conversation_settings\"\n ? conversationSchemaQuery\n : agentSchemaQuery;\n const schema = activeSchemaQuery.data;\n const isSchemaLoading = activeSchemaQuery.isLoading;\n const isReadOnly = false;\n\n const [view, setView] = React.useState<SettingsView>(\"basic\");\n const [values, setValues] = React.useState<SettingsFormValues>({});\n const [dirty, setDirty] = React.useState<SettingsDirtyState>({});\n const hasHydratedViewRef = React.useRef(false);\n\n const sectionKeysSignature = React.useMemo(\n () => JSON.stringify(sectionKeys),\n [sectionKeys],\n );\n const stableSectionKeys = React.useMemo(\n () => JSON.parse(sectionKeysSignature) as string[],\n [sectionKeysSignature],\n );\n\n // Build a filtered schema containing only the requested sections.\n // `isValidSettingsSchema` guards against truthy-but-malformed schema\n // responses (e.g. when the deployment is pointed at a host that does\n // not serve `/api/settings/agent-schema` and returns an SPA shell\n // that parses into an object without a `sections` array). Without\n // the guard, `schema.sections.filter(...)` would throw and React\n // Router would escalate the crash to a full-screen error.\n const filteredSchema = React.useMemo(() => {\n if (!isValidSettingsSchema(schema)) return null;\n const sectionSet = new Set(stableSectionKeys);\n return {\n ...schema,\n sections: schema.sections.filter((s) => sectionSet.has(s.key)),\n };\n }, [schema, stableSectionKeys]);\n\n const showAdvanced =\n forceShowAdvancedView || hasAdvancedSettings(filteredSchema);\n const showAll = allowAllView && hasMinorSettings(filteredSchema);\n const schemaUnavailableMessage = React.useMemo(\n () =>\n getSchemaUnavailableMessage(\n activeSchemaQuery.error,\n t(I18nKey.SETTINGS$SDK_SCHEMA_UNAVAILABLE),\n ),\n [activeSchemaQuery.error, t],\n );\n\n const overridesSignature = React.useMemo(\n () => (initialValueOverrides ? JSON.stringify(initialValueOverrides) : \"\"),\n [initialValueOverrides],\n );\n\n const initialValues = React.useMemo(() => {\n if (!settings || !filteredSchema) return null;\n const base = buildInitialSettingsFormValues(\n settings,\n filteredSchema,\n settingsSource,\n );\n if (!initialValueOverrides) return base;\n return { ...base, ...initialValueOverrides };\n // overridesSignature keeps the memo reactive without depending on\n // a (potentially recreated) object reference each render.\n }, [settings, filteredSchema, settingsSource, overridesSignature]);\n\n const initialView = React.useMemo(() => {\n if (!settings || !filteredSchema) return null;\n\n const resolvedInitialView = getInitialView\n ? getInitialView(settings, filteredSchema)\n : inferInitialView(settings, filteredSchema, settingsSource);\n\n return normalizeView(resolvedInitialView, { showAdvanced, showAll });\n }, [\n settings,\n filteredSchema,\n getInitialView,\n settingsSource,\n showAdvanced,\n showAll,\n ]);\n\n React.useEffect(() => {\n hasHydratedViewRef.current = false;\n setView(\"basic\");\n setValues({});\n setDirty({});\n }, [scope, settingsSource, sectionKeysSignature]);\n\n React.useEffect(() => {\n if (!initialValues || !initialView) return;\n\n setValues(initialValues);\n // Override-supplied keys are pre-populated for the user, so mark\n // them dirty up-front; otherwise the Save button stays disabled\n // until the user touches a field, defeating the point of the\n // override.\n const overrideDirty: SettingsDirtyState = initialValueOverrides\n ? Object.fromEntries(\n Object.keys(initialValueOverrides).map((key) => [key, true]),\n )\n : {};\n setDirty(overrideDirty);\n setView((currentView) => {\n if (!hasHydratedViewRef.current) {\n hasHydratedViewRef.current = true;\n return initialView;\n }\n\n return getLessDetailedView(currentView, initialView);\n });\n // initialValueOverrides is intentionally tracked via\n // overridesSignature on initialValues; including the object ref\n // here would re-fire the effect every render.\n }, [initialValues, initialView]);\n\n const visibleSections = React.useMemo(() => {\n if (!filteredSchema) return [];\n return getVisibleSettingsSections(\n filteredSchema,\n values,\n view,\n excludeKeys,\n );\n }, [filteredSchema, values, view, excludeKeys]);\n\n const handleFieldChange = React.useCallback(\n (fieldKey: string, nextValue: string | boolean) => {\n setValues((prev) => ({ ...prev, [fieldKey]: nextValue }));\n setDirty((prev) => ({ ...prev, [fieldKey]: true }));\n },\n [],\n );\n\n const handleError = React.useCallback(\n (error: AxiosError) => {\n const msg = retrieveAxiosErrorMessage(error);\n displayErrorToast(msg || t(I18nKey.ERROR$GENERIC));\n },\n [t],\n );\n\n // Stable save callback so `onSaveControlChange` can hand a single\n // function reference to the parent across renders. The latest\n // closure is kept up to date via `handleSaveRef`.\n const handleSaveRef = React.useRef<() => void>(() => {});\n const stableSave = React.useCallback(() => {\n handleSaveRef.current();\n }, []);\n\n // Stable accessor for the coerced, dirty-only payload. Mirrors the\n // `handleSaveRef` pattern so the exposed function reference stays stable\n // across renders while always reading the latest closure at call time.\n const buildDirtyPayloadRef = React.useRef<() => Record<string, unknown>>(\n () => ({}),\n );\n const stableGetDirtyPayload = React.useCallback(\n () => buildDirtyPayloadRef.current(),\n [],\n );\n\n const handleSave = () => {\n if (!filteredSchema || isReadOnly) return;\n\n let payload: Record<string, unknown>;\n try {\n const basePayload = buildSdkSettingsPayloadForView(\n filteredSchema,\n values,\n dirty,\n view,\n );\n let defaultPayload: Record<string, unknown>;\n if (settingsSource === \"conversation_settings\") {\n defaultPayload = { conversation_settings_diff: basePayload };\n } else {\n defaultPayload = { agent_settings_diff: basePayload };\n }\n payload = buildPayload\n ? buildPayload(basePayload, { values, dirty, view })\n : defaultPayload;\n } catch (error) {\n displayErrorToast(\n error instanceof Error ? error.message : t(I18nKey.ERROR$GENERIC),\n );\n return;\n }\n\n if (Object.keys(payload).length === 0) return;\n\n saveSettings(payload, {\n onError: handleError,\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.SETTINGS$SAVED_WARNING));\n setDirty({});\n onSaveSuccess?.();\n },\n });\n };\n\n handleSaveRef.current = handleSave;\n // Dirty-only (NOT view-filtered): we must never inject defaults for\n // non-visible fields here, or a custom save flow would reset fields the\n // user never touched. `buildSdkSettingsPayloadForView` is reserved for the\n // built-in full-replace save above.\n buildDirtyPayloadRef.current = () =>\n filteredSchema\n ? buildSdkSettingsPayload(filteredSchema, values, dirty)\n : {};\n\n // Surface save state to the parent. Hooks must run before any\n // conditional early-returns below, so this lives here rather than\n // alongside the JSX. The dependency list deliberately excludes\n // `stableSave` (it never changes) and `onSaveControlChange` (we\n // tolerate ref-instability of the callback to avoid spamming the\n // parent on every render).\n const saveControlIsDirty = Object.keys(dirty).length > 0 || extraDirty;\n React.useEffect(() => {\n if (!onSaveControlChange) return;\n onSaveControlChange({\n save: stableSave,\n isSaving: isPending,\n isDirty: saveControlIsDirty,\n values,\n view,\n getDirtyPayload: stableGetDirtyPayload,\n });\n }, [isPending, saveControlIsDirty, values, view]);\n\n if (isLoading || isFetching || isSchemaLoading) {\n return <LlmSettingsInputsSkeleton />;\n }\n\n if (!filteredSchema || filteredSchema.sections.length === 0) {\n return (\n <Typography.Paragraph className=\"text-tertiary-alt\">\n {schemaUnavailableMessage}\n </Typography.Paragraph>\n );\n }\n\n if (Object.keys(values).length === 0) return <LlmSettingsInputsSkeleton />;\n\n // Scrolling is owned by the settings shell (or onboarding wrapper), not a\n // nested scroll region. Save actions are inline after the last field.\n const bodyClassName = \"flex flex-col gap-8\";\n\n return (\n <div\n data-testid={testId}\n className={\n embedded\n ? \"relative flex min-h-0 w-full flex-1 flex-col\"\n : \"relative w-full min-h-0\"\n }\n >\n <ViewToggle\n view={view}\n setView={setView}\n showAdvanced={showAdvanced}\n showAll={showAll}\n isDisabled={isReadOnly}\n />\n\n <div className={bodyClassName}>\n {header?.({\n values,\n isDisabled: isReadOnly,\n view,\n onChange: handleFieldChange,\n })}\n\n {visibleSections.map((section, sectionIndex) => (\n <section\n key={`${section.key}-${sectionIndex}`}\n className=\"flex flex-col gap-4\"\n >\n <div className=\"flex flex-col gap-4\">\n {section.fields.map((field) => (\n <SchemaField\n key={field.key}\n field={field}\n value={values[field.key]}\n isDisabled={isReadOnly}\n onChange={(nextValue) =>\n handleFieldChange(field.key, nextValue)\n }\n />\n ))}\n </div>\n </section>\n ))}\n\n {!isReadOnly && !hideSaveButton ? (\n <div className=\"flex justify-start pt-2\">\n <BrandButton\n testId=\"save-button\"\n type=\"button\"\n variant=\"primary\"\n isDisabled={\n isPending || (Object.keys(dirty).length === 0 && !extraDirty)\n }\n onClick={handleSave}\n >\n {isPending\n ? t(I18nKey.SETTINGS$SAVING)\n : t(I18nKey.SETTINGS$SAVE_CHANGES)}\n </BrandButton>\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../i18n/declaration.cjs`),t=require(`../node_modules/@openhands/typescript-client/dist/models/acp.cjs`);require(`../node_modules/@openhands/typescript-client/dist/index.cjs`);var n=new Set([`default`,`default (recommended)`]);function r(e){if(typeof e!=`string`)return null;let t=e.trim();return!t||n.has(t.toLowerCase())||t===`acp-managed`?null:t}function i(e){for(let t of[e.runtimeName,e.runtimeId,e.configured,e.sdkLlm]){let e=r(t);if(e)return e}return e.providerDefault??null}var a={"claude-code":{icon:`claude-code`,description_key:e.I18nKey.ONBOARDING$AGENT_CLAUDE_CODE_DESCRIPTION},codex:{icon:`codex`,description_key:e.I18nKey.ONBOARDING$AGENT_CODEX_DESCRIPTION},"gemini-cli":{icon:`gemini`,description_key:e.I18nKey.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION}},o=Object.entries(a).map(([e,n])=>{let r=t.getAcpProvider(e);return{key:e,display_name:r?.display_name??e,default_command:r?[...r.default_command]:[],available_models:r?.available_models?.map(e=>({id:e.id,label:e.label})),default_model:r?.default_model??void 0,description_key:n.description_key,icon:n.icon}});function s(e){if(e)return o.find(t=>t.key===e)}function c(e){let t=s(e);return t?t.display_name:null}function l(e){return s(e)?.icon??`cli-generic`}function u(e,t){return t?s(e)?.available_models?.find(e=>e.id===t)?.label??t:null}exports.ACP_PROVIDERS=o,exports.getAcpProvider=s,exports.getAcpProviderDisplayName=c,exports.labelForAcpModel=u,exports.resolveAcpProviderIcon=l,exports.resolveEffectiveAcpModel=i;
1
+ require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../i18n/declaration.cjs`),t=require(`../node_modules/@openhands/typescript-client/dist/models/acp.cjs`);require(`../node_modules/@openhands/typescript-client/dist/index.cjs`);var n=new Set([`default`,`default (recommended)`]);function r(e){if(typeof e!=`string`)return null;let t=e.trim();return!t||n.has(t.toLowerCase())||t===`acp-managed`?null:t}function i(e){for(let t of[e.runtimeName,e.runtimeId,e.configured,e.sdkLlm]){let e=r(t);if(e)return e}return e.providerDefault??null}var a={"claude-code":{icon:`claude-code`,description_key:e.I18nKey.ONBOARDING$AGENT_CLAUDE_CODE_DESCRIPTION},codex:{icon:`codex`,description_key:e.I18nKey.ONBOARDING$AGENT_CODEX_DESCRIPTION},"gemini-cli":{icon:`gemini`,description_key:e.I18nKey.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION}},o=Object.entries(a).map(([e,n])=>{let r=t.getAcpProvider(e);return{key:e,display_name:r?.display_name??e,default_command:r?[...r.default_command]:[],available_models:r?.available_models?.map(e=>({id:e.id,label:e.label})),default_model:r?.default_model??void 0,description_key:n.description_key,icon:n.icon}});e.I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT;function s(e){if(e)return o.find(t=>t.key===e)}function c(e){let t=s(e);return t?t.display_name:null}function l(e){return s(e)?.icon??`cli-generic`}function u(e,t){return t?s(e)?.available_models?.find(e=>e.id===t)?.label??t:null}exports.ACP_PROVIDERS=o,exports.getAcpProvider=s,exports.getAcpProviderDisplayName=c,exports.labelForAcpModel=u,exports.resolveAcpProviderIcon=l,exports.resolveEffectiveAcpModel=i;
2
2
  //# sourceMappingURL=acp-providers.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"acp-providers.cjs","names":[],"sources":["../../src/constants/acp-providers.ts"],"sourcesContent":["import { getAcpProvider as getClientAcpProvider } from \"@openhands/typescript-client\";\nimport { I18nKey } from \"#/i18n/declaration\";\n\nexport type ACPProviderIcon =\n | \"claude-code\"\n | \"codex\"\n | \"gemini\"\n | \"cli-generic\";\n\nexport const ACP_PROVIDER_FALLBACK_ICON: ACPProviderIcon = \"cli-generic\";\n\n// SDK placeholder strings the ACP wrapper returns before the user has\n// chosen a real model — surfacing either would lie about what's running.\nexport const ACP_DEFAULT_PLACEHOLDERS = new Set([\n \"default\",\n \"default (recommended)\",\n]);\n\n// Sentinel ``agent.llm.model`` returned by older SDKs for ACP conversations\n// in lieu of a real model. Suppressed at every consumer that resolves a\n// display string.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty, not the SDK's \"default\"\n * placeholder, not the legacy ``acp-managed`` sentinel. Returns the trimmed\n * value on success, ``null`` otherwise.\n */\nfunction realAcpModel(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n if (ACP_DEFAULT_PLACEHOLDERS.has(trimmed.toLowerCase())) return null;\n if (trimmed === ACP_MANAGED_SENTINEL) return null;\n return trimmed;\n}\n\n/**\n * Single source of truth for resolving the model string to surface for an\n * ACP conversation/settings context. Consumed by the conversation adapter\n * (chip text), the conversation-creation path (concrete ``acp_model``\n * payload), the Settings → Agent form (initial value), and the chat-input\n * model label.\n *\n * Precedence: SDK runtime fields → user-configured ``acp_model`` →\n * legacy ``agent.llm.model`` → provider default (when ``providerDefault``\n * is passed). Pass ``providerDefault`` only on surfaces that should\n * silently substitute the registry default; omit it for the conversation\n * chip, which must distinguish \"no concrete model\" from \"default\".\n */\nexport function resolveEffectiveAcpModel(inputs: {\n runtimeName?: string | null;\n runtimeId?: string | null;\n configured?: string | null;\n sdkLlm?: string | null;\n providerDefault?: string | null;\n}): string | null {\n for (const candidate of [\n inputs.runtimeName,\n inputs.runtimeId,\n inputs.configured,\n inputs.sdkLlm,\n ]) {\n const value = realAcpModel(candidate);\n if (value) return value;\n }\n return inputs.providerDefault ?? null;\n}\n\n/**\n * Shape of a built-in ACP (Agent Client Protocol) provider as Canvas consumes\n * it. The data fields (display name, launch command, model picker + default)\n * are sourced at module load from ``@openhands/typescript-client``'s ACP\n * registry — the generated mirror of the Python source of truth\n * ``openhands.sdk.settings.acp_providers``. This config only adds the\n * Canvas-specific UI fields ({@link ACPProviderConfig.icon} +\n * {@link ACPProviderConfig.description_key}); see {@link ACP_PROVIDER_UI}.\n */\nexport interface ACPProviderConfig {\n /** Stable registry key, also stored on conversations as ``tags.acpserver``. */\n key: string;\n /** Human-readable name shown in dropdowns and conversation chips. */\n display_name: string;\n /**\n * Tokens passed to the agent-server as ``acp_command`` when this preset\n * is picked. Each entry must be a real ACP-protocol stdio server — the\n * SDK validates this against the {@link ACPProviderConfig.key}.\n *\n * NB: ``npx -y @openai/codex acp`` looks plausible but is **not** an\n * ACP server — the codex CLI has no ``acp`` subcommand and exits with\n * ``Error: stdin is not a terminal`` when spawned without a TTY, which\n * silently deadlocks the agent-server's ACP handshake. Use\n * ``@zed-industries/codex-acp`` (the Zed-shipped wrapper) instead.\n */\n default_command: string[];\n /**\n * Suggested ACP model IDs for the provider's picker, sourced from the\n * typescript-client registry. Not authoritative access checks; users can\n * still enter a custom override in Settings -> Agent.\n */\n available_models?: ACPModelOption[];\n /** Model ID preselected for built-in providers so Canvas never saves blank. */\n default_model?: string;\n /**\n * i18n key for the one-line provider description rendered under the\n * onboarding tile. Stored on the registry so adding a new ACP\n * provider only requires editing this file (not the onboarding tile\n * list separately).\n */\n description_key: I18nKey;\n /**\n * Serializable icon key used by UI surfaces that render provider\n * choices. Kept as a string so the SDK mirror check can continue to\n * parse this registry without importing React components.\n */\n icon?: ACPProviderIcon;\n}\n\nexport interface ACPModelOption {\n /** Exact model ID sent as ``acp_model``. */\n id: string;\n /** Human-readable label shown in Settings -> Agent. */\n label: string;\n}\n\n// Canvas-only UI metadata per built-in provider, keyed by the ACP registry\n// key. Everything else — display name, launch command, model picker list and\n// default — comes from the typescript-client registry below. Adding a model\n// or a provider happens upstream in the SDK; Canvas only owns the brand icon\n// and the onboarding-tile description here. A provider with no entry here is\n// intentionally not surfaced in the UI.\nconst ACP_PROVIDER_UI: Record<\n string,\n { icon: ACPProviderIcon; description_key: I18nKey }\n> = {\n \"claude-code\": {\n icon: \"claude-code\",\n description_key: I18nKey.ONBOARDING$AGENT_CLAUDE_CODE_DESCRIPTION,\n },\n codex: {\n icon: \"codex\",\n description_key: I18nKey.ONBOARDING$AGENT_CODEX_DESCRIPTION,\n },\n \"gemini-cli\": {\n icon: \"gemini\",\n description_key: I18nKey.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION,\n },\n};\n\n// Built-in ACP providers Canvas surfaces, built by enriching each upstream\n// registry record (``@openhands/typescript-client`` → Python SDK) with the\n// Canvas UI metadata above. Model lists + defaults are no longer hand-kept\n// here (closes agent-canvas#740) — they track the SDK via the pinned client.\nexport const ACP_PROVIDERS: ACPProviderConfig[] = Object.entries(\n ACP_PROVIDER_UI,\n).map(([key, ui]) => {\n const info = getClientAcpProvider(key);\n return {\n key,\n display_name: info?.display_name ?? key,\n default_command: info ? [...info.default_command] : [],\n available_models: info?.available_models?.map((model) => ({\n id: model.id,\n label: model.label,\n })),\n default_model: info?.default_model ?? undefined,\n description_key: ui.description_key,\n icon: ui.icon,\n };\n});\n\nexport const ACP_CUSTOM_PRESET_KEY = \"custom\";\n\n/**\n * Look up a built-in ACP provider config by its registry key.\n *\n * Returns ``undefined`` for an empty / null key, for the ``\"custom\"`` preset\n * (which has no registry entry), and for any forward-compatible key Canvas's\n * registry doesn't know about yet. Centralizes the ``ACP_PROVIDERS.find(...)``\n * lookup shared by the resolvers below and by the adapter / settings surfaces\n * so the key-comparison shape lives in one place.\n */\nexport function getAcpProvider(\n key: string | null | undefined,\n): ACPProviderConfig | undefined {\n if (!key) return undefined;\n return ACP_PROVIDERS.find((provider) => provider.key === key);\n}\n\n/**\n * Resolve an ACP provider registry key (the value stored under\n * ``tags.acpserver`` on a conversation) to a human display name for the\n * sidebar chip.\n *\n * Returns ``null`` for an empty / null key and for keys not in\n * {@link ACP_PROVIDERS} — most notably ``\"custom\"`` (the user-supplied\n * command preset has no canonical brand name) and any forward-compatible\n * value Canvas's registry doesn't know about yet. Callers should fall\n * back to a generic ``\"ACP\"`` label in that case so the chip still\n * communicates \"this is an ACP conversation\".\n *\n * Kept separate from {@link buildAcpAgentSettingsDiff}'s lookup so the\n * conversation-card render path can resolve display names without\n * importing the settings-payload builder.\n */\nexport function getAcpProviderDisplayName(\n key: string | null | undefined,\n): string | null {\n const found = getAcpProvider(key);\n return found ? found.display_name : null;\n}\n\n/**\n * Resolve an ACP provider registry key to the icon discriminator the\n * conversation chip should render alongside the model text.\n *\n * Falls back to {@link ACP_PROVIDER_FALLBACK_ICON} for ``\"custom\"``,\n * unknown keys, or a missing key — the chip then shows a neutral\n * terminal glyph that still communicates \"this is an ACP conversation\"\n * without claiming a brand identity we don't know.\n */\nexport function resolveAcpProviderIcon(\n key: string | null | undefined,\n): ACPProviderIcon {\n return getAcpProvider(key)?.icon ?? ACP_PROVIDER_FALLBACK_ICON;\n}\n\n/**\n * Resolve a raw ``acp_model`` ID to the human-readable label the provider's\n * picker shows for it (e.g. ``\"claude-opus-4-7\"`` → ``\"Claude Opus 4.7\"``).\n *\n * Falls back to the raw ID when the provider is unknown or the ID isn't one\n * of its registered {@link ACPModelOption}s — so a user's custom override\n * still renders something meaningful rather than nothing. Returns ``null``\n * only when there is no model to show, letting the conversation chip decide\n * to display the provider name instead.\n */\nexport function labelForAcpModel(\n serverKey: string | null | undefined,\n modelId: string | null | undefined,\n): string | null {\n if (!modelId) return null;\n const provider = getAcpProvider(serverKey);\n const match = provider?.available_models?.find((m) => m.id === modelId);\n return match?.label ?? modelId;\n}\n\n/**\n * Build the ``agent_settings_diff`` payload PATCH /api/settings expects\n * for the agent-kind/provider choice the user just made.\n *\n * Used by both the Settings → Agent page and the onboarding \"choose\n * agent\" step — keeping the shape in one helper means a future change\n * (e.g. always seeding ``acp_command`` from the registry instead of\n * sending ``[]``, or adding new ``acp_*`` reset fields) lands in both\n * surfaces atomically.\n *\n * Returns ``null`` for an unknown ACP provider key by default — the\n * caller can skip the save (the UI shouldn't surface unknown options,\n * but the defensive path keeps a buggy preset list from corrupting\n * settings).\n *\n * Pass ``allowUnknownServer: true`` to opt into pass-through for keys\n * that aren't in {@link ACP_PROVIDERS} or ``ACP_CUSTOM_PRESET_KEY``.\n * The Settings → Agent page uses this when the user opens settings\n * that already carry an ``acp_server`` value canvas's registry\n * doesn't know about (e.g. set out-of-band via the API for a provider\n * we haven't mirrored yet) and saves without changing the command —\n * otherwise the original key would be silently demoted to ``\"custom\"``.\n */\nexport function buildAcpAgentSettingsDiff(\n providerKey: string,\n options: {\n command?: string[];\n model?: string | null;\n allowUnknownServer?: boolean;\n } = {},\n): Record<string, unknown> | null {\n if (providerKey === \"openhands\") {\n // Switching back to OpenHands. The agent-server's ``Settings.update``\n // applies a fresh ``{'agent_kind': ...}`` base whenever the kind\n // flips, so any ``acp_*`` fields would be discarded before\n // validation. Send the kind alone.\n return { agent_kind: \"openhands\" };\n }\n\n const isCustom = providerKey === ACP_CUSTOM_PRESET_KEY;\n const provider = isCustom ? undefined : getAcpProvider(providerKey);\n if (!isCustom && !provider && !options.allowUnknownServer) {\n return null;\n }\n\n const model =\n options.model === undefined\n ? (provider?.default_model ?? null)\n : options.model;\n\n // ``acp_args: []`` resets any API-set ``acp_args`` that would\n // otherwise survive and concatenate to ``acp_command`` at spawn time\n // (the agent-server merges the two before exec). Callers building the\n // payload from a textarea that already shows the merged command\n // (Settings → Agent) round-trip correctly — the merged tokens land in\n // ``acp_command`` here, so no args are lost.\n return {\n agent_kind: \"acp\",\n acp_server: providerKey,\n acp_command: options.command ?? [],\n acp_args: [],\n acp_model: model ?? null,\n };\n}\n"],"mappings":"6OAaA,IAAa,EAA2B,IAAI,IAAI,CAC9C,UACA,wBACD,CAAC,CAYF,SAAS,EAAa,EAA+B,CACnD,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAU,EAAM,MAAM,CAI5B,MAHI,CAAC,GACD,EAAyB,IAAI,EAAQ,aAAa,CAAC,EACnD,IAAA,cAAyC,KACtC,EAgBT,SAAgB,EAAyB,EAMvB,CAChB,IAAK,IAAM,IAAa,CACtB,EAAO,YACP,EAAO,UACP,EAAO,WACP,EAAO,OACR,CAAE,CACD,IAAM,EAAQ,EAAa,EAAU,CACrC,GAAI,EAAO,OAAO,EAEpB,OAAO,EAAO,iBAAmB,KAiEnC,IAAM,EAGF,CACF,cAAe,CACb,KAAM,cACN,gBAAiB,EAAA,QAAQ,yCAC1B,CACD,MAAO,CACL,KAAM,QACN,gBAAiB,EAAA,QAAQ,mCAC1B,CACD,aAAc,CACZ,KAAM,SACN,gBAAiB,EAAA,QAAQ,wCAC1B,CACF,CAMY,EAAqC,OAAO,QACvD,EACD,CAAC,KAAK,CAAC,EAAK,KAAQ,CACnB,IAAM,EAAO,EAAA,eAAqB,EAAI,CACtC,MAAO,CACL,MACA,aAAc,GAAM,cAAgB,EACpC,gBAAiB,EAAO,CAAC,GAAG,EAAK,gBAAgB,CAAG,EAAE,CACtD,iBAAkB,GAAM,kBAAkB,IAAK,IAAW,CACxD,GAAI,EAAM,GACV,MAAO,EAAM,MACd,EAAE,CACH,cAAe,GAAM,eAAiB,IAAA,GACtC,gBAAiB,EAAG,gBACpB,KAAM,EAAG,KACV,EACD,CAaF,SAAgB,EACd,EAC+B,CAC1B,KACL,OAAO,EAAc,KAAM,GAAa,EAAS,MAAQ,EAAI,CAmB/D,SAAgB,EACd,EACe,CACf,IAAM,EAAQ,EAAe,EAAI,CACjC,OAAO,EAAQ,EAAM,aAAe,KAYtC,SAAgB,EACd,EACiB,CACjB,OAAO,EAAe,EAAI,EAAE,MAAA,cAa9B,SAAgB,EACd,EACA,EACe,CAIf,OAHK,EACY,EAAe,EAClB,EAAU,kBAAkB,KAAM,GAAM,EAAE,KAAO,EAAQ,EACzD,OAAS,EAHF"}
1
+ {"version":3,"file":"acp-providers.cjs","names":[],"sources":["../../src/constants/acp-providers.ts"],"sourcesContent":["import { getAcpProvider as getClientAcpProvider } from \"@openhands/typescript-client\";\nimport { I18nKey } from \"#/i18n/declaration\";\n\nexport type ACPProviderIcon =\n | \"claude-code\"\n | \"codex\"\n | \"gemini\"\n | \"cli-generic\";\n\nexport const ACP_PROVIDER_FALLBACK_ICON: ACPProviderIcon = \"cli-generic\";\n\n// SDK placeholder strings the ACP wrapper returns before the user has\n// chosen a real model — surfacing either would lie about what's running.\nexport const ACP_DEFAULT_PLACEHOLDERS = new Set([\n \"default\",\n \"default (recommended)\",\n]);\n\n// Sentinel ``agent.llm.model`` returned by older SDKs for ACP conversations\n// in lieu of a real model. Suppressed at every consumer that resolves a\n// display string.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty, not the SDK's \"default\"\n * placeholder, not the legacy ``acp-managed`` sentinel. Returns the trimmed\n * value on success, ``null`` otherwise.\n */\nfunction realAcpModel(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n if (ACP_DEFAULT_PLACEHOLDERS.has(trimmed.toLowerCase())) return null;\n if (trimmed === ACP_MANAGED_SENTINEL) return null;\n return trimmed;\n}\n\n/**\n * Single source of truth for resolving the model string to surface for an\n * ACP conversation/settings context. Consumed by the conversation adapter\n * (chip text), the conversation-creation path (concrete ``acp_model``\n * payload), the Settings → Agent form (initial value), and the chat-input\n * model label.\n *\n * Precedence: SDK runtime fields → user-configured ``acp_model`` →\n * legacy ``agent.llm.model`` → provider default (when ``providerDefault``\n * is passed). Pass ``providerDefault`` only on surfaces that should\n * silently substitute the registry default; omit it for the conversation\n * chip, which must distinguish \"no concrete model\" from \"default\".\n */\nexport function resolveEffectiveAcpModel(inputs: {\n runtimeName?: string | null;\n runtimeId?: string | null;\n configured?: string | null;\n sdkLlm?: string | null;\n providerDefault?: string | null;\n}): string | null {\n for (const candidate of [\n inputs.runtimeName,\n inputs.runtimeId,\n inputs.configured,\n inputs.sdkLlm,\n ]) {\n const value = realAcpModel(candidate);\n if (value) return value;\n }\n return inputs.providerDefault ?? null;\n}\n\n/**\n * Shape of a built-in ACP (Agent Client Protocol) provider as Canvas consumes\n * it. The data fields (display name, launch command, model picker + default)\n * are sourced at module load from ``@openhands/typescript-client``'s ACP\n * registry — the generated mirror of the Python source of truth\n * ``openhands.sdk.settings.acp_providers``. This config only adds the\n * Canvas-specific UI fields ({@link ACPProviderConfig.icon} +\n * {@link ACPProviderConfig.description_key}); see {@link ACP_PROVIDER_UI}.\n */\nexport interface ACPProviderConfig {\n /** Stable registry key, also stored on conversations as ``tags.acpserver``. */\n key: string;\n /** Human-readable name shown in dropdowns and conversation chips. */\n display_name: string;\n /**\n * Tokens passed to the agent-server as ``acp_command`` when this preset\n * is picked. Each entry must be a real ACP-protocol stdio server — the\n * SDK validates this against the {@link ACPProviderConfig.key}.\n *\n * NB: ``npx -y @openai/codex acp`` looks plausible but is **not** an\n * ACP server — the codex CLI has no ``acp`` subcommand and exits with\n * ``Error: stdin is not a terminal`` when spawned without a TTY, which\n * silently deadlocks the agent-server's ACP handshake. Use\n * ``@zed-industries/codex-acp`` (the Zed-shipped wrapper) instead.\n */\n default_command: string[];\n /**\n * Suggested ACP model IDs for the provider's picker, sourced from the\n * typescript-client registry. Not authoritative access checks; users can\n * still enter a custom override in Settings -> Agent.\n */\n available_models?: ACPModelOption[];\n /** Model ID preselected for built-in providers so Canvas never saves blank. */\n default_model?: string;\n /**\n * i18n key for the one-line provider description rendered under the\n * onboarding tile. Stored on the registry so adding a new ACP\n * provider only requires editing this file (not the onboarding tile\n * list separately).\n */\n description_key: I18nKey;\n /**\n * Serializable icon key used by UI surfaces that render provider\n * choices. Kept as a string so the SDK mirror check can continue to\n * parse this registry without importing React components.\n */\n icon?: ACPProviderIcon;\n}\n\nexport interface ACPModelOption {\n /** Exact model ID sent as ``acp_model``. */\n id: string;\n /** Human-readable label shown in Settings -> Agent. */\n label: string;\n}\n\n// Canvas-only UI metadata per built-in provider, keyed by the ACP registry\n// key. Everything else — display name, launch command, model picker list and\n// default — comes from the typescript-client registry below. Adding a model\n// or a provider happens upstream in the SDK; Canvas only owns the brand icon\n// and the onboarding-tile description here. A provider with no entry here is\n// intentionally not surfaced in the UI.\nconst ACP_PROVIDER_UI: Record<\n string,\n { icon: ACPProviderIcon; description_key: I18nKey }\n> = {\n \"claude-code\": {\n icon: \"claude-code\",\n description_key: I18nKey.ONBOARDING$AGENT_CLAUDE_CODE_DESCRIPTION,\n },\n codex: {\n icon: \"codex\",\n description_key: I18nKey.ONBOARDING$AGENT_CODEX_DESCRIPTION,\n },\n \"gemini-cli\": {\n icon: \"gemini\",\n description_key: I18nKey.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION,\n },\n};\n\n// Built-in ACP providers Canvas surfaces, built by enriching each upstream\n// registry record (``@openhands/typescript-client`` → Python SDK) with the\n// Canvas UI metadata above. Model lists + defaults are no longer hand-kept\n// here (closes agent-canvas#740) — they track the SDK via the pinned client.\nexport const ACP_PROVIDERS: ACPProviderConfig[] = Object.entries(\n ACP_PROVIDER_UI,\n).map(([key, ui]) => {\n const info = getClientAcpProvider(key);\n return {\n key,\n display_name: info?.display_name ?? key,\n default_command: info ? [...info.default_command] : [],\n available_models: info?.available_models?.map((model) => ({\n id: model.id,\n label: model.label,\n })),\n default_model: info?.default_model ?? undefined,\n description_key: ui.description_key,\n icon: ui.icon,\n };\n});\n\nexport const ACP_CUSTOM_PRESET_KEY = \"custom\";\n\n/**\n * A credential an ACP provider authenticates with, surfaced during onboarding\n * so the user can populate it without leaving the flow. The {@link name} is\n * both the global-secret name and the environment variable the agent-server\n * exports into the ACP subprocess — keeping them identical is what makes a\n * saved secret actually reach the provider CLI.\n */\nexport interface ACPProviderSecretField {\n /** Secret name and env var (e.g. ``\"ANTHROPIC_API_KEY\"``). Must satisfy the\n * secret-name pattern ``^[a-zA-Z][a-zA-Z0-9_]{0,63}$``. */\n name: string;\n /** Render as a masked password input (API keys) rather than a plain-text\n * input (base URLs). Every field is optional regardless — the whole step is\n * skippable — so this only controls masking, not whether the field gates. */\n secret?: boolean;\n /** i18n key for the one-line helper text under the field. */\n hint_key: I18nKey;\n}\n\n// Credentials Canvas prompts for during onboarding, keyed by ACP registry key.\n// Only providers that authenticate through an env-var API key appear here:\n// Claude Code (Anthropic) and Codex (OpenAI). Gemini CLI authenticates via an\n// interactive OAuth login rather than a static key, so it has no entry and its\n// onboarding credentials step is skipped. Every field is optional (the step is\n// skippable): the API keys render masked, the base-URL entries are plain-text\n// overrides for proxies/gateways. A provider with no entry simply shows no\n// credentials step.\nconst ACP_PROVIDER_SECRETS: Record<string, ACPProviderSecretField[]> = {\n \"claude-code\": [\n {\n name: \"ANTHROPIC_API_KEY\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n },\n {\n name: \"ANTHROPIC_BASE_URL\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n },\n ],\n codex: [\n {\n name: \"OPENAI_API_KEY\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n },\n {\n name: \"OPENAI_BASE_URL\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n },\n ],\n};\n\n/**\n * List the credentials Canvas should prompt for when onboarding the given ACP\n * provider. Returns ``[]`` for OpenHands, the ``\"custom\"`` preset, providers\n * that don't use a static API key (Gemini CLI), and any unknown key — callers\n * treat an empty list as \"no credentials step for this provider\".\n */\nexport function getAcpProviderSecrets(\n key: string | null | undefined,\n): ACPProviderSecretField[] {\n if (!key) return [];\n return ACP_PROVIDER_SECRETS[key] ?? [];\n}\n\n/**\n * Look up a built-in ACP provider config by its registry key.\n *\n * Returns ``undefined`` for an empty / null key, for the ``\"custom\"`` preset\n * (which has no registry entry), and for any forward-compatible key Canvas's\n * registry doesn't know about yet. Centralizes the ``ACP_PROVIDERS.find(...)``\n * lookup shared by the resolvers below and by the adapter / settings surfaces\n * so the key-comparison shape lives in one place.\n */\nexport function getAcpProvider(\n key: string | null | undefined,\n): ACPProviderConfig | undefined {\n if (!key) return undefined;\n return ACP_PROVIDERS.find((provider) => provider.key === key);\n}\n\n/**\n * Resolve an ACP provider registry key (the value stored under\n * ``tags.acpserver`` on a conversation) to a human display name for the\n * sidebar chip.\n *\n * Returns ``null`` for an empty / null key and for keys not in\n * {@link ACP_PROVIDERS} — most notably ``\"custom\"`` (the user-supplied\n * command preset has no canonical brand name) and any forward-compatible\n * value Canvas's registry doesn't know about yet. Callers should fall\n * back to a generic ``\"ACP\"`` label in that case so the chip still\n * communicates \"this is an ACP conversation\".\n *\n * Kept separate from {@link buildAcpAgentSettingsDiff}'s lookup so the\n * conversation-card render path can resolve display names without\n * importing the settings-payload builder.\n */\nexport function getAcpProviderDisplayName(\n key: string | null | undefined,\n): string | null {\n const found = getAcpProvider(key);\n return found ? found.display_name : null;\n}\n\n/**\n * Resolve an ACP provider registry key to the icon discriminator the\n * conversation chip should render alongside the model text.\n *\n * Falls back to {@link ACP_PROVIDER_FALLBACK_ICON} for ``\"custom\"``,\n * unknown keys, or a missing key — the chip then shows a neutral\n * terminal glyph that still communicates \"this is an ACP conversation\"\n * without claiming a brand identity we don't know.\n */\nexport function resolveAcpProviderIcon(\n key: string | null | undefined,\n): ACPProviderIcon {\n return getAcpProvider(key)?.icon ?? ACP_PROVIDER_FALLBACK_ICON;\n}\n\n/**\n * Resolve a raw ``acp_model`` ID to the human-readable label the provider's\n * picker shows for it (e.g. ``\"claude-opus-4-7\"`` → ``\"Claude Opus 4.7\"``).\n *\n * Falls back to the raw ID when the provider is unknown or the ID isn't one\n * of its registered {@link ACPModelOption}s — so a user's custom override\n * still renders something meaningful rather than nothing. Returns ``null``\n * only when there is no model to show, letting the conversation chip decide\n * to display the provider name instead.\n */\nexport function labelForAcpModel(\n serverKey: string | null | undefined,\n modelId: string | null | undefined,\n): string | null {\n if (!modelId) return null;\n const provider = getAcpProvider(serverKey);\n const match = provider?.available_models?.find((m) => m.id === modelId);\n return match?.label ?? modelId;\n}\n\n/**\n * Build the ``agent_settings_diff`` payload PATCH /api/settings expects\n * for the agent-kind/provider choice the user just made.\n *\n * Used by both the Settings → Agent page and the onboarding \"choose\n * agent\" step — keeping the shape in one helper means a future change\n * (e.g. always seeding ``acp_command`` from the registry instead of\n * sending ``[]``, or adding new ``acp_*`` reset fields) lands in both\n * surfaces atomically.\n *\n * Returns ``null`` for an unknown ACP provider key by default — the\n * caller can skip the save (the UI shouldn't surface unknown options,\n * but the defensive path keeps a buggy preset list from corrupting\n * settings).\n *\n * Pass ``allowUnknownServer: true`` to opt into pass-through for keys\n * that aren't in {@link ACP_PROVIDERS} or ``ACP_CUSTOM_PRESET_KEY``.\n * The Settings → Agent page uses this when the user opens settings\n * that already carry an ``acp_server`` value canvas's registry\n * doesn't know about (e.g. set out-of-band via the API for a provider\n * we haven't mirrored yet) and saves without changing the command —\n * otherwise the original key would be silently demoted to ``\"custom\"``.\n */\nexport function buildAcpAgentSettingsDiff(\n providerKey: string,\n options: {\n command?: string[];\n model?: string | null;\n allowUnknownServer?: boolean;\n } = {},\n): Record<string, unknown> | null {\n if (providerKey === \"openhands\") {\n // Switching back to OpenHands. The agent-server's ``Settings.update``\n // applies a fresh ``{'agent_kind': ...}`` base whenever the kind\n // flips, so any ``acp_*`` fields would be discarded before\n // validation. Send the kind alone.\n return { agent_kind: \"openhands\" };\n }\n\n const isCustom = providerKey === ACP_CUSTOM_PRESET_KEY;\n const provider = isCustom ? undefined : getAcpProvider(providerKey);\n if (!isCustom && !provider && !options.allowUnknownServer) {\n return null;\n }\n\n const model =\n options.model === undefined\n ? (provider?.default_model ?? null)\n : options.model;\n\n // ``acp_args: []`` resets any API-set ``acp_args`` that would\n // otherwise survive and concatenate to ``acp_command`` at spawn time\n // (the agent-server merges the two before exec). Callers building the\n // payload from a textarea that already shows the merged command\n // (Settings → Agent) round-trip correctly — the merged tokens land in\n // ``acp_command`` here, so no args are lost.\n return {\n agent_kind: \"acp\",\n acp_server: providerKey,\n acp_command: options.command ?? [],\n acp_args: [],\n acp_model: model ?? null,\n };\n}\n"],"mappings":"6OAaA,IAAa,EAA2B,IAAI,IAAI,CAC9C,UACA,wBACD,CAAC,CAYF,SAAS,EAAa,EAA+B,CACnD,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAU,EAAM,MAAM,CAI5B,MAHI,CAAC,GACD,EAAyB,IAAI,EAAQ,aAAa,CAAC,EACnD,IAAA,cAAyC,KACtC,EAgBT,SAAgB,EAAyB,EAMvB,CAChB,IAAK,IAAM,IAAa,CACtB,EAAO,YACP,EAAO,UACP,EAAO,WACP,EAAO,OACR,CAAE,CACD,IAAM,EAAQ,EAAa,EAAU,CACrC,GAAI,EAAO,OAAO,EAEpB,OAAO,EAAO,iBAAmB,KAiEnC,IAAM,EAGF,CACF,cAAe,CACb,KAAM,cACN,gBAAiB,EAAA,QAAQ,yCAC1B,CACD,MAAO,CACL,KAAM,QACN,gBAAiB,EAAA,QAAQ,mCAC1B,CACD,aAAc,CACZ,KAAM,SACN,gBAAiB,EAAA,QAAQ,wCAC1B,CACF,CAMY,EAAqC,OAAO,QACvD,EACD,CAAC,KAAK,CAAC,EAAK,KAAQ,CACnB,IAAM,EAAO,EAAA,eAAqB,EAAI,CACtC,MAAO,CACL,MACA,aAAc,GAAM,cAAgB,EACpC,gBAAiB,EAAO,CAAC,GAAG,EAAK,gBAAgB,CAAG,EAAE,CACtD,iBAAkB,GAAM,kBAAkB,IAAK,IAAW,CACxD,GAAI,EAAM,GACV,MAAO,EAAM,MACd,EAAE,CACH,cAAe,GAAM,eAAiB,IAAA,GACtC,gBAAiB,EAAG,gBACpB,KAAM,EAAG,KACV,EACD,CAoCc,EAAA,QAAQ,mCAIR,EAAA,QAAQ,oCAOR,EAAA,QAAQ,mCAIR,EAAA,QAAQ,oCA2BxB,SAAgB,EACd,EAC+B,CAC1B,KACL,OAAO,EAAc,KAAM,GAAa,EAAS,MAAQ,EAAI,CAmB/D,SAAgB,EACd,EACe,CACf,IAAM,EAAQ,EAAe,EAAI,CACjC,OAAO,EAAQ,EAAM,aAAe,KAYtC,SAAgB,EACd,EACiB,CACjB,OAAO,EAAe,EAAI,EAAE,MAAA,cAa9B,SAAgB,EACd,EACA,EACe,CAIf,OAHK,EACY,EAAe,EAClB,EAAU,kBAAkB,KAAM,GAAM,EAAE,KAAO,EAAQ,EACzD,OAAS,EAHF"}
@@ -79,6 +79,31 @@ export interface ACPModelOption {
79
79
  }
80
80
  export declare const ACP_PROVIDERS: ACPProviderConfig[];
81
81
  export declare const ACP_CUSTOM_PRESET_KEY = "custom";
82
+ /**
83
+ * A credential an ACP provider authenticates with, surfaced during onboarding
84
+ * so the user can populate it without leaving the flow. The {@link name} is
85
+ * both the global-secret name and the environment variable the agent-server
86
+ * exports into the ACP subprocess — keeping them identical is what makes a
87
+ * saved secret actually reach the provider CLI.
88
+ */
89
+ export interface ACPProviderSecretField {
90
+ /** Secret name and env var (e.g. ``"ANTHROPIC_API_KEY"``). Must satisfy the
91
+ * secret-name pattern ``^[a-zA-Z][a-zA-Z0-9_]{0,63}$``. */
92
+ name: string;
93
+ /** Render as a masked password input (API keys) rather than a plain-text
94
+ * input (base URLs). Every field is optional regardless — the whole step is
95
+ * skippable — so this only controls masking, not whether the field gates. */
96
+ secret?: boolean;
97
+ /** i18n key for the one-line helper text under the field. */
98
+ hint_key: I18nKey;
99
+ }
100
+ /**
101
+ * List the credentials Canvas should prompt for when onboarding the given ACP
102
+ * provider. Returns ``[]`` for OpenHands, the ``"custom"`` preset, providers
103
+ * that don't use a static API key (Gemini CLI), and any unknown key — callers
104
+ * treat an empty list as "no credentials step for this provider".
105
+ */
106
+ export declare function getAcpProviderSecrets(key: string | null | undefined): ACPProviderSecretField[];
82
107
  /**
83
108
  * Look up a built-in ACP provider config by its registry key.
84
109
  *
@@ -47,6 +47,7 @@ var a = {
47
47
  icon: n.icon
48
48
  };
49
49
  });
50
+ e.ONBOARDING$ACP_SECRET_API_KEY_HINT, e.ONBOARDING$ACP_SECRET_BASE_URL_HINT, e.ONBOARDING$ACP_SECRET_API_KEY_HINT, e.ONBOARDING$ACP_SECRET_BASE_URL_HINT;
50
51
  function s(e) {
51
52
  if (e) return o.find((t) => t.key === e);
52
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"acp-providers.js","names":[],"sources":["../../src/constants/acp-providers.ts"],"sourcesContent":["import { getAcpProvider as getClientAcpProvider } from \"@openhands/typescript-client\";\nimport { I18nKey } from \"#/i18n/declaration\";\n\nexport type ACPProviderIcon =\n | \"claude-code\"\n | \"codex\"\n | \"gemini\"\n | \"cli-generic\";\n\nexport const ACP_PROVIDER_FALLBACK_ICON: ACPProviderIcon = \"cli-generic\";\n\n// SDK placeholder strings the ACP wrapper returns before the user has\n// chosen a real model — surfacing either would lie about what's running.\nexport const ACP_DEFAULT_PLACEHOLDERS = new Set([\n \"default\",\n \"default (recommended)\",\n]);\n\n// Sentinel ``agent.llm.model`` returned by older SDKs for ACP conversations\n// in lieu of a real model. Suppressed at every consumer that resolves a\n// display string.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty, not the SDK's \"default\"\n * placeholder, not the legacy ``acp-managed`` sentinel. Returns the trimmed\n * value on success, ``null`` otherwise.\n */\nfunction realAcpModel(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n if (ACP_DEFAULT_PLACEHOLDERS.has(trimmed.toLowerCase())) return null;\n if (trimmed === ACP_MANAGED_SENTINEL) return null;\n return trimmed;\n}\n\n/**\n * Single source of truth for resolving the model string to surface for an\n * ACP conversation/settings context. Consumed by the conversation adapter\n * (chip text), the conversation-creation path (concrete ``acp_model``\n * payload), the Settings → Agent form (initial value), and the chat-input\n * model label.\n *\n * Precedence: SDK runtime fields → user-configured ``acp_model`` →\n * legacy ``agent.llm.model`` → provider default (when ``providerDefault``\n * is passed). Pass ``providerDefault`` only on surfaces that should\n * silently substitute the registry default; omit it for the conversation\n * chip, which must distinguish \"no concrete model\" from \"default\".\n */\nexport function resolveEffectiveAcpModel(inputs: {\n runtimeName?: string | null;\n runtimeId?: string | null;\n configured?: string | null;\n sdkLlm?: string | null;\n providerDefault?: string | null;\n}): string | null {\n for (const candidate of [\n inputs.runtimeName,\n inputs.runtimeId,\n inputs.configured,\n inputs.sdkLlm,\n ]) {\n const value = realAcpModel(candidate);\n if (value) return value;\n }\n return inputs.providerDefault ?? null;\n}\n\n/**\n * Shape of a built-in ACP (Agent Client Protocol) provider as Canvas consumes\n * it. The data fields (display name, launch command, model picker + default)\n * are sourced at module load from ``@openhands/typescript-client``'s ACP\n * registry — the generated mirror of the Python source of truth\n * ``openhands.sdk.settings.acp_providers``. This config only adds the\n * Canvas-specific UI fields ({@link ACPProviderConfig.icon} +\n * {@link ACPProviderConfig.description_key}); see {@link ACP_PROVIDER_UI}.\n */\nexport interface ACPProviderConfig {\n /** Stable registry key, also stored on conversations as ``tags.acpserver``. */\n key: string;\n /** Human-readable name shown in dropdowns and conversation chips. */\n display_name: string;\n /**\n * Tokens passed to the agent-server as ``acp_command`` when this preset\n * is picked. Each entry must be a real ACP-protocol stdio server — the\n * SDK validates this against the {@link ACPProviderConfig.key}.\n *\n * NB: ``npx -y @openai/codex acp`` looks plausible but is **not** an\n * ACP server — the codex CLI has no ``acp`` subcommand and exits with\n * ``Error: stdin is not a terminal`` when spawned without a TTY, which\n * silently deadlocks the agent-server's ACP handshake. Use\n * ``@zed-industries/codex-acp`` (the Zed-shipped wrapper) instead.\n */\n default_command: string[];\n /**\n * Suggested ACP model IDs for the provider's picker, sourced from the\n * typescript-client registry. Not authoritative access checks; users can\n * still enter a custom override in Settings -> Agent.\n */\n available_models?: ACPModelOption[];\n /** Model ID preselected for built-in providers so Canvas never saves blank. */\n default_model?: string;\n /**\n * i18n key for the one-line provider description rendered under the\n * onboarding tile. Stored on the registry so adding a new ACP\n * provider only requires editing this file (not the onboarding tile\n * list separately).\n */\n description_key: I18nKey;\n /**\n * Serializable icon key used by UI surfaces that render provider\n * choices. Kept as a string so the SDK mirror check can continue to\n * parse this registry without importing React components.\n */\n icon?: ACPProviderIcon;\n}\n\nexport interface ACPModelOption {\n /** Exact model ID sent as ``acp_model``. */\n id: string;\n /** Human-readable label shown in Settings -> Agent. */\n label: string;\n}\n\n// Canvas-only UI metadata per built-in provider, keyed by the ACP registry\n// key. Everything else — display name, launch command, model picker list and\n// default — comes from the typescript-client registry below. Adding a model\n// or a provider happens upstream in the SDK; Canvas only owns the brand icon\n// and the onboarding-tile description here. A provider with no entry here is\n// intentionally not surfaced in the UI.\nconst ACP_PROVIDER_UI: Record<\n string,\n { icon: ACPProviderIcon; description_key: I18nKey }\n> = {\n \"claude-code\": {\n icon: \"claude-code\",\n description_key: I18nKey.ONBOARDING$AGENT_CLAUDE_CODE_DESCRIPTION,\n },\n codex: {\n icon: \"codex\",\n description_key: I18nKey.ONBOARDING$AGENT_CODEX_DESCRIPTION,\n },\n \"gemini-cli\": {\n icon: \"gemini\",\n description_key: I18nKey.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION,\n },\n};\n\n// Built-in ACP providers Canvas surfaces, built by enriching each upstream\n// registry record (``@openhands/typescript-client`` → Python SDK) with the\n// Canvas UI metadata above. Model lists + defaults are no longer hand-kept\n// here (closes agent-canvas#740) — they track the SDK via the pinned client.\nexport const ACP_PROVIDERS: ACPProviderConfig[] = Object.entries(\n ACP_PROVIDER_UI,\n).map(([key, ui]) => {\n const info = getClientAcpProvider(key);\n return {\n key,\n display_name: info?.display_name ?? key,\n default_command: info ? [...info.default_command] : [],\n available_models: info?.available_models?.map((model) => ({\n id: model.id,\n label: model.label,\n })),\n default_model: info?.default_model ?? undefined,\n description_key: ui.description_key,\n icon: ui.icon,\n };\n});\n\nexport const ACP_CUSTOM_PRESET_KEY = \"custom\";\n\n/**\n * Look up a built-in ACP provider config by its registry key.\n *\n * Returns ``undefined`` for an empty / null key, for the ``\"custom\"`` preset\n * (which has no registry entry), and for any forward-compatible key Canvas's\n * registry doesn't know about yet. Centralizes the ``ACP_PROVIDERS.find(...)``\n * lookup shared by the resolvers below and by the adapter / settings surfaces\n * so the key-comparison shape lives in one place.\n */\nexport function getAcpProvider(\n key: string | null | undefined,\n): ACPProviderConfig | undefined {\n if (!key) return undefined;\n return ACP_PROVIDERS.find((provider) => provider.key === key);\n}\n\n/**\n * Resolve an ACP provider registry key (the value stored under\n * ``tags.acpserver`` on a conversation) to a human display name for the\n * sidebar chip.\n *\n * Returns ``null`` for an empty / null key and for keys not in\n * {@link ACP_PROVIDERS} — most notably ``\"custom\"`` (the user-supplied\n * command preset has no canonical brand name) and any forward-compatible\n * value Canvas's registry doesn't know about yet. Callers should fall\n * back to a generic ``\"ACP\"`` label in that case so the chip still\n * communicates \"this is an ACP conversation\".\n *\n * Kept separate from {@link buildAcpAgentSettingsDiff}'s lookup so the\n * conversation-card render path can resolve display names without\n * importing the settings-payload builder.\n */\nexport function getAcpProviderDisplayName(\n key: string | null | undefined,\n): string | null {\n const found = getAcpProvider(key);\n return found ? found.display_name : null;\n}\n\n/**\n * Resolve an ACP provider registry key to the icon discriminator the\n * conversation chip should render alongside the model text.\n *\n * Falls back to {@link ACP_PROVIDER_FALLBACK_ICON} for ``\"custom\"``,\n * unknown keys, or a missing key — the chip then shows a neutral\n * terminal glyph that still communicates \"this is an ACP conversation\"\n * without claiming a brand identity we don't know.\n */\nexport function resolveAcpProviderIcon(\n key: string | null | undefined,\n): ACPProviderIcon {\n return getAcpProvider(key)?.icon ?? ACP_PROVIDER_FALLBACK_ICON;\n}\n\n/**\n * Resolve a raw ``acp_model`` ID to the human-readable label the provider's\n * picker shows for it (e.g. ``\"claude-opus-4-7\"`` → ``\"Claude Opus 4.7\"``).\n *\n * Falls back to the raw ID when the provider is unknown or the ID isn't one\n * of its registered {@link ACPModelOption}s — so a user's custom override\n * still renders something meaningful rather than nothing. Returns ``null``\n * only when there is no model to show, letting the conversation chip decide\n * to display the provider name instead.\n */\nexport function labelForAcpModel(\n serverKey: string | null | undefined,\n modelId: string | null | undefined,\n): string | null {\n if (!modelId) return null;\n const provider = getAcpProvider(serverKey);\n const match = provider?.available_models?.find((m) => m.id === modelId);\n return match?.label ?? modelId;\n}\n\n/**\n * Build the ``agent_settings_diff`` payload PATCH /api/settings expects\n * for the agent-kind/provider choice the user just made.\n *\n * Used by both the Settings → Agent page and the onboarding \"choose\n * agent\" step — keeping the shape in one helper means a future change\n * (e.g. always seeding ``acp_command`` from the registry instead of\n * sending ``[]``, or adding new ``acp_*`` reset fields) lands in both\n * surfaces atomically.\n *\n * Returns ``null`` for an unknown ACP provider key by default — the\n * caller can skip the save (the UI shouldn't surface unknown options,\n * but the defensive path keeps a buggy preset list from corrupting\n * settings).\n *\n * Pass ``allowUnknownServer: true`` to opt into pass-through for keys\n * that aren't in {@link ACP_PROVIDERS} or ``ACP_CUSTOM_PRESET_KEY``.\n * The Settings → Agent page uses this when the user opens settings\n * that already carry an ``acp_server`` value canvas's registry\n * doesn't know about (e.g. set out-of-band via the API for a provider\n * we haven't mirrored yet) and saves without changing the command —\n * otherwise the original key would be silently demoted to ``\"custom\"``.\n */\nexport function buildAcpAgentSettingsDiff(\n providerKey: string,\n options: {\n command?: string[];\n model?: string | null;\n allowUnknownServer?: boolean;\n } = {},\n): Record<string, unknown> | null {\n if (providerKey === \"openhands\") {\n // Switching back to OpenHands. The agent-server's ``Settings.update``\n // applies a fresh ``{'agent_kind': ...}`` base whenever the kind\n // flips, so any ``acp_*`` fields would be discarded before\n // validation. Send the kind alone.\n return { agent_kind: \"openhands\" };\n }\n\n const isCustom = providerKey === ACP_CUSTOM_PRESET_KEY;\n const provider = isCustom ? undefined : getAcpProvider(providerKey);\n if (!isCustom && !provider && !options.allowUnknownServer) {\n return null;\n }\n\n const model =\n options.model === undefined\n ? (provider?.default_model ?? null)\n : options.model;\n\n // ``acp_args: []`` resets any API-set ``acp_args`` that would\n // otherwise survive and concatenate to ``acp_command`` at spawn time\n // (the agent-server merges the two before exec). Callers building the\n // payload from a textarea that already shows the merged command\n // (Settings → Agent) round-trip correctly — the merged tokens land in\n // ``acp_command`` here, so no args are lost.\n return {\n agent_kind: \"acp\",\n acp_server: providerKey,\n acp_command: options.command ?? [],\n acp_args: [],\n acp_model: model ?? null,\n };\n}\n"],"mappings":";;;AAaA,IAAa,IAA2B,IAAI,IAAI,CAC9C,WACA,wBACD,CAAC;AAYF,SAAS,EAAa,GAA+B;AACnD,KAAI,OAAO,KAAU,SAAU,QAAO;CACtC,IAAM,IAAU,EAAM,MAAM;AAI5B,QAHI,CAAC,KACD,EAAyB,IAAI,EAAQ,aAAa,CAAC,IACnD,MAAA,gBAAyC,OACtC;;AAgBT,SAAgB,EAAyB,GAMvB;AAChB,MAAK,IAAM,KAAa;EACtB,EAAO;EACP,EAAO;EACP,EAAO;EACP,EAAO;EACR,EAAE;EACD,IAAM,IAAQ,EAAa,EAAU;AACrC,MAAI,EAAO,QAAO;;AAEpB,QAAO,EAAO,mBAAmB;;AAiEnC,IAAM,IAGF;CACF,eAAe;EACb,MAAM;EACN,iBAAiB,EAAQ;EAC1B;CACD,OAAO;EACL,MAAM;EACN,iBAAiB,EAAQ;EAC1B;CACD,cAAc;EACZ,MAAM;EACN,iBAAiB,EAAQ;EAC1B;CACF,EAMY,IAAqC,OAAO,QACvD,EACD,CAAC,KAAK,CAAC,GAAK,OAAQ;CACnB,IAAM,IAAO,EAAqB,EAAI;AACtC,QAAO;EACL;EACA,cAAc,GAAM,gBAAgB;EACpC,iBAAiB,IAAO,CAAC,GAAG,EAAK,gBAAgB,GAAG,EAAE;EACtD,kBAAkB,GAAM,kBAAkB,KAAK,OAAW;GACxD,IAAI,EAAM;GACV,OAAO,EAAM;GACd,EAAE;EACH,eAAe,GAAM,iBAAiB,KAAA;EACtC,iBAAiB,EAAG;EACpB,MAAM,EAAG;EACV;EACD;AAaF,SAAgB,EACd,GAC+B;AAC1B,OACL,QAAO,EAAc,MAAM,MAAa,EAAS,QAAQ,EAAI;;AAmB/D,SAAgB,EACd,GACe;CACf,IAAM,IAAQ,EAAe,EAAI;AACjC,QAAO,IAAQ,EAAM,eAAe;;AAYtC,SAAgB,EACd,GACiB;AACjB,QAAO,EAAe,EAAI,EAAE,QAAA;;AAa9B,SAAgB,EACd,GACA,GACe;AAIf,QAHK,IACY,EAAe,EAClB,EAAU,kBAAkB,MAAM,MAAM,EAAE,OAAO,EAAQ,EACzD,SAAS,IAHF"}
1
+ {"version":3,"file":"acp-providers.js","names":[],"sources":["../../src/constants/acp-providers.ts"],"sourcesContent":["import { getAcpProvider as getClientAcpProvider } from \"@openhands/typescript-client\";\nimport { I18nKey } from \"#/i18n/declaration\";\n\nexport type ACPProviderIcon =\n | \"claude-code\"\n | \"codex\"\n | \"gemini\"\n | \"cli-generic\";\n\nexport const ACP_PROVIDER_FALLBACK_ICON: ACPProviderIcon = \"cli-generic\";\n\n// SDK placeholder strings the ACP wrapper returns before the user has\n// chosen a real model — surfacing either would lie about what's running.\nexport const ACP_DEFAULT_PLACEHOLDERS = new Set([\n \"default\",\n \"default (recommended)\",\n]);\n\n// Sentinel ``agent.llm.model`` returned by older SDKs for ACP conversations\n// in lieu of a real model. Suppressed at every consumer that resolves a\n// display string.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty, not the SDK's \"default\"\n * placeholder, not the legacy ``acp-managed`` sentinel. Returns the trimmed\n * value on success, ``null`` otherwise.\n */\nfunction realAcpModel(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n if (ACP_DEFAULT_PLACEHOLDERS.has(trimmed.toLowerCase())) return null;\n if (trimmed === ACP_MANAGED_SENTINEL) return null;\n return trimmed;\n}\n\n/**\n * Single source of truth for resolving the model string to surface for an\n * ACP conversation/settings context. Consumed by the conversation adapter\n * (chip text), the conversation-creation path (concrete ``acp_model``\n * payload), the Settings → Agent form (initial value), and the chat-input\n * model label.\n *\n * Precedence: SDK runtime fields → user-configured ``acp_model`` →\n * legacy ``agent.llm.model`` → provider default (when ``providerDefault``\n * is passed). Pass ``providerDefault`` only on surfaces that should\n * silently substitute the registry default; omit it for the conversation\n * chip, which must distinguish \"no concrete model\" from \"default\".\n */\nexport function resolveEffectiveAcpModel(inputs: {\n runtimeName?: string | null;\n runtimeId?: string | null;\n configured?: string | null;\n sdkLlm?: string | null;\n providerDefault?: string | null;\n}): string | null {\n for (const candidate of [\n inputs.runtimeName,\n inputs.runtimeId,\n inputs.configured,\n inputs.sdkLlm,\n ]) {\n const value = realAcpModel(candidate);\n if (value) return value;\n }\n return inputs.providerDefault ?? null;\n}\n\n/**\n * Shape of a built-in ACP (Agent Client Protocol) provider as Canvas consumes\n * it. The data fields (display name, launch command, model picker + default)\n * are sourced at module load from ``@openhands/typescript-client``'s ACP\n * registry — the generated mirror of the Python source of truth\n * ``openhands.sdk.settings.acp_providers``. This config only adds the\n * Canvas-specific UI fields ({@link ACPProviderConfig.icon} +\n * {@link ACPProviderConfig.description_key}); see {@link ACP_PROVIDER_UI}.\n */\nexport interface ACPProviderConfig {\n /** Stable registry key, also stored on conversations as ``tags.acpserver``. */\n key: string;\n /** Human-readable name shown in dropdowns and conversation chips. */\n display_name: string;\n /**\n * Tokens passed to the agent-server as ``acp_command`` when this preset\n * is picked. Each entry must be a real ACP-protocol stdio server — the\n * SDK validates this against the {@link ACPProviderConfig.key}.\n *\n * NB: ``npx -y @openai/codex acp`` looks plausible but is **not** an\n * ACP server — the codex CLI has no ``acp`` subcommand and exits with\n * ``Error: stdin is not a terminal`` when spawned without a TTY, which\n * silently deadlocks the agent-server's ACP handshake. Use\n * ``@zed-industries/codex-acp`` (the Zed-shipped wrapper) instead.\n */\n default_command: string[];\n /**\n * Suggested ACP model IDs for the provider's picker, sourced from the\n * typescript-client registry. Not authoritative access checks; users can\n * still enter a custom override in Settings -> Agent.\n */\n available_models?: ACPModelOption[];\n /** Model ID preselected for built-in providers so Canvas never saves blank. */\n default_model?: string;\n /**\n * i18n key for the one-line provider description rendered under the\n * onboarding tile. Stored on the registry so adding a new ACP\n * provider only requires editing this file (not the onboarding tile\n * list separately).\n */\n description_key: I18nKey;\n /**\n * Serializable icon key used by UI surfaces that render provider\n * choices. Kept as a string so the SDK mirror check can continue to\n * parse this registry without importing React components.\n */\n icon?: ACPProviderIcon;\n}\n\nexport interface ACPModelOption {\n /** Exact model ID sent as ``acp_model``. */\n id: string;\n /** Human-readable label shown in Settings -> Agent. */\n label: string;\n}\n\n// Canvas-only UI metadata per built-in provider, keyed by the ACP registry\n// key. Everything else — display name, launch command, model picker list and\n// default — comes from the typescript-client registry below. Adding a model\n// or a provider happens upstream in the SDK; Canvas only owns the brand icon\n// and the onboarding-tile description here. A provider with no entry here is\n// intentionally not surfaced in the UI.\nconst ACP_PROVIDER_UI: Record<\n string,\n { icon: ACPProviderIcon; description_key: I18nKey }\n> = {\n \"claude-code\": {\n icon: \"claude-code\",\n description_key: I18nKey.ONBOARDING$AGENT_CLAUDE_CODE_DESCRIPTION,\n },\n codex: {\n icon: \"codex\",\n description_key: I18nKey.ONBOARDING$AGENT_CODEX_DESCRIPTION,\n },\n \"gemini-cli\": {\n icon: \"gemini\",\n description_key: I18nKey.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION,\n },\n};\n\n// Built-in ACP providers Canvas surfaces, built by enriching each upstream\n// registry record (``@openhands/typescript-client`` → Python SDK) with the\n// Canvas UI metadata above. Model lists + defaults are no longer hand-kept\n// here (closes agent-canvas#740) — they track the SDK via the pinned client.\nexport const ACP_PROVIDERS: ACPProviderConfig[] = Object.entries(\n ACP_PROVIDER_UI,\n).map(([key, ui]) => {\n const info = getClientAcpProvider(key);\n return {\n key,\n display_name: info?.display_name ?? key,\n default_command: info ? [...info.default_command] : [],\n available_models: info?.available_models?.map((model) => ({\n id: model.id,\n label: model.label,\n })),\n default_model: info?.default_model ?? undefined,\n description_key: ui.description_key,\n icon: ui.icon,\n };\n});\n\nexport const ACP_CUSTOM_PRESET_KEY = \"custom\";\n\n/**\n * A credential an ACP provider authenticates with, surfaced during onboarding\n * so the user can populate it without leaving the flow. The {@link name} is\n * both the global-secret name and the environment variable the agent-server\n * exports into the ACP subprocess — keeping them identical is what makes a\n * saved secret actually reach the provider CLI.\n */\nexport interface ACPProviderSecretField {\n /** Secret name and env var (e.g. ``\"ANTHROPIC_API_KEY\"``). Must satisfy the\n * secret-name pattern ``^[a-zA-Z][a-zA-Z0-9_]{0,63}$``. */\n name: string;\n /** Render as a masked password input (API keys) rather than a plain-text\n * input (base URLs). Every field is optional regardless — the whole step is\n * skippable — so this only controls masking, not whether the field gates. */\n secret?: boolean;\n /** i18n key for the one-line helper text under the field. */\n hint_key: I18nKey;\n}\n\n// Credentials Canvas prompts for during onboarding, keyed by ACP registry key.\n// Only providers that authenticate through an env-var API key appear here:\n// Claude Code (Anthropic) and Codex (OpenAI). Gemini CLI authenticates via an\n// interactive OAuth login rather than a static key, so it has no entry and its\n// onboarding credentials step is skipped. Every field is optional (the step is\n// skippable): the API keys render masked, the base-URL entries are plain-text\n// overrides for proxies/gateways. A provider with no entry simply shows no\n// credentials step.\nconst ACP_PROVIDER_SECRETS: Record<string, ACPProviderSecretField[]> = {\n \"claude-code\": [\n {\n name: \"ANTHROPIC_API_KEY\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n },\n {\n name: \"ANTHROPIC_BASE_URL\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n },\n ],\n codex: [\n {\n name: \"OPENAI_API_KEY\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n },\n {\n name: \"OPENAI_BASE_URL\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n },\n ],\n};\n\n/**\n * List the credentials Canvas should prompt for when onboarding the given ACP\n * provider. Returns ``[]`` for OpenHands, the ``\"custom\"`` preset, providers\n * that don't use a static API key (Gemini CLI), and any unknown key — callers\n * treat an empty list as \"no credentials step for this provider\".\n */\nexport function getAcpProviderSecrets(\n key: string | null | undefined,\n): ACPProviderSecretField[] {\n if (!key) return [];\n return ACP_PROVIDER_SECRETS[key] ?? [];\n}\n\n/**\n * Look up a built-in ACP provider config by its registry key.\n *\n * Returns ``undefined`` for an empty / null key, for the ``\"custom\"`` preset\n * (which has no registry entry), and for any forward-compatible key Canvas's\n * registry doesn't know about yet. Centralizes the ``ACP_PROVIDERS.find(...)``\n * lookup shared by the resolvers below and by the adapter / settings surfaces\n * so the key-comparison shape lives in one place.\n */\nexport function getAcpProvider(\n key: string | null | undefined,\n): ACPProviderConfig | undefined {\n if (!key) return undefined;\n return ACP_PROVIDERS.find((provider) => provider.key === key);\n}\n\n/**\n * Resolve an ACP provider registry key (the value stored under\n * ``tags.acpserver`` on a conversation) to a human display name for the\n * sidebar chip.\n *\n * Returns ``null`` for an empty / null key and for keys not in\n * {@link ACP_PROVIDERS} — most notably ``\"custom\"`` (the user-supplied\n * command preset has no canonical brand name) and any forward-compatible\n * value Canvas's registry doesn't know about yet. Callers should fall\n * back to a generic ``\"ACP\"`` label in that case so the chip still\n * communicates \"this is an ACP conversation\".\n *\n * Kept separate from {@link buildAcpAgentSettingsDiff}'s lookup so the\n * conversation-card render path can resolve display names without\n * importing the settings-payload builder.\n */\nexport function getAcpProviderDisplayName(\n key: string | null | undefined,\n): string | null {\n const found = getAcpProvider(key);\n return found ? found.display_name : null;\n}\n\n/**\n * Resolve an ACP provider registry key to the icon discriminator the\n * conversation chip should render alongside the model text.\n *\n * Falls back to {@link ACP_PROVIDER_FALLBACK_ICON} for ``\"custom\"``,\n * unknown keys, or a missing key — the chip then shows a neutral\n * terminal glyph that still communicates \"this is an ACP conversation\"\n * without claiming a brand identity we don't know.\n */\nexport function resolveAcpProviderIcon(\n key: string | null | undefined,\n): ACPProviderIcon {\n return getAcpProvider(key)?.icon ?? ACP_PROVIDER_FALLBACK_ICON;\n}\n\n/**\n * Resolve a raw ``acp_model`` ID to the human-readable label the provider's\n * picker shows for it (e.g. ``\"claude-opus-4-7\"`` → ``\"Claude Opus 4.7\"``).\n *\n * Falls back to the raw ID when the provider is unknown or the ID isn't one\n * of its registered {@link ACPModelOption}s — so a user's custom override\n * still renders something meaningful rather than nothing. Returns ``null``\n * only when there is no model to show, letting the conversation chip decide\n * to display the provider name instead.\n */\nexport function labelForAcpModel(\n serverKey: string | null | undefined,\n modelId: string | null | undefined,\n): string | null {\n if (!modelId) return null;\n const provider = getAcpProvider(serverKey);\n const match = provider?.available_models?.find((m) => m.id === modelId);\n return match?.label ?? modelId;\n}\n\n/**\n * Build the ``agent_settings_diff`` payload PATCH /api/settings expects\n * for the agent-kind/provider choice the user just made.\n *\n * Used by both the Settings → Agent page and the onboarding \"choose\n * agent\" step — keeping the shape in one helper means a future change\n * (e.g. always seeding ``acp_command`` from the registry instead of\n * sending ``[]``, or adding new ``acp_*`` reset fields) lands in both\n * surfaces atomically.\n *\n * Returns ``null`` for an unknown ACP provider key by default — the\n * caller can skip the save (the UI shouldn't surface unknown options,\n * but the defensive path keeps a buggy preset list from corrupting\n * settings).\n *\n * Pass ``allowUnknownServer: true`` to opt into pass-through for keys\n * that aren't in {@link ACP_PROVIDERS} or ``ACP_CUSTOM_PRESET_KEY``.\n * The Settings → Agent page uses this when the user opens settings\n * that already carry an ``acp_server`` value canvas's registry\n * doesn't know about (e.g. set out-of-band via the API for a provider\n * we haven't mirrored yet) and saves without changing the command —\n * otherwise the original key would be silently demoted to ``\"custom\"``.\n */\nexport function buildAcpAgentSettingsDiff(\n providerKey: string,\n options: {\n command?: string[];\n model?: string | null;\n allowUnknownServer?: boolean;\n } = {},\n): Record<string, unknown> | null {\n if (providerKey === \"openhands\") {\n // Switching back to OpenHands. The agent-server's ``Settings.update``\n // applies a fresh ``{'agent_kind': ...}`` base whenever the kind\n // flips, so any ``acp_*`` fields would be discarded before\n // validation. Send the kind alone.\n return { agent_kind: \"openhands\" };\n }\n\n const isCustom = providerKey === ACP_CUSTOM_PRESET_KEY;\n const provider = isCustom ? undefined : getAcpProvider(providerKey);\n if (!isCustom && !provider && !options.allowUnknownServer) {\n return null;\n }\n\n const model =\n options.model === undefined\n ? (provider?.default_model ?? null)\n : options.model;\n\n // ``acp_args: []`` resets any API-set ``acp_args`` that would\n // otherwise survive and concatenate to ``acp_command`` at spawn time\n // (the agent-server merges the two before exec). Callers building the\n // payload from a textarea that already shows the merged command\n // (Settings → Agent) round-trip correctly — the merged tokens land in\n // ``acp_command`` here, so no args are lost.\n return {\n agent_kind: \"acp\",\n acp_server: providerKey,\n acp_command: options.command ?? [],\n acp_args: [],\n acp_model: model ?? null,\n };\n}\n"],"mappings":";;;AAaA,IAAa,IAA2B,IAAI,IAAI,CAC9C,WACA,wBACD,CAAC;AAYF,SAAS,EAAa,GAA+B;AACnD,KAAI,OAAO,KAAU,SAAU,QAAO;CACtC,IAAM,IAAU,EAAM,MAAM;AAI5B,QAHI,CAAC,KACD,EAAyB,IAAI,EAAQ,aAAa,CAAC,IACnD,MAAA,gBAAyC,OACtC;;AAgBT,SAAgB,EAAyB,GAMvB;AAChB,MAAK,IAAM,KAAa;EACtB,EAAO;EACP,EAAO;EACP,EAAO;EACP,EAAO;EACR,EAAE;EACD,IAAM,IAAQ,EAAa,EAAU;AACrC,MAAI,EAAO,QAAO;;AAEpB,QAAO,EAAO,mBAAmB;;AAiEnC,IAAM,IAGF;CACF,eAAe;EACb,MAAM;EACN,iBAAiB,EAAQ;EAC1B;CACD,OAAO;EACL,MAAM;EACN,iBAAiB,EAAQ;EAC1B;CACD,cAAc;EACZ,MAAM;EACN,iBAAiB,EAAQ;EAC1B;CACF,EAMY,IAAqC,OAAO,QACvD,EACD,CAAC,KAAK,CAAC,GAAK,OAAQ;CACnB,IAAM,IAAO,EAAqB,EAAI;AACtC,QAAO;EACL;EACA,cAAc,GAAM,gBAAgB;EACpC,iBAAiB,IAAO,CAAC,GAAG,EAAK,gBAAgB,GAAG,EAAE;EACtD,kBAAkB,GAAM,kBAAkB,KAAK,OAAW;GACxD,IAAI,EAAM;GACV,OAAO,EAAM;GACd,EAAE;EACH,eAAe,GAAM,iBAAiB,KAAA;EACtC,iBAAiB,EAAG;EACpB,MAAM,EAAG;EACV;EACD;AAoCc,EAAQ,oCAIR,EAAQ,qCAOR,EAAQ,oCAIR,EAAQ;AA2BxB,SAAgB,EACd,GAC+B;AAC1B,OACL,QAAO,EAAc,MAAM,MAAa,EAAS,QAAQ,EAAI;;AAmB/D,SAAgB,EACd,GACe;CACf,IAAM,IAAQ,EAAe,EAAI;AACjC,QAAO,IAAQ,EAAM,eAAe;;AAYtC,SAAgB,EACd,GACiB;AACjB,QAAO,EAAe,EAAI,EAAE,QAAA;;AAa9B,SAAgB,EACd,GACA,GACe;AAIf,QAHK,IACY,EAAe,EAClB,EAAU,kBAAkB,MAAM,MAAM,EAAE,OAAO,EAAQ,EACzD,SAAS,IAHF"}