@oomol-lab/open-connector 1.9.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/open-connector/catalog/apps/agentearth.json +441 -0
- package/assets/open-connector/catalog/apps/appgrowing_mcp.json +863 -0
- package/assets/open-connector/catalog/apps/arcmira.json +610 -0
- package/assets/open-connector/catalog/apps/bainiu.json +654 -0
- package/assets/open-connector/catalog/apps/chaport.json +331 -0
- package/assets/open-connector/catalog/apps/cnb.json +1408 -0
- package/assets/open-connector/catalog/apps/crtsh.json +123 -0
- package/assets/open-connector/catalog/apps/eastmoney_miaoxiang.json +406 -0
- package/assets/open-connector/catalog/apps/fastmoss_mcp.json +876 -876
- package/assets/open-connector/catalog/apps/ftshare.json +1162 -0
- package/assets/open-connector/catalog/apps/fusion-api.json +143 -0
- package/assets/open-connector/catalog/apps/fxbaogao.json +308 -0
- package/assets/open-connector/catalog/apps/github.json +38 -3
- package/assets/open-connector/catalog/apps/hrmos_kintai.json +7519 -0
- package/assets/open-connector/catalog/apps/humor_api.json +351 -0
- package/assets/open-connector/catalog/apps/kaonavi.json +1723 -0
- package/assets/open-connector/catalog/apps/linear_mcp.json +882 -22
- package/assets/open-connector/catalog/apps/listinggood.json +362 -0
- package/assets/open-connector/catalog/apps/notion_mcp.json +338 -1
- package/assets/open-connector/catalog/apps/outlook.json +414 -0
- package/assets/open-connector/catalog/apps/porteden.json +1258 -0
- package/assets/open-connector/catalog/apps/qiuxiaoce.json +915 -0
- package/assets/open-connector/catalog/apps/qqmusic.json +1834 -0
- package/assets/open-connector/catalog/apps/rakuraku_seisan.json +217 -0
- package/assets/open-connector/catalog/apps/redfox.json +9661 -897
- package/assets/open-connector/catalog/apps/salesforce.json +4907 -0
- package/assets/open-connector/catalog/apps/sansan.json +4594 -0
- package/assets/open-connector/catalog/apps/smarthr.json +8071 -0
- package/assets/open-connector/catalog/apps/smooth.json +914 -0
- package/assets/open-connector/catalog/apps/tapd.json +727 -0
- package/assets/open-connector/catalog/apps/tencent_meeting_mcp.json +1301 -0
- package/assets/open-connector/catalog/apps/threatfox.json +203 -0
- package/assets/open-connector/catalog/apps/tradebee.json +915 -0
- package/assets/open-connector/catalog/apps/twittrapi.json +4914 -0
- package/assets/open-connector/catalog/apps/urlhaus.json +246 -0
- package/assets/open-connector/catalog/apps/weread.json +1514 -0
- package/assets/open-connector/catalog/apps/whatsapp.json +12 -0
- package/assets/open-connector/catalog/apps/zhiliaobiaoxun.json +812 -0
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +2 -1
- package/src/connection-service.js +55 -11
- package/src/core/provider-error-codes.d.ts +9 -0
- package/src/core/provider-error-codes.js +13 -0
- package/src/core/request.d.ts +2 -0
- package/src/core/request.js +11 -2
- package/src/mail/imap-smtp/protocol.js +4 -3
- package/src/oauth/oauth-flow-service.js +1 -0
- package/src/oauth/oauth-token.d.ts +2 -0
- package/src/providers/17track/runtime.js +17 -20
- package/src/providers/action-contracts.generated.d.ts +36 -6
- package/src/providers/agentearth/actions.d.ts +2 -0
- package/src/providers/agentearth/actions.js +100 -0
- package/src/providers/agentearth/executors.d.ts +4 -0
- package/src/providers/agentearth/executors.js +109 -0
- package/src/providers/appgrowing_mcp/actions.d.ts +2 -0
- package/src/providers/appgrowing_mcp/actions.js +175 -0
- package/src/providers/appgrowing_mcp/executors.d.ts +4 -0
- package/src/providers/appgrowing_mcp/executors.js +66 -0
- package/src/providers/arcmira/executors.d.ts +3 -0
- package/src/providers/arcmira/executors.js +8 -0
- package/src/providers/arcmira/runtime.d.ts +6 -0
- package/src/providers/arcmira/runtime.js +41 -0
- package/src/providers/bainiu/actions.d.ts +2 -0
- package/src/providers/bainiu/actions.js +108 -0
- package/src/providers/bainiu/executors.d.ts +4 -0
- package/src/providers/bainiu/executors.js +113 -0
- package/src/providers/baixiao/executors.js +1 -1
- package/src/providers/bing_webmaster/executors.js +1 -1
- package/src/providers/chaport/actions.d.ts +2 -0
- package/src/providers/chaport/actions.js +70 -0
- package/src/providers/chaport/executors.d.ts +4 -0
- package/src/providers/chaport/executors.js +128 -0
- package/src/providers/clickup/runtime.js +49 -52
- package/src/providers/cnb/actions.d.ts +2 -0
- package/src/providers/cnb/actions.js +341 -0
- package/src/providers/cnb/executors.d.ts +4 -0
- package/src/providers/cnb/executors.js +135 -0
- package/src/providers/coupontools/runtime.js +11 -14
- package/src/providers/crtsh/executors.d.ts +3 -0
- package/src/providers/crtsh/executors.js +19 -0
- package/src/providers/crtsh/runtime.d.ts +17 -0
- package/src/providers/crtsh/runtime.js +84 -0
- package/src/providers/eastmoney_miaoxiang/actions.d.ts +2 -0
- package/src/providers/eastmoney_miaoxiang/actions.js +95 -0
- package/src/providers/eastmoney_miaoxiang/executors.d.ts +4 -0
- package/src/providers/eastmoney_miaoxiang/executors.js +81 -0
- package/src/providers/fastmoss_mcp/actions-content.d.ts +2 -0
- package/src/providers/fastmoss_mcp/actions-content.js +140 -0
- package/src/providers/fastmoss_mcp/actions-creator.d.ts +2 -0
- package/src/providers/fastmoss_mcp/actions-creator.js +154 -0
- package/src/providers/fastmoss_mcp/actions-product.d.ts +2 -0
- package/src/providers/fastmoss_mcp/actions-product.js +167 -0
- package/src/providers/fastmoss_mcp/actions-research.d.ts +2 -0
- package/src/providers/fastmoss_mcp/actions-research.js +174 -0
- package/src/providers/fastmoss_mcp/actions-shop.d.ts +2 -0
- package/src/providers/fastmoss_mcp/actions-shop.js +142 -0
- package/src/providers/fastmoss_mcp/actions.js +11 -12
- package/src/providers/fastmoss_mcp/schemas.d.ts +51 -0
- package/src/providers/fastmoss_mcp/schemas.js +52 -0
- package/src/providers/ftshare/actions.d.ts +2 -0
- package/src/providers/ftshare/actions.js +124 -0
- package/src/providers/ftshare/executors.d.ts +4 -0
- package/src/providers/ftshare/executors.js +102 -0
- package/src/providers/fusion-api/operations.js +37 -0
- package/src/providers/fxbaogao/actions.d.ts +2 -0
- package/src/providers/fxbaogao/actions.js +74 -0
- package/src/providers/fxbaogao/executors.d.ts +4 -0
- package/src/providers/fxbaogao/executors.js +155 -0
- package/src/providers/github/app-installation.d.ts +37 -0
- package/src/providers/github/app-installation.js +233 -0
- package/src/providers/github/executors.js +93 -15
- package/src/providers/github/runtime-repository.js +21 -1
- package/src/providers/github/runtime-shared.d.ts +2 -0
- package/src/providers/github/runtime-shared.js +1 -0
- package/src/providers/googledrive/runtime-request.js +3 -3
- package/src/providers/hrmos_kintai/actions.d.ts +2 -0
- package/src/providers/hrmos_kintai/actions.js +139 -0
- package/src/providers/hrmos_kintai/attendance-actions.d.ts +2 -0
- package/src/providers/hrmos_kintai/attendance-actions.js +276 -0
- package/src/providers/hrmos_kintai/business-actions.d.ts +2 -0
- package/src/providers/hrmos_kintai/business-actions.js +143 -0
- package/src/providers/hrmos_kintai/business-schemas.d.ts +4 -0
- package/src/providers/hrmos_kintai/business-schemas.js +320 -0
- package/src/providers/hrmos_kintai/executors.d.ts +4 -0
- package/src/providers/hrmos_kintai/executors.js +61 -0
- package/src/providers/hrmos_kintai/export-actions.d.ts +2 -0
- package/src/providers/hrmos_kintai/export-actions.js +58 -0
- package/src/providers/hrmos_kintai/operations.d.ts +9 -0
- package/src/providers/hrmos_kintai/operations.js +278 -0
- package/src/providers/hrmos_kintai/runtime.d.ts +10 -0
- package/src/providers/hrmos_kintai/runtime.js +217 -0
- package/src/providers/hrmos_kintai/schemas.d.ts +10 -0
- package/src/providers/hrmos_kintai/schemas.js +70 -0
- package/src/providers/humor_api/actions.d.ts +2 -0
- package/src/providers/humor_api/actions.js +96 -0
- package/src/providers/humor_api/executors.d.ts +4 -0
- package/src/providers/humor_api/executors.js +76 -0
- package/src/providers/kaonavi/actions.d.ts +4 -0
- package/src/providers/kaonavi/actions.js +144 -0
- package/src/providers/kaonavi/additional-actions.d.ts +10 -0
- package/src/providers/kaonavi/additional-actions.js +203 -0
- package/src/providers/kaonavi/executors.d.ts +4 -0
- package/src/providers/kaonavi/executors.js +69 -0
- package/src/providers/kaonavi/runtime.d.ts +4 -0
- package/src/providers/kaonavi/runtime.js +69 -0
- package/src/providers/linear_mcp/actions.d.ts +14 -0
- package/src/providers/linear_mcp/actions.js +134 -0
- package/src/providers/linear_mcp/runtime-mcp.d.ts +7 -1
- package/src/providers/linear_mcp/runtime-mcp.js +140 -28
- package/src/providers/linkfox/runtime.js +48 -51
- package/src/providers/listinggood/actions.d.ts +2 -0
- package/src/providers/listinggood/actions.js +95 -0
- package/src/providers/listinggood/executors.d.ts +4 -0
- package/src/providers/listinggood/executors.js +99 -0
- package/src/providers/mcp-tools.d.ts +3 -0
- package/src/providers/mcp-tools.js +5 -4
- package/src/providers/notion_mcp/executors.js +53 -24
- package/src/providers/notion_mcp/mcp-response.d.ts +26 -0
- package/src/providers/notion_mcp/mcp-response.js +95 -0
- package/src/providers/omise/executors.js +1 -1
- package/src/providers/outlook/executors.js +35 -2
- package/src/providers/porteden/actions.d.ts +2 -0
- package/src/providers/porteden/actions.js +162 -0
- package/src/providers/porteden/executors.d.ts +4 -0
- package/src/providers/porteden/executors.js +140 -0
- package/src/providers/provider-runtime.d.ts +3 -2
- package/src/providers/pushover/runtime.js +28 -31
- package/src/providers/qianfan/runtime.js +16 -19
- package/src/providers/qiuxiaoce/actions.d.ts +2 -0
- package/src/providers/qiuxiaoce/actions.js +248 -0
- package/src/providers/qiuxiaoce/executors.d.ts +4 -0
- package/src/providers/qiuxiaoce/executors.js +76 -0
- package/src/providers/qiuxiaoce/routes.d.ts +1 -0
- package/src/providers/qiuxiaoce/routes.js +20 -0
- package/src/providers/qqmusic/actions.d.ts +2 -0
- package/src/providers/qqmusic/actions.js +229 -0
- package/src/providers/qqmusic/executors.d.ts +4 -0
- package/src/providers/qqmusic/executors.js +92 -0
- package/src/providers/qqmusic/report-schema.d.ts +4 -0
- package/src/providers/qqmusic/report-schema.js +119 -0
- package/src/providers/rakuraku_seisan/actions.d.ts +2 -0
- package/src/providers/rakuraku_seisan/actions.js +50 -0
- package/src/providers/rakuraku_seisan/executors.d.ts +4 -0
- package/src/providers/rakuraku_seisan/executors.js +40 -0
- package/src/providers/rakuraku_seisan/runtime.d.ts +8 -0
- package/src/providers/rakuraku_seisan/runtime.js +251 -0
- package/src/providers/redfox/actions-account.d.ts +2 -0
- package/src/providers/redfox/actions-account.js +24 -0
- package/src/providers/redfox/actions-autohome.d.ts +2 -0
- package/src/providers/redfox/actions-autohome.js +51 -0
- package/src/providers/redfox/actions-baijiahao.d.ts +2 -0
- package/src/providers/redfox/actions-baijiahao.js +58 -0
- package/src/providers/redfox/actions-bilibili.d.ts +2 -0
- package/src/providers/redfox/actions-bilibili.js +67 -0
- package/src/providers/redfox/actions-dongchedi.d.ts +2 -0
- package/src/providers/redfox/actions-dongchedi.js +49 -0
- package/src/providers/redfox/actions-douyin.d.ts +2 -0
- package/src/providers/redfox/actions-douyin.js +297 -0
- package/src/providers/redfox/actions-generation.d.ts +2 -0
- package/src/providers/redfox/actions-generation.js +293 -0
- package/src/providers/redfox/actions-instagram.d.ts +2 -0
- package/src/providers/redfox/actions-instagram.js +49 -0
- package/src/providers/redfox/actions-kuaishou.d.ts +2 -0
- package/src/providers/redfox/actions-kuaishou.js +53 -0
- package/src/providers/redfox/actions-search.d.ts +2 -0
- package/src/providers/redfox/actions-search.js +125 -0
- package/src/providers/redfox/actions-tiktok.d.ts +2 -0
- package/src/providers/redfox/actions-tiktok.js +136 -0
- package/src/providers/redfox/actions-tools.d.ts +2 -0
- package/src/providers/redfox/actions-tools.js +151 -0
- package/src/providers/redfox/actions-toutiao.d.ts +2 -0
- package/src/providers/redfox/actions-toutiao.js +68 -0
- package/src/providers/redfox/actions-transcription.d.ts +2 -0
- package/src/providers/redfox/actions-transcription.js +165 -0
- package/src/providers/redfox/actions-wechat-channel.d.ts +2 -0
- package/src/providers/redfox/actions-wechat-channel.js +63 -0
- package/src/providers/redfox/actions-wechat.d.ts +2 -0
- package/src/providers/redfox/actions-wechat.js +302 -0
- package/src/providers/redfox/actions-weibo.d.ts +2 -0
- package/src/providers/redfox/actions-weibo.js +61 -0
- package/src/providers/redfox/actions-x.d.ts +2 -0
- package/src/providers/redfox/actions-x.js +126 -0
- package/src/providers/redfox/actions-xiaohongshu.d.ts +2 -0
- package/src/providers/redfox/actions-xiaohongshu.js +244 -0
- package/src/providers/redfox/actions-yiche.d.ts +2 -0
- package/src/providers/redfox/actions-yiche.js +59 -0
- package/src/providers/redfox/actions-youtube.d.ts +2 -0
- package/src/providers/redfox/actions-youtube.js +64 -0
- package/src/providers/redfox/actions-zhihu.d.ts +2 -0
- package/src/providers/redfox/actions-zhihu.js +26 -0
- package/src/providers/redfox/actions.d.ts +2 -0
- package/src/providers/redfox/actions.js +49 -0
- package/src/providers/redfox/executors.d.ts +2 -14
- package/src/providers/redfox/executors.js +28 -551
- package/src/providers/redfox/routes.d.ts +11 -0
- package/src/providers/redfox/routes.js +329 -0
- package/src/providers/redfox/runtime.d.ts +6 -0
- package/src/providers/redfox/runtime.js +151 -0
- package/src/providers/redfox/schemas.d.ts +9 -0
- package/src/providers/redfox/schemas.js +40 -0
- package/src/providers/registry.generated.js +30 -0
- package/src/providers/salesforce/context.d.ts +16 -0
- package/src/providers/salesforce/context.js +58 -0
- package/src/providers/salesforce/executors.d.ts +5 -0
- package/src/providers/salesforce/executors.js +54 -0
- package/src/providers/salesforce/media.d.ts +9 -0
- package/src/providers/salesforce/media.js +66 -0
- package/src/providers/salesforce/oauth.d.ts +2 -0
- package/src/providers/salesforce/oauth.js +89 -0
- package/src/providers/salesforce/runtime-analytics.d.ts +2 -0
- package/src/providers/salesforce/runtime-analytics.js +60 -0
- package/src/providers/salesforce/runtime-automation.d.ts +2 -0
- package/src/providers/salesforce/runtime-automation.js +64 -0
- package/src/providers/salesforce/runtime-base.d.ts +2 -0
- package/src/providers/salesforce/runtime-base.js +51 -0
- package/src/providers/salesforce/runtime-bulk.d.ts +2 -0
- package/src/providers/salesforce/runtime-bulk.js +98 -0
- package/src/providers/salesforce/runtime-data.d.ts +2 -0
- package/src/providers/salesforce/runtime-data.js +207 -0
- package/src/providers/salesforce/runtime-files.d.ts +2 -0
- package/src/providers/salesforce/runtime-files.js +108 -0
- package/src/providers/salesforce/transport.d.ts +9 -0
- package/src/providers/salesforce/transport.js +65 -0
- package/src/providers/sansan/actions.d.ts +2 -0
- package/src/providers/sansan/actions.js +165 -0
- package/src/providers/sansan/admin-actions.d.ts +2 -0
- package/src/providers/sansan/admin-actions.js +181 -0
- package/src/providers/sansan/admin.d.ts +2 -0
- package/src/providers/sansan/admin.js +42 -0
- package/src/providers/sansan/business-actions.d.ts +2 -0
- package/src/providers/sansan/business-actions.js +160 -0
- package/src/providers/sansan/business.d.ts +2 -0
- package/src/providers/sansan/business.js +51 -0
- package/src/providers/sansan/executors.d.ts +4 -0
- package/src/providers/sansan/executors.js +59 -0
- package/src/providers/sansan/file-schema.d.ts +2 -0
- package/src/providers/sansan/file-schema.js +8 -0
- package/src/providers/sansan/files.d.ts +11 -0
- package/src/providers/sansan/files.js +49 -0
- package/src/providers/sansan/organization-actions.d.ts +2 -0
- package/src/providers/sansan/organization-actions.js +198 -0
- package/src/providers/sansan/organization-columns.d.ts +2 -0
- package/src/providers/sansan/organization-columns.js +33 -0
- package/src/providers/sansan/organization.d.ts +2 -0
- package/src/providers/sansan/organization.js +88 -0
- package/src/providers/sansan/read.d.ts +2 -0
- package/src/providers/sansan/read.js +25 -0
- package/src/providers/sansan/request.d.ts +22 -0
- package/src/providers/sansan/request.js +94 -0
- package/src/providers/sansan/schemas.d.ts +7 -0
- package/src/providers/sansan/schemas.js +72 -0
- package/src/providers/shopee/runtime.d.ts +33 -29
- package/src/providers/shopee/runtime.js +166 -169
- package/src/providers/smarthr/actions.d.ts +2 -0
- package/src/providers/smarthr/actions.js +286 -0
- package/src/providers/smarthr/employee-runtime.d.ts +1 -0
- package/src/providers/smarthr/employee-runtime.js +63 -0
- package/src/providers/smarthr/employee-schema.d.ts +3 -0
- package/src/providers/smarthr/employee-schema.js +222 -0
- package/src/providers/smarthr/executors.d.ts +4 -0
- package/src/providers/smarthr/executors.js +35 -0
- package/src/providers/smarthr/pagination.d.ts +1 -0
- package/src/providers/smarthr/pagination.js +84 -0
- package/src/providers/smarthr/resource-routes.d.ts +16 -0
- package/src/providers/smarthr/resource-routes.js +163 -0
- package/src/providers/smarthr/runtime.d.ts +13 -0
- package/src/providers/smarthr/runtime.js +242 -0
- package/src/providers/smarthr/supplemental-actions.d.ts +2 -0
- package/src/providers/smarthr/supplemental-actions.js +541 -0
- package/src/providers/smooth/actions.d.ts +2 -0
- package/src/providers/smooth/actions.js +138 -0
- package/src/providers/smooth/executors.d.ts +4 -0
- package/src/providers/smooth/executors.js +102 -0
- package/src/providers/tapd/actions.d.ts +8 -0
- package/src/providers/tapd/actions.js +89 -0
- package/src/providers/tapd/executors.d.ts +4 -0
- package/src/providers/tapd/executors.js +137 -0
- package/src/providers/teable/runtime.js +12 -15
- package/src/providers/tencent_meeting_mcp/actions.d.ts +2 -0
- package/src/providers/tencent_meeting_mcp/actions.js +420 -0
- package/src/providers/tencent_meeting_mcp/executors.d.ts +4 -0
- package/src/providers/tencent_meeting_mcp/executors.js +117 -0
- package/src/providers/threatfox/executors.d.ts +4 -0
- package/src/providers/threatfox/executors.js +40 -0
- package/src/providers/threatfox/runtime.d.ts +15 -0
- package/src/providers/threatfox/runtime.js +128 -0
- package/src/providers/ticket_tailor/runtime.js +1 -1
- package/src/providers/ticketmaster/runtime.js +3 -6
- package/src/providers/timelinesai/runtime.js +13 -16
- package/src/providers/tradebee/actions.d.ts +2 -0
- package/src/providers/tradebee/actions.js +176 -0
- package/src/providers/tradebee/executors.d.ts +4 -0
- package/src/providers/tradebee/executors.js +83 -0
- package/src/providers/twittrapi/executors.d.ts +4 -0
- package/src/providers/twittrapi/executors.js +17 -0
- package/src/providers/twittrapi/runtime.d.ts +12 -0
- package/src/providers/twittrapi/runtime.js +203 -0
- package/src/providers/urlhaus/executors.d.ts +4 -0
- package/src/providers/urlhaus/executors.js +40 -0
- package/src/providers/urlhaus/runtime.d.ts +15 -0
- package/src/providers/urlhaus/runtime.js +142 -0
- package/src/providers/wangdian/runtime.js +16 -19
- package/src/providers/weread/actions.d.ts +2 -0
- package/src/providers/weread/actions.js +424 -0
- package/src/providers/weread/executors.d.ts +4 -0
- package/src/providers/weread/executors.js +98 -0
- package/src/providers/whatsapp/runtime.js +6 -1
- package/src/providers/workast/runtime.js +12 -17
- package/src/providers/youzan/runtime.js +21 -24
- package/src/providers/zenkit/runtime.js +13 -19
- package/src/providers/zhiliaobiaoxun/actions.d.ts +2 -0
- package/src/providers/zhiliaobiaoxun/actions.js +138 -0
- package/src/providers/zhiliaobiaoxun/executors.d.ts +4 -0
- package/src/providers/zhiliaobiaoxun/executors.js +78 -0
- package/src/server/api/auth.d.ts +2 -0
- package/src/server/api/auth.js +2 -1
- package/src/server/api/console-catalog-routes.d.ts +17 -0
- package/src/server/api/console-catalog-routes.js +88 -0
- package/src/server/api/console-oauth-routes.d.ts +18 -0
- package/src/server/api/console-oauth-routes.js +298 -0
- package/src/server/api/docs-routes.d.ts +12 -0
- package/src/server/api/docs-routes.js +38 -0
- package/src/server/api/http-utils.d.ts +1 -0
- package/src/server/api/http-utils.js +8 -0
- package/src/server/api/marketplace-routes.d.ts +7 -0
- package/src/server/api/marketplace-routes.js +62 -0
- package/src/server/api/mcp-routes.d.ts +18 -0
- package/src/server/api/mcp-routes.js +37 -0
- package/src/server/api/policy-routes.d.ts +13 -0
- package/src/server/api/policy-routes.js +50 -0
- package/src/server/api/policy-snapshot.d.ts +14 -0
- package/src/server/api/policy-snapshot.js +30 -0
- package/src/server/api/run-log-routes.d.ts +7 -0
- package/src/server/api/run-log-routes.js +70 -0
- package/src/server/api/runtime-api.d.ts +0 -7
- package/src/server/api/runtime-api.js +0 -13
- package/src/server/api/runtime-app-routes.d.ts +10 -0
- package/src/server/api/runtime-app-routes.js +72 -0
- package/src/server/api/runtime-catalog-routes.d.ts +37 -0
- package/src/server/api/runtime-catalog-routes.js +133 -0
- package/src/server/api/runtime-execution-routes.d.ts +16 -0
- package/src/server/api/runtime-execution-routes.js +198 -0
- package/src/server/api/saas-oauth-routes.d.ts +9 -0
- package/src/server/api/saas-oauth-routes.js +27 -0
- package/src/server/api/saas-project-routes.d.ts +7 -0
- package/src/server/api/saas-project-routes.js +16 -0
- package/src/server/api/transit-file-routes.d.ts +9 -0
- package/src/server/api/transit-file-routes.js +49 -0
- package/src/server/api/trigger-routes.d.ts +15 -0
- package/src/server/api/trigger-routes.js +48 -0
- package/src/server/connect-app.d.ts +1 -0
- package/src/server/connect-app.js +3 -1
- package/src/server/connect-server.d.ts +3 -54
- package/src/server/connect-server.js +95 -1148
- package/src/providers/fastmoss_mcp/official-tools.json +0 -2922
|
@@ -1,61 +1,33 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { compress } from "hono/compress";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { optionalBoolean, optionalRecord, optionalString, requiredRawString, requiredString, requiredStringArray, } from "../core/cast.js";
|
|
7
|
-
import { PromiseCache } from "../core/promise-cache.js";
|
|
3
|
+
import { ActionPolicyService } from "../core/action-policy.js";
|
|
4
|
+
import { createActionSearchIndexProvider } from "../core/action-search.js";
|
|
5
|
+
import { optionalRecord } from "../core/cast.js";
|
|
8
6
|
import { withProviderHttpDispatch } from "../core/provider-http-dispatch.js";
|
|
9
|
-
import { MarketplaceError } from "../marketplace/marketplace-service.js";
|
|
10
|
-
import { OAuthClientConfigError, OAuthClientConfigService } from "../oauth/oauth-client-config-service.js";
|
|
11
|
-
import { OAuthCallbackError, OAuthFlowError, OAuthFlowService } from "../oauth/oauth-flow-service.js";
|
|
12
7
|
import { ProviderDispatchRequestError, toProviderExecutionError } from "../providers/provider-runtime.js";
|
|
13
8
|
import { SaasError } from "../saas/saas-client.js";
|
|
14
|
-
import { ActionInputDepthError, createIdempotencyExpiry, hashActionRequest, hashIdempotencyKey, readIdempotencyKey, } from "./actions/action-idempotency.js";
|
|
15
9
|
import { ActionRunner } from "./actions/action-runner.js";
|
|
16
|
-
import {
|
|
17
|
-
import { clearLocalAuthCookie, createLocalAuthMiddleware, readLocalAuthSession, readRuntimeGrant } from "./api/auth.js";
|
|
10
|
+
import { clearLocalAuthCookie, createLocalAuthMiddleware, readLocalAuthSession } from "./api/auth.js";
|
|
18
11
|
import { getResponseCachePolicy } from "./api/cache-policy.js";
|
|
19
12
|
import { createConnectionRoutes } from "./api/connection-routes.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
13
|
+
import { createConsoleCatalogRoutes } from "./api/console-catalog-routes.js";
|
|
14
|
+
import { createConsoleOAuthRoutes } from "./api/console-oauth-routes.js";
|
|
15
|
+
import { createDocsRoutes, loadDocsHandler } from "./api/docs-routes.js";
|
|
16
|
+
import { HttpRequestError, internalError, jsonError, notFound } from "./api/http-utils.js";
|
|
17
|
+
import { createMarketplaceRoutes } from "./api/marketplace-routes.js";
|
|
18
|
+
import { createMcpRoutes, loadMcpModule } from "./api/mcp-routes.js";
|
|
19
|
+
import { createPolicyRoutes } from "./api/policy-routes.js";
|
|
20
|
+
import { createPolicySnapshotProvider } from "./api/policy-snapshot.js";
|
|
21
|
+
import { createRunLogRoutes } from "./api/run-log-routes.js";
|
|
22
|
+
import { writeRuntimeFailure, writeRuntimeSuccess } from "./api/runtime-api.js";
|
|
23
|
+
import { createRuntimeAppRoutes } from "./api/runtime-app-routes.js";
|
|
24
|
+
import { createRuntimeCatalogRoutes } from "./api/runtime-catalog-routes.js";
|
|
25
|
+
import { createRuntimeExecutionRoutes } from "./api/runtime-execution-routes.js";
|
|
26
|
+
import { createSaasOAuthRoutes } from "./api/saas-oauth-routes.js";
|
|
27
|
+
import { createSaasProjectRoutes } from "./api/saas-project-routes.js";
|
|
28
|
+
import { createTransitFileRoutes } from "./api/transit-file-routes.js";
|
|
29
|
+
import { createTriggerRoutes } from "./api/trigger-routes.js";
|
|
27
30
|
import { ProxyRunner } from "./proxy/proxy-runner.js";
|
|
28
|
-
import { decodeRunLogCursor } from "./storage/runtime-store.js";
|
|
29
|
-
import { summarizeRuntimeToken } from "./storage/runtime-token-service.js";
|
|
30
|
-
/**
|
|
31
|
-
* The MCP module pulls in @modelcontextprotocol/server and zod, which no other startup path needs, so it is
|
|
32
|
-
* loaded on the first /mcp request. The cache shares one in-flight import; a rejected import is evicted so
|
|
33
|
-
* transient resolution failures retry, while evaluation errors rethrow identically.
|
|
34
|
-
*/
|
|
35
|
-
const mcpModule = new PromiseCache();
|
|
36
|
-
function loadMcpModule() {
|
|
37
|
-
return mcpModule.get("", () => import("../mcp.js"));
|
|
38
|
-
}
|
|
39
|
-
/** The Scalar API reference is only rendered for /docs, so its package is loaded on the first request. */
|
|
40
|
-
const docsHandler = new PromiseCache();
|
|
41
|
-
function loadDocsHandler(openapiUrl = "/openapi.json") {
|
|
42
|
-
return docsHandler.get(openapiUrl, async () => {
|
|
43
|
-
const { Scalar } = await import("@scalar/hono-api-reference");
|
|
44
|
-
return Scalar({
|
|
45
|
-
pageTitle: "OOMOL Connect API Reference",
|
|
46
|
-
url: openapiUrl,
|
|
47
|
-
theme: "default",
|
|
48
|
-
darkMode: false,
|
|
49
|
-
forceDarkModeState: "light",
|
|
50
|
-
customCss: `
|
|
51
|
-
:root {
|
|
52
|
-
--scalar-color-accent: rgb(59, 99, 251);
|
|
53
|
-
--scalar-background-accent: rgba(59, 99, 251, 0.12);
|
|
54
|
-
}
|
|
55
|
-
`,
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
31
|
/**
|
|
60
32
|
* Import and evaluate the modules the /mcp and /docs routes defer to their first request. The Node server never
|
|
61
33
|
* calls this, so its startup graph stays small; Cloudflare Workers call it at app creation so an isolate keeps
|
|
@@ -73,7 +45,7 @@ export class ConnectServer {
|
|
|
73
45
|
actionSearch;
|
|
74
46
|
actionPolicy;
|
|
75
47
|
proxyRunner;
|
|
76
|
-
|
|
48
|
+
getPolicySnapshot;
|
|
77
49
|
constructor(options) {
|
|
78
50
|
this.options = options;
|
|
79
51
|
this.actionSearch = options.actionSearch ?? createActionSearchIndexProvider(options.catalog.actions);
|
|
@@ -86,6 +58,11 @@ export class ConnectServer {
|
|
|
86
58
|
logger: options.logger,
|
|
87
59
|
saas: options.saas,
|
|
88
60
|
});
|
|
61
|
+
this.getPolicySnapshot = createPolicySnapshotProvider({
|
|
62
|
+
actionPolicy: this.actionPolicy,
|
|
63
|
+
runtimePolicyStore: options.runtimePolicyStore,
|
|
64
|
+
logger: options.logger,
|
|
65
|
+
});
|
|
89
66
|
}
|
|
90
67
|
createApp() {
|
|
91
68
|
const app = new Hono();
|
|
@@ -115,149 +92,86 @@ export class ConnectServer {
|
|
|
115
92
|
app.use("/api/*", compress());
|
|
116
93
|
}
|
|
117
94
|
app.use("*", createLocalAuthMiddleware(auth));
|
|
118
|
-
|
|
119
|
-
app.get("/api/marketplace", (context) => context.json(this.options.marketplace.getState()));
|
|
120
|
-
app.get("/api/marketplace/discovery", (context) => this.getMarketplaceDiscovery(context));
|
|
121
|
-
app.put("/api/marketplace", (context) => this.configureMarketplace(context));
|
|
122
|
-
app.patch("/api/marketplace", (context) => this.configureMarketplace(context));
|
|
123
|
-
app.delete("/api/marketplace", (context) => this.deleteMarketplace(context));
|
|
124
|
-
app.get("/api/provider-preferences", async (context) => context.json(await this.options.marketplace.listProviderPreferences()));
|
|
125
|
-
app.patch("/api/provider-preferences/:service", (context) => this.updateProviderPreference(context, context.req.param("service")));
|
|
126
|
-
}
|
|
95
|
+
// Health and runtime discovery / execution endpoints
|
|
127
96
|
app.get("/v1/health", (context) => writeRuntimeSuccess(context, { ok: true, runtime: "oomol-connect" }));
|
|
128
|
-
app.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
app.get("/v1/actions/search", (context) => this.searchRuntimeActions(context));
|
|
147
|
-
app.get("/v1/actions/:actionId", (context) => this.getRuntimeAction(context, context.req.param("actionId")));
|
|
148
|
-
app.post("/v1/actions/:actionId", (context) => this.createRuntimeActionRun(context, context.req.param("actionId")));
|
|
97
|
+
app.route("/v1", createRuntimeCatalogRoutes({
|
|
98
|
+
catalog: this.options.catalog,
|
|
99
|
+
connections: this.options.connections,
|
|
100
|
+
actionSearch: this.actionSearch,
|
|
101
|
+
oauthClientConfigs: this.options.oauthClientConfigs,
|
|
102
|
+
saasProject: this.options.saasProject,
|
|
103
|
+
}));
|
|
104
|
+
app.route("/v1", createRuntimeAppRoutes({
|
|
105
|
+
connections: this.options.connections,
|
|
106
|
+
getPolicySnapshot: this.getPolicySnapshot,
|
|
107
|
+
}));
|
|
108
|
+
app.route("/v1", createRuntimeExecutionRoutes({
|
|
109
|
+
catalog: this.options.catalog,
|
|
110
|
+
actions: this.options.actions,
|
|
111
|
+
idempotency: this.options.idempotency,
|
|
112
|
+
proxyRunner: this.proxyRunner,
|
|
113
|
+
getPolicySnapshot: this.getPolicySnapshot,
|
|
114
|
+
}));
|
|
149
115
|
app.route("/v1", createConnectionRoutes(this.options));
|
|
150
|
-
|
|
151
|
-
app.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
app.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
116
|
+
// Triggers and protocols
|
|
117
|
+
app.route("/", createTriggerRoutes({
|
|
118
|
+
catalog: this.options.catalog,
|
|
119
|
+
triggers: this.options.triggers,
|
|
120
|
+
triggerMaintenance: this.options.triggerMaintenance,
|
|
121
|
+
getPolicySnapshot: this.getPolicySnapshot,
|
|
122
|
+
}));
|
|
123
|
+
app.route("/", createMcpRoutes({
|
|
124
|
+
catalog: this.options.catalog,
|
|
125
|
+
connections: this.options.connections,
|
|
126
|
+
actions: this.options.actions,
|
|
127
|
+
actionSearch: this.actionSearch,
|
|
128
|
+
getPolicySnapshot: this.getPolicySnapshot,
|
|
129
|
+
}));
|
|
130
|
+
// API documentation (OpenAPI and Scalar reference)
|
|
131
|
+
app.route("/", createDocsRoutes({
|
|
132
|
+
providers: this.options.catalog.providers,
|
|
133
|
+
publicOrigin: this.options.publicOrigin,
|
|
134
|
+
serveDocumentation: this.options.serveDocumentation,
|
|
135
|
+
}));
|
|
136
|
+
// Console API routes
|
|
137
|
+
app.route("/api", createConsoleCatalogRoutes({
|
|
138
|
+
catalog: this.options.catalog,
|
|
139
|
+
connections: this.options.connections,
|
|
140
|
+
actionSearch: this.actionSearch,
|
|
141
|
+
publicOrigin: this.options.publicOrigin,
|
|
142
|
+
getPolicySnapshot: this.getPolicySnapshot,
|
|
143
|
+
}));
|
|
176
144
|
app.get("/api/auth/session", async (context) => context.json(await readLocalAuthSession(context, auth)));
|
|
177
145
|
app.post("/api/auth/logout", (context) => {
|
|
178
146
|
clearLocalAuthCookie(context);
|
|
179
147
|
return context.json({ ok: true });
|
|
180
148
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
app.
|
|
185
|
-
app.
|
|
186
|
-
app.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
app.delete("/api/runtime-tokens/:id", (context) => this.revokeRuntimeToken(context, context.req.param("id")));
|
|
193
|
-
app.get("/api/runtime-policy", (context) => this.getRuntimePolicy(context));
|
|
194
|
-
app.put("/api/runtime-policy", (context) => this.updateRuntimePolicy(context));
|
|
149
|
+
if (this.options.marketplace) {
|
|
150
|
+
app.route("/api", createMarketplaceRoutes({ marketplace: this.options.marketplace }));
|
|
151
|
+
}
|
|
152
|
+
app.route("/api", createRunLogRoutes({ actions: this.options.actions }));
|
|
153
|
+
app.route("/api", createTransitFileRoutes(this.options));
|
|
154
|
+
app.route("/api", createPolicyRoutes({
|
|
155
|
+
runtimeTokens: this.options.runtimeTokens,
|
|
156
|
+
runtimePolicyStore: this.options.runtimePolicyStore,
|
|
157
|
+
deploymentPolicyRules: this.actionPolicy.rules,
|
|
158
|
+
getPolicySnapshot: this.getPolicySnapshot,
|
|
159
|
+
}));
|
|
195
160
|
const saas = this.options.saasProject;
|
|
196
161
|
if (saas) {
|
|
197
|
-
app.
|
|
198
|
-
app.put("/api/oauth/managed-project", async (context) => context.json(await saas.configure(await readJsonBody(context, 16_384), context.req.raw.signal)));
|
|
199
|
-
app.delete("/api/oauth/managed-project", async (context) => {
|
|
200
|
-
await saas.remove();
|
|
201
|
-
return context.json(await saas.getState(context.req.raw.signal));
|
|
202
|
-
});
|
|
203
|
-
app.get("/api/oauth/managed-project/provider-configs", async (context) => context.json(await saas.listProviderConfigs(context.req.raw.signal)));
|
|
204
|
-
app.get("/api/oauth/sources/:service", async (context) => context.json(await saas.getSource(context.req.param("service"))));
|
|
205
|
-
app.put("/api/oauth/sources/:service", async (context) => context.json(await saas.setSource(context.req.param("service"), await readJsonBody(context, 16_384), context.req.raw.signal)));
|
|
162
|
+
app.route("/api", createSaasProjectRoutes({ saasProject: saas }));
|
|
206
163
|
}
|
|
207
164
|
if (saas && this.options.saasOAuth) {
|
|
208
|
-
app.
|
|
209
|
-
context.header("Content-Security-Policy", "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'");
|
|
210
|
-
context.header("Referrer-Policy", "no-referrer");
|
|
211
|
-
return context.html(renderSaasCompletionPage());
|
|
212
|
-
});
|
|
213
|
-
app.post("/api/oauth/connection-requests/:id/sync", async (context) => {
|
|
214
|
-
const expectedOrigin = saas.requireOrigin();
|
|
215
|
-
if (context.req.header("origin") !== expectedOrigin)
|
|
216
|
-
throw new SaasError("oauth_source_origin_mismatch", `Open Console at ${expectedOrigin}, or set OOMOL_CONNECT_ORIGIN to the exact Console address in your browser and restart Connect. localhost and 127.0.0.1 are different origins.`, 403);
|
|
217
|
-
if (context.req.header("x-openconnector-request") !== "sync" ||
|
|
218
|
-
context.req.header("content-type")?.split(";")[0].trim().toLowerCase() !== "application/json")
|
|
219
|
-
return context.json({ error: { code: "forbidden", message: "OAuth synchronization requires a same-origin JSON request." } }, 403);
|
|
220
|
-
const result = await this.options.saasOAuth.syncForBrowser(context.req.param("id"), "local-admin", context.req.raw.signal);
|
|
221
|
-
if (!result)
|
|
222
|
-
return jsonError(context, 404, "connection_request_not_found", "Connection request not found.");
|
|
223
|
-
context.header("Cache-Control", "private, no-store");
|
|
224
|
-
return context.json(result);
|
|
225
|
-
});
|
|
165
|
+
app.route("/", createSaasOAuthRoutes({ saasProject: saas, saasOAuth: this.options.saasOAuth }));
|
|
226
166
|
}
|
|
227
|
-
app.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return jsonError(context, 400, "invalid_input", "Invalid OAuth connection request.");
|
|
236
|
-
const { appId, ...input } = parsed.data;
|
|
237
|
-
const target = appId ? await this.options.connections.getStoredConnection(appId) : undefined;
|
|
238
|
-
if (target && (target.service !== input.service || target.connectionName !== input.connectionName))
|
|
239
|
-
return jsonError(context, 400, "invalid_input", "The selected connection no longer matches.");
|
|
240
|
-
return context.json(await this.options.oauthFlow.startConnectionRequest({
|
|
241
|
-
...input,
|
|
242
|
-
target,
|
|
243
|
-
owner: "local-admin",
|
|
244
|
-
signal: context.req.raw.signal,
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
if (error instanceof OAuthClientConfigError ||
|
|
249
|
-
error instanceof OAuthFlowError ||
|
|
250
|
-
error instanceof ConnectionError)
|
|
251
|
-
return jsonError(context, error.code === "unknown_service" || error.code === "app_not_found" ? 404 : 400, error.code, error.message);
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
app.post("/api/oauth/authorizations", (context) => this.createOAuthAuthorization(context));
|
|
256
|
-
app.get("/oauth/callback", (context) => this.completeOAuth(context));
|
|
257
|
-
app.post("/mcp", (context) => this.handleMcp(context));
|
|
258
|
-
app.get("/mcp", (context) => this.rejectMcpMethod(context));
|
|
259
|
-
app.delete("/mcp", (context) => this.rejectMcpMethod(context));
|
|
260
|
-
app.get("/mcp/tools", async (context) => context.json({ tools: (await loadMcpModule()).listMcpToolSummaries() }));
|
|
167
|
+
app.route("/", createConsoleOAuthRoutes({
|
|
168
|
+
connections: this.options.connections,
|
|
169
|
+
oauthFlow: this.options.oauthFlow,
|
|
170
|
+
oauthClientConfigs: this.options.oauthClientConfigs,
|
|
171
|
+
saasProject: this.options.saasProject,
|
|
172
|
+
logger: this.options.logger,
|
|
173
|
+
oauthCloseDelaySeconds: this.options.oauthCloseDelaySeconds,
|
|
174
|
+
}));
|
|
261
175
|
// Without a console the API owns every unknown path; a console host layers its fallback over these 404s.
|
|
262
176
|
if (this.options.registerStaticRoutes)
|
|
263
177
|
this.options.registerStaticRoutes(app);
|
|
@@ -317,971 +231,4 @@ export class ConnectServer {
|
|
|
317
231
|
});
|
|
318
232
|
return app;
|
|
319
233
|
}
|
|
320
|
-
listProviderSummaries(context) {
|
|
321
|
-
const { providerSummariesJson, providerSummariesEtag } = this.options.catalog;
|
|
322
|
-
context.header("ETag", providerSummariesEtag);
|
|
323
|
-
if (requestMatchesEtag(context.req.header("If-None-Match"), providerSummariesEtag)) {
|
|
324
|
-
return context.body(null, 304);
|
|
325
|
-
}
|
|
326
|
-
return context.body(providerSummariesJson, 200, { "Content-Type": "application/json" });
|
|
327
|
-
}
|
|
328
|
-
async configureMarketplace(context) {
|
|
329
|
-
const body = await readJsonBody(context);
|
|
330
|
-
const input = {
|
|
331
|
-
discoveryUrl: optionalString(body.discoveryUrl),
|
|
332
|
-
apiKey: optionalString(body.apiKey),
|
|
333
|
-
enabled: typeof body.enabled === "boolean" ? body.enabled : undefined,
|
|
334
|
-
};
|
|
335
|
-
try {
|
|
336
|
-
return context.json(await this.options.marketplace.configure(input));
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
if (error instanceof MarketplaceError)
|
|
340
|
-
return this.writeMarketplaceError(context, error);
|
|
341
|
-
throw error;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async getMarketplaceDiscovery(context) {
|
|
345
|
-
try {
|
|
346
|
-
return context.json(await this.options.marketplace.getDefaultDiscovery(context.req.raw.signal));
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
if (error instanceof MarketplaceError)
|
|
350
|
-
return this.writeMarketplaceError(context, error);
|
|
351
|
-
throw error;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
writeMarketplaceError(context, error) {
|
|
355
|
-
const status = error.status;
|
|
356
|
-
return jsonError(context, status === 401 || status === 403 || status === 404 || status === 502 || status === 504 ? status : 400, error.code, error.message);
|
|
357
|
-
}
|
|
358
|
-
async deleteMarketplace(context) {
|
|
359
|
-
await this.options.marketplace.remove();
|
|
360
|
-
return context.json(this.options.marketplace.getState());
|
|
361
|
-
}
|
|
362
|
-
async updateProviderPreference(context, service) {
|
|
363
|
-
const body = await readJsonBody(context);
|
|
364
|
-
if (typeof body.enabled !== "boolean") {
|
|
365
|
-
return jsonError(context, 400, "invalid_input", "enabled must be a boolean.");
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
return context.json(await this.options.marketplace.setProviderEnabled(service, body.enabled));
|
|
369
|
-
}
|
|
370
|
-
catch (error) {
|
|
371
|
-
if (error instanceof MarketplaceError)
|
|
372
|
-
return jsonError(context, 404, error.code, error.message);
|
|
373
|
-
throw error;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
async getRuntimeProviderSetup(context, service) {
|
|
377
|
-
const provider = this.options.catalog.providers.find((provider) => provider.service === service);
|
|
378
|
-
if (!provider)
|
|
379
|
-
return writeRuntimeFailure(context, unknownServiceFailure(service));
|
|
380
|
-
let oauth = provider.auth.some((auth) => auth.type === "oauth2")
|
|
381
|
-
? await this.options.oauthClientConfigs.getSummary(service)
|
|
382
|
-
: undefined;
|
|
383
|
-
const source = oauth ? await this.options.saasProject?.getSource(service) : undefined;
|
|
384
|
-
if (oauth && source?.mode === "saas") {
|
|
385
|
-
const { config } = await this.options.saasProject.resolveConfig(service, source.providerConfigId, source.managedProjectId, context.req.raw.signal);
|
|
386
|
-
oauth = {
|
|
387
|
-
...oauth,
|
|
388
|
-
configured: true,
|
|
389
|
-
customClientAvailable: false,
|
|
390
|
-
expectedRedirectUri: config.callbackUrl,
|
|
391
|
-
missingFields: [],
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
return writeRuntimeSuccess(context, serializeRuntimeProviderSetup(provider, oauth));
|
|
395
|
-
}
|
|
396
|
-
getProvider(context, service) {
|
|
397
|
-
const provider = this.options.catalog.providers.find((provider) => provider.service === service);
|
|
398
|
-
if (!provider) {
|
|
399
|
-
return notFound(context);
|
|
400
|
-
}
|
|
401
|
-
return context.json(provider);
|
|
402
|
-
}
|
|
403
|
-
async createTransitFile(context) {
|
|
404
|
-
try {
|
|
405
|
-
if (this.options.uploadTransitFile) {
|
|
406
|
-
return context.json(await this.options.uploadTransitFile(context.req.raw));
|
|
407
|
-
}
|
|
408
|
-
const form = await context.req.raw.formData();
|
|
409
|
-
const file = form.get("file");
|
|
410
|
-
if (!(file instanceof File)) {
|
|
411
|
-
return jsonError(context, 400, "invalid_input", "file is required.");
|
|
412
|
-
}
|
|
413
|
-
const upload = await this.options.transitFiles.create(file);
|
|
414
|
-
return context.json(upload);
|
|
415
|
-
}
|
|
416
|
-
catch (error) {
|
|
417
|
-
return this.handleTransitFileError(context, error);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
async getTransitFile(context, fileId) {
|
|
421
|
-
try {
|
|
422
|
-
return await this.options.transitFiles.response(fileId);
|
|
423
|
-
}
|
|
424
|
-
catch (error) {
|
|
425
|
-
return this.handleTransitFileError(context, error);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
async deleteTransitFile(context, fileId) {
|
|
429
|
-
try {
|
|
430
|
-
const deleted = await this.options.transitFiles.delete(fileId);
|
|
431
|
-
return context.json({ fileId, deleted });
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
return this.handleTransitFileError(context, error);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
handleTransitFileError(context, error) {
|
|
438
|
-
if (error instanceof TransitFileError) {
|
|
439
|
-
return jsonError(context, error.status, error.code, error.message);
|
|
440
|
-
}
|
|
441
|
-
throw error;
|
|
442
|
-
}
|
|
443
|
-
getAction(context, actionId) {
|
|
444
|
-
const action = this.options.catalog.actionsById.get(actionId);
|
|
445
|
-
if (!action) {
|
|
446
|
-
return notFound(context);
|
|
447
|
-
}
|
|
448
|
-
return context.json(action);
|
|
449
|
-
}
|
|
450
|
-
async listRuns(context) {
|
|
451
|
-
const query = readRunLogListInput(context);
|
|
452
|
-
if (!query.ok) {
|
|
453
|
-
return jsonError(context, 400, "invalid_input", query.message);
|
|
454
|
-
}
|
|
455
|
-
return context.json(await this.options.actions.listRuns(query.input));
|
|
456
|
-
}
|
|
457
|
-
async getRun(context, id) {
|
|
458
|
-
const run = await this.options.actions.getRun(id);
|
|
459
|
-
return run ? context.json(run) : jsonError(context, 404, "run_not_found", `Run not found: ${id}.`);
|
|
460
|
-
}
|
|
461
|
-
async searchApiActions(context) {
|
|
462
|
-
const query = readSearchQuery(context);
|
|
463
|
-
if (!query.ok) {
|
|
464
|
-
return jsonError(context, 400, "invalid_input", query.message);
|
|
465
|
-
}
|
|
466
|
-
const index = await this.actionSearch.get();
|
|
467
|
-
return context.json(await this.serializeSearchResults(searchActions(index, query.q, {
|
|
468
|
-
service: query.service,
|
|
469
|
-
limit: query.limit,
|
|
470
|
-
})));
|
|
471
|
-
}
|
|
472
|
-
async getActionMarkdown(context, actionId) {
|
|
473
|
-
const action = this.options.catalog.actionsById.get(actionId);
|
|
474
|
-
if (!action) {
|
|
475
|
-
return notFound(context);
|
|
476
|
-
}
|
|
477
|
-
try {
|
|
478
|
-
const policy = (await this.getPolicySnapshot(context)).evaluate(action);
|
|
479
|
-
return context.text(renderActionMarkdown(action, {
|
|
480
|
-
transport: { kind: "http", origin: this.options.publicOrigin },
|
|
481
|
-
connection: await this.options.connections.getConnectionSummary(action.service, readConnectionName(context)),
|
|
482
|
-
policy,
|
|
483
|
-
}), 200, {
|
|
484
|
-
"content-type": "text/markdown; charset=utf-8",
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
catch (error) {
|
|
488
|
-
if (error instanceof ConnectionError) {
|
|
489
|
-
const status = mapConnectionErrorStatus(error);
|
|
490
|
-
return jsonError(context, status, error.code, error.message);
|
|
491
|
-
}
|
|
492
|
-
throw error;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
listRuntimeProviders(context) {
|
|
496
|
-
const services = context.req.queries("service") ?? [];
|
|
497
|
-
const query = optionalString(context.req.query("q"))?.toLowerCase();
|
|
498
|
-
const providers = this.options.catalog.providers.filter((provider) => {
|
|
499
|
-
if (services.length > 0 && !services.includes(provider.service)) {
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
|
-
if (!query) {
|
|
503
|
-
return true;
|
|
504
|
-
}
|
|
505
|
-
return [
|
|
506
|
-
provider.service,
|
|
507
|
-
provider.displayName,
|
|
508
|
-
provider.categories.join(" "),
|
|
509
|
-
provider.scenario,
|
|
510
|
-
provider.authTypes.join(" "),
|
|
511
|
-
]
|
|
512
|
-
.join(" ")
|
|
513
|
-
.toLowerCase()
|
|
514
|
-
.includes(query);
|
|
515
|
-
});
|
|
516
|
-
return writeRuntimeSuccess(context, providers.map(serializeRuntimeProvider));
|
|
517
|
-
}
|
|
518
|
-
listRuntimeActions(context) {
|
|
519
|
-
const service = optionalString(context.req.query("service"));
|
|
520
|
-
if (!service) {
|
|
521
|
-
const services = [...new Set(this.options.catalog.actions.map((action) => action.service))];
|
|
522
|
-
return writeRuntimeSuccess(context, services.map(serializeRuntimeActionService));
|
|
523
|
-
}
|
|
524
|
-
const actions = this.options.catalog.actions.filter((action) => action.service === service);
|
|
525
|
-
return writeRuntimeSuccess(context, actions.map(serializeRuntimeAction));
|
|
526
|
-
}
|
|
527
|
-
async searchRuntimeActions(context) {
|
|
528
|
-
const query = readSearchQuery(context, 10);
|
|
529
|
-
if (!query.ok) {
|
|
530
|
-
return writeRuntimeFailure(context, {
|
|
531
|
-
status: 400,
|
|
532
|
-
errorCode: "invalid_input",
|
|
533
|
-
message: query.message,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
const index = await this.actionSearch.get();
|
|
537
|
-
const results = searchActions(index, query.q, {
|
|
538
|
-
service: query.service,
|
|
539
|
-
limit: query.limit,
|
|
540
|
-
});
|
|
541
|
-
return writeRuntimeSuccess(context, await this.serializeSearchResults(results));
|
|
542
|
-
}
|
|
543
|
-
async serializeSearchResults(results) {
|
|
544
|
-
const authenticated = new Set(await this.options.connections.listAuthenticatedServices([...new Set(results.map((result) => result.service))]));
|
|
545
|
-
return results.flatMap((result) => {
|
|
546
|
-
const action = this.options.catalog.actionsById.get(result.id);
|
|
547
|
-
if (!action) {
|
|
548
|
-
return [];
|
|
549
|
-
}
|
|
550
|
-
return [serializeActionSearchResult(result, action, authenticated.has(action.service))];
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
getRuntimeAction(context, actionId) {
|
|
554
|
-
const action = this.options.catalog.actionsById.get(actionId);
|
|
555
|
-
if (!action) {
|
|
556
|
-
return writeRuntimeFailure(context, unknownActionFailure(actionId));
|
|
557
|
-
}
|
|
558
|
-
return writeRuntimeSuccess(context, serializeRuntimeAction(action));
|
|
559
|
-
}
|
|
560
|
-
async getRuntimeTriggerPermissions(context, service) {
|
|
561
|
-
const provider = this.options.catalog.providers.find((item) => item.service === service);
|
|
562
|
-
if (!provider)
|
|
563
|
-
throw new HttpRequestError("provider_not_found", "Provider not found.", 404);
|
|
564
|
-
return writeRuntimeSuccess(context, serializeRuntimeTriggerPermissions(provider));
|
|
565
|
-
}
|
|
566
|
-
async executeRuntimeTrigger(context) {
|
|
567
|
-
if (!this.options.triggers)
|
|
568
|
-
throw new HttpRequestError("trigger_not_supported", "Trigger execution is unavailable.", 501);
|
|
569
|
-
const { readTriggerRequest } = await import("../triggers/request.js");
|
|
570
|
-
const request = readTriggerRequest(await readJsonBody(context, 160 * 1024));
|
|
571
|
-
const signal = AbortSignal.any([context.req.raw.signal, AbortSignal.timeout(45_000)]);
|
|
572
|
-
const result = await this.options.triggers.run({
|
|
573
|
-
service: context.req.param("service"),
|
|
574
|
-
triggerId: context.req.param("triggerId"),
|
|
575
|
-
request,
|
|
576
|
-
connectionName: readConnectionName(context),
|
|
577
|
-
connectionId: optionalString(context.req.header("x-oo-connector-app-id")),
|
|
578
|
-
policy: await this.getPolicySnapshot(context),
|
|
579
|
-
grant: readRuntimeGrant(context),
|
|
580
|
-
signal,
|
|
581
|
-
});
|
|
582
|
-
return writeRuntimeSuccess(context, result);
|
|
583
|
-
}
|
|
584
|
-
async createRuntimeActionRun(context, actionId) {
|
|
585
|
-
const action = this.options.catalog.actionsById.get(actionId);
|
|
586
|
-
if (!action) {
|
|
587
|
-
return writeRuntimeFailure(context, unknownActionFailure(actionId));
|
|
588
|
-
}
|
|
589
|
-
const body = await readJsonBody(context);
|
|
590
|
-
const input = body.input ?? {};
|
|
591
|
-
const connectionName = readConnectionName(context, body);
|
|
592
|
-
const connectionId = optionalString(context.req.header("x-oo-connector-app-id"));
|
|
593
|
-
const runtimeGrant = readRuntimeGrant(context);
|
|
594
|
-
let policy;
|
|
595
|
-
try {
|
|
596
|
-
policy = await this.getPolicySnapshot(context);
|
|
597
|
-
}
|
|
598
|
-
catch {
|
|
599
|
-
return writeRuntimeFailure(context, {
|
|
600
|
-
status: 500,
|
|
601
|
-
errorCode: "internal_error",
|
|
602
|
-
message: "Runtime policy is unavailable.",
|
|
603
|
-
meta: { actionId },
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
if (!policy.evaluate(action).allowed) {
|
|
607
|
-
return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId));
|
|
608
|
-
}
|
|
609
|
-
const idempotencyKey = readIdempotencyKey(context.req.header("idempotency-key"));
|
|
610
|
-
if (!idempotencyKey.ok) {
|
|
611
|
-
return writeRuntimeFailure(context, {
|
|
612
|
-
status: 400,
|
|
613
|
-
errorCode: "invalid_input",
|
|
614
|
-
message: idempotencyKey.message,
|
|
615
|
-
meta: { actionId },
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (!idempotencyKey.key) {
|
|
619
|
-
return writeRuntimeActionHttpResult(context, await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId));
|
|
620
|
-
}
|
|
621
|
-
const now = new Date();
|
|
622
|
-
const keyHash = hashIdempotencyKey(idempotencyKey.key);
|
|
623
|
-
let requestHash;
|
|
624
|
-
try {
|
|
625
|
-
requestHash = hashActionRequest({
|
|
626
|
-
actionId,
|
|
627
|
-
connectionName: connectionName ?? defaultConnectionName,
|
|
628
|
-
connectionId,
|
|
629
|
-
input,
|
|
630
|
-
runtimeTokenId: runtimeGrant?.tokenId,
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
catch (error) {
|
|
634
|
-
if (!(error instanceof ActionInputDepthError)) {
|
|
635
|
-
throw error;
|
|
636
|
-
}
|
|
637
|
-
return writeRuntimeFailure(context, {
|
|
638
|
-
status: 400,
|
|
639
|
-
errorCode: "invalid_input",
|
|
640
|
-
message: error.message,
|
|
641
|
-
meta: { actionId },
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
const claimId = crypto.randomUUID();
|
|
645
|
-
const claim = await this.options.idempotency.claim({
|
|
646
|
-
keyHash,
|
|
647
|
-
requestHash,
|
|
648
|
-
claimId,
|
|
649
|
-
now: now.toISOString(),
|
|
650
|
-
expiresAt: createIdempotencyExpiry(now),
|
|
651
|
-
});
|
|
652
|
-
if (claim.kind === "conflict") {
|
|
653
|
-
return writeRuntimeFailure(context, {
|
|
654
|
-
status: 409,
|
|
655
|
-
errorCode: "idempotency_key_conflict",
|
|
656
|
-
message: "Idempotency-Key has already been used with a different request.",
|
|
657
|
-
meta: { actionId },
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
if (claim.kind === "in_progress") {
|
|
661
|
-
return writeRuntimeFailure(context, {
|
|
662
|
-
status: 409,
|
|
663
|
-
errorCode: "idempotency_request_in_progress",
|
|
664
|
-
message: "A request with this Idempotency-Key is still in progress.",
|
|
665
|
-
meta: { actionId },
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
if (claim.kind === "completed") {
|
|
669
|
-
return writeRuntimeActionHttpResult(context, claim.response);
|
|
670
|
-
}
|
|
671
|
-
const result = await this.executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, context.req.raw.signal, connectionId);
|
|
672
|
-
const completed = await this.options.idempotency.complete({
|
|
673
|
-
keyHash,
|
|
674
|
-
requestHash,
|
|
675
|
-
claimId,
|
|
676
|
-
response: result,
|
|
677
|
-
expiresAt: createIdempotencyExpiry(new Date()),
|
|
678
|
-
});
|
|
679
|
-
if (!completed) {
|
|
680
|
-
throw new Error("Idempotency claim was replaced before completion.");
|
|
681
|
-
}
|
|
682
|
-
return writeRuntimeActionHttpResult(context, result);
|
|
683
|
-
}
|
|
684
|
-
async executeRuntimeAction(actionId, input, connectionName, policy, runtimeGrant, signal, connectionId) {
|
|
685
|
-
try {
|
|
686
|
-
const run = await this.options.actions.run({
|
|
687
|
-
actionId,
|
|
688
|
-
input,
|
|
689
|
-
caller: "http",
|
|
690
|
-
connectionName,
|
|
691
|
-
connectionId,
|
|
692
|
-
policy,
|
|
693
|
-
runtimeTokenId: runtimeGrant?.tokenId,
|
|
694
|
-
signal,
|
|
695
|
-
});
|
|
696
|
-
if (!run) {
|
|
697
|
-
return serializeRuntimeFailure(unknownActionFailure(actionId));
|
|
698
|
-
}
|
|
699
|
-
return serializeRuntimeActionResult({
|
|
700
|
-
actionId,
|
|
701
|
-
executionId: run.executionId,
|
|
702
|
-
remoteExecutionId: run.remoteExecutionId,
|
|
703
|
-
failureStatus: run.failureStatus,
|
|
704
|
-
retryAfter: run.retryAfter,
|
|
705
|
-
auditPersisted: run.auditPersisted,
|
|
706
|
-
result: run.result,
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
catch (error) {
|
|
710
|
-
if (error instanceof ConnectionError) {
|
|
711
|
-
return serializeRuntimeFailure({
|
|
712
|
-
status: mapConnectionErrorStatus(error),
|
|
713
|
-
errorCode: error.code,
|
|
714
|
-
message: error.message,
|
|
715
|
-
meta: { actionId },
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
throw error;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
async createRuntimeProxyRequest(context, service) {
|
|
722
|
-
let body;
|
|
723
|
-
try {
|
|
724
|
-
body = await readJsonBody(context);
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
if (error instanceof HttpRequestError) {
|
|
728
|
-
return writeRuntimeFailure(context, {
|
|
729
|
-
status: error.status,
|
|
730
|
-
errorCode: error.code,
|
|
731
|
-
message: error.message,
|
|
732
|
-
meta: { service },
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
throw error;
|
|
736
|
-
}
|
|
737
|
-
let policy;
|
|
738
|
-
try {
|
|
739
|
-
policy = await this.getPolicySnapshot(context);
|
|
740
|
-
}
|
|
741
|
-
catch {
|
|
742
|
-
return writeRuntimeFailure(context, {
|
|
743
|
-
status: 500,
|
|
744
|
-
errorCode: "internal_error",
|
|
745
|
-
message: "Runtime policy is unavailable.",
|
|
746
|
-
meta: { service },
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
const result = await this.proxyRunner.run({
|
|
750
|
-
service,
|
|
751
|
-
input: body,
|
|
752
|
-
connectionName: readConnectionName(context, body),
|
|
753
|
-
connectionId: optionalString(context.req.header("x-oo-connector-app-id")),
|
|
754
|
-
policy,
|
|
755
|
-
signal: context.req.raw.signal,
|
|
756
|
-
});
|
|
757
|
-
if (result.ok) {
|
|
758
|
-
return writeRuntimeSuccess(context, result.response, result.meta);
|
|
759
|
-
}
|
|
760
|
-
return writeRuntimeFailure(context, {
|
|
761
|
-
status: result.status,
|
|
762
|
-
errorCode: result.errorCode,
|
|
763
|
-
message: result.message,
|
|
764
|
-
data: result.data,
|
|
765
|
-
meta: result.meta,
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
async listRuntimeApps(context) {
|
|
769
|
-
let policy;
|
|
770
|
-
try {
|
|
771
|
-
policy = await this.getPolicySnapshot(context);
|
|
772
|
-
}
|
|
773
|
-
catch {
|
|
774
|
-
return writeRuntimeFailure(context, {
|
|
775
|
-
status: 500,
|
|
776
|
-
errorCode: "internal_error",
|
|
777
|
-
message: "Runtime policy is unavailable.",
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
return writeRuntimeSuccess(context, this.filterAllowedConnections(policy, await this.options.connections.listConnections()).map(serializeRuntimeConnectedApp));
|
|
781
|
-
}
|
|
782
|
-
async listRuntimeAppsByService(context, service) {
|
|
783
|
-
let policy;
|
|
784
|
-
try {
|
|
785
|
-
policy = await this.getPolicySnapshot(context);
|
|
786
|
-
}
|
|
787
|
-
catch {
|
|
788
|
-
return writeRuntimeFailure(context, {
|
|
789
|
-
status: 500,
|
|
790
|
-
errorCode: "internal_error",
|
|
791
|
-
message: "Runtime policy is unavailable.",
|
|
792
|
-
meta: { service },
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
try {
|
|
796
|
-
return writeRuntimeSuccess(context, this.filterAllowedConnections(policy, await this.options.connections.listConnectionsByService(service)).map(serializeRuntimeConnectedApp));
|
|
797
|
-
}
|
|
798
|
-
catch (error) {
|
|
799
|
-
if (error instanceof ConnectionError) {
|
|
800
|
-
return writeRuntimeFailure(context, {
|
|
801
|
-
status: mapConnectionErrorStatus(error),
|
|
802
|
-
errorCode: error.code,
|
|
803
|
-
message: error.message,
|
|
804
|
-
meta: { service },
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
throw error;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
async listAuthenticatedRuntimeApps(context) {
|
|
811
|
-
const services = context.req.queries("service") ?? [];
|
|
812
|
-
let policy;
|
|
813
|
-
try {
|
|
814
|
-
policy = await this.getPolicySnapshot(context);
|
|
815
|
-
}
|
|
816
|
-
catch {
|
|
817
|
-
return writeRuntimeFailure(context, {
|
|
818
|
-
status: 500,
|
|
819
|
-
errorCode: "internal_error",
|
|
820
|
-
message: "Runtime policy is unavailable.",
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
const authenticated = new Set(this.filterAllowedConnections(policy, await this.options.connections.listConnections())
|
|
824
|
-
.filter((connection) => connection.configured && connection.authType !== "no_auth")
|
|
825
|
-
.map((connection) => connection.service));
|
|
826
|
-
return writeRuntimeSuccess(context, services.filter((service) => authenticated.has(service)));
|
|
827
|
-
}
|
|
828
|
-
filterAllowedConnections(policy, connections) {
|
|
829
|
-
return connections.filter((connection) => connection.authType === "no_auth" || policy.evaluateConnection(connection.id).allowed);
|
|
830
|
-
}
|
|
831
|
-
async handleMcp(context) {
|
|
832
|
-
const { handleMcpRequest } = await loadMcpModule();
|
|
833
|
-
return await handleMcpRequest(context.req.raw, {
|
|
834
|
-
catalog: this.options.catalog,
|
|
835
|
-
connections: this.options.connections,
|
|
836
|
-
actions: this.options.actions,
|
|
837
|
-
actionSearch: this.actionSearch,
|
|
838
|
-
getPolicySnapshot: () => this.getPolicySnapshot(context),
|
|
839
|
-
runtimeGrant: readRuntimeGrant(context),
|
|
840
|
-
signal: context.req.raw.signal,
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
rejectMcpMethod(context) {
|
|
844
|
-
return context.json({
|
|
845
|
-
jsonrpc: "2.0",
|
|
846
|
-
error: {
|
|
847
|
-
code: -32000,
|
|
848
|
-
message: "Method not allowed.",
|
|
849
|
-
},
|
|
850
|
-
id: null,
|
|
851
|
-
}, 405);
|
|
852
|
-
}
|
|
853
|
-
async listConnections(context) {
|
|
854
|
-
return context.json(await this.options.connections.listConnections());
|
|
855
|
-
}
|
|
856
|
-
async upsertConnection(context, service) {
|
|
857
|
-
const body = await readJsonBody(context);
|
|
858
|
-
const authType = optionalString(body.authType);
|
|
859
|
-
if (!authType) {
|
|
860
|
-
this.options.logger?.warn({
|
|
861
|
-
errorCode: "invalid_input",
|
|
862
|
-
path: context.req.path,
|
|
863
|
-
service,
|
|
864
|
-
}, "connection rejected");
|
|
865
|
-
return jsonError(context, 400, "invalid_input", "authType is required.");
|
|
866
|
-
}
|
|
867
|
-
const values = body.values ?? body;
|
|
868
|
-
const connectionName = readConnectionName(context, body);
|
|
869
|
-
const logContext = {
|
|
870
|
-
operation: "connect",
|
|
871
|
-
path: context.req.path,
|
|
872
|
-
service,
|
|
873
|
-
authType,
|
|
874
|
-
connectionName,
|
|
875
|
-
};
|
|
876
|
-
if (authType === "no_auth") {
|
|
877
|
-
this.options.logger?.info(logContext, "connection started");
|
|
878
|
-
return this.writeConnectionResult(context, this.options.connections.connectWithoutAuth(service, { connectionName }), logContext);
|
|
879
|
-
}
|
|
880
|
-
if (authType === "api_key") {
|
|
881
|
-
this.options.logger?.info(logContext, "connection started");
|
|
882
|
-
return this.writeConnectionResult(context, this.options.connections.connectWithApiKey(service, {
|
|
883
|
-
values,
|
|
884
|
-
connectionName,
|
|
885
|
-
signal: context.req.raw.signal,
|
|
886
|
-
}), logContext);
|
|
887
|
-
}
|
|
888
|
-
if (authType === "custom_credential") {
|
|
889
|
-
this.options.logger?.info(logContext, "connection started");
|
|
890
|
-
return this.writeConnectionResult(context, this.options.connections.connectWithCustomCredential(service, {
|
|
891
|
-
values,
|
|
892
|
-
connectionName,
|
|
893
|
-
signal: context.req.raw.signal,
|
|
894
|
-
}), logContext);
|
|
895
|
-
}
|
|
896
|
-
this.options.logger?.warn({
|
|
897
|
-
...logContext,
|
|
898
|
-
errorCode: "unsupported_auth_type",
|
|
899
|
-
}, "connection rejected");
|
|
900
|
-
return jsonError(context, 400, "unsupported_auth_type", `${service} does not support ${authType}.`);
|
|
901
|
-
}
|
|
902
|
-
async disconnect(context, service) {
|
|
903
|
-
const body = await readJsonBody(context);
|
|
904
|
-
const connectionName = readConnectionName(context, body);
|
|
905
|
-
const logContext = {
|
|
906
|
-
operation: "disconnect",
|
|
907
|
-
path: context.req.path,
|
|
908
|
-
service,
|
|
909
|
-
connectionName,
|
|
910
|
-
};
|
|
911
|
-
this.options.logger?.info(logContext, "connection disconnect started");
|
|
912
|
-
return this.writeConnectionResult(context, this.options.connections.disconnect(service, connectionName, { revoke: optionalBoolean(body.revoke) }), logContext);
|
|
913
|
-
}
|
|
914
|
-
async createOAuthAuthorization(context) {
|
|
915
|
-
const body = await readJsonBody(context);
|
|
916
|
-
const requestedService = optionalString(body.service);
|
|
917
|
-
const connectionName = readConnectionName(context, body);
|
|
918
|
-
try {
|
|
919
|
-
const service = requiredString(body.service, "service", (message) => new OAuthFlowError("invalid_input", message));
|
|
920
|
-
const logContext = {
|
|
921
|
-
path: context.req.path,
|
|
922
|
-
service,
|
|
923
|
-
connectionName,
|
|
924
|
-
};
|
|
925
|
-
this.options.logger?.info(logContext, "oauth authorization started");
|
|
926
|
-
const authorization = await this.options.oauthFlow.startAuthorization({
|
|
927
|
-
service,
|
|
928
|
-
connectionName,
|
|
929
|
-
clientConfig: readOAuthClientConfigInput(body),
|
|
930
|
-
authorizationOptionIds: readOptionalStringArray(body, "authorizationOptionIds"),
|
|
931
|
-
});
|
|
932
|
-
const authorizationUrl = new URL(authorization.authorizationUrl);
|
|
933
|
-
this.options.logger?.info({
|
|
934
|
-
...logContext,
|
|
935
|
-
authorizationHost: authorizationUrl.host,
|
|
936
|
-
redirectUri: authorizationUrl.searchParams.get("redirect_uri") ?? undefined,
|
|
937
|
-
}, "oauth authorization created");
|
|
938
|
-
return context.json(authorization);
|
|
939
|
-
}
|
|
940
|
-
catch (error) {
|
|
941
|
-
if (error instanceof OAuthClientConfigError ||
|
|
942
|
-
error instanceof OAuthFlowError ||
|
|
943
|
-
error instanceof ConnectionError) {
|
|
944
|
-
this.options.logger?.warn({
|
|
945
|
-
errorCode: error.code,
|
|
946
|
-
path: context.req.path,
|
|
947
|
-
service: requestedService,
|
|
948
|
-
connectionName,
|
|
949
|
-
}, "oauth authorization failed");
|
|
950
|
-
const status = error.code === "unknown_service" ? 404 : 400;
|
|
951
|
-
return jsonError(context, status, error.code, error.message);
|
|
952
|
-
}
|
|
953
|
-
throw error;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
async listRuntimeTokens(context) {
|
|
957
|
-
return context.json(await this.options.runtimeTokens.listTokens());
|
|
958
|
-
}
|
|
959
|
-
async createRuntimeToken(context) {
|
|
960
|
-
const body = await readJsonBody(context, policyRequestMaxBytes);
|
|
961
|
-
const name = optionalString(body.name);
|
|
962
|
-
if (!name) {
|
|
963
|
-
return jsonError(context, 400, "invalid_input", "name is required.");
|
|
964
|
-
}
|
|
965
|
-
const created = await this.options.runtimeTokens.createToken(name, readTokenPolicy(body, true));
|
|
966
|
-
return context.json({
|
|
967
|
-
token: created.token,
|
|
968
|
-
record: summarizeRuntimeToken(created.record),
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
async updateRuntimeToken(context, id) {
|
|
972
|
-
const body = await readJsonBody(context, policyRequestMaxBytes);
|
|
973
|
-
const token = await this.options.runtimeTokens.updateTokenPolicy(id, readTokenPolicy(body));
|
|
974
|
-
return token
|
|
975
|
-
? context.json(token)
|
|
976
|
-
: jsonError(context, 404, "runtime_token_not_found", `Runtime token not found: ${id}.`);
|
|
977
|
-
}
|
|
978
|
-
async revokeRuntimeToken(context, id) {
|
|
979
|
-
if (!(await this.options.runtimeTokens.revokeToken(id))) {
|
|
980
|
-
return jsonError(context, 404, "runtime_token_not_found", `Runtime token not found: ${id}.`);
|
|
981
|
-
}
|
|
982
|
-
return context.json({ id, revoked: true });
|
|
983
|
-
}
|
|
984
|
-
async getRuntimePolicy(context) {
|
|
985
|
-
return context.json((await this.getPolicySnapshot(context)).state);
|
|
986
|
-
}
|
|
987
|
-
async updateRuntimePolicy(context) {
|
|
988
|
-
const body = await readJsonBody(context, policyRequestMaxBytes);
|
|
989
|
-
const rules = readRuntimePolicyRules(body);
|
|
990
|
-
const updatedAt = new Date().toISOString();
|
|
991
|
-
await this.options.runtimePolicyStore.set({ rules, updatedAt });
|
|
992
|
-
return context.json({
|
|
993
|
-
deployment: this.actionPolicy.rules,
|
|
994
|
-
runtime: rules,
|
|
995
|
-
updatedAt,
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
async listOAuthConfigs(context) {
|
|
999
|
-
const configs = await this.options.oauthClientConfigs.listConfigs();
|
|
1000
|
-
const sources = await this.options.saasProject?.listSources();
|
|
1001
|
-
return context.json(configs.map((config) => ({
|
|
1002
|
-
...config,
|
|
1003
|
-
oauthSource: sources?.get(config.service) ?? { mode: "local" },
|
|
1004
|
-
customClientAvailable: sources?.has(config.service) ? false : config.customClientAvailable,
|
|
1005
|
-
})));
|
|
1006
|
-
}
|
|
1007
|
-
async upsertOAuthConfig(context, service) {
|
|
1008
|
-
const body = await readJsonBody(context);
|
|
1009
|
-
return this.writeOAuthResult(context, this.options.oauthClientConfigs.upsertConfig({
|
|
1010
|
-
service,
|
|
1011
|
-
clientId: optionalString(body.clientId) ?? "",
|
|
1012
|
-
clientSecret: optionalString(body.clientSecret) ?? "",
|
|
1013
|
-
requestedScopes: readOptionalStringArray(body, "requestedScopes"),
|
|
1014
|
-
redirectUri: readOptionalRawString(body, "redirectUri"),
|
|
1015
|
-
extra: optionalRecord(body.extra),
|
|
1016
|
-
secretExtra: optionalRecord(body.secretExtra),
|
|
1017
|
-
}));
|
|
1018
|
-
}
|
|
1019
|
-
async deleteOAuthConfig(context, service) {
|
|
1020
|
-
return this.writeOAuthResult(context, this.options.oauthClientConfigs.deleteConfig(service));
|
|
1021
|
-
}
|
|
1022
|
-
async completeOAuth(context) {
|
|
1023
|
-
const state = context.req.query("state");
|
|
1024
|
-
const code = context.req.query("code");
|
|
1025
|
-
const logContext = {
|
|
1026
|
-
path: context.req.path,
|
|
1027
|
-
hasState: Boolean(state),
|
|
1028
|
-
hasCode: Boolean(code),
|
|
1029
|
-
};
|
|
1030
|
-
this.options.logger?.info(logContext, "oauth callback received");
|
|
1031
|
-
const providerError = context.req.query("error");
|
|
1032
|
-
if (providerError) {
|
|
1033
|
-
const returnUri = state
|
|
1034
|
-
? await this.options.oauthFlow.rejectAuthorization(state, providerError === "access_denied")
|
|
1035
|
-
: undefined;
|
|
1036
|
-
if (returnUri)
|
|
1037
|
-
return context.redirect(returnUri);
|
|
1038
|
-
const providerErrorDescription = context.req.query("error_description");
|
|
1039
|
-
this.options.logger?.warn({
|
|
1040
|
-
...logContext,
|
|
1041
|
-
errorCode: "oauth_provider_error",
|
|
1042
|
-
providerError,
|
|
1043
|
-
providerErrorDescription,
|
|
1044
|
-
}, "oauth callback failed");
|
|
1045
|
-
return jsonError(context, 400, "oauth_provider_error", `OAuth provider returned error "${providerError}"${providerErrorDescription ? `: ${providerErrorDescription}` : "."}`);
|
|
1046
|
-
}
|
|
1047
|
-
if (!state || !code) {
|
|
1048
|
-
if (state)
|
|
1049
|
-
await this.options.oauthFlow.rejectAuthorization(state, false);
|
|
1050
|
-
this.options.logger?.warn({
|
|
1051
|
-
...logContext,
|
|
1052
|
-
errorCode: "invalid_oauth_callback",
|
|
1053
|
-
}, "oauth callback failed");
|
|
1054
|
-
return jsonError(context, 400, "invalid_oauth_callback", "OAuth callback requires state and code.");
|
|
1055
|
-
}
|
|
1056
|
-
let service;
|
|
1057
|
-
try {
|
|
1058
|
-
const callbackUrl = new URL(context.req.url);
|
|
1059
|
-
const completed = await this.options.oauthFlow.completeAuthorization({
|
|
1060
|
-
state,
|
|
1061
|
-
code,
|
|
1062
|
-
callbackParameters: Object.fromEntries(callbackUrl.searchParams),
|
|
1063
|
-
callbackQuery: callbackUrl.search,
|
|
1064
|
-
signal: context.req.raw.signal,
|
|
1065
|
-
});
|
|
1066
|
-
service = completed.service;
|
|
1067
|
-
if (completed.returnUri)
|
|
1068
|
-
return context.redirect(completed.returnUri);
|
|
1069
|
-
this.options.logger?.info({
|
|
1070
|
-
...logContext,
|
|
1071
|
-
service,
|
|
1072
|
-
}, "oauth callback completed");
|
|
1073
|
-
}
|
|
1074
|
-
catch (error) {
|
|
1075
|
-
if (error instanceof OAuthCallbackError && error.returnUri)
|
|
1076
|
-
return context.redirect(error.returnUri);
|
|
1077
|
-
if (error instanceof OAuthFlowError || error instanceof ConnectionError) {
|
|
1078
|
-
const cancelled = error instanceof ConnectionError && error.code === "connection_cancelled";
|
|
1079
|
-
this.options.logger?.[cancelled ? "info" : "warn"]({
|
|
1080
|
-
...logContext,
|
|
1081
|
-
errorCode: error.code,
|
|
1082
|
-
}, cancelled ? "oauth callback cancelled" : "oauth callback failed");
|
|
1083
|
-
return jsonError(context, error.code === "unknown_service" ? 404 : 400, error.code, error.message);
|
|
1084
|
-
}
|
|
1085
|
-
throw error;
|
|
1086
|
-
}
|
|
1087
|
-
return context.html(renderOAuthCompletionPage(service, this.options.oauthCloseDelaySeconds));
|
|
1088
|
-
}
|
|
1089
|
-
async writeConnectionResult(context, operation, logContext) {
|
|
1090
|
-
try {
|
|
1091
|
-
const result = await operation;
|
|
1092
|
-
if (logContext) {
|
|
1093
|
-
this.options.logger?.info(logContext, logContext.operation === "disconnect" ? "connection disconnect completed" : "connection completed");
|
|
1094
|
-
}
|
|
1095
|
-
return context.json(result);
|
|
1096
|
-
}
|
|
1097
|
-
catch (error) {
|
|
1098
|
-
if (error instanceof ConnectionError) {
|
|
1099
|
-
if (logContext) {
|
|
1100
|
-
const cancelled = error.code === "connection_cancelled";
|
|
1101
|
-
this.options.logger?.[cancelled ? "info" : "warn"]({
|
|
1102
|
-
...logContext,
|
|
1103
|
-
errorCode: error.code,
|
|
1104
|
-
}, cancelled
|
|
1105
|
-
? "connection cancelled"
|
|
1106
|
-
: logContext.operation === "disconnect"
|
|
1107
|
-
? "connection disconnect failed"
|
|
1108
|
-
: "connection failed");
|
|
1109
|
-
}
|
|
1110
|
-
return jsonError(context, error.code === "unknown_service" ? 404 : 400, error.code, error.message);
|
|
1111
|
-
}
|
|
1112
|
-
throw error;
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
async writeOAuthResult(context, operation) {
|
|
1116
|
-
try {
|
|
1117
|
-
return context.json(await operation);
|
|
1118
|
-
}
|
|
1119
|
-
catch (error) {
|
|
1120
|
-
if (error instanceof OAuthClientConfigError || error instanceof OAuthFlowError) {
|
|
1121
|
-
return jsonError(context, error.code === "unknown_service" ? 404 : 400, error.code, error.message);
|
|
1122
|
-
}
|
|
1123
|
-
if (error instanceof HttpRequestError) {
|
|
1124
|
-
return jsonError(context, 400, error.code, error.message);
|
|
1125
|
-
}
|
|
1126
|
-
throw error;
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
getPolicySnapshot(context) {
|
|
1130
|
-
const request = context.req.raw;
|
|
1131
|
-
let snapshot = this.policySnapshots.get(request);
|
|
1132
|
-
if (!snapshot) {
|
|
1133
|
-
snapshot = this.loadPolicySnapshot(context);
|
|
1134
|
-
this.policySnapshots.set(request, snapshot);
|
|
1135
|
-
}
|
|
1136
|
-
return snapshot;
|
|
1137
|
-
}
|
|
1138
|
-
async loadPolicySnapshot(context) {
|
|
1139
|
-
try {
|
|
1140
|
-
const record = await this.options.runtimePolicyStore.get();
|
|
1141
|
-
return this.actionPolicy.createSnapshot(record?.rules ?? emptyPolicyRules(), readRuntimeGrant(context), record?.updatedAt);
|
|
1142
|
-
}
|
|
1143
|
-
catch {
|
|
1144
|
-
this.options.logger?.error({
|
|
1145
|
-
method: context.req.method,
|
|
1146
|
-
path: context.req.path,
|
|
1147
|
-
}, "runtime policy load failed");
|
|
1148
|
-
throw new Error("Runtime policy is unavailable.");
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
function readOAuthClientConfigInput(body) {
|
|
1153
|
-
const keys = ["clientId", "clientSecret", "requestedScopes", "redirectUri", "extra", "secretExtra"];
|
|
1154
|
-
if (!keys.some((key) => key in body)) {
|
|
1155
|
-
return undefined;
|
|
1156
|
-
}
|
|
1157
|
-
return {
|
|
1158
|
-
clientId: optionalString(body.clientId) ?? "",
|
|
1159
|
-
clientSecret: optionalString(body.clientSecret) ?? "",
|
|
1160
|
-
requestedScopes: readOptionalStringArray(body, "requestedScopes"),
|
|
1161
|
-
redirectUri: readOptionalRawString(body, "redirectUri"),
|
|
1162
|
-
extra: optionalRecord(body.extra),
|
|
1163
|
-
secretExtra: optionalRecord(body.secretExtra),
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
/** An absent field is undefined; a present one, null included, must be a string (blank is kept for the caller to read as unset). */
|
|
1167
|
-
function readOptionalRawString(body, fieldName) {
|
|
1168
|
-
if (!(fieldName in body))
|
|
1169
|
-
return undefined;
|
|
1170
|
-
return requiredRawString(body[fieldName], fieldName, (message) => new HttpRequestError("invalid_input", `${message}.`));
|
|
1171
|
-
}
|
|
1172
|
-
function readOptionalStringArray(body, fieldName) {
|
|
1173
|
-
if (!(fieldName in body))
|
|
1174
|
-
return undefined;
|
|
1175
|
-
return requiredStringArray(body[fieldName], fieldName, (message) => new HttpRequestError("invalid_input", `${message}.`));
|
|
1176
|
-
}
|
|
1177
|
-
/**
|
|
1178
|
-
* RFC 7232 `If-None-Match` check. Handles `*`, comma-separated lists, and the
|
|
1179
|
-
* weak-comparison prefix (`W/`) so a validator round-tripped through gzip (which
|
|
1180
|
-
* downgrades strong to weak) still matches.
|
|
1181
|
-
*/
|
|
1182
|
-
function requestMatchesEtag(ifNoneMatch, etag) {
|
|
1183
|
-
if (!ifNoneMatch) {
|
|
1184
|
-
return false;
|
|
1185
|
-
}
|
|
1186
|
-
if (ifNoneMatch.trim() === "*") {
|
|
1187
|
-
return true;
|
|
1188
|
-
}
|
|
1189
|
-
const target = stripWeakPrefix(etag);
|
|
1190
|
-
return ifNoneMatch.split(",").some((candidate) => stripWeakPrefix(candidate.trim()) === target);
|
|
1191
|
-
}
|
|
1192
|
-
function stripWeakPrefix(etag) {
|
|
1193
|
-
return etag.startsWith("W/") ? etag.slice(2) : etag;
|
|
1194
|
-
}
|
|
1195
|
-
function readConnectionName(context, body) {
|
|
1196
|
-
return (optionalString(body?.connectionName) ??
|
|
1197
|
-
optionalString(body?.alias) ??
|
|
1198
|
-
optionalString(context.req.header("x-oo-connector-alias")) ??
|
|
1199
|
-
optionalString(context.req.query("connectionName")) ??
|
|
1200
|
-
optionalString(context.req.query("alias")));
|
|
1201
|
-
}
|
|
1202
|
-
function serializeActionSearchResult(result, action, authenticated) {
|
|
1203
|
-
return {
|
|
1204
|
-
id: result.id,
|
|
1205
|
-
service: result.service,
|
|
1206
|
-
name: result.name,
|
|
1207
|
-
description: result.description,
|
|
1208
|
-
operationType: action.operationType,
|
|
1209
|
-
authenticated,
|
|
1210
|
-
inputSchema: action.inputSchema,
|
|
1211
|
-
outputSchema: action.outputSchema,
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
function readRunLogListInput(context) {
|
|
1215
|
-
const rawLimit = optionalString(context.req.query("limit"));
|
|
1216
|
-
const limit = rawLimit === undefined ? 50 : Number(rawLimit);
|
|
1217
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
1218
|
-
return { ok: false, message: "limit must be an integer between 1 and 100." };
|
|
1219
|
-
}
|
|
1220
|
-
const cursor = optionalString(context.req.query("cursor"));
|
|
1221
|
-
if (cursor !== undefined) {
|
|
1222
|
-
try {
|
|
1223
|
-
decodeRunLogCursor(cursor);
|
|
1224
|
-
}
|
|
1225
|
-
catch {
|
|
1226
|
-
return { ok: false, message: "cursor is invalid." };
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
const input = { limit };
|
|
1230
|
-
if (cursor !== undefined) {
|
|
1231
|
-
input.cursor = cursor;
|
|
1232
|
-
}
|
|
1233
|
-
const service = optionalString(context.req.query("service"));
|
|
1234
|
-
if (service !== undefined) {
|
|
1235
|
-
input.service = service;
|
|
1236
|
-
}
|
|
1237
|
-
const actionId = optionalString(context.req.query("actionId"));
|
|
1238
|
-
if (actionId !== undefined) {
|
|
1239
|
-
if (actionId.length > 256) {
|
|
1240
|
-
return { ok: false, message: "actionId must be at most 256 characters." };
|
|
1241
|
-
}
|
|
1242
|
-
input.actionId = actionId;
|
|
1243
|
-
}
|
|
1244
|
-
const caller = optionalString(context.req.query("caller"));
|
|
1245
|
-
if (caller !== undefined) {
|
|
1246
|
-
if (!isRunLogCaller(caller)) {
|
|
1247
|
-
return { ok: false, message: "caller must be one of http, mcp, or web." };
|
|
1248
|
-
}
|
|
1249
|
-
input.caller = caller;
|
|
1250
|
-
}
|
|
1251
|
-
const ok = optionalString(context.req.query("ok"));
|
|
1252
|
-
if (ok !== undefined) {
|
|
1253
|
-
if (ok !== "true" && ok !== "false") {
|
|
1254
|
-
return { ok: false, message: "ok must be true or false." };
|
|
1255
|
-
}
|
|
1256
|
-
input.ok = ok === "true";
|
|
1257
|
-
}
|
|
1258
|
-
return { ok: true, input };
|
|
1259
|
-
}
|
|
1260
|
-
function isRunLogCaller(value) {
|
|
1261
|
-
return value === "http" || value === "mcp" || value === "web";
|
|
1262
|
-
}
|
|
1263
|
-
function readSearchQuery(context, defaultLimit = DEFAULT_ACTION_SEARCH_LIMIT) {
|
|
1264
|
-
const q = optionalString(context.req.query("q") ?? context.req.query("query"));
|
|
1265
|
-
if (!q || q.length > 256) {
|
|
1266
|
-
return { ok: false, message: "q must be a non-empty string of at most 256 characters." };
|
|
1267
|
-
}
|
|
1268
|
-
const rawLimit = optionalString(context.req.query("limit"));
|
|
1269
|
-
if (!rawLimit) {
|
|
1270
|
-
return {
|
|
1271
|
-
ok: true,
|
|
1272
|
-
q,
|
|
1273
|
-
service: optionalString(context.req.query("service")),
|
|
1274
|
-
limit: defaultLimit,
|
|
1275
|
-
};
|
|
1276
|
-
}
|
|
1277
|
-
const limit = Number(rawLimit);
|
|
1278
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
|
|
1279
|
-
return { ok: false, message: "limit must be an integer between 1 and 50." };
|
|
1280
|
-
}
|
|
1281
|
-
return {
|
|
1282
|
-
ok: true,
|
|
1283
|
-
q,
|
|
1284
|
-
service: optionalString(context.req.query("service")),
|
|
1285
|
-
limit,
|
|
1286
|
-
};
|
|
1287
234
|
}
|