Skip to content

Commit 785b813

Browse files
fix(assistant): improve middleware dispatch and inject kwargs in middleware (#1456)
1 parent f0db283 commit 785b813

File tree

21 files changed

+715
-77
lines changed

21 files changed

+715
-77
lines changed

slack_bolt/app/app.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore
2424

25-
from slack_bolt.context.assistant.assistant_utilities import AssistantUtilities
2625
from slack_bolt.error import BoltError, BoltUnhandledRequestError
2726
from slack_bolt.lazy_listener.thread_runner import ThreadLazyListenerRunner
2827
from slack_bolt.listener.builtins import TokenRevocationListeners
@@ -70,6 +69,7 @@
7069
IgnoringSelfEvents,
7170
CustomMiddleware,
7271
AttachingFunctionToken,
72+
AttachingAgentKwargs,
7373
)
7474
from slack_bolt.middleware.assistant import Assistant
7575
from slack_bolt.middleware.message_listener_matches import MessageListenerMatches
@@ -83,10 +83,6 @@
8383
from slack_bolt.oauth.internals import select_consistent_installation_store
8484
from slack_bolt.oauth.oauth_settings import OAuthSettings
8585
from slack_bolt.request import BoltRequest
86-
from slack_bolt.request.payload_utils import (
87-
is_assistant_event,
88-
to_event,
89-
)
9086
from slack_bolt.response import BoltResponse
9187
from slack_bolt.util.utils import (
9288
create_web_client,
@@ -137,6 +133,7 @@ def __init__(
137133
listener_executor: Optional[Executor] = None,
138134
# for AI Agents & Assistants
139135
assistant_thread_context_store: Optional[AssistantThreadContextStore] = None,
136+
attaching_agent_kwargs_enabled: bool = True,
140137
):
141138
"""Bolt App that provides functionalities to register middleware/listeners.
142139
@@ -357,6 +354,7 @@ def message_hello(message, say):
357354
listener_executor = ThreadPoolExecutor(max_workers=5)
358355

359356
self._assistant_thread_context_store = assistant_thread_context_store
357+
self._attaching_agent_kwargs_enabled = attaching_agent_kwargs_enabled
360358

361359
self._process_before_response = process_before_response
362360
self._listener_runner = ThreadListenerRunner(
@@ -841,10 +839,13 @@ def ask_for_introduction(event, say):
841839
middleware: A list of lister middleware functions.
842840
Only when all the middleware call `next()` method, the listener function can be invoked.
843841
"""
842+
middleware = list(middleware) if middleware else []
844843

845844
def __call__(*args, **kwargs):
846845
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
847846
primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger)
847+
if self._attaching_agent_kwargs_enabled:
848+
middleware.insert(0, AttachingAgentKwargs(self._assistant_thread_context_store))
848849
return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)
849850

850851
return __call__
@@ -902,6 +903,8 @@ def __call__(*args, **kwargs):
902903
primary_matcher = builtin_matchers.message_event(
903904
keyword=keyword, constraints=constraints, base_logger=self._base_logger
904905
)
906+
if self._attaching_agent_kwargs_enabled:
907+
middleware.insert(0, AttachingAgentKwargs(self._assistant_thread_context_store))
905908
middleware.insert(0, MessageListenerMatches(keyword))
906909
return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)
907910

@@ -1398,20 +1401,6 @@ def _init_context(self, req: BoltRequest):
13981401
# It is intended for apps that start lazy listeners from their custom global middleware.
13991402
req.context["listener_runner"] = self.listener_runner
14001403

1401-
# For AI Agents & Assistants
1402-
if is_assistant_event(req.body):
1403-
assistant = AssistantUtilities(
1404-
payload=to_event(req.body), # type: ignore[arg-type]
1405-
context=req.context,
1406-
thread_context_store=self._assistant_thread_context_store,
1407-
)
1408-
req.context["say"] = assistant.say
1409-
req.context["set_status"] = assistant.set_status
1410-
req.context["set_title"] = assistant.set_title
1411-
req.context["set_suggested_prompts"] = assistant.set_suggested_prompts
1412-
req.context["get_thread_context"] = assistant.get_thread_context
1413-
req.context["save_thread_context"] = assistant.save_thread_context
1414-
14151404
@staticmethod
14161405
def _to_listener_functions(
14171406
kwargs: dict,

slack_bolt/app/async_app.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from aiohttp import web
99

1010
from slack_bolt.app.async_server import AsyncSlackAppServer
11-
from slack_bolt.context.assistant.async_assistant_utilities import AsyncAssistantUtilities
1211
from slack_bolt.context.assistant.thread_context_store.async_store import (
1312
AsyncAssistantThreadContextStore,
1413
)
@@ -30,7 +29,6 @@
3029
AsyncMessageListenerMatches,
3130
)
3231
from slack_bolt.oauth.async_internals import select_consistent_installation_store
33-
from slack_bolt.request.payload_utils import is_assistant_event, to_event
3432
from slack_bolt.util.utils import get_name_for_callable, is_callable_coroutine
3533
from slack_bolt.workflows.step.async_step import (
3634
AsyncWorkflowStep,
@@ -88,6 +86,7 @@
8886
AsyncIgnoringSelfEvents,
8987
AsyncUrlVerification,
9088
AsyncAttachingFunctionToken,
89+
AsyncAttachingAgentKwargs,
9190
)
9291
from slack_bolt.middleware.async_custom_middleware import (
9392
AsyncMiddleware,
@@ -143,6 +142,7 @@ def __init__(
143142
verification_token: Optional[str] = None,
144143
# for AI Agents & Assistants
145144
assistant_thread_context_store: Optional[AsyncAssistantThreadContextStore] = None,
145+
attaching_agent_kwargs_enabled: bool = True,
146146
):
147147
"""Bolt App that provides functionalities to register middleware/listeners.
148148
@@ -363,6 +363,7 @@ async def message_hello(message, say): # async function
363363
self._async_listeners: List[AsyncListener] = []
364364

365365
self._assistant_thread_context_store = assistant_thread_context_store
366+
self._attaching_agent_kwargs_enabled = attaching_agent_kwargs_enabled
366367

367368
self._process_before_response = process_before_response
368369
self._async_listener_runner = AsyncioListenerRunner(
@@ -866,10 +867,13 @@ async def ask_for_introduction(event, say):
866867
middleware: A list of lister middleware functions.
867868
Only when all the middleware call `next()` method, the listener function can be invoked.
868869
"""
870+
middleware = list(middleware) if middleware else []
869871

870872
def __call__(*args, **kwargs):
871873
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
872874
primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger)
875+
if self._attaching_agent_kwargs_enabled:
876+
middleware.insert(0, AsyncAttachingAgentKwargs(self._assistant_thread_context_store))
873877
return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)
874878

875879
return __call__
@@ -930,6 +934,8 @@ def __call__(*args, **kwargs):
930934
asyncio=True,
931935
base_logger=self._base_logger,
932936
)
937+
if self._attaching_agent_kwargs_enabled:
938+
middleware.insert(0, AsyncAttachingAgentKwargs(self._assistant_thread_context_store))
933939
middleware.insert(0, AsyncMessageListenerMatches(keyword))
934940
return self._register_listener(list(functions), primary_matcher, matchers, middleware, True)
935941

@@ -1431,20 +1437,6 @@ def _init_context(self, req: AsyncBoltRequest):
14311437
# It is intended for apps that start lazy listeners from their custom global middleware.
14321438
req.context["listener_runner"] = self.listener_runner
14331439

1434-
# For AI Agents & Assistants
1435-
if is_assistant_event(req.body):
1436-
assistant = AsyncAssistantUtilities(
1437-
payload=to_event(req.body), # type: ignore[arg-type]
1438-
context=req.context,
1439-
thread_context_store=self._assistant_thread_context_store,
1440-
)
1441-
req.context["say"] = assistant.say
1442-
req.context["set_status"] = assistant.set_status
1443-
req.context["set_title"] = assistant.set_title
1444-
req.context["set_suggested_prompts"] = assistant.set_suggested_prompts
1445-
req.context["get_thread_context"] = assistant.get_thread_context
1446-
req.context["save_thread_context"] = assistant.save_thread_context
1447-
14481440
@staticmethod
14491441
def _to_listener_functions(
14501442
kwargs: dict,

slack_bolt/context/async_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async def handle_button_clicks(ack, say):
110110
Callable `say()` function
111111
"""
112112
if "say" not in self:
113-
self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts)
113+
self["say"] = AsyncSay(client=self.client, channel=self.channel_id)
114114
return self["say"]
115115

116116
@property

slack_bolt/context/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def handle_button_clicks(ack, say):
111111
Callable `say()` function
112112
"""
113113
if "say" not in self:
114-
self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts)
114+
self["say"] = Say(client=self.client, channel=self.channel_id)
115115
return self["say"]
116116

117117
@property

slack_bolt/middleware/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .ssl_check import SslCheck
1818
from .url_verification import UrlVerification
1919
from .attaching_function_token import AttachingFunctionToken
20+
from .attaching_agent_kwargs import AttachingAgentKwargs
2021

2122
builtin_middleware_classes = [
2223
SslCheck,
@@ -41,5 +42,6 @@
4142
"SslCheck",
4243
"UrlVerification",
4344
"AttachingFunctionToken",
45+
"AttachingAgentKwargs",
4446
"builtin_middleware_classes",
4547
]

slack_bolt/middleware/assistant/assistant.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore
88
from slack_bolt.listener_matcher.builtins import build_listener_matcher
99

10+
from slack_bolt.middleware.attaching_agent_kwargs import AttachingAgentKwargs
1011
from slack_bolt.request.request import BoltRequest
1112
from slack_bolt.response.response import BoltResponse
1213
from slack_bolt.listener_matcher import CustomListenerMatcher
@@ -236,6 +237,15 @@ def process( # type: ignore[return]
236237
if listeners is not None:
237238
for listener in listeners:
238239
if listener.matches(req=req, resp=resp):
240+
middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp)
241+
if next_was_not_called:
242+
if middleware_resp is not None:
243+
return middleware_resp
244+
# The listener middleware didn't call next().
245+
# Skip this listener and try the next one.
246+
continue
247+
if middleware_resp is not None:
248+
resp = middleware_resp
239249
return listener_runner.run(
240250
request=req,
241251
response=resp,
@@ -262,6 +272,7 @@ def build_listener(
262272
return listener_or_functions
263273
elif isinstance(listener_or_functions, list):
264274
middleware = middleware if middleware else []
275+
middleware.insert(0, AttachingAgentKwargs(self.thread_context_store))
265276
functions = listener_or_functions
266277
ack_function = functions.pop(0)
267278

slack_bolt/middleware/assistant/async_assistant.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from slack_bolt.listener.asyncio_runner import AsyncioListenerRunner
1010
from slack_bolt.listener_matcher.builtins import build_listener_matcher
11+
from slack_bolt.middleware.attaching_agent_kwargs.async_attaching_agent_kwargs import AsyncAttachingAgentKwargs
1112
from slack_bolt.request.async_request import AsyncBoltRequest
1213
from slack_bolt.response import BoltResponse
1314
from slack_bolt.error import BoltError
@@ -265,6 +266,15 @@ async def async_process( # type: ignore[return]
265266
if listeners is not None:
266267
for listener in listeners:
267268
if listener is not None and await listener.async_matches(req=req, resp=resp):
269+
middleware_resp, next_was_not_called = await listener.run_async_middleware(req=req, resp=resp)
270+
if next_was_not_called:
271+
if middleware_resp is not None:
272+
return middleware_resp
273+
# The listener middleware didn't call next().
274+
# Skip this listener and try the next one.
275+
continue
276+
if middleware_resp is not None:
277+
resp = middleware_resp
268278
return await listener_runner.run(
269279
request=req,
270280
response=resp,
@@ -291,6 +301,7 @@ def build_listener(
291301
return listener_or_functions
292302
elif isinstance(listener_or_functions, list):
293303
middleware = middleware if middleware else []
304+
middleware.insert(0, AsyncAttachingAgentKwargs(self.thread_context_store))
294305
functions = listener_or_functions
295306
ack_function = functions.pop(0)
296307

slack_bolt/middleware/async_builtins.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
AsyncMessageListenerMatches,
1111
)
1212
from .attaching_function_token.async_attaching_function_token import AsyncAttachingFunctionToken
13+
from .attaching_agent_kwargs.async_attaching_agent_kwargs import AsyncAttachingAgentKwargs
1314

1415
__all__ = [
1516
"AsyncIgnoringSelfEvents",
@@ -18,4 +19,5 @@
1819
"AsyncUrlVerification",
1920
"AsyncMessageListenerMatches",
2021
"AsyncAttachingFunctionToken",
22+
"AsyncAttachingAgentKwargs",
2123
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .attaching_agent_kwargs import AttachingAgentKwargs
2+
3+
__all__ = [
4+
"AttachingAgentKwargs",
5+
]
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import Optional, Callable, Awaitable
2+
3+
from slack_bolt.context.assistant.async_assistant_utilities import AsyncAssistantUtilities
4+
from slack_bolt.context.assistant.thread_context_store.async_store import AsyncAssistantThreadContextStore
5+
from slack_bolt.middleware.async_middleware import AsyncMiddleware
6+
from slack_bolt.request.async_request import AsyncBoltRequest
7+
from slack_bolt.request.payload_utils import is_assistant_event, to_event
8+
from slack_bolt.response import BoltResponse
9+
10+
11+
class AsyncAttachingAgentKwargs(AsyncMiddleware):
12+
13+
thread_context_store: Optional[AsyncAssistantThreadContextStore]
14+
15+
def __init__(self, thread_context_store: Optional[AsyncAssistantThreadContextStore] = None):
16+
self.thread_context_store = thread_context_store
17+
18+
async def async_process(
19+
self,
20+
*,
21+
req: AsyncBoltRequest,
22+
resp: BoltResponse,
23+
next: Callable[[], Awaitable[BoltResponse]],
24+
) -> Optional[BoltResponse]:
25+
event = to_event(req.body)
26+
if event is not None:
27+
if is_assistant_event(req.body):
28+
assistant = AsyncAssistantUtilities(
29+
payload=event,
30+
context=req.context,
31+
thread_context_store=self.thread_context_store,
32+
)
33+
req.context["say"] = assistant.say
34+
req.context["set_status"] = assistant.set_status
35+
req.context["set_title"] = assistant.set_title
36+
req.context["set_suggested_prompts"] = assistant.set_suggested_prompts
37+
req.context["get_thread_context"] = assistant.get_thread_context
38+
req.context["save_thread_context"] = assistant.save_thread_context
39+
return await next()

0 commit comments

Comments
 (0)