@oomol-lab/open-connector 1.6.5 → 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 (243) hide show
  1. package/assets/open-connector/catalog/apps/aisa.json +6138 -312
  2. package/assets/open-connector/catalog/apps/baizhi_mcp.json +135 -0
  3. package/assets/open-connector/catalog/apps/discordbot.json +2888 -248
  4. package/assets/open-connector/catalog/apps/drawio_mcp.json +192 -0
  5. package/assets/open-connector/catalog/apps/firecrawl.json +176 -32
  6. package/assets/open-connector/catalog/apps/framejet.json +273 -0
  7. package/assets/open-connector/catalog/apps/futunn.json +2967 -0
  8. package/assets/open-connector/catalog/apps/gmail.json +225 -0
  9. package/assets/open-connector/catalog/apps/google_analytics.json +4 -1
  10. package/assets/open-connector/catalog/apps/google_bigquery.json +4 -1
  11. package/assets/open-connector/catalog/apps/google_search_console.json +4 -1
  12. package/assets/open-connector/catalog/apps/googlecalendar.json +4 -1
  13. package/assets/open-connector/catalog/apps/googlechat.json +694 -7
  14. package/assets/open-connector/catalog/apps/googledocs.json +4 -1
  15. package/assets/open-connector/catalog/apps/googlesheets.json +4 -1
  16. package/assets/open-connector/catalog/apps/granola.json +23 -0
  17. package/assets/open-connector/catalog/apps/keepa.json +3534 -987
  18. package/assets/open-connector/catalog/apps/linear.json +17 -0
  19. package/assets/open-connector/catalog/apps/linear_mcp.json +510 -0
  20. package/assets/open-connector/catalog/apps/moneyforward_mcp.json +1432 -0
  21. package/assets/open-connector/catalog/apps/notion.json +446 -1
  22. package/assets/open-connector/catalog/apps/notion_mcp.json +490 -0
  23. package/assets/open-connector/catalog/apps/odoo.json +619 -0
  24. package/assets/open-connector/catalog/apps/outlook.json +30 -8
  25. package/assets/open-connector/catalog/apps/outlook_calendar.json +23 -2
  26. package/assets/open-connector/catalog/apps/postiz.json +334 -0
  27. package/assets/open-connector/catalog/apps/proworkflow.json +529 -0
  28. package/assets/open-connector/catalog/apps/sellersprite_mcp.json +2 -1
  29. package/assets/open-connector/catalog/apps/slack.json +223 -0
  30. package/assets/open-connector/catalog/apps/slackbot.json +214 -0
  31. package/assets/open-connector/catalog/apps/tencent_docs.json +3 -0
  32. package/assets/open-connector/catalog/apps/wisburg.json +1528 -0
  33. package/assets/open-connector/catalog/apps/youtube.json +4 -1
  34. package/assets/open-connector/catalog/apps/zerotier.json +3319 -0
  35. package/assets/open-connector/catalog/apps-index.json +1 -1
  36. package/package.json +1 -1
  37. package/src/core/cast.d.ts +10 -0
  38. package/src/core/cast.js +14 -0
  39. package/src/core/guarded-fetch.d.ts +6 -3
  40. package/src/core/guarded-fetch.js +6 -3
  41. package/src/core/provider-scenarios.js +67 -13
  42. package/src/core/request.js +4 -1
  43. package/src/core/types.d.ts +1 -1
  44. package/src/mcp.js +10 -7
  45. package/src/oauth/oauth-client-config-service.d.ts +16 -1
  46. package/src/oauth/oauth-client-config-service.js +59 -3
  47. package/src/oauth/oauth-flow-service.d.ts +6 -0
  48. package/src/oauth/oauth-flow-service.js +4 -2
  49. package/src/providers/17track/runtime.js +1 -1
  50. package/src/providers/action-contracts.generated.d.ts +17 -5
  51. package/src/providers/aifinmarket/runtime.js +1 -1
  52. package/src/providers/aisa/executors.js +734 -46
  53. package/src/providers/aliyun_ack/runtime.js +1 -1
  54. package/src/providers/aliyun_sls/runtime.js +1 -1
  55. package/src/providers/app_store_server/runtime-helpers.js +1 -1
  56. package/src/providers/baizhi_mcp/actions.d.ts +2 -0
  57. package/src/providers/baizhi_mcp/actions.js +40 -0
  58. package/src/providers/baizhi_mcp/executors.d.ts +3 -0
  59. package/src/providers/baizhi_mcp/executors.js +102 -0
  60. package/src/providers/bazhuayu/runtime.js +2 -2
  61. package/src/providers/bilibili/runtime.js +2 -4
  62. package/src/providers/bilibili/upload.js +9 -7
  63. package/src/providers/cloudflare_browser_rendering/executors.js +1 -1
  64. package/src/providers/cloudflare_dns/executors.js +1 -1
  65. package/src/providers/{cloudflare-current-user.js → cloudflare_dns/runtime-user.js} +2 -2
  66. package/src/providers/cloudflare_dns/runtime.d.ts +1 -1
  67. package/src/providers/cloudflare_dns/runtime.js +1 -1
  68. package/src/providers/cloudflare_r2/executors.js +1 -1
  69. package/src/providers/cloudflare_r2/runtime.d.ts +1 -1
  70. package/src/providers/cloudflare_r2/runtime.js +1 -1
  71. package/src/providers/cloudflare_worker/executors.js +1 -1
  72. package/src/providers/cloudflare_worker/runtime.d.ts +1 -1
  73. package/src/providers/cloudflare_worker/runtime.js +1 -1
  74. package/src/providers/dingtalk_mcp/runtime.js +1 -1
  75. package/src/providers/discordbot/executors.d.ts +0 -9
  76. package/src/providers/discordbot/executors.js +9 -101
  77. package/src/providers/discordbot/runtime-guilds.d.ts +3 -0
  78. package/src/providers/discordbot/runtime-guilds.js +464 -0
  79. package/src/providers/discordbot/runtime.d.ts +42 -0
  80. package/src/providers/discordbot/runtime.js +124 -0
  81. package/src/providers/drawio_mcp/executors.d.ts +3 -0
  82. package/src/providers/drawio_mcp/executors.js +19 -0
  83. package/src/providers/drawio_mcp/runtime.d.ts +8 -0
  84. package/src/providers/drawio_mcp/runtime.js +175 -0
  85. package/src/providers/fastmoss_mcp/executors.js +2 -2
  86. package/src/providers/finchina/executors.js +1 -1
  87. package/src/providers/framejet/executors.d.ts +4 -0
  88. package/src/providers/framejet/executors.js +19 -0
  89. package/src/providers/framejet/runtime.d.ts +16 -0
  90. package/src/providers/framejet/runtime.js +148 -0
  91. package/src/providers/futunn/actions.d.ts +2 -0
  92. package/src/providers/futunn/actions.js +152 -0
  93. package/src/providers/futunn/actions.research.d.ts +2 -0
  94. package/src/providers/futunn/actions.research.js +423 -0
  95. package/src/providers/futunn/actions.trading.d.ts +2 -0
  96. package/src/providers/futunn/actions.trading.js +204 -0
  97. package/src/providers/futunn/executors.d.ts +3 -0
  98. package/src/providers/futunn/executors.js +24 -0
  99. package/src/providers/futunn/runtime-research.d.ts +3 -0
  100. package/src/providers/futunn/runtime-research.js +133 -0
  101. package/src/providers/futunn/runtime-trading.d.ts +3 -0
  102. package/src/providers/futunn/runtime-trading.js +39 -0
  103. package/src/providers/futunn/runtime.d.ts +2 -0
  104. package/src/providers/futunn/runtime.js +50 -0
  105. package/src/providers/futunn/transport.d.ts +11 -0
  106. package/src/providers/futunn/transport.js +76 -0
  107. package/src/providers/gildata/executors.js +1 -1
  108. package/src/providers/gmail/executors.js +3 -3
  109. package/src/providers/gmail/message.d.ts +6 -8
  110. package/src/providers/gmail/message.js +4 -0
  111. package/src/providers/gmail/scopes.d.ts +32 -0
  112. package/src/providers/gmail/scopes.js +37 -0
  113. package/src/providers/google_analytics/executors.js +2 -2
  114. package/src/providers/google_analytics/scopes.js +6 -1
  115. package/src/providers/google_bigquery/executors.js +2 -2
  116. package/src/providers/google_bigquery/scopes.js +2 -0
  117. package/src/providers/google_search_console/executors.js +2 -2
  118. package/src/providers/google_search_console/scopes.js +2 -0
  119. package/src/providers/googleads/executors.js +1 -1
  120. package/src/providers/googleads/scopes.d.ts +1 -3
  121. package/src/providers/googleads/scopes.js +2 -4
  122. package/src/providers/googlecalendar/executors.js +1 -1
  123. package/src/providers/googlecalendar/runtime-shared.js +2 -2
  124. package/src/providers/googlecalendar/scopes.js +2 -0
  125. package/src/providers/googlechat/executors.d.ts +1 -3
  126. package/src/providers/googlechat/executors.js +219 -28
  127. package/src/providers/googlechat/members.d.ts +55 -0
  128. package/src/providers/googlechat/members.js +293 -0
  129. package/src/providers/googlechat/runtime.d.ts +16 -0
  130. package/src/providers/googlechat/runtime.js +25 -0
  131. package/src/providers/googlechat/scopes.d.ts +22 -3
  132. package/src/providers/googlechat/scopes.js +30 -7
  133. package/src/providers/googledocs/executors.js +1 -1
  134. package/src/providers/googledrive/executors.js +2 -2
  135. package/src/providers/{google-auth.d.ts → googledrive/runtime-auth.d.ts} +2 -2
  136. package/src/providers/{google-auth.js → googledrive/runtime-auth.js} +3 -3
  137. package/src/providers/googledrive/runtime-collaboration.js +1 -1
  138. package/src/providers/{google-runtime.d.ts → googledrive/runtime-request.d.ts} +1 -1
  139. package/src/providers/{google-runtime.js → googledrive/runtime-request.js} +2 -2
  140. package/src/providers/googleforms/executors.js +2 -2
  141. package/src/providers/googleforms/scopes.d.ts +0 -3
  142. package/src/providers/googleforms/scopes.js +2 -6
  143. package/src/providers/googlemeet/executors.js +2 -2
  144. package/src/providers/googlemeet/scopes.d.ts +0 -3
  145. package/src/providers/googlemeet/scopes.js +2 -6
  146. package/src/providers/googlesheets/executors.js +1 -1
  147. package/src/providers/googlesheets/scopes.js +2 -0
  148. package/src/providers/googleslides/executors.js +2 -2
  149. package/src/providers/googleslides/scopes.d.ts +0 -3
  150. package/src/providers/googleslides/scopes.js +2 -6
  151. package/src/providers/googletasks/executors.js +2 -2
  152. package/src/providers/googletasks/scopes.d.ts +0 -3
  153. package/src/providers/googletasks/scopes.js +2 -6
  154. package/src/providers/granola/actions.js +2 -0
  155. package/src/providers/granola/runtime-mcp.js +2 -0
  156. package/src/providers/granola/runtime.js +2 -2
  157. package/src/providers/indiegogo/executors.js +1 -1
  158. package/src/providers/investoday_mcp/executors.js +2 -2
  159. package/src/providers/iyunbiao/runtime.js +2 -2
  160. package/src/providers/keepa/actions.d.ts +1 -1
  161. package/src/providers/keepa/actions.js +371 -149
  162. package/src/providers/keepa/deal-attributes.d.ts +1 -0
  163. package/src/providers/keepa/deal-attributes.js +26 -0
  164. package/src/providers/keepa/runtime.js +212 -18
  165. package/src/providers/kingdee/executors.js +1 -1
  166. package/src/providers/kingdee/runtime.js +2 -2
  167. package/src/providers/kuaidi100/runtime.js +1 -1
  168. package/src/providers/linear/executors.js +12 -9
  169. package/src/providers/linear_mcp/actions.d.ts +47 -0
  170. package/src/providers/linear_mcp/actions.js +102 -0
  171. package/src/providers/linear_mcp/endpoints.d.ts +3 -0
  172. package/src/providers/linear_mcp/endpoints.js +3 -0
  173. package/src/providers/linear_mcp/executors.d.ts +3 -0
  174. package/src/providers/linear_mcp/executors.js +9 -0
  175. package/src/providers/linear_mcp/runtime-mcp.d.ts +15 -0
  176. package/src/providers/linear_mcp/runtime-mcp.js +238 -0
  177. package/src/providers/lingxing_mcp/runtime.js +1 -1
  178. package/src/providers/mcdonalds_cn_mcp/runtime.js +1 -1
  179. package/src/providers/metabase/mcp-runtime.js +1 -1
  180. package/src/providers/moneyforward_mcp/executors.d.ts +3 -0
  181. package/src/providers/moneyforward_mcp/executors.js +11 -0
  182. package/src/providers/moneyforward_mcp/manifest.d.ts +31 -0
  183. package/src/providers/moneyforward_mcp/manifest.js +204 -0
  184. package/src/providers/moneyforward_mcp/runtime.d.ts +18 -0
  185. package/src/providers/moneyforward_mcp/runtime.js +296 -0
  186. package/src/providers/notion/executors.js +39 -3
  187. package/src/providers/notion_mcp/endpoints.d.ts +3 -0
  188. package/src/providers/notion_mcp/endpoints.js +3 -0
  189. package/src/providers/notion_mcp/executors.d.ts +3 -0
  190. package/src/providers/notion_mcp/executors.js +176 -0
  191. package/src/providers/notion_mcp/mcp-response.d.ts +86 -0
  192. package/src/providers/notion_mcp/mcp-response.js +314 -0
  193. package/src/providers/odoo/executors.d.ts +3 -0
  194. package/src/providers/odoo/executors.js +32 -0
  195. package/src/providers/odoo/runtime.d.ts +16 -0
  196. package/src/providers/odoo/runtime.js +140 -0
  197. package/src/providers/outlook/executors.js +27 -6
  198. package/src/providers/outlook/microsoft-graph.js +17 -6
  199. package/src/providers/outlook_calendar/executors.js +24 -7
  200. package/src/providers/patsnap_mcp/executors.js +2 -2
  201. package/src/providers/postiz/executors.d.ts +4 -0
  202. package/src/providers/postiz/executors.js +83 -0
  203. package/src/providers/provider-runtime.d.ts +15 -0
  204. package/src/providers/provider-runtime.js +57 -4
  205. package/src/providers/proworkflow/executors.d.ts +4 -0
  206. package/src/providers/proworkflow/executors.js +135 -0
  207. package/src/providers/registry.generated.js +12 -0
  208. package/src/providers/scrape_creators/catalog.js +1 -1
  209. package/src/providers/scrape_creators/runtime.js +1 -1
  210. package/src/providers/sellersprite_mcp/executors.js +2 -2
  211. package/src/providers/sellersprite_mcp/official-actions.js +3 -2
  212. package/src/providers/sentry/runtime.js +6 -7
  213. package/src/providers/slack/actions.js +52 -3
  214. package/src/providers/slack/runtime.js +58 -21
  215. package/src/providers/slickdeals/runtime.js +1 -1
  216. package/src/providers/sorftime_mcp/executors.js +2 -2
  217. package/src/providers/tikhub/bounded-response.d.ts +1 -1
  218. package/src/providers/tikhub/bounded-response.js +8 -34
  219. package/src/providers/tikhub/endpoint-catalog.js +1 -1
  220. package/src/providers/tikhub/runtime.js +2 -2
  221. package/src/providers/tongdaxin/executors.js +79 -15
  222. package/src/providers/uspto/executors.js +1 -1
  223. package/src/providers/waffo/executors.js +1 -1
  224. package/src/providers/waffo/runtime.js +1 -1
  225. package/src/providers/wecom_mcp/runtime.js +1 -1
  226. package/src/providers/wisburg/executors.d.ts +4 -0
  227. package/src/providers/wisburg/executors.js +13 -0
  228. package/src/providers/wisburg/runtime.d.ts +12 -0
  229. package/src/providers/wisburg/runtime.js +133 -0
  230. package/src/providers/xydc_mcp/executors.js +2 -2
  231. package/src/providers/youtube/executors.js +1 -1
  232. package/src/providers/youtube/runtime.js +1 -1
  233. package/src/providers/zerotier/executors.d.ts +9 -0
  234. package/src/providers/zerotier/executors.js +629 -0
  235. package/src/providers/zerotier/runtime.d.ts +54 -0
  236. package/src/providers/zerotier/runtime.js +207 -0
  237. package/src/server/api/openapi.js +12 -2
  238. package/src/server/api/runtime-api.d.ts +8 -1
  239. package/src/server/api/runtime-api.js +19 -1
  240. package/src/server/connect-server.js +10 -2
  241. package/src/providers/sentry/scopes.d.ts +0 -1
  242. package/src/providers/sentry/scopes.js +0 -7
  243. /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." }),
@@ -1,6 +1,6 @@
1
- import { compactObject, optionalBoolean, optionalInteger, optionalNumber, 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, providerInputError, providerResponseError, providerUserAgent, requiredInputString, requiredResponseRecord, runProviderRequest, } 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;
@@ -196,7 +196,8 @@ async function slackGetChannelMessages(input, context) {
196
196
  url.searchParams.set("cursor", String(input.cursor));
197
197
  }
198
198
  applySlackHistoryWindow(url, input);
199
- return readSlackMessagePage(await slackGetJson(url, context), "conversations.history");
199
+ const includeRaw = applySlackIncludeRaw(url, input);
200
+ return readSlackMessagePage(await slackGetJson(url, context), "conversations.history", includeRaw);
200
201
  }
201
202
  async function slackConversationsMembers(input, context) {
202
203
  const url = slackApiUrl("conversations.members");
@@ -214,6 +215,7 @@ async function slackConversationsMembers(input, context) {
214
215
  };
215
216
  }
216
217
  async function slackSearchMessages(input, context) {
218
+ const includeRaw = input.includeRaw === true;
217
219
  const query = requiredString(input.query, "query", (message) => new ProviderRequestError(400, message));
218
220
  if (input.page != null && input.cursor != null) {
219
221
  throw new ProviderRequestError(400, "page and cursor cannot be used together");
@@ -244,7 +246,7 @@ async function slackSearchMessages(input, context) {
244
246
  const payload = await slackGetJson(url, context);
245
247
  return {
246
248
  query: optionalString(payload.query) ?? query,
247
- matches: (payload.messages?.matches ?? []).map((match) => normalizeSearchMessageMatch(match)),
249
+ matches: (payload.messages?.matches ?? []).map((match) => normalizeSearchMessageMatch(match, includeRaw)),
248
250
  total: typeof payload.messages?.total === "number" ? payload.messages.total : 0,
249
251
  pagination: payload.messages?.pagination ?? {},
250
252
  paging: payload.messages?.paging ?? {},
@@ -318,7 +320,8 @@ async function slackGetThread(input, context) {
318
320
  url.searchParams.set("cursor", String(input.cursor));
319
321
  }
320
322
  applySlackHistoryWindow(url, input);
321
- return readSlackMessagePage(await slackGetJson(url, context), "conversations.replies");
323
+ const includeRaw = applySlackIncludeRaw(url, input);
324
+ return readSlackMessagePage(await slackGetJson(url, context), "conversations.replies", includeRaw);
322
325
  }
323
326
  async function slackListConversations(input, context) {
324
327
  const url = slackApiUrl("conversations.list");
@@ -651,7 +654,7 @@ async function slackFormRequestJson(context, method, body) {
651
654
  async function readSlackResponseJson(response) {
652
655
  const payload = (optionalRecord(await response.json().catch(() => undefined)) ?? {});
653
656
  if (!response.ok) {
654
- throw slackHttpError(response.status, payload, response.headers.get("retry-after"));
657
+ throw slackHttpError(response, payload);
655
658
  }
656
659
  assertSlackPayload(payload);
657
660
  // Preserve HTTP/Slack failures (including Retry-After) above, but require
@@ -844,6 +847,15 @@ function normalizeConversation(conversation) {
844
847
  purpose: typeof purpose?.value === "string" ? purpose.value : null,
845
848
  userId: optionalString(conversation.user),
846
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),
847
859
  });
848
860
  }
849
861
  /**
@@ -863,13 +875,27 @@ function applySlackHistoryWindow(url, input) {
863
875
  url.searchParams.set("inclusive", String(input.inclusive));
864
876
  }
865
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
+ }
866
892
  /**
867
893
  * Read one `conversations.history` / `conversations.replies` page. A page whose
868
894
  * list is missing or whose rows are not objects is rejected rather than
869
895
  * flattened into an empty result, so a broken upstream response cannot pass
870
896
  * for the documented end of a walk.
871
897
  */
872
- function readSlackMessagePage(payload, method) {
898
+ function readSlackMessagePage(payload, method, includeRaw = false) {
873
899
  if (payload.has_more !== undefined && typeof payload.has_more !== "boolean") {
874
900
  throw slackResponseError(`${method} has_more`);
875
901
  }
@@ -879,7 +905,7 @@ function readSlackMessagePage(payload, method) {
879
905
  if (!record) {
880
906
  throw slackResponseError(`${method} message`);
881
907
  }
882
- return normalizeSlackMessage(record);
908
+ return normalizeSlackMessage(record, includeRaw);
883
909
  }),
884
910
  hasMore: payload.has_more ?? false,
885
911
  nextCursor: readSlackNextCursor(payload, method),
@@ -927,8 +953,13 @@ function requireSlackId(value, label) {
927
953
  * missing value stays distinguishable from an empty one. `userId` is the one
928
954
  * exception kept for compatibility: it stays `""` on a message with no
929
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.
930
961
  */
931
- function normalizeSlackMessage(message) {
962
+ function normalizeSlackMessage(message, includeRaw = false) {
932
963
  const edited = optionalRecord(message.edited) ?? {};
933
964
  const reactions = Array.isArray(message.reactions) ? message.reactions : undefined;
934
965
  return compactObject({
@@ -943,13 +974,16 @@ function normalizeSlackMessage(message) {
943
974
  clientMsgId: optionalString(message.client_msg_id),
944
975
  text: typeof message.text === "string" ? message.text : "",
945
976
  editedTs: optionalString(edited.ts),
977
+ editedUserId: optionalString(edited.user),
946
978
  threadTs: optionalString(message.thread_ts),
947
979
  parentUserId: optionalString(message.parent_user_id),
948
980
  replyCount: optionalInteger(message.reply_count),
949
981
  replyUsersCount: optionalInteger(message.reply_users_count),
982
+ replyUserIds: optionalStringArray(message.reply_users),
950
983
  latestReply: optionalString(message.latest_reply),
951
984
  isLocked: optionalBoolean(message.is_locked),
952
985
  reactions: reactions?.map((reaction) => normalizeSlackReaction(optionalRecord(reaction) ?? {})),
986
+ raw: includeRaw ? message : undefined,
953
987
  });
954
988
  }
955
989
  function normalizeSlackReaction(reaction) {
@@ -959,7 +993,7 @@ function normalizeSlackReaction(reaction) {
959
993
  userIds: Array.isArray(reaction.users) ? reaction.users.map((user) => String(user)) : undefined,
960
994
  });
961
995
  }
962
- function normalizeSearchMessageMatch(match) {
996
+ function normalizeSearchMessageMatch(match, includeRaw = false) {
963
997
  const channel = optionalRecord(match.channel) ?? {};
964
998
  return compactObject({
965
999
  matchId: optionalString(match.iid),
@@ -972,6 +1006,7 @@ function normalizeSearchMessageMatch(match) {
972
1006
  permalink: optionalString(match.permalink),
973
1007
  teamId: optionalString(match.team),
974
1008
  type: optionalString(match.type),
1009
+ raw: includeRaw ? match : undefined,
975
1010
  });
976
1011
  }
977
1012
  function normalizeUser(user) {
@@ -986,6 +1021,14 @@ function normalizeUser(user) {
986
1021
  isAdmin: typeof user.is_admin === "boolean" ? user.is_admin : null,
987
1022
  isOwner: typeof user.is_owner === "boolean" ? user.is_owner : null,
988
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),
989
1032
  });
990
1033
  }
991
1034
  function normalizeFile(file) {
@@ -1037,17 +1080,11 @@ function formatSlackPayloadError(payload) {
1037
1080
  }
1038
1081
  return `${error}: ${details.join("; ")}`;
1039
1082
  }
1040
- function slackHttpError(status, payload, retryAfter) {
1041
- const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${status}`;
1042
- if (status === 429 && retryAfter !== null && /^\d+$/.test(retryAfter)) {
1043
- const retryAfterSeconds = Number(retryAfter);
1044
- if (Number.isSafeInteger(retryAfterSeconds)) {
1045
- // The action envelope carries provider details; retain pacing so callers
1046
- // can resume the same page without guessing when this workspace may retry.
1047
- return new ProviderRequestError(status, message, { ...payload, retryAfterSeconds });
1048
- }
1049
- }
1050
- 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));
1051
1088
  }
1052
1089
  function slackResponseError(message) {
1053
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);
@@ -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;
@@ -1,44 +1,18 @@
1
+ import { readBoundedResponseBytes } from "../../core/request.js";
1
2
  import { TikHubRequestError } from "./errors.js";
2
3
  export class BoundedResponseTooLargeError extends TikHubRequestError {
3
4
  }
4
5
  export async function readBoundedResponseText(response, input) {
5
- const declaredLength = response.headers.get("content-length");
6
- if (declaredLength !== null) {
7
- const parsedLength = Number(declaredLength);
8
- if (Number.isFinite(parsedLength) && parsedLength > input.maxBytes) {
9
- await cancelResponseBody(response);
10
- throw responseTooLarge(input);
11
- }
12
- }
13
- if (!response.body) {
14
- return "";
15
- }
16
- const reader = response.body.getReader();
17
- const chunks = [];
18
- let totalBytes = 0;
19
- while (true) {
20
- const result = await reader.read();
21
- if (result.done) {
22
- break;
23
- }
24
- totalBytes += result.value.byteLength;
25
- if (totalBytes > input.maxBytes) {
26
- await reader.cancel();
27
- throw responseTooLarge(input);
28
- }
29
- chunks.push(result.value);
30
- }
31
- const bytes = new Uint8Array(totalBytes);
32
- let offset = 0;
33
- for (const chunk of chunks) {
34
- bytes.set(chunk, offset);
35
- offset += chunk.byteLength;
36
- }
6
+ const bytes = await readBoundedResponseBytes(response, {
7
+ maxBytes: input.maxBytes,
8
+ fieldName: input.label,
9
+ createError: () => responseTooLarge(input),
10
+ });
37
11
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
38
12
  }
39
- export async function cancelResponseBody(response) {
13
+ export function cancelResponseBody(response) {
40
14
  try {
41
- await response.body?.cancel();
15
+ void response.body?.cancel().catch(() => undefined);
42
16
  }
43
17
  catch {
44
18
  return;
@@ -105,7 +105,7 @@ async function fetchOpenApi(fetcher) {
105
105
  const response = await fetcher(tikhubOpenApiUrl, {
106
106
  method: "GET",
107
107
  headers: { accept: "application/json" },
108
- redirect: "error",
108
+ redirect: "manual",
109
109
  signal: timeout.signal,
110
110
  });
111
111
  if (!response.ok) {
@@ -166,7 +166,7 @@ async function invokeTikHubEndpoint(input, fetcher, apiKey) {
166
166
  "user-agent": providerUserAgent,
167
167
  },
168
168
  ...(serializedBody === undefined ? {} : { body: serializedBody }),
169
- redirect: "error",
169
+ redirect: "manual",
170
170
  signal: timeout.signal,
171
171
  });
172
172
  let responseBody;
@@ -454,7 +454,7 @@ async function requestTikHubUserJson(input) {
454
454
  authorization: `Bearer ${input.apiKey}`,
455
455
  "user-agent": providerUserAgent,
456
456
  },
457
- redirect: "error",
457
+ redirect: "manual",
458
458
  signal: timeout.signal,
459
459
  });
460
460
  const payload = await readTikHubResponseBody(response, tikhubDynamicResponseMaxBytes);
@@ -1,6 +1,7 @@
1
1
  import { ProtocolError, SdkHttpError, UnauthorizedError } from "@modelcontextprotocol/client";
2
2
  import { createHash } from "node:crypto";
3
- import { optionalRecord } from "../../core/cast.js";
3
+ import { optionalNumber, optionalRecord, optionalString } from "../../core/cast.js";
4
+ import { readBoundedResponseBytes } from "../../core/request.js";
4
5
  import { withMcpClient } from "../mcp-client.js";
5
6
  import { defineApiKeyProviderExecutors, providerUserAgent, ProviderRequestError, requiredInputString, } from "../provider-runtime.js";
6
7
  import { tongdaxinActions, tongdaxinReadOnlyToolNames } from "./actions.js";
@@ -9,10 +10,15 @@ import { isTongdaxinNamedActionName, resolveTongdaxinNamedToolCall } from "./nam
9
10
  const service = "tongdaxin";
10
11
  const endpoint = new URL("https://txmcp.tdx.com.cn:3001/txmcp");
11
12
  const requestTimeoutMs = 60_000;
13
+ const errorResponseMaxBytes = 64 * 1024;
14
+ const insufficientPointsToolError = "MCP error -32603: Your usage quota has been reached";
15
+ const insufficientPointsDescription = "TDX MCP quota is below 10. Please recharge before continuing.";
16
+ const rechargeOrigin = "https://vip.tdx.com.cn";
17
+ const rechargePath = "/site/app/pc-mall/main.html";
12
18
  const supportedToolNames = new Set(tongdaxinReadOnlyToolNames);
13
19
  const handlers = {
14
20
  async list_tools(_input, context) {
15
- return { tools: await discoverSupportedTools(context, "execute") };
21
+ return { tools: await discoverSupportedTools(context) };
16
22
  },
17
23
  async call_tool(input, context) {
18
24
  const toolName = requiredInputString(input.toolName, "toolName");
@@ -20,7 +26,8 @@ const handlers = {
20
26
  if (!argumentsValue)
21
27
  throw new ProviderRequestError(400, "arguments must be a JSON object");
22
28
  assertToolArgumentsSize(argumentsValue);
23
- const result = await withTongdaxinClient(context, "execute", false, (client) => client.callTool({ name: toolName, arguments: argumentsValue }, { timeout: requestTimeoutMs, signal: context.signal }));
29
+ const result = await withTongdaxinClient(context, false, (client) => client.callTool({ name: toolName, arguments: argumentsValue }, { timeout: requestTimeoutMs, signal: context.signal }));
30
+ assertSufficientTongdaxinCredit(result);
24
31
  if (!("toolResult" in result) && result.isError) {
25
32
  throw new ProviderRequestError(502, `Tongdaxin MCP tool ${toolName} returned an error`, result);
26
33
  }
@@ -37,7 +44,7 @@ for (const actionName of Object.keys(resolveNamedHandlers())) {
37
44
  throw new ProviderRequestError(400, `Unknown action: ${actionName}`);
38
45
  const toolCall = resolveTongdaxinNamedToolCall(actionName, input);
39
46
  assertToolArgumentsSize(toolCall.arguments);
40
- const tools = await discoverSupportedTools(context, "execute");
47
+ const tools = await discoverSupportedTools(context);
41
48
  const tool = tools.find((candidate) => candidate.name === toolCall.toolName);
42
49
  if (!tool && toolCall.toolName !== "wenda_macro_query") {
43
50
  throw new ProviderRequestError(409, `Tongdaxin action ${actionName} is unavailable because this connection does not expose ${toolCall.toolName}`);
@@ -48,7 +55,8 @@ for (const actionName of Object.keys(resolveNamedHandlers())) {
48
55
  tool.annotations.destructiveHint === true)) {
49
56
  throw new ProviderRequestError(403, `Tongdaxin MCP does not currently affirm ${tool.name} as a non-destructive read-only tool`);
50
57
  }
51
- const result = await withTongdaxinClient(context, "execute", true, (client) => client.callTool({ name: toolCall.toolName, arguments: toolCall.arguments }, { timeout: requestTimeoutMs, signal: context.signal }));
58
+ const result = await withTongdaxinClient(context, true, (client) => client.callTool({ name: toolCall.toolName, arguments: toolCall.arguments }, { timeout: requestTimeoutMs, signal: context.signal }));
59
+ assertSufficientTongdaxinCredit(result);
52
60
  if (!("toolResult" in result) && result.isError) {
53
61
  throw new ProviderRequestError(502, `Tongdaxin MCP tool ${toolCall.toolName} returned an error`, result);
54
62
  }
@@ -62,7 +70,7 @@ export const executors = defineApiKeyProviderExecutors(service, handlers, {
62
70
  export const credentialValidators = {
63
71
  async apiKey(input, { fetcher, signal }) {
64
72
  const context = { apiKey: input.apiKey, fetcher, signal };
65
- const tools = await discoverSupportedTools(context, "validate");
73
+ const tools = await discoverSupportedTools(context);
66
74
  if (tools.length === 0) {
67
75
  throw new ProviderRequestError(400, "Tongdaxin MCP did not expose any supported read-only tools");
68
76
  }
@@ -77,8 +85,8 @@ export const credentialValidators = {
77
85
  };
78
86
  },
79
87
  };
80
- async function discoverSupportedTools(context, phase) {
81
- const result = await withTongdaxinClient(context, phase, true, (client) => client.listTools({}, { timeout: requestTimeoutMs, signal: context.signal }));
88
+ async function discoverSupportedTools(context) {
89
+ const result = await withTongdaxinClient(context, true, (client) => client.listTools({}, { timeout: requestTimeoutMs, signal: context.signal }));
82
90
  return result.tools.map((tool) => ({
83
91
  name: tool.name,
84
92
  description: tool.description,
@@ -86,30 +94,30 @@ async function discoverSupportedTools(context, phase) {
86
94
  inputSchema: tool.inputSchema,
87
95
  }));
88
96
  }
89
- async function withTongdaxinClient(context, phase, retryOnSessionNotFound, run) {
97
+ async function withTongdaxinClient(context, retryOnSessionNotFound, run) {
90
98
  return withMcpClient({
91
99
  endpoint,
92
100
  transport: "streamable_http",
93
- fetcher: context.fetcher,
101
+ fetcher: createTongdaxinFetcher(context.fetcher),
94
102
  headers: {
95
103
  authorization: `Bearer ${context.apiKey}`,
96
104
  "user-agent": providerUserAgent,
97
105
  },
98
- redirect: "error",
106
+ redirect: "manual",
99
107
  signal: context.signal,
100
108
  retryOnSessionNotFound,
101
- mapError: (error) => mapTongdaxinError(error, phase),
109
+ mapError: mapTongdaxinError,
102
110
  }, run);
103
111
  }
104
- function mapTongdaxinError(error, phase) {
112
+ function mapTongdaxinError(error) {
105
113
  if (error instanceof ProviderRequestError)
106
114
  return error;
107
115
  if (error instanceof UnauthorizedError) {
108
- return new ProviderRequestError(phase === "validate" ? 400 : 401, "Tongdaxin API Key is invalid or expired");
116
+ return new ProviderRequestError(401, "Tongdaxin MCP denied this request");
109
117
  }
110
118
  if (error instanceof SdkHttpError) {
111
119
  if (error.status === 401) {
112
- return new ProviderRequestError(phase === "validate" ? 400 : 401, "Tongdaxin API Key is invalid or expired");
120
+ return new ProviderRequestError(401, "Tongdaxin MCP denied this request");
113
121
  }
114
122
  const status = 400 <= error.status && error.status < 500 ? error.status : 502;
115
123
  return new ProviderRequestError(status, `Tongdaxin MCP request failed: ${error.message}`, error);
@@ -119,6 +127,62 @@ function mapTongdaxinError(error, phase) {
119
127
  }
120
128
  return new ProviderRequestError(502, error instanceof Error ? `Tongdaxin MCP request failed: ${error.message}` : "Tongdaxin MCP request failed", error);
121
129
  }
130
+ function createTongdaxinFetcher(fetcher) {
131
+ return (async (...arguments_) => {
132
+ const response = await fetcher(...arguments_);
133
+ const details = await readInsufficientPoints(response);
134
+ if (details) {
135
+ void response.body?.cancel().catch(() => undefined);
136
+ throw insufficientCreditError(details);
137
+ }
138
+ return response;
139
+ });
140
+ }
141
+ async function readInsufficientPoints(response) {
142
+ if (response.status !== 401)
143
+ return undefined;
144
+ try {
145
+ const bytes = await readBoundedResponseBytes(response.clone(), {
146
+ maxBytes: errorResponseMaxBytes,
147
+ fieldName: "Tongdaxin MCP error response",
148
+ createError: (message) => new Error(message),
149
+ });
150
+ const payload = optionalRecord(JSON.parse(new TextDecoder().decode(bytes)));
151
+ const remainingCredit = optionalNumber(payload?.leftValue);
152
+ const rechargeUrl = optionalString(payload?.recharge_url);
153
+ if (payload?.error !== "unauthorized" ||
154
+ payload?.error_description !== insufficientPointsDescription ||
155
+ remainingCredit === undefined ||
156
+ !isOfficialRechargeUrl(rechargeUrl)) {
157
+ return undefined;
158
+ }
159
+ return { remainingCredit, rechargeUrl };
160
+ }
161
+ catch {
162
+ return undefined;
163
+ }
164
+ }
165
+ function isOfficialRechargeUrl(value) {
166
+ if (!value)
167
+ return false;
168
+ try {
169
+ const url = new URL(value);
170
+ return url.origin === rechargeOrigin && url.pathname === rechargePath;
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ function assertSufficientTongdaxinCredit(result) {
177
+ if (!("content" in result) || result.isError !== true)
178
+ return;
179
+ const exhausted = result.content.some((content) => content.type === "text" && content.text === insufficientPointsToolError);
180
+ if (exhausted)
181
+ throw insufficientCreditError();
182
+ }
183
+ function insufficientCreditError(details) {
184
+ return new ProviderRequestError(402, "Tongdaxin AI points are insufficient. Recharge the account before retrying.", details ?? { rechargeUrl: `${rechargeOrigin}${rechargePath}#/page_product_ai_jfb` }, "insufficient_credit");
185
+ }
122
186
  function resolveNamedHandlers() {
123
187
  return Object.fromEntries(tongdaxinActions
124
188
  .filter((action) => action.name !== "list_tools" && action.name !== "call_tool")
@@ -2,7 +2,7 @@ import { defineApiKeyProviderExecutors, defineProviderProxy, ProviderRequestErro
2
2
  async function get(path, accept, apiKey, fetcher, signal) {
3
3
  const response = await fetcher(new URL(path, "https://tsdrapi.uspto.gov"), {
4
4
  headers: { accept, "USPTO-API-KEY": apiKey, "user-agent": providerUserAgent },
5
- redirect: "error",
5
+ redirect: "manual",
6
6
  signal,
7
7
  });
8
8
  const text = await response.text();
@@ -37,7 +37,7 @@ export const proxy = async (input, context) => {
37
37
  method: input.method,
38
38
  headers,
39
39
  body: input.method === "GET" || input.body == null ? undefined : signingBody,
40
- redirect: "error",
40
+ redirect: "manual",
41
41
  signal: context.signal,
42
42
  });
43
43
  if (!response.ok) {
@@ -281,7 +281,7 @@ export async function requestWaffoJson(input) {
281
281
  method: "POST",
282
282
  headers,
283
283
  body,
284
- redirect: "error",
284
+ redirect: "manual",
285
285
  signal: timeout.signal,
286
286
  });
287
287
  const payload = await readProviderJsonBody(response, {
@@ -173,7 +173,7 @@ async function withWeComMcpClient(context, run) {
173
173
  transport: "streamable_http",
174
174
  fetcher: createWeComRateLimitedFetch(context.fetcher, hashWeComRateLimitKey(context)),
175
175
  headers,
176
- redirect: "error",
176
+ redirect: "manual",
177
177
  signal: context.signal,
178
178
  mapError: mapWeComMcpError,
179
179
  }, run);