From 775978dec3cad7799c2de652fa702d797bfff115 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 27 Apr 2026 13:58:18 -0700 Subject: [PATCH 1/2] feat: add authorship arguments to assistant threads and chat stream Add `icon_emoji`, `icon_url`, and `username` parameters to the `assistant.threads.setStatus` and `chat.startStream` Web API methods. Co-Authored-By: Claude --- slack_sdk/web/async_client.py | 19 ++++++++++++- slack_sdk/web/client.py | 19 ++++++++++++- slack_sdk/web/legacy_client.py | 19 ++++++++++++- .../web/test_web_client_coverage.py | 28 +++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index c0ae0bfcf..108ebe09d 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -2091,13 +2091,24 @@ async def assistant_threads_setStatus( thread_ts: str, status: str, loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> AsyncSlackResponse: """Set the status for an AI assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setStatus """ kwargs.update( - {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + { + "channel_id": channel_id, + "thread_ts": thread_ts, + "status": status, + "loading_messages": loading_messages, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, + } ) kwargs = _remove_none_values(kwargs) return await self.api_call("assistant.threads.setStatus", json=kwargs) @@ -2903,6 +2914,9 @@ async def chat_startStream( recipient_user_id: Optional[str] = None, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, task_display_mode: Optional[str] = None, # timeline, plan + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> AsyncSlackResponse: """Starts a new streaming conversation. @@ -2917,6 +2931,9 @@ async def chat_startStream( "recipient_user_id": recipient_user_id, "chunks": chunks, "task_display_mode": task_display_mode, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, } ) _parse_web_class_objects(kwargs) diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 9f7360849..940d8fbb2 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -2081,13 +2081,24 @@ def assistant_threads_setStatus( thread_ts: str, status: str, loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> SlackResponse: """Set the status for an AI assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setStatus """ kwargs.update( - {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + { + "channel_id": channel_id, + "thread_ts": thread_ts, + "status": status, + "loading_messages": loading_messages, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, + } ) kwargs = _remove_none_values(kwargs) return self.api_call("assistant.threads.setStatus", json=kwargs) @@ -2893,6 +2904,9 @@ def chat_startStream( recipient_user_id: Optional[str] = None, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, task_display_mode: Optional[str] = None, # timeline, plan + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> SlackResponse: """Starts a new streaming conversation. @@ -2907,6 +2921,9 @@ def chat_startStream( "recipient_user_id": recipient_user_id, "chunks": chunks, "task_display_mode": task_display_mode, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, } ) _parse_web_class_objects(kwargs) diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index 8d3b65353..db785bbbc 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -2092,13 +2092,24 @@ def assistant_threads_setStatus( thread_ts: str, status: str, loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Set the status for an AI assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setStatus """ kwargs.update( - {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + { + "channel_id": channel_id, + "thread_ts": thread_ts, + "status": status, + "loading_messages": loading_messages, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, + } ) kwargs = _remove_none_values(kwargs) return self.api_call("assistant.threads.setStatus", json=kwargs) @@ -2904,6 +2915,9 @@ def chat_startStream( recipient_user_id: Optional[str] = None, chunks: Optional[Sequence[Union[Dict, Chunk]]] = None, task_display_mode: Optional[str] = None, # timeline, plan + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> Union[Future, SlackResponse]: """Starts a new streaming conversation. @@ -2918,6 +2932,9 @@ def chat_startStream( "recipient_user_id": recipient_user_id, "chunks": chunks, "task_display_mode": task_display_mode, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, } ) _parse_web_class_objects(kwargs) diff --git a/tests/slack_sdk_async/web/test_web_client_coverage.py b/tests/slack_sdk_async/web/test_web_client_coverage.py index 805d0129f..7a16ee61a 100644 --- a/tests/slack_sdk_async/web/test_web_client_coverage.py +++ b/tests/slack_sdk_async/web/test_web_client_coverage.py @@ -441,6 +441,19 @@ async def run_method(self, method_name, method, async_method): status="is typing...", loading_states=["Thinking...", "Writing..."], ) + method( + channel_id="D111", + thread_ts="111.222", + status="counting...", + username="Abacus", + icon_emoji="abacus", + ) + await async_method( + channel_id="D111", + thread_ts="111.222", + status="dreaming...", + icon_url="https://example.com/clouds-square.png", + ) elif method_name == "assistant_threads_setTitle": self.api_methods_to_call.remove(method(channel_id="D111", thread_ts="111.222", title="New chat")["method"]) await async_method(channel_id="D111", thread_ts="111.222", title="New chat") @@ -587,6 +600,21 @@ async def run_method(self, method_name, method, async_method): await async_method(channel="C123", thread_ts="123.123") method(channel="C123", thread_ts="123.123", recipient_team_id="T123", recipient_user_id="U123") await async_method(channel="C123", thread_ts="123.123", recipient_team_id="T123", recipient_user_id="U123") + method( + channel="C123", + thread_ts="123.123", + recipient_team_id="T123", + recipient_user_id="U123", + username="Abacus", + icon_emoji="abacus", + ) + await async_method( + channel="C123", + thread_ts="123.123", + recipient_team_id="T123", + recipient_user_id="U123", + icon_url="https://example.com/clouds-square.png", + ) elif method_name == "chat_stopStream": self.api_methods_to_call.remove( method(channel="C123", ts="123.123", blocks=[{"type": "markdown", "text": "**twelve**"}])["method"] From c6238223d7a2bc71f773fcb6e237f1ee8e648f46 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 4 May 2026 13:04:51 -0700 Subject: [PATCH 2/2] feat: add authorship arguments to chat stream helper --- slack_sdk/web/async_chat_stream.py | 9 +++++++++ slack_sdk/web/async_client.py | 9 +++++++++ slack_sdk/web/chat_stream.py | 9 +++++++++ slack_sdk/web/client.py | 9 +++++++++ tests/slack_sdk/web/test_chat_stream.py | 20 ++++++++++++++++++++ 5 files changed, 56 insertions(+) diff --git a/slack_sdk/web/async_chat_stream.py b/slack_sdk/web/async_chat_stream.py index c5344abaa..a527c41e1 100644 --- a/slack_sdk/web/async_chat_stream.py +++ b/slack_sdk/web/async_chat_stream.py @@ -40,6 +40,9 @@ def __init__( recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, task_display_mode: Optional[str] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -57,6 +60,9 @@ def __init__( recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks with text and "plan" displays all tasks together. + icon_emoji: Emoji to use as the icon for this message. Overrides icon_url. + icon_url: Image URL to use as the icon for this message. + username: The bot's username to display. buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. **kwargs: Additional arguments passed to the underlying API calls. @@ -70,6 +76,9 @@ def __init__( "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, "task_display_mode": task_display_mode, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, **kwargs, } self._buffer = "" diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index 108ebe09d..97ab3855b 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -2977,6 +2977,9 @@ async def chat_stream( recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, task_display_mode: Optional[str] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> AsyncChatStream: """Stream markdown text into a conversation. @@ -3005,6 +3008,9 @@ async def chat_stream( recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks with text and "plan" displays all tasks together. + icon_emoji: Emoji to use as the icon for this message. Overrides icon_url. + icon_url: Image URL to use as the icon for this message. + username: The bot's username to display. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3031,6 +3037,9 @@ async def chat_stream( recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, task_display_mode=task_display_mode, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, buffer_size=buffer_size, **kwargs, ) diff --git a/slack_sdk/web/chat_stream.py b/slack_sdk/web/chat_stream.py index 683859490..7bd2daec9 100644 --- a/slack_sdk/web/chat_stream.py +++ b/slack_sdk/web/chat_stream.py @@ -30,6 +30,9 @@ def __init__( recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, task_display_mode: Optional[str] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ): """Initialize a new ChatStream instance. @@ -47,6 +50,9 @@ def __init__( recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks with text and "plan" displays all tasks together. + icon_emoji: Emoji to use as the icon for this message. Overrides icon_url. + icon_url: Image URL to use as the icon for this message. + username: The bot's username to display. buffer_size: The length of markdown_text to buffer in-memory before calling a method. Increasing this value decreases the number of method calls made for the same amount of text, which is useful to avoid rate limits. **kwargs: Additional arguments passed to the underlying API calls. @@ -60,6 +66,9 @@ def __init__( "recipient_team_id": recipient_team_id, "recipient_user_id": recipient_user_id, "task_display_mode": task_display_mode, + "icon_emoji": icon_emoji, + "icon_url": icon_url, + "username": username, **kwargs, } self._buffer = "" diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index 940d8fbb2..5339db491 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -2967,6 +2967,9 @@ def chat_stream( recipient_team_id: Optional[str] = None, recipient_user_id: Optional[str] = None, task_display_mode: Optional[str] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, **kwargs, ) -> ChatStream: """Stream markdown text into a conversation. @@ -2995,6 +2998,9 @@ def chat_stream( recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. task_display_mode: Specifies how tasks are displayed in the message. A "timeline" displays individual tasks with text and "plan" displays all tasks together. + icon_emoji: Emoji to use as the icon for this message. Overrides icon_url. + icon_url: Image URL to use as the icon for this message. + username: The bot's username to display. **kwargs: Additional arguments passed to the underlying API calls. Returns: @@ -3021,6 +3027,9 @@ def chat_stream( recipient_team_id=recipient_team_id, recipient_user_id=recipient_user_id, task_display_mode=task_display_mode, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, buffer_size=buffer_size, **kwargs, ) diff --git a/tests/slack_sdk/web/test_chat_stream.py b/tests/slack_sdk/web/test_chat_stream.py index 0a11b9d53..2db5dff88 100644 --- a/tests/slack_sdk/web/test_chat_stream.py +++ b/tests/slack_sdk/web/test_chat_stream.py @@ -242,6 +242,26 @@ def test_streams_a_chunk_message(self): '[{"text": "\\n", "type": "markdown_text"}, {"text": ":space_invader:", "type": "markdown_text"}]', ) + def test_streams_with_authorship_args(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + username="Abacus", + icon_emoji="abacus", + ) + self.assertEqual(streamer._stream_args["username"], "Abacus") + self.assertEqual(streamer._stream_args["icon_emoji"], "abacus") + self.assertIsNone(streamer._stream_args["icon_url"]) + + streamer.append(markdown_text="counting...") + streamer.stop() + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("username"), "Abacus") + self.assertEqual(start_request.get("icon_emoji"), "abacus") + self.assertNotIn("icon_url", start_request) + def test_streams_errors_when_appending_to_an_unstarted_stream(self): streamer = self.client.chat_stream( channel="C0123456789",