arcade-slack 1.0.0__py3-none-any.whl → 1.2.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.
- arcade_slack/conversation_retrieval.py +8 -18
- arcade_slack/models.py +1 -5
- arcade_slack/tools/chat.py +3 -17
- arcade_slack/tools/system_context.py +36 -0
- arcade_slack/user_retrieval.py +1 -6
- arcade_slack/who_am_i_util.py +122 -0
- {arcade_slack-1.0.0.dist-info → arcade_slack-1.2.0.dist-info}/METADATA +4 -4
- {arcade_slack-1.0.0.dist-info → arcade_slack-1.2.0.dist-info}/RECORD +10 -8
- {arcade_slack-1.0.0.dist-info → arcade_slack-1.2.0.dist-info}/WHEEL +0 -0
- {arcade_slack-1.0.0.dist-info → arcade_slack-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import cast
|
|
3
2
|
|
|
4
|
-
from arcade_tdk.errors import RetryableToolError
|
|
5
|
-
from slack_sdk.errors import SlackApiError
|
|
3
|
+
from arcade_tdk.errors import RetryableToolError
|
|
6
4
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
7
5
|
|
|
8
6
|
from arcade_slack.models import (
|
|
@@ -20,21 +18,13 @@ async def get_conversation_by_id(
|
|
|
20
18
|
conversation_id: str,
|
|
21
19
|
) -> dict:
|
|
22
20
|
"""Get metadata of a conversation in Slack by the conversation_id."""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return dict(**extract_conversation_metadata(response["channel"]))
|
|
31
|
-
|
|
32
|
-
except SlackApiError as e:
|
|
33
|
-
slack_error = cast(str, e.response.get("error", ""))
|
|
34
|
-
if "not_found" in slack_error.lower():
|
|
35
|
-
message = f"Conversation with ID '{conversation_id}' not found."
|
|
36
|
-
raise ToolExecutionError(message=message, developer_message=message)
|
|
37
|
-
raise
|
|
21
|
+
slack_client = AsyncWebClient(token=auth_token)
|
|
22
|
+
response = await slack_client.conversations_info(
|
|
23
|
+
channel=conversation_id,
|
|
24
|
+
include_locale=True,
|
|
25
|
+
include_num_members=True,
|
|
26
|
+
)
|
|
27
|
+
return dict(**extract_conversation_metadata(response["channel"]))
|
|
38
28
|
|
|
39
29
|
|
|
40
30
|
async def get_channel_by_name(
|
arcade_slack/models.py
CHANGED
|
@@ -5,7 +5,6 @@ from contextlib import suppress
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from typing import Any, Literal, TypedDict
|
|
7
7
|
|
|
8
|
-
from arcade_tdk.errors import ToolExecutionError
|
|
9
8
|
from slack_sdk.errors import SlackApiError
|
|
10
9
|
|
|
11
10
|
from arcade_slack.custom_types import (
|
|
@@ -364,7 +363,4 @@ class GetUserByEmailCaller(AbstractConcurrencySafeCoroutineCaller):
|
|
|
364
363
|
if e.response.get("error") in ["user_not_found", "users_not_found"]:
|
|
365
364
|
return {"user": None, "email": self.email}
|
|
366
365
|
else:
|
|
367
|
-
raise
|
|
368
|
-
message="Error getting user by email",
|
|
369
|
-
developer_message=f"Error getting user by email: {e.response.get('error')}",
|
|
370
|
-
)
|
|
366
|
+
raise
|
arcade_slack/tools/chat.py
CHANGED
|
@@ -3,7 +3,6 @@ from typing import Annotated, cast
|
|
|
3
3
|
from arcade_tdk import ToolContext, tool
|
|
4
4
|
from arcade_tdk.auth import Slack
|
|
5
5
|
from arcade_tdk.errors import ToolExecutionError
|
|
6
|
-
from slack_sdk.errors import SlackApiError
|
|
7
6
|
from slack_sdk.web.async_client import AsyncWebClient
|
|
8
7
|
|
|
9
8
|
from arcade_slack.constants import MAX_PAGINATION_SIZE_LIMIT
|
|
@@ -349,12 +348,7 @@ async def get_conversation_metadata(
|
|
|
349
348
|
|
|
350
349
|
slack_client = AsyncWebClient(token=auth_token)
|
|
351
350
|
|
|
352
|
-
|
|
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)
|
|
351
|
+
current_user = await slack_client.auth_test()
|
|
358
352
|
|
|
359
353
|
if current_user["user_id"] not in user_ids_list:
|
|
360
354
|
user_ids_list.append(current_user["user_id"])
|
|
@@ -367,16 +361,8 @@ async def get_conversation_metadata(
|
|
|
367
361
|
)
|
|
368
362
|
user_ids_list.extend([user["id"] for user in other_users])
|
|
369
363
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return dict(**extract_conversation_metadata(response["channel"]))
|
|
373
|
-
except SlackApiError as e:
|
|
374
|
-
message = "Failed to retrieve conversation metadata."
|
|
375
|
-
slack_error = e.response.get("error", "unknown_error")
|
|
376
|
-
raise ToolExecutionError(
|
|
377
|
-
message=message,
|
|
378
|
-
developer_message=f"{message} Slack error: '{slack_error}'",
|
|
379
|
-
)
|
|
364
|
+
response = await slack_client.conversations_open(users=user_ids_list, return_im=True)
|
|
365
|
+
return dict(**extract_conversation_metadata(response["channel"]))
|
|
380
366
|
|
|
381
367
|
|
|
382
368
|
@tool(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
from arcade_tdk import ToolContext, tool
|
|
4
|
+
from arcade_tdk.auth import Slack
|
|
5
|
+
from slack_sdk.web.async_client import AsyncWebClient
|
|
6
|
+
|
|
7
|
+
from arcade_slack.who_am_i_util import build_who_am_i_response
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@tool(
|
|
11
|
+
requires_auth=Slack(
|
|
12
|
+
scopes=[
|
|
13
|
+
"users:read",
|
|
14
|
+
"users:read.email",
|
|
15
|
+
]
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
async def who_am_i(
|
|
19
|
+
context: ToolContext,
|
|
20
|
+
) -> Annotated[
|
|
21
|
+
dict[str, Any],
|
|
22
|
+
"Get comprehensive user profile and Slack information.",
|
|
23
|
+
]:
|
|
24
|
+
"""
|
|
25
|
+
Get comprehensive user profile and Slack information.
|
|
26
|
+
|
|
27
|
+
This tool provides detailed information about the authenticated user including
|
|
28
|
+
their name, email, profile picture, and other important profile details from
|
|
29
|
+
Slack services.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
auth_token = context.get_auth_token_or_empty()
|
|
33
|
+
slack_client = AsyncWebClient(token=auth_token)
|
|
34
|
+
user_info = await build_who_am_i_response(context, slack_client)
|
|
35
|
+
|
|
36
|
+
return dict(user_info)
|
arcade_slack/user_retrieval.py
CHANGED
|
@@ -114,12 +114,7 @@ async def get_single_user_by_id(auth_token: str, user_id: str) -> dict[str, Any]
|
|
|
114
114
|
if "not_found" in e.response.get("error", ""):
|
|
115
115
|
return None
|
|
116
116
|
else:
|
|
117
|
-
|
|
118
|
-
slack_error_message = e.response.get("error", "Unknown Slack API error")
|
|
119
|
-
raise ToolExecutionError(
|
|
120
|
-
message=message,
|
|
121
|
-
developer_message=f"{message}: {slack_error_message}",
|
|
122
|
-
) from e
|
|
117
|
+
raise
|
|
123
118
|
|
|
124
119
|
|
|
125
120
|
async def get_users_by_username(
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from typing import Any, TypedDict, cast
|
|
2
|
+
|
|
3
|
+
from slack_sdk.web.async_client import AsyncWebClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WhoAmIResponse(TypedDict, total=False):
|
|
7
|
+
user_id: str
|
|
8
|
+
username: str
|
|
9
|
+
display_name: str
|
|
10
|
+
real_name: str
|
|
11
|
+
email: str
|
|
12
|
+
profile_picture_url: str
|
|
13
|
+
title: str
|
|
14
|
+
phone: str
|
|
15
|
+
first_name: str
|
|
16
|
+
last_name: str
|
|
17
|
+
pronouns: str
|
|
18
|
+
status_text: str
|
|
19
|
+
status_emoji: str
|
|
20
|
+
slack_access: bool
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def build_who_am_i_response(context: Any, slack_client: AsyncWebClient) -> WhoAmIResponse:
|
|
24
|
+
"""Build complete who_am_i response from Slack APIs."""
|
|
25
|
+
auth_token = context.get_auth_token_or_empty()
|
|
26
|
+
|
|
27
|
+
auth_info = await _get_auth_info(slack_client)
|
|
28
|
+
|
|
29
|
+
user_profile = await _get_user_profile(auth_token, auth_info["user_id"])
|
|
30
|
+
|
|
31
|
+
user_info: dict[str, Any] = {}
|
|
32
|
+
user_info.update(_extract_auth_info(auth_info))
|
|
33
|
+
user_info.update(_extract_user_profile(user_profile))
|
|
34
|
+
|
|
35
|
+
return cast(WhoAmIResponse, user_info)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _get_auth_info(slack_client: AsyncWebClient) -> dict[str, Any]:
|
|
39
|
+
"""Get authentication information including user_id and team info."""
|
|
40
|
+
response = await slack_client.auth_test()
|
|
41
|
+
return cast(dict[str, Any], response.data)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def _get_user_profile(auth_token: str, user_id: str) -> dict[str, Any]:
|
|
45
|
+
"""Get detailed user profile information."""
|
|
46
|
+
slack_client = AsyncWebClient(token=auth_token)
|
|
47
|
+
response = await slack_client.users_info(user=user_id)
|
|
48
|
+
response_dict = cast(dict[str, Any], response)
|
|
49
|
+
if not response_dict.get("ok"):
|
|
50
|
+
error = response_dict.get("error", "Unknown error")
|
|
51
|
+
raise RuntimeError(f"Failed to get user info: {error}")
|
|
52
|
+
return cast(dict[str, Any], response_dict["user"])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _extract_auth_info(auth_info: dict[str, Any]) -> dict[str, Any]:
|
|
56
|
+
"""Extract authentication information."""
|
|
57
|
+
extracted = {}
|
|
58
|
+
|
|
59
|
+
if auth_info.get("user_id"):
|
|
60
|
+
extracted["user_id"] = auth_info["user_id"]
|
|
61
|
+
if auth_info.get("user"):
|
|
62
|
+
extracted["username"] = auth_info["user"]
|
|
63
|
+
|
|
64
|
+
extracted["slack_access"] = bool(auth_info.get("ok"))
|
|
65
|
+
|
|
66
|
+
return extracted
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _extract_user_profile(user_profile: dict[str, Any]) -> dict[str, Any]:
|
|
70
|
+
"""Extract user profile information."""
|
|
71
|
+
extracted = {}
|
|
72
|
+
|
|
73
|
+
if user_profile.get("real_name"):
|
|
74
|
+
extracted["real_name"] = user_profile["real_name"]
|
|
75
|
+
if user_profile.get("name"):
|
|
76
|
+
extracted["username"] = user_profile["name"]
|
|
77
|
+
|
|
78
|
+
profile = user_profile.get("profile", {})
|
|
79
|
+
extracted.update(_extract_profile_fields(profile))
|
|
80
|
+
extracted.update(_extract_profile_picture(profile))
|
|
81
|
+
|
|
82
|
+
return extracted
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _extract_profile_fields(profile: dict[str, Any]) -> dict[str, Any]:
|
|
86
|
+
"""Extract profile fields from user profile."""
|
|
87
|
+
extracted = {}
|
|
88
|
+
|
|
89
|
+
fields = [
|
|
90
|
+
("display_name", "display_name"),
|
|
91
|
+
("real_name", "real_name"),
|
|
92
|
+
("email", "email"),
|
|
93
|
+
("title", "title"),
|
|
94
|
+
("phone", "phone"),
|
|
95
|
+
("first_name", "first_name"),
|
|
96
|
+
("last_name", "last_name"),
|
|
97
|
+
("pronouns", "pronouns"),
|
|
98
|
+
("status_text", "status_text"),
|
|
99
|
+
("status_emoji", "status_emoji"),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
for profile_key, extracted_key in fields:
|
|
103
|
+
if profile.get(profile_key):
|
|
104
|
+
extracted[extracted_key] = profile[profile_key]
|
|
105
|
+
|
|
106
|
+
return extracted
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _extract_profile_picture(profile: dict[str, Any]) -> dict[str, Any]:
|
|
110
|
+
"""Extract profile picture URL from user profile."""
|
|
111
|
+
for size in [
|
|
112
|
+
"image_1024",
|
|
113
|
+
"image_512",
|
|
114
|
+
"image_192",
|
|
115
|
+
"image_72",
|
|
116
|
+
"image_48",
|
|
117
|
+
"image_32",
|
|
118
|
+
"image_24",
|
|
119
|
+
]:
|
|
120
|
+
if profile.get(size):
|
|
121
|
+
return {"profile_picture_url": profile[size]}
|
|
122
|
+
return {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arcade_slack
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Arcade.dev LLM tools for Slack
|
|
5
5
|
Author-email: Arcade <dev@arcade.dev>
|
|
6
6
|
License: Proprietary - Arcade Software License Agreement v1.0
|
|
@@ -8,12 +8,12 @@ License-File: LICENSE
|
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Requires-Dist: aiodns<2.0.0,>=1.0
|
|
10
10
|
Requires-Dist: aiohttp<4.0.0,>=3.7.3
|
|
11
|
-
Requires-Dist: arcade-tdk<3.0.0,>=2.
|
|
11
|
+
Requires-Dist: arcade-tdk<3.0.0,>=2.5.0
|
|
12
12
|
Requires-Dist: slack-sdk<4.0.0,>=3.31.0
|
|
13
13
|
Requires-Dist: typing; python_version < '3.7'
|
|
14
14
|
Provides-Extra: dev
|
|
15
|
-
Requires-Dist: arcade-ai[evals]<3.0.0,>=2.
|
|
16
|
-
Requires-Dist: arcade-serve<3.0.0,>=2.
|
|
15
|
+
Requires-Dist: arcade-ai[evals]<3.0.0,>=2.2.1; extra == 'dev'
|
|
16
|
+
Requires-Dist: arcade-serve<3.0.0,>=2.1.0; extra == 'dev'
|
|
17
17
|
Requires-Dist: mypy<1.6.0,>=1.5.1; extra == 'dev'
|
|
18
18
|
Requires-Dist: pre-commit<3.5.0,>=3.4.0; extra == 'dev'
|
|
19
19
|
Requires-Dist: pytest-asyncio<0.25.0,>=0.24.0; extra == 'dev'
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
arcade_slack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
arcade_slack/constants.py,sha256=ANAZOmNSqrbZHDqEvQfau7_91if9EeKV-G5D0VBB9vo,524
|
|
3
|
-
arcade_slack/conversation_retrieval.py,sha256=
|
|
3
|
+
arcade_slack/conversation_retrieval.py,sha256=VKc0Cc0k4UZ0OON_1l-HxWRzk_nSCzgoP5lgGyUxLFs,1902
|
|
4
4
|
arcade_slack/critics.py,sha256=Sx0nOmvym4LFNX1gZ34ndHkAKAB--X1zRPePcbiVzDo,1209
|
|
5
5
|
arcade_slack/custom_types.py,sha256=-23JnfXjMQ-H8CyRL3CEv9opPMySox1KINH5rrPIOt0,928
|
|
6
6
|
arcade_slack/exceptions.py,sha256=YQ4CTa1LbR-G7sjnQM8LB7LruxZDqPzvo-cptFYW7E8,385
|
|
7
7
|
arcade_slack/message_retrieval.py,sha256=XdzWLJdKtc5DL7H_qQBei6qJ-rz2uGs-Q0HfYqx7EG4,2496
|
|
8
|
-
arcade_slack/models.py,sha256
|
|
9
|
-
arcade_slack/user_retrieval.py,sha256=
|
|
8
|
+
arcade_slack/models.py,sha256=R0dze0YMQW_v_GqarA2F3ZMR3ueN_ant9K855N9uqlE,11958
|
|
9
|
+
arcade_slack/user_retrieval.py,sha256=F-ChLGHYFFyEnHVZnoSeGq6Gh-NZGda_pYn2Rc9oMv4,6464
|
|
10
10
|
arcade_slack/utils.py,sha256=z6G7GCDXn_7_TshQRx-rnhgmQG8eCQSND5It9_uvhmE,21776
|
|
11
|
+
arcade_slack/who_am_i_util.py,sha256=Nf6EdRiURva5jMhVx1lFOoKQ-2SuMwjXYbPCmtj6Zcw,3670
|
|
11
12
|
arcade_slack/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
arcade_slack/tools/chat.py,sha256
|
|
13
|
+
arcade_slack/tools/chat.py,sha256=-HvXuMTsW095DhtkhMINXmkdazbBdTZvUhaSx0BEIDw,37488
|
|
14
|
+
arcade_slack/tools/system_context.py,sha256=l2gZh_WhrIVL2LzttGAz5yiv0Cl6q_6AFyBBUBskz3E,961
|
|
13
15
|
arcade_slack/tools/users.py,sha256=Kw3MWNOwuUY17X5HGf3JMAH_mfloLRdRzXSBC7Zo7ug,4233
|
|
14
|
-
arcade_slack-1.
|
|
15
|
-
arcade_slack-1.
|
|
16
|
-
arcade_slack-1.
|
|
17
|
-
arcade_slack-1.
|
|
16
|
+
arcade_slack-1.2.0.dist-info/METADATA,sha256=RRPv1QPYgDO-Cn-b9udf55g-qtM3_3qOhP0kJY3Xssw,1015
|
|
17
|
+
arcade_slack-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
arcade_slack-1.2.0.dist-info/licenses/LICENSE,sha256=ixeE7aL9b2B-_ZYHTY1vQcJB4NufKeo-LWwKNObGDN0,1960
|
|
19
|
+
arcade_slack-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|