@openhands/agent-canvas 1.18.0 → 1.20.0

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 (420) hide show
  1. package/README.md +1 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/acp-providers-DOKcGeaV.js +1 -0
  4. package/build/assets/active-backend-context-BETEdaJl.js +2 -0
  5. package/build/assets/{agent-context-settings-D3peZkjX.js → agent-context-settings-Dir0loAn.js} +1 -1
  6. package/build/assets/{agent-profiles-service.api-DkeZaqIl.js → agent-profiles-service.api-CiMVQAIT.js} +1 -1
  7. package/build/assets/{agent-profiles-settings-7yMWYjGF.js → agent-profiles-settings-CT5owzRT.js} +1 -1
  8. package/build/assets/agent-server-conversation-service.api-BtOlitCD.js +88 -0
  9. package/build/assets/agent-settings-BnzyTgCb.js +2 -0
  10. package/build/assets/agent-settings-CeXj3xK-.js +1 -0
  11. package/build/assets/{alert-banner-eYyqvHL3.js → alert-banner-Ccx1Du6n.js} +1 -1
  12. package/build/assets/{api-key-entry-screen-CCBvBD8u.js → api-key-entry-screen-DXIuzBjY.js} +1 -1
  13. package/build/assets/{app-settings-BtYDs-Be.js → app-settings-DkIJVTKL.js} +1 -1
  14. package/build/assets/automation-detail-CF2SweVs.js +3 -0
  15. package/build/assets/automation-disabled-reason-QFtMrdOr.js +1 -0
  16. package/build/assets/{automation-export-BJsfi7n2.js → automation-export-CkvehhPF.js} +1 -1
  17. package/build/assets/{automation-git-sync-B7o_Nbl-.js → automation-git-sync-di4tBAyZ.js} +1 -1
  18. package/build/assets/automation-setup-route-FERj7WEe.js +3 -0
  19. package/build/assets/{automation-templates-Bb98r-kx.js → automation-templates-DlaCRvD-.js} +1 -1
  20. package/build/assets/automations-list-yK7ETmYP.js +1 -0
  21. package/build/assets/{back-nav-button-B2yBiVMB.js → back-nav-button-oDvIxpQ5.js} +1 -1
  22. package/build/assets/backend-form-modal-DzYf_arO.js +1 -0
  23. package/build/assets/{backend-synced-settings-badge-DLEp6cq7.js → backend-synced-settings-badge-DXDDHWX4.js} +1 -1
  24. package/build/assets/{badge-ehL-kgeO.js → badge-CDHZEVeN.js} +1 -1
  25. package/build/assets/{base-modal-C1a0TsSx.js → base-modal-CZq4kB_H.js} +1 -1
  26. package/build/assets/{brand-button-CUMfjb99.js → brand-button-CGUwj6zK.js} +1 -1
  27. package/build/assets/{browser-SE24Tk8g.js → browser-kY6jN61T.js} +2 -2
  28. package/build/assets/{browser-tab-HjJxHA0V.js → browser-tab-DFyZXsnB.js} +1 -1
  29. package/build/assets/{canvas-extension-page-BsUGBb1T.js → canvas-extension-page-DsGq1DtT.js} +1 -1
  30. package/build/assets/{canvas-extensions-C9Tm6s_E.js → canvas-extensions-Cb2Z0Hdc.js} +1 -1
  31. package/build/assets/{canvas-extensions-runtime-B8kxM4rO.js → canvas-extensions-runtime-DS6eT4nK.js} +1 -1
  32. package/build/assets/{chat-send-button-CGvc2wpE.js → chat-send-button-B6aVaD9V.js} +1 -1
  33. package/build/assets/{circle-plus-check-toggle-BDX_ctNs.js → circle-plus-check-toggle-ZOXpJ3I2.js} +1 -1
  34. package/build/assets/{cloud-funnel-analytics-D5uJ_4JE.js → cloud-funnel-analytics-BH_zxjiO.js} +1 -1
  35. package/build/assets/{cog-CAEugqtV.js → cog-DVZT1f0s.js} +1 -1
  36. package/build/assets/{command-menu-CQxs4mtX.js → command-menu-ImeHQ6c2.js} +1 -1
  37. package/build/assets/{commits-tab-D0xgjipP.js → commits-tab-B5SUF66c.js} +1 -1
  38. package/build/assets/{condenser-settings-D63NJRBT.js → condenser-settings-BlGOXEXM.js} +1 -1
  39. package/build/assets/{confirmation-modal-78sd9Vny.js → confirmation-modal-oN1EId79.js} +1 -1
  40. package/build/assets/{context-menu-list-item-Cm7-7Y-7.js → context-menu-list-item-DIm5xQg6.js} +1 -1
  41. package/build/assets/{conversation-B_nweu5p.js → conversation-9a53tdbO.js} +4 -4
  42. package/build/assets/conversation-XRsI28ZM.js +1 -0
  43. package/build/assets/conversation-panel-XRsI28ZM.js +1 -0
  44. package/build/assets/{conversation-state-store-9D_3P7q4.js → conversation-state-store-Br-eE-gg.js} +1 -1
  45. package/build/assets/{conversation-tab-empty-state-B3dpJXX0.js → conversation-tab-empty-state-BT1GrnKP.js} +1 -1
  46. package/build/assets/{copy-to-clipboard-button-B1ljgNxO.js → copy-to-clipboard-button-CvTHqY13.js} +1 -1
  47. package/build/assets/{device-verify-D5QRIS_x.js → device-verify-DX0xdNjo.js} +1 -1
  48. package/build/assets/{dropdown-classes-BGvOvW2_.js → dropdown-classes-DyyMaaXl.js} +1 -1
  49. package/build/assets/edit-automation-modal-bfD-5NIc.js +1 -0
  50. package/build/assets/empty-state-B5advKVW.js +1 -0
  51. package/build/assets/{entry.client-BMafTpjO.js → entry.client-D_2gnSyB.js} +2 -2
  52. package/build/assets/{enum-filter-dropdown-UNaom12H.js → enum-filter-dropdown-VAJp5mqh.js} +1 -1
  53. package/build/assets/{environment-switch-overlay-COibRUA-.js → environment-switch-overlay-C7x_yhEs.js} +1 -1
  54. package/build/assets/{error-state-Cf8LRuuY.js → error-state-BgYl7YeB.js} +1 -1
  55. package/build/assets/{extensions-hub-C_6ahIua.js → extensions-hub-DjhOed1I.js} +1 -1
  56. package/build/assets/{extensions-navigation-C5GfpzFe.js → extensions-navigation-Di-J2KUR.js} +1 -1
  57. package/build/assets/{files-tab-D5PPIWI1.js → files-tab-BNDp3e_x.js} +1 -1
  58. package/build/assets/{form-control-classes-Dbq-7YP_.js → form-control-classes-BVI_szaj.js} +1 -1
  59. package/build/assets/{get-git-path-jU2zjqIB.js → get-git-path-DOe6PM_a.js} +1 -1
  60. package/build/assets/git-repo-dropdown-1fZGYtYm.js +1 -0
  61. package/build/assets/{git-repo-dropdown-uxlFw2gn.js → git-repo-dropdown-DNlinQ7r.js} +1 -1
  62. package/build/assets/{highlighted-source-view-C7pAXrT0.js → highlighted-source-view-DPerL6tF.js} +1 -1
  63. package/build/assets/home-4lyLzYjG.js +1 -0
  64. package/build/assets/home-BGaB0EeK.js +1 -0
  65. package/build/assets/home-automation-run-tooltip-v5n7zXR0.js +1 -0
  66. package/build/assets/{home-store-DCbei3DX.js → home-store-roz1x6D5.js} +1 -1
  67. package/build/assets/{hooks-service-Dnmhqc0Z.js → hooks-service-tzRqtAlm.js} +1 -1
  68. package/build/assets/index-home-6Y_Y7kOo.js +1 -0
  69. package/build/assets/{install-server-modal-B81I-5OE.js → install-server-modal-DkMCfj0J.js} +1 -1
  70. package/build/assets/{key-O84CMFHb.js → key-DpfBWQbk.js} +1 -1
  71. package/build/assets/{launch-ZLxI4opd.js → launch-TrnYlJe6.js} +1 -1
  72. package/build/assets/link-external-B4gx3EeM.js +1 -0
  73. package/build/assets/llm-not-configured-banner-BdjItKQR.js +27 -0
  74. package/build/assets/{llm-settings-CODhjQzy.js → llm-settings-CUgt39uu.js} +1 -1
  75. package/build/assets/llm-settings-DEfw-fUJ.js +1 -0
  76. package/build/assets/{llm-subscription-service-CgIDc38n.js → llm-subscription-service-LGYVzyAc.js} +1 -1
  77. package/build/assets/{loading-spinner-BeLV0IXf.js → loading-spinner-BJc2J0Dw.js} +1 -1
  78. package/build/assets/{manage-backends-modal-cMXqCb-B.js → manage-backends-modal-CIjk0B2H.js} +1 -1
  79. package/build/assets/manifest-23d79467.js +1 -0
  80. package/build/assets/{manifest-subpage-layout-CVZgVKoY.js → manifest-subpage-layout-DGYRoP_8.js} +1 -1
  81. package/build/assets/{markdown-renderer-DyFl9-mL.js → markdown-renderer-DRpwRlNm.js} +1 -1
  82. package/build/assets/mcp-CoUOIGBF.js +1 -0
  83. package/build/assets/mcp-installed-servers-BW51n6PC.js +1 -0
  84. package/build/assets/mcp-marketplace-utils-BwEnDYqm.js +1 -0
  85. package/build/assets/mcp-page-hZ4aJjj_.js +9 -0
  86. package/build/assets/{messages-Bk1xhbU2.js → messages-WUb9Yh-p.js} +4 -4
  87. package/build/assets/{modal-body-C24WsdFb.js → modal-body-B_95ci1c.js} +1 -1
  88. package/build/assets/{modal-classes-B0b9eiL8.js → modal-classes-BPcBCWVj.js} +1 -1
  89. package/build/assets/{modal-close-button-DfqfB2Nj.js → modal-close-button-Cq3dma5u.js} +1 -1
  90. package/build/assets/onboarding-Cc4Av7bi.js +1 -0
  91. package/build/assets/{onboarding-modal-BT3Gx_sZ.js → onboarding-modal-DCFD4Dsa.js} +1 -1
  92. package/build/assets/{organization-service.api-B5CvmnYQ.js → organization-service.api-zhHpQU0t.js} +1 -1
  93. package/build/assets/parse-git-remote-url-BG8pcKL8.js +1 -0
  94. package/build/assets/{planner-tab-CstoOlAB.js → planner-tab-yISBVUKu.js} +1 -1
  95. package/build/assets/{plugins-management-service-5rqVPEGN.js → plugins-management-service-2oP6_tWr.js} +1 -1
  96. package/build/assets/{profiles-service.api-D1XKMgr0.js → profiles-service.api-B5thiTI3.js} +1 -1
  97. package/build/assets/{providers-XmZ9HM4Q.js → providers-Co9PsD0s.js} +1 -1
  98. package/build/assets/{proxy-XP6U1Dhv.js → proxy-BCCQUna8.js} +1 -1
  99. package/build/assets/recommended-automations-launcher-D1Do753p.js +1 -0
  100. package/build/assets/root-D1y82Lhg.js +2 -0
  101. package/build/assets/{root-layout-DUquNZTU.js → root-layout-cQTYEG2w.js} +2 -2
  102. package/build/assets/root-sEWn3ha1.css +1 -0
  103. package/build/assets/{schema-field-Y3F8JvXd.js → schema-field-DSi0d7a8.js} +1 -1
  104. package/build/assets/{sdk-section-page-CNZTLwH9.js → sdk-section-page-DCoI2dtd.js} +1 -1
  105. package/build/assets/{secret-form-9-BoGA40.js → secret-form-BDvzojzN.js} +1 -1
  106. package/build/assets/{secrets-service-BOIsfD9j.js → secrets-service-C3CNNEpY.js} +1 -1
  107. package/build/assets/{secrets-settings-CBJgFr42.js → secrets-settings-BKqfYQt5.js} +1 -1
  108. package/build/assets/{settings-D4hdToD4.js → settings-C8ZLONyx.js} +1 -1
  109. package/build/assets/{settings-dropdown-input-DBvsZ7-Q.js → settings-dropdown-input-Df_HXRk4.js} +1 -1
  110. package/build/assets/{settings-index-DeQphyBK.js → settings-index-BWvlGHg3.js} +1 -1
  111. package/build/assets/{settings-input-dVuzapBA.js → settings-input-B3i5U9GL.js} +1 -1
  112. package/build/assets/{settings-list-classes-DhAPuXm6.js → settings-list-classes-DbqcSiVW.js} +1 -1
  113. package/build/assets/{settings-modal-tHiJ8iYV.js → settings-modal-DHKJSBPL.js} +1 -1
  114. package/build/assets/{settings-nav-GAQjW1a-.js → settings-nav-BThjwLWD.js} +1 -1
  115. package/build/assets/{settings-service.api-C1vZH9Vh.js → settings-service.api-CYBTLGOd.js} +1 -1
  116. package/build/assets/{settings-switch-DDd1cQ8t.js → settings-switch-CI93Iozg.js} +1 -1
  117. package/build/assets/{shared-conversation-Cyg7m7ns.js → shared-conversation-C7OILS-U.js} +1 -1
  118. package/build/assets/{sidebar-layout-DH0E-5KW.js → sidebar-layout-Csb6Geq0.js} +1 -1
  119. package/build/assets/{sidebar-mobile-menu-toggle-CQ4iky__.js → sidebar-mobile-menu-toggle-B2xwXm4s.js} +1 -1
  120. package/build/assets/{sidebar-nav-link-oLQiKUBu.js → sidebar-nav-link-Xv7QEMVS.js} +1 -1
  121. package/build/assets/{sidebar-onboarding-checklist-storage-TfvNNnnr.js → sidebar-onboarding-checklist-storage-_zxMqHS0.js} +1 -1
  122. package/build/assets/{skill-card-pill-row-DfjIfj0K.js → skill-card-pill-row-CxOFySng.js} +1 -1
  123. package/build/assets/{skill-enablement-DXeaImBz.js → skill-enablement-BBKya60C.js} +1 -1
  124. package/build/assets/{skill-filter-DsNLmn76.js → skill-filter-BvtbD--b.js} +1 -1
  125. package/build/assets/{skill-icon-badge-0MK_68Nc.js → skill-icon-badge-CVxE42yg.js} +1 -1
  126. package/build/assets/{skill-scope-BE-H9TSZ.js → skill-scope-C0NxJnpT.js} +1 -1
  127. package/build/assets/{skills-plugins-vzSdxSk_.js → skills-plugins-BGjX4jve.js} +1 -1
  128. package/build/assets/{skills-settings-BiPOH9H9.js → skills-settings-d3ZwIjSP.js} +1 -1
  129. package/build/assets/{styled-tooltip-CejG0rqv.js → styled-tooltip-CknPQH4Y.js} +1 -1
  130. package/build/assets/{task-list-tab-OkZSqnTp.js → task-list-tab-CQisRcPh.js} +1 -1
  131. package/build/assets/{terminal-Dph60VFZ.js → terminal-Bs50gJPC.js} +1 -1
  132. package/build/assets/{toggle-switch-B2uXaAUS.js → toggle-switch-D3TBxlsc.js} +1 -1
  133. package/build/assets/trash-D1QMHInX.js +1 -0
  134. package/build/assets/{typography-DgcLGdHk.js → typography-BdAApXGl.js} +1 -1
  135. package/build/assets/{usage-tab-MCmNRAnR.js → usage-tab-ClVHAFJy.js} +1 -1
  136. package/build/assets/{use-acp-credential-form-ychoGqjC.js → use-acp-credential-form-GfqcTJl5.js} +1 -1
  137. package/build/assets/{use-activate-agent-profile-8L8Xdghy.js → use-activate-agent-profile-DO6SNPkK.js} +1 -1
  138. package/build/assets/{use-agent-profiles-Bi8hSkze.js → use-agent-profiles-YsBlt3tT.js} +1 -1
  139. package/build/assets/{use-automation-health-U99vvHxx.js → use-automation-health-CbQ9n-k7.js} +1 -1
  140. package/build/assets/{use-automation-permissions-Buus0R6h.js → use-automation-permissions-gFy-2VD0.js} +1 -1
  141. package/build/assets/{use-automations-Bq5hVuv0.js → use-automations-7_XBXHQK.js} +1 -1
  142. package/build/assets/{use-can-manage-org-profiles-MzIeEV8F.js → use-can-manage-org-profiles-Dt4YkPO7.js} +1 -1
  143. package/build/assets/{use-canvas-extensions-Ca9KoVEm.js → use-canvas-extensions-BhYQrtMK.js} +1 -1
  144. package/build/assets/{use-cloud-current-user-id-B4ZLUZSa.js → use-cloud-current-user-id-Dqw6qQUg.js} +1 -1
  145. package/build/assets/{use-config-l5tkDHyD.js → use-config-BJ_FJCjV.js} +1 -1
  146. package/build/assets/use-create-conversation-D15Px574.js +1 -0
  147. package/build/assets/use-create-secret-D_qPU88t.js +1 -0
  148. package/build/assets/{use-free-models-gCsJ5BV0.js → use-free-models-FAk2nngq.js} +1 -1
  149. package/build/assets/{use-get-secrets-CMTuWEp_.js → use-get-secrets-Pbo9jh2A.js} +1 -1
  150. package/build/assets/{use-llm-configured-Dl4zg8ht.js → use-llm-configured-DF9BYmIe.js} +1 -1
  151. package/build/assets/{use-llm-profiles-SWsuFmFu.js → use-llm-profiles-U4u1gzuk.js} +1 -1
  152. package/build/assets/use-manifest-capabilities-D81_YdAF.js +1 -0
  153. package/build/assets/{use-onboarding-completion-Xl63ZvzQ.js → use-onboarding-completion-C4W-m2Jk.js} +1 -1
  154. package/build/assets/{use-pinned-home-route-BXDIUaxQ.js → use-pinned-home-route-DByWTojw.js} +1 -1
  155. package/build/assets/{use-plugins-marketplace-BIIaPiaB.js → use-plugins-marketplace-CTRoukH0.js} +1 -1
  156. package/build/assets/{use-save-agent-profile-D-ZHHE94.js → use-save-agent-profile-xpHBxMZM.js} +1 -1
  157. package/build/assets/{use-save-settings-Dapkf4cC.js → use-save-settings-CmX41wlf.js} +1 -1
  158. package/build/assets/{use-settings-C2KKQKVC.js → use-settings-D69cw06D.js} +1 -1
  159. package/build/assets/{use-settings-nav-items-TOc0Rsfd.js → use-settings-nav-items-CVz0SnDr.js} +1 -1
  160. package/build/assets/use-tracking-BAFpgpgq.js +3 -0
  161. package/build/assets/{use-user-conversation-5UFqMoO4.js → use-user-conversation-BBDXUbOc.js} +1 -1
  162. package/build/assets/utils-CbjwNcR-.js +1 -0
  163. package/build/assets/{vendor~browser-hbU4u2Ag.js → vendor~browser-BZ-zIfb6.js} +1 -1
  164. package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~b0x1wrp2-DvZtOcoT.js +2 -0
  165. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~mcp~automations-list~aut~ibfbmr9f-CxhwKuAd.js → vendor~root-layout~index-home~home~conversation-panel~conversation~mcp~automations-list~aut~ibfbmr9f-BIjRfQ4u.js} +1 -1
  166. package/build/assets/vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~inibp4qe-BBfdJIV9.js +48 -0
  167. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-3eJ4ExtV.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-fgzdgvrL.js} +3579 -165
  168. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~jz4f6vo2-C7HiY4Ae.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~jz4f6vo2-BqdU447s.js} +794 -227
  169. package/build/assets/{verification-settings-DdBpaVPz.js → verification-settings-BXspn27q.js} +1 -1
  170. package/build/index.html +4 -4
  171. package/build/locales/ar/openhands.json +18 -1
  172. package/build/locales/ca/openhands.json +18 -1
  173. package/build/locales/de/openhands.json +18 -1
  174. package/build/locales/en/openhands.json +18 -1
  175. package/build/locales/es/openhands.json +18 -1
  176. package/build/locales/fr/openhands.json +18 -1
  177. package/build/locales/it/openhands.json +18 -1
  178. package/build/locales/ja/openhands.json +18 -1
  179. package/build/locales/ko-KR/openhands.json +18 -1
  180. package/build/locales/no/openhands.json +18 -1
  181. package/build/locales/pt/openhands.json +18 -1
  182. package/build/locales/tr/openhands.json +18 -1
  183. package/build/locales/uk/openhands.json +18 -1
  184. package/build/locales/zh-CN/openhands.json +18 -1
  185. package/build/locales/zh-TW/openhands.json +18 -1
  186. package/config/defaults.json +3 -3
  187. package/dist/api/agent-profiles-service/profile-field-support.d.ts +2 -0
  188. package/dist/components/conversation-events/chat/event-content-helpers/get-skill-ready-content.cjs +3 -3
  189. package/dist/components/conversation-events/chat/event-content-helpers/get-skill-ready-content.cjs.map +1 -1
  190. package/dist/components/conversation-events/chat/event-content-helpers/get-skill-ready-content.js +12 -16
  191. package/dist/components/conversation-events/chat/event-content-helpers/get-skill-ready-content.js.map +1 -1
  192. package/dist/components/features/automations/agent-profile-selector.d.ts +7 -0
  193. package/dist/components/features/automations/detail/disabled-reason-banner.d.ts +16 -0
  194. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.cjs +1 -1
  195. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.cjs.map +1 -1
  196. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.js +38 -29
  197. package/dist/components/features/home/featured-automations/home-automation-run-tooltip.js.map +1 -1
  198. package/dist/components/features/settings/agent-profiles/profile-scope-list.d.ts +20 -0
  199. package/dist/config/defaults.cjs +1 -1
  200. package/dist/config/defaults.cjs.map +1 -1
  201. package/dist/config/defaults.js +3 -3
  202. package/dist/config/defaults.js.map +1 -1
  203. package/dist/constants/acp-providers.cjs +1 -1
  204. package/dist/constants/acp-providers.cjs.map +1 -1
  205. package/dist/constants/acp-providers.d.ts +0 -5
  206. package/dist/constants/acp-providers.js +20 -16
  207. package/dist/constants/acp-providers.js.map +1 -1
  208. package/dist/constants/profile-scope.d.ts +21 -0
  209. package/dist/hooks/mutation/use-create-conversation.cjs +1 -1
  210. package/dist/hooks/mutation/use-create-conversation.cjs.map +1 -1
  211. package/dist/hooks/mutation/use-create-conversation.js +30 -19
  212. package/dist/hooks/mutation/use-create-conversation.js.map +1 -1
  213. package/dist/hooks/mutation/use-switch-acp-model.cjs +1 -1
  214. package/dist/hooks/mutation/use-switch-acp-model.cjs.map +1 -1
  215. package/dist/hooks/mutation/use-switch-acp-model.js +11 -12
  216. package/dist/hooks/mutation/use-switch-acp-model.js.map +1 -1
  217. package/dist/hooks/query/query-keys.cjs +1 -1
  218. package/dist/hooks/query/query-keys.cjs.map +1 -1
  219. package/dist/hooks/query/query-keys.d.ts +1 -0
  220. package/dist/hooks/query/query-keys.js +10 -1
  221. package/dist/hooks/query/query-keys.js.map +1 -1
  222. package/dist/hooks/query/use-active-acp-profile-detail.cjs +1 -1
  223. package/dist/hooks/query/use-active-acp-profile-detail.cjs.map +1 -1
  224. package/dist/hooks/query/use-active-acp-profile-detail.d.ts +0 -9
  225. package/dist/hooks/query/use-active-acp-profile-detail.js +4 -13
  226. package/dist/hooks/query/use-active-acp-profile-detail.js.map +1 -1
  227. package/dist/hooks/query/use-automation-sdk-version.cjs +1 -1
  228. package/dist/hooks/query/use-automation-sdk-version.cjs.map +1 -1
  229. package/dist/hooks/query/use-automation-sdk-version.js +3 -1
  230. package/dist/hooks/query/use-automation-sdk-version.js.map +1 -1
  231. package/dist/hooks/use-drag-resize.cjs +1 -1
  232. package/dist/hooks/use-drag-resize.cjs.map +1 -1
  233. package/dist/hooks/use-drag-resize.js +8 -9
  234. package/dist/hooks/use-drag-resize.js.map +1 -1
  235. package/dist/i18n/declaration.cjs +1 -1
  236. package/dist/i18n/declaration.cjs.map +1 -1
  237. package/dist/i18n/declaration.d.ts +18 -1
  238. package/dist/i18n/declaration.js +1 -1
  239. package/dist/i18n/declaration.js.map +1 -1
  240. package/dist/i18n/translation.cjs +3 -3
  241. package/dist/i18n/translation.cjs.map +1 -1
  242. package/dist/i18n/translation.js +289 -0
  243. package/dist/i18n/translation.js.map +1 -1
  244. package/dist/locales/ar/openhands.json +18 -1
  245. package/dist/locales/ca/openhands.json +18 -1
  246. package/dist/locales/de/openhands.json +18 -1
  247. package/dist/locales/en/openhands.json +18 -1
  248. package/dist/locales/es/openhands.json +18 -1
  249. package/dist/locales/fr/openhands.json +18 -1
  250. package/dist/locales/it/openhands.json +18 -1
  251. package/dist/locales/ja/openhands.json +18 -1
  252. package/dist/locales/ko-KR/openhands.json +18 -1
  253. package/dist/locales/no/openhands.json +18 -1
  254. package/dist/locales/pt/openhands.json +18 -1
  255. package/dist/locales/tr/openhands.json +18 -1
  256. package/dist/locales/uk/openhands.json +18 -1
  257. package/dist/locales/zh-CN/openhands.json +18 -1
  258. package/dist/locales/zh-TW/openhands.json +18 -1
  259. package/dist/manifests/automation-setup.cjs.map +1 -1
  260. package/dist/manifests/automation-setup.js.map +1 -1
  261. package/dist/manifests/manifest-local-validation.cjs.map +1 -1
  262. package/dist/manifests/manifest-local-validation.js.map +1 -1
  263. package/dist/manifests/manifest-validation.cjs +1 -1
  264. package/dist/manifests/manifest-validation.cjs.map +1 -1
  265. package/dist/manifests/manifest-validation.js +2 -2
  266. package/dist/manifests/manifest-validation.js.map +1 -1
  267. package/dist/node_modules/@openhands/extensions/automations/bundle-index.cjs +3670 -247
  268. package/dist/node_modules/@openhands/extensions/automations/bundle-index.cjs.map +1 -1
  269. package/dist/node_modules/@openhands/extensions/automations/bundle-index.js +26 -3
  270. package/dist/node_modules/@openhands/extensions/automations/bundle-index.js.map +1 -1
  271. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.cjs +1 -1
  272. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.cjs.map +1 -1
  273. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.js +4 -1
  274. package/dist/node_modules/@openhands/extensions/automations/catalog/github-agents-md-maintainer/manifest.js.map +1 -1
  275. package/dist/node_modules/@openhands/extensions/automations/catalog/github-delivery-watchdog/manifest.cjs +2 -0
  276. package/dist/node_modules/@openhands/extensions/automations/catalog/github-delivery-watchdog/manifest.cjs.map +1 -0
  277. package/dist/node_modules/@openhands/extensions/automations/catalog/github-delivery-watchdog/manifest.js +79 -0
  278. package/dist/node_modules/@openhands/extensions/automations/catalog/github-delivery-watchdog/manifest.js.map +1 -0
  279. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.cjs +1 -11
  280. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.cjs.map +1 -1
  281. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.js +25 -11
  282. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-to-pr/manifest.js.map +1 -1
  283. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-triage/manifest.cjs +2 -0
  284. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-triage/manifest.cjs.map +1 -0
  285. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-triage/manifest.js +72 -0
  286. package/dist/node_modules/@openhands/extensions/automations/catalog/github-issue-triage/manifest.js.map +1 -0
  287. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs +1 -10
  288. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.cjs.map +1 -1
  289. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js +24 -11
  290. package/dist/node_modules/@openhands/extensions/automations/catalog/github-pr-reviewer/manifest.js.map +1 -1
  291. package/dist/node_modules/@openhands/extensions/automations/catalog/gitlab-issue-to-mr/manifest.cjs +12 -0
  292. package/dist/node_modules/@openhands/extensions/automations/catalog/gitlab-issue-to-mr/manifest.cjs.map +1 -0
  293. package/dist/node_modules/@openhands/extensions/automations/catalog/gitlab-issue-to-mr/manifest.js +115 -0
  294. package/dist/node_modules/@openhands/extensions/automations/catalog/gitlab-issue-to-mr/manifest.js.map +1 -0
  295. package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs +1 -1
  296. package/dist/node_modules/@openhands/extensions/automations/catalog-index.cjs.map +1 -1
  297. package/dist/node_modules/@openhands/extensions/automations/catalog-index.js +20 -14
  298. package/dist/node_modules/@openhands/extensions/automations/catalog-index.js.map +1 -1
  299. package/dist/node_modules/@openhands/extensions/integrations/catalog/reportportal.cjs +2 -0
  300. package/dist/node_modules/@openhands/extensions/integrations/catalog/reportportal.cjs.map +1 -0
  301. package/dist/node_modules/@openhands/extensions/integrations/catalog/reportportal.js +73 -0
  302. package/dist/node_modules/@openhands/extensions/integrations/catalog/reportportal.js.map +1 -0
  303. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.cjs +1 -1
  304. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.cjs.map +1 -1
  305. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.js +9 -7
  306. package/dist/node_modules/@openhands/extensions/integrations/catalog-index.js.map +1 -1
  307. package/dist/node_modules/@openhands/extensions/skills/index.cjs +794 -227
  308. package/dist/node_modules/@openhands/extensions/skills/index.cjs.map +1 -1
  309. package/dist/node_modules/@openhands/extensions/skills/index.js +46 -6
  310. package/dist/node_modules/@openhands/extensions/skills/index.js.map +1 -1
  311. package/dist/node_modules/@openhands/typescript-client/dist/client/agent-server-compatibility.cjs +1 -1
  312. package/dist/node_modules/@openhands/typescript-client/dist/client/agent-server-compatibility.cjs.map +1 -1
  313. package/dist/node_modules/@openhands/typescript-client/dist/client/agent-server-compatibility.js +15 -8
  314. package/dist/node_modules/@openhands/typescript-client/dist/client/agent-server-compatibility.js.map +1 -1
  315. package/dist/node_modules/@openhands/typescript-client/dist/client/bash-client.cjs +1 -1
  316. package/dist/node_modules/@openhands/typescript-client/dist/client/bash-client.cjs.map +1 -1
  317. package/dist/node_modules/@openhands/typescript-client/dist/client/bash-client.js +11 -13
  318. package/dist/node_modules/@openhands/typescript-client/dist/client/bash-client.js.map +1 -1
  319. package/dist/node_modules/@openhands/typescript-client/dist/client/conversation-client.cjs +1 -1
  320. package/dist/node_modules/@openhands/typescript-client/dist/client/conversation-client.cjs.map +1 -1
  321. package/dist/node_modules/@openhands/typescript-client/dist/client/conversation-client.js +6 -0
  322. package/dist/node_modules/@openhands/typescript-client/dist/client/conversation-client.js.map +1 -1
  323. package/dist/node_modules/@openhands/typescript-client/dist/client/file-client.cjs +1 -1
  324. package/dist/node_modules/@openhands/typescript-client/dist/client/file-client.cjs.map +1 -1
  325. package/dist/node_modules/@openhands/typescript-client/dist/client/file-client.js +9 -12
  326. package/dist/node_modules/@openhands/typescript-client/dist/client/file-client.js.map +1 -1
  327. package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.cjs +1 -1
  328. package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.cjs.map +1 -1
  329. package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.js +9 -10
  330. package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.js.map +1 -1
  331. package/dist/node_modules/@openhands/typescript-client/dist/client/runtime-transport.cjs +2 -0
  332. package/dist/node_modules/@openhands/typescript-client/dist/client/runtime-transport.cjs.map +1 -0
  333. package/dist/node_modules/@openhands/typescript-client/dist/client/runtime-transport.js +41 -0
  334. package/dist/node_modules/@openhands/typescript-client/dist/client/runtime-transport.js.map +1 -0
  335. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.cjs +1 -1
  336. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.cjs.map +1 -1
  337. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.js +2 -2
  338. package/dist/node_modules/@openhands/typescript-client/dist/client/settings-client.js.map +1 -1
  339. package/dist/node_modules/@openhands/typescript-client/dist/client/vscode-client.cjs +1 -1
  340. package/dist/node_modules/@openhands/typescript-client/dist/client/vscode-client.cjs.map +1 -1
  341. package/dist/node_modules/@openhands/typescript-client/dist/client/vscode-client.js +3 -6
  342. package/dist/node_modules/@openhands/typescript-client/dist/client/vscode-client.js.map +1 -1
  343. package/dist/node_modules/@openhands/typescript-client/dist/clients.cjs +1 -1
  344. package/dist/node_modules/@openhands/typescript-client/dist/clients.js +1 -2
  345. package/dist/node_modules/@openhands/typescript-client/dist/conversation/conversation-manager.cjs +1 -1
  346. package/dist/node_modules/@openhands/typescript-client/dist/conversation/conversation-manager.js +0 -1
  347. package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.cjs +1 -1
  348. package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.cjs.map +1 -1
  349. package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.js +199 -61
  350. package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.js.map +1 -1
  351. package/dist/node_modules/@openhands/typescript-client/dist/workspace/remote-workspace.cjs +1 -1
  352. package/dist/node_modules/@openhands/typescript-client/dist/workspace/remote-workspace.cjs.map +1 -1
  353. package/dist/node_modules/@openhands/typescript-client/dist/workspace/remote-workspace.js +9 -8
  354. package/dist/node_modules/@openhands/typescript-client/dist/workspace/remote-workspace.js.map +1 -1
  355. package/dist/package.cjs +1 -1
  356. package/dist/package.cjs.map +1 -1
  357. package/dist/package.js +3 -3
  358. package/dist/package.js.map +1 -1
  359. package/dist/routes/agent-settings.d.ts +11 -0
  360. package/dist/types/automation.cjs.map +1 -1
  361. package/dist/types/automation.d.ts +26 -2
  362. package/dist/types/automation.js.map +1 -1
  363. package/dist/utils/automation-disabled-reason.cjs +2 -0
  364. package/dist/utils/automation-disabled-reason.cjs.map +1 -0
  365. package/dist/utils/automation-disabled-reason.d.ts +32 -0
  366. package/dist/utils/automation-disabled-reason.js +19 -0
  367. package/dist/utils/automation-disabled-reason.js.map +1 -0
  368. package/dist/utils/parse-git-remote-url.cjs +1 -1
  369. package/dist/utils/parse-git-remote-url.cjs.map +1 -1
  370. package/dist/utils/parse-git-remote-url.d.ts +5 -1
  371. package/dist/utils/parse-git-remote-url.js +20 -13
  372. package/dist/utils/parse-git-remote-url.js.map +1 -1
  373. package/package.json +3 -3
  374. package/scripts/check-sdk-version-sync.mjs +79 -8
  375. package/scripts/dev-safe.mjs +14 -1
  376. package/scripts/docker-build.mjs +3 -0
  377. package/scripts/static-server.mjs +24 -7
  378. package/build/assets/acp-providers-DcY_mKYa.js +0 -1
  379. package/build/assets/active-backend-context-BnH4-L8h.js +0 -2
  380. package/build/assets/agent-server-conversation-service.api-jiCopc2E.js +0 -88
  381. package/build/assets/agent-settings-BF8hM6wb.js +0 -2
  382. package/build/assets/agent-settings-YPutBAeR.js +0 -1
  383. package/build/assets/automation-detail-BxXhb_m2.js +0 -3
  384. package/build/assets/automation-setup-route-qaJ6lLCf.js +0 -3
  385. package/build/assets/automations-list-DEPVNud3.js +0 -1
  386. package/build/assets/backend-form-modal-DxisQV_6.js +0 -1
  387. package/build/assets/conversation-edjzrv5w.js +0 -1
  388. package/build/assets/conversation-panel-edjzrv5w.js +0 -1
  389. package/build/assets/edit-automation-modal-D_JWSmxT.js +0 -1
  390. package/build/assets/empty-state-FK_s1B60.js +0 -1
  391. package/build/assets/git-repo-dropdown-BoDDWXfX.js +0 -1
  392. package/build/assets/home-BM5_Wz_R.js +0 -1
  393. package/build/assets/home-Ivfm7KC4.js +0 -1
  394. package/build/assets/home-automation-run-tooltip-B1N2uO9p.js +0 -1
  395. package/build/assets/index-home-td9BN2T3.js +0 -1
  396. package/build/assets/kebab-menu-item-content-SCJBwZnp.js +0 -1
  397. package/build/assets/link-external-BNZzCM1I.js +0 -1
  398. package/build/assets/llm-not-configured-banner-Dh_ajcM8.js +0 -27
  399. package/build/assets/llm-settings-CZaXYgtw.js +0 -1
  400. package/build/assets/manifest-bfeccb98.js +0 -1
  401. package/build/assets/mcp-CraxoXQv.js +0 -1
  402. package/build/assets/mcp-marketplace-utils-C7PYr293.js +0 -1
  403. package/build/assets/mcp-page-CteH_K0N.js +0 -9
  404. package/build/assets/onboarding-DXFBRqEy.js +0 -1
  405. package/build/assets/parse-git-remote-url-DpDM9bK6.js +0 -1
  406. package/build/assets/recommended-automations-launcher-DEHRKIAg.js +0 -1
  407. package/build/assets/root-CM6IZHlg.css +0 -1
  408. package/build/assets/root-DdFkodKW.js +0 -2
  409. package/build/assets/trash-Ckh7yCaB.js +0 -1
  410. package/build/assets/use-create-conversation-i_PdFdIs.js +0 -1
  411. package/build/assets/use-create-secret-CjWq0J4O.js +0 -1
  412. package/build/assets/use-manifest-capabilities-CdqYfUAC.js +0 -1
  413. package/build/assets/use-tracking-j4uo9VEW.js +0 -3
  414. package/build/assets/utils-BgLSZdyy.js +0 -1
  415. package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~b0x1wrp2-Z0lly3FE.js +0 -2
  416. package/build/assets/vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~inibp4qe-CnsDDzeE.js +0 -48
  417. package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.cjs +0 -2
  418. package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.cjs.map +0 -1
  419. package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.js +0 -21
  420. package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  var e = {
2
2
  _comment: "Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.",
3
3
  versions: {
4
- agentServer: "1.46.0",
5
- agentCanvas: "1.18.0",
6
- automation: "1.11.1"
4
+ agentServer: "1.49.1",
5
+ agentCanvas: "1.20.0",
6
+ automation: "1.13.1"
7
7
  },
8
8
  compatibility: { minimumAgentServer: "1.28.0" },
9
9
  constraints: {
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","names":[],"sources":["../../config/defaults.json"],"sourcesContent":["{\n \"_comment\": \"Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.\",\n \"versions\": {\n \"agentServer\": \"1.46.0\",\n \"agentCanvas\": \"1.18.0\",\n \"automation\": \"1.11.1\"\n },\n \"compatibility\": {\n \"minimumAgentServer\": \"1.28.0\"\n },\n \"constraints\": {\n \"_comment\": \"Temporary upper bound on the agent-client-protocol (acp) transitive dependency. openhands-sdk 1.35.0 requires agent-client-protocol>=0.10.1 with no upper bound, but acp 0.11.0 reordered the ACP prompt() arguments and breaks the SDK's ACP client (a PromptRequest validation error surfaces as 'ACP error: 2 validation errors for PromptRequest'). Pin acp <0.11 until a fixed openhands-sdk ships. Consumed by scripts/dev-safe.mjs (agent-server uvx install) and .github/workflows/mock-llm-e2e.yml (mock ACP server venv).\",\n \"agentClientProtocol\": \"agent-client-protocol<0.11\"\n },\n \"images\": {\n \"agentServer\": \"ghcr.io/openhands/agent-server\",\n \"agentCanvas\": \"ghcr.io/openhands/agent-canvas\"\n },\n \"ports\": {\n \"agentServer\": 18000,\n \"automation\": 18001,\n \"proxy\": 8000,\n \"_vscodeComment\": \"Port the bundled editor binds to inside the container; agent-server's own default. Only the Docker entrypoint reads this — the npm launchers derive the editor port from the agent-server port so several stacks can run side by side. It is never published: the editor is reached through paths.vscodeBasePath on the proxy port.\",\n \"vscode\": 8001\n },\n \"paths\": {\n \"stateSubdir\": \"agent-canvas\",\n \"conversations\": \"agent-canvas/conversations\",\n \"bashEvents\": \"agent-canvas/bash_events\",\n \"automationDb\": \"automation/automations.db\",\n \"canvasBasePath\": \"/canvas\",\n \"_vscodeBasePathComment\": \"Path prefix the bundled editor is served under, on the same origin as the canvas. Passed to agent-server as OH_VSCODE_BASE_PATH (it launches openvscode-server with --server-base-path and advertises the prefix in /api/vscode/url), and registered as an ingress route to the VSCode port. Keeping one value on both sides is what lets a single-origin deployment reach the editor without publishing a second port. Tradeoff: a path prefix routes but does not isolate, so the editor shares the canvas's browser origin and therefore its localStorage — see docs/SELF_HOSTING.md and OpenHands/OpenHands#16492.\",\n \"vscodeBasePath\": \"/vscode\"\n },\n \"packages\": {\n \"agentServer\": \"openhands-agent-server\",\n \"automation\": \"openhands-automation\",\n \"tools\": \"openhands-tools\",\n \"workspace\": \"openhands-workspace\"\n },\n \"telemetry\": {\n \"posthogApiKey\": \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\",\n \"posthogHost\": \"https://us.i.posthog.com\"\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../config/defaults.json"],"sourcesContent":["{\n \"_comment\": \"Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.\",\n \"versions\": {\n \"agentServer\": \"1.49.1\",\n \"agentCanvas\": \"1.20.0\",\n \"automation\": \"1.13.1\"\n },\n \"compatibility\": {\n \"minimumAgentServer\": \"1.28.0\"\n },\n \"constraints\": {\n \"_comment\": \"Temporary upper bound on the agent-client-protocol (acp) transitive dependency. openhands-sdk 1.35.0 requires agent-client-protocol>=0.10.1 with no upper bound, but acp 0.11.0 reordered the ACP prompt() arguments and breaks the SDK's ACP client (a PromptRequest validation error surfaces as 'ACP error: 2 validation errors for PromptRequest'). Pin acp <0.11 until a fixed openhands-sdk ships. Consumed by scripts/dev-safe.mjs (agent-server uvx install) and .github/workflows/mock-llm-e2e.yml (mock ACP server venv).\",\n \"agentClientProtocol\": \"agent-client-protocol<0.11\"\n },\n \"images\": {\n \"agentServer\": \"ghcr.io/openhands/agent-server\",\n \"agentCanvas\": \"ghcr.io/openhands/agent-canvas\"\n },\n \"ports\": {\n \"agentServer\": 18000,\n \"automation\": 18001,\n \"proxy\": 8000,\n \"_vscodeComment\": \"Port the bundled editor binds to inside the container; agent-server's own default. Only the Docker entrypoint reads this — the npm launchers derive the editor port from the agent-server port so several stacks can run side by side. It is never published: the editor is reached through paths.vscodeBasePath on the proxy port.\",\n \"vscode\": 8001\n },\n \"paths\": {\n \"stateSubdir\": \"agent-canvas\",\n \"conversations\": \"agent-canvas/conversations\",\n \"bashEvents\": \"agent-canvas/bash_events\",\n \"automationDb\": \"automation/automations.db\",\n \"canvasBasePath\": \"/canvas\",\n \"_vscodeBasePathComment\": \"Path prefix the bundled editor is served under, on the same origin as the canvas. Passed to agent-server as OH_VSCODE_BASE_PATH (it launches openvscode-server with --server-base-path and advertises the prefix in /api/vscode/url), and registered as an ingress route to the VSCode port. Keeping one value on both sides is what lets a single-origin deployment reach the editor without publishing a second port. Tradeoff: a path prefix routes but does not isolate, so the editor shares the canvas's browser origin and therefore its localStorage — see docs/SELF_HOSTING.md and OpenHands/OpenHands#16492.\",\n \"vscodeBasePath\": \"/vscode\"\n },\n \"packages\": {\n \"agentServer\": \"openhands-agent-server\",\n \"automation\": \"openhands-automation\",\n \"tools\": \"openhands-tools\",\n \"workspace\": \"openhands-workspace\"\n },\n \"telemetry\": {\n \"posthogApiKey\": \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\",\n \"posthogHost\": \"https://us.i.posthog.com\"\n }\n}\n"],"mappings":""}
@@ -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");function n(e){if(typeof e!=`string`)return null;let t=e.trim();return!t||t===`acp-managed`?null:t}function r(e){for(let t of[e.runtimeName,e.runtimeId,e.configured,e.sdkLlm]){let e=n(t);if(e)return e}return e.providerDefault??null}var i={"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}},a=Object.entries(i).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_FILE_BLOB_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT;var o=`gemini-2.5-pro`;function s(e){return e===`gemini-cli`?o:c(e)?.default_model??null}function c(e){if(e)return a.find(t=>t.key===e)}function l(e){let t=c(e);return t?t.display_name:null}function u(e){return c(e)?.icon??`cli-generic`}function d(e,t){return t?c(e)?.available_models?.find(e=>e.id===t)?.label??t:null}exports.getAcpPreferredDefaultModel=s,exports.getAcpProvider=c,exports.getAcpProviderDisplayName=l,exports.labelForAcpModel=d,exports.resolveAcpProviderIcon=u,exports.resolveEffectiveAcpModel=r;
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");function n(e){if(typeof e!=`string`)return null;let t=e.trim();return!t||t===`acp-managed`?null:t}function r(e){for(let t of[e.runtimeName,e.runtimeId,e.configured,e.sdkLlm]){let e=n(t);if(e)return e}return e.providerDefault??null}var i={"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}};function a(e){return t.getAcpProvider(e)?.available_models?.map(e=>({id:e.id,label:e.label}))}var o=Object.entries(i).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:a(e),default_model:r?.default_model??void 0,description_key:n.description_key,icon:n.icon}});e.I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT,e.I18nKey.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT;var s=`gemini-2.5-pro`;function c(e){return e===`gemini-cli`?s:l(e)?.default_model??null}function l(e){if(e)return o.find(t=>t.key===e)}function u(e){let t=l(e);return t?t.display_name:null}function d(e){return l(e)?.icon??`cli-generic`}function f(e,t){return t?l(e)?.available_models?.find(e=>e.id===t)?.label??t:null}exports.getAcpPreferredDefaultModel=c,exports.getAcpProvider=l,exports.getAcpProviderDisplayName=u,exports.labelForAcpModel=f,exports.resolveAcpProviderIcon=d,exports.resolveEffectiveAcpModel=r;
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// 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.\n//\n// NB: ``\"default\"`` is NOT a placeholder. claude-agent-acp 0.44+ exposes\n// ``default`` (\"Default (recommended)\") as a real, selectable model in its\n// configOptions select — the server reports it as ``currentModelId`` and lists\n// it in ``availableModels`` — so it must surface like any other model id.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty and not the legacy\n * ``acp-managed`` sentinel. Returns the trimmed value on success, ``null``\n * 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 (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 * ``@agentclientprotocol/codex-acp`` (the codex ACP 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. Its keys are the harnesses Canvas\n// offers — see {@link SURFACED_ACP_PROVIDERS}.\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/**\n * The ACP harnesses Canvas surfaces — its own declaration of what it offers,\n * independent of what the SDK registry happens to contain. Registering a\n * harness upstream is a no-op here: nothing in Canvas enumerates the registry,\n * so there is no list to keep in step with it.\n *\n * The same constant name carries the same meaning in\n * ``OpenHands/enterprise`` (``openhands/app_server/acp_providers.py``), where\n * it also fails conversation start closed. The two are declared independently\n * and hold the same three keys today.\n */\nexport const SURFACED_ACP_PROVIDERS: readonly string[] =\n Object.keys(ACP_PROVIDER_UI);\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, OAuth tokens, credential\n * blobs) rather than a plain-text input (base URLs, project IDs). Doubles as\n * \"this is an actual credential\": when the onboarding step is required, only\n * a ``secret`` field satisfies it — a base URL or GCP scalar alone can't\n * authenticate anything. */\n secret?: boolean;\n /** Render as a multi-line textarea rather than a single-line input. Set for\n * file-content credentials the user pastes verbatim (Codex ``auth.json``,\n * Gemini Vertex service-account / ADC JSON) — the ones the SDK materialises\n * to disk, which a cloud backend can't consume yet (agent-canvas#1016). */\n multiline?: boolean;\n /** i18n key for the one-line helper text under the field. */\n hint_key: I18nKey;\n /**\n * Interpolation values for {@link hint_key} (e.g. ``{ file: \"~/.codex/auth.json\" }``\n * for the shared \"paste the file contents\" hint). Omitted for hints that\n * take no parameters.\n */\n hint_values?: Record<string, string>;\n}\n\n/**\n * Container credentials — beyond the registry's API key / base URL — that\n * canvas collects per provider so a *containerized* agent-server (no host login\n * state) can authenticate the ACP CLI: subscription tokens plus the file-content\n * blobs and Vertex config the SDK's ``acp_file_secrets`` materialisation\n * consumes (agent-canvas#1013/#1014). They have no registry env-var entry\n * because they're a deployment concern, not a model registry field. The Gemini\n * project/location/flag are plain config, not secrets — grouped here because\n * they travel with the SA blob.\n *\n * The blob *names* (``CODEX_AUTH_JSON`` / ``GOOGLE_APPLICATION_CREDENTIALS_JSON``)\n * duplicate the SDK registry's ``file_secrets`` specs; derive them from the\n * client registry once the pinned ``@openhands/typescript-client`` mirrors that\n * field (same bump that unblocks ``acp_isolate_data_dir``, see #1019).\n */\nconst ACP_RESERVED_CREDENTIALS: Record<string, ACPProviderSecretField[]> = {\n codex: [\n {\n name: \"CODEX_AUTH_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: { file: \"~/.codex/auth.json\" },\n },\n ],\n \"claude-code\": [\n {\n name: \"CLAUDE_CODE_OAUTH_TOKEN\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT,\n },\n ],\n \"gemini-cli\": [\n {\n name: \"GOOGLE_APPLICATION_CREDENTIALS_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: {\n file: \"~/.config/gcloud/application_default_credentials.json\",\n },\n },\n {\n name: \"GOOGLE_CLOUD_PROJECT\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT,\n },\n {\n name: \"GOOGLE_CLOUD_LOCATION\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT,\n },\n {\n name: \"GOOGLE_GENAI_USE_VERTEXAI\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT,\n },\n ],\n};\n\n/**\n * Credential pairs that break each other at runtime, keyed by provider —\n * mirrors the SDK's ``_ENV_CONFLICT_MAP`` (software-agent-sdk#3588). Claude's\n * OAuth token (``CLAUDE_CODE_OAUTH_TOKEN``) authenticates against Anthropic\n * directly, so when it is set the SDK strips:\n * - ``ANTHROPIC_API_KEY`` — otherwise it takes precedence and silently\n * bypasses the subscription;\n * - ``ANTHROPIC_BASE_URL`` — otherwise it proxies the bearer to an endpoint\n * that rejects it (see docs/ACP_AGENTS.md).\n * Either one set alongside the token is silently ignored at runtime, so warn.\n */\nconst ACP_CREDENTIAL_CONFLICTS: Record<string, Array<[string, string]>> = {\n \"claude-code\": [\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_API_KEY\"],\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_BASE_URL\"],\n ],\n};\n\n/**\n * The ``[credential, conflicting]`` pairs the user currently has set for\n * ``providerKey``. ``hasValue`` should cover both typed and already-saved\n * values, since a previously saved secret conflicts just the same.\n */\nexport function getAcpCredentialConflicts(\n key: string | null | undefined,\n hasValue: (name: string) => boolean,\n): Array<[string, string]> {\n if (!key) return [];\n return (ACP_CREDENTIAL_CONFLICTS[key] ?? []).filter(\n ([credential, conflicting]) =>\n hasValue(credential) && hasValue(conflicting),\n );\n}\n\n/**\n * Vertex AI-safe ``acp_model`` for Gemini.\n *\n * Left unset, gemini-cli falls back to its own internal default — a preview\n * flash model that 404s on Google Cloud projects without preview access. And a\n * ``*-flash`` id is no safer to pin: gemini-cli re-resolves flash ids at\n * generation time to its *current default* flash (software-agent-sdk#3532), so\n * a pinned ``gemini-2.5-flash`` still ran ``gemini-3-flash``. Only a non-flash\n * id sticks; ``gemini-2.5-pro`` is broadly available on Vertex and the\n * API-key / Google-login paths, so canvas preselects it. Kept as a named\n * constant so every model-default consumer agrees.\n */\nexport const ACP_VERTEX_SAFE_MODEL = \"gemini-2.5-pro\";\n\n/**\n * The default ``acp_model`` canvas substitutes for ``providerKey`` wherever no\n * concrete model is configured — settings seeding (onboarding, Settings →\n * Agent), the {@link buildAcpAgentSettingsDiff} fallback, and the start-request\n * fallback for a saved ``null``. Overrides only Gemini (→\n * {@link ACP_VERTEX_SAFE_MODEL}, see why above); every other provider keeps its\n * registry {@link ACPProviderConfig.default_model}. Returns ``null`` when\n * there's no override and no registry default, letting the ACP server pick its\n * own.\n *\n * Distinct from {@link ACPProviderConfig.default_model} (which mirrors the SDK\n * registry verbatim, closing agent-canvas#740): this is the *preferred* default,\n * deliberately diverging for Gemini where the registry value isn't safe on\n * every backend — so every default-model surface must route through this, not\n * read ``default_model`` directly.\n */\nexport function getAcpPreferredDefaultModel(\n key: string | null | undefined,\n): string | null {\n if (key === \"gemini-cli\") return ACP_VERTEX_SAFE_MODEL;\n return getAcpProvider(key)?.default_model ?? null;\n}\n\n/**\n * List the credentials Canvas should prompt for when onboarding the given ACP\n * provider. The API-key and base-URL field *names* track the SDK registry's\n * ``api_key_env_var`` / ``base_url_env_var`` (mirrored via\n * ``@openhands/typescript-client``) so they can't drift as providers are added\n * or renamed; the per-provider container credentials (subscription / Vertex\n * blobs) come from {@link ACP_RESERVED_CREDENTIALS}, since those are a\n * containerized-deployment concern with no model-registry entry. Each field\n * name equals the env var the agent-server exports into the provider subprocess\n * (which is what makes a saved secret reach the CLI). On the wire none of this\n * is special — every saved credential travels uniformly as a ``LookupSecret``.\n *\n * Field order is: container subscription/Vertex credentials → API key →\n * optional base URL. Every field is optional at the UI level — the step is\n * skippable, and a subscription / OAuth login on the backend takes precedence\n * over a key at runtime. (Whether the *step* is required is a\n * backend-capability decision the onboarding modal makes; see\n * ``backendRequiresAcpCredentials``.)\n *\n * NB: the base URL is rendered plain-text (not ``secret``), so it never counts\n * toward a required credential step — setting ``ANTHROPIC_BASE_URL`` alongside\n * a ``CLAUDE_CODE_OAUTH_TOKEN`` breaks the token's bearer auth, which the forms\n * surface via {@link getAcpCredentialConflicts}.\n *\n * Returns ``[]`` for OpenHands, the ``\"custom\"`` preset, any unknown key, and a\n * future OAuth-only provider whose registry entry has no ``api_key_env_var`` —\n * callers 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 // Surfaced providers only. The client registry grows with every harness the\n // SDK adds, so reading it directly would offer credential fields for one\n // Canvas never lists.\n if (!getAcpProvider(key)) return [];\n const info = getClientAcpProvider(key);\n if (!info) return [];\n // Subscription / Vertex credentials first — they're the primary auth path for\n // ACP providers (Claude Pro/Max OAuth token, Codex ChatGPT auth.json), with\n // the API key as the fallback below.\n const fields: ACPProviderSecretField[] = [\n ...(ACP_RESERVED_CREDENTIALS[key] ?? []),\n ];\n if (info.api_key_env_var) {\n fields.push({\n name: info.api_key_env_var,\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n });\n }\n if (info.base_url_env_var) {\n fields.push({\n name: info.base_url_env_var,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n });\n }\n return fields;\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 // Undefined model → the *preferred* default (Vertex-safe for Gemini), not\n // the raw registry default — see getAcpPreferredDefaultModel.\n const model =\n options.model === undefined\n ? getAcpPreferredDefaultModel(providerKey)\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":"6OA0BA,SAAS,EAAa,EAA+B,CACnD,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAU,EAAM,KAAK,EAG3B,MAFI,CAAC,GACD,IAAA,cAAyC,KACtC,CACT,CAeA,SAAgB,EAAyB,EAMvB,CAChB,IAAK,IAAM,IAAa,CACtB,EAAO,YACP,EAAO,UACP,EAAO,WACP,EAAO,MACT,EAAG,CACD,IAAM,EAAQ,EAAa,CAAS,EACpC,GAAI,EAAO,OAAO,CACpB,CACA,OAAO,EAAO,iBAAmB,IACnC,CAgEA,IAAM,EAGF,CACF,cAAe,CACb,KAAM,cACN,gBAAiB,EAAA,QAAQ,wCAC3B,EACA,MAAO,CACL,KAAM,QACN,gBAAiB,EAAA,QAAQ,kCAC3B,EACA,aAAc,CACZ,KAAM,SACN,gBAAiB,EAAA,QAAQ,uCAC3B,CACF,EAoBa,EAAqC,OAAO,QACvD,CACF,EAAE,KAAK,CAAC,EAAK,KAAQ,CACnB,IAAM,EAAO,EAAA,eAAqB,CAAG,EACrC,MAAO,CACL,MACA,aAAc,GAAM,cAAgB,EACpC,gBAAiB,EAAO,CAAC,GAAG,EAAK,eAAe,EAAI,CAAC,EACrD,iBAAkB,GAAM,kBAAkB,IAAK,IAAW,CACxD,GAAI,EAAM,GACV,MAAO,EAAM,KACf,EAAE,EACF,cAAe,GAAM,eAAiB,IAAA,GACtC,gBAAiB,EAAG,gBACpB,KAAM,EAAG,IACX,CACF,CAAC,EAyDe,EAAA,QAAQ,qCAQR,EAAA,QAAQ,uCAQR,EAAA,QAAQ,qCAOR,EAAA,QAAQ,uCAIR,EAAA,QAAQ,wCAIR,EAAA,QAAQ,yCAmDxB,IAAa,EAAwB,iBAkBrC,SAAgB,EACd,EACe,CAEf,OADI,IAAQ,aAAqB,EAC1B,EAAe,CAAG,GAAG,eAAiB,IAC/C,CAuEA,SAAgB,EACd,EAC+B,CAC1B,KACL,OAAO,EAAc,KAAM,GAAa,EAAS,MAAQ,CAAG,CAC9D,CAkBA,SAAgB,EACd,EACe,CACf,IAAM,EAAQ,EAAe,CAAG,EAChC,OAAO,EAAQ,EAAM,aAAe,IACtC,CAWA,SAAgB,EACd,EACiB,CACjB,OAAO,EAAe,CAAG,GAAG,MAAA,aAC9B,CAYA,SAAgB,EACd,EACA,EACe,CAIf,OAHK,EACY,EAAe,CAClB,GAAU,kBAAkB,KAAM,GAAM,EAAE,KAAO,CAAO,GACxD,OAAS,EAHF,IAIvB"}
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// 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.\n//\n// NB: ``\"default\"`` is NOT a placeholder. claude-agent-acp 0.44+ exposes\n// ``default`` (\"Default (recommended)\") as a real, selectable model in its\n// configOptions select — the server reports it as ``currentModelId`` and lists\n// it in ``availableModels`` — so it must surface like any other model id.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty and not the legacy\n * ``acp-managed`` sentinel. Returns the trimmed value on success, ``null``\n * 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 (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 * ``@agentclientprotocol/codex-acp`` (the codex ACP 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. Its keys are the harnesses Canvas\n// offers — see {@link SURFACED_ACP_PROVIDERS}.\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\nfunction getAvailableModels(key: string): ACPModelOption[] | undefined {\n return getClientAcpProvider(key)?.available_models?.map((model) => ({\n id: model.id,\n label: model.label,\n }));\n}\n\n/**\n * The ACP harnesses Canvas surfaces — its own declaration of what it offers,\n * independent of what the SDK registry happens to contain. Registering a\n * harness upstream is a no-op here: nothing in Canvas enumerates the registry,\n * so there is no list to keep in step with it.\n */\nexport const SURFACED_ACP_PROVIDERS: readonly string[] =\n Object.keys(ACP_PROVIDER_UI);\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.\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: getAvailableModels(key),\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, OAuth tokens, credential\n * blobs) rather than a plain-text input (base URLs, project IDs). Doubles as\n * \"this is an actual credential\": when the onboarding step is required, only\n * a ``secret`` field satisfies it — a base URL or GCP scalar alone can't\n * authenticate anything. */\n secret?: boolean;\n /** Render as a multi-line textarea rather than a single-line input. Set for\n * file-content credentials the user pastes verbatim (Codex ``auth.json``,\n * Gemini Vertex service-account / ADC JSON) — the ones the SDK materialises\n * to disk, which a cloud backend can't consume yet (agent-canvas#1016). */\n multiline?: boolean;\n /** i18n key for the one-line helper text under the field. */\n hint_key: I18nKey;\n /**\n * Interpolation values for {@link hint_key} (e.g. ``{ file: \"~/.codex/auth.json\" }``\n * for the shared \"paste the file contents\" hint). Omitted for hints that\n * take no parameters.\n */\n hint_values?: Record<string, string>;\n}\n\n/**\n * Container credentials — beyond the registry's API key / base URL — that\n * canvas collects per provider so a *containerized* agent-server (no host login\n * state) can authenticate the ACP CLI: subscription tokens plus the file-content\n * blobs and Vertex config the SDK's ``acp_file_secrets`` materialisation\n * consumes (agent-canvas#1013/#1014). They have no registry env-var entry\n * because they're a deployment concern, not a model registry field. The Gemini\n * project/location/flag are plain config, not secrets — grouped here because\n * they travel with the SA blob.\n *\n * The blob *names* (``CODEX_AUTH_JSON`` / ``GOOGLE_APPLICATION_CREDENTIALS_JSON``)\n * duplicate the SDK registry's ``file_secrets`` specs; derive them from the\n * client registry once the pinned ``@openhands/typescript-client`` mirrors that\n * field (same bump that unblocks ``acp_isolate_data_dir``, see #1019).\n */\nconst ACP_RESERVED_CREDENTIALS: Record<string, ACPProviderSecretField[]> = {\n codex: [\n {\n name: \"CODEX_AUTH_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: { file: \"~/.codex/auth.json\" },\n },\n ],\n \"claude-code\": [\n {\n name: \"CLAUDE_CODE_OAUTH_TOKEN\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT,\n },\n ],\n \"gemini-cli\": [\n {\n name: \"GOOGLE_APPLICATION_CREDENTIALS_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: {\n file: \"~/.config/gcloud/application_default_credentials.json\",\n },\n },\n {\n name: \"GOOGLE_CLOUD_PROJECT\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT,\n },\n {\n name: \"GOOGLE_CLOUD_LOCATION\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT,\n },\n {\n name: \"GOOGLE_GENAI_USE_VERTEXAI\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT,\n },\n ],\n};\n\n/**\n * Credential pairs that break each other at runtime, keyed by provider —\n * mirrors the SDK's ``_ENV_CONFLICT_MAP`` (software-agent-sdk#3588). Claude's\n * OAuth token (``CLAUDE_CODE_OAUTH_TOKEN``) authenticates against Anthropic\n * directly, so when it is set the SDK strips:\n * - ``ANTHROPIC_API_KEY`` — otherwise it takes precedence and silently\n * bypasses the subscription;\n * - ``ANTHROPIC_BASE_URL`` — otherwise it proxies the bearer to an endpoint\n * that rejects it (see docs/ACP_AGENTS.md).\n * Either one set alongside the token is silently ignored at runtime, so warn.\n */\nconst ACP_CREDENTIAL_CONFLICTS: Record<string, Array<[string, string]>> = {\n \"claude-code\": [\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_API_KEY\"],\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_BASE_URL\"],\n ],\n};\n\n/**\n * The ``[credential, conflicting]`` pairs the user currently has set for\n * ``providerKey``. ``hasValue`` should cover both typed and already-saved\n * values, since a previously saved secret conflicts just the same.\n */\nexport function getAcpCredentialConflicts(\n key: string | null | undefined,\n hasValue: (name: string) => boolean,\n): Array<[string, string]> {\n if (!key) return [];\n return (ACP_CREDENTIAL_CONFLICTS[key] ?? []).filter(\n ([credential, conflicting]) =>\n hasValue(credential) && hasValue(conflicting),\n );\n}\n\n/**\n * Vertex AI-safe ``acp_model`` for Gemini.\n *\n * Left unset, gemini-cli falls back to its own internal default — a preview\n * flash model that 404s on Google Cloud projects without preview access. And a\n * ``*-flash`` id is no safer to pin: gemini-cli re-resolves flash ids at\n * generation time to its *current default* flash (software-agent-sdk#3532), so\n * a pinned ``gemini-2.5-flash`` still ran ``gemini-3-flash``. Only a non-flash\n * id sticks; ``gemini-2.5-pro`` is broadly available on Vertex and the\n * API-key / Google-login paths, so canvas preselects it. Kept as a named\n * constant so every model-default consumer agrees.\n */\nexport const ACP_VERTEX_SAFE_MODEL = \"gemini-2.5-pro\";\n\n/**\n * The default ``acp_model`` canvas substitutes for ``providerKey`` wherever no\n * concrete model is configured — settings seeding (onboarding, Settings →\n * Agent), the {@link buildAcpAgentSettingsDiff} fallback, and the start-request\n * fallback for a saved ``null``. Overrides only Gemini (→\n * {@link ACP_VERTEX_SAFE_MODEL}, see why above); every other provider keeps its\n * registry {@link ACPProviderConfig.default_model}. Returns ``null`` when\n * there's no override and no registry default, letting the ACP server pick its\n * own.\n *\n * Distinct from {@link ACPProviderConfig.default_model} (which mirrors the SDK\n * registry verbatim, closing agent-canvas#740): this is the *preferred* default,\n * deliberately diverging for Gemini where the registry value isn't safe on\n * every backend — so every default-model surface must route through this, not\n * read ``default_model`` directly.\n */\nexport function getAcpPreferredDefaultModel(\n key: string | null | undefined,\n): string | null {\n if (key === \"gemini-cli\") return ACP_VERTEX_SAFE_MODEL;\n return getAcpProvider(key)?.default_model ?? null;\n}\n\n/**\n * List the credentials Canvas should prompt for when onboarding the given ACP\n * provider. The API-key and base-URL field *names* track the SDK registry's\n * ``api_key_env_var`` / ``base_url_env_var`` (mirrored via\n * ``@openhands/typescript-client``) so they can't drift as providers are added\n * or renamed; the per-provider container credentials (subscription / Vertex\n * blobs) come from {@link ACP_RESERVED_CREDENTIALS}, since those are a\n * containerized-deployment concern with no model-registry entry. Each field\n * name equals the env var the agent-server exports into the provider subprocess\n * (which is what makes a saved secret reach the CLI). On the wire none of this\n * is special — every saved credential travels uniformly as a ``LookupSecret``.\n *\n * Field order is: container subscription/Vertex credentials → API key →\n * optional base URL. Every field is optional at the UI level — the step is\n * skippable, and a subscription / OAuth login on the backend takes precedence\n * over a key at runtime. (Whether the *step* is required is a\n * backend-capability decision the onboarding modal makes; see\n * ``backendRequiresAcpCredentials``.)\n *\n * NB: the base URL is rendered plain-text (not ``secret``), so it never counts\n * toward a required credential step — setting ``ANTHROPIC_BASE_URL`` alongside\n * a ``CLAUDE_CODE_OAUTH_TOKEN`` breaks the token's bearer auth, which the forms\n * surface via {@link getAcpCredentialConflicts}.\n *\n * Returns ``[]`` for OpenHands, the ``\"custom\"`` preset, any unknown key, and a\n * future OAuth-only provider whose registry entry has no ``api_key_env_var`` —\n * callers 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 // Surfaced providers only. The client registry grows with every harness the\n // SDK adds, so reading it directly would offer credential fields for one\n // Canvas never lists.\n if (!getAcpProvider(key)) return [];\n const info = getClientAcpProvider(key);\n if (!info) return [];\n // Subscription / Vertex credentials first — they're the primary auth path for\n // ACP providers (Claude Pro/Max OAuth token, Codex ChatGPT auth.json), with\n // the API key as the fallback below.\n const fields: ACPProviderSecretField[] = [\n ...(ACP_RESERVED_CREDENTIALS[key] ?? []),\n ];\n if (info.api_key_env_var) {\n fields.push({\n name: info.api_key_env_var,\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n });\n }\n if (info.base_url_env_var) {\n fields.push({\n name: info.base_url_env_var,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n });\n }\n return fields;\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 // Undefined model → the *preferred* default (Vertex-safe for Gemini), not\n // the raw registry default — see getAcpPreferredDefaultModel.\n const model =\n options.model === undefined\n ? getAcpPreferredDefaultModel(providerKey)\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":"6OA0BA,SAAS,EAAa,EAA+B,CACnD,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAU,EAAM,KAAK,EAG3B,MAFI,CAAC,GACD,IAAA,cAAyC,KACtC,CACT,CAeA,SAAgB,EAAyB,EAMvB,CAChB,IAAK,IAAM,IAAa,CACtB,EAAO,YACP,EAAO,UACP,EAAO,WACP,EAAO,MACT,EAAG,CACD,IAAM,EAAQ,EAAa,CAAS,EACpC,GAAI,EAAO,OAAO,CACpB,CACA,OAAO,EAAO,iBAAmB,IACnC,CAgEA,IAAM,EAGF,CACF,cAAe,CACb,KAAM,cACN,gBAAiB,EAAA,QAAQ,wCAC3B,EACA,MAAO,CACL,KAAM,QACN,gBAAiB,EAAA,QAAQ,kCAC3B,EACA,aAAc,CACZ,KAAM,SACN,gBAAiB,EAAA,QAAQ,uCAC3B,CACF,EAEA,SAAS,EAAmB,EAA2C,CACrE,OAAO,EAAA,eAAqB,CAAG,GAAG,kBAAkB,IAAK,IAAW,CAClE,GAAI,EAAM,GACV,MAAO,EAAM,KACf,EAAE,CACJ,CAcA,IAAa,EAAqC,OAAO,QACvD,CACF,EAAE,KAAK,CAAC,EAAK,KAAQ,CACnB,IAAM,EAAO,EAAA,eAAqB,CAAG,EACrC,MAAO,CACL,MACA,aAAc,GAAM,cAAgB,EACpC,gBAAiB,EAAO,CAAC,GAAG,EAAK,eAAe,EAAI,CAAC,EACrD,iBAAkB,EAAmB,CAAG,EACxC,cAAe,GAAM,eAAiB,IAAA,GACtC,gBAAiB,EAAG,gBACpB,KAAM,EAAG,IACX,CACF,CAAC,EAyDe,EAAA,QAAQ,qCAQR,EAAA,QAAQ,uCAQR,EAAA,QAAQ,qCAOR,EAAA,QAAQ,uCAIR,EAAA,QAAQ,wCAIR,EAAA,QAAQ,yCAmDxB,IAAa,EAAwB,iBAkBrC,SAAgB,EACd,EACe,CAEf,OADI,IAAQ,aAAqB,EAC1B,EAAe,CAAG,GAAG,eAAiB,IAC/C,CAuEA,SAAgB,EACd,EAC+B,CAC1B,KACL,OAAO,EAAc,KAAM,GAAa,EAAS,MAAQ,CAAG,CAC9D,CAkBA,SAAgB,EACd,EACe,CACf,IAAM,EAAQ,EAAe,CAAG,EAChC,OAAO,EAAQ,EAAM,aAAe,IACtC,CAWA,SAAgB,EACd,EACiB,CACjB,OAAO,EAAe,CAAG,GAAG,MAAA,aAC9B,CAYA,SAAgB,EACd,EACA,EACe,CAIf,OAHK,EACY,EAAe,CAClB,GAAU,kBAAkB,KAAM,GAAM,EAAE,KAAO,CAAO,GACxD,OAAS,EAHF,IAIvB"}
@@ -81,11 +81,6 @@ export interface ACPModelOption {
81
81
  * independent of what the SDK registry happens to contain. Registering a
82
82
  * harness upstream is a no-op here: nothing in Canvas enumerates the registry,
83
83
  * so there is no list to keep in step with it.
84
- *
85
- * The same constant name carries the same meaning in
86
- * ``OpenHands/enterprise`` (``openhands/app_server/acp_providers.py``), where
87
- * it also fails conversation start closed. The two are declared independently
88
- * and hold the same three keys today.
89
84
  */
90
85
  export declare const SURFACED_ACP_PROVIDERS: readonly string[];
91
86
  export declare const ACP_PROVIDERS: ACPProviderConfig[];
@@ -31,40 +31,44 @@ var i = {
31
31
  icon: "gemini",
32
32
  description_key: e.ONBOARDING$AGENT_GEMINI_CLI_DESCRIPTION
33
33
  }
34
- }, a = Object.entries(i).map(([e, n]) => {
34
+ };
35
+ function a(e) {
36
+ return t(e)?.available_models?.map((e) => ({
37
+ id: e.id,
38
+ label: e.label
39
+ }));
40
+ }
41
+ var o = Object.entries(i).map(([e, n]) => {
35
42
  let r = t(e);
36
43
  return {
37
44
  key: e,
38
45
  display_name: r?.display_name ?? e,
39
46
  default_command: r ? [...r.default_command] : [],
40
- available_models: r?.available_models?.map((e) => ({
41
- id: e.id,
42
- label: e.label
43
- })),
47
+ available_models: a(e),
44
48
  default_model: r?.default_model ?? void 0,
45
49
  description_key: n.description_key,
46
50
  icon: n.icon
47
51
  };
48
52
  });
49
53
  e.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT, e.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT, e.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT, e.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT, e.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT, e.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT;
50
- var o = "gemini-2.5-pro";
51
- function s(e) {
52
- return e === "gemini-cli" ? o : c(e)?.default_model ?? null;
53
- }
54
+ var s = "gemini-2.5-pro";
54
55
  function c(e) {
55
- if (e) return a.find((t) => t.key === e);
56
+ return e === "gemini-cli" ? s : l(e)?.default_model ?? null;
56
57
  }
57
58
  function l(e) {
58
- let t = c(e);
59
- return t ? t.display_name : null;
59
+ if (e) return o.find((t) => t.key === e);
60
60
  }
61
61
  function u(e) {
62
- return c(e)?.icon ?? "cli-generic";
62
+ let t = l(e);
63
+ return t ? t.display_name : null;
64
+ }
65
+ function d(e) {
66
+ return l(e)?.icon ?? "cli-generic";
63
67
  }
64
- function d(e, t) {
65
- return t ? c(e)?.available_models?.find((e) => e.id === t)?.label ?? t : null;
68
+ function f(e, t) {
69
+ return t ? l(e)?.available_models?.find((e) => e.id === t)?.label ?? t : null;
66
70
  }
67
71
  //#endregion
68
- export { s as getAcpPreferredDefaultModel, c as getAcpProvider, l as getAcpProviderDisplayName, d as labelForAcpModel, u as resolveAcpProviderIcon, r as resolveEffectiveAcpModel };
72
+ export { c as getAcpPreferredDefaultModel, l as getAcpProvider, u as getAcpProviderDisplayName, f as labelForAcpModel, d as resolveAcpProviderIcon, r as resolveEffectiveAcpModel };
69
73
 
70
74
  //# sourceMappingURL=acp-providers.js.map
@@ -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// 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.\n//\n// NB: ``\"default\"`` is NOT a placeholder. claude-agent-acp 0.44+ exposes\n// ``default`` (\"Default (recommended)\") as a real, selectable model in its\n// configOptions select — the server reports it as ``currentModelId`` and lists\n// it in ``availableModels`` — so it must surface like any other model id.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty and not the legacy\n * ``acp-managed`` sentinel. Returns the trimmed value on success, ``null``\n * 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 (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 * ``@agentclientprotocol/codex-acp`` (the codex ACP 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. Its keys are the harnesses Canvas\n// offers — see {@link SURFACED_ACP_PROVIDERS}.\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/**\n * The ACP harnesses Canvas surfaces — its own declaration of what it offers,\n * independent of what the SDK registry happens to contain. Registering a\n * harness upstream is a no-op here: nothing in Canvas enumerates the registry,\n * so there is no list to keep in step with it.\n *\n * The same constant name carries the same meaning in\n * ``OpenHands/enterprise`` (``openhands/app_server/acp_providers.py``), where\n * it also fails conversation start closed. The two are declared independently\n * and hold the same three keys today.\n */\nexport const SURFACED_ACP_PROVIDERS: readonly string[] =\n Object.keys(ACP_PROVIDER_UI);\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, OAuth tokens, credential\n * blobs) rather than a plain-text input (base URLs, project IDs). Doubles as\n * \"this is an actual credential\": when the onboarding step is required, only\n * a ``secret`` field satisfies it — a base URL or GCP scalar alone can't\n * authenticate anything. */\n secret?: boolean;\n /** Render as a multi-line textarea rather than a single-line input. Set for\n * file-content credentials the user pastes verbatim (Codex ``auth.json``,\n * Gemini Vertex service-account / ADC JSON) — the ones the SDK materialises\n * to disk, which a cloud backend can't consume yet (agent-canvas#1016). */\n multiline?: boolean;\n /** i18n key for the one-line helper text under the field. */\n hint_key: I18nKey;\n /**\n * Interpolation values for {@link hint_key} (e.g. ``{ file: \"~/.codex/auth.json\" }``\n * for the shared \"paste the file contents\" hint). Omitted for hints that\n * take no parameters.\n */\n hint_values?: Record<string, string>;\n}\n\n/**\n * Container credentials — beyond the registry's API key / base URL — that\n * canvas collects per provider so a *containerized* agent-server (no host login\n * state) can authenticate the ACP CLI: subscription tokens plus the file-content\n * blobs and Vertex config the SDK's ``acp_file_secrets`` materialisation\n * consumes (agent-canvas#1013/#1014). They have no registry env-var entry\n * because they're a deployment concern, not a model registry field. The Gemini\n * project/location/flag are plain config, not secrets — grouped here because\n * they travel with the SA blob.\n *\n * The blob *names* (``CODEX_AUTH_JSON`` / ``GOOGLE_APPLICATION_CREDENTIALS_JSON``)\n * duplicate the SDK registry's ``file_secrets`` specs; derive them from the\n * client registry once the pinned ``@openhands/typescript-client`` mirrors that\n * field (same bump that unblocks ``acp_isolate_data_dir``, see #1019).\n */\nconst ACP_RESERVED_CREDENTIALS: Record<string, ACPProviderSecretField[]> = {\n codex: [\n {\n name: \"CODEX_AUTH_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: { file: \"~/.codex/auth.json\" },\n },\n ],\n \"claude-code\": [\n {\n name: \"CLAUDE_CODE_OAUTH_TOKEN\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT,\n },\n ],\n \"gemini-cli\": [\n {\n name: \"GOOGLE_APPLICATION_CREDENTIALS_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: {\n file: \"~/.config/gcloud/application_default_credentials.json\",\n },\n },\n {\n name: \"GOOGLE_CLOUD_PROJECT\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT,\n },\n {\n name: \"GOOGLE_CLOUD_LOCATION\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT,\n },\n {\n name: \"GOOGLE_GENAI_USE_VERTEXAI\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT,\n },\n ],\n};\n\n/**\n * Credential pairs that break each other at runtime, keyed by provider —\n * mirrors the SDK's ``_ENV_CONFLICT_MAP`` (software-agent-sdk#3588). Claude's\n * OAuth token (``CLAUDE_CODE_OAUTH_TOKEN``) authenticates against Anthropic\n * directly, so when it is set the SDK strips:\n * - ``ANTHROPIC_API_KEY`` — otherwise it takes precedence and silently\n * bypasses the subscription;\n * - ``ANTHROPIC_BASE_URL`` — otherwise it proxies the bearer to an endpoint\n * that rejects it (see docs/ACP_AGENTS.md).\n * Either one set alongside the token is silently ignored at runtime, so warn.\n */\nconst ACP_CREDENTIAL_CONFLICTS: Record<string, Array<[string, string]>> = {\n \"claude-code\": [\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_API_KEY\"],\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_BASE_URL\"],\n ],\n};\n\n/**\n * The ``[credential, conflicting]`` pairs the user currently has set for\n * ``providerKey``. ``hasValue`` should cover both typed and already-saved\n * values, since a previously saved secret conflicts just the same.\n */\nexport function getAcpCredentialConflicts(\n key: string | null | undefined,\n hasValue: (name: string) => boolean,\n): Array<[string, string]> {\n if (!key) return [];\n return (ACP_CREDENTIAL_CONFLICTS[key] ?? []).filter(\n ([credential, conflicting]) =>\n hasValue(credential) && hasValue(conflicting),\n );\n}\n\n/**\n * Vertex AI-safe ``acp_model`` for Gemini.\n *\n * Left unset, gemini-cli falls back to its own internal default — a preview\n * flash model that 404s on Google Cloud projects without preview access. And a\n * ``*-flash`` id is no safer to pin: gemini-cli re-resolves flash ids at\n * generation time to its *current default* flash (software-agent-sdk#3532), so\n * a pinned ``gemini-2.5-flash`` still ran ``gemini-3-flash``. Only a non-flash\n * id sticks; ``gemini-2.5-pro`` is broadly available on Vertex and the\n * API-key / Google-login paths, so canvas preselects it. Kept as a named\n * constant so every model-default consumer agrees.\n */\nexport const ACP_VERTEX_SAFE_MODEL = \"gemini-2.5-pro\";\n\n/**\n * The default ``acp_model`` canvas substitutes for ``providerKey`` wherever no\n * concrete model is configured — settings seeding (onboarding, Settings →\n * Agent), the {@link buildAcpAgentSettingsDiff} fallback, and the start-request\n * fallback for a saved ``null``. Overrides only Gemini (→\n * {@link ACP_VERTEX_SAFE_MODEL}, see why above); every other provider keeps its\n * registry {@link ACPProviderConfig.default_model}. Returns ``null`` when\n * there's no override and no registry default, letting the ACP server pick its\n * own.\n *\n * Distinct from {@link ACPProviderConfig.default_model} (which mirrors the SDK\n * registry verbatim, closing agent-canvas#740): this is the *preferred* default,\n * deliberately diverging for Gemini where the registry value isn't safe on\n * every backend — so every default-model surface must route through this, not\n * read ``default_model`` directly.\n */\nexport function getAcpPreferredDefaultModel(\n key: string | null | undefined,\n): string | null {\n if (key === \"gemini-cli\") return ACP_VERTEX_SAFE_MODEL;\n return getAcpProvider(key)?.default_model ?? null;\n}\n\n/**\n * List the credentials Canvas should prompt for when onboarding the given ACP\n * provider. The API-key and base-URL field *names* track the SDK registry's\n * ``api_key_env_var`` / ``base_url_env_var`` (mirrored via\n * ``@openhands/typescript-client``) so they can't drift as providers are added\n * or renamed; the per-provider container credentials (subscription / Vertex\n * blobs) come from {@link ACP_RESERVED_CREDENTIALS}, since those are a\n * containerized-deployment concern with no model-registry entry. Each field\n * name equals the env var the agent-server exports into the provider subprocess\n * (which is what makes a saved secret reach the CLI). On the wire none of this\n * is special — every saved credential travels uniformly as a ``LookupSecret``.\n *\n * Field order is: container subscription/Vertex credentials → API key →\n * optional base URL. Every field is optional at the UI level — the step is\n * skippable, and a subscription / OAuth login on the backend takes precedence\n * over a key at runtime. (Whether the *step* is required is a\n * backend-capability decision the onboarding modal makes; see\n * ``backendRequiresAcpCredentials``.)\n *\n * NB: the base URL is rendered plain-text (not ``secret``), so it never counts\n * toward a required credential step — setting ``ANTHROPIC_BASE_URL`` alongside\n * a ``CLAUDE_CODE_OAUTH_TOKEN`` breaks the token's bearer auth, which the forms\n * surface via {@link getAcpCredentialConflicts}.\n *\n * Returns ``[]`` for OpenHands, the ``\"custom\"`` preset, any unknown key, and a\n * future OAuth-only provider whose registry entry has no ``api_key_env_var`` —\n * callers 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 // Surfaced providers only. The client registry grows with every harness the\n // SDK adds, so reading it directly would offer credential fields for one\n // Canvas never lists.\n if (!getAcpProvider(key)) return [];\n const info = getClientAcpProvider(key);\n if (!info) return [];\n // Subscription / Vertex credentials first — they're the primary auth path for\n // ACP providers (Claude Pro/Max OAuth token, Codex ChatGPT auth.json), with\n // the API key as the fallback below.\n const fields: ACPProviderSecretField[] = [\n ...(ACP_RESERVED_CREDENTIALS[key] ?? []),\n ];\n if (info.api_key_env_var) {\n fields.push({\n name: info.api_key_env_var,\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n });\n }\n if (info.base_url_env_var) {\n fields.push({\n name: info.base_url_env_var,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n });\n }\n return fields;\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 // Undefined model → the *preferred* default (Vertex-safe for Gemini), not\n // the raw registry default — see getAcpPreferredDefaultModel.\n const model =\n options.model === undefined\n ? getAcpPreferredDefaultModel(providerKey)\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":";;;AA0BA,SAAS,EAAa,GAA+B;CACnD,IAAI,OAAO,KAAU,UAAU,OAAO;CACtC,IAAM,IAAU,EAAM,KAAK;CAG3B,OAFI,CAAC,KACD,MAAA,gBAAyC,OACtC;AACT;AAeA,SAAgB,EAAyB,GAMvB;CAChB,KAAK,IAAM,KAAa;EACtB,EAAO;EACP,EAAO;EACP,EAAO;EACP,EAAO;CACT,GAAG;EACD,IAAM,IAAQ,EAAa,CAAS;EACpC,IAAI,GAAO,OAAO;CACpB;CACA,OAAO,EAAO,mBAAmB;AACnC;AAgEA,IAAM,IAGF;CACF,eAAe;EACb,MAAM;EACN,iBAAiB,EAAQ;CAC3B;CACA,OAAO;EACL,MAAM;EACN,iBAAiB,EAAQ;CAC3B;CACA,cAAc;EACZ,MAAM;EACN,iBAAiB,EAAQ;CAC3B;AACF,GAoBa,IAAqC,OAAO,QACvD,CACF,EAAE,KAAK,CAAC,GAAK,OAAQ;CACnB,IAAM,IAAO,EAAqB,CAAG;CACrC,OAAO;EACL;EACA,cAAc,GAAM,gBAAgB;EACpC,iBAAiB,IAAO,CAAC,GAAG,EAAK,eAAe,IAAI,CAAC;EACrD,kBAAkB,GAAM,kBAAkB,KAAK,OAAW;GACxD,IAAI,EAAM;GACV,OAAO,EAAM;EACf,EAAE;EACF,eAAe,GAAM,iBAAiB,KAAA;EACtC,iBAAiB,EAAG;EACpB,MAAM,EAAG;CACX;AACF,CAAC;AAyDe,EAAQ,sCAQR,EAAQ,wCAQR,EAAQ,sCAOR,EAAQ,wCAIR,EAAQ,yCAIR,EAAQ;AAmDxB,IAAa,IAAwB;AAkBrC,SAAgB,EACd,GACe;CAEf,OADI,MAAQ,eAAqB,IAC1B,EAAe,CAAG,GAAG,iBAAiB;AAC/C;AAuEA,SAAgB,EACd,GAC+B;CAC1B,OACL,OAAO,EAAc,MAAM,MAAa,EAAS,QAAQ,CAAG;AAC9D;AAkBA,SAAgB,EACd,GACe;CACf,IAAM,IAAQ,EAAe,CAAG;CAChC,OAAO,IAAQ,EAAM,eAAe;AACtC;AAWA,SAAgB,EACd,GACiB;CACjB,OAAO,EAAe,CAAG,GAAG,QAAA;AAC9B;AAYA,SAAgB,EACd,GACA,GACe;CAIf,OAHK,IACY,EAAe,CAClB,GAAU,kBAAkB,MAAM,MAAM,EAAE,OAAO,CAAO,GACxD,SAAS,IAHF;AAIvB"}
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// 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.\n//\n// NB: ``\"default\"`` is NOT a placeholder. claude-agent-acp 0.44+ exposes\n// ``default`` (\"Default (recommended)\") as a real, selectable model in its\n// configOptions select — the server reports it as ``currentModelId`` and lists\n// it in ``availableModels`` — so it must surface like any other model id.\nexport const ACP_MANAGED_SENTINEL = \"acp-managed\";\n\n/**\n * Filter for \"real\" ACP model strings — non-empty and not the legacy\n * ``acp-managed`` sentinel. Returns the trimmed value on success, ``null``\n * 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 (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 * ``@agentclientprotocol/codex-acp`` (the codex ACP 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. Its keys are the harnesses Canvas\n// offers — see {@link SURFACED_ACP_PROVIDERS}.\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\nfunction getAvailableModels(key: string): ACPModelOption[] | undefined {\n return getClientAcpProvider(key)?.available_models?.map((model) => ({\n id: model.id,\n label: model.label,\n }));\n}\n\n/**\n * The ACP harnesses Canvas surfaces — its own declaration of what it offers,\n * independent of what the SDK registry happens to contain. Registering a\n * harness upstream is a no-op here: nothing in Canvas enumerates the registry,\n * so there is no list to keep in step with it.\n */\nexport const SURFACED_ACP_PROVIDERS: readonly string[] =\n Object.keys(ACP_PROVIDER_UI);\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.\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: getAvailableModels(key),\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, OAuth tokens, credential\n * blobs) rather than a plain-text input (base URLs, project IDs). Doubles as\n * \"this is an actual credential\": when the onboarding step is required, only\n * a ``secret`` field satisfies it — a base URL or GCP scalar alone can't\n * authenticate anything. */\n secret?: boolean;\n /** Render as a multi-line textarea rather than a single-line input. Set for\n * file-content credentials the user pastes verbatim (Codex ``auth.json``,\n * Gemini Vertex service-account / ADC JSON) — the ones the SDK materialises\n * to disk, which a cloud backend can't consume yet (agent-canvas#1016). */\n multiline?: boolean;\n /** i18n key for the one-line helper text under the field. */\n hint_key: I18nKey;\n /**\n * Interpolation values for {@link hint_key} (e.g. ``{ file: \"~/.codex/auth.json\" }``\n * for the shared \"paste the file contents\" hint). Omitted for hints that\n * take no parameters.\n */\n hint_values?: Record<string, string>;\n}\n\n/**\n * Container credentials — beyond the registry's API key / base URL — that\n * canvas collects per provider so a *containerized* agent-server (no host login\n * state) can authenticate the ACP CLI: subscription tokens plus the file-content\n * blobs and Vertex config the SDK's ``acp_file_secrets`` materialisation\n * consumes (agent-canvas#1013/#1014). They have no registry env-var entry\n * because they're a deployment concern, not a model registry field. The Gemini\n * project/location/flag are plain config, not secrets — grouped here because\n * they travel with the SA blob.\n *\n * The blob *names* (``CODEX_AUTH_JSON`` / ``GOOGLE_APPLICATION_CREDENTIALS_JSON``)\n * duplicate the SDK registry's ``file_secrets`` specs; derive them from the\n * client registry once the pinned ``@openhands/typescript-client`` mirrors that\n * field (same bump that unblocks ``acp_isolate_data_dir``, see #1019).\n */\nconst ACP_RESERVED_CREDENTIALS: Record<string, ACPProviderSecretField[]> = {\n codex: [\n {\n name: \"CODEX_AUTH_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: { file: \"~/.codex/auth.json\" },\n },\n ],\n \"claude-code\": [\n {\n name: \"CLAUDE_CODE_OAUTH_TOKEN\",\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_OAUTH_TOKEN_HINT,\n },\n ],\n \"gemini-cli\": [\n {\n name: \"GOOGLE_APPLICATION_CREDENTIALS_JSON\",\n secret: true,\n multiline: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_FILE_BLOB_HINT,\n hint_values: {\n file: \"~/.config/gcloud/application_default_credentials.json\",\n },\n },\n {\n name: \"GOOGLE_CLOUD_PROJECT\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_PROJECT_HINT,\n },\n {\n name: \"GOOGLE_CLOUD_LOCATION\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_GCP_LOCATION_HINT,\n },\n {\n name: \"GOOGLE_GENAI_USE_VERTEXAI\",\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_VERTEXAI_FLAG_HINT,\n },\n ],\n};\n\n/**\n * Credential pairs that break each other at runtime, keyed by provider —\n * mirrors the SDK's ``_ENV_CONFLICT_MAP`` (software-agent-sdk#3588). Claude's\n * OAuth token (``CLAUDE_CODE_OAUTH_TOKEN``) authenticates against Anthropic\n * directly, so when it is set the SDK strips:\n * - ``ANTHROPIC_API_KEY`` — otherwise it takes precedence and silently\n * bypasses the subscription;\n * - ``ANTHROPIC_BASE_URL`` — otherwise it proxies the bearer to an endpoint\n * that rejects it (see docs/ACP_AGENTS.md).\n * Either one set alongside the token is silently ignored at runtime, so warn.\n */\nconst ACP_CREDENTIAL_CONFLICTS: Record<string, Array<[string, string]>> = {\n \"claude-code\": [\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_API_KEY\"],\n [\"CLAUDE_CODE_OAUTH_TOKEN\", \"ANTHROPIC_BASE_URL\"],\n ],\n};\n\n/**\n * The ``[credential, conflicting]`` pairs the user currently has set for\n * ``providerKey``. ``hasValue`` should cover both typed and already-saved\n * values, since a previously saved secret conflicts just the same.\n */\nexport function getAcpCredentialConflicts(\n key: string | null | undefined,\n hasValue: (name: string) => boolean,\n): Array<[string, string]> {\n if (!key) return [];\n return (ACP_CREDENTIAL_CONFLICTS[key] ?? []).filter(\n ([credential, conflicting]) =>\n hasValue(credential) && hasValue(conflicting),\n );\n}\n\n/**\n * Vertex AI-safe ``acp_model`` for Gemini.\n *\n * Left unset, gemini-cli falls back to its own internal default — a preview\n * flash model that 404s on Google Cloud projects without preview access. And a\n * ``*-flash`` id is no safer to pin: gemini-cli re-resolves flash ids at\n * generation time to its *current default* flash (software-agent-sdk#3532), so\n * a pinned ``gemini-2.5-flash`` still ran ``gemini-3-flash``. Only a non-flash\n * id sticks; ``gemini-2.5-pro`` is broadly available on Vertex and the\n * API-key / Google-login paths, so canvas preselects it. Kept as a named\n * constant so every model-default consumer agrees.\n */\nexport const ACP_VERTEX_SAFE_MODEL = \"gemini-2.5-pro\";\n\n/**\n * The default ``acp_model`` canvas substitutes for ``providerKey`` wherever no\n * concrete model is configured — settings seeding (onboarding, Settings →\n * Agent), the {@link buildAcpAgentSettingsDiff} fallback, and the start-request\n * fallback for a saved ``null``. Overrides only Gemini (→\n * {@link ACP_VERTEX_SAFE_MODEL}, see why above); every other provider keeps its\n * registry {@link ACPProviderConfig.default_model}. Returns ``null`` when\n * there's no override and no registry default, letting the ACP server pick its\n * own.\n *\n * Distinct from {@link ACPProviderConfig.default_model} (which mirrors the SDK\n * registry verbatim, closing agent-canvas#740): this is the *preferred* default,\n * deliberately diverging for Gemini where the registry value isn't safe on\n * every backend — so every default-model surface must route through this, not\n * read ``default_model`` directly.\n */\nexport function getAcpPreferredDefaultModel(\n key: string | null | undefined,\n): string | null {\n if (key === \"gemini-cli\") return ACP_VERTEX_SAFE_MODEL;\n return getAcpProvider(key)?.default_model ?? null;\n}\n\n/**\n * List the credentials Canvas should prompt for when onboarding the given ACP\n * provider. The API-key and base-URL field *names* track the SDK registry's\n * ``api_key_env_var`` / ``base_url_env_var`` (mirrored via\n * ``@openhands/typescript-client``) so they can't drift as providers are added\n * or renamed; the per-provider container credentials (subscription / Vertex\n * blobs) come from {@link ACP_RESERVED_CREDENTIALS}, since those are a\n * containerized-deployment concern with no model-registry entry. Each field\n * name equals the env var the agent-server exports into the provider subprocess\n * (which is what makes a saved secret reach the CLI). On the wire none of this\n * is special — every saved credential travels uniformly as a ``LookupSecret``.\n *\n * Field order is: container subscription/Vertex credentials → API key →\n * optional base URL. Every field is optional at the UI level — the step is\n * skippable, and a subscription / OAuth login on the backend takes precedence\n * over a key at runtime. (Whether the *step* is required is a\n * backend-capability decision the onboarding modal makes; see\n * ``backendRequiresAcpCredentials``.)\n *\n * NB: the base URL is rendered plain-text (not ``secret``), so it never counts\n * toward a required credential step — setting ``ANTHROPIC_BASE_URL`` alongside\n * a ``CLAUDE_CODE_OAUTH_TOKEN`` breaks the token's bearer auth, which the forms\n * surface via {@link getAcpCredentialConflicts}.\n *\n * Returns ``[]`` for OpenHands, the ``\"custom\"`` preset, any unknown key, and a\n * future OAuth-only provider whose registry entry has no ``api_key_env_var`` —\n * callers 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 // Surfaced providers only. The client registry grows with every harness the\n // SDK adds, so reading it directly would offer credential fields for one\n // Canvas never lists.\n if (!getAcpProvider(key)) return [];\n const info = getClientAcpProvider(key);\n if (!info) return [];\n // Subscription / Vertex credentials first — they're the primary auth path for\n // ACP providers (Claude Pro/Max OAuth token, Codex ChatGPT auth.json), with\n // the API key as the fallback below.\n const fields: ACPProviderSecretField[] = [\n ...(ACP_RESERVED_CREDENTIALS[key] ?? []),\n ];\n if (info.api_key_env_var) {\n fields.push({\n name: info.api_key_env_var,\n secret: true,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_API_KEY_HINT,\n });\n }\n if (info.base_url_env_var) {\n fields.push({\n name: info.base_url_env_var,\n hint_key: I18nKey.ONBOARDING$ACP_SECRET_BASE_URL_HINT,\n });\n }\n return fields;\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 // Undefined model → the *preferred* default (Vertex-safe for Gemini), not\n // the raw registry default — see getAcpPreferredDefaultModel.\n const model =\n options.model === undefined\n ? getAcpPreferredDefaultModel(providerKey)\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":";;;AA0BA,SAAS,EAAa,GAA+B;CACnD,IAAI,OAAO,KAAU,UAAU,OAAO;CACtC,IAAM,IAAU,EAAM,KAAK;CAG3B,OAFI,CAAC,KACD,MAAA,gBAAyC,OACtC;AACT;AAeA,SAAgB,EAAyB,GAMvB;CAChB,KAAK,IAAM,KAAa;EACtB,EAAO;EACP,EAAO;EACP,EAAO;EACP,EAAO;CACT,GAAG;EACD,IAAM,IAAQ,EAAa,CAAS;EACpC,IAAI,GAAO,OAAO;CACpB;CACA,OAAO,EAAO,mBAAmB;AACnC;AAgEA,IAAM,IAGF;CACF,eAAe;EACb,MAAM;EACN,iBAAiB,EAAQ;CAC3B;CACA,OAAO;EACL,MAAM;EACN,iBAAiB,EAAQ;CAC3B;CACA,cAAc;EACZ,MAAM;EACN,iBAAiB,EAAQ;CAC3B;AACF;AAEA,SAAS,EAAmB,GAA2C;CACrE,OAAO,EAAqB,CAAG,GAAG,kBAAkB,KAAK,OAAW;EAClE,IAAI,EAAM;EACV,OAAO,EAAM;CACf,EAAE;AACJ;AAcA,IAAa,IAAqC,OAAO,QACvD,CACF,EAAE,KAAK,CAAC,GAAK,OAAQ;CACnB,IAAM,IAAO,EAAqB,CAAG;CACrC,OAAO;EACL;EACA,cAAc,GAAM,gBAAgB;EACpC,iBAAiB,IAAO,CAAC,GAAG,EAAK,eAAe,IAAI,CAAC;EACrD,kBAAkB,EAAmB,CAAG;EACxC,eAAe,GAAM,iBAAiB,KAAA;EACtC,iBAAiB,EAAG;EACpB,MAAM,EAAG;CACX;AACF,CAAC;AAyDe,EAAQ,sCAQR,EAAQ,wCAQR,EAAQ,sCAOR,EAAQ,wCAIR,EAAQ,yCAIR,EAAQ;AAmDxB,IAAa,IAAwB;AAkBrC,SAAgB,EACd,GACe;CAEf,OADI,MAAQ,eAAqB,IAC1B,EAAe,CAAG,GAAG,iBAAiB;AAC/C;AAuEA,SAAgB,EACd,GAC+B;CAC1B,OACL,OAAO,EAAc,MAAM,MAAa,EAAS,QAAQ,CAAG;AAC9D;AAkBA,SAAgB,EACd,GACe;CACf,IAAM,IAAQ,EAAe,CAAG;CAChC,OAAO,IAAQ,EAAM,eAAe;AACtC;AAWA,SAAgB,EACd,GACiB;CACjB,OAAO,EAAe,CAAG,GAAG,QAAA;AAC9B;AAYA,SAAgB,EACd,GACA,GACe;CAIf,OAHK,IACY,EAAe,CAClB,GAAU,kBAAkB,MAAM,MAAM,EAAE,OAAO,CAAO,GACxD,SAAS,IAHF;AAIvB"}
@@ -0,0 +1,21 @@
1
+ /** Picker state for a profile's scope fields: server default, or an explicit list. */
2
+ export type ProfileScopeMode = "standard" | "custom";
3
+ /**
4
+ * Read a stored profile's named scope (MCP servers or secrets) into picker state.
5
+ *
6
+ * Tri-state on the wire: `null`/absent = every configured server, an array =
7
+ * only those keys (`[]` = none).
8
+ */
9
+ export declare function readProfileScope(value: unknown): {
10
+ mode: ProfileScopeMode;
11
+ selected: string[];
12
+ };
13
+ /**
14
+ * Whether two scope selections name the same servers.
15
+ *
16
+ * Compared as a set: the resolver de-duplicates refs and uses them as an
17
+ * allow-list, so neither order nor a repeat changes what the agent gets. The
18
+ * editor emits catalog order while a stored profile keeps whatever order it was
19
+ * written in, so an order-sensitive check reports a profile dirty on open.
20
+ */
21
+ export declare function sameScopeSelection(a: string[], b: string[]): boolean;
@@ -1,2 +1,2 @@
1
- require("../../_virtual/_rolldown/runtime.cjs");const e=require("../query/query-keys.cjs"),t=require("../../node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs"),n=require("../../node_modules/@tanstack/react-query/build/modern/useMutation.cjs"),r=require("../../contexts/active-backend-context.cjs"),i=require("../../api/conversation-metadata-store.cjs"),a=require("../../api/profiles-service/profiles-service.api.cjs"),o=require("../../api/conversation-service/agent-server-conversation-service.api.cjs"),s=require("../use-tracking.cjs"),c=require("../query/use-llm-profiles.cjs"),l=require("../../api/agent-profiles-service/agent-profiles-service.api.cjs"),u=require("../query/use-agent-profiles.cjs"),d=require("../../api/plugins-management-service.cjs"),f=require("../../utils/plugin-display.cjs");var p=[`create-conversation`],m=()=>{let m=t.useQueryClient(),{trackConversationCreated:h}=s.useTracking(),{data:g}=c.useLlmProfiles(),{backend:_,orgId:v}=r.useActiveBackend();return u.useAgentProfiles(),n.useMutation({mutationKey:p,mutationFn:async t=>{let{query:n,conversationInstructions:r,plugins:s,repository:c,workingDir:u,workspaceMode:p,parentConversationId:h,agentType:y,agentProfileId:b}=t,x=await m.ensureQueryData({queryKey:[...e.AGENT_PROFILES_QUERY_KEYS.all,_.id,v],queryFn:l.default.listProfiles,...e.AGENT_PROFILES_RETRY_OPTIONS}),S=b??x.active_agent_profile_id??void 0,C=S?x?.profiles?.find(e=>e.id===S):void 0,w=_.kind===`cloud`,T=S,E=null;if(!w&&C?.name==="default"&&C?.agent_kind===`openhands`)T=void 0;else if(C?.agent_kind===`openhands`&&C.llm_profile_ref){let t=!1;try{let n=await m.ensureQueryData({queryKey:[...e.LLM_PROFILES_QUERY_KEYS.all,_.id,v],queryFn:a.default.listProfiles,retry:!1});t=n.profiles.some(e=>e.name===C.llm_profile_ref),E=n.active_profile??null}catch{}t?!w&&!b&&E&&E!==C.llm_profile_ref&&(T=void 0):(console.warn(`Agent profile "${C.name}" references missing LLM profile "${C.llm_profile_ref}"; launching from agent_settings instead.`),T=void 0)}let D=await o.default.createConversation({initialUserMsg:n,conversationInstructions:r,plugins:s,metadata:c?{selected_repository:c.name,selected_branch:c.branch??null,git_provider:c.gitProvider}:null,workingDirOverride:u,workspaceMode:p,parentConversationId:h,agentType:y,...T?{agentProfileId:T,agentProfileKind:C?.agent_kind}:{}}),O=D.app_conversation_id,k=s?.map(i.toPluginCoordinates)??[],A=k;if(O){let t=[];try{t=await m.ensureQueryData({queryKey:[...e.PLUGINS_QUERY_KEYS.installed,_.id,v],queryFn:()=>d.default.listInstalledPlugins()})}catch{}let n=new Set(k.map(f.pluginReferenceKey)),r=t.filter(e=>e.enabled).map(e=>({source:e.source,ref:e.resolved_ref??null,repo_path:e.repo_path??null,name:e.name})).filter(e=>!n.has(f.pluginReferenceKey(e)));A=[...k,...r]}let j=T&&C?.agent_kind===`openhands`?C.llm_profile_ref:E??g?.active_profile??null;if(O&&(j||A.length)){let e=i.getStoredConversationMetadata(O);i.setStoredConversationMetadata(O,{selected_repository:e?.selected_repository??null,selected_branch:e?.selected_branch??null,git_provider:e?.git_provider??null,selected_workspace:e?.selected_workspace??null,workspace_mode:e?.workspace_mode??null,active_profile:j??e?.active_profile??null,plugins:A.length?A:e?.plugins??null})}return{conversation_id:D.app_conversation_id?D.app_conversation_id:`task-${D.id}`,session_api_key:null,url:D.agent_server_url,task_id:D.id}},onSuccess:async(e,t)=>{h({conversationId:e.conversation_id,taskId:e.task_id,hasRepository:!!t.repository,gitProvider:t.repository?.gitProvider,hasWorkspace:!!t.workingDir,workspaceMode:t.workspaceMode,hasInitialQuery:!!t.query,agentType:t.agentType,hasParentConversation:!!t.parentConversationId,entryPoint:t.entryPoint}),m.invalidateQueries({queryKey:[`user`,`conversations`]}),m.invalidateQueries({queryKey:[`start-tasks`]})}})};exports.CREATE_CONVERSATION_MUTATION_KEY=p,exports.useCreateConversation=m;
1
+ require("../../_virtual/_rolldown/runtime.cjs");const e=require("../query/query-keys.cjs"),t=require("../../node_modules/@tanstack/react-query/build/modern/QueryClientProvider.cjs"),n=require("../../node_modules/@tanstack/react-query/build/modern/useMutation.cjs"),r=require("../../contexts/active-backend-context.cjs"),i=require("../../api/conversation-metadata-store.cjs"),a=require("../../api/profiles-service/profiles-service.api.cjs"),o=require("../../api/conversation-service/agent-server-conversation-service.api.cjs"),s=require("../use-tracking.cjs"),c=require("../query/use-llm-profiles.cjs"),l=require("../../api/agent-profiles-service/agent-profiles-service.api.cjs"),u=require("../query/use-agent-profiles.cjs"),d=require("../../api/plugins-management-service.cjs"),f=require("../../utils/plugin-display.cjs");var p=[`create-conversation`],m=()=>{let m=t.useQueryClient(),{trackConversationCreated:h}=s.useTracking(),{data:g}=c.useLlmProfiles(),{backend:_,orgId:v}=r.useActiveBackend();return u.useAgentProfiles(),n.useMutation({mutationKey:p,mutationFn:async t=>{let{query:n,conversationInstructions:r,plugins:s,repository:c,workingDir:u,workspaceMode:p,parentConversationId:h,agentType:y,agentProfileId:b}=t,x=await m.ensureQueryData({queryKey:[...e.AGENT_PROFILES_QUERY_KEYS.all,_.id,v],queryFn:l.default.listProfiles,...e.AGENT_PROFILES_RETRY_OPTIONS}),S=b??x.active_agent_profile_id??void 0,C=S?x?.profiles?.find(e=>e.id===S):void 0,w=_.kind===`cloud`,T=!1,E=null;if(!w&&C?.name==="default"&&C?.agent_kind===`openhands`)T=!0;else if(C?.agent_kind===`openhands`&&C.llm_profile_ref){let t=!1;try{let n=await m.ensureQueryData({queryKey:[...e.LLM_PROFILES_QUERY_KEYS.all,_.id,v],queryFn:a.default.listProfiles,retry:!1});t=n.profiles.some(e=>e.name===C.llm_profile_ref),E=n.active_profile??null}catch{}t?!w&&!b&&E&&E!==C.llm_profile_ref&&(T=!0):(console.warn(`Agent profile "${C.name}" references missing LLM profile "${C.llm_profile_ref}"; launching from agent_settings instead.`),T=!0)}let D=!0;if(T&&C)try{let t=(await m.fetchQuery({queryKey:e.AGENT_PROFILES_QUERY_KEYS.detail(_.id,v,C.name),queryFn:()=>l.default.getProfile(C.name),...e.AGENT_PROFILES_RETRY_OPTIONS})).profile.secret_refs;D=!Array.isArray(t)}catch{D=!1}let O=T&&D?void 0:S,k=await o.default.createConversation({initialUserMsg:n,conversationInstructions:r,plugins:s,metadata:c?{selected_repository:c.name,selected_branch:c.branch??null,git_provider:c.gitProvider}:null,workingDirOverride:u,workspaceMode:p,parentConversationId:h,agentType:y,...O?{agentProfileId:O,agentProfileKind:C?.agent_kind}:{}}),A=k.app_conversation_id,j=s?.map(i.toPluginCoordinates)??[],M=j;if(A){let t=[];try{t=await m.ensureQueryData({queryKey:[...e.PLUGINS_QUERY_KEYS.installed,_.id,v],queryFn:()=>d.default.listInstalledPlugins()})}catch{}let n=new Set(j.map(f.pluginReferenceKey)),r=t.filter(e=>e.enabled).map(e=>({source:e.source,ref:e.resolved_ref??null,repo_path:e.repo_path??null,name:e.name})).filter(e=>!n.has(f.pluginReferenceKey(e)));M=[...j,...r]}let N=O&&C?.agent_kind===`openhands`?C.llm_profile_ref:E??g?.active_profile??null;if(A&&(N||M.length)){let e=i.getStoredConversationMetadata(A);i.setStoredConversationMetadata(A,{selected_repository:e?.selected_repository??null,selected_branch:e?.selected_branch??null,git_provider:e?.git_provider??null,selected_workspace:e?.selected_workspace??null,workspace_mode:e?.workspace_mode??null,active_profile:N??e?.active_profile??null,plugins:M.length?M:e?.plugins??null})}return{conversation_id:k.app_conversation_id?k.app_conversation_id:`task-${k.id}`,session_api_key:null,url:k.agent_server_url,task_id:k.id}},onSuccess:async(e,t)=>{h({conversationId:e.conversation_id,taskId:e.task_id,hasRepository:!!t.repository,gitProvider:t.repository?.gitProvider,hasWorkspace:!!t.workingDir,workspaceMode:t.workspaceMode,hasInitialQuery:!!t.query,agentType:t.agentType,hasParentConversation:!!t.parentConversationId,entryPoint:t.entryPoint}),m.invalidateQueries({queryKey:[`user`,`conversations`]}),m.invalidateQueries({queryKey:[`start-tasks`]})}})};exports.CREATE_CONVERSATION_MUTATION_KEY=p,exports.useCreateConversation=m;
2
2
  //# sourceMappingURL=use-create-conversation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-create-conversation.cjs","names":[],"sources":["../../../src/hooks/mutation/use-create-conversation.ts"],"sourcesContent":["import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport AgentServerConversationService from \"#/api/conversation-service/agent-server-conversation-service.api\";\nimport { PluginSpec } from \"#/api/conversation-service/agent-server-conversation-service.types\";\nimport { SuggestedTask } from \"#/utils/types\";\nimport { Provider } from \"#/types/settings\";\nimport { useTracking } from \"#/hooks/use-tracking\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\nimport { useAgentProfiles } from \"#/hooks/query/use-agent-profiles\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport ProfilesService from \"#/api/profiles-service/profiles-service.api\";\nimport AgentProfilesService, {\n WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME,\n type AgentProfileListResponse,\n} from \"#/api/agent-profiles-service/agent-profiles-service.api\";\nimport PluginsManagementService, {\n type InstalledPluginInfo,\n} from \"#/api/plugins-management-service\";\nimport {\n PLUGINS_QUERY_KEYS,\n LLM_PROFILES_QUERY_KEYS,\n AGENT_PROFILES_QUERY_KEYS,\n AGENT_PROFILES_RETRY_OPTIONS,\n} from \"#/hooks/query/query-keys\";\nimport { pluginReferenceKey } from \"#/utils/plugin-display\";\nimport {\n getStoredConversationMetadata,\n setStoredConversationMetadata,\n toPluginCoordinates,\n type WorkspaceMode,\n} from \"#/api/conversation-metadata-store\";\n\nexport interface CreateConversationVariables {\n query?: string;\n repository?: {\n name: string;\n gitProvider: Provider;\n branch?: string;\n };\n suggestedTask?: SuggestedTask;\n conversationInstructions?: string;\n parentConversationId?: string;\n agentType?: \"default\" | \"plan\";\n plugins?: PluginSpec[];\n workingDir?: string;\n workspaceMode?: WorkspaceMode;\n // Launch from a specific AgentProfile (local backend). When omitted, the\n // active AgentProfile (if any) is used so home-composed conversations\n // launch from the user's selected profile (#3727).\n agentProfileId?: string;\n entryPoint?: string; // analytics only; not forwarded to the service\n}\n\nexport const CREATE_CONVERSATION_MUTATION_KEY = [\"create-conversation\"];\n\ninterface CreateConversationResponse {\n conversation_id: string;\n session_api_key: string | null;\n url: string | null;\n task_id?: string;\n}\n\nexport const useCreateConversation = () => {\n const queryClient = useQueryClient();\n const { trackConversationCreated } = useTracking();\n // Cache-warm on the home page (the profile picker reads the same query).\n // Stamped onto the conversation at creation so the switcher can show the\n // exact profile even when several profiles share a model (#1082).\n const { data: llmProfiles } = useLlmProfiles();\n // Warm the agent-profiles cache too — the launch path below awaits the same\n // query via ensureQueryData, so a warm cache makes home-launch instant. The\n // hook's maybe-unresolved data is deliberately NOT read at launch time:\n // activation is pointer-only (it never writes agent_settings), so racing a\n // cold cache into the agent_settings fallback would silently launch the\n // wrong agent.\n const { backend, orgId } = useActiveBackend();\n useAgentProfiles();\n\n return useMutation({\n mutationKey: CREATE_CONVERSATION_MUTATION_KEY,\n mutationFn: async (\n variables: CreateConversationVariables,\n ): Promise<CreateConversationResponse> => {\n const {\n query,\n conversationInstructions,\n plugins,\n repository,\n workingDir,\n workspaceMode,\n parentConversationId,\n agentType,\n agentProfileId,\n } = variables;\n\n // The active AgentProfile is the default launch profile for new\n // conversations (#3727), on both local and cloud (cloud gained\n // /api/agent-profiles in OpenHands #15060, #3730). Await the list from\n // the shared query cache: a send fired before the home query resolves\n // must still launch from the active profile. Do not fall back to the\n // global agent_settings when profile discovery fails: activation is\n // pointer-only, so those settings may describe a different agent.\n const agentProfiles: AgentProfileListResponse =\n await queryClient.ensureQueryData({\n queryKey: [...AGENT_PROFILES_QUERY_KEYS.all, backend.id, orgId],\n queryFn: AgentProfilesService.listProfiles,\n ...AGENT_PROFILES_RETRY_OPTIONS,\n });\n\n const requestedAgentProfileId =\n agentProfileId ?? agentProfiles.active_agent_profile_id ?? undefined;\n\n // Fall back to the legacy agent_settings launch when the resolved agent\n // profile can't resolve its LLM. The agent-server seeds a `default`\n // openhands profile whose `llm_profile_ref` can point at an LLM profile\n // that doesn't exist (fresh store, or one configured with named profiles\n // only); launching from it 404s (\"LLM profile '<ref>' not found\") and\n // would brick home-launch. agent_settings reflects the active LLM, so the\n // fallback degrades cleanly until the seed mirrors it (SDK #3933).\n // ACP profiles carry no llm_profile_ref, so they're never gated here.\n const resolvedAgentProfile = requestedAgentProfileId\n ? agentProfiles?.profiles?.find(\n (profile) => profile.id === requestedAgentProfileId,\n )\n : undefined;\n // Cloud has no `agent_settings` payload to fall back to — the downgrade\n // below only makes sense on local, where it exists and carries the\n // canvas-only enrichments. Gating it here keeps cloud always launching\n // from the resolved profile id, so the conversation gets\n // `launched_agent_profile` stamped and the profile's config applied\n // (#1571 review).\n const isCloud = backend.kind === \"cloud\";\n let effectiveAgentProfileId = requestedAgentProfileId;\n // The account-wide active LLM profile from the launch-path fetch below\n // (null when that fetch didn't run or failed). Fresher than the\n // `useLlmProfiles()` render snapshot, which a fast send can outrun.\n let fetchedActiveLlmProfile: string | null = null;\n if (\n !isCloud &&\n resolvedAgentProfile?.name === WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME &&\n resolvedAgentProfile?.agent_kind === \"openhands\"\n ) {\n // The seeded OpenHands `default` profile is the enriched baseline, not a\n // deliberate profile pick — it mirrors global agent_settings. Launch it\n // via agent_settings so the canvas-only enrichments the profile-resolution\n // path drops survive for the common home-launch: the <RUNTIME_SERVICES>\n // system-message suffix and project-skill loading (buildAgentContext).\n // Named profiles are deliberate custom configs and still use the profile\n // path (accepting that enrichment boundary).\n // Trade-off: per-profile fields set on `default` itself don't apply on\n // home-launch — custom per-profile config belongs in a named profile.\n //\n // Scoped to OpenHands: an ACP `default` must keep the profile path.\n // Activation is pointer-only, so global agent_settings is stale (often\n // still OpenHands) when an ACP profile is active — launching it via\n // agent_settings would start the wrong agent. ACP carries no\n // <RUNTIME_SERVICES> enrichment, so there's nothing to preserve.\n //\n // Scoped to local: cloud never writes agent_settings, so it always\n // resolves `default` server-side via agent_profile_id (validated below).\n effectiveAgentProfileId = undefined;\n } else if (\n resolvedAgentProfile?.agent_kind === \"openhands\" &&\n resolvedAgentProfile.llm_profile_ref\n ) {\n // Await the LLM-profile list rather than reading the maybe-unresolved\n // `useLlmProfiles()` result: a send fired before that query loads (or\n // after it errors) must still validate the ref, not launch blind.\n let llmProfileExists = false;\n try {\n const llm = await queryClient.ensureQueryData({\n queryKey: [...LLM_PROFILES_QUERY_KEYS.all, backend.id, orgId],\n queryFn: ProfilesService.listProfiles,\n // Match the agent-profiles fetch above: on a backend where this\n // errors, fall back to agent_settings immediately rather than\n // stalling the send through the default exponential backoff.\n retry: false,\n });\n llmProfileExists = llm.profiles.some(\n (profile) => profile.name === resolvedAgentProfile.llm_profile_ref,\n );\n fetchedActiveLlmProfile = llm.active_profile ?? null;\n } catch {\n // List unavailable → can't validate → fall back to agent_settings.\n }\n if (!llmProfileExists) {\n // Downgrade is silent in the UI; leave a diagnosable trace.\n console.warn(\n `Agent profile \"${resolvedAgentProfile.name}\" references missing ` +\n `LLM profile \"${resolvedAgentProfile.llm_profile_ref}\"; ` +\n \"launching from agent_settings instead.\",\n );\n effectiveAgentProfileId = undefined;\n } else if (\n !isCloud &&\n !agentProfileId &&\n fetchedActiveLlmProfile &&\n fetchedActiveLlmProfile !== resolvedAgentProfile.llm_profile_ref\n ) {\n // The home LLM pill shows — and its dropdown activates — the\n // account-wide active LLM profile, never the pinned ref\n // (useChatInputLlmProfileState), so when the two differ the launch\n // must run the selection or the UI advertises a model the\n // conversation won't use (#16539). Launch via agent_settings, which\n // the dropdown's profile activation syncs to the selection. Scoped\n // to the implicit active-profile launch: an explicit `agentProfileId`\n // (the in-conversation profile picker) is a deliberate profile pick,\n // so its pinned ref stays authoritative. Local-only like the\n // downgrades above — cloud has no agent_settings payload to fall\n // back to. Trade-off: the named profile's non-LLM config doesn't\n // apply to this launch; the start request has no per-launch LLM\n // override that could preserve it (AgentLaunchAdditions carries only\n // a system-message suffix).\n effectiveAgentProfileId = undefined;\n }\n }\n\n // Only extend the call with the profile fields when launching from a\n // profile, so a plain create stays byte-identical to the legacy\n // agent_settings path (#3727). sandboxId is unused here.\n const conversation =\n await AgentServerConversationService.createConversation({\n initialUserMsg: query,\n conversationInstructions,\n plugins,\n metadata: repository\n ? {\n selected_repository: repository.name,\n selected_branch: repository.branch ?? null,\n git_provider: repository.gitProvider,\n }\n : null,\n workingDirOverride: workingDir,\n workspaceMode,\n parentConversationId,\n agentType,\n ...(effectiveAgentProfileId\n ? {\n agentProfileId: effectiveAgentProfileId,\n agentProfileKind: resolvedAgentProfile?.agent_kind,\n }\n : {}),\n });\n\n // Stamp the active LLM profile onto the (local) conversation so the\n // chat switcher shows the exact profile even when several profiles\n // share a model (#1082). Cloud conversations don't use local profiles\n // (app_conversation_id stays null until the sandbox is READY). Merge so\n // the repo/workspace metadata the service just persisted is preserved.\n const localConversationId = conversation.app_conversation_id;\n // Snapshot the conversation's plugins into client-side metadata so the\n // in-conversation plugins view can show what's loaded (coordinates only\n // — strip parameters, which may carry secrets). The agent-server doesn't\n // return a live conversation's loaded plugins, so this snapshot is the\n // source for that view. Two sources, deduped by coordinates:\n // 1. plugins explicitly attached at creation (e.g. the /launch flow);\n // 2. enabled installed plugins, which the SDK auto-loads into every new\n // local conversation (see use-set-plugin-enabled).\n const explicitPlugins = plugins?.map(toPluginCoordinates) ?? [];\n let attachedPlugins: PluginSpec[] = explicitPlugins;\n if (localConversationId) {\n let installed: InstalledPluginInfo[] = [];\n try {\n installed = await queryClient.ensureQueryData({\n // Same key `usePlugins` writes. Unscoped, this served the previous\n // backend's list and attached plugins the target server lacks.\n queryKey: [...PLUGINS_QUERY_KEYS.installed, backend.id, orgId],\n queryFn: () => PluginsManagementService.listInstalledPlugins(),\n });\n } catch {\n // Best-effort: never let plugin lookup block conversation creation.\n }\n const seen = new Set(explicitPlugins.map(pluginReferenceKey));\n const enabledInstalled = installed\n .filter((plugin) => plugin.enabled)\n .map((plugin) => ({\n source: plugin.source,\n ref: plugin.resolved_ref ?? null,\n repo_path: plugin.repo_path ?? null,\n // Keep the human-friendly name so the plugins view shows e.g.\n // \"city-weather\" rather than deriving \"local\" from the source.\n name: plugin.name,\n }))\n .filter((plugin) => !seen.has(pluginReferenceKey(plugin)));\n attachedPlugins = [...explicitPlugins, ...enabledInstalled];\n }\n // A launch from a named OpenHands profile runs that profile's\n // `llm_profile_ref`, which can differ from the standalone active LLM\n // profile — stamp the ref so the switcher pill names the exact profile\n // the conversation runs (#1082). The agent_settings paths (the `default`\n // baseline, a dangling ref, or a dropdown override (#16539) — where\n // effectiveAgentProfileId is cleared) run the active LLM, so they stamp\n // the active profile, preferring the launch-path fetch over the hook's\n // render snapshot. ACP profiles carry no LLM profile, so they fall\n // through to the active-profile stamp (unused by the ACP model chip).\n const activeProfile =\n effectiveAgentProfileId &&\n resolvedAgentProfile?.agent_kind === \"openhands\"\n ? resolvedAgentProfile.llm_profile_ref\n : (fetchedActiveLlmProfile ?? llmProfiles?.active_profile ?? null);\n if (localConversationId && (activeProfile || attachedPlugins.length)) {\n const prev = getStoredConversationMetadata(localConversationId);\n setStoredConversationMetadata(localConversationId, {\n selected_repository: prev?.selected_repository ?? null,\n selected_branch: prev?.selected_branch ?? null,\n git_provider: prev?.git_provider ?? null,\n selected_workspace: prev?.selected_workspace ?? null,\n workspace_mode: prev?.workspace_mode ?? null,\n active_profile: activeProfile ?? prev?.active_profile ?? null,\n plugins: attachedPlugins.length\n ? attachedPlugins\n : (prev?.plugins ?? null),\n });\n }\n\n // OpenHands cloud pattern: when the start task isn't immediately\n // READY (cloud sandbox is still provisioning),\n // app_conversation_id is null. We return a `task-{id}` URL so the\n // conversation route's useTaskPolling can drive it to READY and\n // then redirect to the real `/conversations/{app_conversation_id}`.\n const conversationId = conversation.app_conversation_id\n ? conversation.app_conversation_id\n : `task-${conversation.id}`;\n\n return {\n conversation_id: conversationId,\n session_api_key: null,\n url: conversation.agent_server_url,\n task_id: conversation.id,\n };\n },\n onSuccess: async (data, variables) => {\n trackConversationCreated({\n conversationId: data.conversation_id,\n taskId: data.task_id,\n hasRepository: !!variables.repository,\n gitProvider: variables.repository?.gitProvider,\n hasWorkspace: !!variables.workingDir,\n workspaceMode: variables.workspaceMode,\n hasInitialQuery: !!variables.query,\n agentType: variables.agentType,\n hasParentConversation: !!variables.parentConversationId,\n entryPoint: variables.entryPoint,\n });\n\n // Invalidate (rather than remove) so the existing paginated list stays\n // rendered while a background refetch picks up the new conversation.\n // `removeQueries` would wipe the cache and force the panel back to its\n // initial loading state, dropping loaded pages and scroll position.\n queryClient.invalidateQueries({\n queryKey: [\"user\", \"conversations\"],\n });\n // The cloud path returns a start task (no app_conversation_id\n // yet); the sidebar surfaces those via `useStartTasks` which doesn't\n // poll, so invalidate it explicitly so the in-flight task shows up\n // in the conversation list immediately.\n queryClient.invalidateQueries({\n queryKey: [\"start-tasks\"],\n });\n },\n });\n};\n"],"mappings":"szBAoDA,IAAa,EAAmC,CAAC,qBAAqB,EASzD,MAA8B,CACzC,IAAM,EAAc,EAAA,eAAe,EAC7B,CAAE,4BAA6B,EAAA,YAAY,EAI3C,CAAE,KAAM,GAAgB,EAAA,eAAe,EAOvC,CAAE,UAAS,SAAU,EAAA,iBAAiB,EAG5C,OAFA,EAAA,iBAAiB,EAEV,EAAA,YAAY,CACjB,YAAa,EACb,WAAY,KACV,IACwC,CACxC,GAAM,CACJ,QACA,2BACA,UACA,aACA,aACA,gBACA,uBACA,YACA,kBACE,EASE,EACJ,MAAM,EAAY,gBAAgB,CAChC,SAAU,CAAC,GAAG,EAAA,0BAA0B,IAAK,EAAQ,GAAI,CAAK,EAC9D,QAAS,EAAA,QAAqB,aAC9B,GAAG,EAAA,4BACL,CAAC,EAEG,EACJ,GAAkB,EAAc,yBAA2B,IAAA,GAUvD,EAAuB,EACzB,GAAe,UAAU,KACtB,GAAY,EAAQ,KAAO,CAC9B,EACA,IAAA,GAOE,EAAU,EAAQ,OAAS,QAC7B,EAA0B,EAI1B,EAAyC,KAC7C,GACE,CAAC,GACD,GAAsB,OAAA,WACtB,GAAsB,aAAe,YAoBrC,EAA0B,IAAA,QACrB,GACL,GAAsB,aAAe,aACrC,EAAqB,gBACrB,CAIA,IAAI,EAAmB,GACvB,GAAI,CACF,IAAM,EAAM,MAAM,EAAY,gBAAgB,CAC5C,SAAU,CAAC,GAAG,EAAA,wBAAwB,IAAK,EAAQ,GAAI,CAAK,EAC5D,QAAS,EAAA,QAAgB,aAIzB,MAAO,EACT,CAAC,EACD,EAAmB,EAAI,SAAS,KAC7B,GAAY,EAAQ,OAAS,EAAqB,eACrD,EACA,EAA0B,EAAI,gBAAkB,IAClD,MAAQ,CAER,CACK,EASH,CAAC,GACD,CAAC,GACD,GACA,IAA4B,EAAqB,kBAgBjD,EAA0B,IAAA,KA1B1B,QAAQ,KACN,kBAAkB,EAAqB,KAAK,oCAC1B,EAAqB,gBAAgB,0CAEzD,EACA,EAA0B,IAAA,GAuB9B,CAKA,IAAM,EACJ,MAAM,EAAA,QAA+B,mBAAmB,CACtD,eAAgB,EAChB,2BACA,UACA,SAAU,EACN,CACE,oBAAqB,EAAW,KAChC,gBAAiB,EAAW,QAAU,KACtC,aAAc,EAAW,WAC3B,EACA,KACJ,mBAAoB,EACpB,gBACA,uBACA,YACA,GAAI,EACA,CACE,eAAgB,EAChB,iBAAkB,GAAsB,UAC1C,EACA,CAAC,CACP,CAAC,EAOG,EAAsB,EAAa,oBASnC,EAAkB,GAAS,IAAI,EAAA,mBAAmB,GAAK,CAAC,EAC1D,EAAgC,EACpC,GAAI,EAAqB,CACvB,IAAI,EAAmC,CAAC,EACxC,GAAI,CACF,EAAY,MAAM,EAAY,gBAAgB,CAG5C,SAAU,CAAC,GAAG,EAAA,mBAAmB,UAAW,EAAQ,GAAI,CAAK,EAC7D,YAAe,EAAA,QAAyB,qBAAqB,CAC/D,CAAC,CACH,MAAQ,CAER,CACA,IAAM,EAAO,IAAI,IAAI,EAAgB,IAAI,EAAA,kBAAkB,CAAC,EACtD,EAAmB,EACtB,OAAQ,GAAW,EAAO,OAAO,EACjC,IAAK,IAAY,CAChB,OAAQ,EAAO,OACf,IAAK,EAAO,cAAgB,KAC5B,UAAW,EAAO,WAAa,KAG/B,KAAM,EAAO,IACf,EAAE,EACD,OAAQ,GAAW,CAAC,EAAK,IAAI,EAAA,mBAAmB,CAAM,CAAC,CAAC,EAC3D,EAAkB,CAAC,GAAG,EAAiB,GAAG,CAAgB,CAC5D,CAUA,IAAM,EACJ,GACA,GAAsB,aAAe,YACjC,EAAqB,gBACpB,GAA2B,GAAa,gBAAkB,KACjE,GAAI,IAAwB,GAAiB,EAAgB,QAAS,CACpE,IAAM,EAAO,EAAA,8BAA8B,CAAmB,EAC9D,EAAA,8BAA8B,EAAqB,CACjD,oBAAqB,GAAM,qBAAuB,KAClD,gBAAiB,GAAM,iBAAmB,KAC1C,aAAc,GAAM,cAAgB,KACpC,mBAAoB,GAAM,oBAAsB,KAChD,eAAgB,GAAM,gBAAkB,KACxC,eAAgB,GAAiB,GAAM,gBAAkB,KACzD,QAAS,EAAgB,OACrB,EACC,GAAM,SAAW,IACxB,CAAC,CACH,CAWA,MAAO,CACL,gBALqB,EAAa,oBAChC,EAAa,oBACb,QAAQ,EAAa,KAIvB,gBAAiB,KACjB,IAAK,EAAa,iBAClB,QAAS,EAAa,EACxB,CACF,EACA,UAAW,MAAO,EAAM,IAAc,CACpC,EAAyB,CACvB,eAAgB,EAAK,gBACrB,OAAQ,EAAK,QACb,cAAe,CAAC,CAAC,EAAU,WAC3B,YAAa,EAAU,YAAY,YACnC,aAAc,CAAC,CAAC,EAAU,WAC1B,cAAe,EAAU,cACzB,gBAAiB,CAAC,CAAC,EAAU,MAC7B,UAAW,EAAU,UACrB,sBAAuB,CAAC,CAAC,EAAU,qBACnC,WAAY,EAAU,UACxB,CAAC,EAMD,EAAY,kBAAkB,CAC5B,SAAU,CAAC,OAAQ,eAAe,CACpC,CAAC,EAKD,EAAY,kBAAkB,CAC5B,SAAU,CAAC,aAAa,CAC1B,CAAC,CACH,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"use-create-conversation.cjs","names":[],"sources":["../../../src/hooks/mutation/use-create-conversation.ts"],"sourcesContent":["import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport AgentServerConversationService from \"#/api/conversation-service/agent-server-conversation-service.api\";\nimport { PluginSpec } from \"#/api/conversation-service/agent-server-conversation-service.types\";\nimport { SuggestedTask } from \"#/utils/types\";\nimport { Provider } from \"#/types/settings\";\nimport { useTracking } from \"#/hooks/use-tracking\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\nimport { useAgentProfiles } from \"#/hooks/query/use-agent-profiles\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport ProfilesService from \"#/api/profiles-service/profiles-service.api\";\nimport AgentProfilesService, {\n WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME,\n type AgentProfileListResponse,\n} from \"#/api/agent-profiles-service/agent-profiles-service.api\";\nimport PluginsManagementService, {\n type InstalledPluginInfo,\n} from \"#/api/plugins-management-service\";\nimport {\n PLUGINS_QUERY_KEYS,\n LLM_PROFILES_QUERY_KEYS,\n AGENT_PROFILES_QUERY_KEYS,\n AGENT_PROFILES_RETRY_OPTIONS,\n} from \"#/hooks/query/query-keys\";\nimport { pluginReferenceKey } from \"#/utils/plugin-display\";\nimport {\n getStoredConversationMetadata,\n setStoredConversationMetadata,\n toPluginCoordinates,\n type WorkspaceMode,\n} from \"#/api/conversation-metadata-store\";\n\nexport interface CreateConversationVariables {\n query?: string;\n repository?: {\n name: string;\n gitProvider: Provider;\n branch?: string;\n };\n suggestedTask?: SuggestedTask;\n conversationInstructions?: string;\n parentConversationId?: string;\n agentType?: \"default\" | \"plan\";\n plugins?: PluginSpec[];\n workingDir?: string;\n workspaceMode?: WorkspaceMode;\n // Launch from a specific AgentProfile (local backend). When omitted, the\n // active AgentProfile (if any) is used so home-composed conversations\n // launch from the user's selected profile (#3727).\n agentProfileId?: string;\n entryPoint?: string; // analytics only; not forwarded to the service\n}\n\nexport const CREATE_CONVERSATION_MUTATION_KEY = [\"create-conversation\"];\n\ninterface CreateConversationResponse {\n conversation_id: string;\n session_api_key: string | null;\n url: string | null;\n task_id?: string;\n}\n\nexport const useCreateConversation = () => {\n const queryClient = useQueryClient();\n const { trackConversationCreated } = useTracking();\n // Cache-warm on the home page (the profile picker reads the same query).\n // Stamped onto the conversation at creation so the switcher can show the\n // exact profile even when several profiles share a model (#1082).\n const { data: llmProfiles } = useLlmProfiles();\n // Warm the agent-profiles cache too — the launch path below awaits the same\n // query via ensureQueryData, so a warm cache makes home-launch instant. The\n // hook's maybe-unresolved data is deliberately NOT read at launch time:\n // activation is pointer-only (it never writes agent_settings), so racing a\n // cold cache into the agent_settings fallback would silently launch the\n // wrong agent.\n const { backend, orgId } = useActiveBackend();\n useAgentProfiles();\n\n return useMutation({\n mutationKey: CREATE_CONVERSATION_MUTATION_KEY,\n mutationFn: async (\n variables: CreateConversationVariables,\n ): Promise<CreateConversationResponse> => {\n const {\n query,\n conversationInstructions,\n plugins,\n repository,\n workingDir,\n workspaceMode,\n parentConversationId,\n agentType,\n agentProfileId,\n } = variables;\n\n // The active AgentProfile is the default launch profile for new\n // conversations (#3727), on both local and cloud (cloud gained\n // /api/agent-profiles in OpenHands #15060, #3730). Await the list from\n // the shared query cache: a send fired before the home query resolves\n // must still launch from the active profile. Do not fall back to the\n // global agent_settings when profile discovery fails: activation is\n // pointer-only, so those settings may describe a different agent.\n const agentProfiles: AgentProfileListResponse =\n await queryClient.ensureQueryData({\n queryKey: [...AGENT_PROFILES_QUERY_KEYS.all, backend.id, orgId],\n queryFn: AgentProfilesService.listProfiles,\n ...AGENT_PROFILES_RETRY_OPTIONS,\n });\n\n const requestedAgentProfileId =\n agentProfileId ?? agentProfiles.active_agent_profile_id ?? undefined;\n\n // Fall back to the legacy agent_settings launch when the resolved agent\n // profile can't resolve its LLM. The agent-server seeds a `default`\n // openhands profile whose `llm_profile_ref` can point at an LLM profile\n // that doesn't exist (fresh store, or one configured with named profiles\n // only); launching from it 404s (\"LLM profile '<ref>' not found\") and\n // would brick home-launch. agent_settings reflects the active LLM, so the\n // fallback degrades cleanly until the seed mirrors it (SDK #3933).\n // ACP profiles carry no llm_profile_ref, so they're never gated here.\n const resolvedAgentProfile = requestedAgentProfileId\n ? agentProfiles?.profiles?.find(\n (profile) => profile.id === requestedAgentProfileId,\n )\n : undefined;\n // Cloud has no `agent_settings` payload to fall back to — the downgrade\n // below only makes sense on local, where it exists and carries the\n // canvas-only enrichments. Gating it here keeps cloud always launching\n // from the resolved profile id, so the conversation gets\n // `launched_agent_profile` stamped and the profile's config applied\n // (#1571 review).\n const isCloud = backend.kind === \"cloud\";\n let prefersAgentSettingsFallback = false;\n // The account-wide active LLM profile from the launch-path fetch below\n // (null when that fetch didn't run or failed). Fresher than the\n // `useLlmProfiles()` render snapshot, which a fast send can outrun.\n let fetchedActiveLlmProfile: string | null = null;\n if (\n !isCloud &&\n resolvedAgentProfile?.name === WELL_KNOWN_DEFAULT_AGENT_PROFILE_NAME &&\n resolvedAgentProfile?.agent_kind === \"openhands\"\n ) {\n // The seeded OpenHands `default` profile is the enriched baseline, not a\n // deliberate profile pick — it mirrors global agent_settings. Launch it\n // via agent_settings so the canvas-only enrichments the profile-resolution\n // path drops survive for the common home-launch: the <RUNTIME_SERVICES>\n // system-message suffix and project-skill loading (buildAgentContext).\n // Named profiles are deliberate custom configs and still use the profile\n // path (accepting that enrichment boundary).\n // Trade-off: per-profile fields set on `default` itself don't apply on\n // home-launch — custom per-profile config belongs in a named profile.\n //\n // Scoped to OpenHands: an ACP `default` must keep the profile path.\n // Activation is pointer-only, so global agent_settings is stale (often\n // still OpenHands) when an ACP profile is active — launching it via\n // agent_settings would start the wrong agent. ACP carries no\n // <RUNTIME_SERVICES> enrichment, so there's nothing to preserve.\n //\n // Scoped to local: cloud never writes agent_settings, so it always\n // resolves `default` server-side via agent_profile_id (validated below).\n prefersAgentSettingsFallback = true;\n } else if (\n resolvedAgentProfile?.agent_kind === \"openhands\" &&\n resolvedAgentProfile.llm_profile_ref\n ) {\n // Await the LLM-profile list rather than reading the maybe-unresolved\n // `useLlmProfiles()` result: a send fired before that query loads (or\n // after it errors) must still validate the ref, not launch blind.\n let llmProfileExists = false;\n try {\n const llm = await queryClient.ensureQueryData({\n queryKey: [...LLM_PROFILES_QUERY_KEYS.all, backend.id, orgId],\n queryFn: ProfilesService.listProfiles,\n // Match the agent-profiles fetch above: on a backend where this\n // errors, fall back to agent_settings immediately rather than\n // stalling the send through the default exponential backoff.\n retry: false,\n });\n llmProfileExists = llm.profiles.some(\n (profile) => profile.name === resolvedAgentProfile.llm_profile_ref,\n );\n fetchedActiveLlmProfile = llm.active_profile ?? null;\n } catch {\n // List unavailable → can't validate → fall back to agent_settings.\n }\n if (!llmProfileExists) {\n // Downgrade is silent in the UI; leave a diagnosable trace.\n console.warn(\n `Agent profile \"${resolvedAgentProfile.name}\" references missing ` +\n `LLM profile \"${resolvedAgentProfile.llm_profile_ref}\"; ` +\n \"launching from agent_settings instead.\",\n );\n prefersAgentSettingsFallback = true;\n } else if (\n !isCloud &&\n !agentProfileId &&\n fetchedActiveLlmProfile &&\n fetchedActiveLlmProfile !== resolvedAgentProfile.llm_profile_ref\n ) {\n // The home LLM pill shows — and its dropdown activates — the\n // account-wide active LLM profile, never the pinned ref\n // (useChatInputLlmProfileState), so when the two differ the launch\n // must run the selection or the UI advertises a model the\n // conversation won't use (#16539). Launch via agent_settings, which\n // the dropdown's profile activation syncs to the selection. Scoped\n // to the implicit active-profile launch: an explicit `agentProfileId`\n // (the in-conversation profile picker) is a deliberate profile pick,\n // so its pinned ref stays authoritative. Local-only like the\n // downgrades above — cloud has no agent_settings payload to fall\n // back to. Trade-off: the named profile's non-LLM config doesn't\n // apply to this launch; the start request has no per-launch LLM\n // override that could preserve it (AgentLaunchAdditions carries only\n // a system-message suffix).\n prefersAgentSettingsFallback = true;\n }\n }\n\n // A legacy agent_settings launch carries no profile identity, so Agent\n // Server cannot enforce that profile's secret allow-list. Keep restricted\n // profiles on the server-resolved path even when one of the compatibility\n // fallbacks above would otherwise apply. If the detail cannot be read,\n // fail closed by preserving the profile id and letting Agent Server return\n // the underlying profile-resolution error.\n let profileAllowsAgentSettingsFallback = true;\n if (prefersAgentSettingsFallback && resolvedAgentProfile) {\n try {\n // Revalidate stale cached details: policy may have changed since the\n // profile editor or another client last populated this query.\n const detail = await queryClient.fetchQuery({\n queryKey: AGENT_PROFILES_QUERY_KEYS.detail(\n backend.id,\n orgId,\n resolvedAgentProfile.name,\n ),\n queryFn: () =>\n AgentProfilesService.getProfile(resolvedAgentProfile.name),\n ...AGENT_PROFILES_RETRY_OPTIONS,\n });\n const secretRefs = (detail.profile as { secret_refs?: unknown })\n .secret_refs;\n profileAllowsAgentSettingsFallback = !Array.isArray(secretRefs);\n } catch {\n profileAllowsAgentSettingsFallback = false;\n }\n }\n const effectiveAgentProfileId =\n prefersAgentSettingsFallback && profileAllowsAgentSettingsFallback\n ? undefined\n : requestedAgentProfileId;\n\n // Only extend the call with the profile fields when launching from a\n // profile, so a plain create stays byte-identical to the legacy\n // agent_settings path (#3727). sandboxId is unused here.\n const conversation =\n await AgentServerConversationService.createConversation({\n initialUserMsg: query,\n conversationInstructions,\n plugins,\n metadata: repository\n ? {\n selected_repository: repository.name,\n selected_branch: repository.branch ?? null,\n git_provider: repository.gitProvider,\n }\n : null,\n workingDirOverride: workingDir,\n workspaceMode,\n parentConversationId,\n agentType,\n ...(effectiveAgentProfileId\n ? {\n agentProfileId: effectiveAgentProfileId,\n agentProfileKind: resolvedAgentProfile?.agent_kind,\n }\n : {}),\n });\n\n // Stamp the active LLM profile onto the (local) conversation so the\n // chat switcher shows the exact profile even when several profiles\n // share a model (#1082). Cloud conversations don't use local profiles\n // (app_conversation_id stays null until the sandbox is READY). Merge so\n // the repo/workspace metadata the service just persisted is preserved.\n const localConversationId = conversation.app_conversation_id;\n // Snapshot the conversation's plugins into client-side metadata so the\n // in-conversation plugins view can show what's loaded (coordinates only\n // — strip parameters, which may carry secrets). The agent-server doesn't\n // return a live conversation's loaded plugins, so this snapshot is the\n // source for that view. Two sources, deduped by coordinates:\n // 1. plugins explicitly attached at creation (e.g. the /launch flow);\n // 2. enabled installed plugins, which the SDK auto-loads into every new\n // local conversation (see use-set-plugin-enabled).\n const explicitPlugins = plugins?.map(toPluginCoordinates) ?? [];\n let attachedPlugins: PluginSpec[] = explicitPlugins;\n if (localConversationId) {\n let installed: InstalledPluginInfo[] = [];\n try {\n installed = await queryClient.ensureQueryData({\n // Same key `usePlugins` writes. Unscoped, this served the previous\n // backend's list and attached plugins the target server lacks.\n queryKey: [...PLUGINS_QUERY_KEYS.installed, backend.id, orgId],\n queryFn: () => PluginsManagementService.listInstalledPlugins(),\n });\n } catch {\n // Best-effort: never let plugin lookup block conversation creation.\n }\n const seen = new Set(explicitPlugins.map(pluginReferenceKey));\n const enabledInstalled = installed\n .filter((plugin) => plugin.enabled)\n .map((plugin) => ({\n source: plugin.source,\n ref: plugin.resolved_ref ?? null,\n repo_path: plugin.repo_path ?? null,\n // Keep the human-friendly name so the plugins view shows e.g.\n // \"city-weather\" rather than deriving \"local\" from the source.\n name: plugin.name,\n }))\n .filter((plugin) => !seen.has(pluginReferenceKey(plugin)));\n attachedPlugins = [...explicitPlugins, ...enabledInstalled];\n }\n // A launch from a named OpenHands profile runs that profile's\n // `llm_profile_ref`, which can differ from the standalone active LLM\n // profile — stamp the ref so the switcher pill names the exact profile\n // the conversation runs (#1082). The agent_settings paths (the `default`\n // baseline, a dangling ref, or a dropdown override (#16539) — where\n // effectiveAgentProfileId is cleared) run the active LLM, so they stamp\n // the active profile, preferring the launch-path fetch over the hook's\n // render snapshot. ACP profiles carry no LLM profile, so they fall\n // through to the active-profile stamp (unused by the ACP model chip).\n const activeProfile =\n effectiveAgentProfileId &&\n resolvedAgentProfile?.agent_kind === \"openhands\"\n ? resolvedAgentProfile.llm_profile_ref\n : (fetchedActiveLlmProfile ?? llmProfiles?.active_profile ?? null);\n if (localConversationId && (activeProfile || attachedPlugins.length)) {\n const prev = getStoredConversationMetadata(localConversationId);\n setStoredConversationMetadata(localConversationId, {\n selected_repository: prev?.selected_repository ?? null,\n selected_branch: prev?.selected_branch ?? null,\n git_provider: prev?.git_provider ?? null,\n selected_workspace: prev?.selected_workspace ?? null,\n workspace_mode: prev?.workspace_mode ?? null,\n active_profile: activeProfile ?? prev?.active_profile ?? null,\n plugins: attachedPlugins.length\n ? attachedPlugins\n : (prev?.plugins ?? null),\n });\n }\n\n // OpenHands cloud pattern: when the start task isn't immediately\n // READY (cloud sandbox is still provisioning),\n // app_conversation_id is null. We return a `task-{id}` URL so the\n // conversation route's useTaskPolling can drive it to READY and\n // then redirect to the real `/conversations/{app_conversation_id}`.\n const conversationId = conversation.app_conversation_id\n ? conversation.app_conversation_id\n : `task-${conversation.id}`;\n\n return {\n conversation_id: conversationId,\n session_api_key: null,\n url: conversation.agent_server_url,\n task_id: conversation.id,\n };\n },\n onSuccess: async (data, variables) => {\n trackConversationCreated({\n conversationId: data.conversation_id,\n taskId: data.task_id,\n hasRepository: !!variables.repository,\n gitProvider: variables.repository?.gitProvider,\n hasWorkspace: !!variables.workingDir,\n workspaceMode: variables.workspaceMode,\n hasInitialQuery: !!variables.query,\n agentType: variables.agentType,\n hasParentConversation: !!variables.parentConversationId,\n entryPoint: variables.entryPoint,\n });\n\n // Invalidate (rather than remove) so the existing paginated list stays\n // rendered while a background refetch picks up the new conversation.\n // `removeQueries` would wipe the cache and force the panel back to its\n // initial loading state, dropping loaded pages and scroll position.\n queryClient.invalidateQueries({\n queryKey: [\"user\", \"conversations\"],\n });\n // The cloud path returns a start task (no app_conversation_id\n // yet); the sidebar surfaces those via `useStartTasks` which doesn't\n // poll, so invalidate it explicitly so the in-flight task shows up\n // in the conversation list immediately.\n queryClient.invalidateQueries({\n queryKey: [\"start-tasks\"],\n });\n },\n });\n};\n"],"mappings":"szBAoDA,IAAa,EAAmC,CAAC,qBAAqB,EASzD,MAA8B,CACzC,IAAM,EAAc,EAAA,eAAe,EAC7B,CAAE,4BAA6B,EAAA,YAAY,EAI3C,CAAE,KAAM,GAAgB,EAAA,eAAe,EAOvC,CAAE,UAAS,SAAU,EAAA,iBAAiB,EAG5C,OAFA,EAAA,iBAAiB,EAEV,EAAA,YAAY,CACjB,YAAa,EACb,WAAY,KACV,IACwC,CACxC,GAAM,CACJ,QACA,2BACA,UACA,aACA,aACA,gBACA,uBACA,YACA,kBACE,EASE,EACJ,MAAM,EAAY,gBAAgB,CAChC,SAAU,CAAC,GAAG,EAAA,0BAA0B,IAAK,EAAQ,GAAI,CAAK,EAC9D,QAAS,EAAA,QAAqB,aAC9B,GAAG,EAAA,4BACL,CAAC,EAEG,EACJ,GAAkB,EAAc,yBAA2B,IAAA,GAUvD,EAAuB,EACzB,GAAe,UAAU,KACtB,GAAY,EAAQ,KAAO,CAC9B,EACA,IAAA,GAOE,EAAU,EAAQ,OAAS,QAC7B,EAA+B,GAI/B,EAAyC,KAC7C,GACE,CAAC,GACD,GAAsB,OAAA,WACtB,GAAsB,aAAe,YAoBrC,EAA+B,QAC1B,GACL,GAAsB,aAAe,aACrC,EAAqB,gBACrB,CAIA,IAAI,EAAmB,GACvB,GAAI,CACF,IAAM,EAAM,MAAM,EAAY,gBAAgB,CAC5C,SAAU,CAAC,GAAG,EAAA,wBAAwB,IAAK,EAAQ,GAAI,CAAK,EAC5D,QAAS,EAAA,QAAgB,aAIzB,MAAO,EACT,CAAC,EACD,EAAmB,EAAI,SAAS,KAC7B,GAAY,EAAQ,OAAS,EAAqB,eACrD,EACA,EAA0B,EAAI,gBAAkB,IAClD,MAAQ,CAER,CACK,EASH,CAAC,GACD,CAAC,GACD,GACA,IAA4B,EAAqB,kBAgBjD,EAA+B,KA1B/B,QAAQ,KACN,kBAAkB,EAAqB,KAAK,oCAC1B,EAAqB,gBAAgB,0CAEzD,EACA,EAA+B,GAuBnC,CAQA,IAAI,EAAqC,GACzC,GAAI,GAAgC,EAClC,GAAI,CAaF,IAAM,GAAc,MAVC,EAAY,WAAW,CAC1C,SAAU,EAAA,0BAA0B,OAClC,EAAQ,GACR,EACA,EAAqB,IACvB,EACA,YACE,EAAA,QAAqB,WAAW,EAAqB,IAAI,EAC3D,GAAG,EAAA,4BACL,CAAC,GAC0B,QACxB,YACH,EAAqC,CAAC,MAAM,QAAQ,CAAU,CAChE,MAAQ,CACN,EAAqC,EACvC,CAEF,IAAM,EACJ,GAAgC,EAC5B,IAAA,GACA,EAKA,EACJ,MAAM,EAAA,QAA+B,mBAAmB,CACtD,eAAgB,EAChB,2BACA,UACA,SAAU,EACN,CACE,oBAAqB,EAAW,KAChC,gBAAiB,EAAW,QAAU,KACtC,aAAc,EAAW,WAC3B,EACA,KACJ,mBAAoB,EACpB,gBACA,uBACA,YACA,GAAI,EACA,CACE,eAAgB,EAChB,iBAAkB,GAAsB,UAC1C,EACA,CAAC,CACP,CAAC,EAOG,EAAsB,EAAa,oBASnC,EAAkB,GAAS,IAAI,EAAA,mBAAmB,GAAK,CAAC,EAC1D,EAAgC,EACpC,GAAI,EAAqB,CACvB,IAAI,EAAmC,CAAC,EACxC,GAAI,CACF,EAAY,MAAM,EAAY,gBAAgB,CAG5C,SAAU,CAAC,GAAG,EAAA,mBAAmB,UAAW,EAAQ,GAAI,CAAK,EAC7D,YAAe,EAAA,QAAyB,qBAAqB,CAC/D,CAAC,CACH,MAAQ,CAER,CACA,IAAM,EAAO,IAAI,IAAI,EAAgB,IAAI,EAAA,kBAAkB,CAAC,EACtD,EAAmB,EACtB,OAAQ,GAAW,EAAO,OAAO,EACjC,IAAK,IAAY,CAChB,OAAQ,EAAO,OACf,IAAK,EAAO,cAAgB,KAC5B,UAAW,EAAO,WAAa,KAG/B,KAAM,EAAO,IACf,EAAE,EACD,OAAQ,GAAW,CAAC,EAAK,IAAI,EAAA,mBAAmB,CAAM,CAAC,CAAC,EAC3D,EAAkB,CAAC,GAAG,EAAiB,GAAG,CAAgB,CAC5D,CAUA,IAAM,EACJ,GACA,GAAsB,aAAe,YACjC,EAAqB,gBACpB,GAA2B,GAAa,gBAAkB,KACjE,GAAI,IAAwB,GAAiB,EAAgB,QAAS,CACpE,IAAM,EAAO,EAAA,8BAA8B,CAAmB,EAC9D,EAAA,8BAA8B,EAAqB,CACjD,oBAAqB,GAAM,qBAAuB,KAClD,gBAAiB,GAAM,iBAAmB,KAC1C,aAAc,GAAM,cAAgB,KACpC,mBAAoB,GAAM,oBAAsB,KAChD,eAAgB,GAAM,gBAAkB,KACxC,eAAgB,GAAiB,GAAM,gBAAkB,KACzD,QAAS,EAAgB,OACrB,EACC,GAAM,SAAW,IACxB,CAAC,CACH,CAWA,MAAO,CACL,gBALqB,EAAa,oBAChC,EAAa,oBACb,QAAQ,EAAa,KAIvB,gBAAiB,KACjB,IAAK,EAAa,iBAClB,QAAS,EAAa,EACxB,CACF,EACA,UAAW,MAAO,EAAM,IAAc,CACpC,EAAyB,CACvB,eAAgB,EAAK,gBACrB,OAAQ,EAAK,QACb,cAAe,CAAC,CAAC,EAAU,WAC3B,YAAa,EAAU,YAAY,YACnC,aAAc,CAAC,CAAC,EAAU,WAC1B,cAAe,EAAU,cACzB,gBAAiB,CAAC,CAAC,EAAU,MAC7B,UAAW,EAAU,UACrB,sBAAuB,CAAC,CAAC,EAAU,qBACnC,WAAY,EAAU,UACxB,CAAC,EAMD,EAAY,kBAAkB,CAC5B,SAAU,CAAC,OAAQ,eAAe,CACpC,CAAC,EAKD,EAAY,kBAAkB,CAC5B,SAAU,CAAC,aAAa,CAC1B,CAAC,CACH,CACF,CAAC,CACH"}