Skip to content

Commit f1bc61f

Browse files
feat: add support for say_stream utility (#1462)
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
1 parent 785b813 commit f1bc61f

File tree

18 files changed

+913
-0
lines changed

18 files changed

+913
-0
lines changed

slack_bolt/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .context.fail import Fail
1515
from .context.respond import Respond
1616
from .context.say import Say
17+
from .context.say_stream import SayStream
1718
from .kwargs_injection import Args
1819
from .listener import Listener
1920
from .listener_matcher import CustomListenerMatcher
@@ -42,6 +43,7 @@
4243
"Fail",
4344
"Respond",
4445
"Say",
46+
"SayStream",
4547
"Args",
4648
"Listener",
4749
"CustomListenerMatcher",

slack_bolt/async_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,15 @@ async def command(ack, body, respond):
5959
from .context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts
6060
from .context.get_thread_context.async_get_thread_context import AsyncGetThreadContext
6161
from .context.save_thread_context.async_save_thread_context import AsyncSaveThreadContext
62+
from .context.say_stream.async_say_stream import AsyncSayStream
6263

6364
__all__ = [
6465
"AsyncApp",
6566
"AsyncAck",
6667
"AsyncBoltContext",
6768
"AsyncRespond",
6869
"AsyncSay",
70+
"AsyncSayStream",
6971
"AsyncListener",
7072
"AsyncCustomListenerMatcher",
7173
"AsyncBoltRequest",

slack_bolt/context/async_context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from slack_bolt.context.get_thread_context.async_get_thread_context import AsyncGetThreadContext
1111
from slack_bolt.context.save_thread_context.async_save_thread_context import AsyncSaveThreadContext
1212
from slack_bolt.context.say.async_say import AsyncSay
13+
from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
1314
from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
1415
from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts
1516
from slack_bolt.context.set_title.async_set_title import AsyncSetTitle
@@ -203,6 +204,10 @@ def set_suggested_prompts(self) -> Optional[AsyncSetSuggestedPrompts]:
203204
def get_thread_context(self) -> Optional[AsyncGetThreadContext]:
204205
return self.get("get_thread_context")
205206

207+
@property
208+
def say_stream(self) -> Optional[AsyncSayStream]:
209+
return self.get("say_stream")
210+
206211
@property
207212
def save_thread_context(self) -> Optional[AsyncSaveThreadContext]:
208213
return self.get("save_thread_context")

slack_bolt/context/base_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class BaseContext(dict):
3838
"set_status",
3939
"set_title",
4040
"set_suggested_prompts",
41+
"say_stream",
4142
]
4243
# Note that these items are not copyable, so when you add new items to this list,
4344
# you must modify ThreadListenerRunner/AsyncioListenerRunner's _build_lazy_request method to pass the values.

slack_bolt/context/context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from slack_bolt.context.respond import Respond
1111
from slack_bolt.context.save_thread_context import SaveThreadContext
1212
from slack_bolt.context.say import Say
13+
from slack_bolt.context.say_stream import SayStream
1314
from slack_bolt.context.set_status import SetStatus
1415
from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts
1516
from slack_bolt.context.set_title import SetTitle
@@ -204,6 +205,10 @@ def set_suggested_prompts(self) -> Optional[SetSuggestedPrompts]:
204205
def get_thread_context(self) -> Optional[GetThreadContext]:
205206
return self.get("get_thread_context")
206207

208+
@property
209+
def say_stream(self) -> Optional[SayStream]:
210+
return self.get("say_stream")
211+
207212
@property
208213
def save_thread_context(self) -> Optional[SaveThreadContext]:
209214
return self.get("save_thread_context")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Don't add async module imports here
2+
from .say_stream import SayStream
3+
4+
__all__ = [
5+
"SayStream",
6+
]
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import warnings
2+
from typing import Optional
3+
4+
from slack_sdk.web.async_client import AsyncWebClient
5+
from slack_sdk.web.async_chat_stream import AsyncChatStream
6+
7+
from slack_bolt.warning import ExperimentalWarning
8+
9+
10+
class AsyncSayStream:
11+
client: AsyncWebClient
12+
channel: Optional[str]
13+
recipient_team_id: Optional[str]
14+
recipient_user_id: Optional[str]
15+
thread_ts: Optional[str]
16+
17+
def __init__(
18+
self,
19+
*,
20+
client: AsyncWebClient,
21+
channel: Optional[str] = None,
22+
recipient_team_id: Optional[str] = None,
23+
recipient_user_id: Optional[str] = None,
24+
thread_ts: Optional[str] = None,
25+
):
26+
self.client = client
27+
self.channel = channel
28+
self.recipient_team_id = recipient_team_id
29+
self.recipient_user_id = recipient_user_id
30+
self.thread_ts = thread_ts
31+
32+
async def __call__(
33+
self,
34+
*,
35+
buffer_size: Optional[int] = None,
36+
channel: Optional[str] = None,
37+
recipient_team_id: Optional[str] = None,
38+
recipient_user_id: Optional[str] = None,
39+
thread_ts: Optional[str] = None,
40+
**kwargs,
41+
) -> AsyncChatStream:
42+
"""Starts a new chat stream with context.
43+
44+
Warning: This is an experimental feature and may change in future versions.
45+
"""
46+
warnings.warn(
47+
"say_stream is experimental and may change in future versions.",
48+
category=ExperimentalWarning,
49+
stacklevel=2,
50+
)
51+
52+
channel = channel or self.channel
53+
thread_ts = thread_ts or self.thread_ts
54+
if channel is None:
55+
raise ValueError("say_stream without channel here is unsupported")
56+
if thread_ts is None:
57+
raise ValueError("say_stream without thread_ts here is unsupported")
58+
59+
if buffer_size is not None:
60+
return await self.client.chat_stream(
61+
buffer_size=buffer_size,
62+
channel=channel,
63+
recipient_team_id=recipient_team_id or self.recipient_team_id,
64+
recipient_user_id=recipient_user_id or self.recipient_user_id,
65+
thread_ts=thread_ts,
66+
**kwargs,
67+
)
68+
return await self.client.chat_stream(
69+
channel=channel,
70+
recipient_team_id=recipient_team_id or self.recipient_team_id,
71+
recipient_user_id=recipient_user_id or self.recipient_user_id,
72+
thread_ts=thread_ts,
73+
**kwargs,
74+
)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import warnings
2+
from typing import Optional
3+
4+
from slack_sdk import WebClient
5+
from slack_sdk.web.chat_stream import ChatStream
6+
7+
from slack_bolt.warning import ExperimentalWarning
8+
9+
10+
class SayStream:
11+
client: WebClient
12+
channel: Optional[str]
13+
recipient_team_id: Optional[str]
14+
recipient_user_id: Optional[str]
15+
thread_ts: Optional[str]
16+
17+
def __init__(
18+
self,
19+
*,
20+
client: WebClient,
21+
channel: Optional[str] = None,
22+
recipient_team_id: Optional[str] = None,
23+
recipient_user_id: Optional[str] = None,
24+
thread_ts: Optional[str] = None,
25+
):
26+
self.client = client
27+
self.channel = channel
28+
self.recipient_team_id = recipient_team_id
29+
self.recipient_user_id = recipient_user_id
30+
self.thread_ts = thread_ts
31+
32+
def __call__(
33+
self,
34+
*,
35+
buffer_size: Optional[int] = None,
36+
channel: Optional[str] = None,
37+
recipient_team_id: Optional[str] = None,
38+
recipient_user_id: Optional[str] = None,
39+
thread_ts: Optional[str] = None,
40+
**kwargs,
41+
) -> ChatStream:
42+
"""Starts a new chat stream with context.
43+
44+
Warning: This is an experimental feature and may change in future versions.
45+
"""
46+
warnings.warn(
47+
"say_stream is experimental and may change in future versions.",
48+
category=ExperimentalWarning,
49+
stacklevel=2,
50+
)
51+
52+
channel = channel or self.channel
53+
thread_ts = thread_ts or self.thread_ts
54+
if channel is None:
55+
raise ValueError("say_stream without channel here is unsupported")
56+
if thread_ts is None:
57+
raise ValueError("say_stream without thread_ts here is unsupported")
58+
59+
if buffer_size is not None:
60+
return self.client.chat_stream(
61+
buffer_size=buffer_size,
62+
channel=channel,
63+
recipient_team_id=recipient_team_id or self.recipient_team_id,
64+
recipient_user_id=recipient_user_id or self.recipient_user_id,
65+
thread_ts=thread_ts,
66+
**kwargs,
67+
)
68+
return self.client.chat_stream(
69+
channel=channel,
70+
recipient_team_id=recipient_team_id or self.recipient_team_id,
71+
recipient_user_id=recipient_user_id or self.recipient_user_id,
72+
thread_ts=thread_ts,
73+
**kwargs,
74+
)

slack_bolt/kwargs_injection/args.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from slack_bolt.agent.agent import BoltAgent
1212
from slack_bolt.context.save_thread_context import SaveThreadContext
1313
from slack_bolt.context.say import Say
14+
from slack_bolt.context.say_stream import SayStream
1415
from slack_bolt.context.set_status import SetStatus
1516
from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts
1617
from slack_bolt.context.set_title import SetTitle
@@ -105,6 +106,8 @@ def handle_buttons(args):
105106
"""`save_thread_context()` utility function for AI Agents & Assistants"""
106107
agent: Optional[BoltAgent]
107108
"""`agent` listener argument for AI Agents & Assistants"""
109+
say_stream: Optional[SayStream]
110+
"""`say_stream()` utility function for AI Agents & Assistants"""
108111
# middleware
109112
next: Callable[[], None]
110113
"""`next()` utility function, which tells the middleware chain that it can continue with the next one"""
@@ -139,6 +142,7 @@ def __init__(
139142
get_thread_context: Optional[GetThreadContext] = None,
140143
save_thread_context: Optional[SaveThreadContext] = None,
141144
agent: Optional[BoltAgent] = None,
145+
say_stream: Optional[SayStream] = None,
142146
# As this method is not supposed to be invoked by bolt-python users,
143147
# the naming conflict with the built-in one affects
144148
# only the internals of this method
@@ -173,6 +177,7 @@ def __init__(
173177
self.get_thread_context = get_thread_context
174178
self.save_thread_context = save_thread_context
175179
self.agent = agent
180+
self.say_stream = say_stream
176181

177182
self.next: Callable[[], None] = next
178183
self.next_: Callable[[], None] = next

slack_bolt/kwargs_injection/async_args.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from slack_bolt.context.get_thread_context.async_get_thread_context import AsyncGetThreadContext
1111
from slack_bolt.context.save_thread_context.async_save_thread_context import AsyncSaveThreadContext
1212
from slack_bolt.context.say.async_say import AsyncSay
13+
from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
1314
from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
1415
from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts
1516
from slack_bolt.context.set_title.async_set_title import AsyncSetTitle
@@ -104,6 +105,8 @@ async def handle_buttons(args):
104105
"""`save_thread_context()` utility function for AI Agents & Assistants"""
105106
agent: Optional[AsyncBoltAgent]
106107
"""`agent` listener argument for AI Agents & Assistants"""
108+
say_stream: Optional[AsyncSayStream]
109+
"""`say_stream()` utility function for AI Agents & Assistants"""
107110
# middleware
108111
next: Callable[[], Awaitable[None]]
109112
"""`next()` utility function, which tells the middleware chain that it can continue with the next one"""
@@ -138,6 +141,7 @@ def __init__(
138141
get_thread_context: Optional[AsyncGetThreadContext] = None,
139142
save_thread_context: Optional[AsyncSaveThreadContext] = None,
140143
agent: Optional[AsyncBoltAgent] = None,
144+
say_stream: Optional[AsyncSayStream] = None,
141145
next: Callable[[], Awaitable[None]],
142146
**kwargs, # noqa
143147
):
@@ -169,6 +173,7 @@ def __init__(
169173
self.get_thread_context = get_thread_context
170174
self.save_thread_context = save_thread_context
171175
self.agent = agent
176+
self.say_stream = say_stream
172177

173178
self.next: Callable[[], Awaitable[None]] = next
174179
self.next_: Callable[[], Awaitable[None]] = next

0 commit comments

Comments
 (0)