@oomol-lab/open-connector 1.6.2 → 1.6.3

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 (168) hide show
  1. package/README.md +27 -0
  2. package/assets/open-connector/catalog/apps/assured_platform.json +218 -0
  3. package/assets/open-connector/catalog/apps/baixiao.json +1080 -0
  4. package/assets/open-connector/catalog/apps/benchmarkone.json +956 -0
  5. package/assets/open-connector/catalog/apps/bing_webmaster.json +3097 -0
  6. package/assets/open-connector/catalog/apps/buzzsprout.json +1979 -0
  7. package/assets/open-connector/catalog/apps/feishu.json +13 -19
  8. package/assets/open-connector/catalog/apps/feishu_app_bot.json +10 -17
  9. package/assets/open-connector/catalog/apps/linkfox.json +836 -0
  10. package/assets/open-connector/catalog/apps/metatraderapi.json +1077 -0
  11. package/assets/open-connector/catalog/apps/mktindex.json +2411 -0
  12. package/assets/open-connector/catalog/apps/mojo_helpdesk.json +660 -0
  13. package/assets/open-connector/catalog/apps/mumble.json +446 -0
  14. package/assets/open-connector/catalog/apps/notion.json +112 -3
  15. package/assets/open-connector/catalog/apps/omise.json +438 -0
  16. package/assets/open-connector/catalog/apps/panjiva.json +2858 -0
  17. package/assets/open-connector/catalog/apps/papertrail.json +449 -0
  18. package/assets/open-connector/catalog/apps/peec.json +747 -0
  19. package/assets/open-connector/catalog/apps/polygon_io.json +5156 -584
  20. package/assets/open-connector/catalog/apps/quant_data.json +1928 -0
  21. package/assets/open-connector/catalog/apps/riddle_quiz_maker.json +621 -0
  22. package/assets/open-connector/catalog/apps/slack.json +327 -4
  23. package/assets/open-connector/catalog/apps/slackbot.json +280 -3
  24. package/assets/open-connector/catalog/apps/templatedocs.json +438 -0
  25. package/assets/open-connector/catalog/apps/terminus_app.json +510 -0
  26. package/assets/open-connector/catalog/apps/ticket_tailor.json +584 -0
  27. package/assets/open-connector/catalog/apps/tongdaxin.json +2612 -79
  28. package/assets/open-connector/catalog/apps/veremark.json +514 -0
  29. package/assets/open-connector/catalog/apps/walletap.json +549 -0
  30. package/assets/open-connector/catalog/apps/xiaohongshu_store.json +955 -39
  31. package/assets/open-connector/catalog/apps-index.json +1 -1
  32. package/package.json +5 -1
  33. package/src/connection-service.d.ts +2 -0
  34. package/src/connection-service.js +8 -1
  35. package/src/core/provider-id.d.ts +5 -0
  36. package/src/core/provider-id.js +12 -0
  37. package/src/oauth/oauth-credential-refresh-service.js +2 -0
  38. package/src/oauth/oauth-flow-service.js +2 -0
  39. package/src/providers/action-contracts.generated.d.ts +27 -7
  40. package/src/providers/assured_platform/executors.d.ts +4 -0
  41. package/src/providers/assured_platform/executors.js +18 -0
  42. package/src/providers/assured_platform/runtime.d.ts +7 -0
  43. package/src/providers/assured_platform/runtime.js +108 -0
  44. package/src/providers/baixiao/actions.d.ts +2 -0
  45. package/src/providers/baixiao/actions.js +377 -0
  46. package/src/providers/baixiao/executors.d.ts +4 -0
  47. package/src/providers/baixiao/executors.js +106 -0
  48. package/src/providers/benchmarkone/executors.d.ts +4 -0
  49. package/src/providers/benchmarkone/executors.js +26 -0
  50. package/src/providers/benchmarkone/runtime.d.ts +12 -0
  51. package/src/providers/benchmarkone/runtime.js +113 -0
  52. package/src/providers/bing_webmaster/actions.d.ts +2 -0
  53. package/src/providers/bing_webmaster/actions.js +113 -0
  54. package/src/providers/bing_webmaster/executors.d.ts +4 -0
  55. package/src/providers/bing_webmaster/executors.js +104 -0
  56. package/src/providers/bing_webmaster/management-actions.d.ts +2 -0
  57. package/src/providers/bing_webmaster/management-actions.js +241 -0
  58. package/src/providers/bing_webmaster/reporting-actions.d.ts +2 -0
  59. package/src/providers/bing_webmaster/reporting-actions.js +300 -0
  60. package/src/providers/bing_webmaster/schemas.d.ts +33 -0
  61. package/src/providers/bing_webmaster/schemas.js +313 -0
  62. package/src/providers/buzzsprout/executors.d.ts +4 -0
  63. package/src/providers/buzzsprout/executors.js +21 -0
  64. package/src/providers/buzzsprout/runtime.d.ts +12 -0
  65. package/src/providers/buzzsprout/runtime.js +237 -0
  66. package/src/providers/feishu/shared/drive-actions.d.ts +1 -0
  67. package/src/providers/feishu/shared/drive-actions.js +13 -9
  68. package/src/providers/feishu/shared/drive-runtime.js +15 -3
  69. package/src/providers/feishu/shared/wiki-actions.js +4 -2
  70. package/src/providers/hithink_finance/runtime.js +39 -60
  71. package/src/providers/linkfox/actions.js +2 -0
  72. package/src/providers/linkfox/runtime.js +70 -1
  73. package/src/providers/linkfox/sif-actions.d.ts +10 -0
  74. package/src/providers/linkfox/sif-actions.js +193 -0
  75. package/src/providers/metatraderapi/executors.d.ts +4 -0
  76. package/src/providers/metatraderapi/executors.js +18 -0
  77. package/src/providers/metatraderapi/runtime.d.ts +7 -0
  78. package/src/providers/metatraderapi/runtime.js +216 -0
  79. package/src/providers/mktindex/actions-mmi.d.ts +2 -0
  80. package/src/providers/mktindex/actions-mmi.js +510 -0
  81. package/src/providers/mktindex/actions.d.ts +2 -0
  82. package/src/providers/mktindex/actions.js +2 -0
  83. package/src/providers/mktindex/executors.d.ts +4 -0
  84. package/src/providers/mktindex/executors.js +27 -0
  85. package/src/providers/mktindex/request.d.ts +17 -0
  86. package/src/providers/mktindex/request.js +48 -0
  87. package/src/providers/mktindex/runtime.d.ts +9 -0
  88. package/src/providers/mktindex/runtime.js +492 -0
  89. package/src/providers/mojo_helpdesk/executors.d.ts +4 -0
  90. package/src/providers/mojo_helpdesk/executors.js +18 -0
  91. package/src/providers/mojo_helpdesk/runtime.d.ts +7 -0
  92. package/src/providers/mojo_helpdesk/runtime.js +173 -0
  93. package/src/providers/mumble/executors.d.ts +4 -0
  94. package/src/providers/mumble/executors.js +99 -0
  95. package/src/providers/notion/executors.d.ts +9 -0
  96. package/src/providers/notion/executors.js +161 -6
  97. package/src/providers/omise/executors.d.ts +4 -0
  98. package/src/providers/omise/executors.js +120 -0
  99. package/src/providers/panjiva/actions.d.ts +14 -0
  100. package/src/providers/panjiva/actions.js +389 -0
  101. package/src/providers/panjiva/executors.d.ts +4 -0
  102. package/src/providers/panjiva/executors.js +136 -0
  103. package/src/providers/papertrail/executors.d.ts +4 -0
  104. package/src/providers/papertrail/executors.js +15 -0
  105. package/src/providers/papertrail/runtime.d.ts +7 -0
  106. package/src/providers/papertrail/runtime.js +167 -0
  107. package/src/providers/peec/executors.d.ts +4 -0
  108. package/src/providers/peec/executors.js +15 -0
  109. package/src/providers/peec/runtime.d.ts +7 -0
  110. package/src/providers/peec/runtime.js +128 -0
  111. package/src/providers/polygon_io/actions.d.ts +2 -0
  112. package/src/providers/polygon_io/actions.js +282 -0
  113. package/src/providers/polygon_io/expanded-actions.d.ts +2 -0
  114. package/src/providers/polygon_io/expanded-actions.js +375 -0
  115. package/src/providers/polygon_io/expanded-runtime.d.ts +16 -0
  116. package/src/providers/polygon_io/expanded-runtime.js +277 -0
  117. package/src/providers/polygon_io/runtime.js +21 -2
  118. package/src/providers/quant_data/executors.d.ts +4 -0
  119. package/src/providers/quant_data/executors.js +15 -0
  120. package/src/providers/quant_data/runtime.d.ts +7 -0
  121. package/src/providers/quant_data/runtime.js +118 -0
  122. package/src/providers/registry.generated.js +20 -0
  123. package/src/providers/riddle_quiz_maker/executors.d.ts +4 -0
  124. package/src/providers/riddle_quiz_maker/executors.js +15 -0
  125. package/src/providers/riddle_quiz_maker/runtime.d.ts +7 -0
  126. package/src/providers/riddle_quiz_maker/runtime.js +145 -0
  127. package/src/providers/slack/actions.js +106 -3
  128. package/src/providers/slack/runtime.js +260 -36
  129. package/src/providers/slackbot/actions.js +1 -1
  130. package/src/providers/templatedocs/executors.d.ts +4 -0
  131. package/src/providers/templatedocs/executors.js +18 -0
  132. package/src/providers/templatedocs/runtime.d.ts +13 -0
  133. package/src/providers/templatedocs/runtime.js +151 -0
  134. package/src/providers/terminus_app/executors.d.ts +4 -0
  135. package/src/providers/terminus_app/executors.js +15 -0
  136. package/src/providers/terminus_app/runtime.d.ts +7 -0
  137. package/src/providers/terminus_app/runtime.js +172 -0
  138. package/src/providers/ticket_tailor/executors.d.ts +4 -0
  139. package/src/providers/ticket_tailor/executors.js +15 -0
  140. package/src/providers/ticket_tailor/runtime.d.ts +7 -0
  141. package/src/providers/ticket_tailor/runtime.js +152 -0
  142. package/src/providers/tongdaxin/actions.d.ts +1 -0
  143. package/src/providers/tongdaxin/actions.js +6 -3
  144. package/src/providers/tongdaxin/executors.js +51 -10
  145. package/src/providers/tongdaxin/named-action-output.d.ts +1 -0
  146. package/src/providers/tongdaxin/named-action-output.js +149 -0
  147. package/src/providers/tongdaxin/named-action-routing.d.ts +9 -0
  148. package/src/providers/tongdaxin/named-action-routing.js +417 -0
  149. package/src/providers/tongdaxin/named-actions.d.ts +2 -0
  150. package/src/providers/tongdaxin/named-actions.js +378 -0
  151. package/src/providers/veremark/executors.d.ts +4 -0
  152. package/src/providers/veremark/executors.js +129 -0
  153. package/src/providers/walletap/executors.d.ts +4 -0
  154. package/src/providers/walletap/executors.js +15 -0
  155. package/src/providers/walletap/runtime.d.ts +7 -0
  156. package/src/providers/walletap/runtime.js +147 -0
  157. package/src/providers/weixin_official_account/access-token.js +31 -7
  158. package/src/providers/weixin_store/access-token.js +33 -7
  159. package/src/providers/xiaohongshu_store/executors.js +152 -3
  160. package/src/server/api/openapi.js +3 -0
  161. package/src/server/connector-assets.d.ts +4 -0
  162. package/src/server/connector-assets.js +12 -0
  163. package/src/server/connector-build.d.ts +33 -0
  164. package/src/server/connector-build.js +73 -0
  165. package/src/server/connector-runtime.d.ts +1 -2
  166. package/src/server/connector-runtime.js +2 -8
  167. package/src/server/server-assets.d.ts +0 -28
  168. package/src/server/server-assets.js +0 -50
@@ -15,6 +15,23 @@ const sortDirectionSchema = s.stringEnum(["asc", "desc"], {
15
15
  const conversationTypeSchema = s.stringEnum([...slackConversationTypes], {
16
16
  description: "A Slack conversation type.",
17
17
  });
18
+ // `conversations.history` and `conversations.replies` take the same time
19
+ // window, so it is declared once. The bounds are Slack `ts` strings
20
+ // ("<epoch seconds>.<6 digits>"), NOT epoch integers: Slack compares them
21
+ // against the message `ts` lexically-as-decimal, and a bare integer is a
22
+ // legal value for that same field. Left as `string` rather than a pattern
23
+ // so a caller can pass a `ts` it read back from a message verbatim.
24
+ const historyWindowProperties = {
25
+ oldest: s.string({
26
+ description: "Only return messages after this Slack timestamp, for example '1700000000.123456'. The message at exactly this timestamp is included only when inclusive is true.",
27
+ }),
28
+ latest: s.string({
29
+ description: "Only return messages before this Slack timestamp. Defaults to now. The message at exactly this timestamp is included only when inclusive is true.",
30
+ }),
31
+ inclusive: s.boolean({
32
+ description: "Include messages whose timestamp equals oldest or latest. Slack ignores this unless one of those bounds is set.",
33
+ }),
34
+ };
18
35
  const slackBlockSchema = s.unknownObject("A Slack Block Kit block object. Pass the block exactly as Slack documents it.");
19
36
  const slackAttachmentSchema = s.unknownObject("A Slack legacy attachment object. Prefer blocks for new messages when possible.");
20
37
  const messageContentProperties = {
@@ -33,10 +50,42 @@ const messageContentProperties = {
33
50
  unfurlMedia: s.boolean({ description: "Whether Slack should unfurl media in the message." }),
34
51
  metadata: s.unknownObject("Slack message metadata to attach to the message."),
35
52
  };
53
+ const slackReactionSchema = s.looseObject({
54
+ name: s.string({ description: "The emoji name of the reaction." }),
55
+ count: s.integer({ description: "How many users added this reaction." }),
56
+ userIds: s.array(s.string({ description: "A Slack user ID." }), {
57
+ description: "The users who added this reaction, as far as Slack reports them.",
58
+ }),
59
+ }, { description: "A reaction summary on a Slack message." });
60
+ // Only fields the executor actually normalizes are declared. The object is
61
+ // loose, but that permits extras it does NOT make them appear: the executor
62
+ // 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.
36
66
  const slackMessageSchema = s.looseObject({
37
67
  ts: s.string({ description: "The message timestamp identifier." }),
68
+ type: s.string({ description: "The Slack message type, normally 'message'." }),
69
+ subtype: s.string({
70
+ description: "The Slack message subtype ('channel_join', 'bot_message', …) when the message has one.",
71
+ }),
38
72
  userId: s.string({ description: "The user ID of the message author." }),
73
+ botId: s.string({ description: "The bot ID of the message author when a bot posted it." }),
74
+ appId: s.string({ description: "The Slack app ID that posted the message when an app posted it." }),
75
+ username: s.string({ description: "The display username Slack attached to a bot or app message." }),
76
+ teamId: s.string({ description: "The Slack team ID the message belongs to." }),
77
+ clientMsgId: s.string({ description: "The client-generated message identifier when Slack returns one." }),
39
78
  text: s.string({ description: "The text content of the message." }),
79
+ editedTs: s.string({ description: "The timestamp of the most recent edit, when the message was edited." }),
80
+ threadTs: s.string({
81
+ 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
+ }),
83
+ parentUserId: s.string({ description: "The author of the thread parent, on a threaded reply." }),
84
+ replyCount: s.integer({ description: "The number of replies to this thread parent." }),
85
+ replyUsersCount: s.integer({ description: "The number of distinct users who replied to this thread parent." }),
86
+ latestReply: s.string({ description: "The timestamp of the most recent reply to this thread parent." }),
87
+ isLocked: s.boolean({ description: "Whether the thread is locked." }),
88
+ reactions: s.array(slackReactionSchema, { description: "Reaction summaries attached to the message." }),
40
89
  }, { description: "A Slack message record." });
41
90
  const searchMessageMatchSchema = s.looseObject({
42
91
  matchId: s.string({ description: "Slack's search result item identifier." }),
@@ -103,6 +152,18 @@ const fileSchema = s.looseObject({
103
152
  }, { description: "A Slack file object returned by the Web API." });
104
153
  const reactionItemSchema = s.unknownObject("A Slack item with reactions.");
105
154
  export const slackActions = [
155
+ action({
156
+ name: "get_current_user",
157
+ operationType: "read",
158
+ description: "Get the workspace and user identity of the connected Slack credential, including whether it belongs to a bot.",
159
+ requiredScopes: [],
160
+ inputSchema: s.object({}),
161
+ outputSchema: s.object({
162
+ teamId: s.nonEmptyString("The Slack workspace ID."),
163
+ userId: userIdSchema,
164
+ isBot: s.boolean({ description: "Whether auth.test identifies this credential with a bot_id." }),
165
+ }, { required: ["teamId", "userId", "isBot"] }),
166
+ }),
106
167
  action({
107
168
  name: "list_channels",
108
169
  operationType: "read",
@@ -120,12 +181,46 @@ export const slackActions = [
120
181
  requiredScopes: ["channels:history", "groups:history", "im:history", "mpim:history"],
121
182
  inputSchema: s.object({
122
183
  channelId: channelIdSchema,
123
- limit: s.integer({ minimum: 1, maximum: 100, description: "The maximum number of messages to return." }),
184
+ // 999 is `conversations.history`'s documented ceiling. The former 100
185
+ // was this action's own invention and cost a request per 100 messages.
186
+ limit: s.integer({ minimum: 1, maximum: 999, description: "The maximum number of messages to return." }),
187
+ cursor: s.string({
188
+ description: "The Slack pagination cursor from a previous page. Omit for the first page.",
189
+ }),
190
+ ...historyWindowProperties,
124
191
  }, { required: ["channelId"], description: "Input parameters for reading Slack conversation history." }),
125
192
  outputSchema: s.object({
126
193
  messages: s.array(slackMessageSchema, { description: "The list of messages in the conversation." }),
127
194
  hasMore: s.boolean({ description: "Whether more messages are available beyond this page." }),
128
- }, { required: ["messages", "hasMore"], description: "The output payload for this action." }),
195
+ nextCursor: s.string({
196
+ description: "The cursor for the next page, or an empty string when this is the last page.",
197
+ }),
198
+ }, { required: ["messages", "hasMore", "nextCursor"], description: "The output payload for this action." }),
199
+ }),
200
+ action({
201
+ name: "conversations_members",
202
+ operationType: "read",
203
+ description: "List the member user IDs of a Slack conversation. Returns one page; pass the cursor from nextCursor until it comes back empty.",
204
+ requiredScopes: ["channels:read", "groups:read", "im:read", "mpim:read"],
205
+ inputSchema: s.object({
206
+ channelId: channelIdSchema,
207
+ cursor: s.string({
208
+ description: "The Slack pagination cursor from a previous page. Omit for the first page.",
209
+ }),
210
+ limit: s.integer({
211
+ minimum: 1,
212
+ maximum: 1000,
213
+ description: "The maximum number of members to return per page.",
214
+ }),
215
+ }, { required: ["channelId"], description: "Input parameters for listing Slack conversation members." }),
216
+ outputSchema: s.object({
217
+ memberIds: s.array(userIdSchema, {
218
+ description: "The Slack user IDs that are members of the conversation.",
219
+ }),
220
+ nextCursor: s.string({
221
+ description: "The cursor for the next page, or an empty string when this is the last page.",
222
+ }),
223
+ }, { required: ["memberIds", "nextCursor"], description: "The output payload for this action." }),
129
224
  }),
130
225
  action({
131
226
  name: "search_messages",
@@ -223,11 +318,19 @@ export const slackActions = [
223
318
  inputSchema: s.object({
224
319
  channelId: channelIdSchema,
225
320
  threadTs: s.nonEmptyString("The timestamp of the parent message."),
321
+ limit: s.integer({ minimum: 1, maximum: 999, description: "The maximum number of messages to return." }),
322
+ cursor: s.string({
323
+ description: "The Slack pagination cursor from a previous page. Omit for the first page.",
324
+ }),
325
+ ...historyWindowProperties,
226
326
  }, { required: ["channelId", "threadTs"], description: "Input parameters for reading a Slack thread." }),
227
327
  outputSchema: s.object({
228
328
  messages: s.array(slackMessageSchema, { description: "The list of messages in the thread." }),
229
329
  hasMore: s.boolean({ description: "Whether more messages are available beyond this page." }),
230
- }, { required: ["messages", "hasMore"], description: "The output payload for this action." }),
330
+ nextCursor: s.string({
331
+ description: "The cursor for the next page, or an empty string when this is the last page.",
332
+ }),
333
+ }, { required: ["messages", "hasMore", "nextCursor"], description: "The output payload for this action." }),
231
334
  }),
232
335
  action({
233
336
  name: "list_conversations",
@@ -1,6 +1,6 @@
1
- import { compactObject, optionalBoolean, optionalRecord, optionalString, requiredString } from "../../core/cast.js";
1
+ import { compactObject, optionalBoolean, optionalInteger, optionalRecord, optionalString, requiredString, } from "../../core/cast.js";
2
2
  import { assertPublicHttpUrl, readBoundedResponseBytes } from "../../core/request.js";
3
- import { createProviderTimeout, defineProviderExecutors, isAbortLikeError, ProviderRequestError, providerUserAgent, requireOAuthCredential, } from "../provider-runtime.js";
3
+ import { createProviderTimeout, defineProviderExecutors, isAbortLikeError, ProviderRequestError, providerUserAgent, } 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;
@@ -10,12 +10,26 @@ export function defineSlackProviderExecutors(service, tokenKind, handlers) {
10
10
  service,
11
11
  handlers,
12
12
  async createContext(context, fetcher) {
13
- const credential = await requireOAuthCredential(context, service);
14
- if (readSlackTokenKind(credential.accessToken, credential.metadata) != tokenKind) {
15
- throw new ProviderRequestError(401, `Reconnect ${service} with ${tokenKind} authorization before running its actions.`);
13
+ const credential = await context.getCredential(service);
14
+ let accessToken;
15
+ if (credential?.authType === "oauth2") {
16
+ if (readSlackTokenKind(credential.accessToken, credential.metadata) != tokenKind) {
17
+ throw new ProviderRequestError(401, `Reconnect ${service} with ${tokenKind} authorization before running its actions.`);
18
+ }
19
+ accessToken = credential.accessToken;
20
+ }
21
+ else if (credential?.authType === "api_key") {
22
+ // A pasted token was authorized by no OAuth flow, so the user/bot flow
23
+ // gate above has nothing to check for it; Slack enforces its own
24
+ // per-method token rules. Mirrors github and notion, whose api_key arm
25
+ // is likewise "use this bearer as-is".
26
+ accessToken = credential.apiKey;
27
+ }
28
+ else {
29
+ throw new ProviderRequestError(401, `Configure ${service} credentials first.`);
16
30
  }
17
31
  const providerContext = {
18
- accessToken: credential.accessToken,
32
+ accessToken,
19
33
  fetcher,
20
34
  signal: context.signal,
21
35
  };
@@ -27,12 +41,18 @@ export function defineSlackProviderExecutors(service, tokenKind, handlers) {
27
41
  });
28
42
  }
29
43
  export const slackActionHandlers = {
44
+ get_current_user(_input, context) {
45
+ return slackGetCurrentUser(context);
46
+ },
30
47
  list_channels(input, context) {
31
48
  return slackListChannels(input, context);
32
49
  },
33
50
  get_channel_messages(input, context) {
34
51
  return slackGetChannelMessages(input, context);
35
52
  },
53
+ conversations_members(input, context) {
54
+ return slackConversationsMembers(input, context);
55
+ },
36
56
  search_messages(input, context) {
37
57
  return slackSearchMessages(input, context);
38
58
  },
@@ -101,19 +121,20 @@ export const slackActionHandlers = {
101
121
  },
102
122
  };
103
123
  export const slackCredentialValidators = {
124
+ async apiKey(input, { fetcher, signal }) {
125
+ const payload = await slackAuthTest({ accessToken: input.apiKey, fetcher, signal });
126
+ return {
127
+ profile: slackCredentialProfile(payload),
128
+ metadata: {
129
+ currentAccount: payload,
130
+ },
131
+ };
132
+ },
104
133
  async oauth2(input, { fetcher, signal }) {
105
- const payload = await slackRequestJson({
106
- accessToken: input.accessToken,
107
- fetcher,
108
- signal,
109
- method: "auth.test",
110
- });
134
+ const payload = await slackAuthTest({ accessToken: input.accessToken, fetcher, signal });
111
135
  const responseScopes = readSlackCredentialScopes(input.accessToken, input.metadata);
112
136
  return {
113
- profile: {
114
- accountId: payload.user_id ?? payload.team_id ?? "slack:oauth2",
115
- displayName: payload.team ?? payload.team_id ?? payload.user_id ?? "Slack Workspace",
116
- },
137
+ profile: slackCredentialProfile(payload),
117
138
  grantedScopes: responseScopes.length > 0 ? responseScopes : input.profile.grantedScopes,
118
139
  metadata: {
119
140
  currentAccount: payload,
@@ -121,6 +142,34 @@ export const slackCredentialValidators = {
121
142
  };
122
143
  },
123
144
  };
145
+ async function slackAuthTest(input) {
146
+ return slackRequestJson({ ...input, method: "auth.test" });
147
+ }
148
+ /**
149
+ * Read the identity `auth.test` reports for a token. Slack documents `team_id`
150
+ * and `user_id` on every successful response and `bot_id` only for bot tokens,
151
+ * so a response missing either required ID is malformed rather than anonymous:
152
+ * no fallback identity is manufactured for it, because a connection keyed on a
153
+ * placeholder cannot be told apart from another token in the same state.
154
+ */
155
+ function readSlackAuthTestIdentity(payload) {
156
+ return {
157
+ teamId: requireSlackId(payload.team_id, "auth.test team_id"),
158
+ userId: requireSlackId(payload.user_id, "auth.test user_id"),
159
+ botId: payload.bot_id === undefined ? undefined : requireSlackId(payload.bot_id, "auth.test bot_id"),
160
+ };
161
+ }
162
+ function slackCredentialProfile(payload) {
163
+ const identity = readSlackAuthTestIdentity(payload);
164
+ return {
165
+ accountId: identity.userId,
166
+ displayName: optionalString(payload.team) ?? identity.teamId,
167
+ };
168
+ }
169
+ async function slackGetCurrentUser(context) {
170
+ const identity = readSlackAuthTestIdentity(await slackAuthTest(context));
171
+ return { teamId: identity.teamId, userId: identity.userId, isBot: identity.botId !== undefined };
172
+ }
124
173
  async function slackListChannels(input, context) {
125
174
  const url = slackApiUrl("conversations.list");
126
175
  if (input.limit != null) {
@@ -140,14 +189,25 @@ async function slackGetChannelMessages(input, context) {
140
189
  if (input.limit != null) {
141
190
  url.searchParams.set("limit", String(input.limit));
142
191
  }
192
+ if (input.cursor != null) {
193
+ url.searchParams.set("cursor", String(input.cursor));
194
+ }
195
+ applySlackHistoryWindow(url, input);
196
+ return readSlackMessagePage(await slackGetJson(url, context), "conversations.history");
197
+ }
198
+ async function slackConversationsMembers(input, context) {
199
+ const url = slackApiUrl("conversations.members");
200
+ url.searchParams.set("channel", String(input.channelId));
201
+ if (input.cursor != null) {
202
+ url.searchParams.set("cursor", String(input.cursor));
203
+ }
204
+ if (input.limit != null) {
205
+ url.searchParams.set("limit", String(input.limit));
206
+ }
143
207
  const payload = await slackGetJson(url, context);
144
208
  return {
145
- messages: (payload.messages ?? []).map((message) => ({
146
- ts: message.ts,
147
- userId: message.user ?? "",
148
- text: message.text ?? "",
149
- })),
150
- hasMore: payload.has_more ?? false,
209
+ memberIds: requireSlackArray(payload.members, "conversations.members members").map((member) => requireSlackId(member, "conversations.members member")),
210
+ nextCursor: readSlackNextCursor(payload, "conversations.members"),
151
211
  };
152
212
  }
153
213
  async function slackSearchMessages(input, context) {
@@ -248,15 +308,14 @@ async function slackGetThread(input, context) {
248
308
  const url = slackApiUrl("conversations.replies");
249
309
  url.searchParams.set("channel", String(input.channelId));
250
310
  url.searchParams.set("ts", String(input.threadTs));
251
- const payload = await slackGetJson(url, context);
252
- return {
253
- messages: (payload.messages ?? []).map((message) => ({
254
- ts: message.ts,
255
- userId: message.user ?? "",
256
- text: message.text ?? "",
257
- })),
258
- hasMore: payload.has_more ?? false,
259
- };
311
+ if (input.limit != null) {
312
+ url.searchParams.set("limit", String(input.limit));
313
+ }
314
+ if (input.cursor != null) {
315
+ url.searchParams.set("cursor", String(input.cursor));
316
+ }
317
+ applySlackHistoryWindow(url, input);
318
+ return readSlackMessagePage(await slackGetJson(url, context), "conversations.replies");
260
319
  }
261
320
  async function slackListConversations(input, context) {
262
321
  const url = slackApiUrl("conversations.list");
@@ -269,9 +328,11 @@ async function slackListConversations(input, context) {
269
328
  url.searchParams.set("exclude_archived", String(input.excludeArchived));
270
329
  }
271
330
  const payload = await slackGetJson(url, context);
331
+ // Discovery may prune against this page. Never manufacture an empty list or
332
+ // terminal cursor from an unreadable enumeration response.
272
333
  return {
273
- conversations: (payload.channels ?? []).map((channel) => normalizeConversation(channel)),
274
- nextCursor: normalizeNextCursor(payload.response_metadata?.next_cursor),
334
+ conversations: requireSlackArray(payload.channels, "conversations.list channels").map((channel) => normalizeListedConversation(channel)),
335
+ nextCursor: normalizeNextCursor(readSlackNextCursor(payload, "conversations.list")),
275
336
  };
276
337
  }
277
338
  async function slackGetConversation(input, context) {
@@ -530,11 +591,16 @@ async function slackFormRequestJson(context, method, body) {
530
591
  return readSlackResponseJson(response);
531
592
  }
532
593
  async function readSlackResponseJson(response) {
533
- const payload = (await response.json().catch(() => ({})));
594
+ const payload = (optionalRecord(await response.json().catch(() => undefined)) ?? {});
534
595
  if (!response.ok) {
535
- throw slackHttpError(response.status, payload);
596
+ throw slackHttpError(response.status, payload, response.headers.get("retry-after"));
536
597
  }
537
598
  assertSlackPayload(payload);
599
+ // Preserve HTTP/Slack failures (including Retry-After) above, but require
600
+ // affirmative success before any action can normalize an upstream payload.
601
+ if (payload.ok !== true) {
602
+ throw slackResponseError("ok");
603
+ }
538
604
  return payload;
539
605
  }
540
606
  function buildSlackMessagePayload(input, extra = {}) {
@@ -653,6 +719,43 @@ function normalizeScheduledPostAt(value, fallback) {
653
719
  }
654
720
  return postAt;
655
721
  }
722
+ function normalizeListedConversation(value) {
723
+ const channel = optionalRecord(value);
724
+ if (!channel) {
725
+ throw slackResponseError("conversations.list channel");
726
+ }
727
+ requireSlackId(channel.id, "conversations.list channel id");
728
+ for (const field of ["is_im", "is_mpim", "is_private", "is_channel", "is_group"]) {
729
+ if (channel[field] !== undefined && typeof channel[field] !== "boolean") {
730
+ throw slackResponseError(`conversations.list ${field}`);
731
+ }
732
+ }
733
+ // Positive IM/MPIM/channel flags establish kind even when unrelated negative
734
+ // flags are absent. Privacy is required only to distinguish modern channels.
735
+ if (channel.is_im === true || channel.is_mpim === true) {
736
+ if ((channel.is_im === true && (channel.is_mpim === true || channel.is_group === true)) ||
737
+ channel.is_channel === true ||
738
+ channel.is_private === false) {
739
+ throw slackResponseError("conversations.list conflicting direct message classification");
740
+ }
741
+ }
742
+ else if (channel.is_channel === true) {
743
+ if (typeof channel.is_private !== "boolean" || channel.is_group === true) {
744
+ throw slackResponseError("conversations.list channel classification");
745
+ }
746
+ }
747
+ else if (channel.is_group === true) {
748
+ // Legacy groups and MPIMs can both set is_group. Only is_mpim:false makes
749
+ // this unambiguously a private channel; no negative is_im flag is needed.
750
+ if (channel.is_mpim !== false || channel.is_private === false) {
751
+ throw slackResponseError("conversations.list ambiguous group classification");
752
+ }
753
+ }
754
+ else {
755
+ throw slackResponseError("conversations.list channel classification");
756
+ }
757
+ return normalizeConversation(channel);
758
+ }
656
759
  function normalizeConversationType(conversation) {
657
760
  if (conversation.is_im === true) {
658
761
  return "im";
@@ -685,6 +788,119 @@ function normalizeConversation(conversation) {
685
788
  locale: optionalString(conversation.locale),
686
789
  });
687
790
  }
791
+ /**
792
+ * Apply the shared `conversations.history` / `conversations.replies` time
793
+ * window. Bounds are passed through verbatim: they are Slack `ts` strings,
794
+ * and reformatting one (rounding, re-serializing as a number) would move the
795
+ * boundary Slack compares against.
796
+ */
797
+ function applySlackHistoryWindow(url, input) {
798
+ if (input.oldest != null) {
799
+ url.searchParams.set("oldest", String(input.oldest));
800
+ }
801
+ if (input.latest != null) {
802
+ url.searchParams.set("latest", String(input.latest));
803
+ }
804
+ if (input.inclusive != null) {
805
+ url.searchParams.set("inclusive", String(input.inclusive));
806
+ }
807
+ }
808
+ /**
809
+ * Read one `conversations.history` / `conversations.replies` page. A page whose
810
+ * list is missing or whose rows are not objects is rejected rather than
811
+ * flattened into an empty result, so a broken upstream response cannot pass
812
+ * for the documented end of a walk.
813
+ */
814
+ function readSlackMessagePage(payload, method) {
815
+ if (payload.has_more !== undefined && typeof payload.has_more !== "boolean") {
816
+ throw slackResponseError(`${method} has_more`);
817
+ }
818
+ return {
819
+ messages: requireSlackArray(payload.messages, `${method} messages`).map((message) => {
820
+ const record = optionalRecord(message);
821
+ if (!record) {
822
+ throw slackResponseError(`${method} message`);
823
+ }
824
+ return normalizeSlackMessage(record);
825
+ }),
826
+ hasMore: payload.has_more ?? false,
827
+ nextCursor: readSlackNextCursor(payload, method),
828
+ };
829
+ }
830
+ /**
831
+ * Read a page's `response_metadata.next_cursor`. Slack documents an absent,
832
+ * null or empty cursor as the last page, so those all read as `""`; a present
833
+ * cursor that is not a plain string is malformed, not evidence the walk ended.
834
+ */
835
+ function readSlackNextCursor(payload, method) {
836
+ const metadata = optionalRecord(payload.response_metadata);
837
+ if (payload.response_metadata !== undefined && !metadata) {
838
+ throw slackResponseError(`${method} response_metadata`);
839
+ }
840
+ const cursor = metadata?.next_cursor;
841
+ if (cursor == null || cursor === "") {
842
+ return "";
843
+ }
844
+ return requireSlackId(cursor, `${method} next_cursor`);
845
+ }
846
+ function requireSlackArray(value, label) {
847
+ if (!Array.isArray(value)) {
848
+ throw slackResponseError(label);
849
+ }
850
+ return value;
851
+ }
852
+ /**
853
+ * Require an upstream identifier exactly as Slack sent it. Whitespace is not
854
+ * normalized away: a padded or blank ID is a malformed response, and trimming
855
+ * it would let the wrong key reach a caller.
856
+ */
857
+ function requireSlackId(value, label) {
858
+ if (typeof value !== "string" || value === "" || value.trim() !== value) {
859
+ throw slackResponseError(label);
860
+ }
861
+ return value;
862
+ }
863
+ /**
864
+ * Normalize one `conversations.history` / `conversations.replies` message.
865
+ *
866
+ * `ts` and `text` keep their previous always-present shape (an absent text is
867
+ * `""`, not omitted) because consumers already read them unconditionally;
868
+ * every field added since is omitted when Slack does not send it, so a
869
+ * missing value stays distinguishable from an empty one. `userId` is the one
870
+ * exception kept for compatibility: it stays `""` on a message with no
871
+ * author, where `botId` / `username` carry the identity instead.
872
+ */
873
+ function normalizeSlackMessage(message) {
874
+ const edited = optionalRecord(message.edited) ?? {};
875
+ const reactions = Array.isArray(message.reactions) ? message.reactions : undefined;
876
+ return compactObject({
877
+ ts: requiredString(message.ts, "message ts", () => slackResponseError("conversations message ts")),
878
+ type: optionalString(message.type),
879
+ subtype: optionalString(message.subtype),
880
+ userId: optionalString(message.user) ?? "",
881
+ botId: optionalString(message.bot_id),
882
+ appId: optionalString(message.app_id),
883
+ username: optionalString(message.username),
884
+ teamId: optionalString(message.team),
885
+ clientMsgId: optionalString(message.client_msg_id),
886
+ text: typeof message.text === "string" ? message.text : "",
887
+ editedTs: optionalString(edited.ts),
888
+ threadTs: optionalString(message.thread_ts),
889
+ parentUserId: optionalString(message.parent_user_id),
890
+ replyCount: optionalInteger(message.reply_count),
891
+ replyUsersCount: optionalInteger(message.reply_users_count),
892
+ latestReply: optionalString(message.latest_reply),
893
+ isLocked: optionalBoolean(message.is_locked),
894
+ reactions: reactions?.map((reaction) => normalizeSlackReaction(optionalRecord(reaction) ?? {})),
895
+ });
896
+ }
897
+ function normalizeSlackReaction(reaction) {
898
+ return compactObject({
899
+ name: optionalString(reaction.name),
900
+ count: optionalInteger(reaction.count),
901
+ userIds: Array.isArray(reaction.users) ? reaction.users.map((user) => String(user)) : undefined,
902
+ });
903
+ }
688
904
  function normalizeSearchMessageMatch(match) {
689
905
  const channel = optionalRecord(match.channel) ?? {};
690
906
  return compactObject({
@@ -763,8 +979,16 @@ function formatSlackPayloadError(payload) {
763
979
  }
764
980
  return `${error}: ${details.join("; ")}`;
765
981
  }
766
- function slackHttpError(status, payload) {
982
+ function slackHttpError(status, payload, retryAfter) {
767
983
  const message = payload.error ? formatSlackPayloadError(payload) : `slack request failed with ${status}`;
984
+ if (status === 429 && retryAfter !== null && /^\d+$/.test(retryAfter)) {
985
+ const retryAfterSeconds = Number(retryAfter);
986
+ if (Number.isSafeInteger(retryAfterSeconds)) {
987
+ // The action envelope carries provider details; retain pacing so callers
988
+ // can resume the same page without guessing when this workspace may retry.
989
+ return new ProviderRequestError(status, message, { ...payload, retryAfterSeconds });
990
+ }
991
+ }
768
992
  return new ProviderRequestError(status, message, payload);
769
993
  }
770
994
  function slackResponseError(message) {
@@ -2,7 +2,7 @@ import { defineProviderAction } from "../../core/provider-definition.js";
2
2
  import { slackActions } from "../slack/actions.js";
3
3
  const service = "slackbot";
4
4
  export const slackbotActions = slackActions
5
- .filter((action) => action.name != "search_messages" && action.name != "search_context")
5
+ .filter((action) => action.name != "search_messages" && action.name != "search_context" && action.name != "get_current_user")
6
6
  .map((action) => defineProviderAction(service, {
7
7
  name: action.name,
8
8
  operationType: action.operationType,
@@ -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,18 @@
1
+ import { defineApiKeyProviderExecutors, defineProviderProxy } from "../provider-runtime.js";
2
+ import { templatedocsActionHandlers, templatedocsApiBaseUrl, validateTemplateDocsCredential } from "./runtime.js";
3
+ const service = "templatedocs";
4
+ export const executors = defineApiKeyProviderExecutors(service, templatedocsActionHandlers);
5
+ export const proxy = defineProviderProxy({
6
+ service,
7
+ baseUrl: templatedocsApiBaseUrl,
8
+ auth: { type: "api_key_authorization", prefix: "Bearer " },
9
+ skipDnsValidation: true,
10
+ customizeRequest({ headers }) {
11
+ headers.set("accept", "application/json");
12
+ },
13
+ });
14
+ export const credentialValidators = {
15
+ apiKey(input, { fetcher, signal }) {
16
+ return validateTemplateDocsCredential(input.apiKey, fetcher, signal);
17
+ },
18
+ };
@@ -0,0 +1,13 @@
1
+ import type { CredentialValidationResult, TransitFileWriter } from "../../core/types.ts";
2
+ import type { ProviderActionHandlers, ProviderFetch } from "../provider-runtime.ts";
3
+ export declare const templatedocsApiBaseUrl = "https://templatedocs.io/api";
4
+ interface TemplateDocsContext {
5
+ apiKey: string;
6
+ fetcher: ProviderFetch;
7
+ transitFiles?: TransitFileWriter;
8
+ signal?: AbortSignal;
9
+ }
10
+ type TemplateDocsHandler = (input: Record<string, unknown>, context: TemplateDocsContext) => Promise<unknown>;
11
+ export declare const templatedocsActionHandlers: ProviderActionHandlers<"templatedocs", TemplateDocsHandler>;
12
+ export declare function validateTemplateDocsCredential(apiKey: string, fetcher: ProviderFetch, signal?: AbortSignal): Promise<CredentialValidationResult>;
13
+ export {};