-
Notifications
You must be signed in to change notification settings - Fork 38
Slack reply tools (by Shub) #392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅ 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice first toolkit contribution.
Additionally, this PR exposed some useful data that is missing in the response of existing tools like get_messages_in_direct_message_conversation_by_username
. They should be returning the channel ID. Otherwise, flows similar to 'get my latest dm from personA' --> 'reply to their message in a thread' is not straightforward.
async def get_thread_replies( | ||
context: ToolContext, | ||
channel_id: Annotated[str, "The ID of the channel/conversation containing the thread."], | ||
thread_ts: Annotated[str, "The timestamp ('ts') of the parent message of the thread."], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's help out the caller by specifying the format of a thread_ts in the annotation.
From what I can tell, thread_ts is a unix-epoch timestamp with microsecond precision. e.g., <unix_seconds>.<microsecond_fractions>
.
Consider adding thread_ts validation in a helper func.
"1683041445.123456", # valid
"1609459200.000100", # valid
"1715024400.000789", # valid
"1683041445.12345", # invalid: 5 fractional digits
"1683041445.1234567", # invalid: 7 fractional digits
"abc.defghi", # invalid: not numeric
1683041445.123456, # invalid: not a string
"A list of message objects representing the replies in the thread (excluding the parent message).", | ||
]: | ||
"""Fetches replies for a specific thread in a Slack conversation.""" | ||
token = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can also use Arcade SDK's get_auth_token_or_empty
. (wasn't introduced until arcade-ai v1.0.5 which was after the majority of the slack toolkit was written)
token = context.get_auth_token_or_empty()
context.authorization.token if context.authorization and context.authorization.token else "" | ||
) | ||
if not token: | ||
# This should generally not happen if requires_auth is working, but good practice to check. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, this would be unexpected
result = await client.conversations_replies( | ||
channel=channel_id, | ||
ts=thread_ts, | ||
limit=min(limit, 200), # Ensure limit doesn't exceed API max |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: protect against non-positive too
|
||
messages = result.get("messages", []) | ||
# The first message in the replies is the parent message, skip it. | ||
replies = messages[1:] if messages else [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: messages[1:]
already returns []
if messages is falsey - or in other words, slicing an empty list already returns an empty list.
f"{conversation['id']} ({conversation.get('name', 'DM')})" | ||
for conversation in conversations.get("conversations", []) | ||
) | ||
raise RetryableToolError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI (no action needed): we are in discussion of pivoting away from RetryableToolError since it complicates the flow for direct tool calling (client.tools.execute(...)
).
thread_ts: Annotated[str, "The timestamp ('ts') of the parent message of the thread."], | ||
limit: Annotated[int, "The maximum number of replies to return (max 200)."] = 200, | ||
) -> Annotated[ | ||
list[dict], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is an unsupported return type for Arcade tools (TODO!) so the tool will fail with ToolCallOutput has validation errors
if you execute this tool.
For now, wrap the existing list in a dictionary: {"replies": enriched_replies}
and update the tool's return type hint.
return { | ||
"ok": True, | ||
"ts": result.get("ts"), | ||
"channel": result.get("channel"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very small nit: consider naming the key 'channel_id' since that is what the input parameter is called.
"ok": True, | ||
"ts": result.get("ts"), | ||
"channel": result.get("channel"), | ||
"message_details": result.get("message"), # Includes text, user, etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could benefit from the enrich_message_datetime
helper
5879f88
to
7fe2799
Compare
@shubcodes I'm moving your contributions to the Slack toolkit to a separate PR. It's published as a separate package and it helps to keep things isolated.