-
Notifications
You must be signed in to change notification settings - Fork 0
Add Slack IO module to SDK #86
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
Co-Authored-By: [email protected] <[email protected]>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
WalkthroughSlack messaging support has been integrated into the Agentuity SDK. This includes new data structures and interfaces for Slack events and messages, a Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AgentuitySDK
participant SlackAPI
Client->>AgentuitySDK: Sends Slack event/message payload (raw bytes)
AgentuitySDK->>AgentuitySDK: parse_slack(data)
AgentuitySDK->>AgentuitySDK: Create Slack object
Client->>AgentuitySDK: Calls Slack.send_reply(req, ctx, reply)
AgentuitySDK->>SlackAPI: POST reply via Agentuity Slack API
SlackAPI-->>AgentuitySDK: Response (success/failure)
AgentuitySDK-->>Client: Reply sent status
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–20 minutes Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 5
🧹 Nitpick comments (2)
agentuity/server/types.py (1)
96-100
: Add a docstring for the newslack()
accessorAll other accessors (
email()
,discord()
,telegram()
) are self-documenting via naming, but a short description of the expected return (SlackMessageInterface
) would improve IDE hints and keep the interface symmetrical.agentuity/io/slack.py (1)
168-176
:thread_ts
property ignores slash-command payloadsWhen handling slash-command (
slack-message
) inputs,thread_ts
might be present on the root payload, but the current implementation only inspects event payloads.
Consider:if self._message_payload: return getattr(self._message_payload, "thread_ts", None)to provide consistent access across message types.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
agentuity/io/__init__.py
(1 hunks)agentuity/io/slack.py
(1 hunks)agentuity/server/types.py
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
agentuity/server/types.py (1)
agentuity/io/slack.py (7)
message_type
(116-117)token
(120-121)team_id
(124-125)text
(186-191)user
(194-199)channel
(202-207)send_reply
(335-390)
agentuity/io/slack.py (1)
agentuity/server/types.py (26)
AgentRequestInterface
(21-39)AgentContextInterface
(42-56)SlackMessageInterface
(310-349)agentId
(45-46)channel
(338-339)text
(70-71)text
(162-163)text
(275-276)text
(328-329)data
(24-25)data
(109-110)data
(120-121)get
(38-39)user
(333-334)token
(318-319)team_id
(323-324)message_type
(313-314)metadata
(34-35)api_key
(55-56)base_url
(50-51)send_reply
(220-226)send_reply
(231-234)send_reply
(284-291)send_reply
(304-307)send_reply
(342-349)send_reply
(354-357)
🔇 Additional comments (2)
agentuity/io/__init__.py (1)
2-4
: Public-API addition looks good
Slack
andparse_slack
are properly re-exported; nothing else to address here.agentuity/io/slack.py (1)
369-389
: OpenTelemetry status API changed in 1.17+
trace.Status
is deprecated; use:from opentelemetry.trace import Status, StatusCode ... span.set_status(Status(StatusCode.OK))to avoid runtime warnings / forward-compatibility breaks.
class SlackReplyPayload: | ||
""" | ||
Payload structure for Slack reply requests. | ||
""" | ||
agentId: str | ||
channel: str | ||
text: Optional[str] = None | ||
blocks: Optional[str] = None | ||
thread_ts: Optional[str] = None | ||
|
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.
🛠️ Refactor suggestion
Allow structured blocks
in the reply payload
blocks
can be a list of block objects, yet the dataclass types it as Optional[str]
.
Typing it as Optional[list | dict | str]
(or Any
) prevents mypy errors when callers pass the canonical list form.
- blocks: Optional[str] = None
+ blocks: Optional[Union[list, dict, str]] = None
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In agentuity/io/slack.py around lines 17 to 26, the SlackReplyPayload class
types the blocks attribute as Optional[str], but blocks can be a list or dict
representing structured block objects. Change the type annotation of blocks to
Optional[list | dict | str] or use Any to correctly reflect the possible types
and prevent mypy type errors when callers pass the canonical list form.
async def send_reply( | ||
self, | ||
req: AgentRequestInterface, | ||
ctx: AgentContextInterface, | ||
reply: Union[str, dict], | ||
options: dict = {} | ||
) -> None: |
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.
Replace mutable default argument
options: dict = {}
risks state bleed across invocations. Use None
sentinel instead:
- options: dict = {}
+ options: dict | None = None
and initialise with options = options or {}
.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
async def send_reply( | |
self, | |
req: AgentRequestInterface, | |
ctx: AgentContextInterface, | |
reply: Union[str, dict], | |
options: dict = {} | |
) -> None: | |
async def send_reply( | |
self, | |
req: AgentRequestInterface, | |
ctx: AgentContextInterface, | |
reply: Union[str, dict], | |
options: dict | None = None | |
) -> None: |
🤖 Prompt for AI Agents
In agentuity/io/slack.py around lines 335 to 341, the method send_reply uses a
mutable default argument options: dict = {}, which can cause state to persist
across calls. Change the default value of options to None and inside the method
initialize it with options = options or {} to avoid shared mutable state.
async def parse_slack(data: bytes, message_type: str = 'slack-event') -> Slack: | ||
""" | ||
Parse a slack message from bytes and return a Slack object. | ||
|
||
Args: | ||
data: The raw bytes data containing the Slack message. | ||
message_type: The type of message ('slack-event' or 'slack-message'). | ||
|
||
Returns: | ||
A Slack object representing the parsed message. | ||
|
||
Raises: | ||
ValueError: If the data cannot be parsed as a valid Slack message. | ||
""" | ||
try: | ||
msg_data = json.loads(data.decode('utf-8')) | ||
|
||
if message_type == 'slack-event': | ||
payload = SlackEventPayload(msg_data) | ||
if not payload.token or not payload.type or not payload.team_id: | ||
raise ValueError("Invalid Slack event: missing required fields") | ||
else: | ||
payload = SlackMessagePayload(msg_data) | ||
if not payload.token or not payload.team_id or not payload.channel_id or not payload.user_id: | ||
raise ValueError("Invalid Slack message: missing required fields") | ||
|
||
return Slack(payload, message_type) |
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.
🛠️ Refactor suggestion
parse_slack
assumes JSON – slash commands arrive url-encoded
Slash-command requests (application/x-www-form-urlencoded
) will fail JSON decoding.
Either branch on message_type
or attempt urllib.parse.parse_qs
fallback before raising.
if content_type == "application/x-www-form-urlencoded":
msg_data = dict(parse_qsl(data.decode()))
🤖 Prompt for AI Agents
In agentuity/io/slack.py between lines 393 and 419, the parse_slack function
currently assumes the input data is JSON, which causes failure when handling
slash-command requests that are url-encoded. To fix this, modify the function to
check the content type or message_type and if it indicates a url-encoded form,
decode the data using urllib.parse.parse_qsl to convert it into a dictionary
before processing. This fallback should be implemented before raising a
ValueError to properly handle both JSON and url-encoded inputs.
|
||
class SlackMessageInterface(ABC): | ||
@property | ||
@abstractmethod | ||
def message_type(self) -> str: | ||
pass | ||
|
||
@property | ||
@abstractmethod | ||
def token(self) -> str: | ||
pass | ||
|
||
@property | ||
@abstractmethod | ||
def team_id(self) -> str: | ||
pass | ||
|
||
@property | ||
@abstractmethod | ||
def text(self) -> str: | ||
pass | ||
|
||
@property | ||
@abstractmethod | ||
def user(self) -> str: | ||
pass | ||
|
||
@property | ||
@abstractmethod | ||
def channel(self) -> str: | ||
pass | ||
|
||
@abstractmethod | ||
async def send_reply( | ||
self, | ||
request: "AgentRequestInterface", | ||
context: "AgentContextInterface", | ||
reply: str, | ||
options: dict = None, | ||
) -> None: | ||
pass |
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.
🛠️ Refactor suggestion
Broaden reply
type in the interface
SlackMessageInterface.send_reply()
restricts reply
to str
, yet the concrete implementation in agentuity/io/slack.py
also accepts dict
(text + blocks).
Adopt Union[str, dict]
here so code typed against the interface can legally send rich-format payloads.
- reply: str,
+ reply: Union[str, dict],
Also change the default for options
from None
instead of {}
to avoid the mutable-default trap.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
class SlackMessageInterface(ABC): | |
@property | |
@abstractmethod | |
def message_type(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def token(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def team_id(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def text(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def user(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def channel(self) -> str: | |
pass | |
@abstractmethod | |
async def send_reply( | |
self, | |
request: "AgentRequestInterface", | |
context: "AgentContextInterface", | |
reply: str, | |
options: dict = None, | |
) -> None: | |
pass | |
class SlackMessageInterface(ABC): | |
@property | |
@abstractmethod | |
def message_type(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def token(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def team_id(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def text(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def user(self) -> str: | |
pass | |
@property | |
@abstractmethod | |
def channel(self) -> str: | |
pass | |
@abstractmethod | |
async def send_reply( | |
self, | |
request: "AgentRequestInterface", | |
context: "AgentContextInterface", | |
- reply: str, | |
+ reply: Union[str, dict], | |
options: dict = None, | |
) -> None: | |
pass |
🤖 Prompt for AI Agents
In agentuity/server/types.py between lines 309 and 349, update the send_reply
method in SlackMessageInterface to accept reply as Union[str, dict] instead of
just str to allow rich-format payloads, and change the default value of options
parameter from an empty dict to None to avoid mutable default argument issues.
class SlackServiceInterface(ABC): | ||
@abstractmethod | ||
async def send_reply( | ||
self, agent_id: str, channel: str, text: str, options: dict = None | ||
) -> None: | ||
pass |
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.
🛠️ Refactor suggestion
Align service signature with message interface
For the same reason as above, allow rich text
/blocks
objects:
- async def send_reply(
- self, agent_id: str, channel: str, text: str, options: dict = None
+ async def send_reply(
+ self, agent_id: str, channel: str, text: Union[str, dict], options: dict | None = None
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
class SlackServiceInterface(ABC): | |
@abstractmethod | |
async def send_reply( | |
self, agent_id: str, channel: str, text: str, options: dict = None | |
) -> None: | |
pass | |
class SlackServiceInterface(ABC): | |
@abstractmethod | |
async def send_reply( | |
self, agent_id: str, channel: str, text: Union[str, dict], options: dict | None = None | |
) -> None: | |
pass |
🤖 Prompt for AI Agents
In agentuity/server/types.py around lines 352 to 357, the send_reply method in
SlackServiceInterface currently only accepts a plain text string for the
message. To align with the message interface and support rich content, modify
the method signature to accept both rich text and blocks objects, allowing more
flexible message formatting. Update the parameters to include options for rich
text or blocks instead of just a plain text string.
- Add missing slack() method to Data class following same pattern as telegram(), email(), discord() - This resolves the 'Can't instantiate abstract class Data with abstract method slack' errors - Fixes 57 test failures and 33 errors in CI for Python 3.11 and 3.12 Co-Authored-By: [email protected] <[email protected]>
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.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
agentuity/server/data.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
agentuity/server/data.py (2)
agentuity/server/types.py (1)
slack
(98-99)agentuity/io/slack.py (1)
parse_slack
(393-423)
🪛 GitHub Check: Deploy
agentuity/server/data.py
[failure] 418-418: Ruff (F821)
agentuity/server/data.py:418:31: F821 Undefined name SlackMessageInterface
🪛 GitHub Actions: Run Tests
agentuity/server/data.py
[error] 418-418: ESLint: Undefined name SlackMessageInterface
(F821) at line 418.
🪛 GitHub Actions: Build
agentuity/server/data.py
[error] 418-418: Ruff: Undefined name SlackMessageInterface
(F821)
@@ -414,6 +414,12 @@ | |||
|
|||
data_bytes = await self.binary() | |||
return await parse_telegram(data_bytes) | |||
|
|||
async def slack(self) -> "SlackMessageInterface": |
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.
💡 Verification agent
🧩 Analysis chain
Fix Ruff F821: import SlackMessageInterface and drop quotes in annotation
Ruff flags SlackMessageInterface as undefined. Import it from agentuity.server.types and use a non-string annotation to satisfy the linter.
Apply:
- async def slack(self) -> "SlackMessageInterface":
+ async def slack(self) -> SlackMessageInterface:
Also add the missing import at the top of this file:
from agentuity.server.types import (
DataInterface,
EmailInterface,
DiscordMessageInterface,
TelegramMessageInterface,
+ SlackMessageInterface,
)
Run to verify the import and annotation are present:
🏁 Script executed:
#!/bin/bash
rg -n $'from agentuity.server.types import \\([\\s\\S]*SlackMessageInterface' agentuity/server/data.py
rg -n $'^\\s*async def slack\\(self\\) -> SlackMessageInterface' agentuity/server/data.py
Length of output: 188
🏁 Script executed:
#!/bin/bash
# Check all occurrences of SlackMessageInterface in the file
rg -n 'SlackMessageInterface' agentuity/server/data.py
# Show the async def slack snippet with context
rg -n '^ *async def slack' -A2 agentuity/server/data.py
# Show the import block from agentuity.server.types
rg -n '^from agentuity.server.types import' -A5 agentuity/server/data.py
Length of output: 517
Add missing import and update annotation for SlackMessageInterface
The slack
method is still using a string annotation and SlackMessageInterface
isn’t imported. Please update as follows in agentuity/server/data.py
:
• At the top import block, include SlackMessageInterface
:
from agentuity.server.types import (
DataInterface,
EmailInterface,
DiscordMessageInterface,
TelegramMessageInterface,
+ SlackMessageInterface,
)
• Change the method signature to use the real type instead of a string:
- async def slack(self) -> "SlackMessageInterface":
+ async def slack(self) -> SlackMessageInterface:
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
async def slack(self) -> "SlackMessageInterface": | |
# ── at the top of agentuity/server/data.py ── | |
from agentuity.server.types import ( | |
DataInterface, | |
EmailInterface, | |
DiscordMessageInterface, | |
TelegramMessageInterface, | |
SlackMessageInterface, | |
) | |
# ── update the method signature at line 418 ── | |
async def slack(self) -> SlackMessageInterface: |
🧰 Tools
🪛 GitHub Check: Deploy
[failure] 418-418: Ruff (F821)
agentuity/server/data.py:418:31: F821 Undefined name SlackMessageInterface
🪛 GitHub Actions: Run Tests
[error] 418-418: ESLint: Undefined name SlackMessageInterface
(F821) at line 418.
🪛 GitHub Actions: Build
[error] 418-418: Ruff: Undefined name SlackMessageInterface
(F821)
🤖 Prompt for AI Agents
In agentuity/server/data.py at line 418, the slack method uses a string
annotation for SlackMessageInterface and the interface is not imported. Fix this
by adding an import statement for SlackMessageInterface at the top of the file
and update the method signature to use SlackMessageInterface directly as the
return type annotation instead of a string.
data_bytes = await self.binary() | ||
return await parse_slack(data_bytes) |
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.
🛠️ Refactor suggestion
Support both Slack payload types (event and message) with a safe fallback
Currently this always parses as 'slack-event', breaking 'slack-message' payloads. Try event first, then fall back to message. This matches the PR’s goal to support both payload types.
Apply:
- data_bytes = await self.binary()
- return await parse_slack(data_bytes)
+ data_bytes = await self.binary()
+ try:
+ return await parse_slack(data_bytes, 'slack-event')
+ except ValueError as e_event:
+ try:
+ return await parse_slack(data_bytes, 'slack-message')
+ except ValueError as e_msg:
+ raise ValueError(
+ f"Slack parsing failed for both event and message payloads; "
+ f"event_error={e_event}; message_error={e_msg}"
+ )
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
data_bytes = await self.binary() | |
return await parse_slack(data_bytes) | |
data_bytes = await self.binary() | |
try: | |
return await parse_slack(data_bytes, 'slack-event') | |
except ValueError as e_event: | |
try: | |
return await parse_slack(data_bytes, 'slack-message') | |
except ValueError as e_msg: | |
raise ValueError( | |
f"Slack parsing failed for both event and message payloads; " | |
f"event_error={e_event}; message_error={e_msg}" | |
) |
🤖 Prompt for AI Agents
In agentuity/server/data.py around lines 421 to 422, the code currently parses
the payload only as a 'slack-event', which fails for 'slack-message' payloads.
Modify the code to first attempt parsing as a 'slack-event', and if that fails,
safely fall back to parsing as a 'slack-message'. Implement this by trying the
event parse in a try-except block and on exception, parse as a message to
support both payload types.
- Add SlackMessageInterface to imports in data.py following existing pattern - Resolves F821 undefined name error in CI linting stage Co-Authored-By: [email protected] <[email protected]>
Add Slack IO module to SDK
Summary
Implements Python Slack IO functionality by porting the JavaScript implementation from
sdk-js/src/io/slack.ts
. The new module provides:slack-event
andslack-message
payload typestypes.py
for consistency with existing IO modulesThe implementation follows established patterns from existing IO modules (telegram.py, email.py, discord.py) and mirrors the JavaScript functionality including OpenTelemetry tracing, proper authentication header handling, and structured error responses.
Review & Testing Checklist for Human
/slack/reply
endpoint expects the exact payload structure being sent (agentId, channel, text, blocks, thread_ts)slack-auth-token
metadata extraction matches the actual Slack integration setupRecommended Test Plan:
parse_slack
with various payload formats/slack/reply
API and verify request structureDiagram
Notes
sdk-js/src/io/slack.ts
JavaScript implementationoptions: dict = {}
mutable default - should beoptions: dict = None
with null checktypes.py
were not addressed to avoid scope creephttpx
andopentelemetry
packages (should already be available in SDK)Summary by CodeRabbit