Skip to content

Commit cd52d19

Browse files
srtaalejzimeg
andauthored
feat: add agent set status to BoltAgent (#1441)
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
1 parent 1ad642e commit cd52d19

File tree

4 files changed

+288
-3
lines changed

4 files changed

+288
-3
lines changed

slack_bolt/agent/agent.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from typing import Optional
1+
from typing import List, Optional
22

33
from slack_sdk import WebClient
4+
from slack_sdk.web import SlackResponse
45
from slack_sdk.web.chat_stream import ChatStream
56

67

@@ -71,3 +72,32 @@ def chat_stream(
7172
recipient_user_id=recipient_user_id or self._user_id,
7273
**kwargs,
7374
)
75+
76+
def set_status(
77+
self,
78+
*,
79+
status: str,
80+
loading_messages: Optional[List[str]] = None,
81+
channel: Optional[str] = None,
82+
thread_ts: Optional[str] = None,
83+
**kwargs,
84+
) -> SlackResponse:
85+
"""Sets the status of an assistant thread.
86+
87+
Args:
88+
status: The status text to display.
89+
loading_messages: Optional list of loading messages to cycle through.
90+
channel: Channel ID. Defaults to the channel from the event context.
91+
thread_ts: Thread timestamp. Defaults to the thread_ts from the event context.
92+
**kwargs: Additional arguments passed to ``WebClient.assistant_threads_setStatus()``.
93+
94+
Returns:
95+
``SlackResponse`` from the API call.
96+
"""
97+
return self._client.assistant_threads_setStatus(
98+
channel_id=channel or self._channel_id, # type: ignore[arg-type]
99+
thread_ts=thread_ts or self._thread_ts, # type: ignore[arg-type]
100+
status=status,
101+
loading_messages=loading_messages,
102+
**kwargs,
103+
)

slack_bolt/agent/async_agent.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from typing import Optional
1+
from typing import List, Optional
22

3-
from slack_sdk.web.async_client import AsyncWebClient
3+
from slack_sdk.web.async_client import AsyncSlackResponse, AsyncWebClient
44
from slack_sdk.web.async_chat_stream import AsyncChatStream
55

66

@@ -68,3 +68,32 @@ async def chat_stream(
6868
recipient_user_id=recipient_user_id or self._user_id,
6969
**kwargs,
7070
)
71+
72+
async def set_status(
73+
self,
74+
*,
75+
status: str,
76+
loading_messages: Optional[List[str]] = None,
77+
channel: Optional[str] = None,
78+
thread_ts: Optional[str] = None,
79+
**kwargs,
80+
) -> AsyncSlackResponse:
81+
"""Sets the status of an assistant thread.
82+
83+
Args:
84+
status: The status text to display.
85+
loading_messages: Optional list of loading messages to cycle through.
86+
channel: Channel ID. Defaults to the channel from the event context.
87+
thread_ts: Thread timestamp. Defaults to the thread_ts from the event context.
88+
**kwargs: Additional arguments passed to ``AsyncWebClient.assistant_threads_setStatus()``.
89+
90+
Returns:
91+
``AsyncSlackResponse`` from the API call.
92+
"""
93+
return await self._client.assistant_threads_setStatus(
94+
channel_id=channel or self._channel_id, # type: ignore[arg-type]
95+
thread_ts=thread_ts or self._thread_ts, # type: ignore[arg-type]
96+
status=status,
97+
loading_messages=loading_messages,
98+
**kwargs,
99+
)

tests/slack_bolt/agent/test_agent.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,111 @@ def test_chat_stream_passes_extra_kwargs(self):
9292
buffer_size=512,
9393
)
9494

95+
def test_set_status_uses_context_defaults(self):
96+
"""BoltAgent.set_status() passes context defaults to WebClient.assistant_threads_setStatus()."""
97+
client = MagicMock(spec=WebClient)
98+
client.assistant_threads_setStatus.return_value = MagicMock()
99+
100+
agent = BoltAgent(
101+
client=client,
102+
channel_id="C111",
103+
thread_ts="1234567890.123456",
104+
team_id="T111",
105+
user_id="W222",
106+
)
107+
agent.set_status(status="Thinking...")
108+
109+
client.assistant_threads_setStatus.assert_called_once_with(
110+
channel_id="C111",
111+
thread_ts="1234567890.123456",
112+
status="Thinking...",
113+
loading_messages=None,
114+
)
115+
116+
def test_set_status_with_loading_messages(self):
117+
"""BoltAgent.set_status() forwards loading_messages."""
118+
client = MagicMock(spec=WebClient)
119+
client.assistant_threads_setStatus.return_value = MagicMock()
120+
121+
agent = BoltAgent(
122+
client=client,
123+
channel_id="C111",
124+
thread_ts="1234567890.123456",
125+
team_id="T111",
126+
user_id="W222",
127+
)
128+
agent.set_status(
129+
status="Thinking...",
130+
loading_messages=["Sitting...", "Waiting..."],
131+
)
132+
133+
client.assistant_threads_setStatus.assert_called_once_with(
134+
channel_id="C111",
135+
thread_ts="1234567890.123456",
136+
status="Thinking...",
137+
loading_messages=["Sitting...", "Waiting..."],
138+
)
139+
140+
def test_set_status_overrides_context_defaults(self):
141+
"""Explicit channel/thread_ts override context defaults."""
142+
client = MagicMock(spec=WebClient)
143+
client.assistant_threads_setStatus.return_value = MagicMock()
144+
145+
agent = BoltAgent(
146+
client=client,
147+
channel_id="C111",
148+
thread_ts="1234567890.123456",
149+
team_id="T111",
150+
user_id="W222",
151+
)
152+
agent.set_status(
153+
status="Thinking...",
154+
channel="C999",
155+
thread_ts="9999999999.999999",
156+
)
157+
158+
client.assistant_threads_setStatus.assert_called_once_with(
159+
channel_id="C999",
160+
thread_ts="9999999999.999999",
161+
status="Thinking...",
162+
loading_messages=None,
163+
)
164+
165+
def test_set_status_passes_extra_kwargs(self):
166+
"""Extra kwargs are forwarded to WebClient.assistant_threads_setStatus()."""
167+
client = MagicMock(spec=WebClient)
168+
client.assistant_threads_setStatus.return_value = MagicMock()
169+
170+
agent = BoltAgent(
171+
client=client,
172+
channel_id="C111",
173+
thread_ts="1234567890.123456",
174+
team_id="T111",
175+
user_id="W222",
176+
)
177+
agent.set_status(status="Thinking...", token="xoxb-override")
178+
179+
client.assistant_threads_setStatus.assert_called_once_with(
180+
channel_id="C111",
181+
thread_ts="1234567890.123456",
182+
status="Thinking...",
183+
loading_messages=None,
184+
token="xoxb-override",
185+
)
186+
187+
def test_set_status_requires_status(self):
188+
"""set_status() raises TypeError when status is not provided."""
189+
client = MagicMock(spec=WebClient)
190+
agent = BoltAgent(
191+
client=client,
192+
channel_id="C111",
193+
thread_ts="1234567890.123456",
194+
team_id="T111",
195+
user_id="W222",
196+
)
197+
with pytest.raises(TypeError):
198+
agent.set_status()
199+
95200
def test_import_from_slack_bolt(self):
96201
from slack_bolt import BoltAgent as ImportedBoltAgent
97202

tests/slack_bolt_async/agent/test_async_agent.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ async def fake_chat_stream(**kwargs):
1818
return fake_chat_stream, call_tracker, mock_stream
1919

2020

21+
def _make_async_api_mock():
22+
mock_response = MagicMock()
23+
call_tracker = MagicMock()
24+
25+
async def fake_api_call(**kwargs):
26+
call_tracker(**kwargs)
27+
return mock_response
28+
29+
return fake_api_call, call_tracker, mock_response
30+
31+
2132
class TestAsyncBoltAgent:
2233
@pytest.mark.asyncio
2334
async def test_chat_stream_uses_context_defaults(self):
@@ -107,6 +118,116 @@ async def test_chat_stream_passes_extra_kwargs(self):
107118
buffer_size=512,
108119
)
109120

121+
@pytest.mark.asyncio
122+
async def test_set_status_uses_context_defaults(self):
123+
"""AsyncBoltAgent.set_status() passes context defaults to AsyncWebClient.assistant_threads_setStatus()."""
124+
client = MagicMock(spec=AsyncWebClient)
125+
client.assistant_threads_setStatus, call_tracker, _ = _make_async_api_mock()
126+
127+
agent = AsyncBoltAgent(
128+
client=client,
129+
channel_id="C111",
130+
thread_ts="1234567890.123456",
131+
team_id="T111",
132+
user_id="W222",
133+
)
134+
await agent.set_status(status="Thinking...")
135+
136+
call_tracker.assert_called_once_with(
137+
channel_id="C111",
138+
thread_ts="1234567890.123456",
139+
status="Thinking...",
140+
loading_messages=None,
141+
)
142+
143+
@pytest.mark.asyncio
144+
async def test_set_status_with_loading_messages(self):
145+
"""AsyncBoltAgent.set_status() forwards loading_messages."""
146+
client = MagicMock(spec=AsyncWebClient)
147+
client.assistant_threads_setStatus, call_tracker, _ = _make_async_api_mock()
148+
149+
agent = AsyncBoltAgent(
150+
client=client,
151+
channel_id="C111",
152+
thread_ts="1234567890.123456",
153+
team_id="T111",
154+
user_id="W222",
155+
)
156+
await agent.set_status(
157+
status="Thinking...",
158+
loading_messages=["Sitting...", "Waiting..."],
159+
)
160+
161+
call_tracker.assert_called_once_with(
162+
channel_id="C111",
163+
thread_ts="1234567890.123456",
164+
status="Thinking...",
165+
loading_messages=["Sitting...", "Waiting..."],
166+
)
167+
168+
@pytest.mark.asyncio
169+
async def test_set_status_overrides_context_defaults(self):
170+
"""Explicit channel/thread_ts override context defaults."""
171+
client = MagicMock(spec=AsyncWebClient)
172+
client.assistant_threads_setStatus, call_tracker, _ = _make_async_api_mock()
173+
174+
agent = AsyncBoltAgent(
175+
client=client,
176+
channel_id="C111",
177+
thread_ts="1234567890.123456",
178+
team_id="T111",
179+
user_id="W222",
180+
)
181+
await agent.set_status(
182+
status="Thinking...",
183+
channel="C999",
184+
thread_ts="9999999999.999999",
185+
)
186+
187+
call_tracker.assert_called_once_with(
188+
channel_id="C999",
189+
thread_ts="9999999999.999999",
190+
status="Thinking...",
191+
loading_messages=None,
192+
)
193+
194+
@pytest.mark.asyncio
195+
async def test_set_status_passes_extra_kwargs(self):
196+
"""Extra kwargs are forwarded to AsyncWebClient.assistant_threads_setStatus()."""
197+
client = MagicMock(spec=AsyncWebClient)
198+
client.assistant_threads_setStatus, call_tracker, _ = _make_async_api_mock()
199+
200+
agent = AsyncBoltAgent(
201+
client=client,
202+
channel_id="C111",
203+
thread_ts="1234567890.123456",
204+
team_id="T111",
205+
user_id="W222",
206+
)
207+
await agent.set_status(status="Thinking...", token="xoxb-override")
208+
209+
call_tracker.assert_called_once_with(
210+
channel_id="C111",
211+
thread_ts="1234567890.123456",
212+
status="Thinking...",
213+
loading_messages=None,
214+
token="xoxb-override",
215+
)
216+
217+
@pytest.mark.asyncio
218+
async def test_set_status_requires_status(self):
219+
"""set_status() raises TypeError when status is not provided."""
220+
client = MagicMock(spec=AsyncWebClient)
221+
agent = AsyncBoltAgent(
222+
client=client,
223+
channel_id="C111",
224+
thread_ts="1234567890.123456",
225+
team_id="T111",
226+
user_id="W222",
227+
)
228+
with pytest.raises(TypeError):
229+
await agent.set_status()
230+
110231
@pytest.mark.asyncio
111232
async def test_import_from_agent_module(self):
112233
from slack_bolt.agent.async_agent import AsyncBoltAgent as ImportedAsyncBoltAgent

0 commit comments

Comments
 (0)