Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Register assistant handlers as app listeners
  • Loading branch information
Cr1stal committed May 31, 2026
commit 99553b5c67373613dddeda7688cb31af914b66b5
20 changes: 13 additions & 7 deletions slack_bolt/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ def message_hello(message, say):

self._middleware_list: List[Middleware] = []
self._listeners: List[Listener] = []
self._assistant_listener_insertion_index = 0

if listener_executor is None:
listener_executor = ThreadPoolExecutor(max_workers=5)
Expand Down Expand Up @@ -680,28 +681,33 @@ def middleware_func(logger, body, next):
middleware_or_callable = args[0]
if isinstance(middleware_or_callable, Middleware):
middleware: Middleware = middleware_or_callable
if isinstance(middleware, Assistant) and middleware.auto_inherit_app_middleware is True:
self._register_assistant_listeners(middleware)
return None
self._middleware_list.append(middleware)
if isinstance(middleware, Assistant) and middleware.thread_context_store is not None:
self._assistant_thread_context_store = middleware.thread_context_store
elif not isinstance(middleware, Assistant):
self._inherit_app_middleware_for_assistants(middleware)
elif callable(middleware_or_callable):
middleware = CustomMiddleware(
app_name=self.name,
func=middleware_or_callable,
base_logger=self._base_logger,
)
self._middleware_list.append(middleware)
self._inherit_app_middleware_for_assistants(middleware)
return middleware_or_callable
else:
raise BoltError(f"Unexpected type for a middleware ({type(middleware_or_callable)})")
return None

def _inherit_app_middleware_for_assistants(self, middleware: Middleware) -> None:
for registered_middleware in self._middleware_list[:-1]:
if isinstance(registered_middleware, Assistant):
registered_middleware.inherit_app_middleware(middleware)
def _register_assistant_listeners(self, assistant: Assistant) -> None:
if assistant.thread_context_store is not None:
self._assistant_thread_context_store = assistant.thread_context_store

def register_listener(listener: Listener) -> None:
self._listeners.insert(self._assistant_listener_insertion_index, listener)
self._assistant_listener_insertion_index += 1

assistant._register_app_listeners(register_listener)

# -------------------------
# AI Agents & Assistants
Expand Down
20 changes: 13 additions & 7 deletions slack_bolt/app/async_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ async def message_hello(message, say): # async function

self._async_middleware_list: List[AsyncMiddleware] = []
self._async_listeners: List[AsyncListener] = []
self._assistant_listener_insertion_index = 0

self._assistant_thread_context_store = assistant_thread_context_store
self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled
Expand Down Expand Up @@ -707,28 +708,33 @@ async def middleware_func(logger, body, next):
middleware_or_callable = args[0]
if isinstance(middleware_or_callable, AsyncMiddleware):
middleware: AsyncMiddleware = middleware_or_callable
if isinstance(middleware, AsyncAssistant) and middleware.auto_inherit_app_middleware is True:
self._register_assistant_listeners(middleware)
return None
self._async_middleware_list.append(middleware)
if isinstance(middleware, AsyncAssistant) and middleware.thread_context_store is not None:
self._assistant_thread_context_store = middleware.thread_context_store
elif not isinstance(middleware, AsyncAssistant):
self._inherit_app_middleware_for_assistants(middleware)
elif callable(middleware_or_callable):
middleware = AsyncCustomMiddleware(
app_name=self.name,
func=middleware_or_callable,
base_logger=self._base_logger,
)
self._async_middleware_list.append(middleware)
self._inherit_app_middleware_for_assistants(middleware)
return middleware_or_callable
else:
raise BoltError(f"Unexpected type for a middleware ({type(middleware_or_callable)})")
return None

def _inherit_app_middleware_for_assistants(self, middleware: AsyncMiddleware) -> None:
for registered_middleware in self._async_middleware_list[:-1]:
if isinstance(registered_middleware, AsyncAssistant):
registered_middleware.inherit_app_middleware(middleware)
def _register_assistant_listeners(self, assistant: AsyncAssistant) -> None:
if assistant.thread_context_store is not None:
self._assistant_thread_context_store = assistant.thread_context_store

def register_listener(listener: AsyncListener) -> None:
self._async_listeners.insert(self._assistant_listener_insertion_index, listener)
self._assistant_listener_insertion_index += 1

assistant._register_app_listeners(register_listener)

def assistant(self, assistant: AsyncAssistant) -> Optional[Callable]:
return self.middleware(assistant)
Expand Down
135 changes: 76 additions & 59 deletions slack_bolt/middleware/assistant/assistant.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from functools import wraps
from logging import Logger
from typing import List, Optional, Union, Callable, Tuple
from typing import List, Optional, Union, Callable

from slack_bolt.context.save_thread_context import SaveThreadContext
from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore
Expand Down Expand Up @@ -32,6 +32,8 @@ class Assistant(Middleware):
_thread_context_changed_listeners: Optional[List[Listener]]
_user_message_listeners: Optional[List[Listener]]
_bot_message_listeners: Optional[List[Listener]]
_other_message_sub_event_listeners: Optional[List[Listener]]
_app_listener_registrars: List[Callable[[Listener], None]]

thread_context_store: Optional[AssistantThreadContextStore]
base_logger: Optional[logging.Logger]
Expand All @@ -48,18 +50,13 @@ def __init__(
self.thread_context_store = thread_context_store
self.base_logger = logger
self.auto_inherit_app_middleware = auto_inherit_app_middleware
self._inherited_app_middleware: List[Middleware] = []

self._thread_started_listeners = None
self._thread_context_changed_listeners = None
self._user_message_listeners = None
self._bot_message_listeners = None

def inherit_app_middleware(self, middleware: Middleware) -> None:
if self.auto_inherit_app_middleware is False:
return

self._inherited_app_middleware.append(middleware)
self._other_message_sub_event_listeners = None
self._app_listener_registrars = []

def thread_started(
self,
Expand All @@ -73,23 +70,25 @@ def thread_started(
all_matchers = self._merge_matchers(is_assistant_thread_started_event, matchers)
if is_used_without_argument(args):
func = args[0]
self._thread_started_listeners.append(
self._append_listener(
self._thread_started_listeners,
self.build_listener(
listener_or_functions=func,
matchers=all_matchers,
middleware=middleware, # type: ignore[arg-type]
)
),
)
return func

def _inner(func):
functions = [func] + (lazy if lazy is not None else [])
self._thread_started_listeners.append(
self._append_listener(
self._thread_started_listeners,
self.build_listener(
listener_or_functions=functions,
matchers=all_matchers,
middleware=middleware,
)
),
)

@wraps(func)
Expand All @@ -112,23 +111,25 @@ def user_message(
all_matchers = self._merge_matchers(is_user_message_event_in_assistant_thread, matchers)
if is_used_without_argument(args):
func = args[0]
self._user_message_listeners.append(
self._append_listener(
self._user_message_listeners,
self.build_listener(
listener_or_functions=func,
matchers=all_matchers,
middleware=middleware, # type: ignore[arg-type]
)
),
)
return func

def _inner(func):
functions = [func] + (lazy if lazy is not None else [])
self._user_message_listeners.append(
self._append_listener(
self._user_message_listeners,
self.build_listener(
listener_or_functions=functions,
matchers=all_matchers,
middleware=middleware,
)
),
)

@wraps(func)
Expand All @@ -151,23 +152,25 @@ def bot_message(
all_matchers = self._merge_matchers(is_bot_message_event_in_assistant_thread, matchers)
if is_used_without_argument(args):
func = args[0]
self._bot_message_listeners.append(
self._append_listener(
self._bot_message_listeners,
self.build_listener(
listener_or_functions=func,
matchers=all_matchers,
middleware=middleware, # type: ignore[arg-type]
)
),
)
return func

def _inner(func):
functions = [func] + (lazy if lazy is not None else [])
self._bot_message_listeners.append(
self._append_listener(
self._bot_message_listeners,
self.build_listener(
listener_or_functions=functions,
matchers=all_matchers,
middleware=middleware,
)
),
)

@wraps(func)
Expand All @@ -190,23 +193,25 @@ def thread_context_changed(
all_matchers = self._merge_matchers(is_assistant_thread_context_changed_event, matchers)
if is_used_without_argument(args):
func = args[0]
self._thread_context_changed_listeners.append(
self._append_listener(
self._thread_context_changed_listeners,
self.build_listener(
listener_or_functions=func,
matchers=all_matchers,
middleware=middleware, # type: ignore[arg-type]
)
),
)
return func

def _inner(func):
functions = [func] + (lazy if lazy is not None else [])
self._thread_context_changed_listeners.append(
self._append_listener(
self._thread_context_changed_listeners,
self.build_listener(
listener_or_functions=functions,
matchers=all_matchers,
middleware=middleware,
)
),
)

@wraps(func)
Expand All @@ -230,12 +235,54 @@ def _merge_matchers(
def default_thread_context_changed(save_thread_context: SaveThreadContext, payload: dict):
save_thread_context(payload["assistant_thread"]["context"])

def process( # type: ignore[return]
self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]
) -> Optional[BoltResponse]:
@staticmethod
def default_other_message_sub_event(ack):
ack()

def _register_app_listeners(self, listener_registrar: Callable[[Listener], None]) -> None:
self._ensure_default_thread_context_changed_listener()
self._ensure_other_message_sub_event_listener()
for listener in self._app_listeners:
listener_registrar(listener)
self._app_listener_registrars.append(listener_registrar)

@property
def _app_listeners(self) -> List[Listener]:
listeners: List[Listener] = []
for listener_list in [
self._thread_started_listeners,
self._thread_context_changed_listeners,
self._user_message_listeners,
self._bot_message_listeners,
self._other_message_sub_event_listeners,
]:
if listener_list is not None:
listeners.extend(listener_list)
return listeners

def _append_listener(self, listeners: List[Listener], listener: Listener) -> None:
listeners.append(listener)
for registrar in self._app_listener_registrars:
registrar(listener)

def _ensure_default_thread_context_changed_listener(self) -> None:
if self._thread_context_changed_listeners is None:
self.thread_context_changed(self.default_thread_context_changed)

def _ensure_other_message_sub_event_listener(self) -> None:
if self._other_message_sub_event_listeners is None:
self._other_message_sub_event_listeners = []
self._append_listener(
self._other_message_sub_event_listeners,
self.build_listener(
listener_or_functions=self.default_other_message_sub_event,
matchers=[is_other_message_sub_event_in_assistant_thread],
),
)

def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]) -> Optional[BoltResponse]:
self._ensure_default_thread_context_changed_listener()

listener_runner: ThreadListenerRunner = req.context.listener_runner
for listeners in [
self._thread_started_listeners,
Expand All @@ -246,11 +293,7 @@ def process( # type: ignore[return]
if listeners is not None:
for listener in listeners:
if listener.matches(req=req, resp=resp):
middleware_resp, next_was_not_called = self._run_middleware(
listener=listener,
req=req,
resp=resp,
)
middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp)
if next_was_not_called:
if middleware_resp is not None:
return middleware_resp
Expand All @@ -270,33 +313,7 @@ def process( # type: ignore[return]
return req.context.ack()

next()

def _run_middleware(
self,
*,
listener: Listener,
req: BoltRequest,
resp: BoltResponse,
) -> Tuple[Optional[BoltResponse], bool]:
middleware = list(listener.middleware)
if len(self._inherited_app_middleware) > 0:
insertion_index = 1 if len(middleware) > 0 and isinstance(middleware[0], AttachingConversationKwargs) else 0
middleware = [
*middleware[:insertion_index],
*self._inherited_app_middleware,
*middleware[insertion_index:],
]

for m in middleware:
middleware_state = {"next_called": False}

def next_():
middleware_state["next_called"] = True

resp = m.process(req=req, resp=resp, next=next_) # type: ignore[assignment]
if not middleware_state["next_called"]:
return resp, True
return resp, False
return None

def build_listener(
self,
Expand Down
Loading