@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.
- package/README.md +27 -0
- package/assets/open-connector/catalog/apps/assured_platform.json +218 -0
- package/assets/open-connector/catalog/apps/baixiao.json +1080 -0
- package/assets/open-connector/catalog/apps/benchmarkone.json +956 -0
- package/assets/open-connector/catalog/apps/bing_webmaster.json +3097 -0
- package/assets/open-connector/catalog/apps/buzzsprout.json +1979 -0
- package/assets/open-connector/catalog/apps/feishu.json +13 -19
- package/assets/open-connector/catalog/apps/feishu_app_bot.json +10 -17
- package/assets/open-connector/catalog/apps/linkfox.json +836 -0
- package/assets/open-connector/catalog/apps/metatraderapi.json +1077 -0
- package/assets/open-connector/catalog/apps/mktindex.json +2411 -0
- package/assets/open-connector/catalog/apps/mojo_helpdesk.json +660 -0
- package/assets/open-connector/catalog/apps/mumble.json +446 -0
- package/assets/open-connector/catalog/apps/notion.json +112 -3
- package/assets/open-connector/catalog/apps/omise.json +438 -0
- package/assets/open-connector/catalog/apps/panjiva.json +2858 -0
- package/assets/open-connector/catalog/apps/papertrail.json +449 -0
- package/assets/open-connector/catalog/apps/peec.json +747 -0
- package/assets/open-connector/catalog/apps/polygon_io.json +5156 -584
- package/assets/open-connector/catalog/apps/quant_data.json +1928 -0
- package/assets/open-connector/catalog/apps/riddle_quiz_maker.json +621 -0
- package/assets/open-connector/catalog/apps/slack.json +327 -4
- package/assets/open-connector/catalog/apps/slackbot.json +280 -3
- package/assets/open-connector/catalog/apps/templatedocs.json +438 -0
- package/assets/open-connector/catalog/apps/terminus_app.json +510 -0
- package/assets/open-connector/catalog/apps/ticket_tailor.json +584 -0
- package/assets/open-connector/catalog/apps/tongdaxin.json +2612 -79
- package/assets/open-connector/catalog/apps/veremark.json +514 -0
- package/assets/open-connector/catalog/apps/walletap.json +549 -0
- package/assets/open-connector/catalog/apps/xiaohongshu_store.json +955 -39
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +5 -1
- package/src/connection-service.d.ts +2 -0
- package/src/connection-service.js +8 -1
- package/src/core/provider-id.d.ts +5 -0
- package/src/core/provider-id.js +12 -0
- package/src/oauth/oauth-credential-refresh-service.js +2 -0
- package/src/oauth/oauth-flow-service.js +2 -0
- package/src/providers/action-contracts.generated.d.ts +27 -7
- package/src/providers/assured_platform/executors.d.ts +4 -0
- package/src/providers/assured_platform/executors.js +18 -0
- package/src/providers/assured_platform/runtime.d.ts +7 -0
- package/src/providers/assured_platform/runtime.js +108 -0
- package/src/providers/baixiao/actions.d.ts +2 -0
- package/src/providers/baixiao/actions.js +377 -0
- package/src/providers/baixiao/executors.d.ts +4 -0
- package/src/providers/baixiao/executors.js +106 -0
- package/src/providers/benchmarkone/executors.d.ts +4 -0
- package/src/providers/benchmarkone/executors.js +26 -0
- package/src/providers/benchmarkone/runtime.d.ts +12 -0
- package/src/providers/benchmarkone/runtime.js +113 -0
- package/src/providers/bing_webmaster/actions.d.ts +2 -0
- package/src/providers/bing_webmaster/actions.js +113 -0
- package/src/providers/bing_webmaster/executors.d.ts +4 -0
- package/src/providers/bing_webmaster/executors.js +104 -0
- package/src/providers/bing_webmaster/management-actions.d.ts +2 -0
- package/src/providers/bing_webmaster/management-actions.js +241 -0
- package/src/providers/bing_webmaster/reporting-actions.d.ts +2 -0
- package/src/providers/bing_webmaster/reporting-actions.js +300 -0
- package/src/providers/bing_webmaster/schemas.d.ts +33 -0
- package/src/providers/bing_webmaster/schemas.js +313 -0
- package/src/providers/buzzsprout/executors.d.ts +4 -0
- package/src/providers/buzzsprout/executors.js +21 -0
- package/src/providers/buzzsprout/runtime.d.ts +12 -0
- package/src/providers/buzzsprout/runtime.js +237 -0
- package/src/providers/feishu/shared/drive-actions.d.ts +1 -0
- package/src/providers/feishu/shared/drive-actions.js +13 -9
- package/src/providers/feishu/shared/drive-runtime.js +15 -3
- package/src/providers/feishu/shared/wiki-actions.js +4 -2
- package/src/providers/hithink_finance/runtime.js +39 -60
- package/src/providers/linkfox/actions.js +2 -0
- package/src/providers/linkfox/runtime.js +70 -1
- package/src/providers/linkfox/sif-actions.d.ts +10 -0
- package/src/providers/linkfox/sif-actions.js +193 -0
- package/src/providers/metatraderapi/executors.d.ts +4 -0
- package/src/providers/metatraderapi/executors.js +18 -0
- package/src/providers/metatraderapi/runtime.d.ts +7 -0
- package/src/providers/metatraderapi/runtime.js +216 -0
- package/src/providers/mktindex/actions-mmi.d.ts +2 -0
- package/src/providers/mktindex/actions-mmi.js +510 -0
- package/src/providers/mktindex/actions.d.ts +2 -0
- package/src/providers/mktindex/actions.js +2 -0
- package/src/providers/mktindex/executors.d.ts +4 -0
- package/src/providers/mktindex/executors.js +27 -0
- package/src/providers/mktindex/request.d.ts +17 -0
- package/src/providers/mktindex/request.js +48 -0
- package/src/providers/mktindex/runtime.d.ts +9 -0
- package/src/providers/mktindex/runtime.js +492 -0
- package/src/providers/mojo_helpdesk/executors.d.ts +4 -0
- package/src/providers/mojo_helpdesk/executors.js +18 -0
- package/src/providers/mojo_helpdesk/runtime.d.ts +7 -0
- package/src/providers/mojo_helpdesk/runtime.js +173 -0
- package/src/providers/mumble/executors.d.ts +4 -0
- package/src/providers/mumble/executors.js +99 -0
- package/src/providers/notion/executors.d.ts +9 -0
- package/src/providers/notion/executors.js +161 -6
- package/src/providers/omise/executors.d.ts +4 -0
- package/src/providers/omise/executors.js +120 -0
- package/src/providers/panjiva/actions.d.ts +14 -0
- package/src/providers/panjiva/actions.js +389 -0
- package/src/providers/panjiva/executors.d.ts +4 -0
- package/src/providers/panjiva/executors.js +136 -0
- package/src/providers/papertrail/executors.d.ts +4 -0
- package/src/providers/papertrail/executors.js +15 -0
- package/src/providers/papertrail/runtime.d.ts +7 -0
- package/src/providers/papertrail/runtime.js +167 -0
- package/src/providers/peec/executors.d.ts +4 -0
- package/src/providers/peec/executors.js +15 -0
- package/src/providers/peec/runtime.d.ts +7 -0
- package/src/providers/peec/runtime.js +128 -0
- package/src/providers/polygon_io/actions.d.ts +2 -0
- package/src/providers/polygon_io/actions.js +282 -0
- package/src/providers/polygon_io/expanded-actions.d.ts +2 -0
- package/src/providers/polygon_io/expanded-actions.js +375 -0
- package/src/providers/polygon_io/expanded-runtime.d.ts +16 -0
- package/src/providers/polygon_io/expanded-runtime.js +277 -0
- package/src/providers/polygon_io/runtime.js +21 -2
- package/src/providers/quant_data/executors.d.ts +4 -0
- package/src/providers/quant_data/executors.js +15 -0
- package/src/providers/quant_data/runtime.d.ts +7 -0
- package/src/providers/quant_data/runtime.js +118 -0
- package/src/providers/registry.generated.js +20 -0
- package/src/providers/riddle_quiz_maker/executors.d.ts +4 -0
- package/src/providers/riddle_quiz_maker/executors.js +15 -0
- package/src/providers/riddle_quiz_maker/runtime.d.ts +7 -0
- package/src/providers/riddle_quiz_maker/runtime.js +145 -0
- package/src/providers/slack/actions.js +106 -3
- package/src/providers/slack/runtime.js +260 -36
- package/src/providers/slackbot/actions.js +1 -1
- package/src/providers/templatedocs/executors.d.ts +4 -0
- package/src/providers/templatedocs/executors.js +18 -0
- package/src/providers/templatedocs/runtime.d.ts +13 -0
- package/src/providers/templatedocs/runtime.js +151 -0
- package/src/providers/terminus_app/executors.d.ts +4 -0
- package/src/providers/terminus_app/executors.js +15 -0
- package/src/providers/terminus_app/runtime.d.ts +7 -0
- package/src/providers/terminus_app/runtime.js +172 -0
- package/src/providers/ticket_tailor/executors.d.ts +4 -0
- package/src/providers/ticket_tailor/executors.js +15 -0
- package/src/providers/ticket_tailor/runtime.d.ts +7 -0
- package/src/providers/ticket_tailor/runtime.js +152 -0
- package/src/providers/tongdaxin/actions.d.ts +1 -0
- package/src/providers/tongdaxin/actions.js +6 -3
- package/src/providers/tongdaxin/executors.js +51 -10
- package/src/providers/tongdaxin/named-action-output.d.ts +1 -0
- package/src/providers/tongdaxin/named-action-output.js +149 -0
- package/src/providers/tongdaxin/named-action-routing.d.ts +9 -0
- package/src/providers/tongdaxin/named-action-routing.js +417 -0
- package/src/providers/tongdaxin/named-actions.d.ts +2 -0
- package/src/providers/tongdaxin/named-actions.js +378 -0
- package/src/providers/veremark/executors.d.ts +4 -0
- package/src/providers/veremark/executors.js +129 -0
- package/src/providers/walletap/executors.d.ts +4 -0
- package/src/providers/walletap/executors.js +15 -0
- package/src/providers/walletap/runtime.d.ts +7 -0
- package/src/providers/walletap/runtime.js +147 -0
- package/src/providers/weixin_official_account/access-token.js +31 -7
- package/src/providers/weixin_store/access-token.js +33 -7
- package/src/providers/xiaohongshu_store/executors.js +152 -3
- package/src/server/api/openapi.js +3 -0
- package/src/server/connector-assets.d.ts +4 -0
- package/src/server/connector-assets.js +12 -0
- package/src/server/connector-build.d.ts +33 -0
- package/src/server/connector-build.js +73 -0
- package/src/server/connector-runtime.d.ts +1 -2
- package/src/server/connector-runtime.js +2 -8
- package/src/server/server-assets.d.ts +0 -28
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
274
|
-
nextCursor: normalizeNextCursor(payload.
|
|
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 {};
|