Skip to content

Commit 2f0633d

Browse files
committed
feature(slack-app): implement support for slash commands
1 parent a69b6db commit 2f0633d

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

llmstack/apps/handlers/slack_app.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,10 @@ def _get_slack_app_session_id(self, slack_request_payload):
120120

121121
def _get_input_data(self):
122122
slack_request_payload = self.request.data
123-
124-
slack_message_type = slack_request_payload["type"]
125-
if slack_message_type == "url_verification":
123+
slack_request_type = slack_request_payload.get("type")
124+
if slack_request_type == "url_verification":
126125
return {"input": {"challenge": slack_request_payload["challenge"]}}
127-
elif slack_message_type == "event_callback":
126+
elif slack_request_type == "event_callback":
128127
payload = process_slack_message_text(
129128
slack_request_payload["event"]["text"],
130129
)
@@ -148,6 +147,31 @@ def _get_input_data(self):
148147
),
149148
},
150149
}
150+
# If the request is a command, then the payload will be in the form of a command
151+
elif slack_request_payload.get("command"):
152+
payload = process_slack_message_text(
153+
slack_request_payload["text"],
154+
)
155+
return {
156+
"input": {
157+
"text": slack_request_payload["text"],
158+
"user": slack_request_payload["user_id"],
159+
"slack_user_email": self._slack_user_email,
160+
"token": slack_request_payload["token"],
161+
"team_id": slack_request_payload["team_id"],
162+
"api_app_id": slack_request_payload["api_app_id"],
163+
"team": slack_request_payload["team_id"],
164+
"channel": slack_request_payload["channel_id"],
165+
"text-type": "command",
166+
"ts": "",
167+
**dict(
168+
zip(
169+
list(map(lambda x: x["name"], self.app_data["input_fields"])),
170+
[payload] * len(self.app_data["input_fields"]),
171+
),
172+
),
173+
},
174+
}
151175
else:
152176
raise Exception("Invalid Slack message type")
153177

@@ -203,8 +227,14 @@ def _is_app_accessible(self):
203227
is_valid_app_token = self.request.data.get("token") == self.slack_config.get("verification_token")
204228
is_valid_app_id = self.request.data.get("api_app_id") == self.slack_config.get("app_id")
205229

230+
is_valid_slash_command = False
231+
if self.request.data.get("command") and self.slack_config.get("slash_command_name"):
232+
request_slash_command = self.request.data.get("command")
233+
configured_slash_command = self.slack_config.get("slash_command_name")
234+
is_valid_slash_command = request_slash_command == configured_slash_command
235+
206236
# Validate that the app token, app ID and the request type are all valid.
207-
if not (is_valid_app_token and is_valid_app_id and is_valid_request_type):
237+
if not (is_valid_app_token and is_valid_app_id and (is_valid_request_type or is_valid_slash_command)):
208238
raise Exception("Invalid Slack request")
209239

210240
# URL verification is allowed without any further checks

llmstack/apps/integration_configs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class SlackIntegrationConfig(AppIntegrationConfig):
5151
config_type = "slack"
5252
is_encrypted = True
5353
app_id: str = ""
54+
slash_command_name: str = ""
55+
slash_command_description: str = ""
5456
bot_token: str = ""
5557
verification_token: str = ""
5658
signing_secret: str = ""

llmstack/apps/types/slack.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ class SlackAppConfigSchema(BaseSchema):
3232
widget="password",
3333
description="Signing secret to verify the request from Slack. This secret is available at Features > Basic Information in your app page. More details https://api.slack.com/authentication/verifying-requests-from-slack",
3434
)
35+
slash_command_name: str = Field(
36+
default="promptly",
37+
title="Slash Command Name",
38+
description="The name of the slash command that will be used to trigger the app. Slack commands must start with a slash, be all lowercase, and contain no spaces. Examples: /deploy, /ack, /weather. Ensure that the bot has access to the commands scope under Features > OAuth & Permissions.",
39+
required=True,
40+
)
41+
slash_command_description: str = Field(
42+
title="Slash Command Description",
43+
default="Promptly App",
44+
description="The description of the slash command that will be used to trigger the app.",
45+
required=True,
46+
)
3547

3648

3749
class SlackApp(AppTypeInterface[SlackAppConfigSchema]):

llmstack/client/src/components/apps/AppSlackConfigEditor.jsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ const slackConfigSchema = {
3232
description:
3333
"Signing secret to verify the request from Slack. This secret is available at Features > Basic Information in your app page. More details https://api.slack.com/authentication/verifying-requests-from-slack",
3434
},
35+
slash_command_name: {
36+
type: "string",
37+
title: "Slash Command Name",
38+
description:
39+
"The name of the slash command that will be used to trigger the app. Slack commands must start with a slash, be all lowercase, and contain no spaces. Examples: /deploy, /ack, /weather. Ensure that the bot has access to the commands scope under Features > OAuth & Permissions.",
40+
},
41+
slash_command_description: {
42+
type: "string",
43+
title: "Slash Command Description",
44+
description:
45+
"The description of the slash command that will be used to trigger the app.",
46+
},
3547
},
3648
required: ["app_id", "bot_token", "verification_token", "signing_secret"],
3749
};
@@ -41,6 +53,14 @@ const slackConfigUISchema = {
4153
"ui:widget": "text",
4254
"ui:emptyValue": "",
4355
},
56+
slash_command_name: {
57+
"ui:widget": "text",
58+
"ui:emptyValue": "",
59+
},
60+
slash_command_description: {
61+
"ui:widget": "text",
62+
"ui:emptyValue": "",
63+
},
4464
bot_token: {
4565
"ui:widget": "password",
4666
"ui:emptyValue": "",
@@ -63,6 +83,8 @@ export function AppSlackConfigEditor(props) {
6383
function slackConfigValidate(formData, errors, uiSchema) {
6484
if (
6585
formData.app_id ||
86+
formData.slash_command_name ||
87+
formData.slash_command_description ||
6688
formData.bot_token ||
6789
formData.verification_token ||
6890
formData.signing_secret
@@ -79,6 +101,20 @@ export function AppSlackConfigEditor(props) {
79101
if (!formData.signing_secret) {
80102
errors.signing_secret.addError("Signing Secret is required");
81103
}
104+
105+
const hasSlashCommand = Boolean(
106+
formData.slash_command_name || formData.slash_command_description,
107+
);
108+
if (hasSlashCommand) {
109+
if (!formData.slash_command_name) {
110+
errors.slash_command_name.addError("Slash Command Name is required");
111+
}
112+
if (!formData.slash_command_description) {
113+
errors.slash_command_description.addError(
114+
"Slash Command Description is required",
115+
);
116+
}
117+
}
82118
}
83119

84120
return errors;

0 commit comments

Comments
 (0)