From 212c413465d2f74ca02623f2ba787ddb750e68a2 Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Wed, 24 Sep 2025 23:00:41 -0700
Subject: [PATCH 01/13] version 1.26.0.dev0
---
pyproject.toml | 2 +-
slack_bolt/version.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 024ee6654..416b7e521 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk>=3.35.0,<4"]
+dependencies = ["slack_sdk==3.36.0.dev3"]
[project.urls]
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index 7f9c19341..c82ba217e 100644
--- a/slack_bolt/version.py
+++ b/slack_bolt/version.py
@@ -1,3 +1,3 @@
"""Check the latest version at https://pypi.org/project/slack-bolt/"""
-__version__ = "1.25.0"
+__version__ = "1.26.0.dev0"
From 973af99031d7d4c7f1d4ed3dfea027346d56b4cf Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Thu, 25 Sep 2025 17:26:53 -0700
Subject: [PATCH 02/13] feat: accept markdown_text argument from the say helper
(#1372)
---
slack_bolt/context/say/async_say.py | 12 +++++++-----
slack_bolt/context/say/say.py | 4 +++-
tests/slack_bolt/context/test_say.py | 10 ++++++----
tests/slack_bolt_async/context/test_async_say.py | 13 ++++++++-----
4 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/slack_bolt/context/say/async_say.py b/slack_bolt/context/say/async_say.py
index b771529b0..c492e5d77 100644
--- a/slack_bolt/context/say/async_say.py
+++ b/slack_bolt/context/say/async_say.py
@@ -1,14 +1,14 @@
-from typing import Optional, Union, Dict, Sequence, Callable, Awaitable
+from typing import Awaitable, Callable, Dict, Optional, Sequence, Union
-from slack_sdk.models.metadata import Metadata
-
-from slack_bolt.context.say.internals import _can_say
-from slack_bolt.util.utils import create_copy
from slack_sdk.models.attachments import Attachment
from slack_sdk.models.blocks import Block
+from slack_sdk.models.metadata import Metadata
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.web.async_slack_response import AsyncSlackResponse
+from slack_bolt.context.say.internals import _can_say
+from slack_bolt.util.utils import create_copy
+
class AsyncSay:
client: Optional[AsyncWebClient]
@@ -42,6 +42,7 @@ async def __call__(
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -67,6 +68,7 @@ async def __call__(
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/slack_bolt/context/say/say.py b/slack_bolt/context/say/say.py
index 6cfbcd801..a6e5904e3 100644
--- a/slack_bolt/context/say/say.py
+++ b/slack_bolt/context/say/say.py
@@ -1,4 +1,4 @@
-from typing import Optional, Union, Dict, Sequence, Callable
+from typing import Callable, Dict, Optional, Sequence, Union
from slack_sdk import WebClient
from slack_sdk.models.attachments import Attachment
@@ -45,6 +45,7 @@ def __call__(
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -70,6 +71,7 @@ def __call__(
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/tests/slack_bolt/context/test_say.py b/tests/slack_bolt/context/test_say.py
index 9e465e5d5..6ca1fc96a 100644
--- a/tests/slack_bolt/context/test_say.py
+++ b/tests/slack_bolt/context/test_say.py
@@ -3,10 +3,7 @@
from slack_sdk.web import SlackResponse
from slack_bolt import Say
-from tests.mock_web_api_server import (
- setup_mock_web_api_server,
- cleanup_mock_web_api_server,
-)
+from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server
class TestSay:
@@ -24,6 +21,11 @@ def test_say(self):
response: SlackResponse = say(text="Hi there!")
assert response.status_code == 200
+ def test_say_markdown_text(self):
+ say = Say(client=self.web_client, channel="C111")
+ response: SlackResponse = say(markdown_text="**Greetings!**")
+ assert response.status_code == 200
+
def test_say_unfurl_options(self):
say = Say(client=self.web_client, channel="C111")
response: SlackResponse = say(text="Hi there!", unfurl_media=True, unfurl_links=True)
diff --git a/tests/slack_bolt_async/context/test_async_say.py b/tests/slack_bolt_async/context/test_async_say.py
index 77ac0cc0e..efa90febc 100644
--- a/tests/slack_bolt_async/context/test_async_say.py
+++ b/tests/slack_bolt_async/context/test_async_say.py
@@ -2,12 +2,9 @@
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.web.async_slack_response import AsyncSlackResponse
-from tests.utils import get_event_loop
from slack_bolt.context.say.async_say import AsyncSay
-from tests.mock_web_api_server import (
- cleanup_mock_web_api_server_async,
- setup_mock_web_api_server_async,
-)
+from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async
+from tests.utils import get_event_loop
class TestAsyncSay:
@@ -29,6 +26,12 @@ async def test_say(self):
response: AsyncSlackResponse = await say(text="Hi there!")
assert response.status_code == 200
+ @pytest.mark.asyncio
+ async def test_say_markdown_text(self):
+ say = AsyncSay(client=self.web_client, channel="C111")
+ response: AsyncSlackResponse = await say(markdown_text="**Greetings!**")
+ assert response.status_code == 200
+
@pytest.mark.asyncio
async def test_say_unfurl_options(self):
say = AsyncSay(client=self.web_client, channel="C111")
From c8f2d6e55f417a435119531808c33a1be9c6e5fe Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Mon, 29 Sep 2025 14:15:52 -0700
Subject: [PATCH 03/13] feat: accept assistant.threads.setStatus method
arguments from the assistant class (#1371)
---
docs/reference/async_app.html | 11 ++++-
.../context/set_status/async_set_status.html | 11 ++++-
docs/reference/context/set_status/index.html | 11 ++++-
.../context/set_status/set_status.html | 11 ++++-
docs/reference/index.html | 11 ++++-
.../context/set_status/async_set_status.py | 13 +++++-
slack_bolt/context/set_status/set_status.py | 13 +++++-
tests/slack_bolt/context/test_set_status.py | 38 ++++++++++++++++
.../context/test_async_set_status.py | 45 +++++++++++++++++++
9 files changed, 150 insertions(+), 14 deletions(-)
create mode 100644 tests/slack_bolt/context/test_set_status.py
create mode 100644 tests/slack_bolt_async/context/test_async_set_status.py
diff --git a/docs/reference/async_app.html b/docs/reference/async_app.html
index ad9192253..9a87b0f32 100644
--- a/docs/reference/async_app.html
+++ b/docs/reference/async_app.html
@@ -5248,11 +5248,18 @@ Class variables
self.channel_id = channel_id
self.thread_ts = thread_ts
- async def __call__(self, status: str) -> AsyncSlackResponse:
+ async def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> AsyncSlackResponse:
return await self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/docs/reference/context/set_status/async_set_status.html b/docs/reference/context/set_status/async_set_status.html
index 6a15d70ae..06efd6447 100644
--- a/docs/reference/context/set_status/async_set_status.html
+++ b/docs/reference/context/set_status/async_set_status.html
@@ -70,11 +70,18 @@
self.channel_id = channel_id
self.thread_ts = thread_ts
- async def __call__(self, status: str) -> AsyncSlackResponse:
+ async def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> AsyncSlackResponse:
return await self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/docs/reference/context/set_status/index.html b/docs/reference/context/set_status/index.html
index 9e53da9a5..aa11815e3 100644
--- a/docs/reference/context/set_status/index.html
+++ b/docs/reference/context/set_status/index.html
@@ -81,11 +81,18 @@
self.channel_id = channel_id
self.thread_ts = thread_ts
- def __call__(self, status: str) -> SlackResponse:
+ def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> SlackResponse:
return self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/docs/reference/context/set_status/set_status.html b/docs/reference/context/set_status/set_status.html
index 0ec8df5da..e4d839f64 100644
--- a/docs/reference/context/set_status/set_status.html
+++ b/docs/reference/context/set_status/set_status.html
@@ -70,11 +70,18 @@
self.channel_id = channel_id
self.thread_ts = thread_ts
- def __call__(self, status: str) -> SlackResponse:
+ def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> SlackResponse:
return self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/docs/reference/index.html b/docs/reference/index.html
index 430e36813..e3a2b1169 100644
--- a/docs/reference/index.html
+++ b/docs/reference/index.html
@@ -5786,11 +5786,18 @@ Class variables
self.channel_id = channel_id
self.thread_ts = thread_ts
- def __call__(self, status: str) -> SlackResponse:
+ def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> SlackResponse:
return self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/slack_bolt/context/set_status/async_set_status.py b/slack_bolt/context/set_status/async_set_status.py
index 926ec6de8..e2c451f46 100644
--- a/slack_bolt/context/set_status/async_set_status.py
+++ b/slack_bolt/context/set_status/async_set_status.py
@@ -1,3 +1,5 @@
+from typing import List, Optional
+
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.web.async_slack_response import AsyncSlackResponse
@@ -17,9 +19,16 @@ def __init__(
self.channel_id = channel_id
self.thread_ts = thread_ts
- async def __call__(self, status: str) -> AsyncSlackResponse:
+ async def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> AsyncSlackResponse:
return await self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/slack_bolt/context/set_status/set_status.py b/slack_bolt/context/set_status/set_status.py
index 8df0d49a7..0ed612e16 100644
--- a/slack_bolt/context/set_status/set_status.py
+++ b/slack_bolt/context/set_status/set_status.py
@@ -1,3 +1,5 @@
+from typing import List, Optional
+
from slack_sdk import WebClient
from slack_sdk.web import SlackResponse
@@ -17,9 +19,16 @@ def __init__(
self.channel_id = channel_id
self.thread_ts = thread_ts
- def __call__(self, status: str) -> SlackResponse:
+ def __call__(
+ self,
+ status: str,
+ loading_messages: Optional[List[str]] = None,
+ **kwargs,
+ ) -> SlackResponse:
return self.client.assistant_threads_setStatus(
- status=status,
channel_id=self.channel_id,
thread_ts=self.thread_ts,
+ status=status,
+ loading_messages=loading_messages,
+ **kwargs,
)
diff --git a/tests/slack_bolt/context/test_set_status.py b/tests/slack_bolt/context/test_set_status.py
new file mode 100644
index 000000000..fe998df5e
--- /dev/null
+++ b/tests/slack_bolt/context/test_set_status.py
@@ -0,0 +1,38 @@
+import pytest
+from slack_sdk import WebClient
+from slack_sdk.web import SlackResponse
+
+from slack_bolt.context.set_status import SetStatus
+from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server
+
+
+class TestSetStatus:
+ def setup_method(self):
+ setup_mock_web_api_server(self)
+ valid_token = "xoxb-valid"
+ mock_api_server_base_url = "http://localhost:8888"
+ self.web_client = WebClient(token=valid_token, base_url=mock_api_server_base_url)
+
+ def teardown_method(self):
+ cleanup_mock_web_api_server(self)
+
+ def test_set_status(self):
+ set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: SlackResponse = set_status("Thinking...")
+ assert response.status_code == 200
+
+ def test_set_status_loading_messages(self):
+ set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: SlackResponse = set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Sitting...",
+ "Waiting...",
+ ],
+ )
+ assert response.status_code == 200
+
+ def test_set_status_invalid(self):
+ set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ with pytest.raises(TypeError):
+ set_status()
diff --git a/tests/slack_bolt_async/context/test_async_set_status.py b/tests/slack_bolt_async/context/test_async_set_status.py
new file mode 100644
index 000000000..8df34171f
--- /dev/null
+++ b/tests/slack_bolt_async/context/test_async_set_status.py
@@ -0,0 +1,45 @@
+import pytest
+from slack_sdk.web.async_client import AsyncWebClient
+from slack_sdk.web.async_slack_response import AsyncSlackResponse
+
+from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
+from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async
+from tests.utils import get_event_loop
+
+
+class TestAsyncSetStatus:
+ @pytest.fixture
+ def event_loop(self):
+ setup_mock_web_api_server_async(self)
+ valid_token = "xoxb-valid"
+ mock_api_server_base_url = "http://localhost:8888"
+ self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url)
+
+ loop = get_event_loop()
+ yield loop
+ loop.close()
+ cleanup_mock_web_api_server_async(self)
+
+ @pytest.mark.asyncio
+ async def test_set_status(self):
+ set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: AsyncSlackResponse = await set_status("Thinking...")
+ assert response.status_code == 200
+
+ @pytest.mark.asyncio
+ async def test_set_status_loading_messages(self):
+ set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: AsyncSlackResponse = await set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Sitting...",
+ "Waiting...",
+ ],
+ )
+ assert response.status_code == 200
+
+ @pytest.mark.asyncio
+ async def test_set_status_invalid(self):
+ set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ with pytest.raises(TypeError):
+ await set_status()
From 6a127edebdbbc23e72db9951b762c507e70daec9 Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Tue, 30 Sep 2025 11:07:44 -0700
Subject: [PATCH 04/13] chore(release): version 1.26.0.dev1
---
docs/reference/async_app.html | 2 ++
.../context/assistant/thread_context_store/file/index.html | 2 +-
docs/reference/context/say/async_say.html | 2 ++
docs/reference/context/say/index.html | 2 ++
docs/reference/context/say/say.html | 2 ++
docs/reference/index.html | 4 +++-
pyproject.toml | 2 +-
slack_bolt/version.py | 2 +-
8 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/docs/reference/async_app.html b/docs/reference/async_app.html
index 9a87b0f32..763745a54 100644
--- a/docs/reference/async_app.html
+++ b/docs/reference/async_app.html
@@ -5158,6 +5158,7 @@ Class variables
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -5183,6 +5184,7 @@ Class variables
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/docs/reference/context/assistant/thread_context_store/file/index.html b/docs/reference/context/assistant/thread_context_store/file/index.html
index 4a5d944e1..cbb4e4db6 100644
--- a/docs/reference/context/assistant/thread_context_store/file/index.html
+++ b/docs/reference/context/assistant/thread_context_store/file/index.html
@@ -48,7 +48,7 @@
class FileAssistantThreadContextStore
-(base_dir: str = '/Users/wbergamin/.bolt-app-assistant-thread-contexts')
+(base_dir: str = '/Users/eden.zimbelman/.bolt-app-assistant-thread-contexts')
-
diff --git a/docs/reference/context/say/async_say.html b/docs/reference/context/say/async_say.html
index 8547a1188..e170251fe 100644
--- a/docs/reference/context/say/async_say.html
+++ b/docs/reference/context/say/async_say.html
@@ -87,6 +87,7 @@
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -112,6 +113,7 @@
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/docs/reference/context/say/index.html b/docs/reference/context/say/index.html
index 7a5850760..e2ed0d03f 100644
--- a/docs/reference/context/say/index.html
+++ b/docs/reference/context/say/index.html
@@ -105,6 +105,7 @@
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -130,6 +131,7 @@
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/docs/reference/context/say/say.html b/docs/reference/context/say/say.html
index 5db4f24ba..c66e2776f 100644
--- a/docs/reference/context/say/say.html
+++ b/docs/reference/context/say/say.html
@@ -90,6 +90,7 @@
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -115,6 +116,7 @@
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/docs/reference/index.html b/docs/reference/index.html
index e3a2b1169..3653e10af 100644
--- a/docs/reference/index.html
+++ b/docs/reference/index.html
@@ -5225,7 +5225,7 @@ Class variables
class FileAssistantThreadContextStore
-(base_dir: str = '/Users/wbergamin/.bolt-app-assistant-thread-contexts')
+(base_dir: str = '/Users/eden.zimbelman/.bolt-app-assistant-thread-contexts')
-
@@ -5691,6 +5691,7 @@ Class variables
icon_emoji: Optional[str] = None,
icon_url: Optional[str] = None,
username: Optional[str] = None,
+ markdown_text: Optional[str] = None,
mrkdwn: Optional[bool] = None,
link_names: Optional[bool] = None,
parse: Optional[str] = None, # none, full
@@ -5716,6 +5717,7 @@ Class variables
icon_emoji=icon_emoji,
icon_url=icon_url,
username=username,
+ markdown_text=markdown_text,
mrkdwn=mrkdwn,
link_names=link_names,
parse=parse,
diff --git a/pyproject.toml b/pyproject.toml
index 416b7e521..1c5d7c597 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk==3.36.0.dev3"]
+dependencies = ["slack_sdk==3.36.0.dev4"]
[project.urls]
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index c82ba217e..d2c5aa4ba 100644
--- a/slack_bolt/version.py
+++ b/slack_bolt/version.py
@@ -1,3 +1,3 @@
"""Check the latest version at https://pypi.org/project/slack-bolt/"""
-__version__ = "1.26.0.dev0"
+__version__ = "1.26.0.dev1"
From e3a082114c818f2a157f1a0c71d9eaa5ce1d1751 Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Tue, 30 Sep 2025 11:26:59 -0700
Subject: [PATCH 05/13] chore(release): version 1.26.0.dev2
---
pyproject.toml | 2 +-
slack_bolt/version.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 1c5d7c597..aeebdfee3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk==3.36.0.dev4"]
+dependencies = ["slack_sdk==3.36.0.dev5"]
[project.urls]
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index d2c5aa4ba..b47d8dfb5 100644
--- a/slack_bolt/version.py
+++ b/slack_bolt/version.py
@@ -1,3 +1,3 @@
"""Check the latest version at https://pypi.org/project/slack-bolt/"""
-__version__ = "1.26.0.dev1"
+__version__ = "1.26.0.dev2"
From 23a4731582c5909ac5644aef7e792fa76893926a Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Thu, 2 Oct 2025 11:49:23 -0700
Subject: [PATCH 06/13] chore(release): version 1.26.0.dev3
---
pyproject.toml | 2 +-
slack_bolt/version.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index aeebdfee3..7f0bbf437 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk==3.36.0.dev5"]
+dependencies = ["slack_sdk==3.36.0.dev6"]
[project.urls]
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index b47d8dfb5..acf1eab7f 100644
--- a/slack_bolt/version.py
+++ b/slack_bolt/version.py
@@ -1,3 +1,3 @@
"""Check the latest version at https://pypi.org/project/slack-bolt/"""
-__version__ = "1.26.0.dev2"
+__version__ = "1.26.0.dev3"
From aaabe50eea898fab124dcae92da1702de7dbcb6f Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Fri, 3 Oct 2025 10:26:18 -0700
Subject: [PATCH 07/13] fix(types): accept a sequence of suggested prompts in
assistant context (#1382)
---
.../async_set_suggested_prompts.py | 4 +-
.../set_suggested_prompts.py | 4 +-
.../context/test_set_suggested_prompts.py | 37 +++++++++++++++
.../test_async_set_suggested_prompts.py | 45 +++++++++++++++++++
4 files changed, 86 insertions(+), 4 deletions(-)
create mode 100644 tests/slack_bolt/context/test_set_suggested_prompts.py
create mode 100644 tests/slack_bolt_async/context/test_async_set_suggested_prompts.py
diff --git a/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py b/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py
index aeeb244d7..2079b6448 100644
--- a/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py
+++ b/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py
@@ -1,4 +1,4 @@
-from typing import List, Dict, Union, Optional
+from typing import Dict, List, Optional, Sequence, Union
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.web.async_slack_response import AsyncSlackResponse
@@ -21,7 +21,7 @@ def __init__(
async def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> AsyncSlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py b/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py
index fc9304b17..21ff815e1 100644
--- a/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py
+++ b/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py
@@ -1,4 +1,4 @@
-from typing import List, Dict, Union, Optional
+from typing import Dict, List, Optional, Sequence, Union
from slack_sdk import WebClient
from slack_sdk.web import SlackResponse
@@ -21,7 +21,7 @@ def __init__(
def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> SlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/tests/slack_bolt/context/test_set_suggested_prompts.py b/tests/slack_bolt/context/test_set_suggested_prompts.py
new file mode 100644
index 000000000..792b974b5
--- /dev/null
+++ b/tests/slack_bolt/context/test_set_suggested_prompts.py
@@ -0,0 +1,37 @@
+import pytest
+from slack_sdk import WebClient
+from slack_sdk.web import SlackResponse
+
+from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts
+from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server
+
+
+class TestSetSuggestedPrompts:
+ def setup_method(self):
+ setup_mock_web_api_server(self)
+ valid_token = "xoxb-valid"
+ mock_api_server_base_url = "http://localhost:8888"
+ self.web_client = WebClient(token=valid_token, base_url=mock_api_server_base_url)
+
+ def teardown_method(self):
+ cleanup_mock_web_api_server(self)
+
+ def test_set_suggested_prompts(self):
+ set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: SlackResponse = set_suggested_prompts(prompts=["One", "Two"])
+ assert response.status_code == 200
+
+ def test_set_suggested_prompts_objects(self):
+ set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: SlackResponse = set_suggested_prompts(
+ prompts=[
+ "One",
+ {"title": "Two", "message": "What's before addition?"},
+ ],
+ )
+ assert response.status_code == 200
+
+ def test_set_suggested_prompts_invalid(self):
+ set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ with pytest.raises(TypeError):
+ set_suggested_prompts()
diff --git a/tests/slack_bolt_async/context/test_async_set_suggested_prompts.py b/tests/slack_bolt_async/context/test_async_set_suggested_prompts.py
new file mode 100644
index 000000000..70a24efcb
--- /dev/null
+++ b/tests/slack_bolt_async/context/test_async_set_suggested_prompts.py
@@ -0,0 +1,45 @@
+import asyncio
+
+import pytest
+from slack_sdk.web.async_client import AsyncWebClient
+from slack_sdk.web.async_slack_response import AsyncSlackResponse
+
+from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts
+from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server
+
+
+class TestAsyncSetSuggestedPrompts:
+ @pytest.fixture
+ def event_loop(self):
+ setup_mock_web_api_server(self)
+ valid_token = "xoxb-valid"
+ mock_api_server_base_url = "http://localhost:8888"
+ self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url)
+
+ loop = asyncio.get_event_loop()
+ yield loop
+ loop.close()
+ cleanup_mock_web_api_server(self)
+
+ @pytest.mark.asyncio
+ async def test_set_suggested_prompts(self):
+ set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: AsyncSlackResponse = await set_suggested_prompts(prompts=["One", "Two"])
+ assert response.status_code == 200
+
+ @pytest.mark.asyncio
+ async def test_set_suggested_prompts_objects(self):
+ set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ response: AsyncSlackResponse = await set_suggested_prompts(
+ prompts=[
+ "One",
+ {"title": "Two", "message": "What's before addition?"},
+ ],
+ )
+ assert response.status_code == 200
+
+ @pytest.mark.asyncio
+ async def test_set_suggested_prompts_invalid(self):
+ set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123")
+ with pytest.raises(TypeError):
+ await set_suggested_prompts()
From 502ffe6f0029f46e5fd0c77116e13a6e686c199d Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Fri, 3 Oct 2025 11:36:54 -0700
Subject: [PATCH 08/13] chore(release): version 1.26.0.dev4
---
docs/reference/app/app.html | 11 ++++++++---
docs/reference/app/async_app.html | 11 ++++++++---
docs/reference/app/index.html | 11 ++++++++---
docs/reference/async_app.html | 13 +++++++++----
.../async_set_suggested_prompts.html | 2 +-
.../context/set_suggested_prompts/index.html | 2 +-
.../set_suggested_prompts.html | 2 +-
docs/reference/index.html | 13 +++++++++----
pyproject.toml | 2 +-
slack_bolt/version.py | 2 +-
10 files changed, 47 insertions(+), 22 deletions(-)
diff --git a/docs/reference/app/app.html b/docs/reference/app/app.html
index 3ee02b07c..c91d020ef 100644
--- a/docs/reference/app/app.html
+++ b/docs/reference/app/app.html
@@ -1195,7 +1195,9 @@
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3018,7 +3020,9 @@ Args
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3028,7 +3032,8 @@ Args
return __call__
+Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+details.
diff --git a/docs/reference/app/async_app.html b/docs/reference/app/async_app.html
index 78959986b..9cbc801d0 100644
--- a/docs/reference/app/async_app.html
+++ b/docs/reference/app/async_app.html
@@ -1215,7 +1215,9 @@
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3075,7 +3077,9 @@ Args
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3085,7 +3089,8 @@ Args
return __call__
+Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+details.
def web_app(self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application
diff --git a/docs/reference/app/index.html b/docs/reference/app/index.html
index a46bc2e71..8821e5af9 100644
--- a/docs/reference/app/index.html
+++ b/docs/reference/app/index.html
@@ -1214,7 +1214,9 @@
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3037,7 +3039,9 @@ Args
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3047,7 +3051,8 @@ Args
return __call__
+Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+details.
diff --git a/docs/reference/async_app.html b/docs/reference/async_app.html
index c2c1b6f54..8fd975be9 100644
--- a/docs/reference/async_app.html
+++ b/docs/reference/async_app.html
@@ -1306,7 +1306,9 @@ Class variables
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3166,7 +3168,9 @@ Args
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3176,7 +3180,8 @@ Args
return __call__
+Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+details.
def web_app(self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application
@@ -5307,7 +5312,7 @@ Class variables
async def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> AsyncSlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html b/docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html
index 449a72117..4feda52ba 100644
--- a/docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html
+++ b/docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html
@@ -72,7 +72,7 @@
async def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> AsyncSlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/docs/reference/context/set_suggested_prompts/index.html b/docs/reference/context/set_suggested_prompts/index.html
index ee5371cea..12d864dde 100644
--- a/docs/reference/context/set_suggested_prompts/index.html
+++ b/docs/reference/context/set_suggested_prompts/index.html
@@ -83,7 +83,7 @@
def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> SlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/docs/reference/context/set_suggested_prompts/set_suggested_prompts.html b/docs/reference/context/set_suggested_prompts/set_suggested_prompts.html
index 133d3a55a..6c0385e57 100644
--- a/docs/reference/context/set_suggested_prompts/set_suggested_prompts.html
+++ b/docs/reference/context/set_suggested_prompts/set_suggested_prompts.html
@@ -72,7 +72,7 @@
def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> SlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/docs/reference/index.html b/docs/reference/index.html
index dc2a2fa4c..7bd6d117e 100644
--- a/docs/reference/index.html
+++ b/docs/reference/index.html
@@ -1335,7 +1335,9 @@ Class variables
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3158,7 +3160,9 @@ Args
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
"""Registers a new `view_submission` listener.
- Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details."""
+ Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+ details.
+ """
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3168,7 +3172,8 @@ Args
return __call__
+Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
+details.
@@ -5845,7 +5850,7 @@ Class variables
def __call__(
self,
- prompts: List[Union[str, Dict[str, str]]],
+ prompts: Sequence[Union[str, Dict[str, str]]],
title: Optional[str] = None,
) -> SlackResponse:
prompts_arg: List[Dict[str, str]] = []
diff --git a/pyproject.toml b/pyproject.toml
index 2d1897c4c..aab53aae6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk==3.36.0.dev6"]
+dependencies = ["slack_sdk==3.36.0.dev7"]
[project.urls]
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index acf1eab7f..8a11da6a7 100644
--- a/slack_bolt/version.py
+++ b/slack_bolt/version.py
@@ -1,3 +1,3 @@
"""Check the latest version at https://pypi.org/project/slack-bolt/"""
-__version__ = "1.26.0.dev3"
+__version__ = "1.26.0.dev4"
From 36ddda719779d90543d22cc07e0afca83042bdf9 Mon Sep 17 00:00:00 2001
From: Luke Russell <31357343+lukegalbraithrussell@users.noreply.github.com>
Date: Fri, 3 Oct 2025 18:49:38 -0700
Subject: [PATCH 09/13] docs: adds streaming messages section to sending
message page (#1384)
Co-authored-by: Eden Zimbelman
---
docs/english/concepts/ai-apps.md | 408 ++++++++++++++++++-----
docs/english/concepts/message-sending.md | 60 +++-
2 files changed, 376 insertions(+), 92 deletions(-)
diff --git a/docs/english/concepts/ai-apps.md b/docs/english/concepts/ai-apps.md
index b294c6688..44bd08df1 100644
--- a/docs/english/concepts/ai-apps.md
+++ b/docs/english/concepts/ai-apps.md
@@ -1,83 +1,195 @@
-# Using AI in Apps
-:::info[This feature requires a paid plan]
+# Using AI in Apps {#using-ai-in-apps}
+
+The Slack platform offers features tailored for AI agents and assistants. Your apps can [utilize the `Assistant` class](#assistant) for a side-panel view designed with AI in mind, or they can utilize features applicable to messages throughout Slack, like [chat streaming](#text-streaming) and [feedback buttons](#adding-and-handling-feedback).
+
+If you're unfamiliar with using these feature within Slack, you may want to read the [API documentation on the subject](/ai/). Then come back here to implement them with Bolt!
+
+## The `Assistant` class instance {#assistant}
+
+:::info[Some features within this guide require a paid plan]
If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free.
:::
-The Agents & AI Apps feature comprises a unique messaging experience for Slack. If you're unfamiliar with using the Agents & AI Apps feature within Slack, you'll want to read the [API documentation on the subject](/ai/). Then come back here to implement them with Bolt!
+The [`Assistant`](/tools/bolt-js/reference#the-assistantconfig-configuration-object) class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the Agents & AI Apps feature enabled.
-## Configuring your app to support AI features {#configuring-your-app}
+A typical flow would look like:
-1. Within [App Settings](https://api.slack.com/apps), enable the **Agents & AI Apps** feature.
+1. [The user starts a thread](#handling-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](/reference/events/assistant_thread_started) event.
+2. [The thread context may change at any point](#handling-thread-context-changes). The `Assistant` class can handle any incoming [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) events. The class also provides a default `context` store to keep track of thread context changes as the user moves through Slack.
+3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event.
-2. Within the App Settings **OAuth & Permissions** page, add the following scopes:
-* [`assistant:write`](/reference/scopes/assistant.write)
-* [`chat:write`](/reference/scopes/chat.write)
-* [`im:history`](/reference/scopes/im.history)
-3. Within the App Settings **Event Subscriptions** page, subscribe to the following events:
-* [`assistant_thread_started`](/reference/events/assistant_thread_started)
-* [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
-* [`message.im`](/reference/events/message.im)
+```python
+assistant = Assistant()
-:::info[You _could_ implement your own AI app by [listening](event-listening) for the `assistant_thread_started`, `assistant_thread_context_changed`, and `message.im` events (see implementation details below).]
+# This listener is invoked when a human user opened an assistant thread
+@assistant.thread_started
+def start_assistant_thread(
+ say: Say,
+ get_thread_context: GetThreadContext,
+ set_suggested_prompts: SetSuggestedPrompts,
+ logger: logging.Logger,
+):
+ try:
+ ...
-That being said, using the `Assistant` class will streamline the process. And we already wrote this nice guide for you!
+# This listener is invoked when the human user sends a reply in the assistant thread
+@assistant.user_message
+def respond_in_assistant_thread(
+ client: WebClient,
+ context: BoltContext,
+ get_thread_context: GetThreadContext,
+ logger: logging.Logger,
+ payload: dict,
+ say: Say,
+ set_status: SetStatus,
+):
+ try:
+ ...
+
+# Enable this assistant middleware in your Bolt app
+app.use(assistant)
+```
+
+:::info[Consider the following]
+You _could_ go it alone and [listen](/tools/bolt-python/concepts/event-listening) for the `assistant_thread_started`, `assistant_thread_context_changed`, and `message.im` events in order to implement the AI features in your app. That being said, using the `Assistant` class will streamline the process. And we already wrote this nice guide for you!
+:::
+
+While the `assistant_thread_started` and `assistant_thread_context_changed` events do provide Slack-client thread context information, the `message.im` event does not. Any subsequent user message events won't contain thread context data. For that reason, Bolt not only provides a way to store thread context — the `threadContextStore` property — but it also provides a `DefaultThreadContextStore` instance that is utilized by default. This implementation relies on storing and retrieving [message metadata](/messaging/message-metadata/) as the user interacts with the app.
-## The `Assistant` class instance {#assistant-class}
+If you do provide your own `threadContextStore` property, it must feature `get` and `save` methods.
+
+:::tip[Refer to the [reference docs](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.]
+:::
+
+### Configuring your app to support the `Assistant` class {#configuring-assistant-class}
+
+1. Within [App Settings](https://api.slack.com/apps), enable the **Agents & AI Apps** feature.
+
+2. Within the App Settings **OAuth & Permissions** page, add the following scopes:
+ * [`assistant:write`](/reference/scopes/assistant.write)
+ * [`chat:write`](/reference/scopes/chat.write)
+ * [`im:history`](/reference/scopes/im.history)
-The `Assistant` class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the Agents & AI Apps feature enabled. A typical flow would look like:
+3. Within the App Settings **Event Subscriptions** page, subscribe to the following events:
+ * [`assistant_thread_started`](/reference/events/assistant_thread_started)
+ * [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
+ * [`message.im`](/reference/events/message.im)
-1. [The user starts a thread](#handling-a-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](/reference/events/assistant_thread_started) event.
-2. [The thread context may change at any point](#handling-thread-context-changes). The `Assistant` class can handle any incoming [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) events. The class also provides a default context store to keep track of thread context changes as the user moves through Slack.
-3. [The user responds](#handling-the-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event.
+### Handling a new thread {#handling-new-thread}
+When the user opens a new thread with your AI-enabled app, the [`assistant_thread_started`](/reference/events/assistant_thread_started) event will be sent to your app.
+
+:::tip[When a user opens an app thread while in a channel, the channel info is stored as the thread's `AssistantThreadContext` data.]
+
+You can grab that info by using the `get_thread_context` utility, as subsequent user message event payloads won't include the channel info.
+:::
```python
assistant = Assistant()
-# This listener is invoked when a human user opened an assistant thread
@assistant.thread_started
-def start_assistant_thread(say: Say, set_suggested_prompts: SetSuggestedPrompts):
- # Send the first reply to the human who started chat with your app's assistant bot
- say(":wave: Hi, how can I help you today?")
-
- # Setting suggested prompts is optional
- set_suggested_prompts(
- prompts=[
- # If the suggested prompt is long, you can use {"title": "short one to display", "message": "full prompt"} instead
- "What does SLACK stand for?",
- "When Slack was released?",
- ],
- )
+def start_assistant_thread(
+ say: Say,
+ get_thread_context: GetThreadContext,
+ set_suggested_prompts: SetSuggestedPrompts,
+ logger: logging.Logger,
+):
+ try:
+ say("How can I help you?")
+
+ prompts: List[Dict[str, str]] = [
+ {
+ "title": "Suggest names for my Slack app",
+ "message": "Can you suggest a few names for my Slack app? The app helps my teammates better organize information and plan priorities and action items.",
+ },
+ ]
+
+ thread_context = get_thread_context()
+ if thread_context is not None and thread_context.channel_id is not None:
+ summarize_channel = {
+ "title": "Summarize the referred channel",
+ "message": "Can you generate a brief summary of the referred channel?",
+ }
+ prompts.append(summarize_channel)
+
+ set_suggested_prompts(prompts=prompts)
+ except Exception as e:
+ logger.exception(f"Failed to handle an assistant_thread_started event: {e}", e)
+ say(f":warning: Something went wrong! ({e})")
+```
+
+You can send more complex messages to the user — see [Sending Block Kit alongside messages](#block-kit-interactions) for more info.
+
+### Handling thread context changes {#handling-thread-context-changes}
+
+When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app.
+
+If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](/messaging/message-metadata/) of the first reply from the app.
+
+As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history`, which are used to look up the stored message metadata that contains the thread context (via `get_thread_context`).
+
+To store context elsewhere, pass a custom `AssistantThreadContextStore` implementation to the `Assistant` constructor. We provide `FileAssistantThreadContextStore`, which is a reference implementation that uses the local file system. Since this reference implementation relies on local files, it's not advised for use in production. For production apps, we recommend creating a class that inherits `AssistantThreadContextStore`.
+
+```python
+from slack_bolt import FileAssistantThreadContextStore
+assistant = Assistant(thread_context_store=FileAssistantThreadContextStore())
+```
+
+### Handling the user response {#handling-user-response}
+
+When the user messages your app, the [`message.im`](/reference/events/message.im) event will be sent to your app.
+Messages sent to the app do not contain a [subtype](/reference/events/message#subtypes) and must be deduced based on their shape and any provided [message metadata](/messaging/message-metadata/).
+
+There are three utilities that are particularly useful in curating the user experience:
+* [`say`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.Say)
+* [`setTitle`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetTitle)
+* [`setStatus`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetStatus)
+
+Within the `setStatus` utility, you can cycle through strings passed into a `loading_messages` array.
+
+```python
# This listener is invoked when the human user sends a reply in the assistant thread
@assistant.user_message
def respond_in_assistant_thread(
- payload: dict,
- logger: logging.Logger,
- context: BoltContext,
- set_status: SetStatus,
client: WebClient,
+ context: BoltContext,
+ get_thread_context: GetThreadContext,
+ logger: logging.Logger,
+ payload: dict,
say: Say,
+ set_status: SetStatus,
):
try:
- # Tell the human user the assistant bot acknowledges the request and is working on it
- set_status("is typing...")
+ channel_id = payload["channel"]
+ team_id = payload["team"]
+ thread_ts = payload["thread_ts"]
+ user_id = payload["user"]
+ user_message = payload["text"]
+
+ set_status(
+ status="thinking...",
+ loading_messages=[
+ "Untangling the internet cables…",
+ "Consulting the office goldfish…",
+ "Convincing the AI to stop overthinking…",
+ ],
+ )
# Collect the conversation history with this user
- replies_in_thread = client.conversations_replies(
+ replies = client.conversations_replies(
channel=context.channel_id,
ts=context.thread_ts,
oldest=context.thread_ts,
limit=10,
)
messages_in_thread: List[Dict[str, str]] = []
- for message in replies_in_thread["messages"]:
+ for message in replies["messages"]:
role = "user" if message.get("bot_id") is None else "assistant"
messages_in_thread.append({"role": role, "content": message["text"]})
- # Pass the latest prompt and chat history to the LLM (call_llm is your own code)
returned_message = call_llm(messages_in_thread)
# Post the result in the assistant thread
@@ -93,23 +205,7 @@ def respond_in_assistant_thread(
app.use(assistant)
```
-While the `assistant_thread_started` and `assistant_thread_context_changed` events do provide Slack-client thread context information, the `message.im` event does not. Any subsequent user message events won't contain thread context data. For that reason, Bolt not only provides a way to store thread context — the `threadContextStore` property — but it also provides an instance that is utilized by default. This implementation relies on storing and retrieving [message metadata](/messaging/message-metadata/) as the user interacts with the app.
-
-If you do provide your own `threadContextStore` property, it must feature `get` and `save` methods.
-
-:::tip[Refer to the [module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.]
-:::
-
-## Handling a new thread {#handling-a-new-thread}
-
-When the user opens a new thread with your AI-enabled app, the [`assistant_thread_started`](/reference/events/assistant_thread_started) event will be sent to your app.
-
-:::tip[When a user opens an app thread while in a channel, the channel info is stored as the thread's `AssistantThreadContext` data.]
-
-You can grab that info by using the `get_thread_context` utility, as subsequent user message event payloads won't include the channel info.
-:::
-
-### Block Kit interactions in the app thread {#block-kit-interactions}
+### Sending Block Kit alongside messages {#block-kit-interactions}
For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](/messaging/message-metadata/) to trigger subsequent interactions with the user.
@@ -235,52 +331,182 @@ def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say:
...
```
-## Handling thread context changes {#handling-thread-context-changes}
+See the [_Adding and handling feedback_](#adding-and-handling-feedback) section for adding feedback buttons with Block Kit.
-When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app.
+## Text streaming in messages {#text-streaming}
-If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](/messaging/message-metadata/) of the first reply from the app.
+Three Web API methods work together to provide users a text streaming experience:
-As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history`, which are used to look up the stored message metadata that contains the thread context (via `get_thread_context`).
+* the [`chat.startStream`](/reference/methods/chat.startstream) method starts the text stream,
+* the [`chat.appendStream`](/reference/methods/chat.appendstream) method appends text to the stream, and
+* the [`chat.stopStream`](/reference/methods/chat.stopstream) method stops it.
+
+Since you're using Bolt for Python, built upon the Python Slack SDK, you can use the [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) utility to streamline all three aspects of streaming in your app's messages.
+
+The following example uses OpenAI's streaming API with the new `chat_stream()` functionality, but you can substitute it with the AI client of your choice.
-To store context elsewhere, pass a custom `AssistantThreadContextStore` implementation to the `Assistant` constructor. We provide `FileAssistantThreadContextStore`, which is a reference implementation that uses the local file system. Since this reference implementation relies on local files, it's not advised for use in production. For production apps, we recommend creating a class that inherits `AssistantThreadContextStore`.
```python
-from slack_bolt import FileAssistantThreadContextStore
-assistant = Assistant(thread_context_store=FileAssistantThreadContextStore())
-```
+import os
+from typing import List, Dict
+
+import openai
+from openai import Stream
+from openai.types.responses import ResponseStreamEvent
+
+DEFAULT_SYSTEM_CONTENT = """
+You're an assistant in a Slack workspace.
+Users in the workspace will ask you to help them write something or to think better about a specific topic.
+You'll respond to those questions in a professional way.
+When you include markdown text, convert them to Slack compatible ones.
+When a prompt has Slack's special syntax like <@USER_ID> or <#CHANNEL_ID>, you must keep them as-is in your response.
+"""
+
+def call_llm(
+ messages_in_thread: List[Dict[str, str]],
+ system_content: str = DEFAULT_SYSTEM_CONTENT,
+) -> Stream[ResponseStreamEvent]:
+ openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
+ messages = [{"role": "system", "content": system_content}]
+ messages.extend(messages_in_thread)
+ response = openai_client.responses.create(model="gpt-4o-mini", input=messages, stream=True)
+ return response
+
+@assistant.user_message
+def respond_in_assistant_thread(
+ ...
+):
+ try:
+ ...
+ replies = client.conversations_replies(
+ channel=context.channel_id,
+ ts=context.thread_ts,
+ oldest=context.thread_ts,
+ limit=10,
+ )
+ messages_in_thread: List[Dict[str, str]] = []
+ for message in replies["messages"]:
+ role = "user" if message.get("bot_id") is None else "assistant"
+ messages_in_thread.append({"role": role, "content": message["text"]})
-## Handling the user response {#handling-the-user-response}
+ returned_message = call_llm(messages_in_thread)
-When the user messages your app, the [`message.im`](/reference/events/message.im) event will be sent to your app.
+ streamer = client.chat_stream(
+ channel=channel_id,
+ recipient_team_id=team_id,
+ recipient_user_id=user_id,
+ thread_ts=thread_ts,
+ )
-Messages sent to the app do not contain a [subtype](/reference/events/message#subtypes) and must be deduced based on their shape and any provided [message metadata](/messaging/message-metadata/).
+ # Loop over OpenAI response stream
+ # https://platform.openai.com/docs/api-reference/responses/create
+ for event in returned_message:
+ if event.type == "response.output_text.delta":
+ streamer.append(markdown_text=f"{event.delta}")
+ else:
+ continue
-There are three utilities that are particularly useful in curating the user experience:
-* [`say`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.Say)
-* [`setTitle`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetTitle)
-* [`setStatus`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetStatus)
+ streamer.stop()
-```python
-...
-# This listener is invoked when the human user posts a reply
-@assistant.user_message
-def respond_to_user_messages(logger: logging.Logger, set_status: SetStatus, say: Say):
- try:
- set_status("is typing...")
- say("Please use the buttons in the first reply instead :bow:")
except Exception as e:
- logger.exception(f"Failed to respond to an inquiry: {e}")
- say(f":warning: Sorry, something went wrong during processing your request (error: {e})")
+ logger.exception(f"Failed to handle a user message event: {e}")
+ say(f":warning: Something went wrong! ({e})")
+```
-# Enable this assistant middleware in your Bolt app
-app.use(assistant)
+## Adding and handling feedback {#adding-and-handling-feedback}
+
+Use the [feedback buttons block element](/reference/block-kit/block-elements/feedback-buttons-element/) to allow users to immediately provide feedback regarding your app's responses. Here's a quick example:
+
+```py
+from typing import List
+from slack_sdk.models.blocks import Block, ContextActionsBlock, FeedbackButtonsElement, FeedbackButtonObject
+
+
+def create_feedback_block() -> List[Block]:
+ """
+ Create feedback block with thumbs up/down buttons
+
+ Returns:
+ Block Kit context_actions block
+ """
+ blocks: List[Block] = [
+ ContextActionsBlock(
+ elements=[
+ FeedbackButtonsElement(
+ action_id="feedback",
+ positive_button=FeedbackButtonObject(
+ text="Good Response",
+ accessibility_label="Submit positive feedback on this response",
+ value="good-feedback",
+ ),
+ negative_button=FeedbackButtonObject(
+ text="Bad Response",
+ accessibility_label="Submit negative feedback on this response",
+ value="bad-feedback",
+ ),
+ )
+ ]
+ )
+ ]
+ return blocks
+```
+
+Use the `chat_stream` utility to render the feedback block at the bottom of your app's message.
+
+```js
+...
+ streamer = client.chat_stream(
+ channel=channel_id,
+ recipient_team_id=team_id,
+ recipient_user_id=user_id,
+ thread_ts=thread_ts,
+ )
+
+ # Loop over OpenAI response stream
+ # https://platform.openai.com/docs/api-reference/responses/create
+ for event in returned_message:
+ if event.type == "response.output_text.delta":
+ streamer.append(markdown_text=f"{event.delta}")
+ else:
+ continue
+
+ feedback_block = create_feedback_block()
+ streamer.stop(blocks=feedback_block)
+...
```
-## Full example: Assistant Template {#full-example}
+Then add a response for when the user provides feedback.
+
+```python
+# Handle feedback buttons (thumbs up/down)
+def handle_feedback(ack, body, client, logger: logging.Logger):
+ try:
+ ack()
+ message_ts = body["message"]["ts"]
+ channel_id = body["channel"]["id"]
+ feedback_type = body["actions"][0]["value"]
+ is_positive = feedback_type == "good-feedback"
+
+ if is_positive:
+ client.chat_postEphemeral(
+ channel=channel_id,
+ user=body["user"]["id"],
+ thread_ts=message_ts,
+ text="We're glad you found this useful.",
+ )
+ else:
+ client.chat_postEphemeral(
+ channel=channel_id,
+ user=body["user"]["id"],
+ thread_ts=message_ts,
+ text="Sorry to hear that response wasn't up to par :slightly_frowning_face: Starting a new chat may help with AI mistakes and hallucinations.",
+ )
+
+ logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}")
+ except Exception as error:
+ logger.error(f":warning: Something went wrong! {error}")
+```
-Below is the `assistant.py` listener file of the [Assistant Template repo](https://github.com/slack-samples/bolt-python-assistant-template) we've created for you to build off of.
+## Full example: App Agent Template {#app-agent-template}
-```py reference title="assistant.py"
-https://github.com/slack-samples/bolt-python-assistant-template/blob/main/listeners/assistant.py
-```
\ No newline at end of file
+Want to see the functionality described throughout this guide in action? We've created a [App Agent Template](https://github.com/slack-samples/bolt-python-assistant-template) repo for you to build off of.
diff --git a/docs/english/concepts/message-sending.md b/docs/english/concepts/message-sending.md
index 228a7b6b8..730af76ea 100644
--- a/docs/english/concepts/message-sending.md
+++ b/docs/english/concepts/message-sending.md
@@ -5,6 +5,7 @@ Within your listener function, `say()` is available whenever there is an associa
In the case that you'd like to send a message outside of a listener or you want to do something more advanced (like handle specific errors), you can call `client.chat_postMessage` [using the client attached to your Bolt instance](/tools/bolt-python/concepts/web-api).
Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.
+
```python
# Listens for messages containing "knock knock" and responds with an italicized "who's there?"
@app.message("knock knock")
@@ -38,4 +39,61 @@ def show_datepicker(event, say):
blocks=blocks,
text="Pick a date for me to remind you"
)
-```
\ No newline at end of file
+```
+
+## Streaming messages {#streaming-messages}
+
+You can have your app's messages stream in to replicate conventional AI chatbot behavior. This is done through three Web API methods:
+
+* [`chat_startStream`](/reference/methods/chat.startstream)
+* [`chat_appendStream`](/reference/methods/chat.appendstream)
+* [`chat_stopStream`](/reference/methods/chat.stopstream)
+
+The Python Slack SDK provides a [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility to streamline calling these methods. Here's an excerpt from our [Assistant template app](https://github.com/slack-samples/bolt-python-assistant-template)
+
+```python
+streamer = client.chat_stream(
+ channel=channel_id,
+ recipient_team_id=team_id,
+ recipient_user_id=user_id,
+ thread_ts=thread_ts,
+)
+
+# response from your LLM of choice; OpenAI is the example here
+for event in returned_message:
+ if event.type == "response.output_text.delta":
+ streamer.append(markdown_text=f"{event.delta}")
+ else:
+ continue
+
+feedback_block = create_feedback_block()
+streamer.stop(blocks=feedback_block)
+```
+
+In that example, a [feedback buttons](/reference/block-kit/block-elements/feedback-buttons-element) block element is passed to `streamer.stop` to provide feedback buttons to the user at the bottom of the message. Interaction with these buttons will send a block action event to your app to receive the feedback.
+
+```python
+def create_feedback_block() -> List[Block]:
+ blocks: List[Block] = [
+ ContextActionsBlock(
+ elements=[
+ FeedbackButtonsElement(
+ action_id="feedback",
+ positive_button=FeedbackButtonObject(
+ text="Good Response",
+ accessibility_label="Submit positive feedback on this response",
+ value="good-feedback",
+ ),
+ negative_button=FeedbackButtonObject(
+ text="Bad Response",
+ accessibility_label="Submit negative feedback on this response",
+ value="bad-feedback",
+ ),
+ )
+ ]
+ )
+ ]
+ return blocks
+```
+
+For information on calling the `chat_*Stream` API methods without the helper utility, see the [_Sending streaming messages_](/tools/python-slack-sdk/web#sending-streaming-messages) section of the Python Slack SDK docs.
\ No newline at end of file
From e742d14dbca24ba36090f3ec5ea3da4cb37a8e90 Mon Sep 17 00:00:00 2001
From: Maria Alejandra <104795114+srtaalej@users.noreply.github.com>
Date: Mon, 6 Oct 2025 15:42:24 -0400
Subject: [PATCH 10/13] docs: precede code snippet with standard sentence
formatting (#1386)
---
docs/english/concepts/message-sending.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/english/concepts/message-sending.md b/docs/english/concepts/message-sending.md
index 730af76ea..3fa992e20 100644
--- a/docs/english/concepts/message-sending.md
+++ b/docs/english/concepts/message-sending.md
@@ -49,7 +49,7 @@ You can have your app's messages stream in to replicate conventional AI chatbot
* [`chat_appendStream`](/reference/methods/chat.appendstream)
* [`chat_stopStream`](/reference/methods/chat.stopstream)
-The Python Slack SDK provides a [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility to streamline calling these methods. Here's an excerpt from our [Assistant template app](https://github.com/slack-samples/bolt-python-assistant-template)
+The Python Slack SDK provides a [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility to streamline calling these methods. Here's an excerpt from our [Assistant template app](https://github.com/slack-samples/bolt-python-assistant-template):
```python
streamer = client.chat_stream(
From ceeabcfc7e816184b098ab229f3de92985ed0eb3 Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Mon, 6 Oct 2025 16:16:08 -0700
Subject: [PATCH 11/13] chore: bump slack_sdk to 3.37.0
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index aab53aae6..f6e200009 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk==3.36.0.dev7"]
+dependencies = ["slack_sdk==3.37.0"]
[project.urls]
From 9e5bb32844e35dd01e9763bcf03f869851e674b4 Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Mon, 6 Oct 2025 16:22:05 -0700
Subject: [PATCH 12/13] fix: use expected versions before a release
Co-authored-by: Michael Brooks
---
pyproject.toml | 2 +-
slack_bolt/version.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index f6e200009..5361ef1b4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,7 @@ classifiers = [
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
-dependencies = ["slack_sdk==3.37.0"]
+dependencies = ["slack_sdk>=3.37.0,<4"]
[project.urls]
diff --git a/slack_bolt/version.py b/slack_bolt/version.py
index 8a11da6a7..7f9c19341 100644
--- a/slack_bolt/version.py
+++ b/slack_bolt/version.py
@@ -1,3 +1,3 @@
"""Check the latest version at https://pypi.org/project/slack-bolt/"""
-__version__ = "1.26.0.dev4"
+__version__ = "1.25.0"
From ca89676ff65d8e17eed3c51742a41677b2751293 Mon Sep 17 00:00:00 2001
From: Eden Zimbelman
Date: Mon, 6 Oct 2025 16:27:49 -0700
Subject: [PATCH 13/13] docs: match the sample app implementation
Co-authored-by: Maria Alejandra <104795114+srtaalej@users.noreply.github.com>
---
docs/english/concepts/message-sending.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/english/concepts/message-sending.md b/docs/english/concepts/message-sending.md
index 3fa992e20..9741bb396 100644
--- a/docs/english/concepts/message-sending.md
+++ b/docs/english/concepts/message-sending.md
@@ -59,7 +59,8 @@ streamer = client.chat_stream(
thread_ts=thread_ts,
)
-# response from your LLM of choice; OpenAI is the example here
+# Loop over OpenAI response stream
+# https://platform.openai.com/docs/api-reference/responses/create
for event in returned_message:
if event.type == "response.output_text.delta":
streamer.append(markdown_text=f"{event.delta}")