@openhands/agent-canvas 1.0.0-rc.2 → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README.windows.md +2 -2
- package/build/assets/acp-providers-C55k29rf.js +1 -0
- package/build/assets/{add-backend-modal-CIfhseZn.js → add-backend-modal-BdYxoUdL.js} +1 -1
- package/build/assets/agent-server-conversation-service.api-js3oYcdU.js +5 -0
- package/build/assets/{agent-settings-B6htMeS2.js → agent-settings-BNrffu3I.js} +1 -1
- package/build/assets/{alert-banner-D41ZKDZT.js → alert-banner-BzU93oDh.js} +1 -1
- package/build/assets/{analytics-consent-form-modal-TINgM_jV.js → analytics-consent-form-modal-Ce-_Skcd.js} +1 -1
- package/build/assets/{api-key-entry-screen-Di1vHgIU.js → api-key-entry-screen-CGyS-Cna.js} +1 -1
- package/build/assets/{app-settings-CjCa1MOB.js → app-settings-DmOs4oik.js} +1 -1
- package/build/assets/{automation-detail-DPoxzTdV.js → automation-detail-DLbCVJCw.js} +1 -1
- package/build/assets/{automations-list-DwUEe2Ea.js → automations-list-Ces5shz5.js} +1 -1
- package/build/assets/{backend-form-modal-B6q897ZX.js → backend-form-modal-BUy-PzZN.js} +1 -1
- package/build/assets/{backend-synced-settings-badge-BwTawSCE.js → backend-synced-settings-badge-1A63yxGs.js} +1 -1
- package/build/assets/browser-B8bp1IqT.js +5 -0
- package/build/assets/{browser-tab-DiRTKik8.js → browser-tab-CiBRWB4X.js} +1 -1
- package/build/assets/chat-send-button-qKFZuB9E.js +1 -0
- package/build/assets/{circle-plus-check-toggle-B3_W6-nt.js → circle-plus-check-toggle-CboDp7XB.js} +1 -1
- package/build/assets/{condenser-settings-DczjwkIp.js → condenser-settings-FCBjvqSo.js} +1 -1
- package/build/assets/conversation-CT8AvxEH.js +1 -0
- package/build/assets/conversation-panel-DqDDs4WZ.js +1 -0
- package/build/assets/conversation-service.api-y_eB_43m.js +1 -0
- package/build/assets/conversation-store-CiYGBud3.js +6 -0
- package/build/assets/conversation-vNuLKgz6.js +19 -0
- package/build/assets/conversation-websocket-context-Bb4XBXoc.js +3 -0
- package/build/assets/{device-verify-BVl4GEvt.js → device-verify-BOQz2LJQ.js} +1 -1
- package/build/assets/{edit-automation-modal-kc_FzbzK.js → edit-automation-modal-C-oC5tis.js} +1 -1
- package/build/assets/{entry.client-CWkbusD1.js → entry.client-Db3BpFL_.js} +2 -2
- package/build/assets/{enum-filter-dropdown-DlY0Q3fj.js → enum-filter-dropdown-DFwoVtOw.js} +1 -1
- package/build/assets/{extensions-hub-NbQnt-cn.js → extensions-hub-BxZbAtjP.js} +1 -1
- package/build/assets/extensions-navigation-BIb-vAa2.js +1 -0
- package/build/assets/{files-tab-mK7Mdyuf.js → files-tab-DpulQlIo.js} +1 -1
- package/build/assets/{git-control-bar-branch-button-C1qmab0K.js → git-control-bar-branch-button-D1uam-nI.js} +1 -1
- package/build/assets/home-sJAFzI6v.js +1 -0
- package/build/assets/install-server-modal-BjEzlLF5.js +1 -0
- package/build/assets/{launch-BADsYeGp.js → launch-DgtB1JTX.js} +1 -1
- package/build/assets/llm-settings-B8kPJ0Of.js +1 -0
- package/build/assets/llm-settings-CjUpPsvA.js +1 -0
- package/build/assets/{manage-backends-modal-sH7l5NgI.js → manage-backends-modal-CTA-2x4F.js} +1 -1
- package/build/assets/manifest-d3e8504d.js +1 -0
- package/build/assets/{markdown-renderer-CZq_UdmE.js → markdown-renderer-oOUs3tw5.js} +1 -1
- package/build/assets/{mcp-DdQ72_AO.js → mcp-kJYdM8aJ.js} +1 -1
- package/build/assets/messages-BFJXB6MW.js +36 -0
- package/build/assets/model-selector-z_QOzaRV.js +1 -0
- package/build/assets/{onboarding-DCL9stdH.js → onboarding-CZ_7nd-M.js} +1 -1
- package/build/assets/{path-utils-CNd_jqv_.js → path-utils-0KWEiRs9.js} +1 -1
- package/build/assets/{planner-tab-QBnZgIXd.js → planner-tab-CNmJiZ22.js} +1 -1
- package/build/assets/{recommended-automations-launcher-B01jchhe.js → recommended-automations-launcher-B1kfmFTQ.js} +8 -10
- package/build/assets/{root-BBV8Ew9E.js → root-CIZ7Rv1X.js} +2 -2
- package/build/assets/root-layout-B5ijp9At.js +2 -0
- package/build/assets/{sdk-section-page-BQKe3asw.js → sdk-section-page-DTyvCc52.js} +1 -1
- package/build/assets/{sdk-settings-schema-DoRnefvb.js → sdk-settings-schema-B0KrqqPX.js} +1 -1
- package/build/assets/{secrets-settings-D2EfzdpC.js → secrets-settings-BQNSDLKS.js} +1 -1
- package/build/assets/{settings-B7jVceyu.js → settings-BUlJrO_h.js} +1 -1
- package/build/assets/{settings-dropdown-input-BD7ziSoR.js → settings-dropdown-input-BUk4m23x.js} +1 -1
- package/build/assets/{settings-index-Xj0v9Oas.js → settings-index-DE91Eahc.js} +1 -1
- package/build/assets/{settings-modal-CDBy1S9S.js → settings-modal-CCaPYVqW.js} +1 -1
- package/build/assets/{settings-service.api-CTQ-LpAA.js → settings-service.api-4u2RKC8k.js} +1 -1
- package/build/assets/{settings-switch-CSHSqH99.js → settings-switch-BQiAS9ga.js} +1 -1
- package/build/assets/{settings-utils-BCbzc6-u.js → settings-utils-chxTa1vn.js} +1 -1
- package/build/assets/{shared-conversation-BHEbOdHj.js → shared-conversation-sBPLAawM.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-YYPXGikp.js → sidebar-mobile-menu-toggle-BDXWzhyB.js} +1 -1
- package/build/assets/{sidebar-nav-link-CiXbBMQx.js → sidebar-nav-link-Bhlzlhp8.js} +1 -1
- package/build/assets/{skill-card-pill-row-BXILn-GK.js → skill-card-pill-row-Bg5joQf6.js} +1 -1
- package/build/assets/{skills-plugins-Bs5HiF1O.js → skills-plugins-fihjo1KZ.js} +1 -1
- package/build/assets/{skills-settings-CQYxMmWf.js → skills-settings-NQnuMwZP.js} +1 -1
- package/build/assets/{styled-tooltip-DEr7oa0m.js → styled-tooltip-GXy1qxsO.js} +1 -1
- package/build/assets/{task-list-tab-R9N3Wd-U.js → task-list-tab-Hl9BuVfq.js} +1 -1
- package/build/assets/telemetry-j9SLrtY7.js +2 -0
- package/build/assets/{terminal-BTM3UFcQ.js → terminal-DRwe8--D.js} +1 -1
- package/build/assets/{use-active-conversation-BY5F6A1G.js → use-active-conversation-DJGoI9Mc.js} +1 -1
- package/build/assets/{use-agent-settings-schema-CsuMq16G.js → use-agent-settings-schema-DtusObuC.js} +1 -1
- package/build/assets/{use-agent-state-DX5NKEa_.js → use-agent-state-BXgWhFh6.js} +1 -1
- package/build/assets/{use-create-conversation-4iJytCT1.js → use-create-conversation-CzvaVA5A.js} +1 -1
- package/build/assets/use-event-store-DSpvASbC.js +1 -0
- package/build/assets/{use-get-secrets-BMnFFBUx.js → use-get-secrets-DiLgP147.js} +1 -1
- package/build/assets/use-handle-plan-click-CUZwmKIk.js +1 -0
- package/build/assets/{use-launch-skill-in-chat-BoqKmEHC.js → use-launch-skill-in-chat-ClRJlIx7.js} +1 -1
- package/build/assets/use-runtime-is-ready-CWkGQFsb.js +1 -0
- package/build/assets/{use-save-settings-hK6LYt0s.js → use-save-settings-CP23emUY.js} +1 -1
- package/build/assets/use-settings-0qFqC-r6.js +1 -0
- package/build/assets/{use-settings-nav-items-oZ-BlOWX.js → use-settings-nav-items-C6YxUn4h.js} +1 -1
- package/build/assets/{use-task-list-DydbuRFM.js → use-task-list-JPudBRv3.js} +1 -1
- package/build/assets/{use-tracking-DQU60djN.js → use-tracking-CjLZgh8X.js} +1 -1
- package/build/assets/{use-unified-vscode-url-D2Buvmxj.js → use-unified-vscode-url-DSn2tT08.js} +1 -1
- package/build/assets/{use-user-conversation-DwOGM1lc.js → use-user-conversation-CQ5IwnZk.js} +1 -1
- package/build/assets/{vendor~browser-3J6WDaAB.js → vendor~browser-Dwwc84Zf.js} +1 -1
- package/build/assets/{vendor~browser-tab-DYZ-OmbT.js → vendor~browser-tab-Bhohe29F.js} +1 -1
- package/build/assets/{vendor~files-tab-Diy4WrQz.js → vendor~files-tab-B447_Zns.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-CWwn0K2j.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-CQQAW8hY.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-dZ3D8RVL.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DFsI4CLy.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BighOCzm.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DcvlNgbn.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-D13hiNGV.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-ojk_J4bB.js} +1 -1
- package/build/assets/{vendor~launch-D65Vw0VZ.js → vendor~launch-BdXJCmwc.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation~shared-conversation-BJPgfJoU.js → vendor~root-layout~conversation-panel~conversation~shared-conversation-DToubnIU.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~mcp~~jpfhx3ls-BkicN-14.js → vendor~root-layout~home~conversation-panel~conversation~extensions-hub~skills-settings~mcp~~jpfhx3ls-zdl_Rltz.js} +2 -2
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~launch~settings~settings-index~agen~jxrvuot9-Cwz6a1DC.js → vendor~root-layout~home~conversation-panel~conversation~launch~settings~settings-index~agen~jxrvuot9-BaCzvZac.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~shared-conversation~alert-banner~pl~rqjteh0a-C37jLHRk.js → vendor~root-layout~home~conversation-panel~conversation~shared-conversation~alert-banner~pl~rqjteh0a-BwbkftxT.js} +1 -1
- package/build/assets/{vendor~root-layout~home~mcp~automations-list-JQ-neDIa.js → vendor~root-layout~home~mcp~automations-list-CtdIEhjQ.js} +1 -1
- package/build/assets/{vendor~root-layout~home~mcp~automations-list-BTTZ58Mb.js → vendor~root-layout~home~mcp~automations-list-DGOI7kQz.js} +1 -1
- package/build/assets/{vendor~root-layout~home~mcp~llm-settings~agent-settings~condenser-settings~verification-set~o7tv66sg-UYEKKX0Y.js → vendor~root-layout~home~mcp~llm-settings~agent-settings~condenser-settings~verification-set~o7tv66sg-iPAfRWNQ.js} +2 -2
- package/build/assets/{verification-settings-Rabe5TpK.js → verification-settings-Cm02KmeI.js} +1 -1
- package/build/assets/{vscode-tab-zLD5Z-PP.js → vscode-tab-AF70Yke0.js} +1 -1
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +1 -1
- package/build/locales/ca/openhands.json +1 -1
- package/build/locales/de/openhands.json +1 -1
- package/build/locales/en/openhands.json +1 -1
- package/build/locales/es/openhands.json +1 -1
- package/build/locales/fr/openhands.json +1 -1
- package/build/locales/it/openhands.json +1 -1
- package/build/locales/ja/openhands.json +1 -1
- package/build/locales/ko-KR/openhands.json +1 -1
- package/build/locales/no/openhands.json +1 -1
- package/build/locales/pt/openhands.json +1 -1
- package/build/locales/tr/openhands.json +1 -1
- package/build/locales/uk/openhands.json +1 -1
- package/build/locales/zh-CN/openhands.json +1 -1
- package/build/locales/zh-TW/openhands.json +1 -1
- package/config/defaults.json +1 -1
- package/dist/api/agent-server-adapter.cjs +3 -3
- package/dist/api/agent-server-adapter.cjs.map +1 -1
- package/dist/api/agent-server-adapter.js +90 -85
- package/dist/api/agent-server-adapter.js.map +1 -1
- package/dist/api/app-preferences-store.cjs.map +1 -1
- package/dist/api/app-preferences-store.d.ts +6 -2
- package/dist/api/app-preferences-store.js.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/should-render-event.cjs +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/should-render-event.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/should-render-event.js +9 -9
- package/dist/components/conversation-events/chat/event-content-helpers/should-render-event.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message.js +80 -71
- package/dist/components/conversation-events/chat/event-message.js.map +1 -1
- package/dist/components/features/automations/recommended-automations-launcher.d.ts +7 -1
- package/dist/components/features/home/workspace-selection-form.d.ts +1 -0
- package/dist/components/features/onboarding/steps/setup-llm-step.d.ts +8 -0
- package/dist/components/features/sidebar/sidebar.cjs +1 -1
- package/dist/components/features/sidebar/sidebar.js +6 -6
- package/dist/components/features/skills/extensions-navigation.cjs +1 -1
- package/dist/components/features/skills/extensions-navigation.cjs.map +1 -1
- package/dist/components/features/skills/extensions-navigation.d.ts +1 -1
- package/dist/components/features/skills/extensions-navigation.js +27 -29
- package/dist/components/features/skills/extensions-navigation.js.map +1 -1
- package/dist/i18n/translation.cjs +1 -1
- package/dist/i18n/translation.cjs.map +1 -1
- package/dist/i18n/translation.js +15 -15
- package/dist/i18n/translation.js.map +1 -1
- package/dist/locales/ar/openhands.json +1 -1
- package/dist/locales/ca/openhands.json +1 -1
- package/dist/locales/de/openhands.json +1 -1
- package/dist/locales/en/openhands.json +1 -1
- package/dist/locales/es/openhands.json +1 -1
- package/dist/locales/fr/openhands.json +1 -1
- package/dist/locales/it/openhands.json +1 -1
- package/dist/locales/ja/openhands.json +1 -1
- package/dist/locales/ko-KR/openhands.json +1 -1
- package/dist/locales/no/openhands.json +1 -1
- package/dist/locales/pt/openhands.json +1 -1
- package/dist/locales/tr/openhands.json +1 -1
- package/dist/locales/uk/openhands.json +1 -1
- package/dist/locales/zh-CN/openhands.json +1 -1
- package/dist/locales/zh-TW/openhands.json +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp.cjs +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp.js +10 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/dist/routes/llm-settings.cjs +1 -1
- package/dist/routes/llm-settings.cjs.map +1 -1
- package/dist/routes/llm-settings.js +21 -21
- package/dist/routes/llm-settings.js.map +1 -1
- package/dist/types/agent-server/core/events/index.d.ts +1 -0
- package/dist/types/agent-server/core/events/streaming-delta-event.d.ts +7 -0
- package/dist/types/agent-server/core/openhands-event.d.ts +2 -2
- package/dist/types/agent-server/type-guards.cjs +1 -1
- package/dist/types/agent-server/type-guards.cjs.map +1 -1
- package/dist/types/agent-server/type-guards.d.ts +2 -0
- package/dist/types/agent-server/type-guards.js +3 -3
- package/dist/types/agent-server/type-guards.js.map +1 -1
- package/dist/utils/handle-event-for-ui.cjs +1 -1
- package/dist/utils/handle-event-for-ui.cjs.map +1 -1
- package/dist/utils/handle-event-for-ui.js +69 -13
- package/dist/utils/handle-event-for-ui.js.map +1 -1
- package/dist/utils/mcp-config.cjs +1 -1
- package/dist/utils/mcp-config.cjs.map +1 -1
- package/dist/utils/mcp-config.js +27 -19
- package/dist/utils/mcp-config.js.map +1 -1
- package/dist/utils/mcp-marketplace-utils.cjs +1 -1
- package/dist/utils/mcp-marketplace-utils.cjs.map +1 -1
- package/dist/utils/mcp-marketplace-utils.js +38 -18
- package/dist/utils/mcp-marketplace-utils.js.map +1 -1
- package/dist/utils/normalize-display-model.cjs +1 -1
- package/dist/utils/normalize-display-model.cjs.map +1 -1
- package/dist/utils/normalize-display-model.js +8 -7
- package/dist/utils/normalize-display-model.js.map +1 -1
- package/dist/utils/openhands-llm.cjs +1 -1
- package/dist/utils/openhands-llm.cjs.map +1 -1
- package/dist/utils/openhands-llm.d.ts +13 -0
- package/dist/utils/openhands-llm.js +9 -3
- package/dist/utils/openhands-llm.js.map +1 -1
- package/package.json +2 -2
- package/scripts/check-sdk-version-sync.mjs +6 -6
- package/scripts/dev-safe.mjs +53 -118
- package/scripts/dev-with-automation.mjs +58 -4
- package/scripts/runtime-services-info.mjs +199 -0
- package/scripts/static-server.mjs +42 -4
- package/build/assets/acp-providers-DZEi8wDz.js +0 -1
- package/build/assets/agent-server-conversation-service.api-B82pNNTm.js +0 -5
- package/build/assets/browser-DTei6kki.js +0 -5
- package/build/assets/chat-send-button-06dIuWXm.js +0 -1
- package/build/assets/conversation-CBlJiDaV.js +0 -19
- package/build/assets/conversation-Dss8XCN_.js +0 -1
- package/build/assets/conversation-panel-B5sVpsz5.js +0 -1
- package/build/assets/conversation-service.api-B4s-xIOK.js +0 -1
- package/build/assets/conversation-store-kHcewy1E.js +0 -1
- package/build/assets/conversation-websocket-context-C2yKCqvj.js +0 -3
- package/build/assets/extensions-navigation-oOk5yl8X.js +0 -1
- package/build/assets/home-ChuA06Hv.js +0 -1
- package/build/assets/install-server-modal--lZ1HSHB.js +0 -1
- package/build/assets/llm-settings-D477P0Lg.js +0 -1
- package/build/assets/llm-settings-DtlQ7i4C.js +0 -1
- package/build/assets/manifest-17af0b17.js +0 -1
- package/build/assets/messages-luW9zf1w.js +0 -36
- package/build/assets/middleware-B5rcobhi.js +0 -6
- package/build/assets/model-selector-DzQRgNGZ.js +0 -1
- package/build/assets/root-layout-pQASEqtQ.js +0 -2
- package/build/assets/sidebar-store-B76R2gP8.js +0 -1
- package/build/assets/telemetry-DCrd7gnV.js +0 -2
- package/build/assets/use-event-store-Rw1YbvLm.js +0 -1
- package/build/assets/use-handle-plan-click-DYd5a6zN.js +0 -1
- package/build/assets/use-runtime-is-ready-DP-KKHO6.js +0 -1
- package/build/assets/use-settings-Clf0Y_P3.js +0 -1
- /package/build/assets/{automation-LZB0MjdV.js → automation-BzmydDjr.js} +0 -0
- /package/build/assets/{browser-store-BjhV_9wS.js → browser-store-C5uQbbIi.js} +0 -0
- /package/build/assets/{checkmark-BB7QCG5T.js → checkmark-SEZPnU2I.js} +0 -0
- /package/build/assets/{chevron-left-small-CuuwpRi9.js → chevron-left-small-x9_-ucHG.js} +0 -0
- /package/build/assets/{close-Cz0OAgTy.js → close-CbOsKMRg.js} +0 -0
- /package/build/assets/{code-tag-Cz9AIz-X.js → code-tag-C0vR_IQH.js} +0 -0
- /package/build/assets/{command-store-DAd3K0d_.js → command-store-BUWgE-vZ.js} +0 -0
- /package/build/assets/{confirmation-modal-C_lds1Tb.js → confirmation-modal-BPgWqWRI.js} +0 -0
- /package/build/assets/{context-menu-list-item-DhG1Ln5m.js → context-menu-list-item-CHWCx1Mc.js} +0 -0
- /package/build/assets/{conversation-state-store-BUU90dVq.js → conversation-state-store-DpgQaK_O.js} +0 -0
- /package/build/assets/{conversation-tab-empty-state-5bW9CQke.js → conversation-tab-empty-state-Ba5hbJhn.js} +0 -0
- /package/build/assets/{copy-CA1Dblua.js → copy-CAxUvM-U.js} +0 -0
- /package/build/assets/{dist-CBUfAk0Z.js → dist-CeJSjcAI.js} +0 -0
- /package/build/assets/{ellipsis-button-DRRmCrWi.js → ellipsis-button-CiLx8dqc.js} +0 -0
- /package/build/assets/{environment-switch-overlay-BrHKX6_Z.js → environment-switch-overlay-Cow6h62p.js} +0 -0
- /package/build/assets/{file-DM0ihEsO.js → file-BJCSojij.js} +0 -0
- /package/build/assets/{files-tab-store-QlUCuW8b.js → files-tab-store-CZAEwv3x.js} +0 -0
- /package/build/assets/{folder-UGYUKpqb.js → folder-wShAU0y7.js} +0 -0
- /package/build/assets/{git-status-mapper-N4-7eaeC.js → git-status-mapper-C3rfzUex.js} +0 -0
- /package/build/assets/{globe-2otpEmVh.js → globe-CQ_5xwpo.js} +0 -0
- /package/build/assets/{handle-capture-consent-OitMkDHc.js → handle-capture-consent-DDnXMC9Y.js} +0 -0
- /package/build/assets/{iconBase-B_5IRYeZ.js → iconBase-1A_WRQ4E.js} +0 -0
- /package/build/assets/{lesson-plan-5O2tVbD1.js → lesson-plan-B607buca.js} +0 -0
- /package/build/assets/{link-external-kU6aFXU6.js → link-external-DCsHkAj4.js} +0 -0
- /package/build/assets/{map-provider-BHow6ugd.js → map-provider-XSFHmdDs.js} +0 -0
- /package/build/assets/{modal-close-button-BCvw9IUN.js → modal-close-button-DY-rVmld.js} +0 -0
- /package/build/assets/{plan-components-BRiIX8Wn.js → plan-components-DsiDjcDi.js} +0 -0
- /package/build/assets/{sdk-settings-field-metadata-CtO73dY6.js → sdk-settings-field-metadata-CETNItbo.js} +0 -0
- /package/build/assets/{search-Cbh-hHBu.js → search-BqXT2Ied.js} +0 -0
- /package/build/assets/{secrets-service-DEIB-Cfk.js → secrets-service-rJqZtDmI.js} +0 -0
- /package/build/assets/{settings-KgLvVrSm.js → settings-BgL2HUsU.js} +0 -0
- /package/build/assets/{settings-gear-xGs_SPgZ.js → settings-gear-DFmoMp-6.js} +0 -0
- /package/build/assets/{settings-input-CPr7vX81.js → settings-input-DsoZj8Dm.js} +0 -0
- /package/build/assets/{settings-like-page-layout-classes-GknosJgv.js → settings-like-page-layout-classes-DENKlZpD.js} +0 -0
- /package/build/assets/{settings-list-classes-CYDn4jUg.js → settings-list-classes-D8VAVZmi.js} +0 -0
- /package/build/assets/{settings-section-header-context-aD2iq1gD.js → settings-section-header-context-D61smD-W.js} +0 -0
- /package/build/assets/{skills-CCaEu1KQ.js → skills-BDOWVle-.js} +0 -0
- /package/build/assets/{switch-skeleton-C87Tx3Tc.js → switch-skeleton-QpdcdxRP.js} +0 -0
- /package/build/assets/{terminal-DQJ6IJUd.js → terminal-BUww3Ssl.js} +0 -0
- /package/build/assets/{toggle-switch-C-7juZ1u.js → toggle-switch-DDr-DnEc.js} +0 -0
- /package/build/assets/{typography-U1gkzkXo.js → typography-Cx7uw7z3.js} +0 -0
- /package/build/assets/{u-check-circle-lbkXL2z4.js → u-check-circle-CftRwky1.js} +0 -0
- /package/build/assets/{u-check-circle-half-CirnoxXG.js → u-check-circle-half-sGVk0BtL.js} +0 -0
- /package/build/assets/{u-circuit-Danff2ks.js → u-circuit-Cg9tH5qb.js} +0 -0
- /package/build/assets/{u-edit-CH5nNya1.js → u-edit-foF02hwH.js} +0 -0
- /package/build/assets/{use-breakpoint-2sN462wJ.js → use-breakpoint-Dt2knceC.js} +0 -0
- /package/build/assets/{use-click-outside-element-_vianyPb.js → use-click-outside-element-Bu2A3s59.js} +0 -0
- /package/build/assets/{use-is-authed-DrocXcet.js → use-is-authed-fNsj-Adj.js} +0 -0
- /package/build/assets/{use-llm-profiles-BhZRf-NX.js → use-llm-profiles-CyNVoVqm.js} +0 -0
- /package/build/assets/{use-skills-v8pQ02ze.js → use-skills-BWHATXK-.js} +0 -0
- /package/build/assets/{v4-BMWDcIWQ.js → v4-BygpdDmc.js} +0 -0
- /package/build/assets/{vendor~browser-C3GKF4mj.js → vendor~browser-BYEwwJqV.js} +0 -0
- /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-DMcFTqbt.js → vendor~conversation-panel~conversation~alert-banner-BnHToS5O.js} +0 -0
- /package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-Cntsv2EN.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-B4oeCCli.js} +0 -0
- /package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DTosGXG7.js → vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-D8cqyq4k.js} +0 -0
- /package/build/assets/{vendor~terminal-CnKZILnC.js → vendor~terminal-aeP3PnKf.js} +0 -0
- /package/build/assets/{vscode-url-helper-CwQPl6QN.js → vscode-url-helper-DOCkV_-G.js} +0 -0
- /package/build/assets/{waiting-for-runtime-message-B1Dzhtj5.js → waiting-for-runtime-message-Bg27u9JB.js} +0 -0
- /package/build/assets/{x-mark-CmcVOTk2.js → x-mark-Cn-YJVbN.js} +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);var e={sse_servers:[],stdio_servers:[],shttp_servers:[]}
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);var e={sse_servers:[],stdio_servers:[],shttp_servers:[]},t=`https://mcp.linear.app/sse`,n=`https://mcp.linear.app/mcp`;function r(e,n){return n===`sse`?e.split(`?`)[0].replace(/\/+$/,``)===t:!1}function i(t){if(!t||typeof t!=`object`)return{...e};let i=t;if(!(`mcpServers`in i)||!i.mcpServers||typeof i.mcpServers!=`object`)return{...e};let a=[],o=[],s=[],c=[],l=i.mcpServers;for(let[e,t]of Object.entries(l)){if(!t||typeof t!=`object`)continue;let i=t.url;if(i){let e=t.transport,o=t.auth,l=typeof o==`string`&&o!==`oauth`?o:void 0;if(r(i,e)){let e={url:n};l&&(e.api_key=l),c.push(e)}else if(e===`sse`){let e={url:i};l&&(e.api_key=l),a.push(e)}else{let e={url:i};l&&(e.api_key=l),t.timeout!=null&&(e.timeout=t.timeout),s.push(e)}}else{let n={name:e,command:t.command};t.args&&(n.args=t.args),t.env&&(n.env=t.env),o.push(n)}}let u=e=>e.replace(/\/+$/,``);for(let e of c)s.some(t=>u(typeof t==`string`?t:t.url)===u(e.url))||s.push(e);return{sse_servers:a,stdio_servers:o,shttp_servers:s}}function a(e){let t={},n=e=>{if(!(e in t))return e;let n=1;for(;`${e}_${n}`in t;)n+=1;return`${e}_${n}`};for(let r of e.sse_servers){let e={};typeof r==`string`?e.url=r:(e.url=r.url,r.api_key&&(e.auth=r.api_key)),e.transport=`sse`,t[n(`sse`)]=e}for(let r of e.shttp_servers){let e={};typeof r==`string`?e.url=r:(e.url=r.url,r.api_key&&(e.auth=r.api_key),r.timeout!=null&&(e.timeout=r.timeout)),t[n(`shttp`)]=e}for(let r of e.stdio_servers){let e={command:r.command};r.args&&(e.args=r.args),r.env&&(e.env=r.env),t[n(r.name||`stdio`)]=e}return Object.keys(t).length>0?{mcpServers:t}:null}exports.parseMcpConfig=i,exports.toSdkMcpConfig=a;
|
|
2
2
|
//# sourceMappingURL=mcp-config.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-config.cjs","names":[],"sources":["../../src/utils/mcp-config.ts"],"sourcesContent":["import {\n MCPConfig,\n MCPSSEServer,\n MCPSHTTPServer,\n MCPStdioServer,\n SettingsValue,\n} from \"#/types/settings\";\n\nconst EMPTY_MCP_CONFIG: MCPConfig = {\n sse_servers: [],\n stdio_servers: [],\n shttp_servers: [],\n};\n\ntype SdkMcpServerConfig = Record<string, SettingsValue>;\ntype SdkMcpConfig = { mcpServers: Record<string, SdkMcpServerConfig> };\n\n/**\n * Parse an SDK mcp_config value ({ mcpServers: { ... } }) and convert it\n * to the frontend MCPConfig format used by UI components.\n */\nexport function parseMcpConfig(value: unknown): MCPConfig {\n if (!value || typeof value !== \"object\") {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const obj = value as Record<string, unknown>;\n\n if (\n !(\"mcpServers\" in obj) ||\n !obj.mcpServers ||\n typeof obj.mcpServers !== \"object\"\n ) {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const sseServers: (string | MCPSSEServer)[] = [];\n const stdioServers: MCPStdioServer[] = [];\n const shttpServers: (string | MCPSHTTPServer)[] = [];\n\n const mcpServers = obj.mcpServers as Record<string, Record<string, unknown>>;\n\n for (const [serverName, serverConfig] of Object.entries(mcpServers)) {\n if (!serverConfig || typeof serverConfig !== \"object\") continue;\n\n const url = serverConfig.url as string | undefined;\n\n if (url) {\n const transport = serverConfig.transport as string | undefined;\n const auth = serverConfig.auth as string | undefined;\n const apiKey =\n typeof auth === \"string\" && auth !== \"oauth\" ? auth : undefined;\n\n if (transport === \"sse\") {\n const server: MCPSSEServer = { url };\n if (apiKey) server.api_key = apiKey;\n sseServers.push(server);\n } else {\n const server: MCPSHTTPServer = { url };\n if (apiKey) server.api_key = apiKey;\n if (serverConfig.timeout != null) {\n server.timeout = serverConfig.timeout as number;\n }\n shttpServers.push(server);\n }\n } else {\n const stdioServer: MCPStdioServer = {\n name: serverName,\n command: serverConfig.command as string,\n };\n if (serverConfig.args) {\n stdioServer.args = serverConfig.args as string[];\n }\n if (serverConfig.env) {\n stdioServer.env = serverConfig.env as Record<string, string>;\n }\n stdioServers.push(stdioServer);\n }\n }\n\n return {\n sse_servers: sseServers,\n stdio_servers: stdioServers,\n shttp_servers: shttpServers,\n };\n}\n\n/**\n * Convert the frontend MCPConfig format back to the SDK { mcpServers: { ... } }\n * shape expected by agent_settings.mcp_config on the backend.\n *\n * Names are only suffixed (``_1``, ``_2``, …) when an earlier entry has\n * already claimed the bare base name. We intentionally do NOT use a single\n * monotonic counter across server types: that would, for example, rename a\n * stdio server \"myname\" to \"myname_1\" the moment any sse/shttp entry is\n * persisted ahead of it, and shift the suffix on every save as the count\n * of other server types changes. With per-base collision suffixing,\n * unrelated entries keep their human-meaningful names stable across edits.\n */\nexport function toSdkMcpConfig(config: MCPConfig): SdkMcpConfig | null {\n const mcpServers: Record<string, SdkMcpServerConfig> = {};\n\n const reserve = (base: string): string => {\n if (!(base in mcpServers)) return base;\n let i = 1;\n while (`${base}_${i}` in mcpServers) i += 1;\n return `${base}_${i}`;\n };\n\n for (const entry of config.sse_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n }\n server.transport = \"sse\";\n mcpServers[reserve(\"sse\")] = server;\n }\n\n for (const entry of config.shttp_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n if (entry.timeout != null) server.timeout = entry.timeout;\n }\n mcpServers[reserve(\"shttp\")] = server;\n }\n\n for (const entry of config.stdio_servers) {\n const server: SdkMcpServerConfig = {\n command: entry.command,\n };\n if (entry.args) server.args = entry.args;\n if (entry.env) server.env = entry.env;\n mcpServers[reserve(entry.name || \"stdio\")] = server;\n }\n\n return Object.keys(mcpServers).length > 0 ? { mcpServers } : null;\n}\n"],"mappings":"6CAQA,IAAM,EAA8B,CAClC,YAAa,EAAE,CACf,cAAe,EAAE,CACjB,cAAe,EAAE,CAClB,
|
|
1
|
+
{"version":3,"file":"mcp-config.cjs","names":[],"sources":["../../src/utils/mcp-config.ts"],"sourcesContent":["import {\n MCPConfig,\n MCPSSEServer,\n MCPSHTTPServer,\n MCPStdioServer,\n SettingsValue,\n} from \"#/types/settings\";\n\nconst EMPTY_MCP_CONFIG: MCPConfig = {\n sse_servers: [],\n stdio_servers: [],\n shttp_servers: [],\n};\n\nconst LINEAR_DEPRECATED_SSE_URL = \"https://mcp.linear.app/sse\";\nconst LINEAR_SHTTP_URL = \"https://mcp.linear.app/mcp\";\n\n/**\n * Linear removed its MCP SSE transport (the /sse endpoint rejects every\n * call since 2026-04-08). Detect persisted configs that still point at\n * the dead endpoint so they can be migrated to streamable HTTP at the\n * /mcp replacement. Matches only the exact deprecated URL (tolerating a\n * trailing slash or query string) — nothing else is rewritten.\n */\nfunction isDeprecatedLinearSse(\n url: string,\n transport: string | undefined,\n): boolean {\n if (transport !== \"sse\") return false;\n const normalized = url.split(\"?\")[0].replace(/\\/+$/, \"\");\n return normalized === LINEAR_DEPRECATED_SSE_URL;\n}\n\ntype SdkMcpServerConfig = Record<string, SettingsValue>;\ntype SdkMcpConfig = { mcpServers: Record<string, SdkMcpServerConfig> };\n\n/**\n * Parse an SDK mcp_config value ({ mcpServers: { ... } }) and convert it\n * to the frontend MCPConfig format used by UI components.\n */\nexport function parseMcpConfig(value: unknown): MCPConfig {\n if (!value || typeof value !== \"object\") {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const obj = value as Record<string, unknown>;\n\n if (\n !(\"mcpServers\" in obj) ||\n !obj.mcpServers ||\n typeof obj.mcpServers !== \"object\"\n ) {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const sseServers: (string | MCPSSEServer)[] = [];\n const stdioServers: MCPStdioServer[] = [];\n const shttpServers: (string | MCPSHTTPServer)[] = [];\n // Legacy Linear SSE entries rewritten to the /mcp endpoint. Collected\n // separately and merged after the loop so an existing hand-added /mcp\n // entry (with its own api_key/timeout) wins over the migrated one.\n const migratedShttpServers: MCPSHTTPServer[] = [];\n\n const mcpServers = obj.mcpServers as Record<string, Record<string, unknown>>;\n\n for (const [serverName, serverConfig] of Object.entries(mcpServers)) {\n if (!serverConfig || typeof serverConfig !== \"object\") continue;\n\n const url = serverConfig.url as string | undefined;\n\n if (url) {\n const transport = serverConfig.transport as string | undefined;\n const auth = serverConfig.auth as string | undefined;\n const apiKey =\n typeof auth === \"string\" && auth !== \"oauth\" ? auth : undefined;\n\n if (isDeprecatedLinearSse(url, transport)) {\n const server: MCPSHTTPServer = { url: LINEAR_SHTTP_URL };\n if (apiKey) server.api_key = apiKey;\n migratedShttpServers.push(server);\n } else if (transport === \"sse\") {\n const server: MCPSSEServer = { url };\n if (apiKey) server.api_key = apiKey;\n sseServers.push(server);\n } else {\n const server: MCPSHTTPServer = { url };\n if (apiKey) server.api_key = apiKey;\n if (serverConfig.timeout != null) {\n server.timeout = serverConfig.timeout as number;\n }\n shttpServers.push(server);\n }\n } else {\n const stdioServer: MCPStdioServer = {\n name: serverName,\n command: serverConfig.command as string,\n };\n if (serverConfig.args) {\n stdioServer.args = serverConfig.args as string[];\n }\n if (serverConfig.env) {\n stdioServer.env = serverConfig.env as Record<string, string>;\n }\n stdioServers.push(stdioServer);\n }\n }\n\n const normalizeUrl = (u: string) => u.replace(/\\/+$/, \"\");\n for (const migrated of migratedShttpServers) {\n const alreadyPresent = shttpServers.some((entry) => {\n const entryUrl = typeof entry === \"string\" ? entry : entry.url;\n return normalizeUrl(entryUrl) === normalizeUrl(migrated.url);\n });\n if (!alreadyPresent) shttpServers.push(migrated);\n }\n\n return {\n sse_servers: sseServers,\n stdio_servers: stdioServers,\n shttp_servers: shttpServers,\n };\n}\n\n/**\n * Convert the frontend MCPConfig format back to the SDK { mcpServers: { ... } }\n * shape expected by agent_settings.mcp_config on the backend.\n *\n * Names are only suffixed (``_1``, ``_2``, …) when an earlier entry has\n * already claimed the bare base name. We intentionally do NOT use a single\n * monotonic counter across server types: that would, for example, rename a\n * stdio server \"myname\" to \"myname_1\" the moment any sse/shttp entry is\n * persisted ahead of it, and shift the suffix on every save as the count\n * of other server types changes. With per-base collision suffixing,\n * unrelated entries keep their human-meaningful names stable across edits.\n */\nexport function toSdkMcpConfig(config: MCPConfig): SdkMcpConfig | null {\n const mcpServers: Record<string, SdkMcpServerConfig> = {};\n\n const reserve = (base: string): string => {\n if (!(base in mcpServers)) return base;\n let i = 1;\n while (`${base}_${i}` in mcpServers) i += 1;\n return `${base}_${i}`;\n };\n\n for (const entry of config.sse_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n }\n server.transport = \"sse\";\n mcpServers[reserve(\"sse\")] = server;\n }\n\n for (const entry of config.shttp_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n if (entry.timeout != null) server.timeout = entry.timeout;\n }\n mcpServers[reserve(\"shttp\")] = server;\n }\n\n for (const entry of config.stdio_servers) {\n const server: SdkMcpServerConfig = {\n command: entry.command,\n };\n if (entry.args) server.args = entry.args;\n if (entry.env) server.env = entry.env;\n mcpServers[reserve(entry.name || \"stdio\")] = server;\n }\n\n return Object.keys(mcpServers).length > 0 ? { mcpServers } : null;\n}\n"],"mappings":"6CAQA,IAAM,EAA8B,CAClC,YAAa,EAAE,CACf,cAAe,EAAE,CACjB,cAAe,EAAE,CAClB,CAEK,EAA4B,6BAC5B,EAAmB,6BASzB,SAAS,EACP,EACA,EACS,CAGT,OAFI,IAAc,MACC,EAAI,MAAM,IAAI,CAAC,GAAG,QAAQ,OAAQ,GAC9C,GAAe,EAFU,GAYlC,SAAgB,EAAe,EAA2B,CACxD,GAAI,CAAC,GAAS,OAAO,GAAU,SAC7B,MAAO,CAAE,GAAG,EAAkB,CAGhC,IAAM,EAAM,EAEZ,GACE,EAAE,eAAgB,IAClB,CAAC,EAAI,YACL,OAAO,EAAI,YAAe,SAE1B,MAAO,CAAE,GAAG,EAAkB,CAGhC,IAAM,EAAwC,EAAE,CAC1C,EAAiC,EAAE,CACnC,EAA4C,EAAE,CAI9C,EAAyC,EAAE,CAE3C,EAAa,EAAI,WAEvB,IAAK,GAAM,CAAC,EAAY,KAAiB,OAAO,QAAQ,EAAW,CAAE,CACnE,GAAI,CAAC,GAAgB,OAAO,GAAiB,SAAU,SAEvD,IAAM,EAAM,EAAa,IAEzB,GAAI,EAAK,CACP,IAAM,EAAY,EAAa,UACzB,EAAO,EAAa,KACpB,EACJ,OAAO,GAAS,UAAY,IAAS,QAAU,EAAO,IAAA,GAExD,GAAI,EAAsB,EAAK,EAAU,CAAE,CACzC,IAAM,EAAyB,CAAE,IAAK,EAAkB,CACpD,IAAQ,EAAO,QAAU,GAC7B,EAAqB,KAAK,EAAO,SACxB,IAAc,MAAO,CAC9B,IAAM,EAAuB,CAAE,MAAK,CAChC,IAAQ,EAAO,QAAU,GAC7B,EAAW,KAAK,EAAO,KAClB,CACL,IAAM,EAAyB,CAAE,MAAK,CAClC,IAAQ,EAAO,QAAU,GACzB,EAAa,SAAW,OAC1B,EAAO,QAAU,EAAa,SAEhC,EAAa,KAAK,EAAO,MAEtB,CACL,IAAM,EAA8B,CAClC,KAAM,EACN,QAAS,EAAa,QACvB,CACG,EAAa,OACf,EAAY,KAAO,EAAa,MAE9B,EAAa,MACf,EAAY,IAAM,EAAa,KAEjC,EAAa,KAAK,EAAY,EAIlC,IAAM,EAAgB,GAAc,EAAE,QAAQ,OAAQ,GAAG,CACzD,IAAK,IAAM,KAAY,EACE,EAAa,KAAM,GAEjC,EADU,OAAO,GAAU,SAAW,EAAQ,EAAM,IAC9B,GAAK,EAAa,EAAS,IAAI,CAEzD,EAAgB,EAAa,KAAK,EAAS,CAGlD,MAAO,CACL,YAAa,EACb,cAAe,EACf,cAAe,EAChB,CAeH,SAAgB,EAAe,EAAwC,CACrE,IAAM,EAAiD,EAAE,CAEnD,EAAW,GAAyB,CACxC,GAAI,EAAE,KAAQ,GAAa,OAAO,EAClC,IAAI,EAAI,EACR,KAAO,GAAG,EAAK,GAAG,MAAO,GAAY,GAAK,EAC1C,MAAO,GAAG,EAAK,GAAG,KAGpB,IAAK,IAAM,KAAS,EAAO,YAAa,CACtC,IAAM,EAA6B,EAAE,CACjC,OAAO,GAAU,SACnB,EAAO,IAAM,GAEb,EAAO,IAAM,EAAM,IACf,EAAM,UAAS,EAAO,KAAO,EAAM,UAEzC,EAAO,UAAY,MACnB,EAAW,EAAQ,MAAM,EAAI,EAG/B,IAAK,IAAM,KAAS,EAAO,cAAe,CACxC,IAAM,EAA6B,EAAE,CACjC,OAAO,GAAU,SACnB,EAAO,IAAM,GAEb,EAAO,IAAM,EAAM,IACf,EAAM,UAAS,EAAO,KAAO,EAAM,SACnC,EAAM,SAAW,OAAM,EAAO,QAAU,EAAM,UAEpD,EAAW,EAAQ,QAAQ,EAAI,EAGjC,IAAK,IAAM,KAAS,EAAO,cAAe,CACxC,IAAM,EAA6B,CACjC,QAAS,EAAM,QAChB,CACG,EAAM,OAAM,EAAO,KAAO,EAAM,MAChC,EAAM,MAAK,EAAO,IAAM,EAAM,KAClC,EAAW,EAAQ,EAAM,MAAQ,QAAQ,EAAI,EAG/C,OAAO,OAAO,KAAK,EAAW,CAAC,OAAS,EAAI,CAAE,aAAY,CAAG"}
|
package/dist/utils/mcp-config.js
CHANGED
|
@@ -3,39 +3,47 @@ var e = {
|
|
|
3
3
|
sse_servers: [],
|
|
4
4
|
stdio_servers: [],
|
|
5
5
|
shttp_servers: []
|
|
6
|
-
};
|
|
7
|
-
function
|
|
6
|
+
}, t = "https://mcp.linear.app/sse", n = "https://mcp.linear.app/mcp";
|
|
7
|
+
function r(e, n) {
|
|
8
|
+
return n === "sse" ? e.split("?")[0].replace(/\/+$/, "") === t : !1;
|
|
9
|
+
}
|
|
10
|
+
function i(t) {
|
|
8
11
|
if (!t || typeof t != "object") return { ...e };
|
|
9
|
-
let
|
|
10
|
-
if (!("mcpServers" in
|
|
11
|
-
let
|
|
12
|
-
for (let [e, t] of Object.entries(
|
|
12
|
+
let i = t;
|
|
13
|
+
if (!("mcpServers" in i) || !i.mcpServers || typeof i.mcpServers != "object") return { ...e };
|
|
14
|
+
let a = [], o = [], s = [], c = [], l = i.mcpServers;
|
|
15
|
+
for (let [e, t] of Object.entries(l)) {
|
|
13
16
|
if (!t || typeof t != "object") continue;
|
|
14
|
-
let
|
|
15
|
-
if (
|
|
16
|
-
let e = t.transport,
|
|
17
|
-
if (e
|
|
17
|
+
let i = t.url;
|
|
18
|
+
if (i) {
|
|
19
|
+
let e = t.transport, o = t.auth, l = typeof o == "string" && o !== "oauth" ? o : void 0;
|
|
20
|
+
if (r(i, e)) {
|
|
18
21
|
let e = { url: n };
|
|
19
|
-
|
|
22
|
+
l && (e.api_key = l), c.push(e);
|
|
23
|
+
} else if (e === "sse") {
|
|
24
|
+
let e = { url: i };
|
|
25
|
+
l && (e.api_key = l), a.push(e);
|
|
20
26
|
} else {
|
|
21
|
-
let e = { url:
|
|
22
|
-
|
|
27
|
+
let e = { url: i };
|
|
28
|
+
l && (e.api_key = l), t.timeout != null && (e.timeout = t.timeout), s.push(e);
|
|
23
29
|
}
|
|
24
30
|
} else {
|
|
25
31
|
let n = {
|
|
26
32
|
name: e,
|
|
27
33
|
command: t.command
|
|
28
34
|
};
|
|
29
|
-
t.args && (n.args = t.args), t.env && (n.env = t.env),
|
|
35
|
+
t.args && (n.args = t.args), t.env && (n.env = t.env), o.push(n);
|
|
30
36
|
}
|
|
31
37
|
}
|
|
38
|
+
let u = (e) => e.replace(/\/+$/, "");
|
|
39
|
+
for (let e of c) s.some((t) => u(typeof t == "string" ? t : t.url) === u(e.url)) || s.push(e);
|
|
32
40
|
return {
|
|
33
|
-
sse_servers:
|
|
34
|
-
stdio_servers:
|
|
35
|
-
shttp_servers:
|
|
41
|
+
sse_servers: a,
|
|
42
|
+
stdio_servers: o,
|
|
43
|
+
shttp_servers: s
|
|
36
44
|
};
|
|
37
45
|
}
|
|
38
|
-
function
|
|
46
|
+
function a(e) {
|
|
39
47
|
let t = {}, n = (e) => {
|
|
40
48
|
if (!(e in t)) return e;
|
|
41
49
|
let n = 1;
|
|
@@ -57,6 +65,6 @@ function n(e) {
|
|
|
57
65
|
return Object.keys(t).length > 0 ? { mcpServers: t } : null;
|
|
58
66
|
}
|
|
59
67
|
//#endregion
|
|
60
|
-
export {
|
|
68
|
+
export { i as parseMcpConfig, a as toSdkMcpConfig };
|
|
61
69
|
|
|
62
70
|
//# sourceMappingURL=mcp-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-config.js","names":[],"sources":["../../src/utils/mcp-config.ts"],"sourcesContent":["import {\n MCPConfig,\n MCPSSEServer,\n MCPSHTTPServer,\n MCPStdioServer,\n SettingsValue,\n} from \"#/types/settings\";\n\nconst EMPTY_MCP_CONFIG: MCPConfig = {\n sse_servers: [],\n stdio_servers: [],\n shttp_servers: [],\n};\n\ntype SdkMcpServerConfig = Record<string, SettingsValue>;\ntype SdkMcpConfig = { mcpServers: Record<string, SdkMcpServerConfig> };\n\n/**\n * Parse an SDK mcp_config value ({ mcpServers: { ... } }) and convert it\n * to the frontend MCPConfig format used by UI components.\n */\nexport function parseMcpConfig(value: unknown): MCPConfig {\n if (!value || typeof value !== \"object\") {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const obj = value as Record<string, unknown>;\n\n if (\n !(\"mcpServers\" in obj) ||\n !obj.mcpServers ||\n typeof obj.mcpServers !== \"object\"\n ) {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const sseServers: (string | MCPSSEServer)[] = [];\n const stdioServers: MCPStdioServer[] = [];\n const shttpServers: (string | MCPSHTTPServer)[] = [];\n\n const mcpServers = obj.mcpServers as Record<string, Record<string, unknown>>;\n\n for (const [serverName, serverConfig] of Object.entries(mcpServers)) {\n if (!serverConfig || typeof serverConfig !== \"object\") continue;\n\n const url = serverConfig.url as string | undefined;\n\n if (url) {\n const transport = serverConfig.transport as string | undefined;\n const auth = serverConfig.auth as string | undefined;\n const apiKey =\n typeof auth === \"string\" && auth !== \"oauth\" ? auth : undefined;\n\n if (transport === \"sse\") {\n const server: MCPSSEServer = { url };\n if (apiKey) server.api_key = apiKey;\n sseServers.push(server);\n } else {\n const server: MCPSHTTPServer = { url };\n if (apiKey) server.api_key = apiKey;\n if (serverConfig.timeout != null) {\n server.timeout = serverConfig.timeout as number;\n }\n shttpServers.push(server);\n }\n } else {\n const stdioServer: MCPStdioServer = {\n name: serverName,\n command: serverConfig.command as string,\n };\n if (serverConfig.args) {\n stdioServer.args = serverConfig.args as string[];\n }\n if (serverConfig.env) {\n stdioServer.env = serverConfig.env as Record<string, string>;\n }\n stdioServers.push(stdioServer);\n }\n }\n\n return {\n sse_servers: sseServers,\n stdio_servers: stdioServers,\n shttp_servers: shttpServers,\n };\n}\n\n/**\n * Convert the frontend MCPConfig format back to the SDK { mcpServers: { ... } }\n * shape expected by agent_settings.mcp_config on the backend.\n *\n * Names are only suffixed (``_1``, ``_2``, …) when an earlier entry has\n * already claimed the bare base name. We intentionally do NOT use a single\n * monotonic counter across server types: that would, for example, rename a\n * stdio server \"myname\" to \"myname_1\" the moment any sse/shttp entry is\n * persisted ahead of it, and shift the suffix on every save as the count\n * of other server types changes. With per-base collision suffixing,\n * unrelated entries keep their human-meaningful names stable across edits.\n */\nexport function toSdkMcpConfig(config: MCPConfig): SdkMcpConfig | null {\n const mcpServers: Record<string, SdkMcpServerConfig> = {};\n\n const reserve = (base: string): string => {\n if (!(base in mcpServers)) return base;\n let i = 1;\n while (`${base}_${i}` in mcpServers) i += 1;\n return `${base}_${i}`;\n };\n\n for (const entry of config.sse_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n }\n server.transport = \"sse\";\n mcpServers[reserve(\"sse\")] = server;\n }\n\n for (const entry of config.shttp_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n if (entry.timeout != null) server.timeout = entry.timeout;\n }\n mcpServers[reserve(\"shttp\")] = server;\n }\n\n for (const entry of config.stdio_servers) {\n const server: SdkMcpServerConfig = {\n command: entry.command,\n };\n if (entry.args) server.args = entry.args;\n if (entry.env) server.env = entry.env;\n mcpServers[reserve(entry.name || \"stdio\")] = server;\n }\n\n return Object.keys(mcpServers).length > 0 ? { mcpServers } : null;\n}\n"],"mappings":";AAQA,IAAM,IAA8B;CAClC,aAAa,EAAE;CACf,eAAe,EAAE;CACjB,eAAe,EAAE;CAClB;
|
|
1
|
+
{"version":3,"file":"mcp-config.js","names":[],"sources":["../../src/utils/mcp-config.ts"],"sourcesContent":["import {\n MCPConfig,\n MCPSSEServer,\n MCPSHTTPServer,\n MCPStdioServer,\n SettingsValue,\n} from \"#/types/settings\";\n\nconst EMPTY_MCP_CONFIG: MCPConfig = {\n sse_servers: [],\n stdio_servers: [],\n shttp_servers: [],\n};\n\nconst LINEAR_DEPRECATED_SSE_URL = \"https://mcp.linear.app/sse\";\nconst LINEAR_SHTTP_URL = \"https://mcp.linear.app/mcp\";\n\n/**\n * Linear removed its MCP SSE transport (the /sse endpoint rejects every\n * call since 2026-04-08). Detect persisted configs that still point at\n * the dead endpoint so they can be migrated to streamable HTTP at the\n * /mcp replacement. Matches only the exact deprecated URL (tolerating a\n * trailing slash or query string) — nothing else is rewritten.\n */\nfunction isDeprecatedLinearSse(\n url: string,\n transport: string | undefined,\n): boolean {\n if (transport !== \"sse\") return false;\n const normalized = url.split(\"?\")[0].replace(/\\/+$/, \"\");\n return normalized === LINEAR_DEPRECATED_SSE_URL;\n}\n\ntype SdkMcpServerConfig = Record<string, SettingsValue>;\ntype SdkMcpConfig = { mcpServers: Record<string, SdkMcpServerConfig> };\n\n/**\n * Parse an SDK mcp_config value ({ mcpServers: { ... } }) and convert it\n * to the frontend MCPConfig format used by UI components.\n */\nexport function parseMcpConfig(value: unknown): MCPConfig {\n if (!value || typeof value !== \"object\") {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const obj = value as Record<string, unknown>;\n\n if (\n !(\"mcpServers\" in obj) ||\n !obj.mcpServers ||\n typeof obj.mcpServers !== \"object\"\n ) {\n return { ...EMPTY_MCP_CONFIG };\n }\n\n const sseServers: (string | MCPSSEServer)[] = [];\n const stdioServers: MCPStdioServer[] = [];\n const shttpServers: (string | MCPSHTTPServer)[] = [];\n // Legacy Linear SSE entries rewritten to the /mcp endpoint. Collected\n // separately and merged after the loop so an existing hand-added /mcp\n // entry (with its own api_key/timeout) wins over the migrated one.\n const migratedShttpServers: MCPSHTTPServer[] = [];\n\n const mcpServers = obj.mcpServers as Record<string, Record<string, unknown>>;\n\n for (const [serverName, serverConfig] of Object.entries(mcpServers)) {\n if (!serverConfig || typeof serverConfig !== \"object\") continue;\n\n const url = serverConfig.url as string | undefined;\n\n if (url) {\n const transport = serverConfig.transport as string | undefined;\n const auth = serverConfig.auth as string | undefined;\n const apiKey =\n typeof auth === \"string\" && auth !== \"oauth\" ? auth : undefined;\n\n if (isDeprecatedLinearSse(url, transport)) {\n const server: MCPSHTTPServer = { url: LINEAR_SHTTP_URL };\n if (apiKey) server.api_key = apiKey;\n migratedShttpServers.push(server);\n } else if (transport === \"sse\") {\n const server: MCPSSEServer = { url };\n if (apiKey) server.api_key = apiKey;\n sseServers.push(server);\n } else {\n const server: MCPSHTTPServer = { url };\n if (apiKey) server.api_key = apiKey;\n if (serverConfig.timeout != null) {\n server.timeout = serverConfig.timeout as number;\n }\n shttpServers.push(server);\n }\n } else {\n const stdioServer: MCPStdioServer = {\n name: serverName,\n command: serverConfig.command as string,\n };\n if (serverConfig.args) {\n stdioServer.args = serverConfig.args as string[];\n }\n if (serverConfig.env) {\n stdioServer.env = serverConfig.env as Record<string, string>;\n }\n stdioServers.push(stdioServer);\n }\n }\n\n const normalizeUrl = (u: string) => u.replace(/\\/+$/, \"\");\n for (const migrated of migratedShttpServers) {\n const alreadyPresent = shttpServers.some((entry) => {\n const entryUrl = typeof entry === \"string\" ? entry : entry.url;\n return normalizeUrl(entryUrl) === normalizeUrl(migrated.url);\n });\n if (!alreadyPresent) shttpServers.push(migrated);\n }\n\n return {\n sse_servers: sseServers,\n stdio_servers: stdioServers,\n shttp_servers: shttpServers,\n };\n}\n\n/**\n * Convert the frontend MCPConfig format back to the SDK { mcpServers: { ... } }\n * shape expected by agent_settings.mcp_config on the backend.\n *\n * Names are only suffixed (``_1``, ``_2``, …) when an earlier entry has\n * already claimed the bare base name. We intentionally do NOT use a single\n * monotonic counter across server types: that would, for example, rename a\n * stdio server \"myname\" to \"myname_1\" the moment any sse/shttp entry is\n * persisted ahead of it, and shift the suffix on every save as the count\n * of other server types changes. With per-base collision suffixing,\n * unrelated entries keep their human-meaningful names stable across edits.\n */\nexport function toSdkMcpConfig(config: MCPConfig): SdkMcpConfig | null {\n const mcpServers: Record<string, SdkMcpServerConfig> = {};\n\n const reserve = (base: string): string => {\n if (!(base in mcpServers)) return base;\n let i = 1;\n while (`${base}_${i}` in mcpServers) i += 1;\n return `${base}_${i}`;\n };\n\n for (const entry of config.sse_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n }\n server.transport = \"sse\";\n mcpServers[reserve(\"sse\")] = server;\n }\n\n for (const entry of config.shttp_servers) {\n const server: SdkMcpServerConfig = {};\n if (typeof entry === \"string\") {\n server.url = entry;\n } else {\n server.url = entry.url;\n if (entry.api_key) server.auth = entry.api_key;\n if (entry.timeout != null) server.timeout = entry.timeout;\n }\n mcpServers[reserve(\"shttp\")] = server;\n }\n\n for (const entry of config.stdio_servers) {\n const server: SdkMcpServerConfig = {\n command: entry.command,\n };\n if (entry.args) server.args = entry.args;\n if (entry.env) server.env = entry.env;\n mcpServers[reserve(entry.name || \"stdio\")] = server;\n }\n\n return Object.keys(mcpServers).length > 0 ? { mcpServers } : null;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);function e(e){return e.connectionOptions.filter(e=>e.provider===`mcp`&&!!e.transport)}function t(t){let n=e(t);return n.find(e=>e.id===t.defaultConnectionOptionId)??n[0]}function n(e){return e.auth.strategy!==`oauth2`}function r(t){let r=e(t),i=r.find(e=>e.id===t.defaultConnectionOptionId);return i&&n(i)?i:r.find(n)}function i(e){return t(e)?.transport}function a(e){return e.filter(e=>!!t(e))}var
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);function e(e){return e.connectionOptions.filter(e=>e.provider===`mcp`&&!!e.transport)}function t(t){let n=e(t);return n.find(e=>e.id===t.defaultConnectionOptionId)??n[0]}function n(e){return e.auth.strategy!==`oauth2`}function r(t){let r=e(t),i=r.find(e=>e.id===t.defaultConnectionOptionId);return i&&n(i)?i:r.find(n)}function i(e){return t(e)?.transport}var a=`https://mcp.linear.app/sse`,o=`https://mcp.linear.app/mcp`,s=`https://linear.app/docs/mcp`;function c(e){return e.id===`linear`?{...e,docsUrl:s,installHint:`Authenticate with a Linear API key (Linear → Settings → Security & access) — sent as a Bearer token. Optional when the endpoint accepts your OAuth session.`,connectionOptions:e.connectionOptions.map(e=>e.transport?.kind===`sse`&&d(e.transport.url,a)?{...e,auth:{...e.auth,strategy:`bearer`},transport:{kind:`shttp`,url:o,apiKeyOptional:e.transport.apiKeyOptional}}:e)}:e}function l(e){return e.map(c).filter(e=>!!t(e))}var u=e=>{try{return new URL(e)}catch{return null}};function d(e,t){let n=typeof e==`string`?e:``,r=typeof t==`string`?t:``;if(!n||!r)return!1;let i=u(n),a=u(r);return!i||!a?n.replace(/\/+$/,``)===r.replace(/\/+$/,``):i.protocol===a.protocol&&i.host===a.host&&i.pathname.replace(/\/+$/,``)===a.pathname.replace(/\/+$/,``)}function f(e,t){if(e.kind===`shttp`){let n=e.url;return t.type===`shttp`&&!!t.url&&d(t.url,n)}if(e.kind===`sse`){let n=e.url;return t.type===`sse`&&!!t.url&&d(t.url,n)}return t.type===`stdio`&&t.name===e.serverName}function p(e,t){return!e.runtimeAvailability||e.runtimeAvailability===`all`?!0:e.runtimeAvailability===t}function m(e){return e.trim().toLowerCase()}function h(e){return e.map((e,t)=>({entry:e,index:t})).sort((e,t)=>(t.entry.popularityRank??0)-(e.entry.popularityRank??0)||e.index-t.index).map(({entry:e})=>e)}function g(e,t){let n=m(t);return n?[e.name,e.description,e.id,...e.keywords??[]].join(` `).toLowerCase().includes(n):!0}function _(e,t,n){let r=m(n);return r?[e.type,`name`in e?e.name:void 0,`command`in e?e.command:void 0,`args`in e?e.args?.join(` `):void 0,`url`in e?e.url:void 0,t?.name,t?.description,t?.id,...t?.keywords??[]].filter(Boolean).join(` `).toLowerCase().includes(r):!0}function v(t,n){return n.find(n=>e(n).some(e=>f(e.transport,t)))}exports.findCatalogEntryForServer=v,exports.getDefaultMcpTransport=i,exports.getInstallableMcpConnectionOption=r,exports.getMarketplaceEntriesByPopularity=h,exports.getMcpMarketplaceCatalog=l,exports.installedServerMatchesQuery=_,exports.isMarketplaceEntryAvailable=p,exports.marketplaceEntryMatchesQuery=g;
|
|
2
2
|
//# sourceMappingURL=mcp-marketplace-utils.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-marketplace-utils.cjs","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationConnectionOption,\n IntegrationTransport,\n} from \"@openhands/extensions/integrations\";\n\nexport type { MarketplaceEntry };\n\nexport type McpMarketplaceConnectionOption = IntegrationConnectionOption & {\n provider: \"mcp\";\n transport: IntegrationTransport;\n};\n\nexport function getMcpConnectionOptions(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption[] {\n return entry.connectionOptions.filter(\n (option): option is McpMarketplaceConnectionOption =>\n option.provider === \"mcp\" && !!option.transport,\n );\n}\n\nexport function getDefaultMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n return (\n options.find((option) => option.id === entry.defaultConnectionOptionId) ??\n options[0]\n );\n}\n\nfunction isLocallyInstallableMcpOption(\n option: McpMarketplaceConnectionOption,\n): boolean {\n // The local install modal writes static MCP server config. OAuth options\n // describe hosted redirect flows, so prefer an API/stdio fallback when one\n // exists and leave OAuth as the default connection for hosted integrations.\n return option.auth.strategy !== \"oauth2\";\n}\n\nexport function getInstallableMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n const defaultOption = options.find(\n (option) => option.id === entry.defaultConnectionOptionId,\n );\n if (defaultOption && isLocallyInstallableMcpOption(defaultOption)) {\n return defaultOption;\n }\n return options.find(isLocallyInstallableMcpOption);\n}\n\nexport function getDefaultMcpTransport(\n entry: MarketplaceEntry,\n): IntegrationTransport | undefined {\n return getDefaultMcpConnectionOption(entry)?.transport;\n}\n\nexport function getMcpMarketplaceCatalog(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog.filter((entry) => !!getDefaultMcpConnectionOption(entry));\n}\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n transport: IntegrationTransport,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n return (\n servers.find((server) => transportMatchesServer(transport, server)) ?? null\n );\n}\n\nexport function findInstalledEntryMatch(\n entry: MarketplaceEntry,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n for (const option of getMcpConnectionOptions(entry)) {\n const match = findInstalledMatch(option.transport, servers);\n if (match) return match;\n }\n return null;\n}\n\nfunction transportMatchesServer(\n transport: IntegrationTransport,\n server: MCPServerConfig,\n): boolean {\n if (transport.kind === \"shttp\") {\n const tplUrl = transport.url;\n return (\n server.type === \"shttp\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n if (transport.kind === \"sse\") {\n const tplUrl = transport.url;\n return (\n server.type === \"sse\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n // stdio: match on the registered server name.\n return server.type === \"stdio\" && server.name === transport.serverName;\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check every MCP option rather than only the default. Some unified\n // integration entries default to OAuth-hosted MCP while still exposing\n // an API/stdio option; existing installed servers should match either.\n return getMcpConnectionOptions(entry).some((option) =>\n transportMatchesServer(option.transport, server),\n );\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-marketplace-utils.cjs","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationConnectionOption,\n IntegrationTransport,\n} from \"@openhands/extensions/integrations\";\n\nexport type { MarketplaceEntry };\n\nexport type McpMarketplaceConnectionOption = IntegrationConnectionOption & {\n provider: \"mcp\";\n transport: IntegrationTransport;\n};\n\nexport function getMcpConnectionOptions(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption[] {\n return entry.connectionOptions.filter(\n (option): option is McpMarketplaceConnectionOption =>\n option.provider === \"mcp\" && !!option.transport,\n );\n}\n\nexport function getDefaultMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n return (\n options.find((option) => option.id === entry.defaultConnectionOptionId) ??\n options[0]\n );\n}\n\nfunction isLocallyInstallableMcpOption(\n option: McpMarketplaceConnectionOption,\n): boolean {\n // The local install modal writes static MCP server config. OAuth options\n // describe hosted redirect flows, so prefer an API/stdio fallback when one\n // exists and leave OAuth as the default connection for hosted integrations.\n return option.auth.strategy !== \"oauth2\";\n}\n\nexport function getInstallableMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n const defaultOption = options.find(\n (option) => option.id === entry.defaultConnectionOptionId,\n );\n if (defaultOption && isLocallyInstallableMcpOption(defaultOption)) {\n return defaultOption;\n }\n return options.find(isLocallyInstallableMcpOption);\n}\n\nexport function getDefaultMcpTransport(\n entry: MarketplaceEntry,\n): IntegrationTransport | undefined {\n return getDefaultMcpConnectionOption(entry)?.transport;\n}\n\nconst LINEAR_DEPRECATED_SSE_URL = \"https://mcp.linear.app/sse\";\nconst LINEAR_SHTTP_URL = \"https://mcp.linear.app/mcp\";\nconst LINEAR_DOCS_URL = \"https://linear.app/docs/mcp\";\n\n/**\n * Upstream @openhands/extensions still ships Linear's deprecated SSE\n * transport (removed upstream on 2026-04-08; the /sse endpoint now\n * rejects every call). Rewrite the entry to streamable HTTP at the\n * /mcp replacement endpoint until the pinned dependency catches up.\n *\n * The /mcp endpoint authenticates via OAuth 2.1 or a Linear API key\n * sent as \"Authorization: Bearer <token>\". This client has no\n * interactive OAuth flow for MCP installs, so switch the auth\n * strategy from \"none\" to \"bearer\" — the install modal then offers\n * an (optional) API key field and the agent server forwards it as a\n * Bearer header.\n *\n * Patches immutably — the imported catalog JSON is shared module\n * state and must not be mutated.\n */\nfunction patchLinearEntry(entry: MarketplaceEntry): MarketplaceEntry {\n if (entry.id !== \"linear\") return entry;\n return {\n ...entry,\n docsUrl: LINEAR_DOCS_URL,\n installHint:\n \"Authenticate with a Linear API key (Linear → Settings → Security & access) — sent as a Bearer token. Optional when the endpoint accepts your OAuth session.\",\n connectionOptions: entry.connectionOptions.map((option) =>\n option.transport?.kind === \"sse\" &&\n urlsMatch(option.transport.url, LINEAR_DEPRECATED_SSE_URL)\n ? {\n ...option,\n auth: { ...option.auth, strategy: \"bearer\" as const },\n transport: {\n kind: \"shttp\" as const,\n url: LINEAR_SHTTP_URL,\n apiKeyOptional: option.transport.apiKeyOptional,\n },\n }\n : option,\n ),\n };\n}\n\nexport function getMcpMarketplaceCatalog(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map(patchLinearEntry)\n .filter((entry) => !!getDefaultMcpConnectionOption(entry));\n}\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n transport: IntegrationTransport,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n return (\n servers.find((server) => transportMatchesServer(transport, server)) ?? null\n );\n}\n\nexport function findInstalledEntryMatch(\n entry: MarketplaceEntry,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n for (const option of getMcpConnectionOptions(entry)) {\n const match = findInstalledMatch(option.transport, servers);\n if (match) return match;\n }\n return null;\n}\n\nfunction transportMatchesServer(\n transport: IntegrationTransport,\n server: MCPServerConfig,\n): boolean {\n if (transport.kind === \"shttp\") {\n const tplUrl = transport.url;\n return (\n server.type === \"shttp\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n if (transport.kind === \"sse\") {\n const tplUrl = transport.url;\n return (\n server.type === \"sse\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n // stdio: match on the registered server name.\n return server.type === \"stdio\" && server.name === transport.serverName;\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check every MCP option rather than only the default. Some unified\n // integration entries default to OAuth-hosted MCP while still exposing\n // an API/stdio option; existing installed servers should match either.\n return getMcpConnectionOptions(entry).some((option) =>\n transportMatchesServer(option.transport, server),\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -16,47 +16,67 @@ function r(t) {
|
|
|
16
16
|
function i(e) {
|
|
17
17
|
return t(e)?.transport;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
var a = "https://mcp.linear.app/sse", o = "https://mcp.linear.app/mcp", s = "https://linear.app/docs/mcp";
|
|
20
|
+
function c(e) {
|
|
21
|
+
return e.id === "linear" ? {
|
|
22
|
+
...e,
|
|
23
|
+
docsUrl: s,
|
|
24
|
+
installHint: "Authenticate with a Linear API key (Linear → Settings → Security & access) — sent as a Bearer token. Optional when the endpoint accepts your OAuth session.",
|
|
25
|
+
connectionOptions: e.connectionOptions.map((e) => e.transport?.kind === "sse" && d(e.transport.url, a) ? {
|
|
26
|
+
...e,
|
|
27
|
+
auth: {
|
|
28
|
+
...e.auth,
|
|
29
|
+
strategy: "bearer"
|
|
30
|
+
},
|
|
31
|
+
transport: {
|
|
32
|
+
kind: "shttp",
|
|
33
|
+
url: o,
|
|
34
|
+
apiKeyOptional: e.transport.apiKeyOptional
|
|
35
|
+
}
|
|
36
|
+
} : e)
|
|
37
|
+
} : e;
|
|
21
38
|
}
|
|
22
|
-
|
|
39
|
+
function l(e) {
|
|
40
|
+
return e.map(c).filter((e) => !!t(e));
|
|
41
|
+
}
|
|
42
|
+
var u = (e) => {
|
|
23
43
|
try {
|
|
24
44
|
return new URL(e);
|
|
25
45
|
} catch {
|
|
26
46
|
return null;
|
|
27
47
|
}
|
|
28
48
|
};
|
|
29
|
-
function
|
|
49
|
+
function d(e, t) {
|
|
30
50
|
let n = typeof e == "string" ? e : "", r = typeof t == "string" ? t : "";
|
|
31
51
|
if (!n || !r) return !1;
|
|
32
|
-
let i =
|
|
52
|
+
let i = u(n), a = u(r);
|
|
33
53
|
return !i || !a ? n.replace(/\/+$/, "") === r.replace(/\/+$/, "") : i.protocol === a.protocol && i.host === a.host && i.pathname.replace(/\/+$/, "") === a.pathname.replace(/\/+$/, "");
|
|
34
54
|
}
|
|
35
|
-
function
|
|
55
|
+
function f(e, t) {
|
|
36
56
|
if (e.kind === "shttp") {
|
|
37
57
|
let n = e.url;
|
|
38
|
-
return t.type === "shttp" && !!t.url &&
|
|
58
|
+
return t.type === "shttp" && !!t.url && d(t.url, n);
|
|
39
59
|
}
|
|
40
60
|
if (e.kind === "sse") {
|
|
41
61
|
let n = e.url;
|
|
42
|
-
return t.type === "sse" && !!t.url &&
|
|
62
|
+
return t.type === "sse" && !!t.url && d(t.url, n);
|
|
43
63
|
}
|
|
44
64
|
return t.type === "stdio" && t.name === e.serverName;
|
|
45
65
|
}
|
|
46
|
-
function
|
|
66
|
+
function p(e, t) {
|
|
47
67
|
return !e.runtimeAvailability || e.runtimeAvailability === "all" ? !0 : e.runtimeAvailability === t;
|
|
48
68
|
}
|
|
49
|
-
function
|
|
69
|
+
function m(e) {
|
|
50
70
|
return e.trim().toLowerCase();
|
|
51
71
|
}
|
|
52
|
-
function
|
|
72
|
+
function h(e) {
|
|
53
73
|
return e.map((e, t) => ({
|
|
54
74
|
entry: e,
|
|
55
75
|
index: t
|
|
56
76
|
})).sort((e, t) => (t.entry.popularityRank ?? 0) - (e.entry.popularityRank ?? 0) || e.index - t.index).map(({ entry: e }) => e);
|
|
57
77
|
}
|
|
58
|
-
function
|
|
59
|
-
let n =
|
|
78
|
+
function g(e, t) {
|
|
79
|
+
let n = m(t);
|
|
60
80
|
return n ? [
|
|
61
81
|
e.name,
|
|
62
82
|
e.description,
|
|
@@ -64,8 +84,8 @@ function f(e, t) {
|
|
|
64
84
|
...e.keywords ?? []
|
|
65
85
|
].join(" ").toLowerCase().includes(n) : !0;
|
|
66
86
|
}
|
|
67
|
-
function
|
|
68
|
-
let r =
|
|
87
|
+
function _(e, t, n) {
|
|
88
|
+
let r = m(n);
|
|
69
89
|
return r ? [
|
|
70
90
|
e.type,
|
|
71
91
|
"name" in e ? e.name : void 0,
|
|
@@ -78,10 +98,10 @@ function p(e, t, n) {
|
|
|
78
98
|
...t?.keywords ?? []
|
|
79
99
|
].filter(Boolean).join(" ").toLowerCase().includes(r) : !0;
|
|
80
100
|
}
|
|
81
|
-
function
|
|
82
|
-
return n.find((n) => e(n).some((e) =>
|
|
101
|
+
function v(t, n) {
|
|
102
|
+
return n.find((n) => e(n).some((e) => f(e.transport, t)));
|
|
83
103
|
}
|
|
84
104
|
//#endregion
|
|
85
|
-
export {
|
|
105
|
+
export { v as findCatalogEntryForServer, i as getDefaultMcpTransport, r as getInstallableMcpConnectionOption, h as getMarketplaceEntriesByPopularity, l as getMcpMarketplaceCatalog, _ as installedServerMatchesQuery, p as isMarketplaceEntryAvailable, g as marketplaceEntryMatchesQuery };
|
|
86
106
|
|
|
87
107
|
//# sourceMappingURL=mcp-marketplace-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-marketplace-utils.js","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationConnectionOption,\n IntegrationTransport,\n} from \"@openhands/extensions/integrations\";\n\nexport type { MarketplaceEntry };\n\nexport type McpMarketplaceConnectionOption = IntegrationConnectionOption & {\n provider: \"mcp\";\n transport: IntegrationTransport;\n};\n\nexport function getMcpConnectionOptions(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption[] {\n return entry.connectionOptions.filter(\n (option): option is McpMarketplaceConnectionOption =>\n option.provider === \"mcp\" && !!option.transport,\n );\n}\n\nexport function getDefaultMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n return (\n options.find((option) => option.id === entry.defaultConnectionOptionId) ??\n options[0]\n );\n}\n\nfunction isLocallyInstallableMcpOption(\n option: McpMarketplaceConnectionOption,\n): boolean {\n // The local install modal writes static MCP server config. OAuth options\n // describe hosted redirect flows, so prefer an API/stdio fallback when one\n // exists and leave OAuth as the default connection for hosted integrations.\n return option.auth.strategy !== \"oauth2\";\n}\n\nexport function getInstallableMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n const defaultOption = options.find(\n (option) => option.id === entry.defaultConnectionOptionId,\n );\n if (defaultOption && isLocallyInstallableMcpOption(defaultOption)) {\n return defaultOption;\n }\n return options.find(isLocallyInstallableMcpOption);\n}\n\nexport function getDefaultMcpTransport(\n entry: MarketplaceEntry,\n): IntegrationTransport | undefined {\n return getDefaultMcpConnectionOption(entry)?.transport;\n}\n\nexport function getMcpMarketplaceCatalog(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog.filter((entry) => !!getDefaultMcpConnectionOption(entry));\n}\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n transport: IntegrationTransport,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n return (\n servers.find((server) => transportMatchesServer(transport, server)) ?? null\n );\n}\n\nexport function findInstalledEntryMatch(\n entry: MarketplaceEntry,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n for (const option of getMcpConnectionOptions(entry)) {\n const match = findInstalledMatch(option.transport, servers);\n if (match) return match;\n }\n return null;\n}\n\nfunction transportMatchesServer(\n transport: IntegrationTransport,\n server: MCPServerConfig,\n): boolean {\n if (transport.kind === \"shttp\") {\n const tplUrl = transport.url;\n return (\n server.type === \"shttp\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n if (transport.kind === \"sse\") {\n const tplUrl = transport.url;\n return (\n server.type === \"sse\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n // stdio: match on the registered server name.\n return server.type === \"stdio\" && server.name === transport.serverName;\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check every MCP option rather than only the default. Some unified\n // integration entries default to OAuth-hosted MCP while still exposing\n // an API/stdio option; existing installed servers should match either.\n return getMcpConnectionOptions(entry).some((option) =>\n transportMatchesServer(option.transport, server),\n );\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-marketplace-utils.js","names":[],"sources":["../../src/utils/mcp-marketplace-utils.ts"],"sourcesContent":["import { MCPServerConfig } from \"#/types/mcp-server\";\nimport type {\n IntegrationCatalogEntry as MarketplaceEntry,\n IntegrationConnectionOption,\n IntegrationTransport,\n} from \"@openhands/extensions/integrations\";\n\nexport type { MarketplaceEntry };\n\nexport type McpMarketplaceConnectionOption = IntegrationConnectionOption & {\n provider: \"mcp\";\n transport: IntegrationTransport;\n};\n\nexport function getMcpConnectionOptions(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption[] {\n return entry.connectionOptions.filter(\n (option): option is McpMarketplaceConnectionOption =>\n option.provider === \"mcp\" && !!option.transport,\n );\n}\n\nexport function getDefaultMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n return (\n options.find((option) => option.id === entry.defaultConnectionOptionId) ??\n options[0]\n );\n}\n\nfunction isLocallyInstallableMcpOption(\n option: McpMarketplaceConnectionOption,\n): boolean {\n // The local install modal writes static MCP server config. OAuth options\n // describe hosted redirect flows, so prefer an API/stdio fallback when one\n // exists and leave OAuth as the default connection for hosted integrations.\n return option.auth.strategy !== \"oauth2\";\n}\n\nexport function getInstallableMcpConnectionOption(\n entry: MarketplaceEntry,\n): McpMarketplaceConnectionOption | undefined {\n const options = getMcpConnectionOptions(entry);\n const defaultOption = options.find(\n (option) => option.id === entry.defaultConnectionOptionId,\n );\n if (defaultOption && isLocallyInstallableMcpOption(defaultOption)) {\n return defaultOption;\n }\n return options.find(isLocallyInstallableMcpOption);\n}\n\nexport function getDefaultMcpTransport(\n entry: MarketplaceEntry,\n): IntegrationTransport | undefined {\n return getDefaultMcpConnectionOption(entry)?.transport;\n}\n\nconst LINEAR_DEPRECATED_SSE_URL = \"https://mcp.linear.app/sse\";\nconst LINEAR_SHTTP_URL = \"https://mcp.linear.app/mcp\";\nconst LINEAR_DOCS_URL = \"https://linear.app/docs/mcp\";\n\n/**\n * Upstream @openhands/extensions still ships Linear's deprecated SSE\n * transport (removed upstream on 2026-04-08; the /sse endpoint now\n * rejects every call). Rewrite the entry to streamable HTTP at the\n * /mcp replacement endpoint until the pinned dependency catches up.\n *\n * The /mcp endpoint authenticates via OAuth 2.1 or a Linear API key\n * sent as \"Authorization: Bearer <token>\". This client has no\n * interactive OAuth flow for MCP installs, so switch the auth\n * strategy from \"none\" to \"bearer\" — the install modal then offers\n * an (optional) API key field and the agent server forwards it as a\n * Bearer header.\n *\n * Patches immutably — the imported catalog JSON is shared module\n * state and must not be mutated.\n */\nfunction patchLinearEntry(entry: MarketplaceEntry): MarketplaceEntry {\n if (entry.id !== \"linear\") return entry;\n return {\n ...entry,\n docsUrl: LINEAR_DOCS_URL,\n installHint:\n \"Authenticate with a Linear API key (Linear → Settings → Security & access) — sent as a Bearer token. Optional when the endpoint accepts your OAuth session.\",\n connectionOptions: entry.connectionOptions.map((option) =>\n option.transport?.kind === \"sse\" &&\n urlsMatch(option.transport.url, LINEAR_DEPRECATED_SSE_URL)\n ? {\n ...option,\n auth: { ...option.auth, strategy: \"bearer\" as const },\n transport: {\n kind: \"shttp\" as const,\n url: LINEAR_SHTTP_URL,\n apiKeyOptional: option.transport.apiKeyOptional,\n },\n }\n : option,\n ),\n };\n}\n\nexport function getMcpMarketplaceCatalog(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map(patchLinearEntry)\n .filter((entry) => !!getDefaultMcpConnectionOption(entry));\n}\n\nconst tryUrl = (raw: string): URL | null => {\n try {\n return new URL(raw);\n } catch {\n return null;\n }\n};\n\n/**\n * Loose URL match that ignores query strings, trailing slashes, and\n * default ports. We want clicking \"Linear\" to flag the entry as\n * installed even if the user pasted the URL with extra trailing slash\n * or a different port-equivalent variant.\n *\n * Defensive against runtime data that doesn't match the static type:\n * if either input is not a string (e.g. parsed from an older settings\n * blob), we fall through the URL parsing path and the safe trim\n * fallback below, never calling `.replace` on undefined.\n */\nexport function urlsMatch(a: unknown, b: unknown): boolean {\n const aStr = typeof a === \"string\" ? a : \"\";\n const bStr = typeof b === \"string\" ? b : \"\";\n if (!aStr || !bStr) return false;\n const left = tryUrl(aStr);\n const right = tryUrl(bStr);\n if (!left || !right) {\n return aStr.replace(/\\/+$/, \"\") === bStr.replace(/\\/+$/, \"\");\n }\n return (\n left.protocol === right.protocol &&\n left.host === right.host &&\n left.pathname.replace(/\\/+$/, \"\") === right.pathname.replace(/\\/+$/, \"\")\n );\n}\n\n/**\n * Decide whether a marketplace template is already represented by one\n * of the installed MCP servers. Used to render an \"Installed\" badge on\n * the marketplace tile. Returns the first matching server, or null.\n */\nexport function findInstalledMatch(\n transport: IntegrationTransport,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n return (\n servers.find((server) => transportMatchesServer(transport, server)) ?? null\n );\n}\n\nexport function findInstalledEntryMatch(\n entry: MarketplaceEntry,\n servers: MCPServerConfig[],\n): MCPServerConfig | null {\n for (const option of getMcpConnectionOptions(entry)) {\n const match = findInstalledMatch(option.transport, servers);\n if (match) return match;\n }\n return null;\n}\n\nfunction transportMatchesServer(\n transport: IntegrationTransport,\n server: MCPServerConfig,\n): boolean {\n if (transport.kind === \"shttp\") {\n const tplUrl = transport.url;\n return (\n server.type === \"shttp\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n if (transport.kind === \"sse\") {\n const tplUrl = transport.url;\n return (\n server.type === \"sse\" && !!server.url && urlsMatch(server.url, tplUrl)\n );\n }\n\n // stdio: match on the registered server name.\n return server.type === \"stdio\" && server.name === transport.serverName;\n}\n\nexport function isMarketplaceEntryAvailable(\n entry: MarketplaceEntry,\n backendKind: \"local\" | \"cloud\",\n): boolean {\n if (!entry.runtimeAvailability || entry.runtimeAvailability === \"all\")\n return true;\n return entry.runtimeAvailability === backendKind;\n}\n\nfunction normalize(query: string): string {\n return query.trim().toLowerCase();\n}\n\n/**\n * Case-insensitive substring match against the catalog entry's\n * user-visible identity (name, description, id, keywords). Empty\n * queries always match.\n */\nexport function getMarketplaceEntriesByPopularity(\n catalog: MarketplaceEntry[],\n): MarketplaceEntry[] {\n return catalog\n .map((entry, index) => ({ entry, index }))\n .sort((a, b) => {\n const byPopularity =\n (b.entry.popularityRank ?? 0) - (a.entry.popularityRank ?? 0);\n return byPopularity || a.index - b.index;\n })\n .map(({ entry }) => entry);\n}\n\nexport function getMarketplaceEntryById(\n id: string,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => entry.id === id);\n}\n\nexport function marketplaceEntryMatchesQuery(\n entry: MarketplaceEntry,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n entry.name,\n entry.description,\n entry.id,\n ...(entry.keywords ?? []),\n ]\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Search match for an installed (already-configured) server. We\n * search the server's own identifying fields and — if it's a catalog\n * entry — its catalog name/keywords too, so typing \"Slack\" matches\n * the installed Slack tile even though the persisted server is just\n * `{ type: \"stdio\", name: \"slack\", ... }`.\n */\nexport function installedServerMatchesQuery(\n server: MCPServerConfig,\n catalogEntry: MarketplaceEntry | undefined,\n rawQuery: string,\n): boolean {\n const q = normalize(rawQuery);\n if (!q) return true;\n const haystack = [\n server.type,\n \"name\" in server ? server.name : undefined,\n \"command\" in server ? server.command : undefined,\n \"args\" in server ? server.args?.join(\" \") : undefined,\n \"url\" in server ? server.url : undefined,\n catalogEntry?.name,\n catalogEntry?.description,\n catalogEntry?.id,\n ...(catalogEntry?.keywords ?? []),\n ]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n return haystack.includes(q);\n}\n\n/**\n * Look up the catalog entry that best matches an installed server.\n * Mirrors the lookup used in `installed-server-card.tsx` for\n * rendering the friendly icon.\n */\nexport function findCatalogEntryForServer(\n server: MCPServerConfig,\n catalog: MarketplaceEntry[],\n): MarketplaceEntry | undefined {\n return catalog.find((entry) => {\n // Check every MCP option rather than only the default. Some unified\n // integration entries default to OAuth-hosted MCP while still exposing\n // an API/stdio option; existing installed servers should match either.\n return getMcpConnectionOptions(entry).some((option) =>\n transportMatchesServer(option.transport, server),\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);const e=require(`./openhands-llm.cjs`);var t=`litellm_proxy/`;function n(n,r,i){if(!n)return``;if(!n.startsWith(t)||!e.isOpenHandsProxyBaseUrl(r))return n;let a=n.slice(14);return i.includes(a)?`openhands/${a}`:n}exports.normalizeDisplayModel=n;
|
|
2
2
|
//# sourceMappingURL=normalize-display-model.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-display-model.cjs","names":[],"sources":["../../src/utils/normalize-display-model.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"normalize-display-model.cjs","names":[],"sources":["../../src/utils/normalize-display-model.ts"],"sourcesContent":["import { isOpenHandsProxyBaseUrl } from \"#/utils/openhands-llm\";\n\nconst LITELLM_PROXY_PREFIX = \"litellm_proxy/\";\n\n/**\n * Reverse the SDK's `openhands/* -> litellm_proxy/*` rewrite for display.\n *\n * The agent-server's LLM validator stores curated OpenHands models as\n * `litellm_proxy/<m>` against the All-Hands proxy base URL. Without\n * normalization the GUI re-loads such settings as `litellm_proxy`, so the\n * provider dropdown silently switches off \"OpenHands\" after every save.\n */\nexport function normalizeDisplayModel(\n rawModel: string | null | undefined,\n baseUrl: string | null | undefined,\n openhandsVerifiedModels: readonly string[],\n): string {\n if (!rawModel) return \"\";\n if (!rawModel.startsWith(LITELLM_PROXY_PREFIX)) return rawModel;\n if (!isOpenHandsProxyBaseUrl(baseUrl)) return rawModel;\n const modelName = rawModel.slice(LITELLM_PROXY_PREFIX.length);\n if (!openhandsVerifiedModels.includes(modelName)) return rawModel;\n return `openhands/${modelName}`;\n}\n"],"mappings":"oFAEA,IAAM,EAAuB,iBAU7B,SAAgB,EACd,EACA,EACA,EACQ,CACR,GAAI,CAAC,EAAU,MAAO,GAEtB,GADI,CAAC,EAAS,WAAW,EAAqB,EAC1C,CAAC,EAAA,wBAAwB,EAAQ,CAAE,OAAO,EAC9C,IAAM,EAAY,EAAS,MAAM,GAA4B,CAE7D,OADK,EAAwB,SAAS,EAAU,CACzC,aAAa,IADqC"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { isOpenHandsProxyBaseUrl as e } from "./openhands-llm.js";
|
|
1
2
|
//#region src/utils/normalize-display-model.ts
|
|
2
|
-
var
|
|
3
|
-
function
|
|
4
|
-
if (!
|
|
5
|
-
if (!
|
|
6
|
-
let
|
|
7
|
-
return
|
|
3
|
+
var t = "litellm_proxy/";
|
|
4
|
+
function n(n, r, i) {
|
|
5
|
+
if (!n) return "";
|
|
6
|
+
if (!n.startsWith(t) || !e(r)) return n;
|
|
7
|
+
let a = n.slice(14);
|
|
8
|
+
return i.includes(a) ? `openhands/${a}` : n;
|
|
8
9
|
}
|
|
9
10
|
//#endregion
|
|
10
|
-
export {
|
|
11
|
+
export { n as normalizeDisplayModel };
|
|
11
12
|
|
|
12
13
|
//# sourceMappingURL=normalize-display-model.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-display-model.js","names":[],"sources":["../../src/utils/normalize-display-model.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"normalize-display-model.js","names":[],"sources":["../../src/utils/normalize-display-model.ts"],"sourcesContent":["import { isOpenHandsProxyBaseUrl } from \"#/utils/openhands-llm\";\n\nconst LITELLM_PROXY_PREFIX = \"litellm_proxy/\";\n\n/**\n * Reverse the SDK's `openhands/* -> litellm_proxy/*` rewrite for display.\n *\n * The agent-server's LLM validator stores curated OpenHands models as\n * `litellm_proxy/<m>` against the All-Hands proxy base URL. Without\n * normalization the GUI re-loads such settings as `litellm_proxy`, so the\n * provider dropdown silently switches off \"OpenHands\" after every save.\n */\nexport function normalizeDisplayModel(\n rawModel: string | null | undefined,\n baseUrl: string | null | undefined,\n openhandsVerifiedModels: readonly string[],\n): string {\n if (!rawModel) return \"\";\n if (!rawModel.startsWith(LITELLM_PROXY_PREFIX)) return rawModel;\n if (!isOpenHandsProxyBaseUrl(baseUrl)) return rawModel;\n const modelName = rawModel.slice(LITELLM_PROXY_PREFIX.length);\n if (!openhandsVerifiedModels.includes(modelName)) return rawModel;\n return `openhands/${modelName}`;\n}\n"],"mappings":";;AAEA,IAAM,IAAuB;AAU7B,SAAgB,EACd,GACA,GACA,GACQ;AACR,KAAI,CAAC,EAAU,QAAO;AAEtB,KADI,CAAC,EAAS,WAAW,EAAqB,IAC1C,CAAC,EAAwB,EAAQ,CAAE,QAAO;CAC9C,IAAM,IAAY,EAAS,MAAM,GAA4B;AAE7D,QADK,EAAwB,SAAS,EAAU,GACzC,aAAa,MADqC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require(`../_virtual/_rolldown/runtime.cjs`);var e=`https://llm-proxy.app.all-hands.dev/`;function
|
|
1
|
+
require(`../_virtual/_rolldown/runtime.cjs`);var e=`https://llm-proxy.app.all-hands.dev/`,t=new Set([`https://llm-proxy.app.all-hands.dev`,`https://llm-proxy.app.all-hands.dev/v1`]),n=`litellm_proxy/`;function r(e){return typeof e==`string`&&e.startsWith(`openhands/`)}function i(e){return typeof e==`string`&&t.has(e.trim().replace(/\/+$/,``))}function a(e,t){return r(e)?!0:typeof e==`string`&&e.startsWith(n)&&i(t)}exports.OPENHANDS_LLM_PROXY_BASE_URL=e,exports.isOpenHandsProxyBaseUrl=i,exports.isOpenHandsProxyModel=a;
|
|
2
2
|
//# sourceMappingURL=openhands-llm.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openhands-llm.cjs","names":[],"sources":["../../src/utils/openhands-llm.ts"],"sourcesContent":["export const OPENHANDS_LLM_PROXY_BASE_URL =\n \"https://llm-proxy.app.all-hands.dev/\";\n\nexport function isOpenHandsProviderModel(model: unknown): model is string {\n return typeof model === \"string\" && model.startsWith(\"openhands/\");\n}\n"],"mappings":"6CAAA,IAAa,EACX,
|
|
1
|
+
{"version":3,"file":"openhands-llm.cjs","names":[],"sources":["../../src/utils/openhands-llm.ts"],"sourcesContent":["export const OPENHANDS_LLM_PROXY_BASE_URL =\n \"https://llm-proxy.app.all-hands.dev/\";\n\n// Accepted spellings of the All-Hands LiteLLM proxy base URL, normalized\n// without a trailing slash. The agent-server stores curated OpenHands models\n// against one of these once its validator rewrites `openhands/*` to\n// `litellm_proxy/*`.\nconst OPENHANDS_LLM_PROXY_BASE_URLS = new Set([\n \"https://llm-proxy.app.all-hands.dev\",\n \"https://llm-proxy.app.all-hands.dev/v1\",\n]);\n\nconst LITELLM_PROXY_PREFIX = \"litellm_proxy/\";\n\nexport function isOpenHandsProviderModel(model: unknown): model is string {\n return typeof model === \"string\" && model.startsWith(\"openhands/\");\n}\n\n/**\n * True when `baseUrl` points at the All-Hands LiteLLM proxy, ignoring any\n * trailing slash.\n */\nexport function isOpenHandsProxyBaseUrl(baseUrl: unknown): baseUrl is string {\n return (\n typeof baseUrl === \"string\" &&\n OPENHANDS_LLM_PROXY_BASE_URLS.has(baseUrl.trim().replace(/\\/+$/, \"\"))\n );\n}\n\n/**\n * True for any OpenHands-backed model: the `openhands/*` id the GUI submits, or\n * the `litellm_proxy/*` id the SDK rewrites it to once it is paired with the\n * OpenHands proxy base URL. Both forms must keep the proxy `base_url` on save —\n * dropping it strips the api_base and silently reroutes the request to the\n * default OpenAI endpoint, leaving an unusable profile (issue #1146).\n */\nexport function isOpenHandsProxyModel(\n model: unknown,\n baseUrl: unknown,\n): model is string {\n if (isOpenHandsProviderModel(model)) return true;\n return (\n typeof model === \"string\" &&\n model.startsWith(LITELLM_PROXY_PREFIX) &&\n isOpenHandsProxyBaseUrl(baseUrl)\n );\n}\n"],"mappings":"6CAAA,IAAa,EACX,uCAMI,EAAgC,IAAI,IAAI,CAC5C,sCACA,yCACD,CAAC,CAEI,EAAuB,iBAE7B,SAAgB,EAAyB,EAAiC,CACxE,OAAO,OAAO,GAAU,UAAY,EAAM,WAAW,aAAa,CAOpE,SAAgB,EAAwB,EAAqC,CAC3E,OACE,OAAO,GAAY,UACnB,EAA8B,IAAI,EAAQ,MAAM,CAAC,QAAQ,OAAQ,GAAG,CAAC,CAWzE,SAAgB,EACd,EACA,EACiB,CAEjB,OADI,EAAyB,EAAM,CAAS,GAE1C,OAAO,GAAU,UACjB,EAAM,WAAW,EAAqB,EACtC,EAAwB,EAAQ"}
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
export declare const OPENHANDS_LLM_PROXY_BASE_URL = "https://llm-proxy.app.all-hands.dev/";
|
|
2
2
|
export declare function isOpenHandsProviderModel(model: unknown): model is string;
|
|
3
|
+
/**
|
|
4
|
+
* True when `baseUrl` points at the All-Hands LiteLLM proxy, ignoring any
|
|
5
|
+
* trailing slash.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isOpenHandsProxyBaseUrl(baseUrl: unknown): baseUrl is string;
|
|
8
|
+
/**
|
|
9
|
+
* True for any OpenHands-backed model: the `openhands/*` id the GUI submits, or
|
|
10
|
+
* the `litellm_proxy/*` id the SDK rewrites it to once it is paired with the
|
|
11
|
+
* OpenHands proxy base URL. Both forms must keep the proxy `base_url` on save —
|
|
12
|
+
* dropping it strips the api_base and silently reroutes the request to the
|
|
13
|
+
* default OpenAI endpoint, leaving an unusable profile (issue #1146).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isOpenHandsProxyModel(model: unknown, baseUrl: unknown): model is string;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
//#region src/utils/openhands-llm.ts
|
|
2
|
-
var e = "https://llm-proxy.app.all-hands.dev/";
|
|
3
|
-
function
|
|
2
|
+
var e = "https://llm-proxy.app.all-hands.dev/", t = new Set(["https://llm-proxy.app.all-hands.dev", "https://llm-proxy.app.all-hands.dev/v1"]), n = "litellm_proxy/";
|
|
3
|
+
function r(e) {
|
|
4
4
|
return typeof e == "string" && e.startsWith("openhands/");
|
|
5
5
|
}
|
|
6
|
+
function i(e) {
|
|
7
|
+
return typeof e == "string" && t.has(e.trim().replace(/\/+$/, ""));
|
|
8
|
+
}
|
|
9
|
+
function a(e, t) {
|
|
10
|
+
return r(e) ? !0 : typeof e == "string" && e.startsWith(n) && i(t);
|
|
11
|
+
}
|
|
6
12
|
//#endregion
|
|
7
|
-
export { e as OPENHANDS_LLM_PROXY_BASE_URL,
|
|
13
|
+
export { e as OPENHANDS_LLM_PROXY_BASE_URL, i as isOpenHandsProxyBaseUrl, a as isOpenHandsProxyModel };
|
|
8
14
|
|
|
9
15
|
//# sourceMappingURL=openhands-llm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openhands-llm.js","names":[],"sources":["../../src/utils/openhands-llm.ts"],"sourcesContent":["export const OPENHANDS_LLM_PROXY_BASE_URL =\n \"https://llm-proxy.app.all-hands.dev/\";\n\nexport function isOpenHandsProviderModel(model: unknown): model is string {\n return typeof model === \"string\" && model.startsWith(\"openhands/\");\n}\n"],"mappings":";AAAA,IAAa,IACX;
|
|
1
|
+
{"version":3,"file":"openhands-llm.js","names":[],"sources":["../../src/utils/openhands-llm.ts"],"sourcesContent":["export const OPENHANDS_LLM_PROXY_BASE_URL =\n \"https://llm-proxy.app.all-hands.dev/\";\n\n// Accepted spellings of the All-Hands LiteLLM proxy base URL, normalized\n// without a trailing slash. The agent-server stores curated OpenHands models\n// against one of these once its validator rewrites `openhands/*` to\n// `litellm_proxy/*`.\nconst OPENHANDS_LLM_PROXY_BASE_URLS = new Set([\n \"https://llm-proxy.app.all-hands.dev\",\n \"https://llm-proxy.app.all-hands.dev/v1\",\n]);\n\nconst LITELLM_PROXY_PREFIX = \"litellm_proxy/\";\n\nexport function isOpenHandsProviderModel(model: unknown): model is string {\n return typeof model === \"string\" && model.startsWith(\"openhands/\");\n}\n\n/**\n * True when `baseUrl` points at the All-Hands LiteLLM proxy, ignoring any\n * trailing slash.\n */\nexport function isOpenHandsProxyBaseUrl(baseUrl: unknown): baseUrl is string {\n return (\n typeof baseUrl === \"string\" &&\n OPENHANDS_LLM_PROXY_BASE_URLS.has(baseUrl.trim().replace(/\\/+$/, \"\"))\n );\n}\n\n/**\n * True for any OpenHands-backed model: the `openhands/*` id the GUI submits, or\n * the `litellm_proxy/*` id the SDK rewrites it to once it is paired with the\n * OpenHands proxy base URL. Both forms must keep the proxy `base_url` on save —\n * dropping it strips the api_base and silently reroutes the request to the\n * default OpenAI endpoint, leaving an unusable profile (issue #1146).\n */\nexport function isOpenHandsProxyModel(\n model: unknown,\n baseUrl: unknown,\n): model is string {\n if (isOpenHandsProviderModel(model)) return true;\n return (\n typeof model === \"string\" &&\n model.startsWith(LITELLM_PROXY_PREFIX) &&\n isOpenHandsProxyBaseUrl(baseUrl)\n );\n}\n"],"mappings":";AAAA,IAAa,IACX,wCAMI,IAAgC,IAAI,IAAI,CAC5C,uCACA,yCACD,CAAC,EAEI,IAAuB;AAE7B,SAAgB,EAAyB,GAAiC;AACxE,QAAO,OAAO,KAAU,YAAY,EAAM,WAAW,aAAa;;AAOpE,SAAgB,EAAwB,GAAqC;AAC3E,QACE,OAAO,KAAY,YACnB,EAA8B,IAAI,EAAQ,MAAM,CAAC,QAAQ,QAAQ,GAAG,CAAC;;AAWzE,SAAgB,EACd,GACA,GACiB;AAEjB,QADI,EAAyB,EAAM,GAAS,KAE1C,OAAO,KAAU,YACjB,EAAM,WAAW,EAAqB,IACtC,EAAwB,EAAQ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openhands/agent-canvas",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.3",
|
|
4
4
|
"description": "Agent Canvas UI for OpenHands - run AI coding agents with a visual interface",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@heroui/react": "2.8.10",
|
|
24
24
|
"@microlink/react-json-view": "1.31.20",
|
|
25
25
|
"@monaco-editor/react": "4.7.0",
|
|
26
|
-
"@openhands/extensions": "
|
|
26
|
+
"@openhands/extensions": "github:OpenHands/extensions#8a6690071e0e9229ae70b30ff0352aff9e6fca21",
|
|
27
27
|
"@openhands/typescript-client": "1.24.3",
|
|
28
28
|
"@react-router/node": "7.14.2",
|
|
29
29
|
"@react-router/serve": "7.14.2",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Usage:
|
|
21
21
|
* node scripts/check-sdk-version-sync.mjs
|
|
22
|
-
* EXPECTED_SDK_VERSION=1.
|
|
22
|
+
* EXPECTED_SDK_VERSION=1.25.0 node scripts/check-sdk-version-sync.mjs
|
|
23
23
|
* node scripts/check-sdk-version-sync.mjs --check-pypi
|
|
24
24
|
*
|
|
25
25
|
* Environment variables:
|
|
@@ -79,7 +79,7 @@ Triggering from other repos:
|
|
|
79
79
|
-H "Authorization: token \$GITHUB_TOKEN" \\
|
|
80
80
|
-H "Accept: application/vnd.github.v3+json" \\
|
|
81
81
|
https://api.github.com/repos/OpenHands/agent-canvas/dispatches \\
|
|
82
|
-
-d '{"event_type": "sdk-version-check", "client_payload": {"version": "1.
|
|
82
|
+
-d '{"event_type": "sdk-version-check", "client_payload": {"version": "1.25.0"}}'
|
|
83
83
|
`);
|
|
84
84
|
process.exit(0);
|
|
85
85
|
}
|
|
@@ -268,9 +268,9 @@ async function fetchPyPIDependencies(packageName, version) {
|
|
|
268
268
|
* Parse PyPI requires_dist array and extract SDK package versions
|
|
269
269
|
*
|
|
270
270
|
* PyPI returns dependencies in PEP 508 format like:
|
|
271
|
-
* "openhands-sdk>=1.
|
|
272
|
-
* "openhands-tools==1.
|
|
273
|
-
* "openhands-workspace (>=1.
|
|
271
|
+
* "openhands-sdk>=1.25.0,<2.0.0"
|
|
272
|
+
* "openhands-tools==1.25.0"
|
|
273
|
+
* "openhands-workspace (>=1.25.0)"
|
|
274
274
|
*/
|
|
275
275
|
function parseSdkVersionsFromRequiresDist(requiresDist) {
|
|
276
276
|
const versions = {};
|
|
@@ -284,7 +284,7 @@ function parseSdkVersionsFromRequiresDist(requiresDist) {
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
// Extract the version number - look for patterns like:
|
|
287
|
-
// ">=1.
|
|
287
|
+
// ">=1.25.0", "==1.25.0", "(>=1.25.0)", "~=1.25.0"
|
|
288
288
|
// After the package name and before any comma or closing paren
|
|
289
289
|
const versionPattern = /[><=~!]+\s*([0-9]+(?:\.[0-9]+)*)/;
|
|
290
290
|
const match = dep.match(versionPattern);
|