@openhands/agent-canvas 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/README.md +1 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/{acp-providers-BQwoUBpe.js → acp-providers-DerAmTyY.js} +1 -1
  4. package/build/assets/{active-backend-context-BCXRv5pm.js → active-backend-context-DHhm10VS.js} +1 -1
  5. package/build/assets/{add-backend-modal-BKHzq5no.js → add-backend-modal-BoMkMyLB.js} +1 -1
  6. package/build/assets/{agent-profiles-settings-CEp5CIz8.js → agent-profiles-settings-CYPloKYK.js} +1 -1
  7. package/build/assets/{agent-server-compatibility-D6DSanlx.js → agent-server-compatibility-C0MtTTwb.js} +1 -1
  8. package/build/assets/{agent-server-conversation-service.api-cylugfDz.js → agent-server-conversation-service.api-lzc8dabY.js} +4 -4
  9. package/build/assets/{agent-settings-C-tann3x.js → agent-settings-GwiXe_6k.js} +1 -1
  10. package/build/assets/agent-settings-OdsyI2gX.js +1 -0
  11. package/build/assets/{alert-banner-Dd4woGbm.js → alert-banner-Dh51WURQ.js} +1 -1
  12. package/build/assets/{api-key-entry-screen-Bll_CkuQ.js → api-key-entry-screen-2gsoZAOK.js} +1 -1
  13. package/build/assets/app-settings-CD9euh81.js +1 -0
  14. package/build/assets/{automation-detail-Z9dN2f5l.js → automation-detail-Bn8q0AG3.js} +1 -1
  15. package/build/assets/{automation-export-D5uL0Tf8.js → automation-export-Djc8Tf0M.js} +1 -1
  16. package/build/assets/{automations-list-BMOWcqzZ.js → automations-list-Dtiolk1h.js} +1 -1
  17. package/build/assets/{back-nav-button-ZUDTMOTB.js → back-nav-button-DVknCcSP.js} +1 -1
  18. package/build/assets/backend-form-modal-B8GtBA-J.js +1 -0
  19. package/build/assets/{backend-synced-settings-badge-Db_REKVw.js → backend-synced-settings-badge-yVh6Kf4E.js} +1 -1
  20. package/build/assets/{base-modal-Cb0C6m0Q.js → base-modal-CIF_785P.js} +1 -1
  21. package/build/assets/{brand-button-2Tp3aXk1.js → brand-button-B1izLAdR.js} +1 -1
  22. package/build/assets/{browser-QYLVr1kS.js → browser-DKaeq_sT.js} +2 -2
  23. package/build/assets/browser-store-BI6uklCG.js +1 -0
  24. package/build/assets/{browser-tab-BTTP1OE2.js → browser-tab-qb_ftCxW.js} +1 -1
  25. package/build/assets/{circle-plus-check-toggle-QQJKUnF6.js → circle-plus-check-toggle-DoFIjVE0.js} +1 -1
  26. package/build/assets/client-source-CtQjJEy6.js +1 -0
  27. package/build/assets/cloud-funnel-analytics-uzzLlLuj.js +1 -0
  28. package/build/assets/{color-themes-bVMmJJG6.js → color-themes-CHizm1xF.js} +1 -1
  29. package/build/assets/{combobox-caret-A5jMVHgE.js → combobox-caret-CxZpqoSR.js} +1 -1
  30. package/build/assets/{command-menu-BMVEme4H.js → command-menu-oTvKDU3Y.js} +1 -1
  31. package/build/assets/{command-store-jrWX_CcW.js → command-store-D8J5LMW1.js} +1 -1
  32. package/build/assets/{condenser-settings-CIa6eyB4.js → condenser-settings-Q1rHh4Qe.js} +1 -1
  33. package/build/assets/{confirmation-modal-4n_944zq.js → confirmation-modal-DGBWM2Ls.js} +1 -1
  34. package/build/assets/{context-menu-list-item-DvfFic5I.js → context-menu-list-item-6psNyl7X.js} +1 -1
  35. package/build/assets/conversation-B8d8_Upd.js +1 -0
  36. package/build/assets/{conversation-D4o4dG2t.js → conversation-Lmqm6VJC.js} +20 -20
  37. package/build/assets/conversation-panel-B8n40Swu.js +1 -0
  38. package/build/assets/{conversation-service.api-0JG_fF9d.js → conversation-service.api-D8QU-BiL.js} +1 -1
  39. package/build/assets/conversation-state-store-B2Bp32XI.js +1 -0
  40. package/build/assets/{conversation-store-BCSpLuEf.js → conversation-store-LptbOvKa.js} +1 -1
  41. package/build/assets/{copy-to-clipboard-button-Dp4Q_hXi.js → copy-to-clipboard-button-CFzH_NOX.js} +1 -1
  42. package/build/assets/{device-verify-CM086oKs.js → device-verify-WBSjJBLH.js} +1 -1
  43. package/build/assets/{dropdown-classes-DZq0BQjb.js → dropdown-classes-D3wMJlXT.js} +1 -1
  44. package/build/assets/{ellipsis-button-TPJdyxnK.js → ellipsis-button-CbNnHlIq.js} +1 -1
  45. package/build/assets/{entry.client-DypYHsYm.js → entry.client-Dnnak-Hq.js} +2 -2
  46. package/build/assets/{enum-filter-dropdown-CW_mBg0e.js → enum-filter-dropdown-JLYv1t8L.js} +1 -1
  47. package/build/assets/{environment-switch-overlay-6laJch-9.js → environment-switch-overlay-B1PFO8P8.js} +1 -1
  48. package/build/assets/{extensions-hub-BJFbFfKS.js → extensions-hub-DqvwvpCi.js} +1 -1
  49. package/build/assets/{extensions-navigation-DqSl7CJw.js → extensions-navigation-BMc9Nluv.js} +1 -1
  50. package/build/assets/{files-tab-c316dKAq.js → files-tab-BAkaV4aO.js} +1 -1
  51. package/build/assets/{files-tab-store-tfCav6bB.js → files-tab-store-DJsPxYdg.js} +1 -1
  52. package/build/assets/{git-provider-icon-DUb2pEos.js → git-provider-icon-xvbukHaT.js} +1 -1
  53. package/build/assets/{home-D7ds4Jyw.js → home-D01kSnmo.js} +1 -1
  54. package/build/assets/home-NZO2bF4N.js +1 -0
  55. package/build/assets/index-home-BsI6i0TC.js +1 -0
  56. package/build/assets/{install-server-modal-CvHvzZj3.js → install-server-modal-BYjqoRyX.js} +1 -1
  57. package/build/assets/{launch-A47TK2Me.js → launch-DlXspDku.js} +1 -1
  58. package/build/assets/lesson-plan-cNUjpdFx.js +4 -0
  59. package/build/assets/{llm-not-configured-banner-B7DR8SPs.js → llm-not-configured-banner-DUPyoFIZ.js} +7 -7
  60. package/build/assets/{llm-settings-Cvi7q4ox.js → llm-settings-Cs_iieFY.js} +1 -1
  61. package/build/assets/llm-settings-J2ke2foG.js +1 -0
  62. package/build/assets/{llm-subscription-service-Cz1yBBvE.js → llm-subscription-service-C0zN0-rH.js} +1 -1
  63. package/build/assets/{manage-backends-modal-DAVhNUCe.js → manage-backends-modal-nMoCZ2a0.js} +1 -1
  64. package/build/assets/manifest-f1b46f62.js +1 -0
  65. package/build/assets/{map-provider-XSFHmdDs.js → map-provider-BbIb12oh.js} +1 -1
  66. package/build/assets/{markdown-renderer-B6cJJ_MN.js → markdown-renderer-B4V2OMFP.js} +1 -1
  67. package/build/assets/{mcp-BwssgMpg.js → mcp-kZM-cAZw.js} +1 -1
  68. package/build/assets/{messages-D8MeG2ZQ.js → messages-CJAv5n4j.js} +1 -1
  69. package/build/assets/{model-selector-BxTbbKub.js → model-selector-D51W2Q9b.js} +1 -1
  70. package/build/assets/{navigation-link-imH6S7h2.js → navigation-link-D8hfwvxI.js} +1 -1
  71. package/build/assets/{onboarding-CyicadGt.js → onboarding-Dx8n8HFr.js} +1 -1
  72. package/build/assets/onboarding-modal-Bxuxpfdz.js +1 -0
  73. package/build/assets/{organization-service.api-QknzKDh3.js → organization-service.api-Da2jiuBS.js} +1 -1
  74. package/build/assets/{path-utils-BECGet7G.js → path-utils-D53ZHUQU.js} +1 -1
  75. package/build/assets/{planner-tab-DPKHmMAX.js → planner-tab-BM-pKx0z.js} +1 -1
  76. package/build/assets/{plugins-management-service-DuTTxquo.js → plugins-management-service-DISfOL13.js} +1 -1
  77. package/build/assets/{profiles-service.api-D6jvZOWu.js → profiles-service.api-DWetW2TL.js} +1 -1
  78. package/build/assets/providers-C7lMslCi.js +1 -0
  79. package/build/assets/{proxy-CuhhyjdX.js → proxy-G4ce0fnQ.js} +1 -1
  80. package/build/assets/{recommended-automations-launcher-DA6DWS7V.js → recommended-automations-launcher-BWRRvMPE.js} +1 -1
  81. package/build/assets/root-layout-ClEl-JlJ.js +2 -0
  82. package/build/assets/root-oT83qVlk.js +2 -0
  83. package/build/assets/{runtime-waiting-state-DeD9G7yJ.js → runtime-waiting-state-CnyVhX4T.js} +1 -1
  84. package/build/assets/{schema-field-BOLdL_U7.js → schema-field-CwdqUOnE.js} +1 -1
  85. package/build/assets/{sdk-section-page-ZIltERxf.js → sdk-section-page-Cwj0T4gF.js} +1 -1
  86. package/build/assets/{sdk-settings-schema-Ba_Pa9uf.js → sdk-settings-schema-CLR01YIK.js} +1 -1
  87. package/build/assets/{secrets-service-50DElFny.js → secrets-service-Ch7RH8eH.js} +1 -1
  88. package/build/assets/{secrets-settings-Digl7JLe.js → secrets-settings-Bc_pBX6F.js} +1 -1
  89. package/build/assets/{settings-dropdown-input-BD5C9fBn.js → settings-dropdown-input-Dhx9ErGd.js} +1 -1
  90. package/build/assets/{settings-index-CODBijya.js → settings-index-BzSIM1-N.js} +1 -1
  91. package/build/assets/{settings-input-CzNOq-Pj.js → settings-input-BsgQWS5R.js} +1 -1
  92. package/build/assets/{settings-list-classes-CuxH9ZoR.js → settings-list-classes-DSJJX99U.js} +1 -1
  93. package/build/assets/{settings-modal-BcSWpKI0.js → settings-modal-BnZ7mMu7.js} +1 -1
  94. package/build/assets/settings-n9a395O0.js +1 -0
  95. package/build/assets/{settings-service.api-Bsu1PvYO.js → settings-service.api-DPTFyRXL.js} +1 -1
  96. package/build/assets/{settings-switch-DqmHAfWJ.js → settings-switch-Bes8dIlV.js} +1 -1
  97. package/build/assets/{settings-utils-B1lBErvV.js → settings-utils-DzCVfiR3.js} +1 -1
  98. package/build/assets/{shared-conversation-CDskdmrb.js → shared-conversation-CIDDti9I.js} +1 -1
  99. package/build/assets/{sidebar-mobile-menu-toggle-DmCmZgq1.js → sidebar-mobile-menu-toggle-CTX7J4wM.js} +1 -1
  100. package/build/assets/{sidebar-nav-link-DMc8AIe7.js → sidebar-nav-link-CHqyFXwV.js} +1 -1
  101. package/build/assets/{sidebar-store-DXWSCFpa.js → sidebar-store-uiUXBU1N.js} +1 -1
  102. package/build/assets/{skill-card-pill-row-DOIWjiMF.js → skill-card-pill-row-BPYKQ3IN.js} +1 -1
  103. package/build/assets/{skills-plugins-DLgINYuY.js → skills-plugins-HZEbNDmX.js} +1 -1
  104. package/build/assets/{skills-settings-DPiCWalR.js → skills-settings-BdqijFlC.js} +1 -1
  105. package/build/assets/{task-list-tab-CuhcDycu.js → task-list-tab-DyfgG4MC.js} +1 -1
  106. package/build/assets/telemetry-SHxpVxk8.js +2 -0
  107. package/build/assets/{terminal-CVqF-tCD.js → terminal-BDTa1HEr.js} +1 -1
  108. package/build/assets/{typography-CQn9wyEX.js → typography-CzfoD9DY.js} +1 -1
  109. package/build/assets/{use-acp-credential-form-LlfZO_DD.js → use-acp-credential-form-DHd_Id-q.js} +1 -1
  110. package/build/assets/{use-activate-agent-profile-DpQjsXM0.js → use-activate-agent-profile-BRDdSwQ0.js} +1 -1
  111. package/build/assets/{use-active-agent-profile-Gw_uJujV.js → use-active-agent-profile-BMDTXsay.js} +1 -1
  112. package/build/assets/{use-active-conversation-B0sttISF.js → use-active-conversation-BbeVgedA.js} +1 -1
  113. package/build/assets/{use-agent-profiles-LHZEK0Gg.js → use-agent-profiles-Dn_S_Tkz.js} +1 -1
  114. package/build/assets/{use-agent-state-C5uA0bRj.js → use-agent-state-5qM3X30V.js} +1 -1
  115. package/build/assets/{use-backends-health-DKt1SmiU.js → use-backends-health-BagmAE_W.js} +1 -1
  116. package/build/assets/{use-can-manage-org-profiles-BAtqsbCi.js → use-can-manage-org-profiles-CyYqfzeo.js} +1 -1
  117. package/build/assets/{use-cloud-current-user-id-C8m1oBy4.js → use-cloud-current-user-id-BwMX7jHW.js} +1 -1
  118. package/build/assets/use-config-B39iHYFi.js +1 -0
  119. package/build/assets/{use-create-conversation-CWQGNQ9J.js → use-create-conversation-CkKUQVbP.js} +1 -1
  120. package/build/assets/{use-create-secret-BIrsX3OB.js → use-create-secret-DEgM0MXZ.js} +1 -1
  121. package/build/assets/{use-event-store-CsD17BKu.js → use-event-store-Bbaz51GR.js} +1 -1
  122. package/build/assets/{use-handle-plan-click-DEuO7Byo.js → use-handle-plan-click-BFcE0Xms.js} +1 -1
  123. package/build/assets/{use-launch-skill-in-chat-CdHOoQGM.js → use-launch-skill-in-chat-DJWEfVc5.js} +1 -1
  124. package/build/assets/{use-llm-profiles-DQpQxVOD.js → use-llm-profiles-CgRtnj74.js} +1 -1
  125. package/build/assets/{use-plugins-marketplace-Bkk07W_M.js → use-plugins-marketplace-DOJp9rlE.js} +1 -1
  126. package/build/assets/use-runtime-is-ready-ziHQvXZQ.js +1 -0
  127. package/build/assets/{use-save-agent-profile-BxEsjnW0.js → use-save-agent-profile-i4FcEP98.js} +1 -1
  128. package/build/assets/{use-save-settings-ioSUMqbE.js → use-save-settings-DiDEbIJw.js} +1 -1
  129. package/build/assets/{use-settings-DNJC4gqu.js → use-settings-BeJap6YL.js} +1 -1
  130. package/build/assets/{use-settings-nav-items-BUsrDbVR.js → use-settings-nav-items-D5JJjFHJ.js} +1 -1
  131. package/build/assets/{use-skills-BsmKm23M.js → use-skills-Blpg2BqE.js} +1 -1
  132. package/build/assets/{use-task-list-rs7Xbzl8.js → use-task-list-CfisapBW.js} +1 -1
  133. package/build/assets/use-tracking-DFoPQUSU.js +1 -0
  134. package/build/assets/{use-user-conversation-B337kUKB.js → use-user-conversation-BrN2xeQU.js} +1 -1
  135. package/build/assets/{vendor~browser-tab-Xn94EtUo.js → vendor~browser-tab-CsMb-pta.js} +1 -1
  136. package/build/assets/{vendor~browser-5Njjw1Cl.js → vendor~browser-wXQ-WZEC.js} +1 -1
  137. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~dhilvkxv-BIieS_OD.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~dhilvkxv-eGLBu60s.js} +2 -2
  138. package/build/assets/{vendor~files-tab-CiC3-lGh.js → vendor~files-tab-B-5fBohe.js} +1 -1
  139. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DWteoTyr.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BD7v71DM.js} +1 -1
  140. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-tlf6AnDe.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BT05Osu4.js} +1 -1
  141. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-B8XNNEQZ.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-COhHANvV.js} +1 -1
  142. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-Dc5nKwLO.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DgzWSMRc.js} +1 -1
  143. package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-CnJRLn5s.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DqZatR3B.js} +1 -1
  144. package/build/assets/{vendor~launch-28DK5Rsl.js → vendor~launch-_Km3J77E.js} +1 -1
  145. package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~g9hift2g-DmmpG9SI.js → vendor~module-D5a_AClf.js} +1 -1
  146. package/build/assets/{vendor~root-layout~conversation-panel~conversation~shared-conversation-lYNG9C1U.js → vendor~root-layout~conversation-panel~conversation~shared-conversation-BrguKXCn.js} +1 -1
  147. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation-Zhq6xAxR.js → vendor~root-layout~index-home~home~conversation-panel~conversation-BTvhGHHG.js} +1 -1
  148. package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation~aler~gfu7qdtz-B81b08bw.js → vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation~aler~gfu7qdtz-BOKQkbc6.js} +1 -1
  149. package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~ftka0fnu-jpyLM1Nn.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~ftka0fnu-cFNmfAE6.js} +1 -1
  150. package/build/assets/{verification-settings-HRgEM-_3.js → verification-settings-C8KzAdAX.js} +1 -1
  151. package/build/index.html +4 -4
  152. package/config/defaults.json +1 -1
  153. package/dist/api/cloud/conversation-service.api.cjs +1 -1
  154. package/dist/api/cloud/conversation-service.api.js +4 -4
  155. package/dist/api/option-service/option-service.api.cjs +1 -1
  156. package/dist/api/option-service/option-service.api.cjs.map +1 -1
  157. package/dist/api/option-service/option-service.api.js +0 -1
  158. package/dist/api/option-service/option-service.api.js.map +1 -1
  159. package/dist/api/option-service/option.types.d.ts +0 -1
  160. package/dist/components/features/backends/backend-form-modal.cjs +1 -1
  161. package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
  162. package/dist/components/features/backends/backend-form-modal.d.ts +1 -1
  163. package/dist/components/features/backends/backend-form-modal.js +137 -144
  164. package/dist/components/features/backends/backend-form-modal.js.map +1 -1
  165. package/dist/components/providers/agent-server-ui-providers.cjs +1 -1
  166. package/dist/components/providers/agent-server-ui-providers.cjs.map +1 -1
  167. package/dist/components/providers/agent-server-ui-providers.d.ts +4 -2
  168. package/dist/components/providers/agent-server-ui-providers.js +9 -2
  169. package/dist/components/providers/agent-server-ui-providers.js.map +1 -1
  170. package/dist/components/providers/index.cjs +1 -1
  171. package/dist/components/providers/index.d.ts +1 -2
  172. package/dist/components/providers/index.js +0 -1
  173. package/dist/components/providers/telemetry-provider.cjs +2 -0
  174. package/dist/components/providers/telemetry-provider.cjs.map +1 -0
  175. package/dist/components/providers/telemetry-provider.d.ts +6 -0
  176. package/dist/components/providers/telemetry-provider.js +64 -0
  177. package/dist/components/providers/telemetry-provider.js.map +1 -0
  178. package/dist/config/defaults.cjs +1 -1
  179. package/dist/config/defaults.cjs.map +1 -1
  180. package/dist/config/defaults.js +1 -1
  181. package/dist/config/defaults.js.map +1 -1
  182. package/dist/contexts/conversation-websocket-context.cjs +3 -3
  183. package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
  184. package/dist/contexts/conversation-websocket-context.js +197 -204
  185. package/dist/contexts/conversation-websocket-context.js.map +1 -1
  186. package/dist/hooks/mutation/use-save-settings.cjs +1 -1
  187. package/dist/hooks/mutation/use-save-settings.cjs.map +1 -1
  188. package/dist/hooks/mutation/use-save-settings.d.ts +3 -1
  189. package/dist/hooks/mutation/use-save-settings.js +6 -5
  190. package/dist/hooks/mutation/use-save-settings.js.map +1 -1
  191. package/dist/hooks/query/use-task-polling.cjs +1 -1
  192. package/dist/hooks/query/use-task-polling.cjs.map +1 -1
  193. package/dist/hooks/query/use-task-polling.d.ts +19 -3
  194. package/dist/hooks/query/use-task-polling.js +36 -35
  195. package/dist/hooks/query/use-task-polling.js.map +1 -1
  196. package/dist/hooks/use-telemetry.cjs +1 -1
  197. package/dist/hooks/use-telemetry.cjs.map +1 -1
  198. package/dist/hooks/use-telemetry.d.ts +3 -3
  199. package/dist/hooks/use-telemetry.js +16 -22
  200. package/dist/hooks/use-telemetry.js.map +1 -1
  201. package/dist/hooks/use-tracking.cjs +1 -1
  202. package/dist/hooks/use-tracking.cjs.map +1 -1
  203. package/dist/hooks/use-tracking.d.ts +3 -4
  204. package/dist/hooks/use-tracking.js +37 -37
  205. package/dist/hooks/use-tracking.js.map +1 -1
  206. package/dist/index.cjs +1 -1
  207. package/dist/index.js +2 -2
  208. package/dist/lib/index.d.ts +2 -2
  209. package/dist/package.cjs +1 -1
  210. package/dist/package.cjs.map +1 -1
  211. package/dist/package.js +2 -2
  212. package/dist/package.js.map +1 -1
  213. package/dist/routes/app-settings.cjs +1 -1
  214. package/dist/routes/app-settings.cjs.map +1 -1
  215. package/dist/routes/app-settings.js +66 -68
  216. package/dist/routes/app-settings.js.map +1 -1
  217. package/dist/routes/conversation.cjs +1 -1
  218. package/dist/routes/conversation.cjs.map +1 -1
  219. package/dist/routes/conversation.js +1 -1
  220. package/dist/routes/conversation.js.map +1 -1
  221. package/dist/routes/llm-settings.cjs +1 -1
  222. package/dist/routes/llm-settings.cjs.map +1 -1
  223. package/dist/routes/llm-settings.js +4 -1
  224. package/dist/routes/llm-settings.js.map +1 -1
  225. package/dist/services/cloud-funnel-analytics.cjs +1 -1
  226. package/dist/services/cloud-funnel-analytics.cjs.map +1 -1
  227. package/dist/services/cloud-funnel-analytics.d.ts +3 -11
  228. package/dist/services/cloud-funnel-analytics.js +16 -30
  229. package/dist/services/cloud-funnel-analytics.js.map +1 -1
  230. package/dist/services/telemetry.cjs +1 -1
  231. package/dist/services/telemetry.cjs.map +1 -1
  232. package/dist/services/telemetry.d.ts +46 -9
  233. package/dist/services/telemetry.js +181 -72
  234. package/dist/services/telemetry.js.map +1 -1
  235. package/dist/themes/color-themes.cjs.map +1 -1
  236. package/dist/themes/color-themes.d.ts +3 -6
  237. package/dist/themes/color-themes.js.map +1 -1
  238. package/dist/utils/error-handler.cjs +1 -1
  239. package/dist/utils/error-handler.cjs.map +1 -1
  240. package/dist/utils/error-handler.d.ts +1 -4
  241. package/dist/utils/error-handler.js +6 -7
  242. package/dist/utils/error-handler.js.map +1 -1
  243. package/dist/utils/map-provider.cjs +1 -1
  244. package/dist/utils/map-provider.cjs.map +1 -1
  245. package/dist/utils/map-provider.d.ts +1 -0
  246. package/dist/utils/map-provider.js +2 -1
  247. package/dist/utils/map-provider.js.map +1 -1
  248. package/package.json +2 -2
  249. package/build/assets/agent-settings-DGUKNSnU.js +0 -1
  250. package/build/assets/app-settings-BpViWwpC.js +0 -1
  251. package/build/assets/backend-form-modal-DhBT8P4g.js +0 -1
  252. package/build/assets/browser-store-BGXOGo-R.js +0 -1
  253. package/build/assets/client-source-Biqarhgr.js +0 -1
  254. package/build/assets/cloud-funnel-analytics-D-27T5Ao.js +0 -1
  255. package/build/assets/conversation-CHnEXGRM.js +0 -1
  256. package/build/assets/conversation-panel-11FjLCId.js +0 -1
  257. package/build/assets/conversation-state-store-JiUQp02U.js +0 -1
  258. package/build/assets/handle-capture-consent-DDnXMC9Y.js +0 -1
  259. package/build/assets/home-BQ-DC06B.js +0 -1
  260. package/build/assets/index-home-BRFDWs_4.js +0 -1
  261. package/build/assets/lesson-plan-DSf95kQ2.js +0 -4
  262. package/build/assets/llm-settings-Ch4ZbW3c.js +0 -1
  263. package/build/assets/manifest-958c741a.js +0 -1
  264. package/build/assets/onboarding-modal-DQwihcTz.js +0 -1
  265. package/build/assets/option-service.api-DAkg_F7p.js +0 -1
  266. package/build/assets/package-Dk_VtWJ4.js +0 -1
  267. package/build/assets/providers-B8pxQK8x.js +0 -1
  268. package/build/assets/root-BhP6bTBB.js +0 -2
  269. package/build/assets/root-layout-oBaHHW4s.js +0 -2
  270. package/build/assets/settings-BfgLHsgd.js +0 -1
  271. package/build/assets/telemetry-DHiTRl5_.js +0 -2
  272. package/build/assets/use-config-CXy4vD0H.js +0 -1
  273. package/build/assets/use-runtime-is-ready-D4pkGT6E.js +0 -1
  274. package/build/assets/use-tracking-poxQHMwR.js +0 -1
  275. package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ljq5gbus-B2XD4bi2.js +0 -1
  276. package/dist/components/providers/posthog-wrapper.cjs +0 -2
  277. package/dist/components/providers/posthog-wrapper.cjs.map +0 -1
  278. package/dist/components/providers/posthog-wrapper.d.ts +0 -4
  279. package/dist/components/providers/posthog-wrapper.js +0 -49
  280. package/dist/components/providers/posthog-wrapper.js.map +0 -1
  281. package/dist/node_modules/posthog-js/react/dist/esm/index.cjs +0 -2
  282. package/dist/node_modules/posthog-js/react/dist/esm/index.cjs.map +0 -1
  283. package/dist/node_modules/posthog-js/react/dist/esm/index.js +0 -113
  284. package/dist/node_modules/posthog-js/react/dist/esm/index.js.map +0 -1
  285. package/dist/utils/handle-capture-consent.cjs +0 -2
  286. package/dist/utils/handle-capture-consent.cjs.map +0 -1
  287. package/dist/utils/handle-capture-consent.d.ts +0 -7
  288. package/dist/utils/handle-capture-consent.js +0 -8
  289. package/dist/utils/handle-capture-consent.js.map +0 -1
  290. /package/build/assets/{__vite-browser-external-DU-Cb_sT.js → __vite-browser-external-BQUj0sYu.js} +0 -0
  291. /package/build/assets/{agent-server-ui-style-scope-COnDHTm5.js → agent-server-ui-style-scope-qhnhZHBV.js} +0 -0
  292. /package/build/assets/{automation-BVEmGghX.js → automation-LZB0MjdV.js} +0 -0
  293. /package/build/assets/{canvas-ui-BhxjoA1f.js → canvas-ui-COgjltGz.js} +0 -0
  294. /package/build/assets/{checkmark-DhcMGwtO.js → checkmark-h7udgXMM.js} +0 -0
  295. /package/build/assets/{close-DO5Rq_CS.js → close-DLFb5kfv.js} +0 -0
  296. /package/build/assets/{code-tag-BvjtzUsr.js → code-tag-DYlAhEQY.js} +0 -0
  297. /package/build/assets/{conversation-local-storage-B31voBg7.js → conversation-local-storage-F6zVMM--.js} +0 -0
  298. /package/build/assets/{conversation-tab-empty-state-BUhKVwoi.js → conversation-tab-empty-state-CNUmIms2.js} +0 -0
  299. /package/build/assets/{copy-DLUw2us-.js → copy-BLmGeJcd.js} +0 -0
  300. /package/build/assets/{dist-Dj01FXbZ.js → dist-CodhvuAP.js} +0 -0
  301. /package/build/assets/{environment-switch-store-D9hB9PvY.js → environment-switch-store-DkjlKWzN.js} +0 -0
  302. /package/build/assets/{extension-module-card-classes-BlO4w702.js → extension-module-card-classes-9V3J9J_b.js} +0 -0
  303. /package/build/assets/{folder-ynxP2EIL.js → folder-Bk311brm.js} +0 -0
  304. /package/build/assets/{form-control-classes-CvUaw8_L.js → form-control-classes-CbdN5aO3.js} +0 -0
  305. /package/build/assets/{get-language-from-path-rWEQrOGU.js → get-language-from-path-DBqBsCWH.js} +0 -0
  306. /package/build/assets/{git-status-mapper-C3rfzUex.js → git-status-mapper-DnL9OC8_.js} +0 -0
  307. /package/build/assets/{iconBase-CuvxAQZ5.js → iconBase-Dz51Zn5x.js} +0 -0
  308. /package/build/assets/{link-external-BVDiJfZt.js → link-external-CaBmUEs_.js} +0 -0
  309. /package/build/assets/{llm-subscription-BkdHwtQe.js → llm-subscription-DtsX5Lhg.js} +0 -0
  310. /package/build/assets/{loading-spinner-Txxx1I1p.js → loading-spinner-BcdnT0mN.js} +0 -0
  311. /package/build/assets/{middleware-CWYDMVuG.js → middleware-DYHYNAnP.js} +0 -0
  312. /package/build/assets/{modal-backdrop-Dsmzgr5s.js → modal-backdrop-zVwfOJZr.js} +0 -0
  313. /package/build/assets/{modal-body-CDZdZ-eT.js → modal-body-Cc2zp0xb.js} +0 -0
  314. /package/build/assets/{modal-classes-B4oit_4c.js → modal-classes-3qT9Ap7Q.js} +0 -0
  315. /package/build/assets/{modal-close-button-DQElFMPo.js → modal-close-button-BcIBUdSq.js} +0 -0
  316. /package/build/assets/{navigation-context-SjzfpygH.js → navigation-context-DPs2G0X8.js} +0 -0
  317. /package/build/assets/{objectWithoutPropertiesLoose-CQwg6tC0.js → objectWithoutPropertiesLoose-BgYIAs2y.js} +0 -0
  318. /package/build/assets/{openhands-logo-C-gDp1wr.js → openhands-logo-bcNw5bkY.js} +0 -0
  319. /package/build/assets/{plan-components-CgPHWz3E.js → plan-components-BdZXPyZU.js} +0 -0
  320. /package/build/assets/{react-ao3tWj6K.js → react-DPDgL6du.js} +0 -0
  321. /package/build/assets/{search-pb4UKTfK.js → search-BgOCR7_c.js} +0 -0
  322. /package/build/assets/{segmented-toggle-C1-ZVyr5.js → segmented-toggle-kzwZkLaj.js} +0 -0
  323. /package/build/assets/{settings-gear-D_HVvE5L.js → settings-gear-MXCyZW0x.js} +0 -0
  324. /package/build/assets/{settings-like-page-layout-classes-DLrsPCaI.js → settings-like-page-layout-classes-Cdl6jOe5.js} +0 -0
  325. /package/build/assets/{settings-DksoDv5z.js → settings-mQUckT0g.js} +0 -0
  326. /package/build/assets/{settings-section-header-context-C2MUIG88.js → settings-section-header-context-COMOIaxV.js} +0 -0
  327. /package/build/assets/{sidebar-layout-Cn6eQaEI.js → sidebar-layout-Bm7pa3Vk.js} +0 -0
  328. /package/build/assets/{skills-CH4xgIh-.js → skills-EqzxYBvT.js} +0 -0
  329. /package/build/assets/{styled-tooltip-CvsjHuIf.js → styled-tooltip-B14i_Uac.js} +0 -0
  330. /package/build/assets/{switch-skeleton-BpSOvZ-n.js → switch-skeleton-DqQwalyu.js} +0 -0
  331. /package/build/assets/{terminal-Dokfkxn0.js → terminal-Cgf___rK.js} +0 -0
  332. /package/build/assets/{toggle-switch-CS0ClOy2.js → toggle-switch-DX_k418f.js} +0 -0
  333. /package/build/assets/{u-check-circle-DPWVO-9N.js → u-check-circle-BqoIxNz_.js} +0 -0
  334. /package/build/assets/{u-check-circle-half-BGNAAjOW.js → u-check-circle-half-MsShT8Ec.js} +0 -0
  335. /package/build/assets/{u-edit-BIskOoEG.js → u-edit-DL5LgKon.js} +0 -0
  336. /package/build/assets/{use-breakpoint-OjLm94cF.js → use-breakpoint-BP3lqLMx.js} +0 -0
  337. /package/build/assets/{use-click-outside-element-Jpt2WWd7.js → use-click-outside-element-D9BYdANT.js} +0 -0
  338. /package/build/assets/{use-is-authed-Dvbpiu3B.js → use-is-authed-BezEyfeh.js} +0 -0
  339. /package/build/assets/{use-onboarding-completion-DkRpDTsD.js → use-onboarding-completion-C_Swglzp.js} +0 -0
  340. /package/build/assets/{v4-DPgam_jW.js → v4-DbCkTSoU.js} +0 -0
  341. /package/build/assets/{vendor~browser-BvEKaLBz.js → vendor~browser-By2MVv_N.js} +0 -0
  342. /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-CznX01nZ.js → vendor~conversation-panel~conversation~alert-banner-CxfzCmkN.js} +0 -0
  343. /package/build/assets/{vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BuL5kjfS.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-FghQ3Z1a.js} +0 -0
  344. /package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~launch~skills-settings~a~j2jlu22h-DUYl8gbZ.js → vendor~root-layout~index-home~home~conversation-panel~conversation~launch~skills-settings~a~j2jlu22h-mC0v44WR.js} +0 -0
  345. /package/build/assets/{vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-pIgQDxTc.js → vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-C4-GDgiF.js} +0 -0
  346. /package/build/assets/{vendor~terminal-jAaNQWS9.js → vendor~terminal-CnKZILnC.js} +0 -0
  347. /package/build/assets/{x-mark-C5przfXl.js → x-mark-Batp6gXa.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.js","names":[],"sources":["../../src/routes/conversation.tsx"],"sourcesContent":["import React from \"react\";\nimport { useNavigate, useLocation, useMatch } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\n\nimport { useConversationId } from \"#/hooks/use-conversation-id\";\nimport { useCommandStore } from \"#/stores/command-store\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { useAgentStore } from \"#/stores/agent-store\";\nimport { useConversationStateStore } from \"#/stores/conversation-state-store\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n clearLastConversationId,\n setLastConversationId,\n} from \"#/api/backend-registry/last-conversation-store\";\nimport { AgentState } from \"#/types/agent-state\";\n\nimport { EventHandler } from \"../wrapper/event-handler\";\n\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useTaskPolling } from \"#/hooks/query/use-task-polling\";\n\nimport { displayErrorToast } from \"#/utils/custom-toast-handlers\";\nimport { useIsAuthed } from \"#/hooks/query/use-is-authed\";\nimport { ConversationMain } from \"#/components/features/conversation/conversation-main/conversation-main\";\nimport { ConversationMobilePanelPage } from \"#/components/features/conversation/conversation-main/conversation-mobile-panel-page\";\n\nimport { WebSocketProviderWrapper } from \"#/contexts/websocket-provider-wrapper\";\nimport { useErrorMessageStore } from \"#/stores/error-message-store\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { resumeCloudSandbox } from \"#/api/cloud/conversation-service.api\";\n\nfunction AppContent() {\n const { t } = useTranslation(\"openhands\");\n const { conversationId } = useConversationId();\n const panelViewMatch = useMatch(\"/conversations/:conversationId/panel\");\n\n const { isTask, taskStatus, taskDetail } = useTaskPolling();\n\n // The conversationId in the URL belongs to whichever backend was\n // active when the route first mounted. If the user switches backends\n // while this route is still mounted, the id is meaningless under the\n // new backend — disable the active-conversation fetch (and its 404\n // toast) so we don't fire a request that the BackendSelector's\n // redirect will immediately navigate away from anyway. Mirrors the\n // same guard in `routes/automation-detail.tsx`.\n const active = useActiveBackend();\n const mountedBackendId = React.useRef(active.backend.id);\n const mountedOrgId = React.useRef(active.orgId);\n const backendChanged =\n mountedBackendId.current !== active.backend.id ||\n mountedOrgId.current !== active.orgId;\n\n const { data: conversation, isFetched } = useActiveConversation();\n const { data: isAuthed } = useIsAuthed();\n const { resetConversationState } = useConversationStore();\n const navigate = useNavigate();\n const location = useLocation();\n const clearTerminal = useCommandStore((state) => state.clearTerminal);\n const resetConversationRuntimeState = useConversationStateStore(\n (state) => state.reset,\n );\n const setCurrentAgentState = useAgentStore(\n (state) => state.setCurrentAgentState,\n );\n const removeErrorMessage = useErrorMessageStore(\n (state) => state.removeErrorMessage,\n );\n\n // Per-conversation UI/runtime resets. The event store is cleared separately,\n // inside ConversationWebSocketProvider, so the clear is ordered *before* the\n // preloaded-history re-seed (see the note there) — clearing it here would run\n // too late and wipe the freshly seeded history on a conversation switch.\n React.useEffect(() => {\n clearTerminal();\n resetConversationState();\n resetConversationRuntimeState();\n setCurrentAgentState(AgentState.LOADING);\n removeErrorMessage();\n }, [\n conversationId,\n clearTerminal,\n resetConversationState,\n resetConversationRuntimeState,\n setCurrentAgentState,\n removeErrorMessage,\n ]);\n\n React.useEffect(() => {\n if (isTask && taskStatus === \"ERROR\") {\n displayErrorToast(\n taskDetail || t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK),\n );\n // Navigate back to the original conversation when a resume task fails so\n // the user isn't stranded at the dead task-{id} URL. The resume effect's\n // ref prevents it from immediately retrying once we land there.\n const resumedFrom = (location.state as Record<string, unknown> | null)\n ?.resumedFromConversationId as string | undefined;\n navigate(\n resumedFrom ? `/conversations/${resumedFrom}` : \"/conversations\",\n { replace: true },\n );\n }\n }, [isTask, taskStatus, taskDetail, t, navigate, location.state]);\n\n React.useEffect(() => {\n if (!isFetched || !isAuthed) return;\n // The BackendSelector is in the middle of redirecting us away from\n // this route — don't toast/navigate based on a 404 that's just\n // \"this id doesn't exist on the new backend\".\n if (backendChanged) return;\n\n if (!conversation) {\n // Clear the per-backend \"last selected\" slot so the next switch\n // to this backend doesn't try to revisit a stale id.\n clearLastConversationId(active.backend.id, active.orgId);\n displayErrorToast(t(I18nKey.CONVERSATION$NOT_EXIST_OR_NO_PERMISSION));\n navigate(\"/conversations\");\n }\n }, [\n conversation,\n isFetched,\n isAuthed,\n navigate,\n t,\n backendChanged,\n active.backend.id,\n active.orgId,\n ]);\n\n // Remember the most recently selected conversation for the current\n // (backend, org) so flipping back to this backend later restores the\n // user to where they left off. Skip while a backend switch is in\n // flight: the id in the URL is from the previous backend and would\n // otherwise overwrite the new backend's memory.\n React.useEffect(() => {\n if (backendChanged) return;\n if (!conversationId) return;\n if (conversationId.startsWith(\"task-\")) return;\n setLastConversationId(active.backend.id, active.orgId, conversationId);\n }, [conversationId, backendChanged, active.backend.id, active.orgId]);\n\n // Cloud conversation resume: mirrors OpenHands' useSandboxRecovery.\n //\n // When the cloud API reports sandbox_status === \"PAUSED\" the sandbox is\n // sleeping. The correct wake-up call is POST /api/v1/sandboxes/{id}/resume\n // (a lightweight unpause). The previous approach — creating a new start task\n // via POST /api/v1/app-conversations — was wrong: it tries to provision a\n // fresh conversation in the sandbox and is subject to a 120-second cold-start\n // timeout that can fail. The resume endpoint simply unpauses the existing one.\n //\n // After calling resume we stay on the current URL. The 3-second refetch\n // interval in useActiveConversation (active while conversation_url is null)\n // polls until conversation_url populates, then the WebSocket connects.\n //\n // A ref guards against duplicate triggers per unique conversation.id within\n // the same route-mount lifetime.\n const resumeTriggeredForRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n if (!isFetched || !conversation) return;\n if (active.backend.kind !== \"cloud\") return;\n if (conversation.sandbox_status !== \"PAUSED\") return; // only resume PAUSED sandboxes\n if (!conversation.sandbox_id) return; // no sandbox to resume\n if (resumeTriggeredForRef.current === conversation.id) return; // already sent\n\n resumeTriggeredForRef.current = conversation.id;\n\n resumeCloudSandbox(conversation.sandbox_id).catch(() => {\n displayErrorToast(t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK));\n });\n }, [\n isFetched,\n conversation?.id,\n conversation?.sandbox_status,\n conversation?.sandbox_id,\n active.backend.kind,\n t,\n ]);\n\n // A backend switch is in flight (BackendSelector flips the active backend\n // and redirects to /conversations on the next tick). The conversationId in\n // the URL belongs to the *previous* backend, so unmount the whole\n // conversation subtree now — before any per-conversation query (history,\n // metrics, sub-conversations, runtime info, …) re-fires against a backend\n // the id is foreign to. Those foreign fetches fail response validation and\n // surface \"agent server returned data this UI does not understand\". This is\n // deterministic regardless of the navigate-vs-setActive render race: React\n // re-renders this parent before its children, so returning null removes them\n // before they can issue the request.\n if (backendChanged) {\n return null;\n }\n\n const content = (\n <EventHandler>\n <div data-testid=\"app-route\" className=\"flex h-full flex-col\">\n {panelViewMatch ? (\n <ConversationMobilePanelPage\n onNavigateBack={() => navigate(`/conversations/${conversationId}`)}\n />\n ) : (\n <ConversationMain />\n )}\n </div>\n </EventHandler>\n );\n\n return (\n <WebSocketProviderWrapper conversationId={conversationId}>\n {content}\n </WebSocketProviderWrapper>\n );\n}\n\nexport function ConversationView() {\n return <AppContent />;\n}\n\nexport default ConversationView;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAS,IAAa;CACpB,IAAM,EAAE,SAAM,EAAe,YAAY,EACnC,EAAE,sBAAmB,GAAmB,EACxC,IAAiB,EAAS,uCAAuC,EAEjE,EAAE,WAAQ,eAAY,kBAAe,GAAgB,EASrD,IAAS,GAAkB,EAC3B,IAAmB,EAAM,OAAO,EAAO,QAAQ,GAAG,EAClD,IAAe,EAAM,OAAO,EAAO,MAAM,EACzC,IACJ,EAAiB,YAAY,EAAO,QAAQ,MAC5C,EAAa,YAAY,EAAO,OAE5B,EAAE,MAAM,GAAc,iBAAc,GAAuB,EAC3D,EAAE,MAAM,MAAa,GAAa,EAClC,EAAE,8BAA2B,GAAsB,EACnD,IAAW,GAAa,EACxB,IAAW,GAAa,EACxB,IAAgB,GAAiB,MAAU,EAAM,cAAc,EAC/D,IAAgC,GACnC,MAAU,EAAM,MAClB,EACK,IAAuB,GAC1B,MAAU,EAAM,qBAClB,EACK,IAAqB,GACxB,MAAU,EAAM,mBAClB;AAoED,CA9DA,EAAM,gBAAgB;AAKpB,EAJA,GAAe,EACf,GAAwB,EACxB,GAA+B,EAC/B,EAAqB,EAAW,QAAQ,EACxC,GAAoB;IACnB;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAEF,EAAM,gBAAgB;AACpB,MAAI,KAAU,MAAe,SAAS;AACpC,KACE,KAAc,EAAE,EAAQ,uCAAuC,CAChE;GAID,IAAM,IAAe,EAAS,OAC1B;AACJ,KACE,IAAc,kBAAkB,MAAgB,kBAChD,EAAE,SAAS,IAAM,CAClB;;IAEF;EAAC;EAAQ;EAAY;EAAY;EAAG;EAAU,EAAS;EAAM,CAAC,EAEjE,EAAM,gBAAgB;AAChB,GAAC,KAAa,CAAC,KAIf,KAEC,MAGH,EAAwB,EAAO,QAAQ,IAAI,EAAO,MAAM,EACxD,EAAkB,EAAE,EAAQ,wCAAwC,CAAC,EACrE,EAAS,iBAAiB;IAE3B;EACD;EACA;EACA;EACA;EACA;EACA;EACA,EAAO,QAAQ;EACf,EAAO;EACR,CAAC,EAOF,EAAM,gBAAgB;AAChB,OACC,MACD,EAAe,WAAW,QAAQ,IACtC,EAAsB,EAAO,QAAQ,IAAI,EAAO,OAAO,EAAe;IACrE;EAAC;EAAgB;EAAgB,EAAO,QAAQ;EAAI,EAAO;EAAM,CAAC;CAiBrE,IAAM,IAAwB,EAAM,OAAsB,KAAK;AAkD/D,QAjDA,EAAM,gBAAgB;AAChB,GAAC,KAAa,CAAC,KACf,EAAO,QAAQ,SAAS,WACxB,EAAa,mBAAmB,YAC/B,EAAa,cACd,EAAsB,YAAY,EAAa,OAEnD,EAAsB,UAAU,EAAa,IAE7C,EAAmB,EAAa,WAAW,CAAC,YAAY;AACtD,KAAkB,EAAE,EAAQ,uCAAuC,CAAC;IACpE;IACD;EACD;EACA,GAAc;EACd,GAAc;EACd,GAAc;EACd,EAAO,QAAQ;EACf;EACD,CAAC,EAYE,IACK,OAkBP,kBAAC,GAAD;EAA0C;YAd1C,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;GAAK,eAAY;GAAY,WAAU;aACpC,IACC,kBAAC,GAAD,EACE,sBAAsB,EAAS,kBAAkB,IAAiB,EAClE,CAAA,GAEF,kBAAC,GAAD,EAAoB,CAAA;GAElB,CAAA,EACO,CAKZ;EACwB,CAAA;;AAI/B,SAAgB,IAAmB;AACjC,QAAO,kBAAC,GAAD,EAAc,CAAA"}
1
+ {"version":3,"file":"conversation.js","names":[],"sources":["../../src/routes/conversation.tsx"],"sourcesContent":["import React from \"react\";\nimport { useNavigate, useLocation, useMatch } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\n\nimport { useConversationId } from \"#/hooks/use-conversation-id\";\nimport { useCommandStore } from \"#/stores/command-store\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { useAgentStore } from \"#/stores/agent-store\";\nimport { useConversationStateStore } from \"#/stores/conversation-state-store\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n clearLastConversationId,\n setLastConversationId,\n} from \"#/api/backend-registry/last-conversation-store\";\nimport { AgentState } from \"#/types/agent-state\";\n\nimport { EventHandler } from \"../wrapper/event-handler\";\n\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useTaskPollingController } from \"#/hooks/query/use-task-polling\";\n\nimport { displayErrorToast } from \"#/utils/custom-toast-handlers\";\nimport { useIsAuthed } from \"#/hooks/query/use-is-authed\";\nimport { ConversationMain } from \"#/components/features/conversation/conversation-main/conversation-main\";\nimport { ConversationMobilePanelPage } from \"#/components/features/conversation/conversation-main/conversation-mobile-panel-page\";\n\nimport { WebSocketProviderWrapper } from \"#/contexts/websocket-provider-wrapper\";\nimport { useErrorMessageStore } from \"#/stores/error-message-store\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { resumeCloudSandbox } from \"#/api/cloud/conversation-service.api\";\n\nfunction AppContent() {\n const { t } = useTranslation(\"openhands\");\n const { conversationId } = useConversationId();\n const panelViewMatch = useMatch(\"/conversations/:conversationId/panel\");\n\n const { isTask, taskStatus, taskDetail } = useTaskPollingController();\n\n // The conversationId in the URL belongs to whichever backend was\n // active when the route first mounted. If the user switches backends\n // while this route is still mounted, the id is meaningless under the\n // new backend — disable the active-conversation fetch (and its 404\n // toast) so we don't fire a request that the BackendSelector's\n // redirect will immediately navigate away from anyway. Mirrors the\n // same guard in `routes/automation-detail.tsx`.\n const active = useActiveBackend();\n const mountedBackendId = React.useRef(active.backend.id);\n const mountedOrgId = React.useRef(active.orgId);\n const backendChanged =\n mountedBackendId.current !== active.backend.id ||\n mountedOrgId.current !== active.orgId;\n\n const { data: conversation, isFetched } = useActiveConversation();\n const { data: isAuthed } = useIsAuthed();\n const { resetConversationState } = useConversationStore();\n const navigate = useNavigate();\n const location = useLocation();\n const clearTerminal = useCommandStore((state) => state.clearTerminal);\n const resetConversationRuntimeState = useConversationStateStore(\n (state) => state.reset,\n );\n const setCurrentAgentState = useAgentStore(\n (state) => state.setCurrentAgentState,\n );\n const removeErrorMessage = useErrorMessageStore(\n (state) => state.removeErrorMessage,\n );\n\n // Per-conversation UI/runtime resets. The event store is cleared separately,\n // inside ConversationWebSocketProvider, so the clear is ordered *before* the\n // preloaded-history re-seed (see the note there) — clearing it here would run\n // too late and wipe the freshly seeded history on a conversation switch.\n React.useEffect(() => {\n clearTerminal();\n resetConversationState();\n resetConversationRuntimeState();\n setCurrentAgentState(AgentState.LOADING);\n removeErrorMessage();\n }, [\n conversationId,\n clearTerminal,\n resetConversationState,\n resetConversationRuntimeState,\n setCurrentAgentState,\n removeErrorMessage,\n ]);\n\n React.useEffect(() => {\n if (isTask && taskStatus === \"ERROR\") {\n displayErrorToast(\n taskDetail || t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK),\n );\n // Navigate back to the original conversation when a resume task fails so\n // the user isn't stranded at the dead task-{id} URL. The resume effect's\n // ref prevents it from immediately retrying once we land there.\n const resumedFrom = (location.state as Record<string, unknown> | null)\n ?.resumedFromConversationId as string | undefined;\n navigate(\n resumedFrom ? `/conversations/${resumedFrom}` : \"/conversations\",\n { replace: true },\n );\n }\n }, [isTask, taskStatus, taskDetail, t, navigate, location.state]);\n\n React.useEffect(() => {\n if (!isFetched || !isAuthed) return;\n // The BackendSelector is in the middle of redirecting us away from\n // this route — don't toast/navigate based on a 404 that's just\n // \"this id doesn't exist on the new backend\".\n if (backendChanged) return;\n\n if (!conversation) {\n // Clear the per-backend \"last selected\" slot so the next switch\n // to this backend doesn't try to revisit a stale id.\n clearLastConversationId(active.backend.id, active.orgId);\n displayErrorToast(t(I18nKey.CONVERSATION$NOT_EXIST_OR_NO_PERMISSION));\n navigate(\"/conversations\");\n }\n }, [\n conversation,\n isFetched,\n isAuthed,\n navigate,\n t,\n backendChanged,\n active.backend.id,\n active.orgId,\n ]);\n\n // Remember the most recently selected conversation for the current\n // (backend, org) so flipping back to this backend later restores the\n // user to where they left off. Skip while a backend switch is in\n // flight: the id in the URL is from the previous backend and would\n // otherwise overwrite the new backend's memory.\n React.useEffect(() => {\n if (backendChanged) return;\n if (!conversationId) return;\n if (conversationId.startsWith(\"task-\")) return;\n setLastConversationId(active.backend.id, active.orgId, conversationId);\n }, [conversationId, backendChanged, active.backend.id, active.orgId]);\n\n // Cloud conversation resume: mirrors OpenHands' useSandboxRecovery.\n //\n // When the cloud API reports sandbox_status === \"PAUSED\" the sandbox is\n // sleeping. The correct wake-up call is POST /api/v1/sandboxes/{id}/resume\n // (a lightweight unpause). The previous approach — creating a new start task\n // via POST /api/v1/app-conversations — was wrong: it tries to provision a\n // fresh conversation in the sandbox and is subject to a 120-second cold-start\n // timeout that can fail. The resume endpoint simply unpauses the existing one.\n //\n // After calling resume we stay on the current URL. The 3-second refetch\n // interval in useActiveConversation (active while conversation_url is null)\n // polls until conversation_url populates, then the WebSocket connects.\n //\n // A ref guards against duplicate triggers per unique conversation.id within\n // the same route-mount lifetime.\n const resumeTriggeredForRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n if (!isFetched || !conversation) return;\n if (active.backend.kind !== \"cloud\") return;\n if (conversation.sandbox_status !== \"PAUSED\") return; // only resume PAUSED sandboxes\n if (!conversation.sandbox_id) return; // no sandbox to resume\n if (resumeTriggeredForRef.current === conversation.id) return; // already sent\n\n resumeTriggeredForRef.current = conversation.id;\n\n resumeCloudSandbox(conversation.sandbox_id).catch(() => {\n displayErrorToast(t(I18nKey.CONVERSATION$FAILED_TO_START_FROM_TASK));\n });\n }, [\n isFetched,\n conversation?.id,\n conversation?.sandbox_status,\n conversation?.sandbox_id,\n active.backend.kind,\n t,\n ]);\n\n // A backend switch is in flight (BackendSelector flips the active backend\n // and redirects to /conversations on the next tick). The conversationId in\n // the URL belongs to the *previous* backend, so unmount the whole\n // conversation subtree now — before any per-conversation query (history,\n // metrics, sub-conversations, runtime info, …) re-fires against a backend\n // the id is foreign to. Those foreign fetches fail response validation and\n // surface \"agent server returned data this UI does not understand\". This is\n // deterministic regardless of the navigate-vs-setActive render race: React\n // re-renders this parent before its children, so returning null removes them\n // before they can issue the request.\n if (backendChanged) {\n return null;\n }\n\n const content = (\n <EventHandler>\n <div data-testid=\"app-route\" className=\"flex h-full flex-col\">\n {panelViewMatch ? (\n <ConversationMobilePanelPage\n onNavigateBack={() => navigate(`/conversations/${conversationId}`)}\n />\n ) : (\n <ConversationMain />\n )}\n </div>\n </EventHandler>\n );\n\n return (\n <WebSocketProviderWrapper conversationId={conversationId}>\n {content}\n </WebSocketProviderWrapper>\n );\n}\n\nexport function ConversationView() {\n return <AppContent />;\n}\n\nexport default ConversationView;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAS,IAAa;CACpB,IAAM,EAAE,SAAM,EAAe,YAAY,EACnC,EAAE,sBAAmB,GAAmB,EACxC,IAAiB,EAAS,uCAAuC,EAEjE,EAAE,WAAQ,eAAY,kBAAe,GAA0B,EAS/D,IAAS,GAAkB,EAC3B,IAAmB,EAAM,OAAO,EAAO,QAAQ,GAAG,EAClD,IAAe,EAAM,OAAO,EAAO,MAAM,EACzC,IACJ,EAAiB,YAAY,EAAO,QAAQ,MAC5C,EAAa,YAAY,EAAO,OAE5B,EAAE,MAAM,GAAc,iBAAc,GAAuB,EAC3D,EAAE,MAAM,MAAa,GAAa,EAClC,EAAE,8BAA2B,GAAsB,EACnD,IAAW,GAAa,EACxB,IAAW,GAAa,EACxB,IAAgB,GAAiB,MAAU,EAAM,cAAc,EAC/D,IAAgC,GACnC,MAAU,EAAM,MAClB,EACK,IAAuB,GAC1B,MAAU,EAAM,qBAClB,EACK,IAAqB,GACxB,MAAU,EAAM,mBAClB;AAoED,CA9DA,EAAM,gBAAgB;AAKpB,EAJA,GAAe,EACf,GAAwB,EACxB,GAA+B,EAC/B,EAAqB,EAAW,QAAQ,EACxC,GAAoB;IACnB;EACD;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAEF,EAAM,gBAAgB;AACpB,MAAI,KAAU,MAAe,SAAS;AACpC,KACE,KAAc,EAAE,EAAQ,uCAAuC,CAChE;GAID,IAAM,IAAe,EAAS,OAC1B;AACJ,KACE,IAAc,kBAAkB,MAAgB,kBAChD,EAAE,SAAS,IAAM,CAClB;;IAEF;EAAC;EAAQ;EAAY;EAAY;EAAG;EAAU,EAAS;EAAM,CAAC,EAEjE,EAAM,gBAAgB;AAChB,GAAC,KAAa,CAAC,KAIf,KAEC,MAGH,EAAwB,EAAO,QAAQ,IAAI,EAAO,MAAM,EACxD,EAAkB,EAAE,EAAQ,wCAAwC,CAAC,EACrE,EAAS,iBAAiB;IAE3B;EACD;EACA;EACA;EACA;EACA;EACA;EACA,EAAO,QAAQ;EACf,EAAO;EACR,CAAC,EAOF,EAAM,gBAAgB;AAChB,OACC,MACD,EAAe,WAAW,QAAQ,IACtC,EAAsB,EAAO,QAAQ,IAAI,EAAO,OAAO,EAAe;IACrE;EAAC;EAAgB;EAAgB,EAAO,QAAQ;EAAI,EAAO;EAAM,CAAC;CAiBrE,IAAM,IAAwB,EAAM,OAAsB,KAAK;AAkD/D,QAjDA,EAAM,gBAAgB;AAChB,GAAC,KAAa,CAAC,KACf,EAAO,QAAQ,SAAS,WACxB,EAAa,mBAAmB,YAC/B,EAAa,cACd,EAAsB,YAAY,EAAa,OAEnD,EAAsB,UAAU,EAAa,IAE7C,EAAmB,EAAa,WAAW,CAAC,YAAY;AACtD,KAAkB,EAAE,EAAQ,uCAAuC,CAAC;IACpE;IACD;EACD;EACA,GAAc;EACd,GAAc;EACd,GAAc;EACd,EAAO,QAAQ;EACf;EACD,CAAC,EAYE,IACK,OAkBP,kBAAC,GAAD;EAA0C;YAd1C,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;GAAK,eAAY;GAAY,WAAU;aACpC,IACC,kBAAC,GAAD,EACE,sBAAsB,EAAS,kBAAkB,IAAiB,EAClE,CAAA,GAEF,kBAAC,GAAD,EAAoB,CAAA;GAElB,CAAA,EACO,CAKZ;EACwB,CAAA;;AAI/B,SAAgB,IAAmB;AACjC,QAAO,kBAAC,GAAD,EAAc,CAAA"}
@@ -1,2 +1,2 @@
1
- const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../node_modules/react-i18next/dist/es/useTranslation.cjs`),n=require(`../i18n/declaration.cjs`),r=require(`../services/settings.cjs`),i=require(`../constants/llm-subscription.cjs`),a=require(`../hooks/query/use-settings.cjs`),o=require(`../components/features/settings/settings-input.cjs`),s=require(`../components/features/settings/settings-dropdown-input.cjs`),c=require(`../utils/extract-model-and-provider.cjs`),l=require(`../ui/help-link.cjs`),u=require(`../components/shared/modals/settings/model-selector.cjs`),d=require(`../hooks/query/use-agent-settings-schema.cjs`),f=require(`../components/features/settings/llm-settings/openai-subscription-auth-card.cjs`),p=require(`../components/features/settings/key-status-icon.cjs`),m=require(`../utils/sdk-settings-schema.cjs`),h=require(`../components/features/settings/sdk-settings/sdk-section-page.cjs`);require(`../components/features/settings/llm-profiles/index.cjs`);const g=require(`../hooks/query/use-llm-subscription-models.cjs`);let _=require(`react`);_=e.__toESM(_,1);let v=require(`react/jsx-runtime`);var y=new Set([`llm.model`,`llm.api_key`,`llm.base_url`,i.LLM_AUTH_TYPE_KEY,i.LLM_SUBSCRIPTION_VENDOR_KEY]),b=(e,t)=>!e||!t?null:`${e}/${t}`,x=(e,t)=>e?.sections.flatMap(e=>e.fields).find(e=>e.key===t)?.default??null,S={openai:new Set([`https://api.openai.com`,`https://api.openai.com/v1`])},C=e=>{try{let t=new URL(e),n=t.pathname.replace(/\/+$/,``)||``;return`${t.origin}${n}`}catch{return e.trim().replace(/\/+$/,``)}},w=(e,t)=>{let n=C(t),{provider:r}=c.extractModelAndProvider(e);if(r){let e=S[r];if(e)return e.has(n)}return Object.values(S).some(e=>e?.has(n))};function T({testId:e}){let{t:r}=t.useTranslation(`openhands`);return(0,v.jsx)(l.HelpLink,{testId:e,text:r(n.I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT),linkText:r(n.I18nKey.SETTINGS$NAV_API_KEYS),href:`https://app.all-hands.dev/settings/api-keys`,suffix:` ${r(n.I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`})}function E({scope:e=`personal`,onSaveSuccess:c,initialValueOverrides:S,embedded:C,hideSaveButton:E,suppressSuccessToast:D,onSaveControlChange:O}){let{t:k}=t.useTranslation(`openhands`),{data:A}=a.useSettings(e),{data:j}=d.useAgentSettingsSchema(A?.agent_settings_schema),M=A?.agent_settings?.llm,N=i.resolveLlmAuthType(S?.[`llm.auth_type`]??M?.auth_type),[P,F]=_.default.useState(N===i.LLM_AUTH_TYPE_SUBSCRIPTION),{data:I,isLoading:L,isFetching:R}=g.useOpenAISubscriptionModels({enabled:P}),z=P&&!I&&(L||R),B=_.default.useRef(null),V=_.default.useRef(null);_.default.useEffect(()=>{N===`subscription`&&F(!0)},[N]);let H=String(r.DEFAULT_SETTINGS.agent_settings?.llm?.model??``),U=_.default.useCallback((e,t)=>{let n=m.inferInitialView(e,t);if(n!==`basic`)return n;let r=e.llm_model??``,i=e.llm_base_url?.trim()??``;return i.length>0&&!w(r,i)?`all`:`basic`},[]),W=_.default.useCallback(({values:e,isDisabled:t,view:r,onChange:a})=>{let c=typeof e[`llm.model`]==`string`?e[`llm.model`]:``,d=typeof e[`llm.base_url`]==`string`?e[`llm.base_url`]:``,m=c.startsWith(`openhands/`),h=i.resolveLlmAuthType(e[i.LLM_AUTH_TYPE_KEY]),g=h===i.LLM_AUTH_TYPE_SUBSCRIPTION,_=t||g&&z,y=I?.includes(c)?c:I?.[0]??``,x=typeof e[`llm.api_key`]==`string`?e[`llm.api_key`]:``,S=C?x.length>0:!!A?.llm_api_key_set,w=(e,r)=>(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(o.SettingsInput,{testId:e,label:k(n.I18nKey.SETTINGS_FORM$API_KEY),type:`password`,className:`w-full`,value:x,placeholder:S?`<hidden>`:``,onChange:e=>a(`llm.api_key`,e),isDisabled:t,startContent:S?(0,v.jsx)(p.KeyStatusIcon,{isSet:S}):void 0}),(0,v.jsx)(l.HelpLink,{testId:r,text:k(n.I18nKey.SETTINGS$DONT_KNOW_API_KEY),linkText:k(n.I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS),href:`https://docs.openhands.dev/usage/local-setup#getting-an-api-key`})]}),E=e=>{let t=e===`subscription`?i.LLM_AUTH_TYPE_SUBSCRIPTION:i.LLM_AUTH_TYPE_API_KEY;if(a(i.LLM_AUTH_TYPE_KEY,t),t===`subscription`){F(!0),c&&!I?.includes(c)&&(B.current=c);let e=V.current&&I?.includes(V.current)?V.current:I?.[0];a(i.LLM_SUBSCRIPTION_VENDOR_KEY,i.OPENAI_SUBSCRIPTION_VENDOR),!I?.includes(c)&&e&&a(`llm.model`,e);return}c&&I?.includes(c)&&(V.current=c,a(`llm.model`,B.current??H))},D=()=>(0,v.jsx)(s.SettingsDropdownInput,{testId:`llm-auth-type-input`,name:i.LLM_AUTH_TYPE_KEY,label:k(n.I18nKey.SETTINGS$LLM_AUTH_TYPE),items:[{key:i.LLM_AUTH_TYPE_API_KEY,label:k(n.I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY)},{key:i.LLM_AUTH_TYPE_SUBSCRIPTION,label:k(n.I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION)}],selectedKey:h,isClearable:!1,required:!0,isDisabled:_,onSelectionChange:E}),O=()=>(0,v.jsxs)(`div`,{className:`flex flex-col gap-6`,"data-testid":`llm-subscription-settings`,children:[(0,v.jsx)(s.SettingsDropdownInput,{testId:`llm-subscription-model-input`,name:`llm.subscription_model`,label:k(n.I18nKey.SETTINGS$SUBSCRIPTION_MODEL),items:(I??[]).map(e=>({key:e,label:e})),selectedKey:y,isClearable:!1,required:!0,isDisabled:_||!I?.length,onSelectionChange:e=>{let t=typeof e==`string`?e:I?.[0];t&&a(`llm.model`,t)}}),(0,v.jsx)(f.OpenAISubscriptionAuthCard,{isDisabled:t})]});return(0,v.jsx)(`div`,{className:`flex flex-col gap-6`,children:r===`basic`?(0,v.jsxs)(`div`,{className:`flex flex-col gap-6`,"data-testid":`llm-settings-form-basic`,children:[D(),g?O():(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(u.ModelSelector,{currentModel:c||void 0,onChange:(e,t)=>{let n=b(e,t);n&&a(`llm.model`,n)},wrapperClassName:`!flex-col !gap-6`,isDisabled:t}),m?(0,v.jsx)(T,{testId:`openhands-api-key-help`}):null,w(`llm-api-key-input`,`llm-api-key-help-anchor`)]})]}):(0,v.jsxs)(`div`,{className:`flex flex-col gap-6`,"data-testid":`llm-settings-form-advanced`,children:[D(),g?O():(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(o.SettingsInput,{testId:`llm-custom-model-input`,label:k(n.I18nKey.SETTINGS$CUSTOM_MODEL),type:`text`,className:`w-full`,value:c,placeholder:H,onChange:e=>a(`llm.model`,e),isDisabled:t}),m?(0,v.jsx)(T,{testId:`openhands-api-key-help-2`}):null,(0,v.jsx)(o.SettingsInput,{testId:`base-url-input`,label:k(n.I18nKey.SETTINGS$BASE_URL),type:`text`,className:`w-full`,value:d,placeholder:`https://api.openai.com`,onChange:e=>a(`llm.base_url`,e),isDisabled:t}),w(`llm-api-key-input`,`llm-api-key-help-anchor-advanced`)]})]})})},[H,C,z,A?.llm_api_key_set,I,k]),G=_.default.useCallback((e,t)=>{let n=structuredClone(e.agent_settings_diff??{}),r=n.llm??{};if(i.resolveLlmAuthType(t.values[`llm.auth_type`])===`subscription`){r.auth_type=i.LLM_AUTH_TYPE_SUBSCRIPTION,r.subscription_vendor=i.OPENAI_SUBSCRIPTION_VENDOR;let e=typeof r.model==`string`?r.model:String(t.values[`llm.model`]??``),n=I?.[0];if(!I?.includes(e)&&!n)throw Error(`Subscription models are not loaded yet.`);r.model=I?.includes(e)?e:n,delete r.api_key,delete r.base_url}else t.dirty[`llm.auth_type`]&&(r.auth_type=i.LLM_AUTH_TYPE_API_KEY,r.subscription_vendor=null),t.view===`basic`&&r.model!==void 0&&(r.base_url=x(j,`llm.base_url`));return n.llm=r,{agent_settings_diff:n}},[j,I]);return(0,v.jsx)(h.SdkSectionPage,{scope:e,settingsSources:[{settingsSource:`agent_settings`,sectionKeys:[`llm`],excludeKeys:y}],header:W,buildPayload:G,getInitialView:U,forceShowAdvancedView:!0,allowAllView:!0,onSaveSuccess:c,initialValueOverrides:S,embedded:C,hideSaveButton:E,suppressSuccessToast:D,onSaveControlChange:O,testId:`llm-settings-screen`})}exports.LlmSettingsScreen=E;
1
+ const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../node_modules/react-i18next/dist/es/useTranslation.cjs`),n=require(`../i18n/declaration.cjs`),r=require(`../services/settings.cjs`),i=require(`../constants/llm-subscription.cjs`),a=require(`../hooks/query/use-settings.cjs`),o=require(`../components/features/settings/settings-input.cjs`),s=require(`../components/features/settings/settings-dropdown-input.cjs`),c=require(`../utils/extract-model-and-provider.cjs`),l=require(`../ui/help-link.cjs`),u=require(`../components/shared/modals/settings/model-selector.cjs`),d=require(`../hooks/query/use-agent-settings-schema.cjs`),f=require(`../components/features/settings/llm-settings/openai-subscription-auth-card.cjs`),p=require(`../components/features/settings/key-status-icon.cjs`),m=require(`../utils/sdk-settings-schema.cjs`),h=require(`../components/features/settings/sdk-settings/sdk-section-page.cjs`);require(`../components/features/settings/llm-profiles/index.cjs`);const g=require(`../hooks/query/use-llm-subscription-models.cjs`);let _=require(`react`);_=e.__toESM(_,1);let v=require(`react/jsx-runtime`);var y=new Set([`llm.model`,`llm.api_key`,`llm.base_url`,i.LLM_AUTH_TYPE_KEY,i.LLM_SUBSCRIPTION_VENDOR_KEY]),b=(e,t)=>!e||!t?null:`${e}/${t}`,x=(e,t)=>e?.sections.flatMap(e=>e.fields).find(e=>e.key===t)?.default??null,S={openai:new Set([`https://api.openai.com`,`https://api.openai.com/v1`]),moonshot:new Set([`https://api.kimi.com/coding/v1`])},C=e=>{try{let t=new URL(e),n=t.pathname.replace(/\/+$/,``)||``;return`${t.origin}${n}`}catch{return e.trim().replace(/\/+$/,``)}},w=(e,t)=>{let n=C(t),{provider:r}=c.extractModelAndProvider(e);if(r){let e=S[r];if(e)return e.has(n)}return Object.values(S).some(e=>e?.has(n))};function T({testId:e}){let{t:r}=t.useTranslation(`openhands`);return(0,v.jsx)(l.HelpLink,{testId:e,text:r(n.I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT),linkText:r(n.I18nKey.SETTINGS$NAV_API_KEYS),href:`https://app.all-hands.dev/settings/api-keys`,suffix:` ${r(n.I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`})}function E({scope:e=`personal`,onSaveSuccess:c,initialValueOverrides:S,embedded:C,hideSaveButton:E,suppressSuccessToast:D,onSaveControlChange:O}){let{t:k}=t.useTranslation(`openhands`),{data:A}=a.useSettings(e),{data:j}=d.useAgentSettingsSchema(A?.agent_settings_schema),M=A?.agent_settings?.llm,N=i.resolveLlmAuthType(S?.[`llm.auth_type`]??M?.auth_type),[P,F]=_.default.useState(N===i.LLM_AUTH_TYPE_SUBSCRIPTION),{data:I,isLoading:L,isFetching:R}=g.useOpenAISubscriptionModels({enabled:P}),z=P&&!I&&(L||R),B=_.default.useRef(null),V=_.default.useRef(null);_.default.useEffect(()=>{N===`subscription`&&F(!0)},[N]);let H=String(r.DEFAULT_SETTINGS.agent_settings?.llm?.model??``),U=_.default.useCallback((e,t)=>{let n=m.inferInitialView(e,t);if(n!==`basic`)return n;let r=e.llm_model??``,i=e.llm_base_url?.trim()??``;return i.length>0&&!w(r,i)?`all`:`basic`},[]),W=_.default.useCallback(({values:e,isDisabled:t,view:r,onChange:a})=>{let c=typeof e[`llm.model`]==`string`?e[`llm.model`]:``,d=typeof e[`llm.base_url`]==`string`?e[`llm.base_url`]:``,m=c.startsWith(`openhands/`),h=i.resolveLlmAuthType(e[i.LLM_AUTH_TYPE_KEY]),g=h===i.LLM_AUTH_TYPE_SUBSCRIPTION,_=t||g&&z,y=I?.includes(c)?c:I?.[0]??``,x=typeof e[`llm.api_key`]==`string`?e[`llm.api_key`]:``,S=C?x.length>0:!!A?.llm_api_key_set,w=(e,r)=>(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(o.SettingsInput,{testId:e,label:k(n.I18nKey.SETTINGS_FORM$API_KEY),type:`password`,className:`w-full`,value:x,placeholder:S?`<hidden>`:``,onChange:e=>a(`llm.api_key`,e),isDisabled:t,startContent:S?(0,v.jsx)(p.KeyStatusIcon,{isSet:S}):void 0}),(0,v.jsx)(l.HelpLink,{testId:r,text:k(n.I18nKey.SETTINGS$DONT_KNOW_API_KEY),linkText:k(n.I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS),href:`https://docs.openhands.dev/usage/local-setup#getting-an-api-key`})]}),E=e=>{let t=e===`subscription`?i.LLM_AUTH_TYPE_SUBSCRIPTION:i.LLM_AUTH_TYPE_API_KEY;if(a(i.LLM_AUTH_TYPE_KEY,t),t===`subscription`){F(!0),c&&!I?.includes(c)&&(B.current=c);let e=V.current&&I?.includes(V.current)?V.current:I?.[0];a(i.LLM_SUBSCRIPTION_VENDOR_KEY,i.OPENAI_SUBSCRIPTION_VENDOR),!I?.includes(c)&&e&&a(`llm.model`,e);return}c&&I?.includes(c)&&(V.current=c,a(`llm.model`,B.current??H))},D=()=>(0,v.jsx)(s.SettingsDropdownInput,{testId:`llm-auth-type-input`,name:i.LLM_AUTH_TYPE_KEY,label:k(n.I18nKey.SETTINGS$LLM_AUTH_TYPE),items:[{key:i.LLM_AUTH_TYPE_API_KEY,label:k(n.I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY)},{key:i.LLM_AUTH_TYPE_SUBSCRIPTION,label:k(n.I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION)}],selectedKey:h,isClearable:!1,required:!0,isDisabled:_,onSelectionChange:E}),O=()=>(0,v.jsxs)(`div`,{className:`flex flex-col gap-6`,"data-testid":`llm-subscription-settings`,children:[(0,v.jsx)(s.SettingsDropdownInput,{testId:`llm-subscription-model-input`,name:`llm.subscription_model`,label:k(n.I18nKey.SETTINGS$SUBSCRIPTION_MODEL),items:(I??[]).map(e=>({key:e,label:e})),selectedKey:y,isClearable:!1,required:!0,isDisabled:_||!I?.length,onSelectionChange:e=>{let t=typeof e==`string`?e:I?.[0];t&&a(`llm.model`,t)}}),(0,v.jsx)(f.OpenAISubscriptionAuthCard,{isDisabled:t})]});return(0,v.jsx)(`div`,{className:`flex flex-col gap-6`,children:r===`basic`?(0,v.jsxs)(`div`,{className:`flex flex-col gap-6`,"data-testid":`llm-settings-form-basic`,children:[D(),g?O():(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(u.ModelSelector,{currentModel:c||void 0,onChange:(e,t)=>{let n=b(e,t);n&&a(`llm.model`,n)},wrapperClassName:`!flex-col !gap-6`,isDisabled:t}),m?(0,v.jsx)(T,{testId:`openhands-api-key-help`}):null,w(`llm-api-key-input`,`llm-api-key-help-anchor`)]})]}):(0,v.jsxs)(`div`,{className:`flex flex-col gap-6`,"data-testid":`llm-settings-form-advanced`,children:[D(),g?O():(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(o.SettingsInput,{testId:`llm-custom-model-input`,label:k(n.I18nKey.SETTINGS$CUSTOM_MODEL),type:`text`,className:`w-full`,value:c,placeholder:H,onChange:e=>a(`llm.model`,e),isDisabled:t}),m?(0,v.jsx)(T,{testId:`openhands-api-key-help-2`}):null,(0,v.jsx)(o.SettingsInput,{testId:`base-url-input`,label:k(n.I18nKey.SETTINGS$BASE_URL),type:`text`,className:`w-full`,value:d,placeholder:`https://api.openai.com`,onChange:e=>a(`llm.base_url`,e),isDisabled:t}),w(`llm-api-key-input`,`llm-api-key-help-anchor-advanced`)]})]})})},[H,C,z,A?.llm_api_key_set,I,k]),G=_.default.useCallback((e,t)=>{let n=structuredClone(e.agent_settings_diff??{}),r=n.llm??{};if(i.resolveLlmAuthType(t.values[`llm.auth_type`])===`subscription`){r.auth_type=i.LLM_AUTH_TYPE_SUBSCRIPTION,r.subscription_vendor=i.OPENAI_SUBSCRIPTION_VENDOR;let e=typeof r.model==`string`?r.model:String(t.values[`llm.model`]??``),n=I?.[0];if(!I?.includes(e)&&!n)throw Error(`Subscription models are not loaded yet.`);r.model=I?.includes(e)?e:n,delete r.api_key,delete r.base_url}else t.dirty[`llm.auth_type`]&&(r.auth_type=i.LLM_AUTH_TYPE_API_KEY,r.subscription_vendor=null),t.view===`basic`&&r.model!==void 0&&(r.base_url=x(j,`llm.base_url`));return n.llm=r,{agent_settings_diff:n}},[j,I]);return(0,v.jsx)(h.SdkSectionPage,{scope:e,settingsSources:[{settingsSource:`agent_settings`,sectionKeys:[`llm`],excludeKeys:y}],header:W,buildPayload:G,getInitialView:U,forceShowAdvancedView:!0,allowAllView:!0,onSaveSuccess:c,initialValueOverrides:S,embedded:C,hideSaveButton:E,suppressSuccessToast:D,onSaveControlChange:O,testId:`llm-settings-screen`})}exports.LlmSettingsScreen=E;
2
2
  //# sourceMappingURL=llm-settings.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"llm-settings.cjs","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"ymCAmCA,IAAM,EAAoB,IAAI,IAAI,CAChC,YACA,cACA,eACA,EAAA,kBACA,EAAA,4BACD,CAAC,CAEI,GAAgB,EAAyB,IACzC,CAAC,GAAY,CAAC,EAAc,KACzB,GAAG,EAAS,GAAG,IAGlB,GACJ,EACA,IAEA,GAAQ,SACL,QAAS,GAAY,EAAQ,OAAO,CACpC,KAAM,GAAU,EAAM,MAAQ,EAAS,EAAE,SAAW,KAEnD,EAAyE,CAC7E,OAAQ,IAAI,IAAI,CAAC,yBAA0B,4BAA4B,CAAC,CACzE,CAEK,EAAoB,GAAoB,CAC5C,GAAI,CACF,IAAM,EAAY,IAAI,IAAI,EAAQ,CAC5B,EAAiB,EAAU,SAAS,QAAQ,OAAQ,GAAG,EAAI,GACjE,MAAO,GAAG,EAAU,SAAS,SACvB,CACN,OAAO,EAAQ,MAAM,CAAC,QAAQ,OAAQ,GAAG,GAIvC,GAA4B,EAAe,IAAoB,CACnE,IAAM,EAAoB,EAAiB,EAAQ,CAC7C,CAAE,YAAa,EAAA,wBAAwB,EAAM,CAEnD,GAAI,EAAU,CACZ,IAAM,EAAgB,EAAiC,GACvD,GAAI,EACF,OAAO,EAAc,IAAI,EAAkB,CAI/C,OAAO,OAAO,OAAO,EAAiC,CAAC,KAAM,GAC3D,GAAe,IAAI,EAAkB,CACtC,EAOH,SAAS,EAAoB,CAAE,UAAoC,CACjE,GAAM,CAAE,KAAM,EAAA,eAAe,YAAY,CAEzC,OACE,EAAA,EAAA,KAAC,EAAA,SAAD,CACU,SACR,KAAM,EAAE,EAAA,QAAQ,qCAAqC,CACrD,SAAU,EAAE,EAAA,QAAQ,sBAAsB,CAC1C,KAAK,8CACL,OAAQ,IAAI,EAAE,EAAA,QAAQ,uCAAuC,GAC7D,CAAA,CAIN,SAAgB,EAAkB,CAChC,QAAQ,WACR,gBACA,wBACA,WACA,iBACA,uBACA,uBAeC,CACD,GAAM,CAAE,KAAM,EAAA,eAAe,YAAY,CAEnC,CAAE,KAAM,GAAa,EAAA,YAAY,EAAM,CACvC,CAAE,KAAM,GAAW,EAAA,uBACvB,GAAU,sBACX,CACK,EAAuB,GAAU,gBAAgB,IAGjD,EAAkB,EAAA,mBACtB,IAAA,kBACE,GAAsB,UACzB,CACK,CAAC,EAA0B,GAC/B,EAAA,QAAM,SAAS,IAAoB,EAAA,2BAA2B,CAC1D,CACJ,KAAM,EACN,UAAW,EACX,WAAY,GACV,EAAA,4BAA4B,CAAE,QAAS,EAA0B,CAAC,CAChE,EACJ,GACA,CAAC,IACA,GAA+B,GAC5B,EAAqB,EAAA,QAAM,OAAsB,KAAK,CACtD,EAA2B,EAAA,QAAM,OAAsB,KAAK,CAElE,EAAA,QAAM,cAAgB,CAChB,IAAA,gBACF,EAA4B,GAAK,EAElC,CAAC,EAAgB,CAAC,CAErB,IAAM,EAAe,OAClB,EAAA,iBAAiB,gBAAgB,KAAiC,OACjE,GACH,CAEK,EAAiB,EAAA,QAAM,aAEzB,EACA,IACiB,CACjB,IAAM,EAAa,EAAA,iBAAiB,EAAiB,EAAe,CACpE,GAAI,IAAe,QACjB,OAAO,EAGT,IAAM,EAAe,EAAgB,WAAa,GAC5C,EAAiB,EAAgB,cAAc,MAAM,EAAI,GAK/D,OAHE,EAAe,OAAS,GACxB,CAAC,EAAyB,EAAc,EAAe,CAE/B,MAAQ,SAEpC,EAAE,CACH,CAEK,EAAc,EAAA,QAAM,aACvB,CAAE,SAAQ,aAAY,OAAM,cAAsC,CACjE,IAAM,EACJ,OAAO,EAAO,cAAiB,SAAW,EAAO,aAAe,GAC5D,EACJ,OAAO,EAAO,iBAAoB,SAC9B,EAAO,gBACP,GACA,EAA0B,EAAW,WAAW,aAAa,CAC7D,EAAW,EAAA,mBAAmB,EAAO,EAAA,mBAAmB,CACxD,EAAqB,IAAa,EAAA,2BAClC,EACJ,GAAe,GAAsB,EACjC,EAAyB,GAAoB,SAAS,EAAW,CACnE,EACC,IAAqB,IAAM,GAE1B,EACJ,OAAO,EAAO,gBAAmB,SAAW,EAAO,eAAiB,GAKhE,EAAc,EAChB,EAAY,OAAS,EACrB,EAAQ,GAAU,gBAEhB,GAAqB,EAAgB,KACzC,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAA,cAAD,CACU,SACR,MAAO,EAAE,EAAA,QAAQ,sBAAsB,CACvC,KAAK,WACL,UAAU,SACV,MAAO,EAEP,YAAa,EAAc,WAAa,GACxC,SAAW,GAAU,EAAS,cAAe,EAAM,CACvC,aACZ,aACE,GAAc,EAAA,EAAA,KAAC,EAAA,cAAD,CAAe,MAAO,EAAe,CAAA,CAAG,IAAA,GAExD,CAAA,EAEF,EAAA,EAAA,KAAC,EAAA,SAAD,CACE,OAAQ,EACR,KAAM,EAAE,EAAA,QAAQ,2BAA2B,CAC3C,SAAU,EAAE,EAAA,QAAQ,gCAAgC,CACpD,KAAK,kEACL,CAAA,CACD,CAAA,CAAA,CAGC,EAAwB,GAAkC,CAC9D,IAAM,EACJ,IAAA,eACI,EAAA,2BACA,EAAA,sBAGN,GAFA,EAAS,EAAA,kBAAmB,EAAa,CAErC,IAAA,eAA6C,CAC/C,EAA4B,GAAK,CAC7B,GAAc,CAAC,GAAoB,SAAS,EAAW,GACzD,EAAmB,QAAU,GAE/B,IAAM,EACJ,EAAyB,SACzB,GAAoB,SAAS,EAAyB,QAAQ,CAC1D,EAAyB,QACzB,IAAqB,GAC3B,EAAS,EAAA,4BAA6B,EAAA,2BAA2B,CAE/D,CAAC,GAAoB,SAAS,EAAW,EACzC,GAEA,EAAS,YAAa,EAA0B,CAElD,OAGE,GAAc,GAAoB,SAAS,EAAW,GACxD,EAAyB,QAAU,EACnC,EAAS,YAAa,EAAmB,SAAW,EAAa,GAI/D,OACJ,EAAA,EAAA,KAAC,EAAA,sBAAD,CACE,OAAO,sBACP,KAAM,EAAA,kBACN,MAAO,EAAE,EAAA,QAAQ,uBAAuB,CACxC,MAAO,CACL,CACE,IAAK,EAAA,sBACL,MAAO,EAAE,EAAA,QAAQ,+BAA+B,CACjD,CACD,CACE,IAAK,EAAA,2BACL,MAAO,EAAE,EAAA,QAAQ,oCAAoC,CACtD,CACF,CACD,YAAa,EACb,YAAa,GACb,SAAA,GACA,WAAY,EACZ,kBAAmB,EACnB,CAAA,CAGE,OACJ,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,sBACV,cAAY,qCAFd,EAIE,EAAA,EAAA,KAAC,EAAA,sBAAD,CACE,OAAO,+BACP,KAAK,yBACL,MAAO,EAAE,EAAA,QAAQ,4BAA4B,CAC7C,OAAQ,GAAsB,EAAE,EAAE,IAAK,IAAW,CAChD,IAAK,EACL,MAAO,EACR,EAAE,CACH,YAAa,EACb,YAAa,GACb,SAAA,GACA,WACE,GAAqC,CAAC,GAAoB,OAE5D,kBAAoB,GAAgB,CAClC,IAAM,EACJ,OAAO,GAAgB,SACnB,EACA,IAAqB,GACvB,GACF,EAAS,YAAa,EAAU,EAGpC,CAAA,EACF,EAAA,EAAA,KAAC,EAAA,2BAAD,CAAwC,aAAc,CAAA,CAClD,GAGR,OACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,+BACZ,IAAS,SACR,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,sBACV,cAAY,mCAFd,CAIG,GAAqB,CAErB,EACC,GAA4B,EAE5B,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAA,cAAD,CACE,aAAc,GAAc,IAAA,GAC5B,UAAW,EAAU,IAAU,CAC7B,IAAM,EAAY,EAAa,EAAU,EAAM,CAC3C,GACF,EAAS,YAAa,EAAU,EAGpC,iBAAiB,mBACL,aACZ,CAAA,CAED,GACC,EAAA,EAAA,KAAC,EAAD,CAAqB,OAAO,yBAA2B,CAAA,CACrD,KAEH,EAEC,oBAEA,0BACD,CACA,CAAA,CAAA,CAED,IAEN,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,sBACV,cAAY,sCAFd,CAIG,GAAqB,CAErB,EACC,GAA4B,EAE5B,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAA,cAAD,CACE,OAAO,yBACP,MAAO,EAAE,EAAA,QAAQ,sBAAsB,CACvC,KAAK,OACL,UAAU,SACV,MAAO,EACP,YAAa,EACb,SAAW,GAAU,EAAS,YAAa,EAAM,CACrC,aACZ,CAAA,CAED,GACC,EAAA,EAAA,KAAC,EAAD,CAAqB,OAAO,2BAA6B,CAAA,CACvD,MAEJ,EAAA,EAAA,KAAC,EAAA,cAAD,CACE,OAAO,iBACP,MAAO,EAAE,EAAA,QAAQ,kBAAkB,CACnC,KAAK,OACL,UAAU,SACV,MAAO,EAEP,YAAY,yBACZ,SAAW,GAAU,EAAS,eAAgB,EAAM,CACxC,aACZ,CAAA,CAED,EAEC,oBAEA,mCACD,CACA,CAAA,CAAA,CAED,GAEJ,CAAA,EAGV,CACE,EACA,EACA,EACA,GAAU,gBACV,EACA,EACD,CACF,CAEK,EAAe,EAAA,QAAM,aAEvB,EACA,IAKG,CAIH,IAAM,EAAgB,gBACnB,EAAe,qBAAmD,EAAE,CACtE,CACK,EAAO,EAAc,KAAO,EAAE,CAGpC,GAFiB,EAAA,mBAAmB,EAAQ,OAAA,iBAExC,GAAA,eAAyC,CAC3C,EAAI,UAAY,EAAA,2BAChB,EAAI,oBAAsB,EAAA,2BAC1B,IAAM,EACJ,OAAO,EAAI,OAAU,SACjB,EAAI,MACJ,OAAO,EAAQ,OAAO,cAAgB,GAAG,CACzC,EAA4B,IAAqB,GACvD,GACE,CAAC,GAAoB,SAAS,EAAM,EACpC,CAAC,EAED,MAAU,MAAM,0CAA0C,CAE5D,EAAI,MAAQ,GAAoB,SAAS,EAAM,CAC3C,EACA,EACJ,OAAO,EAAI,QACX,OAAO,EAAI,cAEP,EAAQ,MAAA,mBACV,EAAI,UAAY,EAAA,sBAChB,EAAI,oBAAsB,MAExB,EAAQ,OAAS,SAAW,EAAI,QAAU,IAAA,KAC5C,EAAI,SAAW,EAA2B,EAAQ,eAAe,EAKrE,MADA,GAAc,IAAM,EACb,CAAE,oBAAqB,EAAe,EAE/C,CAAC,EAAQ,EAAmB,CAC7B,CAED,OACE,EAAA,EAAA,KAAC,EAAA,eAAD,CACS,QACP,gBAAiB,CACf,CACE,eAAgB,iBAChB,YAAa,CAAC,MAAM,CACpB,YAAa,EACd,CACF,CACD,OAAQ,EACM,eACE,iBAChB,sBAAA,GACA,aAAA,GACe,gBACQ,wBACb,WACM,iBACM,uBACD,sBACrB,OAAO,sBACP,CAAA"}
1
+ {"version":3,"file":"llm-settings.cjs","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n moonshot: new Set([\"https://api.kimi.com/coding/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"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"}
@@ -24,7 +24,10 @@ var T = new Set([
24
24
  "llm.base_url",
25
25
  i,
26
26
  o
27
- ]), E = (e, t) => !e || !t ? null : `${e}/${t}`, D = (e, t) => e?.sections.flatMap((e) => e.fields).find((e) => e.key === t)?.default ?? null, O = { openai: new Set(["https://api.openai.com", "https://api.openai.com/v1"]) }, k = (e) => {
27
+ ]), E = (e, t) => !e || !t ? null : `${e}/${t}`, D = (e, t) => e?.sections.flatMap((e) => e.fields).find((e) => e.key === t)?.default ?? null, O = {
28
+ openai: new Set(["https://api.openai.com", "https://api.openai.com/v1"]),
29
+ moonshot: new Set(["https://api.kimi.com/coding/v1"])
30
+ }, k = (e) => {
28
31
  try {
29
32
  let t = new URL(e), n = t.pathname.replace(/\/+$/, "") || "";
30
33
  return `${t.origin}${n}`;
@@ -1 +1 @@
1
- {"version":3,"file":"llm-settings.js","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"llm-settings.js","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n moonshot: new Set([\"https://api.kimi.com/coding/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../node_modules/@openhands/typescript-client/dist/client/device-flow-client.cjs`),t=require(`../api/client-source.cjs`);require(`../api/device-flow-client.cjs`);const n=require(`./telemetry.cjs`);var r={client_source:t.AGENT_CANVAS_CLIENT_SOURCE,client_version:t.AGENT_CANVAS_CLIENT_VERSION};function i(e,t){return n.isTelemetryEnabled()?(n.trackEvent(e,{...t,...r}),!0):!1}function a(t){let n=e.isOpenHandsCloudHost(t);return{is_openhands_cloud:n,is_custom_host:!n}}function o(e,t){return i(`cloud_device_authorization_started`,{...a(e),source:t})}function s(e,t){return i(`cloud_device_authorization_succeeded`,{...a(e),source:t})}function c({backendKind:e,connectionMethod:t,host:n,hasApiKey:r,source:o}){return i(`backend_added`,{backend_kind:e,connection_method:t,...a(n),has_api_key:r,source:o})}function l(e,t){return i(`cloud_conversation_ready`,{task_id:e,conversation_id:t})}exports.trackCanvasBackendAdded=c,exports.trackCloudConversationReady=l,exports.trackCloudDeviceAuthorizationStarted=o,exports.trackCloudDeviceAuthorizationSucceeded=s;
1
+ require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../node_modules/@openhands/typescript-client/dist/client/device-flow-client.cjs`),t=require(`../api/client-source.cjs`),n=require(`./telemetry.cjs`);require(`../api/device-flow-client.cjs`);var r=`${t.AGENT_CANVAS_CLIENT_SOURCE}:cloud_conversation_ready`;function i(e,t){n.trackEvent(e,t)}function a(t){let n=e.isOpenHandsCloudHost(t);return{is_openhands_cloud:n,is_custom_host:!n}}function o(e,t){i(`cloud_device_authorization_started`,{...a(e),source:t})}function s(e,t){i(`cloud_device_authorization_succeeded`,{...a(e),source:t})}function c(e,t){i(`cloud_conversation_ready`,{$insert_id:`${r}:${e}`,task_id:e,conversation_id:t})}exports.trackCloudConversationReady=c,exports.trackCloudDeviceAuthorizationStarted=o,exports.trackCloudDeviceAuthorizationSucceeded=s;
2
2
  //# sourceMappingURL=cloud-funnel-analytics.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-funnel-analytics.cjs","names":[],"sources":["../../src/services/cloud-funnel-analytics.ts"],"sourcesContent":["import type { BackendKind } from \"#/api/backend-registry/types\";\nimport {\n AGENT_CANVAS_CLIENT_SOURCE,\n AGENT_CANVAS_CLIENT_VERSION,\n} from \"#/api/client-source\";\nimport { isOpenHandsCloudHost } from \"#/api/device-flow-client\";\nimport { isTelemetryEnabled, trackEvent } from \"#/services/telemetry\";\n\nexport type CloudConnectionSource =\n | \"onboarding\"\n | \"add_backend_modal\"\n | \"manage_backends_modal\";\n\nconst commonProperties = {\n client_source: AGENT_CANVAS_CLIENT_SOURCE,\n client_version: AGENT_CANVAS_CLIENT_VERSION,\n};\n\nfunction trackCloudFunnelEvent(\n event: string,\n properties: Record<string, unknown>,\n): boolean {\n if (!isTelemetryEnabled()) return false;\n void trackEvent(event, { ...properties, ...commonProperties });\n return true;\n}\n\nfunction hostClassification(host: string) {\n const isOpenhandsCloud = isOpenHandsCloudHost(host);\n return {\n is_openhands_cloud: isOpenhandsCloud,\n is_custom_host: !isOpenhandsCloud,\n };\n}\n\nexport function trackCloudDeviceAuthorizationStarted(\n host: string,\n source?: CloudConnectionSource,\n): boolean {\n return trackCloudFunnelEvent(\"cloud_device_authorization_started\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCloudDeviceAuthorizationSucceeded(\n host: string,\n source?: CloudConnectionSource,\n): boolean {\n return trackCloudFunnelEvent(\"cloud_device_authorization_succeeded\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCanvasBackendAdded({\n backendKind,\n connectionMethod,\n host,\n hasApiKey,\n source,\n}: {\n backendKind: BackendKind;\n connectionMethod: \"manual\" | \"cloud_login\";\n host: string;\n hasApiKey: boolean;\n source?: CloudConnectionSource;\n}): boolean {\n return trackCloudFunnelEvent(\"backend_added\", {\n backend_kind: backendKind,\n connection_method: connectionMethod,\n ...hostClassification(host),\n has_api_key: hasApiKey,\n source,\n });\n}\n\nexport function trackCloudConversationReady(\n taskId: string,\n conversationId: string,\n): boolean {\n return trackCloudFunnelEvent(\"cloud_conversation_ready\", {\n task_id: taskId,\n conversation_id: conversationId,\n });\n}\n"],"mappings":"kQAaA,IAAM,EAAmB,CACvB,cAAe,EAAA,2BACf,eAAgB,EAAA,4BACjB,CAED,SAAS,EACP,EACA,EACS,CAGT,OAFK,EAAA,oBAAoB,EACpB,EAAA,WAAW,EAAO,CAAE,GAAG,EAAY,GAAG,EAAkB,CAAC,CACvD,IAF2B,GAKpC,SAAS,EAAmB,EAAc,CACxC,IAAM,EAAmB,EAAA,qBAAqB,EAAK,CACnD,MAAO,CACL,mBAAoB,EACpB,eAAgB,CAAC,EAClB,CAGH,SAAgB,EACd,EACA,EACS,CACT,OAAO,EAAsB,qCAAsC,CACjE,GAAG,EAAmB,EAAK,CAC3B,SACD,CAAC,CAGJ,SAAgB,EACd,EACA,EACS,CACT,OAAO,EAAsB,uCAAwC,CACnE,GAAG,EAAmB,EAAK,CAC3B,SACD,CAAC,CAGJ,SAAgB,EAAwB,CACtC,cACA,mBACA,OACA,YACA,UAOU,CACV,OAAO,EAAsB,gBAAiB,CAC5C,aAAc,EACd,kBAAmB,EACnB,GAAG,EAAmB,EAAK,CAC3B,YAAa,EACb,SACD,CAAC,CAGJ,SAAgB,EACd,EACA,EACS,CACT,OAAO,EAAsB,2BAA4B,CACvD,QAAS,EACT,gBAAiB,EAClB,CAAC"}
1
+ {"version":3,"file":"cloud-funnel-analytics.cjs","names":[],"sources":["../../src/services/cloud-funnel-analytics.ts"],"sourcesContent":["import { AGENT_CANVAS_CLIENT_SOURCE } from \"#/api/client-source\";\nimport { isOpenHandsCloudHost } from \"#/api/device-flow-client\";\nimport { trackEvent } from \"#/services/telemetry\";\n\nexport type CloudConnectionSource =\n | \"onboarding\"\n | \"add_backend_modal\"\n | \"manage_backends_modal\";\n\nconst CLOUD_CONVERSATION_READY_INSERT_ID_PREFIX = `${AGENT_CANVAS_CLIENT_SOURCE}:cloud_conversation_ready`;\n\nfunction trackCloudFunnelEvent(\n event: string,\n properties: Record<string, unknown>,\n): void {\n void trackEvent(event, properties);\n}\n\nfunction hostClassification(host: string) {\n const isOpenhandsCloud = isOpenHandsCloudHost(host);\n return {\n is_openhands_cloud: isOpenhandsCloud,\n is_custom_host: !isOpenhandsCloud,\n };\n}\n\nexport function trackCloudDeviceAuthorizationStarted(\n host: string,\n source?: CloudConnectionSource,\n): void {\n trackCloudFunnelEvent(\"cloud_device_authorization_started\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCloudDeviceAuthorizationSucceeded(\n host: string,\n source?: CloudConnectionSource,\n): void {\n trackCloudFunnelEvent(\"cloud_device_authorization_succeeded\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCloudConversationReady(\n taskId: string,\n conversationId: string,\n): void {\n trackCloudFunnelEvent(\"cloud_conversation_ready\", {\n $insert_id: `${CLOUD_CONVERSATION_READY_INSERT_ID_PREFIX}:${taskId}`,\n task_id: taskId,\n conversation_id: conversationId,\n });\n}\n"],"mappings":"4PASA,IAAM,EAA4C,GAAG,EAAA,2BAA2B,2BAEhF,SAAS,EACP,EACA,EACM,CACD,EAAA,WAAW,EAAO,EAAW,CAGpC,SAAS,EAAmB,EAAc,CACxC,IAAM,EAAmB,EAAA,qBAAqB,EAAK,CACnD,MAAO,CACL,mBAAoB,EACpB,eAAgB,CAAC,EAClB,CAGH,SAAgB,EACd,EACA,EACM,CACN,EAAsB,qCAAsC,CAC1D,GAAG,EAAmB,EAAK,CAC3B,SACD,CAAC,CAGJ,SAAgB,EACd,EACA,EACM,CACN,EAAsB,uCAAwC,CAC5D,GAAG,EAAmB,EAAK,CAC3B,SACD,CAAC,CAGJ,SAAgB,EACd,EACA,EACM,CACN,EAAsB,2BAA4B,CAChD,WAAY,GAAG,EAA0C,GAAG,IAC5D,QAAS,EACT,gBAAiB,EAClB,CAAC"}
@@ -1,12 +1,4 @@
1
- import type { BackendKind } from "#/api/backend-registry/types";
2
1
  export type CloudConnectionSource = "onboarding" | "add_backend_modal" | "manage_backends_modal";
3
- export declare function trackCloudDeviceAuthorizationStarted(host: string, source?: CloudConnectionSource): boolean;
4
- export declare function trackCloudDeviceAuthorizationSucceeded(host: string, source?: CloudConnectionSource): boolean;
5
- export declare function trackCanvasBackendAdded({ backendKind, connectionMethod, host, hasApiKey, source, }: {
6
- backendKind: BackendKind;
7
- connectionMethod: "manual" | "cloud_login";
8
- host: string;
9
- hasApiKey: boolean;
10
- source?: CloudConnectionSource;
11
- }): boolean;
12
- export declare function trackCloudConversationReady(taskId: string, conversationId: string): boolean;
2
+ export declare function trackCloudDeviceAuthorizationStarted(host: string, source?: CloudConnectionSource): void;
3
+ export declare function trackCloudDeviceAuthorizationSucceeded(host: string, source?: CloudConnectionSource): void;
4
+ export declare function trackCloudConversationReady(taskId: string, conversationId: string): void;
@@ -1,53 +1,39 @@
1
1
  import { isOpenHandsCloudHost as e } from "../node_modules/@openhands/typescript-client/dist/client/device-flow-client.js";
2
- import { AGENT_CANVAS_CLIENT_SOURCE as t, AGENT_CANVAS_CLIENT_VERSION as n } from "../api/client-source.js";
2
+ import { AGENT_CANVAS_CLIENT_SOURCE as t } from "../api/client-source.js";
3
+ import { trackEvent as n } from "./telemetry.js";
3
4
  import "../api/device-flow-client.js";
4
- import { isTelemetryEnabled as r, trackEvent as i } from "./telemetry.js";
5
5
  //#region src/services/cloud-funnel-analytics.ts
6
- var a = {
7
- client_source: t,
8
- client_version: n
9
- };
10
- function o(e, t) {
11
- return r() ? (i(e, {
12
- ...t,
13
- ...a
14
- }), !0) : !1;
6
+ var r = `${t}:cloud_conversation_ready`;
7
+ function i(e, t) {
8
+ n(e, t);
15
9
  }
16
- function s(t) {
10
+ function a(t) {
17
11
  let n = e(t);
18
12
  return {
19
13
  is_openhands_cloud: n,
20
14
  is_custom_host: !n
21
15
  };
22
16
  }
23
- function c(e, t) {
24
- return o("cloud_device_authorization_started", {
25
- ...s(e),
17
+ function o(e, t) {
18
+ i("cloud_device_authorization_started", {
19
+ ...a(e),
26
20
  source: t
27
21
  });
28
22
  }
29
- function l(e, t) {
30
- return o("cloud_device_authorization_succeeded", {
31
- ...s(e),
23
+ function s(e, t) {
24
+ i("cloud_device_authorization_succeeded", {
25
+ ...a(e),
32
26
  source: t
33
27
  });
34
28
  }
35
- function u({ backendKind: e, connectionMethod: t, host: n, hasApiKey: r, source: i }) {
36
- return o("backend_added", {
37
- backend_kind: e,
38
- connection_method: t,
39
- ...s(n),
40
- has_api_key: r,
41
- source: i
42
- });
43
- }
44
- function d(e, t) {
45
- return o("cloud_conversation_ready", {
29
+ function c(e, t) {
30
+ i("cloud_conversation_ready", {
31
+ $insert_id: `${r}:${e}`,
46
32
  task_id: e,
47
33
  conversation_id: t
48
34
  });
49
35
  }
50
36
  //#endregion
51
- export { u as trackCanvasBackendAdded, d as trackCloudConversationReady, c as trackCloudDeviceAuthorizationStarted, l as trackCloudDeviceAuthorizationSucceeded };
37
+ export { c as trackCloudConversationReady, o as trackCloudDeviceAuthorizationStarted, s as trackCloudDeviceAuthorizationSucceeded };
52
38
 
53
39
  //# sourceMappingURL=cloud-funnel-analytics.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-funnel-analytics.js","names":[],"sources":["../../src/services/cloud-funnel-analytics.ts"],"sourcesContent":["import type { BackendKind } from \"#/api/backend-registry/types\";\nimport {\n AGENT_CANVAS_CLIENT_SOURCE,\n AGENT_CANVAS_CLIENT_VERSION,\n} from \"#/api/client-source\";\nimport { isOpenHandsCloudHost } from \"#/api/device-flow-client\";\nimport { isTelemetryEnabled, trackEvent } from \"#/services/telemetry\";\n\nexport type CloudConnectionSource =\n | \"onboarding\"\n | \"add_backend_modal\"\n | \"manage_backends_modal\";\n\nconst commonProperties = {\n client_source: AGENT_CANVAS_CLIENT_SOURCE,\n client_version: AGENT_CANVAS_CLIENT_VERSION,\n};\n\nfunction trackCloudFunnelEvent(\n event: string,\n properties: Record<string, unknown>,\n): boolean {\n if (!isTelemetryEnabled()) return false;\n void trackEvent(event, { ...properties, ...commonProperties });\n return true;\n}\n\nfunction hostClassification(host: string) {\n const isOpenhandsCloud = isOpenHandsCloudHost(host);\n return {\n is_openhands_cloud: isOpenhandsCloud,\n is_custom_host: !isOpenhandsCloud,\n };\n}\n\nexport function trackCloudDeviceAuthorizationStarted(\n host: string,\n source?: CloudConnectionSource,\n): boolean {\n return trackCloudFunnelEvent(\"cloud_device_authorization_started\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCloudDeviceAuthorizationSucceeded(\n host: string,\n source?: CloudConnectionSource,\n): boolean {\n return trackCloudFunnelEvent(\"cloud_device_authorization_succeeded\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCanvasBackendAdded({\n backendKind,\n connectionMethod,\n host,\n hasApiKey,\n source,\n}: {\n backendKind: BackendKind;\n connectionMethod: \"manual\" | \"cloud_login\";\n host: string;\n hasApiKey: boolean;\n source?: CloudConnectionSource;\n}): boolean {\n return trackCloudFunnelEvent(\"backend_added\", {\n backend_kind: backendKind,\n connection_method: connectionMethod,\n ...hostClassification(host),\n has_api_key: hasApiKey,\n source,\n });\n}\n\nexport function trackCloudConversationReady(\n taskId: string,\n conversationId: string,\n): boolean {\n return trackCloudFunnelEvent(\"cloud_conversation_ready\", {\n task_id: taskId,\n conversation_id: conversationId,\n });\n}\n"],"mappings":";;;;;AAaA,IAAM,IAAmB;CACvB,eAAe;CACf,gBAAgB;CACjB;AAED,SAAS,EACP,GACA,GACS;AAGT,QAFK,GAAoB,IACpB,EAAW,GAAO;EAAE,GAAG;EAAY,GAAG;EAAkB,CAAC,EACvD,MAF2B;;AAKpC,SAAS,EAAmB,GAAc;CACxC,IAAM,IAAmB,EAAqB,EAAK;AACnD,QAAO;EACL,oBAAoB;EACpB,gBAAgB,CAAC;EAClB;;AAGH,SAAgB,EACd,GACA,GACS;AACT,QAAO,EAAsB,sCAAsC;EACjE,GAAG,EAAmB,EAAK;EAC3B;EACD,CAAC;;AAGJ,SAAgB,EACd,GACA,GACS;AACT,QAAO,EAAsB,wCAAwC;EACnE,GAAG,EAAmB,EAAK;EAC3B;EACD,CAAC;;AAGJ,SAAgB,EAAwB,EACtC,gBACA,qBACA,SACA,cACA,aAOU;AACV,QAAO,EAAsB,iBAAiB;EAC5C,cAAc;EACd,mBAAmB;EACnB,GAAG,EAAmB,EAAK;EAC3B,aAAa;EACb;EACD,CAAC;;AAGJ,SAAgB,EACd,GACA,GACS;AACT,QAAO,EAAsB,4BAA4B;EACvD,SAAS;EACT,iBAAiB;EAClB,CAAC"}
1
+ {"version":3,"file":"cloud-funnel-analytics.js","names":[],"sources":["../../src/services/cloud-funnel-analytics.ts"],"sourcesContent":["import { AGENT_CANVAS_CLIENT_SOURCE } from \"#/api/client-source\";\nimport { isOpenHandsCloudHost } from \"#/api/device-flow-client\";\nimport { trackEvent } from \"#/services/telemetry\";\n\nexport type CloudConnectionSource =\n | \"onboarding\"\n | \"add_backend_modal\"\n | \"manage_backends_modal\";\n\nconst CLOUD_CONVERSATION_READY_INSERT_ID_PREFIX = `${AGENT_CANVAS_CLIENT_SOURCE}:cloud_conversation_ready`;\n\nfunction trackCloudFunnelEvent(\n event: string,\n properties: Record<string, unknown>,\n): void {\n void trackEvent(event, properties);\n}\n\nfunction hostClassification(host: string) {\n const isOpenhandsCloud = isOpenHandsCloudHost(host);\n return {\n is_openhands_cloud: isOpenhandsCloud,\n is_custom_host: !isOpenhandsCloud,\n };\n}\n\nexport function trackCloudDeviceAuthorizationStarted(\n host: string,\n source?: CloudConnectionSource,\n): void {\n trackCloudFunnelEvent(\"cloud_device_authorization_started\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCloudDeviceAuthorizationSucceeded(\n host: string,\n source?: CloudConnectionSource,\n): void {\n trackCloudFunnelEvent(\"cloud_device_authorization_succeeded\", {\n ...hostClassification(host),\n source,\n });\n}\n\nexport function trackCloudConversationReady(\n taskId: string,\n conversationId: string,\n): void {\n trackCloudFunnelEvent(\"cloud_conversation_ready\", {\n $insert_id: `${CLOUD_CONVERSATION_READY_INSERT_ID_PREFIX}:${taskId}`,\n task_id: taskId,\n conversation_id: conversationId,\n });\n}\n"],"mappings":";;;;;AASA,IAAM,IAA4C,GAAG,EAA2B;AAEhF,SAAS,EACP,GACA,GACM;AACD,GAAW,GAAO,EAAW;;AAGpC,SAAS,EAAmB,GAAc;CACxC,IAAM,IAAmB,EAAqB,EAAK;AACnD,QAAO;EACL,oBAAoB;EACpB,gBAAgB,CAAC;EAClB;;AAGH,SAAgB,EACd,GACA,GACM;AACN,GAAsB,sCAAsC;EAC1D,GAAG,EAAmB,EAAK;EAC3B;EACD,CAAC;;AAGJ,SAAgB,EACd,GACA,GACM;AACN,GAAsB,wCAAwC;EAC5D,GAAG,EAAmB,EAAK;EAC3B;EACD,CAAC;;AAGJ,SAAgB,EACd,GACA,GACM;AACN,GAAsB,4BAA4B;EAChD,YAAY,GAAG,EAA0C,GAAG;EAC5D,SAAS;EACT,iBAAiB;EAClB,CAAC"}
@@ -1,2 +1,2 @@
1
- require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../package.cjs`);var t=`openhands-telemetry-consent`,n=`openhands-telemetry-first-use`,r=`openhands-telemetry-session`,i=`agent-canvas`,a=`phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK`,o=`https://z.openhands.dev`,s=`https://us.posthog.com`,c=!1,l=null;function u(){return typeof window<`u`&&typeof localStorage<`u`}async function d(){if(!u())return null;if(l)return l;try{let{default:e}=await Promise.resolve().then(()=>require(`../node_modules/posthog-js/dist/module.cjs`));return e}catch{return null}}function f(){return typeof process<`u`&&process.env?.DO_NOT_TRACK===`1`||typeof navigator<`u`&&(navigator.doNotTrack===`1`||typeof window<`u`&&window.doNotTrack===`1`)}async function p(t=!1){if(c)return l;let n=await d();return n?(l=n.init(a,{api_host:o,ui_host:s,opt_out_capturing_by_default:!t,capture_pageview:!1,autocapture:!1,persistence:`localStorage`,persistence_name:i,consent_persistence_name:`${i}-consent`,disable_session_recording:!0,loaded:t=>{t.register({package_name:e.default.name,package_version:e.default.version})}},i),c=!0,l):null}function m(){if(!u())return`pending`;if(f())return`denied`;try{let e=localStorage.getItem(t);if(e===`granted`||e===`denied`)return e}catch{}return`pending`}async function h(e){if(u())try{localStorage.setItem(t,e);let n=await p();if(!n)return;e===`granted`?n.opt_in_capturing():n.opt_out_capturing()}catch{}}function g(){return m()===`granted`}function _(){if(!u())return!1;try{return localStorage.getItem(n)===`true`}catch{return!1}}function v(){if(u())try{localStorage.setItem(n,`true`)}catch{}}async function y(){if(f()||_())return;let e=await p(!0);e&&((e.has_opted_out_capturing?.()??!1)&&e.opt_in_capturing(),e.capture(`canvas_install`,{platform:typeof navigator<`u`?navigator.platform:`unknown`,user_agent:typeof navigator<`u`?navigator.userAgent:`unknown`,referrer:typeof document<`u`?document.referrer:``,url_origin:typeof window<`u`?window.location.origin:``,embedded:typeof window<`u`&&window.self!==window.top}),v(),m()!==`granted`&&e.opt_out_capturing())}function b(){if(!u())return!1;try{return sessionStorage.getItem(r)===`true`}catch{return!1}}function x(){if(u())try{sessionStorage.setItem(r,`true`)}catch{}}async function S(){if(!g()||b())return;let e=await p();e&&(e.capture(`canvas_new_session`,{is_first_use:!_()}),x())}async function C(e,t={}){if(!g())return;let n=await p();n&&n.capture(e,t)}async function w(){if(u())try{localStorage.removeItem(t),localStorage.removeItem(n),sessionStorage.removeItem(r),c&&l&&l.reset()}catch{}}async function T(){return c?l:null}exports.clearTelemetryData=w,exports.getPostHogInstance=T,exports.getTelemetryConsent=m,exports.isTelemetryEnabled=g,exports.setTelemetryConsent=h,exports.trackEvent=C,exports.trackInstall=y,exports.trackSessionStart=S;
1
+ require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`../package.cjs`),t=require(`../api/client-source.cjs`);var n=`openhands-telemetry-consent`,r=`openhands-telemetry-consent-pending-cloud-sync`,i=`openhands-telemetry-consent-change`,a=`openhands-telemetry-first-use`,o=`openhands-telemetry-session`,s=`agent-canvas`,c=`phc_BgzfxKdgsYMLFTmJqt424ZoyVHvKFfrwttLimzdYTKFK`,l=`https://z.openhands.dev`,u=`https://us.posthog.com`,d=null,f=null,p,m={},h=!1,g,_=0,v=-1,y=Object.freeze({client_source:t.AGENT_CANVAS_CLIENT_SOURCE,client_version:t.AGENT_CANVAS_CLIENT_VERSION,package_name:e.default.name,package_version:e.default.version});function b(e){return e?{...e,properties:{...e.properties,...y}}:null}function x(e){h||j()!==`granted`?e.opt_out_capturing():e.opt_in_capturing()}function S(e,t=!1){e.reset(t),v=-1,x(e)}function C(e){if(g===void 0||!L())return;let t=g?.distinctId,n=e.get_property(`$user_id`);if(n!=null&&n!==t&&S(e),g===null){v=_;return}(e.get_property(`$user_id`)!==g.distinctId||v!==_)&&(e.identify(g.distinctId,g.properties),v=_)}function w(e){if(e===!1){if(h)return;h=!0,d?.opt_out_capturing(),N();return}let t=h;if(h=!1,!d&&!f){let t=Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0));m={...m,...t}}t&&(d&&(x(d),C(d)),N())}function T(){return h?null:{apiKey:m.apiKey||c,apiHost:m.apiHost||l,uiHost:m.uiHost||u}}function E(){return typeof window<`u`&&typeof localStorage<`u`}async function D(){if(!E())return null;if(d)return d;try{let{default:e}=await Promise.resolve().then(()=>require(`../node_modules/posthog-js/dist/module.cjs`));return e}catch{return null}}function O(){return!!(h||typeof process<`u`&&process.env?.DO_NOT_TRACK===`1`||typeof navigator<`u`&&(navigator.doNotTrack===`1`||typeof window<`u`&&window.doNotTrack===`1`))}function k(e){d||(p=e)}async function A(e=!1){if(d)return d;if(f)return f;f=(async()=>{let t=T();if(!t)return null;let n=await D();if(!n)return null;let r=n.init(t.apiKey,{api_host:t.apiHost,ui_host:t.uiHost,opt_out_capturing_by_default:!e,capture_pageview:!1,autocapture:!1,persistence:`localStorage`,persistence_name:s,consent_persistence_name:`${s}-consent`,person_profiles:`identified_only`,disable_session_recording:!0,bootstrap:p,before_send:b},s);return r?(d=r,p=void 0,h?d.opt_out_capturing():j()===`granted`?(d.opt_in_capturing(),C(d)):e||d.opt_out_capturing(),d):null})();try{return await f}finally{d||(f=null)}}function j(){if(!E())return`pending`;if(O())return`denied`;try{let e=localStorage.getItem(n);if(e===`granted`||e===`denied`)return e}catch{}return`pending`}function M(e){if(!E())return()=>{};let t=t=>{(t.key===n||t.key===r)&&e()};return window.addEventListener(i,e),window.addEventListener(`storage`,t),()=>{window.removeEventListener(i,e),window.removeEventListener(`storage`,t)}}function N(){E()&&window.dispatchEvent(new Event(i))}function P(e){if(E())try{localStorage.setItem(r,e)}catch{}}function F(e){if(E())try{if(e!==void 0&&localStorage.getItem(r)!==e)return;localStorage.removeItem(r),N()}catch{}}async function I(e,{syncToCloud:t=!0}={}){if(E())try{if(localStorage.setItem(n,e),h)return;let t=d??await A();if(!t)return;e===`granted`?(t.opt_in_capturing(),C(t)):t.get_property(`$user_id`)==null?t.opt_out_capturing():S(t)}catch{}finally{t&&P(e),N()}}function L(){return j()===`granted`}function R(){if(!E())return!1;try{return localStorage.getItem(a)===`true`}catch{return!1}}function z(){if(E())try{localStorage.setItem(a,`true`)}catch{}}async function B(){if(O()||R())return;let e=await A(!0);!e||O()||((e.has_opted_out_capturing?.()??!1)&&e.opt_in_capturing(),e.capture(`canvas_install`,{platform:typeof navigator<`u`?navigator.platform:`unknown`,user_agent:typeof navigator<`u`?navigator.userAgent:`unknown`,referrer:typeof document<`u`?document.referrer:``,url_origin:typeof window<`u`?window.location.origin:``,embedded:typeof window<`u`&&window.self!==window.top}),z(),j()!==`granted`&&e.opt_out_capturing())}function V(){if(!E())return!1;try{return sessionStorage.getItem(o)===`true`}catch{return!1}}function H(){if(E())try{sessionStorage.setItem(o,`true`)}catch{}}async function U(){if(!L())return null;let e=await A();return!e||!L()?null:(e.has_opted_out_capturing?.()&&e.opt_in_capturing(),C(e),e)}async function W(){if(V())return;let e=await U();e&&(e.capture(`canvas_new_session`,{is_first_use:!R()}),H())}async function G(e,t={}){let n=await U();n&&n.capture(e,t)}async function K(e,t={}){let n=await U();n&&n.captureException(e,t)}async function q(){if(E()){try{localStorage.removeItem(n),localStorage.removeItem(a)}catch{}F();try{sessionStorage.removeItem(o)}catch{}g=null,_+=1,v=-1;try{d&&S(d,!0)}catch{try{d?.opt_out_capturing()}catch{}}}}exports.clearTelemetryData=q,exports.configurePostHogBootstrap=k,exports.configureTelemetry=w,exports.getTelemetryConsent=j,exports.initializePostHogClient=A,exports.isTelemetryEnabled=L,exports.setTelemetryConsent=I,exports.subscribeTelemetryConsent=M,exports.trackEvent=G,exports.trackException=K,exports.trackInstall=B,exports.trackSessionStart=W;
2
2
  //# sourceMappingURL=telemetry.cjs.map