arcade-slack 0.1.6__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
@@ -1,130 +1,1079 @@
1
- from typing import Annotated
1
+ from typing import Annotated, cast
2
2
 
3
- from slack_sdk import WebClient
3
+ from arcade_tdk import ToolContext, tool
4
+ from arcade_tdk.auth import Slack
5
+ from arcade_tdk.errors import ToolExecutionError
4
6
  from slack_sdk.errors import SlackApiError
7
+ from slack_sdk.web.async_client import AsyncWebClient
5
8
 
6
- from arcade.sdk import ToolContext, tool
7
- from arcade.sdk.auth import Slack
8
- from arcade.sdk.errors import RetryableToolError, ToolExecutionError
9
+ from arcade_slack.constants import MAX_PAGINATION_SIZE_LIMIT
10
+ from arcade_slack.conversation_retrieval import (
11
+ get_channel_by_name,
12
+ get_conversation_by_id,
13
+ )
14
+ from arcade_slack.message_retrieval import retrieve_messages_in_conversation
15
+ from arcade_slack.models import (
16
+ ConversationType,
17
+ )
18
+ from arcade_slack.user_retrieval import (
19
+ get_users_by_id,
20
+ get_users_by_id_username_or_email,
21
+ )
22
+ from arcade_slack.utils import (
23
+ async_paginate,
24
+ extract_conversation_metadata,
25
+ populate_users_in_messages,
26
+ raise_for_users_not_found,
27
+ )
9
28
 
10
29
 
11
30
  @tool(
12
31
  requires_auth=Slack(
13
32
  scopes=[
33
+ "channels:read",
34
+ "groups:read",
35
+ "mpim:read",
36
+ "im:read",
37
+ "users:read",
38
+ "users:read.email",
14
39
  "chat:write",
40
+ "mpim:write",
15
41
  "im:write",
16
- "users.profile:read",
42
+ ],
43
+ )
44
+ )
45
+ async def send_message(
46
+ context: ToolContext,
47
+ message: Annotated[str, "The content of the message to send."],
48
+ channel_name: Annotated[
49
+ str | None,
50
+ "The channel name to send the message to. Prefer providing a conversation_id, "
51
+ "when available, since the performance is better.",
52
+ ] = None,
53
+ conversation_id: Annotated[str | None, "The conversation ID to send the message to."] = None,
54
+ user_ids: Annotated[list[str] | None, "The Slack user IDs of the people to message."] = None,
55
+ emails: Annotated[list[str] | None, "The emails of the people to message."] = None,
56
+ usernames: Annotated[
57
+ list[str] | None,
58
+ "The Slack usernames of the people to message. Prefer providing user_ids and/or emails, "
59
+ "when available, since the performance is better.",
60
+ ] = None,
61
+ ) -> Annotated[dict, "The response from the Slack API"]:
62
+ """Send a message to a Channel, Direct Message (IM/DM), or Multi-Person (MPIM) conversation
63
+
64
+ Provide exactly one of:
65
+ - channel_name; or
66
+ - conversation_id; or
67
+ - any combination of user_ids, usernames, and/or emails.
68
+
69
+ In case multiple user_ids, usernames, and/or emails are provided, the tool will open a
70
+ multi-person conversation with the specified people and send the message to it.
71
+ """
72
+ if conversation_id and any([channel_name, user_ids, usernames, emails]):
73
+ raise ToolExecutionError(
74
+ "Provide exactly one of: channel_name, OR conversation_id, OR any combination of "
75
+ "user_ids, usernames, and/or emails."
76
+ )
77
+
78
+ if not conversation_id:
79
+ conversation = await get_conversation_metadata(
80
+ context=context,
81
+ channel_name=channel_name,
82
+ user_ids=user_ids,
83
+ usernames=usernames,
84
+ emails=emails,
85
+ )
86
+ conversation_id = conversation["id"]
87
+
88
+ slack_client = AsyncWebClient(token=context.get_auth_token_or_empty())
89
+ response = await slack_client.chat_postMessage(channel=cast(str, conversation_id), text=message)
90
+ return {"success": True, "data": response.data}
91
+
92
+
93
+ @tool(
94
+ requires_auth=Slack(
95
+ scopes=[
96
+ "channels:read",
97
+ "groups:read",
98
+ "im:read",
99
+ "mpim:read",
17
100
  "users:read",
101
+ "users:read.email",
18
102
  ],
19
103
  )
20
104
  )
21
- def send_dm_to_user(
105
+ async def get_users_in_conversation(
22
106
  context: ToolContext,
23
- user_name: Annotated[
24
- str,
25
- "The Slack username of the person you want to message. Slack usernames are ALWAYS lowercase.",
26
- ],
27
- message: Annotated[str, "The message you want to send"],
28
- ):
29
- """Send a direct message to a user in Slack."""
107
+ conversation_id: Annotated[str | None, "The ID of the conversation to get users in."] = None,
108
+ channel_name: Annotated[
109
+ str | None,
110
+ "The name of the channel to get users in. Prefer providing a conversation_id, "
111
+ "when available, since the performance is better.",
112
+ ] = None,
113
+ # The user object is relatively small, so we allow a higher limit.
114
+ limit: Annotated[
115
+ int, "The maximum number of users to return. Defaults to 200. Maximum is 500."
116
+ ] = 200,
117
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
118
+ ) -> Annotated[dict, "Information about each user in the conversation"]:
119
+ """Get the users in a Slack conversation (Channel, DM/IM, or MPIM) by its ID or by channel name.
120
+
121
+ Provide exactly one of conversation_id or channel_name. Prefer providing a conversation_id,
122
+ when available, since the performance is better.
123
+ """
124
+ limit = max(1, min(limit, 500))
125
+
126
+ if sum({bool(conversation_id), bool(channel_name)}) != 1:
127
+ raise ToolExecutionError("Provide exactly one of conversation_id OR channel_name.")
128
+
129
+ auth_token = context.get_auth_token_or_empty()
130
+
131
+ if not conversation_id:
132
+ channel = await get_channel_by_name(auth_token, cast(str, channel_name))
133
+ conversation_id = channel["id"]
134
+
135
+ slack_client = AsyncWebClient(token=auth_token)
136
+ user_ids, next_cursor = await async_paginate(
137
+ func=slack_client.conversations_members,
138
+ response_key="members",
139
+ limit=limit,
140
+ next_cursor=next_cursor,
141
+ channel=conversation_id,
142
+ )
143
+
144
+ response = await get_users_by_id(auth_token, user_ids)
30
145
 
31
- slackClient = WebClient(token=context.authorization.token)
146
+ await raise_for_users_not_found(context, [response])
147
+
148
+ return {
149
+ "users": [user for user in response["users"] if not user.get("is_bot")],
150
+ "next_cursor": next_cursor,
151
+ }
152
+
153
+
154
+ @tool(
155
+ requires_auth=Slack(
156
+ scopes=[
157
+ "channels:read",
158
+ "groups:read",
159
+ "mpim:read",
160
+ "im:read",
161
+ "users:read",
162
+ "users:read.email",
163
+ "channels:history",
164
+ "groups:history",
165
+ "mpim:history",
166
+ "im:history",
167
+ ]
168
+ )
169
+ )
170
+ async def get_messages(
171
+ context: ToolContext,
172
+ conversation_id: Annotated[
173
+ str | None,
174
+ "The ID of the conversation to get messages from. Provide exactly one of conversation_id "
175
+ "OR any combination of user_ids, usernames, and/or emails.",
176
+ ] = None,
177
+ channel_name: Annotated[
178
+ str | None,
179
+ "The name of the channel to get messages from. Prefer providing a conversation_id, "
180
+ "when available, since the performance is better.",
181
+ ] = None,
182
+ user_ids: Annotated[
183
+ list[str] | None, "The IDs of the users in the conversation to get messages from."
184
+ ] = None,
185
+ usernames: Annotated[
186
+ list[str] | None,
187
+ "The usernames of the users in the conversation to get messages from. Prefer providing"
188
+ "user_ids and/or emails, when available, since the performance is better.",
189
+ ] = None,
190
+ emails: Annotated[
191
+ list[str] | None,
192
+ "The emails of the users in the conversation to get messages from.",
193
+ ] = None,
194
+ oldest_relative: Annotated[
195
+ str | None,
196
+ (
197
+ "The oldest message to include in the results, specified as a time offset from the "
198
+ "current time in the format 'DD:HH:MM'"
199
+ ),
200
+ ] = None,
201
+ latest_relative: Annotated[
202
+ str | None,
203
+ (
204
+ "The latest message to include in the results, specified as a time offset from the "
205
+ "current time in the format 'DD:HH:MM'"
206
+ ),
207
+ ] = None,
208
+ oldest_datetime: Annotated[
209
+ str | None,
210
+ (
211
+ "The oldest message to include in the results, specified as a datetime object in the "
212
+ "format 'YYYY-MM-DD HH:MM:SS'"
213
+ ),
214
+ ] = None,
215
+ latest_datetime: Annotated[
216
+ str | None,
217
+ (
218
+ "The latest message to include in the results, specified as a datetime object in the "
219
+ "format 'YYYY-MM-DD HH:MM:SS'"
220
+ ),
221
+ ] = None,
222
+ limit: Annotated[
223
+ # The message object can be relatively large, so we limit maximum to 100
224
+ # to preserve LLM's context window and reduce the likelihood of hallucinations.
225
+ int, "The maximum number of messages to return. Defaults to 20. Maximum is 100."
226
+ ] = 20,
227
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
228
+ ) -> Annotated[
229
+ dict,
230
+ "The messages in a Slack Channel, DM (direct message) or MPIM (multi-person) conversation.",
231
+ ]:
232
+ """Get messages in a Slack Channel, DM (direct message) or MPIM (multi-person) conversation.
233
+
234
+ Provide exactly one of:
235
+ - conversation_id; or
236
+ - channel_name; or
237
+ - any combination of user_ids, usernames, and/or emails.
238
+
239
+ To filter messages by an absolute datetime, use 'oldest_datetime' and/or 'latest_datetime'. If
240
+ only 'oldest_datetime' is provided, it will return messages from the oldest_datetime to the
241
+ current time. If only 'latest_datetime' is provided, it will return messages since the
242
+ beginning of the conversation to the latest_datetime.
243
+
244
+ To filter messages by a relative datetime (e.g. 3 days ago, 1 hour ago, etc.), use
245
+ 'oldest_relative' and/or 'latest_relative'. If only 'oldest_relative' is provided, it will
246
+ return messages from the oldest_relative to the current time. If only 'latest_relative' is
247
+ provided, it will return messages from the current time to the latest_relative.
248
+
249
+ Do not provide both 'oldest_datetime' and 'oldest_relative' or both 'latest_datetime' and
250
+ 'latest_relative'.
251
+
252
+ Leave all arguments with the default None to get messages without date/time filtering"""
253
+ limit = max(1, min(limit, 100))
254
+
255
+ if not conversation_id:
256
+ conversation = await get_conversation_metadata(
257
+ context=context,
258
+ channel_name=channel_name,
259
+ user_ids=user_ids,
260
+ usernames=usernames,
261
+ emails=emails,
262
+ )
263
+ conversation_id = conversation["id"]
264
+
265
+ response = await retrieve_messages_in_conversation(
266
+ auth_token=context.get_auth_token_or_empty(),
267
+ conversation_id=cast(str, conversation_id),
268
+ oldest_relative=oldest_relative,
269
+ latest_relative=latest_relative,
270
+ oldest_datetime=oldest_datetime,
271
+ latest_datetime=latest_datetime,
272
+ limit=limit,
273
+ next_cursor=next_cursor,
274
+ )
275
+
276
+ response["messages"] = await populate_users_in_messages(
277
+ auth_token=context.get_auth_token_or_empty(),
278
+ messages=response["messages"],
279
+ )
280
+
281
+ return cast(dict, response)
282
+
283
+
284
+ @tool(
285
+ requires_auth=Slack(
286
+ scopes=[
287
+ "channels:read",
288
+ "groups:read",
289
+ "mpim:read",
290
+ "im:read",
291
+ "users:read",
292
+ "users:read.email",
293
+ ],
294
+ )
295
+ )
296
+ async def get_conversation_metadata(
297
+ context: ToolContext,
298
+ conversation_id: Annotated[str | None, "The ID of the conversation to get metadata for"] = None,
299
+ channel_name: Annotated[
300
+ str | None,
301
+ "The name of the channel to get metadata for. Prefer providing a conversation_id, "
302
+ "when available, since the performance is better.",
303
+ ] = None,
304
+ usernames: Annotated[
305
+ list[str] | None,
306
+ "The usernames of the users to get the conversation metadata. "
307
+ "Prefer providing user_ids and/or emails, when available, since the performance is better.",
308
+ ] = None,
309
+ emails: Annotated[
310
+ list[str] | None,
311
+ "The emails of the users to get the conversation metadata.",
312
+ ] = None,
313
+ user_ids: Annotated[
314
+ list[str] | None,
315
+ "The IDs of the users to get the conversation metadata.",
316
+ ] = None,
317
+ ) -> Annotated[
318
+ dict | None,
319
+ "The conversation metadata.",
320
+ ]:
321
+ """Get metadata of a Channel, a Direct Message (IM / DM) or a Multi-Person (MPIM) conversation.
322
+
323
+ Use this tool to retrieve metadata about a conversation with a conversation_id, a channel name,
324
+ or by the user_id(s), username(s), and/or email(s) of the user(s) in the conversation.
325
+
326
+ This tool does not return the messages in a conversation. To get the messages, use the
327
+ 'Slack.GetMessages' tool instead.
328
+
329
+ Provide exactly one of:
330
+ - conversation_id; or
331
+ - channel_name; or
332
+ - any combination of user_ids, usernames, and/or emails.
333
+ """
334
+ if bool(conversation_id) + bool(channel_name) + any([user_ids, usernames, emails]) > 1:
335
+ raise ToolExecutionError(
336
+ "Provide exactly one of: conversation_id, OR channel_name, OR any combination of "
337
+ "user_ids, usernames, and/or emails."
338
+ )
339
+
340
+ auth_token = context.get_auth_token_or_empty()
341
+
342
+ if conversation_id:
343
+ return await get_conversation_by_id(auth_token, conversation_id)
344
+
345
+ elif channel_name:
346
+ return await get_channel_by_name(auth_token, channel_name)
347
+
348
+ user_ids_list = user_ids if isinstance(user_ids, list) else []
349
+
350
+ slack_client = AsyncWebClient(token=auth_token)
32
351
 
33
352
  try:
34
- # Step 1: Retrieve the user's Slack ID based on their username
35
- userListResponse = slackClient.users_list()
36
- user_id = None
37
- for user in userListResponse["members"]:
38
- if user["name"].lower() == user_name.lower():
39
- user_id = user["id"]
40
- break
41
-
42
- if not user_id:
43
- raise RetryableToolError(
44
- "User not found",
45
- developer_message=f"User with username '{user_name}' not found.",
46
- additional_prompt_content=format_users(userListResponse),
47
- retry_after_ms=500, # Play nice with Slack API rate limits
48
- )
49
-
50
- # Step 2: Retrieve the DM channel ID with the user
51
- im_response = slackClient.conversations_open(users=[user_id])
52
- dm_channel_id = im_response["channel"]["id"]
53
-
54
- # Step 3: Send the message as if it's from you (because we're using a user token)
55
- slackClient.chat_postMessage(channel=dm_channel_id, text=message)
353
+ current_user = await slack_client.auth_test()
354
+ except SlackApiError as e:
355
+ message = "Failed to get currently authenticated user's info."
356
+ developer_message = f"{message} Slack error: '{e.response.get('error', 'unknown_error')}'"
357
+ raise ToolExecutionError(message, developer_message)
358
+
359
+ if current_user["user_id"] not in user_ids_list:
360
+ user_ids_list.append(current_user["user_id"])
361
+
362
+ if usernames or emails:
363
+ other_users = await get_users_by_id_username_or_email(
364
+ context=context,
365
+ usernames=usernames,
366
+ emails=emails,
367
+ )
368
+ user_ids_list.extend([user["id"] for user in other_users])
56
369
 
370
+ try:
371
+ response = await slack_client.conversations_open(users=user_ids_list, return_im=True)
372
+ return dict(**extract_conversation_metadata(response["channel"]))
57
373
  except SlackApiError as e:
58
- error_message = e.response["error"] if "error" in e.response else str(e)
374
+ message = "Failed to retrieve conversation metadata."
375
+ slack_error = e.response.get("error", "unknown_error")
59
376
  raise ToolExecutionError(
60
- "Error sending message",
61
- developer_message=f"Slack API Error: {error_message}",
377
+ message=message,
378
+ developer_message=f"{message} Slack error: '{slack_error}'",
62
379
  )
63
380
 
64
381
 
65
- def format_users(userListResponse: dict) -> str:
66
- csv_string = "All active Slack users:\n\nname,real_name\n"
67
- for user in userListResponse["members"]:
68
- if not user.get("deleted", False):
69
- name = user.get("name", "")
70
- real_name = user.get("profile", {}).get("real_name", "")
71
- csv_string += f"{name},{real_name}\n"
72
- return csv_string.strip()
382
+ @tool(
383
+ requires_auth=Slack(
384
+ scopes=["channels:read", "groups:read", "im:read", "mpim:read"],
385
+ )
386
+ )
387
+ async def list_conversations(
388
+ context: ToolContext,
389
+ conversation_types: Annotated[
390
+ list[ConversationType] | None,
391
+ "Optionally filter by the type(s) of conversations. Defaults to None (all types).",
392
+ ] = None,
393
+ # The conversation object is relatively small, so we allow a higher limit.
394
+ limit: Annotated[
395
+ int,
396
+ f"The maximum number of conversations to list. Defaults to {MAX_PAGINATION_SIZE_LIMIT}. "
397
+ "Maximum is 500.",
398
+ ] = MAX_PAGINATION_SIZE_LIMIT,
399
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
400
+ ) -> Annotated[dict, "The list of conversations found with metadata"]:
401
+ """List metadata for Slack conversations (channels, DMs, MPIMs) the user is a member of.
402
+
403
+ This tool does not return the messages in a conversation. To get the messages, use the
404
+ 'Slack.GetMessages' tool instead. Calling this tool when the user is asking for messages
405
+ will release too much CO2 in the atmosphere and contribute to global warming.
406
+ """
407
+ limit = max(1, min(limit, 500))
408
+
409
+ if conversation_types:
410
+ conversation_types_filter = ",".join(
411
+ conversation_type.to_slack_name_str() for conversation_type in conversation_types
412
+ )
413
+ else:
414
+ conversation_types_filter = None
415
+
416
+ slack_client = AsyncWebClient(token=context.get_auth_token_or_empty())
417
+
418
+ results, next_cursor = await async_paginate(
419
+ slack_client.conversations_list,
420
+ "channels",
421
+ limit=limit,
422
+ next_cursor=next_cursor,
423
+ types=conversation_types_filter,
424
+ exclude_archived=True,
425
+ )
426
+
427
+ return {
428
+ "conversations": [
429
+ dict(**extract_conversation_metadata(conversation))
430
+ for conversation in results
431
+ if conversation.get("is_im") or conversation.get("is_member")
432
+ ],
433
+ "next_cursor": next_cursor,
434
+ }
435
+
436
+
437
+ ##################################################################################
438
+ # NOTE: The tools below are kept here for backwards compatibility. Prefer using: #
439
+ # - send_message
440
+ # - get_messages
441
+ # - get_conversation_metadata
442
+ # - get_users_in_conversation
443
+ # - list_conversations
444
+ ##################################################################################
73
445
 
74
446
 
75
447
  @tool(
76
448
  requires_auth=Slack(
77
449
  scopes=[
78
- "chat:write",
79
450
  "channels:read",
80
451
  "groups:read",
452
+ "mpim:read",
453
+ "im:read",
454
+ "users:read",
455
+ "users:read.email",
456
+ "chat:write",
457
+ "mpim:write",
458
+ "im:write",
81
459
  ],
82
460
  )
83
461
  )
84
- def send_message_to_channel(
462
+ async def send_dm_to_user(
85
463
  context: ToolContext,
86
- channel_name: Annotated[
464
+ user_name: Annotated[
87
465
  str,
88
- "The Slack channel name where you want to send the message. Slack channel names are ALWAYS lowercase.",
466
+ (
467
+ "The Slack username of the person you want to message. "
468
+ "Slack usernames are ALWAYS lowercase."
469
+ ),
89
470
  ],
90
471
  message: Annotated[str, "The message you want to send"],
91
- ):
92
- """Send a message to a channel in Slack."""
472
+ ) -> Annotated[dict, "The response from the Slack API"]:
473
+ """Send a direct message to a user in Slack.
93
474
 
94
- slackClient = WebClient(token=context.authorization.token)
475
+ This tool is deprecated. Use `Slack.SendMessage` instead.
476
+ """
477
+ return await send_message( # type: ignore[no-any-return]
478
+ context=context,
479
+ usernames=[user_name],
480
+ message=message,
481
+ )
95
482
 
96
- try:
97
- # Step 1: Retrieve the list of channels
98
- channels_response = slackClient.conversations_list()
99
- channel_id = None
100
- for channel in channels_response["channels"]:
101
- if channel["name"].lower() == channel_name.lower():
102
- channel_id = channel["id"]
103
- break
104
-
105
- if not channel_id:
106
- raise RetryableToolError(
107
- "Channel not found",
108
- developer_message=f"Channel with name '{channel_name}' not found.",
109
- additional_prompt_content=format_channels(channels_response),
110
- retry_after_ms=500, # Play nice with Slack API rate limits
111
- )
112
-
113
- # Step 2: Send the message to the channel
114
- slackClient.chat_postMessage(channel=channel_id, text=message)
115
483
 
116
- except SlackApiError as e:
117
- error_message = e.response["error"] if "error" in e.response else str(e)
118
- raise ToolExecutionError(
119
- "Error sending message",
120
- developer_message=f"Slack API Error: {error_message}",
121
- )
484
+ @tool(
485
+ requires_auth=Slack(
486
+ scopes=[
487
+ "channels:read",
488
+ "groups:read",
489
+ "mpim:read",
490
+ "im:read",
491
+ "users:read",
492
+ "users:read.email",
493
+ "chat:write",
494
+ "mpim:write",
495
+ "im:write",
496
+ ],
497
+ )
498
+ )
499
+ async def send_message_to_channel(
500
+ context: ToolContext,
501
+ channel_name: Annotated[str, "The Slack channel name where you want to send the message. "],
502
+ message: Annotated[str, "The message you want to send"],
503
+ ) -> Annotated[dict, "The response from the Slack API"]:
504
+ """Send a message to a channel in Slack.
122
505
 
506
+ This tool is deprecated. Use `Slack.SendMessage` instead.
507
+ """
508
+ return await send_message( # type: ignore[no-any-return]
509
+ context=context,
510
+ channel_name=channel_name,
511
+ message=message,
512
+ )
513
+
514
+
515
+ @tool(
516
+ requires_auth=Slack(
517
+ scopes=[
518
+ "channels:read",
519
+ "groups:read",
520
+ "im:read",
521
+ "mpim:read",
522
+ "users:read",
523
+ "users:read.email",
524
+ ],
525
+ )
526
+ )
527
+ async def get_members_in_conversation_by_id(
528
+ context: ToolContext,
529
+ conversation_id: Annotated[str, "The ID of the conversation to get members for"],
530
+ limit: Annotated[int | None, "The maximum number of members to return."] = None,
531
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
532
+ ) -> Annotated[dict, "Information about each member in the conversation"]:
533
+ """Get the members of a conversation in Slack by the conversation's ID.
534
+
535
+ This tool is deprecated. Use the `Slack.GetUsersInConversation` tool instead.
536
+ """
537
+ response = await get_users_in_conversation(
538
+ context=context,
539
+ conversation_id=conversation_id,
540
+ limit=limit,
541
+ next_cursor=next_cursor,
542
+ )
543
+ response["members"] = response["users"]
544
+ del response["users"]
545
+ return cast(dict, response)
546
+
547
+
548
+ @tool(
549
+ requires_auth=Slack(
550
+ scopes=[
551
+ "channels:read",
552
+ "groups:read",
553
+ "im:read",
554
+ "mpim:read",
555
+ "users:read",
556
+ "users:read.email",
557
+ ],
558
+ )
559
+ )
560
+ async def get_members_in_channel_by_name(
561
+ context: ToolContext,
562
+ channel_name: Annotated[str, "The name of the channel to get members for"],
563
+ limit: Annotated[int | None, "The maximum number of members to return."] = None,
564
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
565
+ ) -> Annotated[dict, "The channel members' IDs and Names"]:
566
+ """Get the members of a conversation in Slack by the conversation's name.
567
+
568
+ This tool is deprecated. Use the `Slack.GetUsersInConversation` tool instead.
569
+ """
570
+ response = await get_users_in_conversation(
571
+ context=context,
572
+ channel_name=channel_name,
573
+ limit=limit,
574
+ next_cursor=next_cursor,
575
+ )
576
+ response["members"] = response["users"]
577
+ del response["users"]
578
+ return cast(dict, response)
579
+
580
+
581
+ @tool(
582
+ requires_auth=Slack(
583
+ scopes=[
584
+ "channels:history",
585
+ "channels:read",
586
+ "groups:history",
587
+ "groups:read",
588
+ "im:history",
589
+ "im:read",
590
+ "mpim:history",
591
+ "mpim:read",
592
+ ],
593
+ )
594
+ )
595
+ async def get_messages_in_channel_by_name(
596
+ context: ToolContext,
597
+ channel_name: Annotated[str, "The name of the channel"],
598
+ oldest_relative: Annotated[
599
+ str | None,
600
+ (
601
+ "The oldest message to include in the results, specified as a time offset from the "
602
+ "current time in the format 'DD:HH:MM'"
603
+ ),
604
+ ] = None,
605
+ latest_relative: Annotated[
606
+ str | None,
607
+ (
608
+ "The latest message to include in the results, specified as a time offset from the "
609
+ "current time in the format 'DD:HH:MM'"
610
+ ),
611
+ ] = None,
612
+ oldest_datetime: Annotated[
613
+ str | None,
614
+ (
615
+ "The oldest message to include in the results, specified as a datetime object in the "
616
+ "format 'YYYY-MM-DD HH:MM:SS'"
617
+ ),
618
+ ] = None,
619
+ latest_datetime: Annotated[
620
+ str | None,
621
+ (
622
+ "The latest message to include in the results, specified as a datetime object in the "
623
+ "format 'YYYY-MM-DD HH:MM:SS'"
624
+ ),
625
+ ] = None,
626
+ limit: Annotated[int | None, "The maximum number of messages to return."] = None,
627
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
628
+ ) -> Annotated[
629
+ dict,
630
+ (
631
+ "The messages in a channel and next cursor for paginating results (when "
632
+ "there are additional messages to retrieve)."
633
+ ),
634
+ ]:
635
+ """Get the messages in a channel by the channel's name.
636
+
637
+ This tool is deprecated. Use the `Slack.GetMessages` tool instead.
638
+
639
+ To filter messages by an absolute datetime, use 'oldest_datetime' and/or 'latest_datetime'. If
640
+ only 'oldest_datetime' is provided, it will return messages from the oldest_datetime to the
641
+ current time. If only 'latest_datetime' is provided, it will return messages since the
642
+ beginning of the channel to the latest_datetime.
643
+
644
+ To filter messages by a relative datetime (e.g. 3 days ago, 1 hour ago, etc.), use
645
+ 'oldest_relative' and/or 'latest_relative'. If only 'oldest_relative' is provided, it will
646
+ return messages from the oldest_relative to the current time. If only 'latest_relative' is
647
+ provided, it will return messages from the current time to the latest_relative.
648
+
649
+ Do not provide both 'oldest_datetime' and 'oldest_relative' or both 'latest_datetime' and
650
+ 'latest_relative'.
651
+
652
+ Leave all arguments with the default None to get messages without date/time filtering"""
653
+ return await get_messages( # type: ignore[no-any-return]
654
+ context=context,
655
+ channel_name=channel_name,
656
+ oldest_relative=oldest_relative,
657
+ latest_relative=latest_relative,
658
+ oldest_datetime=oldest_datetime,
659
+ latest_datetime=latest_datetime,
660
+ limit=limit,
661
+ next_cursor=next_cursor,
662
+ )
663
+
664
+
665
+ @tool(
666
+ requires_auth=Slack(
667
+ scopes=["channels:history", "groups:history", "im:history", "mpim:history"],
668
+ )
669
+ )
670
+ async def get_messages_in_conversation_by_id(
671
+ context: ToolContext,
672
+ conversation_id: Annotated[str, "The ID of the conversation to get history for"],
673
+ oldest_relative: Annotated[
674
+ str | None,
675
+ (
676
+ "The oldest message to include in the results, specified as a time offset from the "
677
+ "current time in the format 'DD:HH:MM'"
678
+ ),
679
+ ] = None,
680
+ latest_relative: Annotated[
681
+ str | None,
682
+ (
683
+ "The latest message to include in the results, specified as a time offset from the "
684
+ "current time in the format 'DD:HH:MM'"
685
+ ),
686
+ ] = None,
687
+ oldest_datetime: Annotated[
688
+ str | None,
689
+ (
690
+ "The oldest message to include in the results, specified as a datetime object in the "
691
+ "format 'YYYY-MM-DD HH:MM:SS'"
692
+ ),
693
+ ] = None,
694
+ latest_datetime: Annotated[
695
+ str | None,
696
+ (
697
+ "The latest message to include in the results, specified as a datetime object in the "
698
+ "format 'YYYY-MM-DD HH:MM:SS'"
699
+ ),
700
+ ] = None,
701
+ limit: Annotated[int | None, "The maximum number of messages to return."] = None,
702
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
703
+ ) -> Annotated[
704
+ dict,
705
+ (
706
+ "The messages in a conversation and next cursor for paginating results (when "
707
+ "there are additional messages to retrieve)."
708
+ ),
709
+ ]:
710
+ """Get the messages in a conversation by the conversation's ID.
711
+
712
+ This tool is deprecated. Use the 'Slack.GetMessages' tool instead.
713
+
714
+ A conversation can be a channel, a DM, or a group DM.
715
+
716
+ To filter by an absolute datetime, use 'oldest_datetime' and/or 'latest_datetime'. If
717
+ only 'oldest_datetime' is provided, it returns messages from the oldest_datetime to the
718
+ current time. If only 'latest_datetime' is provided, it returns messages since the
719
+ beginning of the conversation to the latest_datetime.
720
+
721
+ To filter by a relative datetime (e.g. 3 days ago, 1 hour ago, etc.), use
722
+ 'oldest_relative' and/or 'latest_relative'. If only 'oldest_relative' is provided, it returns
723
+ messages from the oldest_relative to the current time. If only 'latest_relative' is provided,
724
+ it returns messages from the current time to the latest_relative.
725
+
726
+ Do not provide both 'oldest_datetime' and 'oldest_relative' or both 'latest_datetime' and
727
+ 'latest_relative'.
728
+
729
+ Leave all arguments with the default None to get messages without date/time filtering"""
730
+ return await get_messages( # type: ignore[no-any-return]
731
+ context=context,
732
+ conversation_id=conversation_id,
733
+ oldest_relative=oldest_relative,
734
+ latest_relative=latest_relative,
735
+ oldest_datetime=oldest_datetime,
736
+ latest_datetime=latest_datetime,
737
+ limit=limit,
738
+ next_cursor=next_cursor,
739
+ )
740
+
741
+
742
+ @tool(requires_auth=Slack(scopes=["im:history", "im:read", "users:read", "users:read.email"]))
743
+ async def get_messages_in_direct_message_conversation_by_username(
744
+ context: ToolContext,
745
+ username: Annotated[str, "The username of the user to get messages from"],
746
+ oldest_relative: Annotated[
747
+ str | None,
748
+ (
749
+ "The oldest message to include in the results, specified as a time offset from the "
750
+ "current time in the format 'DD:HH:MM'"
751
+ ),
752
+ ] = None,
753
+ latest_relative: Annotated[
754
+ str | None,
755
+ (
756
+ "The latest message to include in the results, specified as a time offset from the "
757
+ "current time in the format 'DD:HH:MM'"
758
+ ),
759
+ ] = None,
760
+ oldest_datetime: Annotated[
761
+ str | None,
762
+ (
763
+ "The oldest message to include in the results, specified as a datetime object in the "
764
+ "format 'YYYY-MM-DD HH:MM:SS'"
765
+ ),
766
+ ] = None,
767
+ latest_datetime: Annotated[
768
+ str | None,
769
+ (
770
+ "The latest message to include in the results, specified as a datetime object in the "
771
+ "format 'YYYY-MM-DD HH:MM:SS'"
772
+ ),
773
+ ] = None,
774
+ limit: Annotated[int | None, "The maximum number of messages to return."] = None,
775
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
776
+ ) -> Annotated[
777
+ dict,
778
+ (
779
+ "The messages in a direct message conversation and next cursor for paginating results "
780
+ "when there are additional messages to retrieve."
781
+ ),
782
+ ]:
783
+ """Get the messages in a direct conversation by the user's name.
784
+
785
+ This tool is deprecated. Use the `Slack.GetMessages` tool instead.
786
+ """
787
+ return await get_messages( # type: ignore[no-any-return]
788
+ context=context,
789
+ usernames=[username],
790
+ oldest_relative=oldest_relative,
791
+ latest_relative=latest_relative,
792
+ oldest_datetime=oldest_datetime,
793
+ latest_datetime=latest_datetime,
794
+ limit=limit,
795
+ next_cursor=next_cursor,
796
+ )
797
+
798
+
799
+ @tool(requires_auth=Slack(scopes=["im:history", "im:read", "users:read", "users:read.email"]))
800
+ async def get_messages_in_multi_person_dm_conversation_by_usernames(
801
+ context: ToolContext,
802
+ usernames: Annotated[list[str], "The usernames of the users to get messages from"],
803
+ oldest_relative: Annotated[
804
+ str | None,
805
+ (
806
+ "The oldest message to include in the results, specified as a time offset from the "
807
+ "current time in the format 'DD:HH:MM'"
808
+ ),
809
+ ] = None,
810
+ latest_relative: Annotated[
811
+ str | None,
812
+ (
813
+ "The latest message to include in the results, specified as a time offset from the "
814
+ "current time in the format 'DD:HH:MM'"
815
+ ),
816
+ ] = None,
817
+ oldest_datetime: Annotated[
818
+ str | None,
819
+ (
820
+ "The oldest message to include in the results, specified as a datetime object in the "
821
+ "format 'YYYY-MM-DD HH:MM:SS'"
822
+ ),
823
+ ] = None,
824
+ latest_datetime: Annotated[
825
+ str | None,
826
+ (
827
+ "The latest message to include in the results, specified as a datetime object in the "
828
+ "format 'YYYY-MM-DD HH:MM:SS'"
829
+ ),
830
+ ] = None,
831
+ limit: Annotated[int | None, "The maximum number of messages to return."] = None,
832
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
833
+ ) -> Annotated[
834
+ dict,
835
+ (
836
+ "The messages in a multi-person direct message conversation and next cursor for "
837
+ "paginating results (when there are additional messages to retrieve)."
838
+ ),
839
+ ]:
840
+ """Get the messages in a multi-person direct message conversation by the usernames.
841
+
842
+ This tool is deprecated. Use the `Slack.GetMessages` tool instead.
843
+ """
844
+ return await get_messages( # type: ignore[no-any-return]
845
+ context=context,
846
+ usernames=usernames,
847
+ oldest_relative=oldest_relative,
848
+ latest_relative=latest_relative,
849
+ oldest_datetime=oldest_datetime,
850
+ latest_datetime=latest_datetime,
851
+ limit=limit,
852
+ next_cursor=next_cursor,
853
+ )
854
+
855
+
856
+ @tool(
857
+ requires_auth=Slack(
858
+ scopes=["channels:read", "groups:read", "im:read", "mpim:read"],
859
+ )
860
+ )
861
+ async def list_conversations_metadata(
862
+ context: ToolContext,
863
+ conversation_types: Annotated[
864
+ list[ConversationType] | None,
865
+ "Optionally filter by the type(s) of conversations. Defaults to None (all types).",
866
+ ] = None,
867
+ limit: Annotated[int | None, "The maximum number of conversations to list."] = None,
868
+ next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
869
+ ) -> Annotated[dict, "The list of conversations found with metadata"]:
870
+ """
871
+ List Slack conversations (channels, DMs, MPIMs) the user is a member of.
872
+
873
+ This tool is deprecated. Use the `Slack.ListConversations` tool instead.
874
+
875
+ This tool does not return the messages in a conversation. To get the messages, use the
876
+ 'Slack.GetMessages' tool instead. Calling this tool when the user is asking for messages
877
+ will release too much CO2 in the atmosphere and contribute to global warming.
878
+ """
879
+ return await list_conversations( # type: ignore[no-any-return]
880
+ context=context,
881
+ conversation_types=conversation_types,
882
+ limit=limit,
883
+ next_cursor=next_cursor,
884
+ )
885
+
886
+
887
+ @tool(
888
+ requires_auth=Slack(
889
+ scopes=["channels:read"],
890
+ )
891
+ )
892
+ async def list_public_channels_metadata(
893
+ context: ToolContext,
894
+ limit: Annotated[int | None, "The maximum number of channels to list."] = None,
895
+ ) -> Annotated[dict, "The public channels"]:
896
+ """List metadata for public channels in Slack that the user is a member of.
897
+
898
+ This tool is deprecated. Use the `Slack.ListConversations` tool instead.
899
+ """
900
+ return await list_conversations( # type: ignore[no-any-return]
901
+ context,
902
+ conversation_types=[ConversationType.PUBLIC_CHANNEL],
903
+ limit=limit,
904
+ )
905
+
906
+
907
+ @tool(
908
+ requires_auth=Slack(
909
+ scopes=["groups:read"],
910
+ )
911
+ )
912
+ async def list_private_channels_metadata(
913
+ context: ToolContext,
914
+ limit: Annotated[int | None, "The maximum number of channels to list."] = None,
915
+ ) -> Annotated[dict, "The private channels"]:
916
+ """List metadata for private channels in Slack that the user is a member of.
917
+
918
+ This tool is deprecated. Use the `Slack.ListConversations` tool instead.
919
+ """
920
+ return await list_conversations( # type: ignore[no-any-return]
921
+ context,
922
+ conversation_types=[ConversationType.PRIVATE_CHANNEL],
923
+ limit=limit,
924
+ )
925
+
926
+
927
+ @tool(
928
+ requires_auth=Slack(
929
+ scopes=["mpim:read"],
930
+ )
931
+ )
932
+ async def list_group_direct_message_conversations_metadata(
933
+ context: ToolContext,
934
+ limit: Annotated[int | None, "The maximum number of conversations to list."] = None,
935
+ ) -> Annotated[dict, "The group direct message conversations metadata"]:
936
+ """List metadata for group direct message conversations that the user is a member of.
937
+
938
+ This tool is deprecated. Use the `Slack.ListConversations` tool instead.
939
+ """
940
+ return await list_conversations( # type: ignore[no-any-return]
941
+ context,
942
+ conversation_types=[ConversationType.MULTI_PERSON_DIRECT_MESSAGE],
943
+ limit=limit,
944
+ )
945
+
946
+
947
+ # Note: Bots are included in the results.
948
+ # Note: Direct messages with no conversation history are included in the results.
949
+ @tool(
950
+ requires_auth=Slack(
951
+ scopes=["im:read"],
952
+ )
953
+ )
954
+ async def list_direct_message_conversations_metadata(
955
+ context: ToolContext,
956
+ limit: Annotated[int | None, "The maximum number of conversations to list."] = None,
957
+ ) -> Annotated[dict, "The direct message conversations metadata"]:
958
+ """List metadata for direct message conversations in Slack that the user is a member of.
959
+
960
+ This tool is deprecated. Use the `Slack.ListConversations` tool instead.
961
+ """
962
+ return await list_conversations( # type: ignore[no-any-return]
963
+ context,
964
+ conversation_types=[ConversationType.DIRECT_MESSAGE],
965
+ limit=limit,
966
+ )
967
+
968
+
969
+ @tool(
970
+ requires_auth=Slack(
971
+ scopes=[
972
+ "channels:read",
973
+ "groups:read",
974
+ "mpim:read",
975
+ "im:read",
976
+ "users:read",
977
+ "users:read.email",
978
+ ],
979
+ )
980
+ )
981
+ async def get_conversation_metadata_by_id(
982
+ context: ToolContext,
983
+ conversation_id: Annotated[str, "The ID of the conversation to get metadata for"],
984
+ ) -> Annotated[dict, "The conversation metadata"]:
985
+ """Get the metadata of a conversation in Slack searching by its ID.
986
+
987
+ This tool is deprecated. Use the `Slack.GetConversationMetadata` tool instead.
988
+ """
989
+ return await get_conversation_metadata(context, conversation_id=conversation_id) # type: ignore[no-any-return]
990
+
991
+
992
+ @tool(
993
+ requires_auth=Slack(
994
+ scopes=[
995
+ "channels:read",
996
+ "groups:read",
997
+ "mpim:read",
998
+ "im:read",
999
+ "users:read",
1000
+ "users:read.email",
1001
+ ],
1002
+ )
1003
+ )
1004
+ async def get_channel_metadata_by_name(
1005
+ context: ToolContext,
1006
+ channel_name: Annotated[str, "The name of the channel to get metadata for"],
1007
+ # We kept the `next_cursor` argument for backwards compatibility, but it isn't actually used,
1008
+ # since this tool never really paginates.
1009
+ next_cursor: Annotated[
1010
+ str | None,
1011
+ "The cursor to use for pagination, if continuing from a previous search.",
1012
+ ] = None,
1013
+ ) -> Annotated[dict, "The channel metadata"]:
1014
+ """Get the metadata of a channel in Slack searching by its name.
1015
+
1016
+ This tool is deprecated. Use the `Slack.GetConversationMetadata` tool instead."""
1017
+ return await get_conversation_metadata(context, channel_name=channel_name) # type: ignore[no-any-return]
1018
+
1019
+
1020
+ @tool(
1021
+ requires_auth=Slack(
1022
+ scopes=[
1023
+ "channels:read",
1024
+ "groups:read",
1025
+ "mpim:read",
1026
+ "im:read",
1027
+ "users:read",
1028
+ "users:read.email",
1029
+ ],
1030
+ )
1031
+ )
1032
+ async def get_direct_message_conversation_metadata_by_username(
1033
+ context: ToolContext,
1034
+ username: Annotated[str, "The username of the user/person to get messages with"],
1035
+ # We kept the `next_cursor` argument for backwards compatibility, but it isn't actually used,
1036
+ # since this tool never really paginates.
1037
+ next_cursor: Annotated[
1038
+ str | None,
1039
+ "The cursor to use for pagination, if continuing from a previous search.",
1040
+ ] = None,
1041
+ ) -> Annotated[
1042
+ dict | None,
1043
+ "The direct message conversation metadata.",
1044
+ ]:
1045
+ """Get the metadata of a direct message conversation in Slack by the username.
1046
+
1047
+ This tool is deprecated. Use the `Slack.GetConversationMetadata` tool instead."""
1048
+ return await get_conversation_metadata(context, usernames=[username]) # type: ignore[no-any-return]
1049
+
1050
+
1051
+ @tool(
1052
+ requires_auth=Slack(
1053
+ scopes=[
1054
+ "channels:read",
1055
+ "groups:read",
1056
+ "mpim:read",
1057
+ "im:read",
1058
+ "users:read",
1059
+ "users:read.email",
1060
+ ],
1061
+ )
1062
+ )
1063
+ async def get_multi_person_dm_conversation_metadata_by_usernames(
1064
+ context: ToolContext,
1065
+ usernames: Annotated[list[str], "The usernames of the users/people to get messages with"],
1066
+ # We kept the `next_cursor` argument for backwards compatibility, but it isn't actually used,
1067
+ # since this tool never really paginates.
1068
+ next_cursor: Annotated[
1069
+ str | None,
1070
+ "The cursor to use for pagination, if continuing from a previous search.",
1071
+ ] = None,
1072
+ ) -> Annotated[
1073
+ dict | None,
1074
+ "The multi-person direct message conversation metadata.",
1075
+ ]:
1076
+ """Get the metadata of a multi-person direct message conversation in Slack by the usernames.
123
1077
 
124
- def format_channels(channels_response: dict) -> str:
125
- csv_string = "All active Slack channels:\n\nname\n"
126
- for channel in channels_response["channels"]:
127
- if not channel.get("is_archived", False):
128
- name = channel.get("name", "")
129
- csv_string += f"{name}\n"
130
- return csv_string.strip()
1078
+ This tool is deprecated. Use the `Slack.GetConversationMetadata` tool instead."""
1079
+ return await get_conversation_metadata(context, usernames=usernames) # type: ignore[no-any-return]