@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.
Files changed (320) hide show
  1. package/assets/open-connector/catalog/apps/aisa.json +6394 -0
  2. package/assets/open-connector/catalog/apps/baizhi_mcp.json +135 -0
  3. package/assets/open-connector/catalog/apps/bilibili.json +2586 -0
  4. package/assets/open-connector/catalog/apps/bitwarden.json +1423 -0
  5. package/assets/open-connector/catalog/apps/cronfree.json +303 -0
  6. package/assets/open-connector/catalog/apps/cufinder.json +368 -0
  7. package/assets/open-connector/catalog/apps/discordbot.json +2888 -248
  8. package/assets/open-connector/catalog/apps/drawio_mcp.json +192 -0
  9. package/assets/open-connector/catalog/apps/firecrawl.json +176 -32
  10. package/assets/open-connector/catalog/apps/framejet.json +273 -0
  11. package/assets/open-connector/catalog/apps/futunn.json +2967 -0
  12. package/assets/open-connector/catalog/apps/gmail.json +298 -0
  13. package/assets/open-connector/catalog/apps/google_analytics.json +4 -1
  14. package/assets/open-connector/catalog/apps/google_bigquery.json +4 -1
  15. package/assets/open-connector/catalog/apps/google_search_console.json +4 -1
  16. package/assets/open-connector/catalog/apps/googlecalendar.json +4 -1
  17. package/assets/open-connector/catalog/apps/googlechat.json +694 -7
  18. package/assets/open-connector/catalog/apps/googledocs.json +4 -1
  19. package/assets/open-connector/catalog/apps/googlesheets.json +4 -1
  20. package/assets/open-connector/catalog/apps/granola.json +23 -0
  21. package/assets/open-connector/catalog/apps/investoday_mcp.json +22228 -197
  22. package/assets/open-connector/catalog/apps/ixspy.json +2274 -0
  23. package/assets/open-connector/catalog/apps/keepa.json +3534 -987
  24. package/assets/open-connector/catalog/apps/linear.json +17 -0
  25. package/assets/open-connector/catalog/apps/linear_mcp.json +510 -0
  26. package/assets/open-connector/catalog/apps/moneyforward_mcp.json +1432 -0
  27. package/assets/open-connector/catalog/apps/notion.json +446 -1
  28. package/assets/open-connector/catalog/apps/notion_mcp.json +490 -0
  29. package/assets/open-connector/catalog/apps/odoo.json +619 -0
  30. package/assets/open-connector/catalog/apps/osv.json +663 -0
  31. package/assets/open-connector/catalog/apps/outlook.json +30 -8
  32. package/assets/open-connector/catalog/apps/outlook_calendar.json +23 -2
  33. package/assets/open-connector/catalog/apps/pdf_vector.json +329 -0
  34. package/assets/open-connector/catalog/apps/postiz.json +334 -0
  35. package/assets/open-connector/catalog/apps/proworkflow.json +529 -0
  36. package/assets/open-connector/catalog/apps/pulsetic.json +406 -0
  37. package/assets/open-connector/catalog/apps/sellersprite_mcp.json +2 -1
  38. package/assets/open-connector/catalog/apps/shopee.json +1785 -0
  39. package/assets/open-connector/catalog/apps/slack.json +304 -0
  40. package/assets/open-connector/catalog/apps/slackbot.json +295 -0
  41. package/assets/open-connector/catalog/apps/speak_ai.json +360 -0
  42. package/assets/open-connector/catalog/apps/tencent_docs.json +3 -0
  43. package/assets/open-connector/catalog/apps/wisburg.json +1528 -0
  44. package/assets/open-connector/catalog/apps/youtube.json +4 -1
  45. package/assets/open-connector/catalog/apps/zerotier.json +3319 -0
  46. package/assets/open-connector/catalog/apps/zyhub.json +1585 -0
  47. package/assets/open-connector/catalog/apps-index.json +1 -1
  48. package/package.json +1 -1
  49. package/src/core/cast.d.ts +10 -0
  50. package/src/core/cast.js +14 -0
  51. package/src/core/guarded-fetch.d.ts +6 -3
  52. package/src/core/guarded-fetch.js +6 -3
  53. package/src/core/json-schema.d.ts +5 -1
  54. package/src/core/json-schema.js +14 -2
  55. package/src/core/provider-scenarios.js +67 -13
  56. package/src/core/request.js +4 -1
  57. package/src/core/types.d.ts +10 -1
  58. package/src/mcp.js +10 -7
  59. package/src/oauth/oauth-client-config-service.d.ts +16 -1
  60. package/src/oauth/oauth-client-config-service.js +59 -3
  61. package/src/oauth/oauth-credential-refresh-service.js +3 -1
  62. package/src/oauth/oauth-flow-service.d.ts +6 -0
  63. package/src/oauth/oauth-flow-service.js +21 -4
  64. package/src/oauth/oauth-token.d.ts +13 -0
  65. package/src/providers/17track/runtime.js +1 -1
  66. package/src/providers/action-contracts.generated.d.ts +32 -8
  67. package/src/providers/aifinmarket/runtime.js +1 -1
  68. package/src/providers/aisa/executors.d.ts +4 -0
  69. package/src/providers/aisa/executors.js +864 -0
  70. package/src/providers/aliyun_ack/runtime.js +1 -1
  71. package/src/providers/aliyun_sls/runtime.js +1 -1
  72. package/src/providers/app_store_server/runtime-helpers.js +1 -1
  73. package/src/providers/baizhi_mcp/actions.d.ts +2 -0
  74. package/src/providers/baizhi_mcp/actions.js +40 -0
  75. package/src/providers/baizhi_mcp/executors.d.ts +3 -0
  76. package/src/providers/baizhi_mcp/executors.js +102 -0
  77. package/src/providers/bazhuayu/runtime.js +2 -2
  78. package/src/providers/bilibili/articles.d.ts +3 -0
  79. package/src/providers/bilibili/articles.js +369 -0
  80. package/src/providers/bilibili/executors.d.ts +8 -0
  81. package/src/providers/bilibili/executors.js +262 -0
  82. package/src/providers/bilibili/runtime.d.ts +95 -0
  83. package/src/providers/bilibili/runtime.js +236 -0
  84. package/src/providers/bilibili/upload.d.ts +14 -0
  85. package/src/providers/bilibili/upload.js +107 -0
  86. package/src/providers/bitwarden/executors.d.ts +3 -0
  87. package/src/providers/bitwarden/executors.js +146 -0
  88. package/src/providers/cloudflare_browser_rendering/executors.js +1 -1
  89. package/src/providers/cloudflare_dns/executors.js +1 -1
  90. package/src/providers/{cloudflare-current-user.js → cloudflare_dns/runtime-user.js} +2 -2
  91. package/src/providers/cloudflare_dns/runtime.d.ts +1 -1
  92. package/src/providers/cloudflare_dns/runtime.js +1 -1
  93. package/src/providers/cloudflare_r2/executors.js +1 -1
  94. package/src/providers/cloudflare_r2/runtime.d.ts +1 -1
  95. package/src/providers/cloudflare_r2/runtime.js +1 -1
  96. package/src/providers/cloudflare_worker/executors.js +1 -1
  97. package/src/providers/cloudflare_worker/runtime.d.ts +1 -1
  98. package/src/providers/cloudflare_worker/runtime.js +1 -1
  99. package/src/providers/cronfree/executors.d.ts +4 -0
  100. package/src/providers/cronfree/executors.js +89 -0
  101. package/src/providers/cufinder/executors.d.ts +4 -0
  102. package/src/providers/cufinder/executors.js +105 -0
  103. package/src/providers/dingtalk_mcp/runtime.js +1 -1
  104. package/src/providers/discordbot/executors.d.ts +0 -9
  105. package/src/providers/discordbot/executors.js +9 -101
  106. package/src/providers/discordbot/runtime-guilds.d.ts +3 -0
  107. package/src/providers/discordbot/runtime-guilds.js +464 -0
  108. package/src/providers/discordbot/runtime.d.ts +42 -0
  109. package/src/providers/discordbot/runtime.js +124 -0
  110. package/src/providers/drawio_mcp/executors.d.ts +3 -0
  111. package/src/providers/drawio_mcp/executors.js +19 -0
  112. package/src/providers/drawio_mcp/runtime.d.ts +8 -0
  113. package/src/providers/drawio_mcp/runtime.js +175 -0
  114. package/src/providers/elevenreader/runtime.js +6 -5
  115. package/src/providers/fastmoss_mcp/executors.js +2 -2
  116. package/src/providers/finchina/executors.js +1 -1
  117. package/src/providers/framejet/executors.d.ts +4 -0
  118. package/src/providers/framejet/executors.js +19 -0
  119. package/src/providers/framejet/runtime.d.ts +16 -0
  120. package/src/providers/framejet/runtime.js +148 -0
  121. package/src/providers/futunn/actions.d.ts +2 -0
  122. package/src/providers/futunn/actions.js +152 -0
  123. package/src/providers/futunn/actions.research.d.ts +2 -0
  124. package/src/providers/futunn/actions.research.js +423 -0
  125. package/src/providers/futunn/actions.trading.d.ts +2 -0
  126. package/src/providers/futunn/actions.trading.js +204 -0
  127. package/src/providers/futunn/executors.d.ts +3 -0
  128. package/src/providers/futunn/executors.js +24 -0
  129. package/src/providers/futunn/runtime-research.d.ts +3 -0
  130. package/src/providers/futunn/runtime-research.js +133 -0
  131. package/src/providers/futunn/runtime-trading.d.ts +3 -0
  132. package/src/providers/futunn/runtime-trading.js +39 -0
  133. package/src/providers/futunn/runtime.d.ts +2 -0
  134. package/src/providers/futunn/runtime.js +50 -0
  135. package/src/providers/futunn/transport.d.ts +11 -0
  136. package/src/providers/futunn/transport.js +76 -0
  137. package/src/providers/gildata/executors.js +1 -1
  138. package/src/providers/gmail/attachment-stream.d.ts +2 -0
  139. package/src/providers/gmail/attachment-stream.js +162 -0
  140. package/src/providers/gmail/executors.d.ts +3 -1
  141. package/src/providers/gmail/executors.js +54 -26
  142. package/src/providers/gmail/message.d.ts +6 -8
  143. package/src/providers/gmail/message.js +4 -0
  144. package/src/providers/gmail/scopes.d.ts +32 -0
  145. package/src/providers/gmail/scopes.js +37 -0
  146. package/src/providers/google_analytics/executors.js +2 -2
  147. package/src/providers/google_analytics/scopes.js +6 -1
  148. package/src/providers/google_bigquery/executors.js +2 -2
  149. package/src/providers/google_bigquery/scopes.js +2 -0
  150. package/src/providers/google_search_console/executors.js +2 -2
  151. package/src/providers/google_search_console/scopes.js +2 -0
  152. package/src/providers/googleads/executors.js +1 -1
  153. package/src/providers/googleads/scopes.d.ts +1 -3
  154. package/src/providers/googleads/scopes.js +2 -4
  155. package/src/providers/googlecalendar/executors.js +1 -1
  156. package/src/providers/googlecalendar/runtime-shared.js +2 -2
  157. package/src/providers/googlecalendar/scopes.js +2 -0
  158. package/src/providers/googlechat/executors.d.ts +1 -3
  159. package/src/providers/googlechat/executors.js +219 -28
  160. package/src/providers/googlechat/members.d.ts +55 -0
  161. package/src/providers/googlechat/members.js +293 -0
  162. package/src/providers/googlechat/runtime.d.ts +16 -0
  163. package/src/providers/googlechat/runtime.js +25 -0
  164. package/src/providers/googlechat/scopes.d.ts +22 -3
  165. package/src/providers/googlechat/scopes.js +30 -7
  166. package/src/providers/googledocs/executors.js +1 -1
  167. package/src/providers/googledrive/executors.js +2 -2
  168. package/src/providers/{google-auth.d.ts → googledrive/runtime-auth.d.ts} +2 -2
  169. package/src/providers/{google-auth.js → googledrive/runtime-auth.js} +3 -3
  170. package/src/providers/googledrive/runtime-collaboration.js +1 -1
  171. package/src/providers/{google-runtime.d.ts → googledrive/runtime-request.d.ts} +1 -1
  172. package/src/providers/{google-runtime.js → googledrive/runtime-request.js} +2 -2
  173. package/src/providers/googleforms/executors.js +2 -2
  174. package/src/providers/googleforms/scopes.d.ts +0 -3
  175. package/src/providers/googleforms/scopes.js +2 -6
  176. package/src/providers/googlemeet/executors.js +2 -2
  177. package/src/providers/googlemeet/scopes.d.ts +0 -3
  178. package/src/providers/googlemeet/scopes.js +2 -6
  179. package/src/providers/googlesheets/executors.js +1 -1
  180. package/src/providers/googlesheets/scopes.js +2 -0
  181. package/src/providers/googleslides/executors.js +2 -2
  182. package/src/providers/googleslides/scopes.d.ts +0 -3
  183. package/src/providers/googleslides/scopes.js +2 -6
  184. package/src/providers/googletasks/executors.js +2 -2
  185. package/src/providers/googletasks/scopes.d.ts +0 -3
  186. package/src/providers/googletasks/scopes.js +2 -6
  187. package/src/providers/granola/actions.js +2 -0
  188. package/src/providers/granola/runtime-mcp.js +2 -0
  189. package/src/providers/granola/runtime.js +2 -2
  190. package/src/providers/indiegogo/executors.js +1 -1
  191. package/src/providers/investoday_mcp/executors.js +2 -2
  192. package/src/providers/investoday_mcp/official-actions.js +19 -372
  193. package/src/providers/investoday_mcp/official-tool-types.d.ts +10 -0
  194. package/src/providers/investoday_mcp/official-tool-types.js +4 -0
  195. package/src/providers/investoday_mcp/official-tools/charts.d.ts +2 -0
  196. package/src/providers/investoday_mcp/official-tools/charts.js +32 -0
  197. package/src/providers/investoday_mcp/official-tools/fixed-income.d.ts +2 -0
  198. package/src/providers/investoday_mcp/official-tools/fixed-income.js +326 -0
  199. package/src/providers/investoday_mcp/official-tools/funds.d.ts +2 -0
  200. package/src/providers/investoday_mcp/official-tools/funds.js +1085 -0
  201. package/src/providers/investoday_mcp/official-tools/hong-kong.d.ts +2 -0
  202. package/src/providers/investoday_mcp/official-tools/hong-kong.js +275 -0
  203. package/src/providers/investoday_mcp/official-tools/indices-industries.d.ts +2 -0
  204. package/src/providers/investoday_mcp/official-tools/indices-industries.js +642 -0
  205. package/src/providers/investoday_mcp/official-tools/macro-calendar.d.ts +2 -0
  206. package/src/providers/investoday_mcp/official-tools/macro-calendar.js +398 -0
  207. package/src/providers/investoday_mcp/official-tools/other.d.ts +2 -0
  208. package/src/providers/investoday_mcp/official-tools/other.js +131 -0
  209. package/src/providers/investoday_mcp/official-tools/research-news.d.ts +2 -0
  210. package/src/providers/investoday_mcp/official-tools/research-news.js +302 -0
  211. package/src/providers/investoday_mcp/official-tools/stocks.d.ts +2 -0
  212. package/src/providers/investoday_mcp/official-tools/stocks.js +2829 -0
  213. package/src/providers/ixspy/executors.d.ts +3 -0
  214. package/src/providers/ixspy/executors.js +220 -0
  215. package/src/providers/iyunbiao/runtime.js +2 -2
  216. package/src/providers/keepa/actions.d.ts +1 -1
  217. package/src/providers/keepa/actions.js +371 -149
  218. package/src/providers/keepa/deal-attributes.d.ts +1 -0
  219. package/src/providers/keepa/deal-attributes.js +26 -0
  220. package/src/providers/keepa/runtime.js +212 -18
  221. package/src/providers/kingdee/executors.js +1 -1
  222. package/src/providers/kingdee/runtime.js +2 -2
  223. package/src/providers/kuaidi100/runtime.js +1 -1
  224. package/src/providers/linear/executors.js +12 -9
  225. package/src/providers/linear_mcp/actions.d.ts +47 -0
  226. package/src/providers/linear_mcp/actions.js +102 -0
  227. package/src/providers/linear_mcp/endpoints.d.ts +3 -0
  228. package/src/providers/linear_mcp/endpoints.js +3 -0
  229. package/src/providers/linear_mcp/executors.d.ts +3 -0
  230. package/src/providers/linear_mcp/executors.js +9 -0
  231. package/src/providers/linear_mcp/runtime-mcp.d.ts +15 -0
  232. package/src/providers/linear_mcp/runtime-mcp.js +238 -0
  233. package/src/providers/lingxing_mcp/runtime.js +1 -1
  234. package/src/providers/mcdonalds_cn_mcp/runtime.js +1 -1
  235. package/src/providers/metabase/mcp-runtime.js +1 -1
  236. package/src/providers/moneyforward_mcp/executors.d.ts +3 -0
  237. package/src/providers/moneyforward_mcp/executors.js +11 -0
  238. package/src/providers/moneyforward_mcp/manifest.d.ts +31 -0
  239. package/src/providers/moneyforward_mcp/manifest.js +204 -0
  240. package/src/providers/moneyforward_mcp/runtime.d.ts +18 -0
  241. package/src/providers/moneyforward_mcp/runtime.js +296 -0
  242. package/src/providers/notion/executors.js +39 -3
  243. package/src/providers/notion_mcp/endpoints.d.ts +3 -0
  244. package/src/providers/notion_mcp/endpoints.js +3 -0
  245. package/src/providers/notion_mcp/executors.d.ts +3 -0
  246. package/src/providers/notion_mcp/executors.js +176 -0
  247. package/src/providers/notion_mcp/mcp-response.d.ts +86 -0
  248. package/src/providers/notion_mcp/mcp-response.js +314 -0
  249. package/src/providers/odoo/executors.d.ts +3 -0
  250. package/src/providers/odoo/executors.js +32 -0
  251. package/src/providers/odoo/runtime.d.ts +16 -0
  252. package/src/providers/odoo/runtime.js +140 -0
  253. package/src/providers/osv/executors.d.ts +2 -0
  254. package/src/providers/osv/executors.js +11 -0
  255. package/src/providers/osv/runtime.d.ts +9 -0
  256. package/src/providers/osv/runtime.js +66 -0
  257. package/src/providers/outlook/executors.js +27 -6
  258. package/src/providers/outlook/microsoft-graph.js +17 -6
  259. package/src/providers/outlook_calendar/executors.js +24 -7
  260. package/src/providers/patsnap_mcp/executors.js +2 -2
  261. package/src/providers/pdf_vector/executors.d.ts +4 -0
  262. package/src/providers/pdf_vector/executors.js +108 -0
  263. package/src/providers/postiz/executors.d.ts +4 -0
  264. package/src/providers/postiz/executors.js +83 -0
  265. package/src/providers/provider-runtime.d.ts +18 -0
  266. package/src/providers/provider-runtime.js +60 -4
  267. package/src/providers/proworkflow/executors.d.ts +4 -0
  268. package/src/providers/proworkflow/executors.js +135 -0
  269. package/src/providers/pulsetic/executors.d.ts +4 -0
  270. package/src/providers/pulsetic/executors.js +126 -0
  271. package/src/providers/registry.generated.js +24 -0
  272. package/src/providers/scrape_creators/catalog.js +1 -1
  273. package/src/providers/scrape_creators/runtime.js +1 -1
  274. package/src/providers/sellersprite_mcp/executors.js +2 -2
  275. package/src/providers/sellersprite_mcp/official-actions.js +3 -2
  276. package/src/providers/sentry/runtime.js +6 -7
  277. package/src/providers/shopee/actions.d.ts +2 -0
  278. package/src/providers/shopee/actions.js +474 -0
  279. package/src/providers/shopee/executors.d.ts +3 -0
  280. package/src/providers/shopee/executors.js +85 -0
  281. package/src/providers/shopee/runtime.d.ts +79 -0
  282. package/src/providers/shopee/runtime.js +693 -0
  283. package/src/providers/slack/actions.js +69 -3
  284. package/src/providers/slack/runtime.js +116 -21
  285. package/src/providers/slickdeals/runtime.js +1 -1
  286. package/src/providers/sorftime_mcp/executors.js +2 -2
  287. package/src/providers/speak_ai/executors.d.ts +3 -0
  288. package/src/providers/speak_ai/executors.js +187 -0
  289. package/src/providers/tikhub/bounded-response.d.ts +1 -1
  290. package/src/providers/tikhub/bounded-response.js +8 -34
  291. package/src/providers/tikhub/endpoint-catalog.js +1 -1
  292. package/src/providers/tikhub/runtime.js +2 -2
  293. package/src/providers/tongdaxin/executors.js +79 -15
  294. package/src/providers/tushare/runtime.js +22 -9
  295. package/src/providers/uspto/executors.js +1 -1
  296. package/src/providers/waffo/executors.js +1 -1
  297. package/src/providers/waffo/runtime.js +1 -1
  298. package/src/providers/wecom_mcp/runtime.js +1 -1
  299. package/src/providers/wisburg/executors.d.ts +4 -0
  300. package/src/providers/wisburg/executors.js +13 -0
  301. package/src/providers/wisburg/runtime.d.ts +12 -0
  302. package/src/providers/wisburg/runtime.js +133 -0
  303. package/src/providers/xydc_mcp/executors.js +2 -2
  304. package/src/providers/youtube/executors.js +1 -1
  305. package/src/providers/youtube/runtime.js +1 -1
  306. package/src/providers/zerotier/executors.d.ts +9 -0
  307. package/src/providers/zerotier/executors.js +629 -0
  308. package/src/providers/zerotier/runtime.d.ts +54 -0
  309. package/src/providers/zerotier/runtime.js +207 -0
  310. package/src/providers/zyhub/executors.d.ts +3 -0
  311. package/src/providers/zyhub/executors.js +240 -0
  312. package/src/server/api/openapi.js +12 -2
  313. package/src/server/api/runtime-api.d.ts +8 -1
  314. package/src/server/api/runtime-api.js +19 -1
  315. package/src/server/connect-server.js +10 -2
  316. package/src/server/files/transit-files.d.ts +3 -2
  317. package/src/server/files/transit-files.js +31 -35
  318. package/src/providers/sentry/scopes.d.ts +0 -1
  319. package/src/providers/sentry/scopes.js +0 -7
  320. /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` and `files`. Those
64
- // are unbounded nested payloads on a row shape that ETL reads in bulk, and
65
- // each wants its own normalized contract rather than a raw passthrough.
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
- return readSlackMessagePage(await slackGetJson(url, context), "conversations.history");
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
- return readSlackMessagePage(await slackGetJson(url, context), "conversations.replies");
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.status, payload, response.headers.get("retry-after"));
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(status, payload, retryAfter) {
983
- const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${status}`;
984
- if (status === 429 && retryAfter !== null && /^\d+$/.test(retryAfter)) {
985
- const retryAfterSeconds = Number(retryAfter);
986
- if (Number.isSafeInteger(retryAfterSeconds)) {
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}`);
@@ -155,7 +155,7 @@ async function requestSlickdealsJson(input) {
155
155
  authorization: `Bearer ${input.token}`,
156
156
  "user-agent": providerUserAgent,
157
157
  },
158
- redirect: "error",
158
+ redirect: "manual",
159
159
  signal,
160
160
  });
161
161
  const payload = await readPayload(response);
@@ -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: "error",
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: "error",
82
+ redirect: "manual",
83
83
  signal: context.signal,
84
84
  mapError: mapSorftimeError,
85
85
  }, run);
@@ -0,0 +1,3 @@
1
+ import type { CredentialValidators, ProviderExecutors } from "../../core/types.ts";
2
+ export declare const executors: ProviderExecutors;
3
+ export declare const credentialValidators: CredentialValidators;
@@ -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): Promise<void>;
8
+ export declare function cancelResponseBody(response: Response): void;