@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
@@ -0,0 +1,124 @@
1
+ import { looseArray, optionalRecord, optionalString, recordOrEmpty } from "../../core/cast.js";
2
+ import { jsonObject } from "../../core/request.js";
3
+ import { providerInputError, ProviderRequestError, requiredInputString } from "../provider-runtime.js";
4
+ // Pin actions to v10. An unversioned route goes to Discord's default version, which is
5
+ // still the deprecated v6: 31-bit numeric permissions, no threads, and a 204 without the
6
+ // member from Modify Guild Member.
7
+ const discordRestBaseUrl = "https://discord.com/api/v10";
8
+ // The proxy stays unversioned so existing proxy paths, including ones that already
9
+ // start with a version such as /v10, keep resolving as before.
10
+ export const discordProxyBaseUrl = "https://discord.com/api";
11
+ /** Send a Discord request and parse the JSON response body. */
12
+ export async function discordbotRequestJson(input) {
13
+ return readDiscordbotJson(await discordbotRequest(input));
14
+ }
15
+ /**
16
+ * Send a Discord request whose success may be 200 with a JSON body or 204 with none,
17
+ * returning null for the 204.
18
+ */
19
+ export async function discordbotRequestJsonOrNull(input) {
20
+ const response = await discordbotRequest(input);
21
+ return response.status === 204 ? null : readDiscordbotJson(response);
22
+ }
23
+ /** Send a Discord request that answers with 204 No Content. */
24
+ export async function discordbotRequestNoContent(input) {
25
+ await discordbotRequest(input);
26
+ return { success: true };
27
+ }
28
+ /** Send a Discord request, mapping non-2xx responses to provider errors unless `skipError` is set. */
29
+ export async function discordbotRequest(input) {
30
+ const url = new URL(`${discordRestBaseUrl}${input.path}`);
31
+ for (const [key, value] of Object.entries(input.query ?? {})) {
32
+ if (value !== undefined) {
33
+ url.searchParams.set(key, String(value));
34
+ }
35
+ }
36
+ const headers = new Headers();
37
+ if (input.authenticated !== false) {
38
+ headers.set("authorization", `Bot ${input.context.apiKey}`);
39
+ }
40
+ if (input.body !== undefined) {
41
+ headers.set("content-type", "application/json");
42
+ }
43
+ const auditLogReason = optionalString(input.auditLogReason);
44
+ if (auditLogReason) {
45
+ // Discord reads the header as URL-encoded UTF-8.
46
+ headers.set("x-audit-log-reason", encodeURIComponent(auditLogReason));
47
+ }
48
+ const response = await input.context.fetcher(url, {
49
+ method: input.method,
50
+ headers,
51
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
52
+ signal: input.context.signal,
53
+ });
54
+ if (!input.skipError && !response.ok) {
55
+ throw await toDiscordbotError(response);
56
+ }
57
+ return response;
58
+ }
59
+ /**
60
+ * Read a required snowflake input for use as one URL path segment. Only digits are
61
+ * accepted, because percent-encoding cannot keep an arbitrary id inside one segment:
62
+ * URL parsing resolves `.` and `..` as dot segments, and Discord decodes `%2F` into a
63
+ * path separator before routing. Either would send the request, with the same method
64
+ * and bot token, to another endpoint: `role_id: ".."` turns a role DELETE into one on
65
+ * the guild itself, and `user_id: "U/roles/R"` on Add Guild Member grants role R.
66
+ */
67
+ export function requiredPath(value, field) {
68
+ const segment = requiredInputString(value, field);
69
+ if (!/^\d+$/.test(segment)) {
70
+ throw providerInputError(`${field} must be a numeric Discord snowflake id`);
71
+ }
72
+ return segment;
73
+ }
74
+ async function readDiscordbotJson(response) {
75
+ try {
76
+ return (await response.json());
77
+ }
78
+ catch {
79
+ throw new ProviderRequestError(502, "Discord returned invalid JSON");
80
+ }
81
+ }
82
+ async function toDiscordbotError(response) {
83
+ const text = await response.text().catch(() => "");
84
+ let payload;
85
+ try {
86
+ payload = optionalRecord(JSON.parse(text));
87
+ }
88
+ catch { }
89
+ const message = optionalString(payload?.message) ??
90
+ optionalString(payload?.error_description) ??
91
+ optionalString(payload?.error) ??
92
+ text;
93
+ // A form error's message is only "Invalid Form Body"; the failing fields are in `errors`.
94
+ const formErrors = collectDiscordbotFormErrors(payload?.errors, "");
95
+ const resolvedMessage = formErrors.length > 0
96
+ ? `${message}: ${formErrors.join("; ")}`
97
+ : message || `Discord request failed with ${response.status}`;
98
+ const details = payload
99
+ ? jsonObject({ code: payload.code, errors: payload.errors, retry_after: payload.retry_after })
100
+ : undefined;
101
+ // Discord answers 403 when the bot lacks a guild or channel permission, such as
102
+ // "Missing Permissions" or "Missing Access". The token still works, so this must
103
+ // not read as authorization_failed, which clients treat as "reconnect".
104
+ if (response.status === 403) {
105
+ return new ProviderRequestError(403, resolvedMessage, details, "invalid_input");
106
+ }
107
+ return new ProviderRequestError(response.status, resolvedMessage, details);
108
+ }
109
+ /**
110
+ * Flatten Discord's form errors into `path: message` entries. Each failing JSON key,
111
+ * or array index, nests down to an `_errors` list of `{ code, message }`; an `_errors`
112
+ * list at the top level describes the request as a whole and has no path.
113
+ */
114
+ function collectDiscordbotFormErrors(errors, path) {
115
+ return Object.entries(recordOrEmpty(errors)).flatMap(([key, value]) => {
116
+ if (key !== "_errors") {
117
+ return collectDiscordbotFormErrors(value, path ? `${path}.${key}` : key);
118
+ }
119
+ return looseArray(value).flatMap((item) => {
120
+ const message = optionalString(optionalRecord(item)?.message);
121
+ return message ? [path ? `${path}: ${message}` : message] : [];
122
+ });
123
+ });
124
+ }
@@ -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,19 @@
1
+ import { isPrivateNetworkAccessAllowed } from "../../core/request.js";
2
+ import { createProviderFetch, defineProviderExecutors, requireCustomCredential } from "../provider-runtime.js";
3
+ import { createDrawioMcpContext, drawioMcpActionHandlers, validateDrawioMcpCredential } from "./runtime.js";
4
+ const service = "drawio_mcp";
5
+ export const executors = defineProviderExecutors({
6
+ service,
7
+ handlers: drawioMcpActionHandlers,
8
+ async createContext(context, fetcher) {
9
+ const credential = await requireCustomCredential(context, service);
10
+ return createDrawioMcpContext(credential.values, fetcher, context.signal);
11
+ },
12
+ allowPrivateNetwork: isPrivateNetworkAccessAllowed,
13
+ });
14
+ export const credentialValidators = {
15
+ customCredential(input, { fetcher, signal }) {
16
+ const guardedFetcher = createProviderFetch({ fetch: fetcher, allowPrivateNetwork: isPrivateNetworkAccessAllowed });
17
+ return validateDrawioMcpCredential(input.values, guardedFetcher, signal);
18
+ },
19
+ };
@@ -0,0 +1,8 @@
1
+ import type { CredentialValidationResult } from "../../core/types.ts";
2
+ import type { McpToolOptions } from "../mcp-tools.ts";
3
+ import type { ProviderActionHandlers, ProviderFetch, ProviderRuntimeHandler } from "../provider-runtime.ts";
4
+ export declare const drawioMcpActionHandlers: ProviderActionHandlers<"drawio_mcp", ProviderRuntimeHandler<McpToolOptions>>;
5
+ /** Resolve the MCP server a connection points at; a blank endpoint selects the hosted draw.io server. */
6
+ export declare function createDrawioMcpContext(values: Record<string, string>, fetcher: ProviderFetch, signal?: AbortSignal): McpToolOptions;
7
+ /** Confirm the endpoint serves the draw.io tools and derive a stable identity from the endpoint. */
8
+ export declare function validateDrawioMcpCredential(values: Record<string, string>, fetcher: ProviderFetch, signal?: AbortSignal): Promise<CredentialValidationResult>;
@@ -0,0 +1,175 @@
1
+ import { sha256Hex } from "../../core/aws-sigv4.js";
2
+ import { looseArray, optionalInteger, optionalNumber, optionalRawString, optionalRecord, optionalString, } from "../../core/cast.js";
3
+ import { assertPublicHttpUrl, isPrivateNetworkAccessAllowed } from "../../core/request.js";
4
+ import { callMcpTool, listMcpTools } from "../mcp-tools.js";
5
+ import { parseProviderJsonBodyText, providerInputError, providerResponseError, requiredInputString, } from "../provider-runtime.js";
6
+ const hostedEndpoint = "https://mcp.draw.io/mcp";
7
+ const defaultShapeLimit = 10;
8
+ // The two create_diagram tool errors that judge the submitted source; anything else is a server failure.
9
+ const sourceVerdictPrefixes = ["Could not extract draw.io XML", "Provide exactly one of"];
10
+ const errorsHeading = "ERRORS (will cause rendering issues):\n- ";
11
+ const warningsHeading = "WARNINGS (may cause issues):\n- ";
12
+ export const drawioMcpActionHandlers = {
13
+ async create_diagram(input, options) {
14
+ const result = await callMcpTool({
15
+ ...options,
16
+ toolName: "create_diagram",
17
+ arguments: { mermaid: optionalString(input.mermaid), xml: optionalString(input.xml) },
18
+ transformToolResult: rejectDiagramSource,
19
+ });
20
+ const texts = readTextContent(result);
21
+ const editorUrl = readEditorUrl(texts);
22
+ if (editorUrl === undefined) {
23
+ throw providerResponseError("draw.io MCP create_diagram response did not include an editor link; a self-hosted server must be jgraph/drawio-mcp mcp-app-server 1.0.4 or later");
24
+ }
25
+ return { editorUrl, ...readFindings(texts) };
26
+ },
27
+ async search_shapes(input, options) {
28
+ const result = await callMcpTool({
29
+ ...options,
30
+ toolName: "search_shapes",
31
+ arguments: {
32
+ query: requiredInputString(input.query, "query"),
33
+ limit: optionalInteger(input.limit) ?? defaultShapeLimit,
34
+ },
35
+ });
36
+ return { shapes: readShapes(result) };
37
+ },
38
+ };
39
+ /** Resolve the MCP server a connection points at; a blank endpoint selects the hosted draw.io server. */
40
+ export function createDrawioMcpContext(values, fetcher, signal) {
41
+ return {
42
+ endpoint: normalizeDrawioMcpEndpoint(values.mcpEndpoint),
43
+ service: "draw.io",
44
+ fetcher,
45
+ signal,
46
+ terminateSession: true,
47
+ maxResponseBytes: 4 * 1024 * 1024,
48
+ // tools/list is about 64 KB because create_diagram embeds draw.io's drawing reference in its description.
49
+ toolListMaxBytes: 1024 * 1024,
50
+ toolListMaxPages: 5,
51
+ toolListMaxTools: 100,
52
+ };
53
+ }
54
+ /** Confirm the endpoint serves the draw.io tools and derive a stable identity from the endpoint. */
55
+ export async function validateDrawioMcpCredential(values, fetcher, signal) {
56
+ const options = createDrawioMcpContext(values, fetcher, signal);
57
+ const discovered = new Set((await listMcpTools(options)).map((tool) => tool.name));
58
+ const missing = Object.keys(drawioMcpActionHandlers).filter((toolName) => !discovered.has(toolName));
59
+ if (missing.length > 0) {
60
+ throw providerInputError(`mcpEndpoint does not serve the draw.io MCP tools: missing ${missing.join(", ")}`);
61
+ }
62
+ return {
63
+ profile: {
64
+ accountId: `drawio_mcp:mcp:${sha256Hex(options.endpoint).slice(0, 16)}`,
65
+ displayName: `draw.io MCP · ${new URL(options.endpoint).host}`,
66
+ },
67
+ grantedScopes: [],
68
+ metadata: { mcpEndpoint: options.endpoint },
69
+ };
70
+ }
71
+ function normalizeDrawioMcpEndpoint(value) {
72
+ const allowPrivateNetwork = isPrivateNetworkAccessAllowed();
73
+ const url = assertPublicHttpUrl(optionalString(value) ?? hostedEndpoint, {
74
+ fieldName: "mcpEndpoint",
75
+ createError: providerInputError,
76
+ allowPrivateNetwork,
77
+ });
78
+ if (url.username || url.password) {
79
+ throw providerInputError("mcpEndpoint must not include a username or password");
80
+ }
81
+ if (url.protocol === "http:" && !allowPrivateNetwork) {
82
+ throw providerInputError("mcpEndpoint must use https unless private-network access is enabled");
83
+ }
84
+ url.search = "";
85
+ url.hash = "";
86
+ return url.toString();
87
+ }
88
+ // Also drops structured content: the editor link and findings only ever arrive as text blocks,
89
+ // which the shared result normalizer would otherwise discard in favour of structuredContent.
90
+ function rejectDiagramSource(result) {
91
+ const message = "content" in result && result.isError ? readTextContent(result).join("\n") : "";
92
+ if (sourceVerdictPrefixes.some((prefix) => message.startsWith(prefix))) {
93
+ throw providerInputError(`draw.io rejected the diagram source: ${message.slice(0, 300)}`);
94
+ }
95
+ return { ...result, structuredContent: undefined };
96
+ }
97
+ // draw.io puts the editor link on a line of its own after the source echo, which is single-line JSON, and after
98
+ // the XML findings, which quote attribute values from the source verbatim, line breaks included. Skipping the
99
+ // findings and taking the last such line keeps a link written inside the submitted source from being returned instead.
100
+ function readEditorUrl(texts) {
101
+ return texts
102
+ .filter((text) => !isFindingsText(text))
103
+ .flatMap((text) => text.split("\n"))
104
+ .findLast((line) => line.startsWith("https://") && line.includes("#create="));
105
+ }
106
+ // draw.io reports XML problems in one text block of "ERRORS (...):\n- item" and "WARNINGS (...):\n- item" sections.
107
+ function isFindingsText(text) {
108
+ return text.startsWith("ERRORS ") || text.startsWith("WARNINGS ");
109
+ }
110
+ function readTextContent(result) {
111
+ return looseArray(optionalRecord(result)?.content).flatMap((item) => {
112
+ const content = optionalRecord(item);
113
+ const text = content?.type === "text" ? optionalRawString(content.text) : undefined;
114
+ return text === undefined ? [] : [text];
115
+ });
116
+ }
117
+ // Findings items quote attribute values verbatim, so blank lines and line starts can come from the source. Only
118
+ // draw.io's exact layout splits the block: each heading is followed by the items joined with "\n- ", and a blank
119
+ // line separates the errors section from the warnings section. The layout leaves no way to tell a quoted value from
120
+ // that structure: a value that itself contains "\n- " still reads as two items, and one that contains the whole
121
+ // "\n\nWARNINGS (may cause issues):\n- " separator can move items between errors and warnings. The editor link is
122
+ // unaffected, since readEditorUrl skips the findings block as a whole.
123
+ function readFindings(texts) {
124
+ const text = texts.find(isFindingsText) ?? "";
125
+ if (text.startsWith(warningsHeading)) {
126
+ return { errors: [], warnings: splitFindings(text.slice(warningsHeading.length)) };
127
+ }
128
+ if (!text.startsWith(errorsHeading)) {
129
+ return { errors: [], warnings: [] };
130
+ }
131
+ const body = text.slice(errorsHeading.length);
132
+ const separator = body.lastIndexOf(`\n\n${warningsHeading}`);
133
+ if (separator < 0) {
134
+ return { errors: splitFindings(body), warnings: [] };
135
+ }
136
+ return {
137
+ errors: splitFindings(body.slice(0, separator)),
138
+ warnings: splitFindings(body.slice(separator + 2 + warningsHeading.length)),
139
+ };
140
+ }
141
+ // draw.io never reports an empty finding, so an empty piece can only come from a quoted value that contains the
142
+ // separator, such as one ending in "\n- ", and is dropped.
143
+ function splitFindings(section) {
144
+ return section.split("\n- ").filter((item) => item !== "");
145
+ }
146
+ // Newer draw.io servers return structured shapes; the hosted server returns a JSON array as text.
147
+ function readShapes(result) {
148
+ const structured = optionalRecord(result)?.shapes;
149
+ const payload = structured === undefined ? parseShapeText(readTextContent(result)[0]) : structured;
150
+ if (!Array.isArray(payload)) {
151
+ throw providerResponseError("draw.io MCP search_shapes returned an unexpected payload");
152
+ }
153
+ return payload.map(readShape);
154
+ }
155
+ function parseShapeText(text) {
156
+ if (text === undefined) {
157
+ throw providerResponseError("draw.io MCP search_shapes response did not include any text");
158
+ }
159
+ if (text.startsWith("No shapes found"))
160
+ return [];
161
+ return parseProviderJsonBodyText(text, {
162
+ emptyBody: [],
163
+ invalidJsonMessage: `draw.io MCP search_shapes returned an unexpected payload: ${text.slice(0, 200)}`,
164
+ });
165
+ }
166
+ function readShape(value) {
167
+ const shape = optionalRecord(value);
168
+ const style = optionalRawString(shape?.style);
169
+ const width = optionalNumber(shape?.w);
170
+ const height = optionalNumber(shape?.h);
171
+ if (!style || width === undefined || height === undefined) {
172
+ throw providerResponseError("draw.io MCP search_shapes returned a shape without a style or size");
173
+ }
174
+ return { title: optionalRawString(shape?.title) ?? "", style, width, height };
175
+ }
@@ -1,7 +1,7 @@
1
1
  import { booleanString, compactObject, optionalBoolean, optionalInteger, optionalRecord, optionalString, requiredString, stringRecord, } from "../../core/cast.js";
2
2
  import { readBoundedResponseBytes } from "../../core/request.js";
3
3
  import { providerInputError, providerUserAgent, ProviderRequestError } from "../provider-runtime.js";
4
- const elevenreaderApiBaseUrl = "https://api.elevenlabs.io/v1";
4
+ const elevenreaderApiOrigin = "https://api.elevenlabs.io";
5
5
  export const elevenreaderActionHandlers = {
6
6
  get_user_info(_input, context) {
7
7
  return getElevenreaderUserInfo(context);
@@ -74,7 +74,8 @@ async function getElevenreaderModels(context) {
74
74
  }
75
75
  async function searchElevenreaderVoices(input, context) {
76
76
  const payload = await requestElevenreaderJson({
77
- path: "/voices/search",
77
+ path: "/voices",
78
+ version: "v2",
78
79
  query: compactObject({
79
80
  search: optionalString(input.search),
80
81
  category: optionalString(input.category),
@@ -163,7 +164,7 @@ async function readElevenreaderText(input, context) {
163
164
  }
164
165
  async function requestElevenreaderJson(input) {
165
166
  const hasBody = input.body !== undefined;
166
- const response = await input.fetcher(buildElevenreaderUrl(input.path, input.query), {
167
+ const response = await input.fetcher(buildElevenreaderUrl(input.path, input.query, input.version), {
167
168
  method: hasBody ? "POST" : "GET",
168
169
  headers: hasBody ? elevenreaderJsonHeaders(input.apiKey) : elevenreaderHeaders(input.apiKey),
169
170
  body: hasBody ? JSON.stringify(input.body) : undefined,
@@ -174,8 +175,8 @@ async function requestElevenreaderJson(input) {
174
175
  }
175
176
  return readElevenreaderJson(response);
176
177
  }
177
- function buildElevenreaderUrl(path, query) {
178
- const url = new URL(`${elevenreaderApiBaseUrl}${path}`);
178
+ function buildElevenreaderUrl(path, query, version = "v1") {
179
+ const url = new URL(`${elevenreaderApiOrigin}/${version}${path}`);
179
180
  for (const [key, value] of Object.entries(query ?? {})) {
180
181
  if (value !== undefined) {
181
182
  url.searchParams.set(key, value);
@@ -25,7 +25,7 @@ export const proxy = defineProviderProxy({
25
25
  baseUrl: origin,
26
26
  auth: { type: "api_key_query", name: "api_key" },
27
27
  skipDnsValidation: true,
28
- redirect: "error",
28
+ redirect: "manual",
29
29
  allowedEndpoint(value) {
30
30
  const url = new URL(value, origin);
31
31
  return (url.origin === origin &&
@@ -57,7 +57,7 @@ function connectionInput(key, fetcher, signal) {
57
57
  service: "FastMoss",
58
58
  fetcher,
59
59
  signal,
60
- redirect: "error",
60
+ redirect: "manual",
61
61
  terminateSession: true,
62
62
  maxResponseBytes: 16 * 1024 * 1024,
63
63
  };
@@ -98,7 +98,7 @@ function mcpInput(apiKey, fetcher, signal) {
98
98
  service: "FinChina",
99
99
  headers: { "x-api-key": apiKey },
100
100
  fetcher,
101
- redirect: "error",
101
+ redirect: "manual",
102
102
  terminateSession: true,
103
103
  };
104
104
  }
@@ -0,0 +1,4 @@
1
+ import type { CredentialValidators, ProviderExecutors, ProviderProxyExecutor } from "../../core/types.ts";
2
+ export declare const executors: ProviderExecutors;
3
+ export declare const proxy: ProviderProxyExecutor;
4
+ export declare const credentialValidators: CredentialValidators;
@@ -0,0 +1,19 @@
1
+ import { defineApiKeyProviderExecutors, defineProviderProxy } from "../provider-runtime.js";
2
+ import { framejetActionHandlers, framejetApiBaseUrl, framejetCaptureTimeoutMs, readFramejetError, validateFramejetApiKey, } from "./runtime.js";
3
+ const service = "framejet";
4
+ export const executors = defineApiKeyProviderExecutors(service, framejetActionHandlers, {
5
+ skipDnsValidation: true,
6
+ });
7
+ export const proxy = defineProviderProxy({
8
+ service,
9
+ baseUrl: framejetApiBaseUrl,
10
+ auth: { type: "api_key_header", name: "X-Api-Key" },
11
+ readError: readFramejetError,
12
+ skipDnsValidation: true,
13
+ timeoutMs: framejetCaptureTimeoutMs,
14
+ });
15
+ export const credentialValidators = {
16
+ apiKey(input, { fetcher, signal }) {
17
+ return validateFramejetApiKey(input.apiKey, fetcher, signal);
18
+ },
19
+ };
@@ -0,0 +1,16 @@
1
+ import type { CredentialValidationResult } from "../../core/types.ts";
2
+ import type { ApiKeyProviderContext, ProviderActionHandlers, ProviderFetch } from "../provider-runtime.ts";
3
+ import { ProviderRequestError } from "../provider-runtime.ts";
4
+ export declare const framejetApiBaseUrl = "https://framejet.dev";
5
+ export declare const framejetCaptureTimeoutMs = 90000;
6
+ type FramejetActionHandler = (input: Record<string, unknown>, context: ApiKeyProviderContext) => Promise<unknown>;
7
+ export declare const framejetActionHandlers: ProviderActionHandlers<"framejet", FramejetActionHandler>;
8
+ /**
9
+ * Check a key against GET /v1/me, which does not spend a screenshot.
10
+ */
11
+ export declare function validateFramejetApiKey(apiKey: string, fetcher: ProviderFetch, signal?: AbortSignal): Promise<CredentialValidationResult>;
12
+ /**
13
+ * Map a failed Framejet response for both actions and the proxy, so a spent quota reads the same on either path.
14
+ */
15
+ export declare function readFramejetError(response: Response): Promise<ProviderRequestError>;
16
+ export {};
@@ -0,0 +1,148 @@
1
+ import { createHmac } from "node:crypto";
2
+ import { optionalIntegerOrNull, optionalRecord, optionalString } from "../../core/cast.js";
3
+ import { readBoundedResponseBytes } from "../../core/request.js";
4
+ import { providerInputError, providerResponseError, providerUserAgent, readProviderErrorTextBody, ProviderRequestError, requiredInputString, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
5
+ export const framejetApiBaseUrl = "https://framejet.dev";
6
+ // A capture may wait for slow pages, page steps and goal mode, so it gets more than the default budget.
7
+ // The proxy shares this budget, since GET /v1/take is the capture endpoint on both paths.
8
+ export const framejetCaptureTimeoutMs = 90_000;
9
+ const framejetCaptureParams = ["format", "full_page", "width", "height", "dpr", "clean", "delay", "actions"];
10
+ // Framejet caps each value at 200 characters, which the schema enforces, and the joined list at 1000.
11
+ const framejetMaxValuesLength = 1000;
12
+ export const framejetActionHandlers = {
13
+ take_screenshot(input, context) {
14
+ return takeFramejetScreenshot(input, context);
15
+ },
16
+ create_signed_url(input, context) {
17
+ return createFramejetSignedUrl(input, context);
18
+ },
19
+ };
20
+ /**
21
+ * Check a key against GET /v1/me, which does not spend a screenshot.
22
+ */
23
+ export async function validateFramejetApiKey(apiKey, fetcher, signal) {
24
+ const key = requiredInputString(apiKey, "apiKey");
25
+ await runProviderRequest({ signal, label: "Framejet" }, async (requestSignal) => {
26
+ const response = await fetcher(new URL("/v1/me", framejetApiBaseUrl), {
27
+ headers: framejetHeaders(key),
28
+ signal: requestSignal,
29
+ });
30
+ if (!response.ok) {
31
+ const error = await readFramejetError(response);
32
+ // A rejected key is a field error on the connect form, not a reconnect prompt.
33
+ throw response.status === 401 || response.status === 403 ? providerInputError(error.message) : error;
34
+ }
35
+ });
36
+ return {
37
+ profile: {
38
+ accountId: "api_key",
39
+ displayName: "Framejet API Key",
40
+ },
41
+ grantedScopes: [],
42
+ metadata: {
43
+ apiBaseUrl: framejetApiBaseUrl,
44
+ validationEndpoint: "/v1/me",
45
+ },
46
+ };
47
+ }
48
+ async function takeFramejetScreenshot(input, context) {
49
+ const transitFiles = context.transitFiles;
50
+ if (!transitFiles) {
51
+ throw new ProviderRequestError(500, "take_screenshot requires transit file storage");
52
+ }
53
+ const query = buildFramejetQuery(input, ["goal", "values", "cache"]);
54
+ if ([...(query.get("values") ?? "")].length > framejetMaxValuesLength) {
55
+ throw providerInputError(`values must total at most ${framejetMaxValuesLength} characters, including the | separators`);
56
+ }
57
+ const url = new URL("/v1/take", framejetApiBaseUrl);
58
+ url.search = query.toString();
59
+ return runProviderRequest({ signal: context.signal, label: "Framejet", timeoutMs: framejetCaptureTimeoutMs }, async (signal) => {
60
+ const response = await context.fetcher(url, { headers: framejetHeaders(context.apiKey), signal });
61
+ if (!response.ok) {
62
+ throw await readFramejetError(response);
63
+ }
64
+ const bytes = await readBoundedResponseBytes(response, {
65
+ maxBytes: transitFiles.maxBytes,
66
+ fieldName: "Framejet screenshot",
67
+ createError: (message) => new ProviderRequestError(413, message),
68
+ });
69
+ const mimeType = response.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
70
+ const name = mimeType === "image/jpeg" ? "framejet-screenshot.jpg" : "framejet-screenshot.png";
71
+ const upload = await transitFiles.create(new File([Uint8Array.from(bytes)], name, { type: mimeType }));
72
+ return {
73
+ file: {
74
+ fileId: upload.fileId,
75
+ downloadUrl: upload.downloadUrl,
76
+ sizeBytes: upload.sizeBytes,
77
+ name: upload.name,
78
+ mimeType: upload.mimeType,
79
+ },
80
+ cache: response.headers.get("x-framejet-cache") === "HIT" ? "HIT" : "MISS",
81
+ remaining: optionalIntegerOrNull(response.headers.get("x-framejet-remaining")),
82
+ };
83
+ });
84
+ }
85
+ async function createFramejetSignedUrl(input, context) {
86
+ const signingKey = await runProviderRequest({ signal: context.signal, label: "Framejet" }, async (signal) => {
87
+ const response = await context.fetcher(new URL("/v1/signing-key", framejetApiBaseUrl), {
88
+ headers: framejetHeaders(context.apiKey),
89
+ signal,
90
+ });
91
+ if (!response.ok) {
92
+ throw await readFramejetError(response);
93
+ }
94
+ return requiredResponseRecord(await response.json(), "Framejet signing key response");
95
+ });
96
+ const keyId = optionalString(signingKey.key_id);
97
+ const secret = optionalString(signingKey.signing_secret);
98
+ if (!keyId || !secret) {
99
+ throw providerResponseError("Framejet signing key response is missing key_id or signing_secret");
100
+ }
101
+ const query = buildFramejetQuery(input, []);
102
+ query.set("key_id", keyId);
103
+ if (typeof input.expires_in === "number") {
104
+ query.set("expires", String(Math.floor(Date.now() / 1000) + input.expires_in));
105
+ }
106
+ // Framejet verifies the exact bytes before "&sig=", so sign the query string exactly as it is sent.
107
+ const signed = query.toString();
108
+ const sig = createHmac("sha256", secret).update(signed).digest("hex");
109
+ return { signed_url: `${framejetApiBaseUrl}/v1/take?${signed}&sig=${sig}` };
110
+ }
111
+ function buildFramejetQuery(input, extraParams) {
112
+ const query = new URLSearchParams({ url: requiredInputString(input.url, "url") });
113
+ for (const name of [...framejetCaptureParams, ...extraParams]) {
114
+ const raw = input[name];
115
+ const value = Array.isArray(raw) ? raw.join("|") : raw;
116
+ if (value === undefined || value === null || value === "") {
117
+ continue;
118
+ }
119
+ query.set(name, String(value));
120
+ }
121
+ return query;
122
+ }
123
+ function framejetHeaders(apiKey) {
124
+ return {
125
+ "user-agent": providerUserAgent,
126
+ "x-api-key": apiKey,
127
+ };
128
+ }
129
+ /**
130
+ * Map a failed Framejet response for both actions and the proxy, so a spent quota reads the same on either path.
131
+ */
132
+ export async function readFramejetError(response) {
133
+ const text = (await readProviderErrorTextBody(response, "Framejet error response")).trim();
134
+ let payload;
135
+ try {
136
+ payload = optionalRecord(JSON.parse(text));
137
+ }
138
+ catch {
139
+ payload = undefined;
140
+ }
141
+ const message = optionalString(payload?.error) ?? (text || `Framejet request failed with status ${response.status}`);
142
+ const code = optionalString(payload?.code);
143
+ const fullMessage = code ? `${message} (${code})` : message;
144
+ // 402 is Framejet's monthly quota; every other status maps through the shared convention.
145
+ return response.status === 402
146
+ ? new ProviderRequestError(402, fullMessage, payload, "insufficient_credit")
147
+ : new ProviderRequestError(response.status, fullMessage, payload);
148
+ }
@@ -0,0 +1,2 @@
1
+ import type { ActionDefinition } from "../../core/types.ts";
2
+ export declare const futunnActions: ActionDefinition[];