@oomol-lab/open-connector 1.6.4 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/open-connector/catalog/apps/aisa.json +6394 -0
- package/assets/open-connector/catalog/apps/baizhi_mcp.json +135 -0
- package/assets/open-connector/catalog/apps/bilibili.json +2586 -0
- package/assets/open-connector/catalog/apps/bitwarden.json +1423 -0
- package/assets/open-connector/catalog/apps/cronfree.json +303 -0
- package/assets/open-connector/catalog/apps/cufinder.json +368 -0
- package/assets/open-connector/catalog/apps/discordbot.json +2888 -248
- package/assets/open-connector/catalog/apps/drawio_mcp.json +192 -0
- package/assets/open-connector/catalog/apps/firecrawl.json +176 -32
- package/assets/open-connector/catalog/apps/framejet.json +273 -0
- package/assets/open-connector/catalog/apps/futunn.json +2967 -0
- package/assets/open-connector/catalog/apps/gmail.json +298 -0
- package/assets/open-connector/catalog/apps/google_analytics.json +4 -1
- package/assets/open-connector/catalog/apps/google_bigquery.json +4 -1
- package/assets/open-connector/catalog/apps/google_search_console.json +4 -1
- package/assets/open-connector/catalog/apps/googlecalendar.json +4 -1
- package/assets/open-connector/catalog/apps/googlechat.json +694 -7
- package/assets/open-connector/catalog/apps/googledocs.json +4 -1
- package/assets/open-connector/catalog/apps/googlesheets.json +4 -1
- package/assets/open-connector/catalog/apps/granola.json +23 -0
- package/assets/open-connector/catalog/apps/investoday_mcp.json +22228 -197
- package/assets/open-connector/catalog/apps/ixspy.json +2274 -0
- package/assets/open-connector/catalog/apps/keepa.json +3534 -987
- package/assets/open-connector/catalog/apps/linear.json +17 -0
- package/assets/open-connector/catalog/apps/linear_mcp.json +510 -0
- package/assets/open-connector/catalog/apps/moneyforward_mcp.json +1432 -0
- package/assets/open-connector/catalog/apps/notion.json +446 -1
- package/assets/open-connector/catalog/apps/notion_mcp.json +490 -0
- package/assets/open-connector/catalog/apps/odoo.json +619 -0
- package/assets/open-connector/catalog/apps/osv.json +663 -0
- package/assets/open-connector/catalog/apps/outlook.json +30 -8
- package/assets/open-connector/catalog/apps/outlook_calendar.json +23 -2
- package/assets/open-connector/catalog/apps/pdf_vector.json +329 -0
- package/assets/open-connector/catalog/apps/postiz.json +334 -0
- package/assets/open-connector/catalog/apps/proworkflow.json +529 -0
- package/assets/open-connector/catalog/apps/pulsetic.json +406 -0
- package/assets/open-connector/catalog/apps/sellersprite_mcp.json +2 -1
- package/assets/open-connector/catalog/apps/shopee.json +1785 -0
- package/assets/open-connector/catalog/apps/slack.json +304 -0
- package/assets/open-connector/catalog/apps/slackbot.json +295 -0
- package/assets/open-connector/catalog/apps/speak_ai.json +360 -0
- package/assets/open-connector/catalog/apps/tencent_docs.json +3 -0
- package/assets/open-connector/catalog/apps/wisburg.json +1528 -0
- package/assets/open-connector/catalog/apps/youtube.json +4 -1
- package/assets/open-connector/catalog/apps/zerotier.json +3319 -0
- package/assets/open-connector/catalog/apps/zyhub.json +1585 -0
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +1 -1
- package/src/core/cast.d.ts +10 -0
- package/src/core/cast.js +14 -0
- package/src/core/guarded-fetch.d.ts +6 -3
- package/src/core/guarded-fetch.js +6 -3
- package/src/core/json-schema.d.ts +5 -1
- package/src/core/json-schema.js +14 -2
- package/src/core/provider-scenarios.js +67 -13
- package/src/core/request.js +4 -1
- package/src/core/types.d.ts +10 -1
- package/src/mcp.js +10 -7
- package/src/oauth/oauth-client-config-service.d.ts +16 -1
- package/src/oauth/oauth-client-config-service.js +59 -3
- package/src/oauth/oauth-credential-refresh-service.js +3 -1
- package/src/oauth/oauth-flow-service.d.ts +6 -0
- package/src/oauth/oauth-flow-service.js +21 -4
- package/src/oauth/oauth-token.d.ts +13 -0
- package/src/providers/17track/runtime.js +1 -1
- package/src/providers/action-contracts.generated.d.ts +32 -8
- package/src/providers/aifinmarket/runtime.js +1 -1
- package/src/providers/aisa/executors.d.ts +4 -0
- package/src/providers/aisa/executors.js +864 -0
- package/src/providers/aliyun_ack/runtime.js +1 -1
- package/src/providers/aliyun_sls/runtime.js +1 -1
- package/src/providers/app_store_server/runtime-helpers.js +1 -1
- package/src/providers/baizhi_mcp/actions.d.ts +2 -0
- package/src/providers/baizhi_mcp/actions.js +40 -0
- package/src/providers/baizhi_mcp/executors.d.ts +3 -0
- package/src/providers/baizhi_mcp/executors.js +102 -0
- package/src/providers/bazhuayu/runtime.js +2 -2
- package/src/providers/bilibili/articles.d.ts +3 -0
- package/src/providers/bilibili/articles.js +369 -0
- package/src/providers/bilibili/executors.d.ts +8 -0
- package/src/providers/bilibili/executors.js +262 -0
- package/src/providers/bilibili/runtime.d.ts +95 -0
- package/src/providers/bilibili/runtime.js +236 -0
- package/src/providers/bilibili/upload.d.ts +14 -0
- package/src/providers/bilibili/upload.js +107 -0
- package/src/providers/bitwarden/executors.d.ts +3 -0
- package/src/providers/bitwarden/executors.js +146 -0
- package/src/providers/cloudflare_browser_rendering/executors.js +1 -1
- package/src/providers/cloudflare_dns/executors.js +1 -1
- package/src/providers/{cloudflare-current-user.js → cloudflare_dns/runtime-user.js} +2 -2
- package/src/providers/cloudflare_dns/runtime.d.ts +1 -1
- package/src/providers/cloudflare_dns/runtime.js +1 -1
- package/src/providers/cloudflare_r2/executors.js +1 -1
- package/src/providers/cloudflare_r2/runtime.d.ts +1 -1
- package/src/providers/cloudflare_r2/runtime.js +1 -1
- package/src/providers/cloudflare_worker/executors.js +1 -1
- package/src/providers/cloudflare_worker/runtime.d.ts +1 -1
- package/src/providers/cloudflare_worker/runtime.js +1 -1
- package/src/providers/cronfree/executors.d.ts +4 -0
- package/src/providers/cronfree/executors.js +89 -0
- package/src/providers/cufinder/executors.d.ts +4 -0
- package/src/providers/cufinder/executors.js +105 -0
- package/src/providers/dingtalk_mcp/runtime.js +1 -1
- package/src/providers/discordbot/executors.d.ts +0 -9
- package/src/providers/discordbot/executors.js +9 -101
- package/src/providers/discordbot/runtime-guilds.d.ts +3 -0
- package/src/providers/discordbot/runtime-guilds.js +464 -0
- package/src/providers/discordbot/runtime.d.ts +42 -0
- package/src/providers/discordbot/runtime.js +124 -0
- package/src/providers/drawio_mcp/executors.d.ts +3 -0
- package/src/providers/drawio_mcp/executors.js +19 -0
- package/src/providers/drawio_mcp/runtime.d.ts +8 -0
- package/src/providers/drawio_mcp/runtime.js +175 -0
- package/src/providers/elevenreader/runtime.js +6 -5
- package/src/providers/fastmoss_mcp/executors.js +2 -2
- package/src/providers/finchina/executors.js +1 -1
- package/src/providers/framejet/executors.d.ts +4 -0
- package/src/providers/framejet/executors.js +19 -0
- package/src/providers/framejet/runtime.d.ts +16 -0
- package/src/providers/framejet/runtime.js +148 -0
- package/src/providers/futunn/actions.d.ts +2 -0
- package/src/providers/futunn/actions.js +152 -0
- package/src/providers/futunn/actions.research.d.ts +2 -0
- package/src/providers/futunn/actions.research.js +423 -0
- package/src/providers/futunn/actions.trading.d.ts +2 -0
- package/src/providers/futunn/actions.trading.js +204 -0
- package/src/providers/futunn/executors.d.ts +3 -0
- package/src/providers/futunn/executors.js +24 -0
- package/src/providers/futunn/runtime-research.d.ts +3 -0
- package/src/providers/futunn/runtime-research.js +133 -0
- package/src/providers/futunn/runtime-trading.d.ts +3 -0
- package/src/providers/futunn/runtime-trading.js +39 -0
- package/src/providers/futunn/runtime.d.ts +2 -0
- package/src/providers/futunn/runtime.js +50 -0
- package/src/providers/futunn/transport.d.ts +11 -0
- package/src/providers/futunn/transport.js +76 -0
- package/src/providers/gildata/executors.js +1 -1
- package/src/providers/gmail/attachment-stream.d.ts +2 -0
- package/src/providers/gmail/attachment-stream.js +162 -0
- package/src/providers/gmail/executors.d.ts +3 -1
- package/src/providers/gmail/executors.js +54 -26
- package/src/providers/gmail/message.d.ts +6 -8
- package/src/providers/gmail/message.js +4 -0
- package/src/providers/gmail/scopes.d.ts +32 -0
- package/src/providers/gmail/scopes.js +37 -0
- package/src/providers/google_analytics/executors.js +2 -2
- package/src/providers/google_analytics/scopes.js +6 -1
- package/src/providers/google_bigquery/executors.js +2 -2
- package/src/providers/google_bigquery/scopes.js +2 -0
- package/src/providers/google_search_console/executors.js +2 -2
- package/src/providers/google_search_console/scopes.js +2 -0
- package/src/providers/googleads/executors.js +1 -1
- package/src/providers/googleads/scopes.d.ts +1 -3
- package/src/providers/googleads/scopes.js +2 -4
- package/src/providers/googlecalendar/executors.js +1 -1
- package/src/providers/googlecalendar/runtime-shared.js +2 -2
- package/src/providers/googlecalendar/scopes.js +2 -0
- package/src/providers/googlechat/executors.d.ts +1 -3
- package/src/providers/googlechat/executors.js +219 -28
- package/src/providers/googlechat/members.d.ts +55 -0
- package/src/providers/googlechat/members.js +293 -0
- package/src/providers/googlechat/runtime.d.ts +16 -0
- package/src/providers/googlechat/runtime.js +25 -0
- package/src/providers/googlechat/scopes.d.ts +22 -3
- package/src/providers/googlechat/scopes.js +30 -7
- package/src/providers/googledocs/executors.js +1 -1
- package/src/providers/googledrive/executors.js +2 -2
- package/src/providers/{google-auth.d.ts → googledrive/runtime-auth.d.ts} +2 -2
- package/src/providers/{google-auth.js → googledrive/runtime-auth.js} +3 -3
- package/src/providers/googledrive/runtime-collaboration.js +1 -1
- package/src/providers/{google-runtime.d.ts → googledrive/runtime-request.d.ts} +1 -1
- package/src/providers/{google-runtime.js → googledrive/runtime-request.js} +2 -2
- package/src/providers/googleforms/executors.js +2 -2
- package/src/providers/googleforms/scopes.d.ts +0 -3
- package/src/providers/googleforms/scopes.js +2 -6
- package/src/providers/googlemeet/executors.js +2 -2
- package/src/providers/googlemeet/scopes.d.ts +0 -3
- package/src/providers/googlemeet/scopes.js +2 -6
- package/src/providers/googlesheets/executors.js +1 -1
- package/src/providers/googlesheets/scopes.js +2 -0
- package/src/providers/googleslides/executors.js +2 -2
- package/src/providers/googleslides/scopes.d.ts +0 -3
- package/src/providers/googleslides/scopes.js +2 -6
- package/src/providers/googletasks/executors.js +2 -2
- package/src/providers/googletasks/scopes.d.ts +0 -3
- package/src/providers/googletasks/scopes.js +2 -6
- package/src/providers/granola/actions.js +2 -0
- package/src/providers/granola/runtime-mcp.js +2 -0
- package/src/providers/granola/runtime.js +2 -2
- package/src/providers/indiegogo/executors.js +1 -1
- package/src/providers/investoday_mcp/executors.js +2 -2
- package/src/providers/investoday_mcp/official-actions.js +19 -372
- package/src/providers/investoday_mcp/official-tool-types.d.ts +10 -0
- package/src/providers/investoday_mcp/official-tool-types.js +4 -0
- package/src/providers/investoday_mcp/official-tools/charts.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/charts.js +32 -0
- package/src/providers/investoday_mcp/official-tools/fixed-income.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/fixed-income.js +326 -0
- package/src/providers/investoday_mcp/official-tools/funds.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/funds.js +1085 -0
- package/src/providers/investoday_mcp/official-tools/hong-kong.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/hong-kong.js +275 -0
- package/src/providers/investoday_mcp/official-tools/indices-industries.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/indices-industries.js +642 -0
- package/src/providers/investoday_mcp/official-tools/macro-calendar.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/macro-calendar.js +398 -0
- package/src/providers/investoday_mcp/official-tools/other.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/other.js +131 -0
- package/src/providers/investoday_mcp/official-tools/research-news.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/research-news.js +302 -0
- package/src/providers/investoday_mcp/official-tools/stocks.d.ts +2 -0
- package/src/providers/investoday_mcp/official-tools/stocks.js +2829 -0
- package/src/providers/ixspy/executors.d.ts +3 -0
- package/src/providers/ixspy/executors.js +220 -0
- package/src/providers/iyunbiao/runtime.js +2 -2
- package/src/providers/keepa/actions.d.ts +1 -1
- package/src/providers/keepa/actions.js +371 -149
- package/src/providers/keepa/deal-attributes.d.ts +1 -0
- package/src/providers/keepa/deal-attributes.js +26 -0
- package/src/providers/keepa/runtime.js +212 -18
- package/src/providers/kingdee/executors.js +1 -1
- package/src/providers/kingdee/runtime.js +2 -2
- package/src/providers/kuaidi100/runtime.js +1 -1
- package/src/providers/linear/executors.js +12 -9
- package/src/providers/linear_mcp/actions.d.ts +47 -0
- package/src/providers/linear_mcp/actions.js +102 -0
- package/src/providers/linear_mcp/endpoints.d.ts +3 -0
- package/src/providers/linear_mcp/endpoints.js +3 -0
- package/src/providers/linear_mcp/executors.d.ts +3 -0
- package/src/providers/linear_mcp/executors.js +9 -0
- package/src/providers/linear_mcp/runtime-mcp.d.ts +15 -0
- package/src/providers/linear_mcp/runtime-mcp.js +238 -0
- package/src/providers/lingxing_mcp/runtime.js +1 -1
- package/src/providers/mcdonalds_cn_mcp/runtime.js +1 -1
- package/src/providers/metabase/mcp-runtime.js +1 -1
- package/src/providers/moneyforward_mcp/executors.d.ts +3 -0
- package/src/providers/moneyforward_mcp/executors.js +11 -0
- package/src/providers/moneyforward_mcp/manifest.d.ts +31 -0
- package/src/providers/moneyforward_mcp/manifest.js +204 -0
- package/src/providers/moneyforward_mcp/runtime.d.ts +18 -0
- package/src/providers/moneyforward_mcp/runtime.js +296 -0
- package/src/providers/notion/executors.js +39 -3
- package/src/providers/notion_mcp/endpoints.d.ts +3 -0
- package/src/providers/notion_mcp/endpoints.js +3 -0
- package/src/providers/notion_mcp/executors.d.ts +3 -0
- package/src/providers/notion_mcp/executors.js +176 -0
- package/src/providers/notion_mcp/mcp-response.d.ts +86 -0
- package/src/providers/notion_mcp/mcp-response.js +314 -0
- package/src/providers/odoo/executors.d.ts +3 -0
- package/src/providers/odoo/executors.js +32 -0
- package/src/providers/odoo/runtime.d.ts +16 -0
- package/src/providers/odoo/runtime.js +140 -0
- package/src/providers/osv/executors.d.ts +2 -0
- package/src/providers/osv/executors.js +11 -0
- package/src/providers/osv/runtime.d.ts +9 -0
- package/src/providers/osv/runtime.js +66 -0
- package/src/providers/outlook/executors.js +27 -6
- package/src/providers/outlook/microsoft-graph.js +17 -6
- package/src/providers/outlook_calendar/executors.js +24 -7
- package/src/providers/patsnap_mcp/executors.js +2 -2
- package/src/providers/pdf_vector/executors.d.ts +4 -0
- package/src/providers/pdf_vector/executors.js +108 -0
- package/src/providers/postiz/executors.d.ts +4 -0
- package/src/providers/postiz/executors.js +83 -0
- package/src/providers/provider-runtime.d.ts +18 -0
- package/src/providers/provider-runtime.js +60 -4
- package/src/providers/proworkflow/executors.d.ts +4 -0
- package/src/providers/proworkflow/executors.js +135 -0
- package/src/providers/pulsetic/executors.d.ts +4 -0
- package/src/providers/pulsetic/executors.js +126 -0
- package/src/providers/registry.generated.js +24 -0
- package/src/providers/scrape_creators/catalog.js +1 -1
- package/src/providers/scrape_creators/runtime.js +1 -1
- package/src/providers/sellersprite_mcp/executors.js +2 -2
- package/src/providers/sellersprite_mcp/official-actions.js +3 -2
- package/src/providers/sentry/runtime.js +6 -7
- package/src/providers/shopee/actions.d.ts +2 -0
- package/src/providers/shopee/actions.js +474 -0
- package/src/providers/shopee/executors.d.ts +3 -0
- package/src/providers/shopee/executors.js +85 -0
- package/src/providers/shopee/runtime.d.ts +79 -0
- package/src/providers/shopee/runtime.js +693 -0
- package/src/providers/slack/actions.js +69 -3
- package/src/providers/slack/runtime.js +116 -21
- package/src/providers/slickdeals/runtime.js +1 -1
- package/src/providers/sorftime_mcp/executors.js +2 -2
- package/src/providers/speak_ai/executors.d.ts +3 -0
- package/src/providers/speak_ai/executors.js +187 -0
- package/src/providers/tikhub/bounded-response.d.ts +1 -1
- package/src/providers/tikhub/bounded-response.js +8 -34
- package/src/providers/tikhub/endpoint-catalog.js +1 -1
- package/src/providers/tikhub/runtime.js +2 -2
- package/src/providers/tongdaxin/executors.js +79 -15
- package/src/providers/tushare/runtime.js +22 -9
- package/src/providers/uspto/executors.js +1 -1
- package/src/providers/waffo/executors.js +1 -1
- package/src/providers/waffo/runtime.js +1 -1
- package/src/providers/wecom_mcp/runtime.js +1 -1
- package/src/providers/wisburg/executors.d.ts +4 -0
- package/src/providers/wisburg/executors.js +13 -0
- package/src/providers/wisburg/runtime.d.ts +12 -0
- package/src/providers/wisburg/runtime.js +133 -0
- package/src/providers/xydc_mcp/executors.js +2 -2
- package/src/providers/youtube/executors.js +1 -1
- package/src/providers/youtube/runtime.js +1 -1
- package/src/providers/zerotier/executors.d.ts +9 -0
- package/src/providers/zerotier/executors.js +629 -0
- package/src/providers/zerotier/runtime.d.ts +54 -0
- package/src/providers/zerotier/runtime.js +207 -0
- package/src/providers/zyhub/executors.d.ts +3 -0
- package/src/providers/zyhub/executors.js +240 -0
- package/src/server/api/openapi.js +12 -2
- package/src/server/api/runtime-api.d.ts +8 -1
- package/src/server/api/runtime-api.js +19 -1
- package/src/server/connect-server.js +10 -2
- package/src/server/files/transit-files.d.ts +3 -2
- package/src/server/files/transit-files.js +31 -35
- package/src/providers/sentry/scopes.d.ts +0 -1
- package/src/providers/sentry/scopes.js +0 -7
- /package/src/providers/{cloudflare-current-user.d.ts → cloudflare_dns/runtime-user.d.ts} +0 -0
|
@@ -50,6 +50,16 @@ const messageContentProperties = {
|
|
|
50
50
|
unfurlMedia: s.boolean({ description: "Whether Slack should unfurl media in the message." }),
|
|
51
51
|
metadata: s.unknownObject("Slack message metadata to attach to the message."),
|
|
52
52
|
};
|
|
53
|
+
// Shared by `conversations.history` and `conversations.replies`: opt in to
|
|
54
|
+
// the untouched vendor record. Off by default so the row read in bulk stays
|
|
55
|
+
// lean. Opting in also asks Slack for message metadata, which it leaves out
|
|
56
|
+
// unless the request sets `include_all_metadata`.
|
|
57
|
+
const includeRawProperties = {
|
|
58
|
+
includeRaw: s.boolean({
|
|
59
|
+
description: "When true, each returned message also carries the untouched Slack record under raw, including its blocks, attachments, files and message metadata. Defaults to false.",
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
const rawMessageSchema = s.unknownObject("The untouched Slack record, exactly as the Slack API returned it. Present only when includeRaw is true.");
|
|
53
63
|
const slackReactionSchema = s.looseObject({
|
|
54
64
|
name: s.string({ description: "The emoji name of the reaction." }),
|
|
55
65
|
count: s.integer({ description: "How many users added this reaction." }),
|
|
@@ -60,9 +70,10 @@ const slackReactionSchema = s.looseObject({
|
|
|
60
70
|
// Only fields the executor actually normalizes are declared. The object is
|
|
61
71
|
// loose, but that permits extras it does NOT make them appear: the executor
|
|
62
72
|
// builds each row explicitly, so an undeclared Slack field is simply not
|
|
63
|
-
// emitted. Deliberately absent: `blocks`, `attachments
|
|
64
|
-
// are unbounded nested payloads on a row shape that ETL
|
|
65
|
-
//
|
|
73
|
+
// emitted. Deliberately absent: `blocks`, `attachments`, `files` and
|
|
74
|
+
// `metadata`. Those are unbounded nested payloads on a row shape that ETL
|
|
75
|
+
// reads in bulk, so they stay out of the default row; a caller that needs
|
|
76
|
+
// them passes includeRaw and reads them from `raw`, the untouched record.
|
|
66
77
|
const slackMessageSchema = s.looseObject({
|
|
67
78
|
ts: s.string({ description: "The message timestamp identifier." }),
|
|
68
79
|
type: s.string({ description: "The Slack message type, normally 'message'." }),
|
|
@@ -77,15 +88,20 @@ const slackMessageSchema = s.looseObject({
|
|
|
77
88
|
clientMsgId: s.string({ description: "The client-generated message identifier when Slack returns one." }),
|
|
78
89
|
text: s.string({ description: "The text content of the message." }),
|
|
79
90
|
editedTs: s.string({ description: "The timestamp of the most recent edit, when the message was edited." }),
|
|
91
|
+
editedUserId: s.string({ description: "The user ID of the most recent editor, when the message was edited." }),
|
|
80
92
|
threadTs: s.string({
|
|
81
93
|
description: "The timestamp of the thread parent. Equal to ts on a thread parent, and absent on a message that is not in a thread.",
|
|
82
94
|
}),
|
|
83
95
|
parentUserId: s.string({ description: "The author of the thread parent, on a threaded reply." }),
|
|
84
96
|
replyCount: s.integer({ description: "The number of replies to this thread parent." }),
|
|
85
97
|
replyUsersCount: s.integer({ description: "The number of distinct users who replied to this thread parent." }),
|
|
98
|
+
replyUserIds: s.array(s.string({ description: "A Slack user ID." }), {
|
|
99
|
+
description: "Up to five user IDs of people who replied to this thread parent. Slack caps this list; use replyUsersCount for the total.",
|
|
100
|
+
}),
|
|
86
101
|
latestReply: s.string({ description: "The timestamp of the most recent reply to this thread parent." }),
|
|
87
102
|
isLocked: s.boolean({ description: "Whether the thread is locked." }),
|
|
88
103
|
reactions: s.array(slackReactionSchema, { description: "Reaction summaries attached to the message." }),
|
|
104
|
+
raw: rawMessageSchema,
|
|
89
105
|
}, { description: "A Slack message record." });
|
|
90
106
|
const searchMessageMatchSchema = s.looseObject({
|
|
91
107
|
matchId: s.string({ description: "Slack's search result item identifier." }),
|
|
@@ -98,6 +114,7 @@ const searchMessageMatchSchema = s.looseObject({
|
|
|
98
114
|
permalink: s.string({ description: "A Slack permalink for the matching message." }),
|
|
99
115
|
teamId: s.string({ description: "The Slack team ID returned for the match." }),
|
|
100
116
|
type: s.string({ description: "The Slack result type." }),
|
|
117
|
+
raw: rawMessageSchema,
|
|
101
118
|
}, { description: "A normalized Slack message search match." });
|
|
102
119
|
const conversationSchema = s.object({
|
|
103
120
|
channelId: s.string({ description: "The unique identifier of the conversation." }),
|
|
@@ -113,6 +130,21 @@ const conversationSchema = s.object({
|
|
|
113
130
|
purpose: s.nullable(s.string({ description: "The conversation purpose." })),
|
|
114
131
|
userId: s.string({ description: "The linked user identifier for IM conversations." }),
|
|
115
132
|
locale: s.string({ description: "The locale returned by Slack when requested." }),
|
|
133
|
+
created: s.integer({ description: "Creation time as a Unix timestamp in seconds when Slack provides it." }),
|
|
134
|
+
updated: s.integer({
|
|
135
|
+
description: "Last settings update time, passed through as Slack returns it. Slack documents epoch milliseconds for channels (unlike created) and a Unix timestamp for legacy IM and MPIM objects.",
|
|
136
|
+
}),
|
|
137
|
+
creatorId: s.string({ description: "The user who created the conversation when Slack provides it." }),
|
|
138
|
+
isShared: s.boolean({ description: "Whether the conversation is shared with another workspace." }),
|
|
139
|
+
isExtShared: s.boolean({ description: "Whether the conversation is shared with an external organization." }),
|
|
140
|
+
isOrgShared: s.boolean({
|
|
141
|
+
description: "Whether the conversation is shared between workspaces of the same Enterprise organization.",
|
|
142
|
+
}),
|
|
143
|
+
contextTeamId: s.string({
|
|
144
|
+
description: "The ID of the workspace the conversation is within when Slack provides it.",
|
|
145
|
+
}),
|
|
146
|
+
lastRead: s.string({ description: "The last-read message timestamp when Slack provides it." }),
|
|
147
|
+
unreadCount: s.integer({ description: "The unread message count when Slack provides it." }),
|
|
116
148
|
}, {
|
|
117
149
|
required: ["channelId", "name", "type", "isArchived", "isPrivate", "isMember", "topic", "purpose"],
|
|
118
150
|
description: "A normalized Slack conversation record.",
|
|
@@ -127,6 +159,18 @@ const userSchema = s.object({
|
|
|
127
159
|
isAdmin: s.nullable(s.boolean({ description: "Whether the user is an admin." })),
|
|
128
160
|
isOwner: s.nullable(s.boolean({ description: "Whether the user is an owner." })),
|
|
129
161
|
locale: s.string({ description: "The locale returned by Slack when requested." }),
|
|
162
|
+
email: s.string({
|
|
163
|
+
description: "The profile email. Slack returns it only when the token holds the users:read.email scope.",
|
|
164
|
+
}),
|
|
165
|
+
tz: s.string({ description: "The user's time zone identifier when Slack provides it." }),
|
|
166
|
+
tzOffset: s.integer({ description: "The user's UTC offset in seconds when Slack provides it." }),
|
|
167
|
+
updated: s.integer({ description: "When the user object was last updated, as a Unix timestamp in seconds." }),
|
|
168
|
+
teamId: s.string({ description: "The user's team ID when Slack provides it." }),
|
|
169
|
+
isRestricted: s.boolean({
|
|
170
|
+
description: "Whether the user is a guest. Single-channel guests also set isUltraRestricted.",
|
|
171
|
+
}),
|
|
172
|
+
isUltraRestricted: s.boolean({ description: "Whether the user is a single-channel guest." }),
|
|
173
|
+
isAppUser: s.boolean({ description: "Whether the user is an authorized user of the calling app." }),
|
|
130
174
|
}, {
|
|
131
175
|
required: ["userId", "username", "realName", "displayName", "isBot", "isDeleted", "isAdmin", "isOwner"],
|
|
132
176
|
description: "A normalized Slack user record.",
|
|
@@ -188,6 +232,7 @@ export const slackActions = [
|
|
|
188
232
|
description: "The Slack pagination cursor from a previous page. Omit for the first page.",
|
|
189
233
|
}),
|
|
190
234
|
...historyWindowProperties,
|
|
235
|
+
...includeRawProperties,
|
|
191
236
|
}, { required: ["channelId"], description: "Input parameters for reading Slack conversation history." }),
|
|
192
237
|
outputSchema: s.object({
|
|
193
238
|
messages: s.array(slackMessageSchema, { description: "The list of messages in the conversation." }),
|
|
@@ -242,6 +287,9 @@ export const slackActions = [
|
|
|
242
287
|
sort: searchSortSchema,
|
|
243
288
|
sortDir: sortDirectionSchema,
|
|
244
289
|
teamId: s.string({ description: "The encoded team ID to search when using an org-level token." }),
|
|
290
|
+
includeRaw: s.boolean({
|
|
291
|
+
description: "When true, each returned match also carries the untouched Slack search match under raw, including any blocks, attachments and files. Defaults to false.",
|
|
292
|
+
}),
|
|
245
293
|
}, { required: ["query"], description: "Input parameters for searching Slack messages." }),
|
|
246
294
|
outputSchema: s.object({
|
|
247
295
|
query: s.string({ description: "The search query Slack executed." }),
|
|
@@ -323,6 +371,7 @@ export const slackActions = [
|
|
|
323
371
|
description: "The Slack pagination cursor from a previous page. Omit for the first page.",
|
|
324
372
|
}),
|
|
325
373
|
...historyWindowProperties,
|
|
374
|
+
...includeRawProperties,
|
|
326
375
|
}, { required: ["channelId", "threadTs"], description: "Input parameters for reading a Slack thread." }),
|
|
327
376
|
outputSchema: s.object({
|
|
328
377
|
messages: s.array(slackMessageSchema, { description: "The list of messages in the thread." }),
|
|
@@ -537,6 +586,23 @@ export const slackActions = [
|
|
|
537
586
|
inputSchema: s.object({ fileId: fileIdSchema }, { required: ["fileId"], description: "Input parameters for fetching a Slack file." }),
|
|
538
587
|
outputSchema: s.object({ file: fileSchema }, { required: ["file"], description: "The output payload for this action." }),
|
|
539
588
|
}),
|
|
589
|
+
action({
|
|
590
|
+
name: "download_file",
|
|
591
|
+
operationType: "read",
|
|
592
|
+
description: "Download a Slack-hosted file into transit storage using the connected identity's access.",
|
|
593
|
+
requiredScopes: ["files:read"],
|
|
594
|
+
inputSchema: s.requiredObject("The Slack file to download.", { fileId: fileIdSchema }),
|
|
595
|
+
outputSchema: s.requiredObject("A Slack file downloaded into transit storage.", {
|
|
596
|
+
fileId: fileIdSchema,
|
|
597
|
+
file: s.requiredObject("The downloaded file in transit storage.", {
|
|
598
|
+
fileId: s.nonEmptyString("The transit file identifier."),
|
|
599
|
+
downloadUrl: s.url("The transit URL for downloading the stored file."),
|
|
600
|
+
sizeBytes: s.nonNegativeInteger("The stored file size in bytes."),
|
|
601
|
+
name: s.nonEmptyString("The stored file name."),
|
|
602
|
+
mimeType: s.nonEmptyString("The stored file MIME type."),
|
|
603
|
+
}),
|
|
604
|
+
}),
|
|
605
|
+
}),
|
|
540
606
|
action({
|
|
541
607
|
name: "delete_file",
|
|
542
608
|
operationType: "destructive",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { compactObject, optionalBoolean, optionalInteger, optionalRecord, optionalString, requiredString, } from "../../core/cast.js";
|
|
1
|
+
import { compactObject, optionalBoolean, optionalInteger, optionalNumber, optionalRecord, optionalString, optionalStringArray, requiredString, } from "../../core/cast.js";
|
|
2
2
|
import { assertPublicHttpUrl, readBoundedResponseBytes } from "../../core/request.js";
|
|
3
|
-
import { createProviderTimeout, defineProviderExecutors, isAbortLikeError, ProviderRequestError, providerUserAgent, } from "../provider-runtime.js";
|
|
3
|
+
import { createProviderTimeout, defineProviderExecutors, isAbortLikeError, ProviderRequestError, providerInputError, providerResponseError, providerUserAgent, requiredInputString, requiredResponseRecord, runProviderRequest, withRetryAfterSeconds, } from "../provider-runtime.js";
|
|
4
4
|
import { slackConversationTypes } from "./constants.js";
|
|
5
5
|
export const slackApiBaseUrl = "https://slack.com/api";
|
|
6
6
|
const slackFileUrlMaxBytes = 100 * 1024 * 1024;
|
|
@@ -116,6 +116,9 @@ export const slackActionHandlers = {
|
|
|
116
116
|
get_file(input, context) {
|
|
117
117
|
return slackGetFile(input, context);
|
|
118
118
|
},
|
|
119
|
+
download_file(input, context) {
|
|
120
|
+
return slackDownloadFile(input, context);
|
|
121
|
+
},
|
|
119
122
|
delete_file(input, context) {
|
|
120
123
|
return slackDeleteFile(input, context);
|
|
121
124
|
},
|
|
@@ -193,7 +196,8 @@ async function slackGetChannelMessages(input, context) {
|
|
|
193
196
|
url.searchParams.set("cursor", String(input.cursor));
|
|
194
197
|
}
|
|
195
198
|
applySlackHistoryWindow(url, input);
|
|
196
|
-
|
|
199
|
+
const includeRaw = applySlackIncludeRaw(url, input);
|
|
200
|
+
return readSlackMessagePage(await slackGetJson(url, context), "conversations.history", includeRaw);
|
|
197
201
|
}
|
|
198
202
|
async function slackConversationsMembers(input, context) {
|
|
199
203
|
const url = slackApiUrl("conversations.members");
|
|
@@ -211,6 +215,7 @@ async function slackConversationsMembers(input, context) {
|
|
|
211
215
|
};
|
|
212
216
|
}
|
|
213
217
|
async function slackSearchMessages(input, context) {
|
|
218
|
+
const includeRaw = input.includeRaw === true;
|
|
214
219
|
const query = requiredString(input.query, "query", (message) => new ProviderRequestError(400, message));
|
|
215
220
|
if (input.page != null && input.cursor != null) {
|
|
216
221
|
throw new ProviderRequestError(400, "page and cursor cannot be used together");
|
|
@@ -241,7 +246,7 @@ async function slackSearchMessages(input, context) {
|
|
|
241
246
|
const payload = await slackGetJson(url, context);
|
|
242
247
|
return {
|
|
243
248
|
query: optionalString(payload.query) ?? query,
|
|
244
|
-
matches: (payload.messages?.matches ?? []).map((match) => normalizeSearchMessageMatch(match)),
|
|
249
|
+
matches: (payload.messages?.matches ?? []).map((match) => normalizeSearchMessageMatch(match, includeRaw)),
|
|
245
250
|
total: typeof payload.messages?.total === "number" ? payload.messages.total : 0,
|
|
246
251
|
pagination: payload.messages?.pagination ?? {},
|
|
247
252
|
paging: payload.messages?.paging ?? {},
|
|
@@ -315,7 +320,8 @@ async function slackGetThread(input, context) {
|
|
|
315
320
|
url.searchParams.set("cursor", String(input.cursor));
|
|
316
321
|
}
|
|
317
322
|
applySlackHistoryWindow(url, input);
|
|
318
|
-
|
|
323
|
+
const includeRaw = applySlackIncludeRaw(url, input);
|
|
324
|
+
return readSlackMessagePage(await slackGetJson(url, context), "conversations.replies", includeRaw);
|
|
319
325
|
}
|
|
320
326
|
async function slackListConversations(input, context) {
|
|
321
327
|
const url = slackApiUrl("conversations.list");
|
|
@@ -545,6 +551,61 @@ async function slackGetFile(input, context) {
|
|
|
545
551
|
file: normalizeFile(payload.file ?? {}),
|
|
546
552
|
};
|
|
547
553
|
}
|
|
554
|
+
async function slackDownloadFile(input, context) {
|
|
555
|
+
const { transitFiles } = context;
|
|
556
|
+
if (!transitFiles) {
|
|
557
|
+
throw providerInputError("Slack download_file requires transit file storage");
|
|
558
|
+
}
|
|
559
|
+
const fileId = requiredInputString(input.fileId, "fileId");
|
|
560
|
+
return runProviderRequest({ signal: context.signal, label: "Slack file download" }, async (signal) => {
|
|
561
|
+
signal.throwIfAborted();
|
|
562
|
+
const infoUrl = slackApiUrl("files.info");
|
|
563
|
+
infoUrl.searchParams.set("file", fileId);
|
|
564
|
+
const payload = await slackGetJson(infoUrl, { ...context, signal });
|
|
565
|
+
const metadata = requiredResponseRecord(payload.file, "Slack file metadata");
|
|
566
|
+
const privateUrl = optionalString(metadata.url_private_download) ?? optionalString(metadata.url_private);
|
|
567
|
+
if (metadata.is_external === true || !privateUrl) {
|
|
568
|
+
throw providerInputError("This Slack file has no downloadable Slack-hosted content");
|
|
569
|
+
}
|
|
570
|
+
const url = assertPublicHttpUrl(privateUrl, { fieldName: "Slack file URL", createError: providerResponseError });
|
|
571
|
+
// Only Slack's file origin may receive the connection's bearer token.
|
|
572
|
+
// The shared fetch guard drops it if a redirect crosses origins.
|
|
573
|
+
if (url.origin !== "https://files.slack.com") {
|
|
574
|
+
throw providerResponseError("Slack file URL must use https://files.slack.com");
|
|
575
|
+
}
|
|
576
|
+
if ((optionalNumber(metadata.size) ?? 0) > transitFiles.maxBytes) {
|
|
577
|
+
throw new ProviderRequestError(413, `Slack file download exceeds ${transitFiles.maxBytes} bytes`);
|
|
578
|
+
}
|
|
579
|
+
const response = await context.fetcher(url, {
|
|
580
|
+
headers: { authorization: `Bearer ${context.accessToken}`, "user-agent": providerUserAgent },
|
|
581
|
+
signal,
|
|
582
|
+
});
|
|
583
|
+
try {
|
|
584
|
+
if (!response.ok) {
|
|
585
|
+
throw new ProviderRequestError(response.status, `Slack file download failed with HTTP ${response.status}`);
|
|
586
|
+
}
|
|
587
|
+
const mimeType = optionalString(response.headers.get("content-type")) ??
|
|
588
|
+
optionalString(metadata.mimetype) ??
|
|
589
|
+
"application/octet-stream";
|
|
590
|
+
// Slack can return a sign-in page instead of the requested bytes.
|
|
591
|
+
if (mimeType.split(";")[0]?.trim().toLowerCase() === "text/html" && metadata.mimetype !== "text/html") {
|
|
592
|
+
throw providerResponseError("Slack returned an HTML page instead of the requested file");
|
|
593
|
+
}
|
|
594
|
+
const bytes = await readBoundedResponseBytes(response, {
|
|
595
|
+
maxBytes: transitFiles.maxBytes,
|
|
596
|
+
fieldName: "Slack file download",
|
|
597
|
+
createError: (message) => new ProviderRequestError(413, message),
|
|
598
|
+
});
|
|
599
|
+
signal.throwIfAborted();
|
|
600
|
+
const name = optionalString(metadata.name) ?? fileId;
|
|
601
|
+
const file = await transitFiles.create(new File([Uint8Array.from(bytes)], name, { type: mimeType }));
|
|
602
|
+
return { fileId, file };
|
|
603
|
+
}
|
|
604
|
+
finally {
|
|
605
|
+
await response.body?.cancel().catch(() => undefined);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
548
609
|
async function slackDeleteFile(input, context) {
|
|
549
610
|
await slackRequestJson({
|
|
550
611
|
...context,
|
|
@@ -593,7 +654,7 @@ async function slackFormRequestJson(context, method, body) {
|
|
|
593
654
|
async function readSlackResponseJson(response) {
|
|
594
655
|
const payload = (optionalRecord(await response.json().catch(() => undefined)) ?? {});
|
|
595
656
|
if (!response.ok) {
|
|
596
|
-
throw slackHttpError(response
|
|
657
|
+
throw slackHttpError(response, payload);
|
|
597
658
|
}
|
|
598
659
|
assertSlackPayload(payload);
|
|
599
660
|
// Preserve HTTP/Slack failures (including Retry-After) above, but require
|
|
@@ -786,6 +847,15 @@ function normalizeConversation(conversation) {
|
|
|
786
847
|
purpose: typeof purpose?.value === "string" ? purpose.value : null,
|
|
787
848
|
userId: optionalString(conversation.user),
|
|
788
849
|
locale: optionalString(conversation.locale),
|
|
850
|
+
created: optionalInteger(conversation.created),
|
|
851
|
+
updated: optionalInteger(conversation.updated),
|
|
852
|
+
creatorId: optionalString(conversation.creator),
|
|
853
|
+
isShared: optionalBoolean(conversation.is_shared),
|
|
854
|
+
isExtShared: optionalBoolean(conversation.is_ext_shared),
|
|
855
|
+
isOrgShared: optionalBoolean(conversation.is_org_shared),
|
|
856
|
+
contextTeamId: optionalString(conversation.context_team_id),
|
|
857
|
+
lastRead: optionalString(conversation.last_read),
|
|
858
|
+
unreadCount: optionalInteger(conversation.unread_count),
|
|
789
859
|
});
|
|
790
860
|
}
|
|
791
861
|
/**
|
|
@@ -805,13 +875,27 @@ function applySlackHistoryWindow(url, input) {
|
|
|
805
875
|
url.searchParams.set("inclusive", String(input.inclusive));
|
|
806
876
|
}
|
|
807
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Apply the shared `includeRaw` opt-in of `conversations.history` /
|
|
880
|
+
* `conversations.replies` and report whether it is on. Only a literal `true`
|
|
881
|
+
* opts in. Slack leaves message metadata out of both methods unless the
|
|
882
|
+
* request sets `include_all_metadata`, and `raw` promises the whole record, so
|
|
883
|
+
* opting in asks for it too.
|
|
884
|
+
*/
|
|
885
|
+
function applySlackIncludeRaw(url, input) {
|
|
886
|
+
const includeRaw = input.includeRaw === true;
|
|
887
|
+
if (includeRaw) {
|
|
888
|
+
url.searchParams.set("include_all_metadata", "true");
|
|
889
|
+
}
|
|
890
|
+
return includeRaw;
|
|
891
|
+
}
|
|
808
892
|
/**
|
|
809
893
|
* Read one `conversations.history` / `conversations.replies` page. A page whose
|
|
810
894
|
* list is missing or whose rows are not objects is rejected rather than
|
|
811
895
|
* flattened into an empty result, so a broken upstream response cannot pass
|
|
812
896
|
* for the documented end of a walk.
|
|
813
897
|
*/
|
|
814
|
-
function readSlackMessagePage(payload, method) {
|
|
898
|
+
function readSlackMessagePage(payload, method, includeRaw = false) {
|
|
815
899
|
if (payload.has_more !== undefined && typeof payload.has_more !== "boolean") {
|
|
816
900
|
throw slackResponseError(`${method} has_more`);
|
|
817
901
|
}
|
|
@@ -821,7 +905,7 @@ function readSlackMessagePage(payload, method) {
|
|
|
821
905
|
if (!record) {
|
|
822
906
|
throw slackResponseError(`${method} message`);
|
|
823
907
|
}
|
|
824
|
-
return normalizeSlackMessage(record);
|
|
908
|
+
return normalizeSlackMessage(record, includeRaw);
|
|
825
909
|
}),
|
|
826
910
|
hasMore: payload.has_more ?? false,
|
|
827
911
|
nextCursor: readSlackNextCursor(payload, method),
|
|
@@ -869,8 +953,13 @@ function requireSlackId(value, label) {
|
|
|
869
953
|
* missing value stays distinguishable from an empty one. `userId` is the one
|
|
870
954
|
* exception kept for compatibility: it stays `""` on a message with no
|
|
871
955
|
* author, where `botId` / `username` carry the identity instead.
|
|
956
|
+
*
|
|
957
|
+
* `files`, `attachments`, `blocks` and `metadata` stay out of the row: they
|
|
958
|
+
* are unbounded nested payloads on a row read in bulk. With `includeRaw` the
|
|
959
|
+
* whole untouched record rides along under `raw` for a consumer that needs
|
|
960
|
+
* them, or any other field this normalizer does not model.
|
|
872
961
|
*/
|
|
873
|
-
function normalizeSlackMessage(message) {
|
|
962
|
+
function normalizeSlackMessage(message, includeRaw = false) {
|
|
874
963
|
const edited = optionalRecord(message.edited) ?? {};
|
|
875
964
|
const reactions = Array.isArray(message.reactions) ? message.reactions : undefined;
|
|
876
965
|
return compactObject({
|
|
@@ -885,13 +974,16 @@ function normalizeSlackMessage(message) {
|
|
|
885
974
|
clientMsgId: optionalString(message.client_msg_id),
|
|
886
975
|
text: typeof message.text === "string" ? message.text : "",
|
|
887
976
|
editedTs: optionalString(edited.ts),
|
|
977
|
+
editedUserId: optionalString(edited.user),
|
|
888
978
|
threadTs: optionalString(message.thread_ts),
|
|
889
979
|
parentUserId: optionalString(message.parent_user_id),
|
|
890
980
|
replyCount: optionalInteger(message.reply_count),
|
|
891
981
|
replyUsersCount: optionalInteger(message.reply_users_count),
|
|
982
|
+
replyUserIds: optionalStringArray(message.reply_users),
|
|
892
983
|
latestReply: optionalString(message.latest_reply),
|
|
893
984
|
isLocked: optionalBoolean(message.is_locked),
|
|
894
985
|
reactions: reactions?.map((reaction) => normalizeSlackReaction(optionalRecord(reaction) ?? {})),
|
|
986
|
+
raw: includeRaw ? message : undefined,
|
|
895
987
|
});
|
|
896
988
|
}
|
|
897
989
|
function normalizeSlackReaction(reaction) {
|
|
@@ -901,7 +993,7 @@ function normalizeSlackReaction(reaction) {
|
|
|
901
993
|
userIds: Array.isArray(reaction.users) ? reaction.users.map((user) => String(user)) : undefined,
|
|
902
994
|
});
|
|
903
995
|
}
|
|
904
|
-
function normalizeSearchMessageMatch(match) {
|
|
996
|
+
function normalizeSearchMessageMatch(match, includeRaw = false) {
|
|
905
997
|
const channel = optionalRecord(match.channel) ?? {};
|
|
906
998
|
return compactObject({
|
|
907
999
|
matchId: optionalString(match.iid),
|
|
@@ -914,6 +1006,7 @@ function normalizeSearchMessageMatch(match) {
|
|
|
914
1006
|
permalink: optionalString(match.permalink),
|
|
915
1007
|
teamId: optionalString(match.team),
|
|
916
1008
|
type: optionalString(match.type),
|
|
1009
|
+
raw: includeRaw ? match : undefined,
|
|
917
1010
|
});
|
|
918
1011
|
}
|
|
919
1012
|
function normalizeUser(user) {
|
|
@@ -928,6 +1021,14 @@ function normalizeUser(user) {
|
|
|
928
1021
|
isAdmin: typeof user.is_admin === "boolean" ? user.is_admin : null,
|
|
929
1022
|
isOwner: typeof user.is_owner === "boolean" ? user.is_owner : null,
|
|
930
1023
|
locale: optionalString(user.locale),
|
|
1024
|
+
email: optionalString(profile.email),
|
|
1025
|
+
tz: optionalString(user.tz),
|
|
1026
|
+
tzOffset: optionalInteger(user.tz_offset),
|
|
1027
|
+
updated: optionalInteger(user.updated),
|
|
1028
|
+
teamId: optionalString(user.team_id),
|
|
1029
|
+
isRestricted: optionalBoolean(user.is_restricted),
|
|
1030
|
+
isUltraRestricted: optionalBoolean(user.is_ultra_restricted),
|
|
1031
|
+
isAppUser: optionalBoolean(user.is_app_user),
|
|
931
1032
|
});
|
|
932
1033
|
}
|
|
933
1034
|
function normalizeFile(file) {
|
|
@@ -979,17 +1080,11 @@ function formatSlackPayloadError(payload) {
|
|
|
979
1080
|
}
|
|
980
1081
|
return `${error}: ${details.join("; ")}`;
|
|
981
1082
|
}
|
|
982
|
-
function slackHttpError(
|
|
983
|
-
const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${status}`;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
// The action envelope carries provider details; retain pacing so callers
|
|
988
|
-
// can resume the same page without guessing when this workspace may retry.
|
|
989
|
-
return new ProviderRequestError(status, message, { ...payload, retryAfterSeconds });
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
return new ProviderRequestError(status, message, payload);
|
|
1083
|
+
function slackHttpError(response, payload) {
|
|
1084
|
+
const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${response.status}`;
|
|
1085
|
+
// The action envelope carries provider details; retain pacing so callers
|
|
1086
|
+
// can resume the same page without guessing when this workspace may retry.
|
|
1087
|
+
return new ProviderRequestError(response.status, message, withRetryAfterSeconds(response, payload));
|
|
993
1088
|
}
|
|
994
1089
|
function slackResponseError(message) {
|
|
995
1090
|
return new ProviderRequestError(502, `slack response is invalid: ${message}`);
|
|
@@ -45,7 +45,7 @@ export const proxy = defineProviderProxy({
|
|
|
45
45
|
baseUrl: sorftimeMcpEndpoint,
|
|
46
46
|
auth: { type: "api_key_authorization", prefix: "Bearer " },
|
|
47
47
|
skipDnsValidation: true,
|
|
48
|
-
redirect: "
|
|
48
|
+
redirect: "manual",
|
|
49
49
|
timeoutMs: requestTimeoutMs,
|
|
50
50
|
allowedEndpoint(endpoint) {
|
|
51
51
|
const url = new URL(endpoint, sorftimeMcpEndpoint);
|
|
@@ -79,7 +79,7 @@ function withSorftimeClient(context, run) {
|
|
|
79
79
|
authorization: `Bearer ${context.apiKey}`,
|
|
80
80
|
"user-agent": providerUserAgent,
|
|
81
81
|
},
|
|
82
|
-
redirect: "
|
|
82
|
+
redirect: "manual",
|
|
83
83
|
signal: context.signal,
|
|
84
84
|
mapError: mapSorftimeError,
|
|
85
85
|
}, run);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { sha256Hex } from "../../core/aws-sigv4.js";
|
|
2
|
+
import { compactObject, looseArray, optionalInteger, optionalString } from "../../core/cast.js";
|
|
3
|
+
import { defineApiKeyProviderExecutors, providerInputError, ProviderRequestError, providerResponseError, providerUserAgent, readProviderJsonBody, requiredInputString, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
|
|
4
|
+
const service = "speak_ai";
|
|
5
|
+
const apiBaseUrl = "https://api.speakai.co/v1";
|
|
6
|
+
const tokenCache = new Map();
|
|
7
|
+
const handlers = {
|
|
8
|
+
async upload_media(input, context) {
|
|
9
|
+
const data = await request("/media/upload", {
|
|
10
|
+
method: "POST",
|
|
11
|
+
body: compactObject({
|
|
12
|
+
name: requiredInputString(input.name, "name"),
|
|
13
|
+
url: requiredInputString(input.mediaUrl, "mediaUrl"),
|
|
14
|
+
mediaType: optionalString(input.mediaType),
|
|
15
|
+
description: optionalString(input.description),
|
|
16
|
+
sourceLanguage: optionalString(input.sourceLanguage),
|
|
17
|
+
tags: optionalString(input.tags),
|
|
18
|
+
folderId: optionalString(input.folderId),
|
|
19
|
+
}),
|
|
20
|
+
}, context, "execute");
|
|
21
|
+
return { mediaId: requiredResponseString(data.mediaId, "Speak AI upload mediaId") };
|
|
22
|
+
},
|
|
23
|
+
async list_media(input, context) {
|
|
24
|
+
const data = await request(buildUrl("/media", input, {
|
|
25
|
+
mediaType: "mediaType",
|
|
26
|
+
page: "page",
|
|
27
|
+
pageSize: "pageSize",
|
|
28
|
+
sortBy: "sortBy",
|
|
29
|
+
filterMedia: "filterMedia",
|
|
30
|
+
filterName: "filterName",
|
|
31
|
+
folderId: "folderId",
|
|
32
|
+
}), { method: "GET" }, context, "execute");
|
|
33
|
+
return {
|
|
34
|
+
totalCount: requireInteger(data.totalCount, "totalCount"),
|
|
35
|
+
pages: requireInteger(data.pages, "pages"),
|
|
36
|
+
media: looseArray(data.mediaList),
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
async get_media_status(input, context) {
|
|
40
|
+
const data = await media(input, context, "status");
|
|
41
|
+
return {
|
|
42
|
+
mediaId: requiredResponseString(data.mediaId, "mediaId"),
|
|
43
|
+
state: requiredResponseString(data.state, "state"),
|
|
44
|
+
media: data,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
async get_transcript(input, context) {
|
|
48
|
+
const data = await media(input, context, "transcript");
|
|
49
|
+
return { mediaId: requiredResponseString(data.mediaId, "mediaId"), transcript: data };
|
|
50
|
+
},
|
|
51
|
+
async get_media_insights(input, context) {
|
|
52
|
+
const data = await media(input, context, "insight");
|
|
53
|
+
return { mediaId: requiredResponseString(data.mediaId, "mediaId"), insights: data };
|
|
54
|
+
},
|
|
55
|
+
async list_folders(input, context) {
|
|
56
|
+
const data = await request(buildUrl("/folder", input, { page: "page", pageSize: "pageSize", sortBy: "sortBy" }), { method: "GET" }, context, "execute");
|
|
57
|
+
return {
|
|
58
|
+
totalCount: requireInteger(data.totalCount, "totalCount"),
|
|
59
|
+
pages: data.pages == null ? null : requireInteger(data.pages, "pages"),
|
|
60
|
+
folders: looseArray(data.folders),
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
export const executors = defineApiKeyProviderExecutors(service, handlers, {
|
|
65
|
+
skipDnsValidation: true,
|
|
66
|
+
});
|
|
67
|
+
export const credentialValidators = {
|
|
68
|
+
async apiKey(input, { fetcher }) {
|
|
69
|
+
const token = await authenticate(input.apiKey, fetcher, "validate");
|
|
70
|
+
return {
|
|
71
|
+
profile: { accountId: token.email ?? "speak-ai-api-key", displayName: token.email ?? "Speak AI API Key" },
|
|
72
|
+
grantedScopes: [],
|
|
73
|
+
metadata: { apiBaseUrl, validationEndpoint: "/auth/accessToken" },
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
async function media(input, context, family) {
|
|
78
|
+
const mediaId = requiredInputString(input.mediaId, "mediaId");
|
|
79
|
+
const data = await request(`/media/${family}/${encodeURIComponent(mediaId)}`, { method: "GET" }, context, "execute");
|
|
80
|
+
return data.state === "notUploaded" && data.mediaId === undefined ? { ...data, mediaId } : data;
|
|
81
|
+
}
|
|
82
|
+
async function request(path, init, context, phase) {
|
|
83
|
+
let token = await authenticate(context.apiKey, context.fetcher, phase);
|
|
84
|
+
let response = await fetchToken(path, init, context, token.accessToken);
|
|
85
|
+
let payload = await readProviderJsonBody(response, {
|
|
86
|
+
emptyBody: {},
|
|
87
|
+
invalidJsonMessage: "Speak AI returned invalid JSON",
|
|
88
|
+
});
|
|
89
|
+
if (phase === "execute" && response.status === 401) {
|
|
90
|
+
tokenCache.delete(sha256Hex(context.apiKey));
|
|
91
|
+
token = await authenticate(context.apiKey, context.fetcher, phase);
|
|
92
|
+
response = await fetchToken(path, init, context, token.accessToken);
|
|
93
|
+
payload = await readProviderJsonBody(response, {
|
|
94
|
+
emptyBody: {},
|
|
95
|
+
invalidJsonMessage: "Speak AI returned invalid JSON",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!response.ok)
|
|
99
|
+
throw error(response.status, payload, phase);
|
|
100
|
+
const envelope = requiredResponseRecord(payload, "Speak AI response");
|
|
101
|
+
if (envelope.state === "notUploaded" && envelope.data === undefined)
|
|
102
|
+
return envelope;
|
|
103
|
+
return requiredResponseRecord(envelope.data, "Speak AI response data");
|
|
104
|
+
}
|
|
105
|
+
function requiredResponseString(value, label) {
|
|
106
|
+
const result = optionalString(value);
|
|
107
|
+
if (!result)
|
|
108
|
+
throw providerResponseError(`${label} must be a non-empty string`);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
function fetchToken(path, init, context, accessToken) {
|
|
112
|
+
return runProviderRequest({ signal: context.signal, label: "Speak AI" }, (signal) => context.fetcher(path instanceof URL ? path : `${apiBaseUrl}${path}`, {
|
|
113
|
+
method: init.method,
|
|
114
|
+
headers: {
|
|
115
|
+
accept: "application/json",
|
|
116
|
+
"content-type": "application/json",
|
|
117
|
+
"user-agent": providerUserAgent,
|
|
118
|
+
"x-speakai-key": context.apiKey,
|
|
119
|
+
"x-access-token": accessToken,
|
|
120
|
+
},
|
|
121
|
+
body: init.body ? JSON.stringify(init.body) : undefined,
|
|
122
|
+
signal,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
async function authenticate(apiKey, fetcher, phase) {
|
|
126
|
+
const key = sha256Hex(apiKey);
|
|
127
|
+
if (phase === "validate")
|
|
128
|
+
tokenCache.delete(key);
|
|
129
|
+
const cached = tokenCache.get(key);
|
|
130
|
+
if (cached) {
|
|
131
|
+
const value = await cached;
|
|
132
|
+
if (Date.now() < value.expiresAt)
|
|
133
|
+
return value;
|
|
134
|
+
tokenCache.delete(key);
|
|
135
|
+
}
|
|
136
|
+
const pending = runProviderRequest({ label: "Speak AI authentication" }, async (signal) => {
|
|
137
|
+
const response = await fetcher(`${apiBaseUrl}/auth/accessToken`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: { "content-type": "application/json", "user-agent": providerUserAgent, "x-speakai-key": apiKey },
|
|
140
|
+
body: "{}",
|
|
141
|
+
signal,
|
|
142
|
+
});
|
|
143
|
+
const payload = await readProviderJsonBody(response, {
|
|
144
|
+
emptyBody: {},
|
|
145
|
+
invalidJsonMessage: "Speak AI returned invalid JSON",
|
|
146
|
+
});
|
|
147
|
+
if (!response.ok)
|
|
148
|
+
throw error(response.status, payload, phase);
|
|
149
|
+
const data = requiredResponseRecord(requiredResponseRecord(payload, "Speak AI authentication response").data, "Speak AI authentication data");
|
|
150
|
+
return {
|
|
151
|
+
accessToken: requiredInputString(data.accessToken, "accessToken"),
|
|
152
|
+
email: optionalString(data.email),
|
|
153
|
+
expiresAt: Date.now() + 3_000_000,
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
tokenCache.set(key, pending);
|
|
157
|
+
try {
|
|
158
|
+
return await pending;
|
|
159
|
+
}
|
|
160
|
+
catch (cause) {
|
|
161
|
+
tokenCache.delete(key);
|
|
162
|
+
throw cause;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function error(status, payload, phase) {
|
|
166
|
+
const body = requiredResponseRecord(payload, "Speak AI error response");
|
|
167
|
+
const message = optionalString(body.message) ?? "Speak AI request failed";
|
|
168
|
+
if (status === 429)
|
|
169
|
+
return new ProviderRequestError(429, message, payload);
|
|
170
|
+
if (phase === "validate" && optionalInteger(body.code) === 401)
|
|
171
|
+
return providerInputError(message);
|
|
172
|
+
if (status === 400 || status === 422)
|
|
173
|
+
return providerInputError(message);
|
|
174
|
+
return new ProviderRequestError(status || 502, message, payload);
|
|
175
|
+
}
|
|
176
|
+
function requireInteger(value, label) {
|
|
177
|
+
if (typeof value !== "number" || !Number.isInteger(value))
|
|
178
|
+
throw new ProviderRequestError(502, `Speak AI ${label} must be an integer`);
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
function buildUrl(path, input, mapping) {
|
|
182
|
+
const url = new URL(`${apiBaseUrl}${path}`);
|
|
183
|
+
for (const [key, query] of Object.entries(mapping))
|
|
184
|
+
if (input[key] != null)
|
|
185
|
+
url.searchParams.set(query, String(input[key]));
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
@@ -5,4 +5,4 @@ export declare function readBoundedResponseText(response: Response, input: {
|
|
|
5
5
|
maxBytes: number;
|
|
6
6
|
label: string;
|
|
7
7
|
}): Promise<string>;
|
|
8
|
-
export declare function cancelResponseBody(response: Response):
|
|
8
|
+
export declare function cancelResponseBody(response: Response): void;
|