diff --git a/docs/english/web.md b/docs/english/web.md index 5069e5461..49d6c5871 100644 --- a/docs/english/web.md +++ b/docs/english/web.md @@ -33,6 +33,8 @@ except SlackApiError as e: assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' ``` +### Sending ephemeral messages + Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same as sending a regular message but with an additional `user` parameter. ``` python @@ -51,6 +53,114 @@ response = client.chat_postEphemeral( See the [`chat.postEphemeral`](/reference/methods/chat.postEphemeral) API method for more details. +### Sending streaming messages {#sending-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) + +:::tip[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.] + +See the [_Streaming messages_](/tools/bolt-python/concepts/message-sending#streaming-messages) section of the Bolt for Python docs for implementation instructions. + +::: + +#### Starting the message stream {#starting-stream} + +First you need to begin the message stream: + +```python +# Example: Stream a response to any message +@app.message() +def handle_message(message, client): + channel_id = event.get("channel") + team_id = event.get("team") + thread_ts = event.get("thread_ts") or event.get("ts") + user_id = event.get("user") + + # Start a new message stream + stream_response = client.chat_startStream( + channel=channel_id, + recipient_team_id=team_id, + recipient_user_id=user_id, + thread_ts=thread_ts, + ) + stream_ts = stream_response["ts"] +``` + +#### Appending content to the message stream {#appending-stream} + +With the stream started, you can then append text to it in chunks to convey a streaming effect. + +The structure of the text coming in will depend on your source. The following code snippet uses OpenAI's response structure as an example: + +```python +# continued from above + for event in returned_message: + if event.type == "response.output_text.delta": + client.chat_appendStream( + channel=channel_id, + ts=stream_ts, + markdown_text=f"{event.delta}" + ) + else: + continue +``` + +#### Stopping the message stream {#stopping-stream} + +Your app can then end the stream with the `chat_stopStream` method: + +```python +# continued from above + client.chat_stopStream( + channel=channel_id, + ts=stream_ts + ) +``` + +The method also provides you an opportunity to request user feedback on your app's responses using the [feedback buttons](/reference/block-kit/block-elements/feedback-buttons-element) block element within the [context actions](/reference/block-kit/blocks/context-actions-block) block. The user will be presented with thumbs up and thumbs down buttons which send an action to your app when pressed. + +```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 + +@app.message() +def handle_message(message, client): + # ... previous streaming code ... + + # Stop the stream and add interactive elements + feedback_block = create_feedback_block() + client.chat_stopStream( + channel=channel_id, + ts=stream_ts, + blocks=feedback_block + ) +``` + +See [Formatting messages with Block Kit](#block-kit) below for more details on using Block Kit with messages. + ## Formatting messages with Block Kit {#block-kit} Messages posted from apps can contain more than just text; they can also include full user interfaces composed of blocks using [Block Kit](/block-kit). diff --git a/docs/reference/index.html b/docs/reference/index.html index 6671d4f16..282903146 100644 --- a/docs/reference/index.html +++ b/docs/reference/index.html @@ -2201,13 +2201,17 @@

Classes

channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs) def assistant_threads_setTitle( self, @@ -2217,7 +2221,7 @@

Classes

title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2232,7 +2236,7 @@

Classes

prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2746,6 +2750,27 @@

Classes

# -------------------------- + def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> SlackResponse: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.appendStream", json=kwargs) + def chat_delete( self, *, @@ -2950,6 +2975,146 @@

Classes

# NOTE: intentionally using json over params for the API methods using blocks/attachments return self.api_call("chat.scheduleMessage", json=kwargs) + def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return self.api_call("chat.scheduledMessages.list", params=kwargs) + + def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.startStream", json=kwargs) + + def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> SlackResponse: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.stopStream", json=kwargs) + + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -3030,32 +3195,6 @@

Classes

# NOTE: intentionally using json over params for API methods using blocks/attachments return self.api_call("chat.update", json=kwargs) - def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> SlackResponse: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return self.api_call("chat.scheduledMessages.list", params=kwargs) - def conversations_acceptSharedInvite( self, *, @@ -8890,7 +9029,7 @@

Methods

https://docs.slack.dev/reference/methods/apps.uninstall

-def assistant_threads_setStatus(self, *, channel_id: str, thread_ts: str, status: str, **kwargs) ‑> SlackResponse +def assistant_threads_setStatus(self,
*,
channel_id: str,
thread_ts: str,
status: str,
loading_messages: List[str] | None = None,
**kwargs) ‑> SlackResponse
@@ -8903,15 +9042,19 @@

Methods

channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs)
-

Revokes a token. +

@@ -8931,7 +9074,7 @@

Methods

prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -8939,7 +9082,7 @@

Methods

kwargs.update({"title": title}) return self.api_call("assistant.threads.setSuggestedPrompts", json=kwargs)
-

Revokes a token. +

Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts

@@ -8958,13 +9101,13 @@

Methods

title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) return self.api_call("assistant.threads.setTitle", params=kwargs)
-

Revokes a token. +

Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle

@@ -9835,6 +9978,38 @@

Methods

Unarchives a channel.

+
+def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +
+
+
+ +Expand source code + +
def chat_appendStream(
+    self,
+    *,
+    channel: str,
+    ts: str,
+    markdown_text: str,
+    **kwargs,
+) -> SlackResponse:
+    """Appends text to an existing streaming conversation.
+    https://docs.slack.dev/reference/methods/chat.appendStream
+    """
+    kwargs.update(
+        {
+            "channel": channel,
+            "ts": ts,
+            "markdown_text": markdown_text,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("chat.appendStream", json=kwargs)
+
+

Appends text to an existing streaming conversation. +https://docs.slack.dev/reference/methods/chat.appendStream

+
def chat_delete(self, *, channel: str, ts: str, as_user: bool | None = None, **kwargs) ‑> SlackResponse
@@ -10153,6 +10328,195 @@

Methods

Lists all scheduled messages. https://docs.slack.dev/reference/methods/chat.scheduledMessages.list

+
+def chat_startStream(self,
*,
channel: str,
thread_ts: str,
markdown_text: str | None = None,
recipient_team_id: str | None = None,
recipient_user_id: str | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def chat_startStream(
+    self,
+    *,
+    channel: str,
+    thread_ts: str,
+    markdown_text: Optional[str] = None,
+    recipient_team_id: Optional[str] = None,
+    recipient_user_id: Optional[str] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Starts a new streaming conversation.
+    https://docs.slack.dev/reference/methods/chat.startStream
+    """
+    kwargs.update(
+        {
+            "channel": channel,
+            "thread_ts": thread_ts,
+            "markdown_text": markdown_text,
+            "recipient_team_id": recipient_team_id,
+            "recipient_user_id": recipient_user_id,
+        }
+    )
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("chat.startStream", json=kwargs)
+
+

Starts a new streaming conversation. +https://docs.slack.dev/reference/methods/chat.startStream

+
+
+def chat_stopStream(self,
*,
channel: str,
ts: str,
markdown_text: str | None = None,
blocks: str | Sequence[Dict | Block] | None = None,
metadata: Dict | Metadata | None = None,
**kwargs) ‑> SlackResponse
+
+
+
+ +Expand source code + +
def chat_stopStream(
+    self,
+    *,
+    channel: str,
+    ts: str,
+    markdown_text: Optional[str] = None,
+    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
+    metadata: Optional[Union[Dict, Metadata]] = None,
+    **kwargs,
+) -> SlackResponse:
+    """Stops a streaming conversation.
+    https://docs.slack.dev/reference/methods/chat.stopStream
+    """
+    kwargs.update(
+        {
+            "channel": channel,
+            "ts": ts,
+            "markdown_text": markdown_text,
+            "blocks": blocks,
+            "metadata": metadata,
+        }
+    )
+    _parse_web_class_objects(kwargs)
+    kwargs = _remove_none_values(kwargs)
+    return self.api_call("chat.stopStream", json=kwargs)
+
+ +
+
+def chat_stream(self,
*,
buffer_size: int = 256,
channel: str,
thread_ts: str,
recipient_team_id: str | None = None,
recipient_user_id: str | None = None,
**kwargs) ‑> ChatStream
+
+
+
+ +Expand source code + +
def chat_stream(
+    self,
+    *,
+    buffer_size: int = 256,
+    channel: str,
+    thread_ts: str,
+    recipient_team_id: Optional[str] = None,
+    recipient_user_id: Optional[str] = None,
+    **kwargs,
+) -> ChatStream:
+    """Stream markdown text into a conversation.
+
+    This method starts a new chat stream in a conversation that can be appended to. After appending an entire message,
+    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
+
+    The following methods are used:
+
+    - chat.startStream: Starts a new streaming conversation.
+      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
+    - chat.appendStream: Appends text to an existing streaming conversation.
+      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
+    - chat.stopStream: Stops a streaming conversation.
+      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
+
+    Args:
+        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
+          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
+          limits. Default: 256.
+        channel: An encoded ID that represents a channel, private group, or DM.
+        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
+          request.
+        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
+          streaming to channels.
+        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
+        **kwargs: Additional arguments passed to the underlying API calls.
+
+    Returns:
+        ChatStream instance for managing the stream
+
+    Example:
+        ```python
+        streamer = client.chat_stream(
+            channel="C0123456789",
+            thread_ts="1700000001.123456",
+            recipient_team_id="T0123456789",
+            recipient_user_id="U0123456789",
+        )
+        streamer.append(markdown_text="**hello wo")
+        streamer.append(markdown_text="rld!**")
+        streamer.stop()
+        ```
+    """
+    return ChatStream(
+        self,
+        logger=self._logger,
+        channel=channel,
+        thread_ts=thread_ts,
+        recipient_team_id=recipient_team_id,
+        recipient_user_id=recipient_user_id,
+        buffer_size=buffer_size,
+        **kwargs,
+    )
+
+

Stream markdown text into a conversation.

+

This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

+

The following methods are used:

+
    +
  • chat.startStream: Starts a new streaming conversation. +Reference.
  • +
  • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
  • +
  • chat.stopStream: Stops a streaming conversation. +Reference.
  • +
+

Args

+
+
buffer_size
+
The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
+
channel
+
An encoded ID that represents a channel, private group, or DM.
+
thread_ts
+
Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
+
recipient_team_id
+
The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
+
recipient_user_id
+
The encoded ID of the user to receive the streaming text. Required when streaming to channels.
+
**kwargs
+
Additional arguments passed to the underlying API calls.
+
+

Returns

+

ChatStream instance for managing the stream

+

Example

+
streamer = client.chat_stream(
+    channel="C0123456789",
+    thread_ts="1700000001.123456",
+    recipient_team_id="T0123456789",
+    recipient_user_id="U0123456789",
+)
+streamer.append(markdown_text="**hello wo")
+streamer.append(markdown_text="rld!**")
+streamer.stop()
+
+
def chat_unfurl(self,
*,
channel: str | None = None,
ts: str | None = None,
source: str | None = None,
unfurl_id: str | None = None,
unfurls: Dict[str, Dict] | None = None,
user_auth_blocks: str | Sequence[Dict | Block] | None = None,
user_auth_message: str | None = None,
user_auth_required: bool | None = None,
user_auth_url: str | None = None,
**kwargs) ‑> SlackResponse
@@ -15119,6 +15483,7 @@

WebClientchannels_setPurpose
  • channels_setTopic
  • channels_unarchive
  • +
  • chat_appendStream
  • chat_delete
  • chat_deleteScheduledMessage
  • chat_getPermalink
  • @@ -15127,6 +15492,9 @@

    WebClientchat_postMessage
  • chat_scheduleMessage
  • chat_scheduledMessages_list
  • +
  • chat_startStream
  • +
  • chat_stopStream
  • +
  • chat_stream
  • chat_unfurl
  • chat_update
  • conversations_acceptSharedInvite
  • diff --git a/docs/reference/models/basic_objects.html b/docs/reference/models/basic_objects.html index b49694566..972273cc9 100644 --- a/docs/reference/models/basic_objects.html +++ b/docs/reference/models/basic_objects.html @@ -194,6 +194,7 @@

    Subclasses

  • AttachmentField
  • ConfirmObject
  • DispatchActionConfig
  • +
  • FeedbackButtonObject
  • Option
  • OptionGroup
  • SlackFile
  • diff --git a/docs/reference/models/blocks/basic_components.html b/docs/reference/models/blocks/basic_components.html index 2d1205201..8a48c9a6a 100644 --- a/docs/reference/models/blocks/basic_components.html +++ b/docs/reference/models/blocks/basic_components.html @@ -341,6 +341,157 @@

    Inherited members

    +
    +class FeedbackButtonObject +(*,
    text: str | Dict[str, Any] | PlainTextObject,
    accessibility_label: str | None = None,
    value: str,
    **others: Dict[str, Any])
    +
    +
    +
    + +Expand source code + +
    class FeedbackButtonObject(JsonObject):
    +    attributes: Set[str] = set()
    +
    +    text_max_length = 75
    +    value_max_length = 2000
    +
    +    @classmethod
    +    def parse(cls, feedback_button: Union["FeedbackButtonObject", Dict[str, Any]]):
    +        if feedback_button:
    +            if isinstance(feedback_button, FeedbackButtonObject):
    +                return feedback_button
    +            elif isinstance(feedback_button, dict):
    +                return FeedbackButtonObject(**feedback_button)
    +            else:
    +                # Not yet implemented: show some warning here
    +                return None
    +        return None
    +
    +    def __init__(
    +        self,
    +        *,
    +        text: Union[str, Dict[str, Any], PlainTextObject],
    +        accessibility_label: Optional[str] = None,
    +        value: str,
    +        **others: Dict[str, Any],
    +    ):
    +        """
    +        A feedback button element object for either positive or negative feedback.
    +
    +        Args:
    +            text (required): An object containing some text. Maximum length for this field is 75 characters.
    +            accessibility_label: A label for longer descriptive text about a button element. This label will be read out by
    +                screen readers instead of the button `text` object.
    +            value (required): The button value. Maximum length for this field is 2000 characters.
    +        """
    +        self._text: Optional[TextObject] = PlainTextObject.parse(text, default_type=PlainTextObject.type)
    +        self._accessibility_label: Optional[str] = accessibility_label
    +        self._value: Optional[str] = value
    +        show_unknown_key_warning(self, others)
    +
    +    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    +    def text_length(self) -> bool:
    +        return self._text is None or len(self._text.text) <= self.text_max_length
    +
    +    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
    +    def value_length(self) -> bool:
    +        return self._value is None or len(self._value) <= self.value_max_length
    +
    +    def to_dict(self) -> Dict[str, Any]:
    +        self.validate_json()
    +        json: Dict[str, Union[str, dict]] = {}
    +        if self._text:
    +            json["text"] = self._text.to_dict()
    +        if self._accessibility_label:
    +            json["accessibility_label"] = self._accessibility_label
    +        if self._value:
    +            json["value"] = self._value
    +        return json
    +
    +

    The base class for JSON serializable class objects

    +

    A feedback button element object for either positive or negative feedback.

    +

    Args

    +
    +
    text : required
    +
    An object containing some text. Maximum length for this field is 75 characters.
    +
    accessibility_label
    +
    A label for longer descriptive text about a button element. This label will be read out by +screen readers instead of the button text object.
    +
    value : required
    +
    The button value. Maximum length for this field is 2000 characters.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var attributes : Set[str]
    +
    +

    The type of the None singleton.

    +
    +
    var text_max_length
    +
    +

    The type of the None singleton.

    +
    +
    var value_max_length
    +
    +

    The type of the None singleton.

    +
    +
    +

    Static methods

    +
    +
    +def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any]) +
    +
    +
    +
    +
    +

    Methods

    +
    +
    +def text_length(self) ‑> bool +
    +
    +
    + +Expand source code + +
    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
    +def text_length(self) -> bool:
    +    return self._text is None or len(self._text.text) <= self.text_max_length
    +
    +
    +
    +
    +def value_length(self) ‑> bool +
    +
    +
    + +Expand source code + +
    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
    +def value_length(self) -> bool:
    +    return self._value is None or len(self._value) <= self.value_max_length
    +
    +
    +
    +
    +

    Inherited members

    + +
    class MarkdownTextObject (*, text: str, verbatim: bool | None = None) @@ -1427,6 +1578,17 @@

    FeedbackButtonObject

    + + +
  • MarkdownTextObject

    • attributes
    • diff --git a/docs/reference/models/blocks/block_elements.html b/docs/reference/models/blocks/block_elements.html index 0e14eb433..ac23c73a1 100644 --- a/docs/reference/models/blocks/block_elements.html +++ b/docs/reference/models/blocks/block_elements.html @@ -1843,6 +1843,89 @@

      Inherited members

    +
    +class FeedbackButtonsElement +(*,
    action_id: str | None = None,
    positive_button: dict | FeedbackButtonObject,
    negative_button: dict | FeedbackButtonObject,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class FeedbackButtonsElement(InteractiveElement):
    +    type = "feedback_buttons"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union({"positive_button", "negative_button"})
    +
    +    def __init__(
    +        self,
    +        *,
    +        action_id: Optional[str] = None,
    +        positive_button: Union[dict, FeedbackButtonObject],
    +        negative_button: Union[dict, FeedbackButtonObject],
    +        **others: dict,
    +    ):
    +        """Buttons to indicate positive or negative feedback.
    +
    +        Args:
    +            action_id (required): An identifier for this action.
    +                You can use this when you receive an interaction payload to identify the source of the action.
    +                Should be unique among all other action_ids in the containing block.
    +                Maximum length for this field is 255 characters.
    +            positive_button (required): A button to indicate positive feedback.
    +            negative_button (required): A button to indicate negative feedback.
    +        """
    +        super().__init__(action_id=action_id, type=self.type)
    +        show_unknown_key_warning(self, others)
    +
    +        self.positive_button = FeedbackButtonObject.parse(positive_button)
    +        self.negative_button = FeedbackButtonObject.parse(negative_button)
    +
    +

    Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

    +

    Buttons to indicate positive or negative feedback.

    +

    Args

    +
    +
    action_id : required
    +
    An identifier for this action. +You can use this when you receive an interaction payload to identify the source of the action. +Should be unique among all other action_ids in the containing block. +Maximum length for this field is 255 characters.
    +
    positive_button : required
    +
    A button to indicate positive feedback.
    +
    negative_button : required
    +
    A button to indicate negative feedback.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Inherited members

    + +
    class FileInputElement (*,
    action_id: str | None = None,
    filetypes: List[str] | None = None,
    max_files: int | None = None,
    **others: dict)
    @@ -1962,6 +2045,117 @@

    Inherited members

  • +
    +class IconButtonElement +(*,
    action_id: str | None = None,
    icon: str,
    text: str | dict | TextObject,
    accessibility_label: str | None = None,
    value: str | None = None,
    visible_to_user_ids: List[str] | None = None,
    confirm: dict | ConfirmObject | None = None,
    **others: dict)
    +
    +
    +
    + +Expand source code + +
    class IconButtonElement(InteractiveElement):
    +    type = "icon_button"
    +
    +    @property
    +    def attributes(self) -> Set[str]:  # type: ignore[override]
    +        return super().attributes.union({"icon", "text", "accessibility_label", "value", "visible_to_user_ids", "confirm"})
    +
    +    def __init__(
    +        self,
    +        *,
    +        action_id: Optional[str] = None,
    +        icon: str,
    +        text: Union[str, dict, TextObject],
    +        accessibility_label: Optional[str] = None,
    +        value: Optional[str] = None,
    +        visible_to_user_ids: Optional[List[str]] = None,
    +        confirm: Optional[Union[dict, ConfirmObject]] = None,
    +        **others: dict,
    +    ):
    +        """An icon button to perform actions.
    +
    +        Args:
    +            action_id: An identifier for this action.
    +                You can use this when you receive an interaction payload to identify the source of the action.
    +                Should be unique among all other action_ids in the containing block.
    +                Maximum length for this field is 255 characters.
    +            icon (required): The icon to show (e.g., 'trash').
    +            text (required): Defines an object containing some text.
    +            accessibility_label: A label for longer descriptive text about a button element.
    +                This label will be read out by screen readers instead of the button text object.
    +                Maximum length for this field is 75 characters.
    +            value: The button value.
    +                Maximum length for this field is 2000 characters.
    +            visible_to_user_ids: User IDs for which the icon appears.
    +                Maximum length for this field is 10 user IDs.
    +            confirm: A confirm object that defines an optional confirmation dialog after the button is clicked.
    +        """
    +        super().__init__(action_id=action_id, type=self.type)
    +        show_unknown_key_warning(self, others)
    +
    +        self.icon = icon
    +        self.text = TextObject.parse(text, PlainTextObject.type)
    +        self.accessibility_label = accessibility_label
    +        self.value = value
    +        self.visible_to_user_ids = visible_to_user_ids
    +        self.confirm = ConfirmObject.parse(confirm) if confirm else None
    +
    +

    Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

    +

    An icon button to perform actions.

    +

    Args

    +
    +
    action_id
    +
    An identifier for this action. +You can use this when you receive an interaction payload to identify the source of the action. +Should be unique among all other action_ids in the containing block. +Maximum length for this field is 255 characters.
    +
    icon : required
    +
    The icon to show (e.g., 'trash').
    +
    text : required
    +
    Defines an object containing some text.
    +
    accessibility_label
    +
    A label for longer descriptive text about a button element. +This label will be read out by screen readers instead of the button text object. +Maximum length for this field is 75 characters.
    +
    value
    +
    The button value. +Maximum length for this field is 2000 characters.
    +
    visible_to_user_ids
    +
    User IDs for which the icon appears. +Maximum length for this field is 10 user IDs.
    +
    confirm
    +
    A confirm object that defines an optional confirmation dialog after the button is clicked.
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var type
    +
    +

    The type of the None singleton.

    +
    +
    +

    Inherited members

    + +
    class ImageElement (*,
    alt_text: str | None = None,
    image_url: str | None = None,
    slack_file: Dict[str, Any] | SlackFile | None = None,
    **others: dict)
    @@ -2259,6 +2453,8 @@

    Ancestors

    Subclasses

    • ButtonElement
    • +
    • FeedbackButtonsElement
    • +
    • IconButtonElement
    • InputInteractiveElement
    • OverflowMenuElement
    • WorkflowButtonElement
    • @@ -4946,6 +5142,12 @@

      FeedbackButtonsElement

      + + +
    • FileInputElement

      • attributes
      • @@ -4953,6 +5155,12 @@

      • +

        IconButtonElement

        + +
      • +
      • ImageElement

        • alt_text_max_length
        • diff --git a/docs/reference/models/blocks/blocks.html b/docs/reference/models/blocks/blocks.html index 1c6126505..e01a02436 100644 --- a/docs/reference/models/blocks/blocks.html +++ b/docs/reference/models/blocks/blocks.html @@ -220,6 +220,8 @@

          Inherited members

          return ActionsBlock(**block) elif type == ContextBlock.type: return ContextBlock(**block) + elif type == ContextActionsBlock.type: + return ContextActionsBlock(**block) elif type == InputBlock.type: return InputBlock(**block) elif type == FileBlock.type: @@ -257,6 +259,7 @@

          Subclasses

          +
          +class ContextActionsBlock +(*,
          elements: Sequence[dict | FeedbackButtonsElement | IconButtonElement],
          block_id: str | None = None,
          **others: dict)
          +
          +
          +
          + +Expand source code + +
          class ContextActionsBlock(Block):
          +    type = "context_actions"
          +    elements_max_length = 5
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union({"elements"})
          +
          +    def __init__(
          +        self,
          +        *,
          +        elements: Sequence[Union[dict, FeedbackButtonsElement, IconButtonElement]],
          +        block_id: Optional[str] = None,
          +        **others: dict,
          +    ):
          +        """Displays actions as contextual info, which can include both feedback buttons and icon buttons.
          +
          +        Args:
          +            elements (required): An array of feedback_buttons or icon_button block elements. Maximum number of items is 5.
          +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
          +                Maximum length for this field is 255 characters.
          +                block_id should be unique for each message and each iteration of a message.
          +                If a message is updated, use a new block_id.
          +        """
          +        super().__init__(type=self.type, block_id=block_id)
          +        show_unknown_key_warning(self, others)
          +
          +        self.elements = BlockElement.parse_all(elements)
          +
          +    @JsonValidator("elements attribute must be specified")
          +    def _validate_elements(self):
          +        return self.elements is None or len(self.elements) > 0
          +
          +    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
          +    def _validate_elements_length(self):
          +        return self.elements is None or len(self.elements) <= self.elements_max_length
          +
          +

          Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

          +

          Displays actions as contextual info, which can include both feedback buttons and icon buttons.

          +

          Args

          +
          +
          elements : required
          +
          An array of feedback_buttons or icon_button block elements. Maximum number of items is 5.
          +
          block_id
          +
          A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
          +
          +

          Ancestors

          + +

          Class variables

          +
          +
          var elements_max_length
          +
          +

          The type of the None singleton.

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Instance variables

          +
          +
          prop attributes : Set[str]
          +
          +
          + +Expand source code + +
          @property
          +def attributes(self) -> Set[str]:  # type: ignore[override]
          +    return super().attributes.union({"elements"})
          +
          +

          Build an unordered collection of unique elements.

          +
          +
          +

          Inherited members

          + +
          class ContextBlock (*,
          elements: Sequence[dict | ImageElement | TextObject],
          block_id: str | None = None,
          **others: dict)
          @@ -1714,6 +1822,14 @@

          ContextActionsBlock

          + + +
        • ContextBlock

          • attributes
          • diff --git a/docs/reference/models/blocks/index.html b/docs/reference/models/blocks/index.html index 22d3d0856..ad0c7b088 100644 --- a/docs/reference/models/blocks/index.html +++ b/docs/reference/models/blocks/index.html @@ -242,6 +242,8 @@

            Inherited members

            return ActionsBlock(**block) elif type == ContextBlock.type: return ContextBlock(**block) + elif type == ContextActionsBlock.type: + return ContextActionsBlock(**block) elif type == InputBlock.type: return InputBlock(**block) elif type == FileBlock.type: @@ -279,6 +281,7 @@

            Subclasses

            +
            +class ContextActionsBlock +(*,
            elements: Sequence[dict | FeedbackButtonsElement | IconButtonElement],
            block_id: str | None = None,
            **others: dict)
            +
            +
            +
            + +Expand source code + +
            class ContextActionsBlock(Block):
            +    type = "context_actions"
            +    elements_max_length = 5
            +
            +    @property
            +    def attributes(self) -> Set[str]:  # type: ignore[override]
            +        return super().attributes.union({"elements"})
            +
            +    def __init__(
            +        self,
            +        *,
            +        elements: Sequence[Union[dict, FeedbackButtonsElement, IconButtonElement]],
            +        block_id: Optional[str] = None,
            +        **others: dict,
            +    ):
            +        """Displays actions as contextual info, which can include both feedback buttons and icon buttons.
            +
            +        Args:
            +            elements (required): An array of feedback_buttons or icon_button block elements. Maximum number of items is 5.
            +            block_id: A string acting as a unique identifier for a block. If not specified, one will be generated.
            +                Maximum length for this field is 255 characters.
            +                block_id should be unique for each message and each iteration of a message.
            +                If a message is updated, use a new block_id.
            +        """
            +        super().__init__(type=self.type, block_id=block_id)
            +        show_unknown_key_warning(self, others)
            +
            +        self.elements = BlockElement.parse_all(elements)
            +
            +    @JsonValidator("elements attribute must be specified")
            +    def _validate_elements(self):
            +        return self.elements is None or len(self.elements) > 0
            +
            +    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
            +    def _validate_elements_length(self):
            +        return self.elements is None or len(self.elements) <= self.elements_max_length
            +
            +

            Blocks are a series of components that can be combined +to create visually rich and compellingly interactive messages. +https://docs.slack.dev/reference/block-kit/blocks

            +

            Displays actions as contextual info, which can include both feedback buttons and icon buttons.

            +

            Args

            +
            +
            elements : required
            +
            An array of feedback_buttons or icon_button block elements. Maximum number of items is 5.
            +
            block_id
            +
            A string acting as a unique identifier for a block. If not specified, one will be generated. +Maximum length for this field is 255 characters. +block_id should be unique for each message and each iteration of a message. +If a message is updated, use a new block_id.
            +
            +

            Ancestors

            + +

            Class variables

            +
            +
            var elements_max_length
            +
            +

            The type of the None singleton.

            +
            +
            var type
            +
            +

            The type of the None singleton.

            +
            +
            +

            Instance variables

            +
            +
            prop attributes : Set[str]
            +
            +
            + +Expand source code + +
            @property
            +def attributes(self) -> Set[str]:  # type: ignore[override]
            +    return super().attributes.union({"elements"})
            +
            +

            Build an unordered collection of unique elements.

            +
            +
            +

            Inherited members

            + +
            class ContextBlock (*,
            elements: Sequence[dict | ImageElement | TextObject],
            block_id: str | None = None,
            **others: dict)
            @@ -2617,6 +2725,240 @@

            Inherited members

          +
          +class FeedbackButtonObject +(*,
          text: str | Dict[str, Any] | PlainTextObject,
          accessibility_label: str | None = None,
          value: str,
          **others: Dict[str, Any])
          +
          +
          +
          + +Expand source code + +
          class FeedbackButtonObject(JsonObject):
          +    attributes: Set[str] = set()
          +
          +    text_max_length = 75
          +    value_max_length = 2000
          +
          +    @classmethod
          +    def parse(cls, feedback_button: Union["FeedbackButtonObject", Dict[str, Any]]):
          +        if feedback_button:
          +            if isinstance(feedback_button, FeedbackButtonObject):
          +                return feedback_button
          +            elif isinstance(feedback_button, dict):
          +                return FeedbackButtonObject(**feedback_button)
          +            else:
          +                # Not yet implemented: show some warning here
          +                return None
          +        return None
          +
          +    def __init__(
          +        self,
          +        *,
          +        text: Union[str, Dict[str, Any], PlainTextObject],
          +        accessibility_label: Optional[str] = None,
          +        value: str,
          +        **others: Dict[str, Any],
          +    ):
          +        """
          +        A feedback button element object for either positive or negative feedback.
          +
          +        Args:
          +            text (required): An object containing some text. Maximum length for this field is 75 characters.
          +            accessibility_label: A label for longer descriptive text about a button element. This label will be read out by
          +                screen readers instead of the button `text` object.
          +            value (required): The button value. Maximum length for this field is 2000 characters.
          +        """
          +        self._text: Optional[TextObject] = PlainTextObject.parse(text, default_type=PlainTextObject.type)
          +        self._accessibility_label: Optional[str] = accessibility_label
          +        self._value: Optional[str] = value
          +        show_unknown_key_warning(self, others)
          +
          +    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
          +    def text_length(self) -> bool:
          +        return self._text is None or len(self._text.text) <= self.text_max_length
          +
          +    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
          +    def value_length(self) -> bool:
          +        return self._value is None or len(self._value) <= self.value_max_length
          +
          +    def to_dict(self) -> Dict[str, Any]:
          +        self.validate_json()
          +        json: Dict[str, Union[str, dict]] = {}
          +        if self._text:
          +            json["text"] = self._text.to_dict()
          +        if self._accessibility_label:
          +            json["accessibility_label"] = self._accessibility_label
          +        if self._value:
          +            json["value"] = self._value
          +        return json
          +
          +

          The base class for JSON serializable class objects

          +

          A feedback button element object for either positive or negative feedback.

          +

          Args

          +
          +
          text : required
          +
          An object containing some text. Maximum length for this field is 75 characters.
          +
          accessibility_label
          +
          A label for longer descriptive text about a button element. This label will be read out by +screen readers instead of the button text object.
          +
          value : required
          +
          The button value. Maximum length for this field is 2000 characters.
          +
          +

          Ancestors

          + +

          Class variables

          +
          +
          var attributes : Set[str]
          +
          +

          The type of the None singleton.

          +
          +
          var text_max_length
          +
          +

          The type of the None singleton.

          +
          +
          var value_max_length
          +
          +

          The type of the None singleton.

          +
          +
          +

          Static methods

          +
          +
          +def parse(feedback_button: ForwardRef('FeedbackButtonObject') | Dict[str, Any]) +
          +
          +
          +
          +
          +

          Methods

          +
          +
          +def text_length(self) ‑> bool +
          +
          +
          + +Expand source code + +
          @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
          +def text_length(self) -> bool:
          +    return self._text is None or len(self._text.text) <= self.text_max_length
          +
          +
          +
          +
          +def value_length(self) ‑> bool +
          +
          +
          + +Expand source code + +
          @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
          +def value_length(self) -> bool:
          +    return self._value is None or len(self._value) <= self.value_max_length
          +
          +
          +
          +
          +

          Inherited members

          + +
          +
          +class FeedbackButtonsElement +(*,
          action_id: str | None = None,
          positive_button: dict | FeedbackButtonObject,
          negative_button: dict | FeedbackButtonObject,
          **others: dict)
          +
          +
          +
          + +Expand source code + +
          class FeedbackButtonsElement(InteractiveElement):
          +    type = "feedback_buttons"
          +
          +    @property
          +    def attributes(self) -> Set[str]:  # type: ignore[override]
          +        return super().attributes.union({"positive_button", "negative_button"})
          +
          +    def __init__(
          +        self,
          +        *,
          +        action_id: Optional[str] = None,
          +        positive_button: Union[dict, FeedbackButtonObject],
          +        negative_button: Union[dict, FeedbackButtonObject],
          +        **others: dict,
          +    ):
          +        """Buttons to indicate positive or negative feedback.
          +
          +        Args:
          +            action_id (required): An identifier for this action.
          +                You can use this when you receive an interaction payload to identify the source of the action.
          +                Should be unique among all other action_ids in the containing block.
          +                Maximum length for this field is 255 characters.
          +            positive_button (required): A button to indicate positive feedback.
          +            negative_button (required): A button to indicate negative feedback.
          +        """
          +        super().__init__(action_id=action_id, type=self.type)
          +        show_unknown_key_warning(self, others)
          +
          +        self.positive_button = FeedbackButtonObject.parse(positive_button)
          +        self.negative_button = FeedbackButtonObject.parse(negative_button)
          +
          +

          Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

          +

          Buttons to indicate positive or negative feedback.

          +

          Args

          +
          +
          action_id : required
          +
          An identifier for this action. +You can use this when you receive an interaction payload to identify the source of the action. +Should be unique among all other action_ids in the containing block. +Maximum length for this field is 255 characters.
          +
          positive_button : required
          +
          A button to indicate positive feedback.
          +
          negative_button : required
          +
          A button to indicate negative feedback.
          +
          +

          Ancestors

          + +

          Class variables

          +
          +
          var type
          +
          +

          The type of the None singleton.

          +
          +
          +

          Inherited members

          + +
          class FileBlock (*,
          external_id: str,
          source: str = 'remote',
          block_id: str | None = None,
          **others: dict)
          @@ -2825,6 +3167,117 @@

          Inherited members

        +
        +class IconButtonElement +(*,
        action_id: str | None = None,
        icon: str,
        text: str | dict | TextObject,
        accessibility_label: str | None = None,
        value: str | None = None,
        visible_to_user_ids: List[str] | None = None,
        confirm: dict | ConfirmObject | None = None,
        **others: dict)
        +
        +
        +
        + +Expand source code + +
        class IconButtonElement(InteractiveElement):
        +    type = "icon_button"
        +
        +    @property
        +    def attributes(self) -> Set[str]:  # type: ignore[override]
        +        return super().attributes.union({"icon", "text", "accessibility_label", "value", "visible_to_user_ids", "confirm"})
        +
        +    def __init__(
        +        self,
        +        *,
        +        action_id: Optional[str] = None,
        +        icon: str,
        +        text: Union[str, dict, TextObject],
        +        accessibility_label: Optional[str] = None,
        +        value: Optional[str] = None,
        +        visible_to_user_ids: Optional[List[str]] = None,
        +        confirm: Optional[Union[dict, ConfirmObject]] = None,
        +        **others: dict,
        +    ):
        +        """An icon button to perform actions.
        +
        +        Args:
        +            action_id: An identifier for this action.
        +                You can use this when you receive an interaction payload to identify the source of the action.
        +                Should be unique among all other action_ids in the containing block.
        +                Maximum length for this field is 255 characters.
        +            icon (required): The icon to show (e.g., 'trash').
        +            text (required): Defines an object containing some text.
        +            accessibility_label: A label for longer descriptive text about a button element.
        +                This label will be read out by screen readers instead of the button text object.
        +                Maximum length for this field is 75 characters.
        +            value: The button value.
        +                Maximum length for this field is 2000 characters.
        +            visible_to_user_ids: User IDs for which the icon appears.
        +                Maximum length for this field is 10 user IDs.
        +            confirm: A confirm object that defines an optional confirmation dialog after the button is clicked.
        +        """
        +        super().__init__(action_id=action_id, type=self.type)
        +        show_unknown_key_warning(self, others)
        +
        +        self.icon = icon
        +        self.text = TextObject.parse(text, PlainTextObject.type)
        +        self.accessibility_label = accessibility_label
        +        self.value = value
        +        self.visible_to_user_ids = visible_to_user_ids
        +        self.confirm = ConfirmObject.parse(confirm) if confirm else None
        +
        +

        Block Elements are things that exists inside of your Blocks. +https://docs.slack.dev/reference/block-kit/block-elements/

        +

        An icon button to perform actions.

        +

        Args

        +
        +
        action_id
        +
        An identifier for this action. +You can use this when you receive an interaction payload to identify the source of the action. +Should be unique among all other action_ids in the containing block. +Maximum length for this field is 255 characters.
        +
        icon : required
        +
        The icon to show (e.g., 'trash').
        +
        text : required
        +
        Defines an object containing some text.
        +
        accessibility_label
        +
        A label for longer descriptive text about a button element. +This label will be read out by screen readers instead of the button text object. +Maximum length for this field is 75 characters.
        +
        value
        +
        The button value. +Maximum length for this field is 2000 characters.
        +
        visible_to_user_ids
        +
        User IDs for which the icon appears. +Maximum length for this field is 10 user IDs.
        +
        confirm
        +
        A confirm object that defines an optional confirmation dialog after the button is clicked.
        +
        +

        Ancestors

        + +

        Class variables

        +
        +
        var type
        +
        +

        The type of the None singleton.

        +
        +
        +

        Inherited members

        + +
        class ImageBlock (*,
        alt_text: str,
        image_url: str | None = None,
        slack_file: Dict[str, Any] | SlackFile | None = None,
        title: str | dict | PlainTextObject | None = None,
        block_id: str | None = None,
        **others: dict)
        @@ -3435,6 +3888,8 @@

        Ancestors

        Subclasses

        • ButtonElement
        • +
        • FeedbackButtonsElement
        • +
        • IconButtonElement
        • InputInteractiveElement
        • OverflowMenuElement
        • WorkflowButtonElement
        • @@ -7443,6 +7898,14 @@

          ContextActionsBlock

          + + +
        • ContextBlock

          • attributes
          • @@ -7514,6 +7977,23 @@

            FeedbackButtonObject

            + + +
          • +

            FeedbackButtonsElement

            + +
          • +
          • FileBlock

            • attributes
            • @@ -7529,6 +8009,12 @@

              IconButtonElement

              + + +
            • ImageBlock

              • alt_text_max_length
              • diff --git a/docs/reference/models/index.html b/docs/reference/models/index.html index 418bd9fa8..3888d29d7 100644 --- a/docs/reference/models/index.html +++ b/docs/reference/models/index.html @@ -298,6 +298,7 @@

                Subclasses

              • AttachmentField
              • ConfirmObject
              • DispatchActionConfig
              • +
              • FeedbackButtonObject
              • Option
              • OptionGroup
              • SlackFile
              • diff --git a/docs/reference/web/async_chat_stream.html b/docs/reference/web/async_chat_stream.html new file mode 100644 index 000000000..ca7bf2506 --- /dev/null +++ b/docs/reference/web/async_chat_stream.html @@ -0,0 +1,506 @@ + + + + + + +slack_sdk.web.async_chat_stream API documentation + + + + + + + + + + + +
                +
                +
                +

                Module slack_sdk.web.async_chat_stream

                +
                +
                +
                +
                +
                +
                +
                +
                +
                +
                +

                Classes

                +
                +
                +class AsyncChatStream +(client: AsyncWebClient,
                *,
                channel: str,
                logger: logging.Logger,
                thread_ts: str,
                buffer_size: int,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs)
                +
                +
                +
                + +Expand source code + +
                class AsyncChatStream:
                +    """A helper class for streaming markdown text into a conversation using the chat streaming APIs.
                +
                +    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API
                +    methods, with automatic buffering and state management.
                +    """
                +
                +    def __init__(
                +        self,
                +        client: "AsyncWebClient",
                +        *,
                +        channel: str,
                +        logger: logging.Logger,
                +        thread_ts: str,
                +        buffer_size: int,
                +        recipient_team_id: Optional[str] = None,
                +        recipient_user_id: Optional[str] = None,
                +        **kwargs,
                +    ):
                +        """Initialize a new ChatStream instance.
                +
                +        The __init__ method creates a unique ChatStream instance that keeps track of one chat stream.
                +
                +        Args:
                +            client: The WebClient instance to use for API calls.
                +            channel: An encoded ID that represents a channel, private group, or DM.
                +            logger: A logging channel for outputs.
                +            thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
                +              request.
                +            recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
                +              streaming to channels.
                +            recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +            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.
                +        """
                +        self._client = client
                +        self._logger = logger
                +        self._token: Optional[str] = kwargs.pop("token", None)
                +        self._stream_args = {
                +            "channel": channel,
                +            "thread_ts": thread_ts,
                +            "recipient_team_id": recipient_team_id,
                +            "recipient_user_id": recipient_user_id,
                +            **kwargs,
                +        }
                +        self._buffer = ""
                +        self._state = "starting"
                +        self._stream_ts: Optional[str] = None
                +        self._buffer_size = buffer_size
                +
                +    async def append(
                +        self,
                +        *,
                +        markdown_text: str,
                +        **kwargs,
                +    ) -> Optional[AsyncSlackResponse]:
                +        """Append to the stream.
                +
                +        The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
                +        is stopped this method cannot be called.
                +
                +        Args:
                +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +              what will be appended to the message received so far.
                +            **kwargs: Additional arguments passed to the underlying API calls.
                +
                +        Returns:
                +            AsyncSlackResponse if the buffer was flushed, None if buffering.
                +
                +        Raises:
                +            SlackRequestError: If the stream is already completed.
                +
                +        Example:
                +            ```python
                +            streamer = client.chat_stream(
                +                channel="C0123456789",
                +                thread_ts="1700000001.123456",
                +                recipient_team_id="T0123456789",
                +                recipient_user_id="U0123456789",
                +            )
                +            streamer.append(markdown_text="**hello wo")
                +            streamer.append(markdown_text="rld!**")
                +            streamer.stop()
                +            ```
                +        """
                +        if self._state == "completed":
                +            raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
                +        if kwargs.get("token"):
                +            self._token = kwargs.pop("token")
                +        self._buffer += markdown_text
                +        if len(self._buffer) >= self._buffer_size:
                +            return await self._flush_buffer(**kwargs)
                +        details = {
                +            "buffer_length": len(self._buffer),
                +            "buffer_size": self._buffer_size,
                +            "channel": self._stream_args.get("channel"),
                +            "recipient_team_id": self._stream_args.get("recipient_team_id"),
                +            "recipient_user_id": self._stream_args.get("recipient_user_id"),
                +            "thread_ts": self._stream_args.get("thread_ts"),
                +        }
                +        self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
                +        return None
                +
                +    async def stop(
                +        self,
                +        *,
                +        markdown_text: Optional[str] = None,
                +        blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +        metadata: Optional[Union[Dict, Metadata]] = None,
                +        **kwargs,
                +    ) -> AsyncSlackResponse:
                +        """Stop the stream and finalize the message.
                +
                +        Args:
                +            blocks: A list of blocks that will be rendered at the bottom of the finalized message.
                +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +              what will be appended to the message received so far.
                +            metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
                +              post to Slack is accessible to any app or user who is a member of that workspace.
                +            **kwargs: Additional arguments passed to the underlying API calls.
                +
                +        Returns:
                +            AsyncSlackResponse from the chat.stopStream API call.
                +
                +        Raises:
                +            SlackRequestError: If the stream is already completed.
                +
                +        Example:
                +            ```python
                +            streamer = client.chat_stream(
                +                channel="C0123456789",
                +                thread_ts="1700000001.123456",
                +                recipient_team_id="T0123456789",
                +                recipient_user_id="U0123456789",
                +            )
                +            streamer.append(markdown_text="**hello wo")
                +            streamer.append(markdown_text="rld!**")
                +            streamer.stop()
                +            ```
                +        """
                +        if self._state == "completed":
                +            raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
                +        if kwargs.get("token"):
                +            self._token = kwargs.pop("token")
                +        if markdown_text:
                +            self._buffer += markdown_text
                +        if not self._stream_ts:
                +            response = await self._client.chat_startStream(
                +                **self._stream_args,
                +                token=self._token,
                +            )
                +            if not response.get("ts"):
                +                raise e.SlackRequestError("Failed to stop stream: stream not started")
                +            self._stream_ts = str(response["ts"])
                +            self._state = "in_progress"
                +        response = await self._client.chat_stopStream(
                +            token=self._token,
                +            channel=self._stream_args["channel"],
                +            ts=self._stream_ts,
                +            blocks=blocks,
                +            markdown_text=self._buffer,
                +            metadata=metadata,
                +            **kwargs,
                +        )
                +        self._state = "completed"
                +        return response
                +
                +    async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse:
                +        """Flush the internal buffer by making appropriate API calls."""
                +        if not self._stream_ts:
                +            response = await self._client.chat_startStream(
                +                **self._stream_args,
                +                token=self._token,
                +                **kwargs,
                +                markdown_text=self._buffer,
                +            )
                +            self._stream_ts = response.get("ts")
                +            self._state = "in_progress"
                +        else:
                +            response = await self._client.chat_appendStream(
                +                token=self._token,
                +                channel=self._stream_args["channel"],
                +                ts=self._stream_ts,
                +                **kwargs,
                +                markdown_text=self._buffer,
                +            )
                +        self._buffer = ""
                +        return response
                +
                +

                A helper class for streaming markdown text into a conversation using the chat streaming APIs.

                +

                This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API +methods, with automatic buffering and state management.

                +

                Initialize a new ChatStream instance.

                +

                The init method creates a unique ChatStream instance that keeps track of one chat stream.

                +

                Args

                +
                +
                client
                +
                The WebClient instance to use for API calls.
                +
                channel
                +
                An encoded ID that represents a channel, private group, or DM.
                +
                logger
                +
                A logging channel for outputs.
                +
                thread_ts
                +
                Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
                +
                recipient_team_id
                +
                The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
                +
                recipient_user_id
                +
                The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +
                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.
                +
                +

                Methods

                +
                +
                +async def append(self, *, markdown_text: str, **kwargs) ‑> AsyncSlackResponse | None +
                +
                +
                + +Expand source code + +
                async def append(
                +    self,
                +    *,
                +    markdown_text: str,
                +    **kwargs,
                +) -> Optional[AsyncSlackResponse]:
                +    """Append to the stream.
                +
                +    The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
                +    is stopped this method cannot be called.
                +
                +    Args:
                +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +          what will be appended to the message received so far.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        AsyncSlackResponse if the buffer was flushed, None if buffering.
                +
                +    Raises:
                +        SlackRequestError: If the stream is already completed.
                +
                +    Example:
                +        ```python
                +        streamer = client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        streamer.append(markdown_text="**hello wo")
                +        streamer.append(markdown_text="rld!**")
                +        streamer.stop()
                +        ```
                +    """
                +    if self._state == "completed":
                +        raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
                +    if kwargs.get("token"):
                +        self._token = kwargs.pop("token")
                +    self._buffer += markdown_text
                +    if len(self._buffer) >= self._buffer_size:
                +        return await self._flush_buffer(**kwargs)
                +    details = {
                +        "buffer_length": len(self._buffer),
                +        "buffer_size": self._buffer_size,
                +        "channel": self._stream_args.get("channel"),
                +        "recipient_team_id": self._stream_args.get("recipient_team_id"),
                +        "recipient_user_id": self._stream_args.get("recipient_user_id"),
                +        "thread_ts": self._stream_args.get("thread_ts"),
                +    }
                +    self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
                +    return None
                +
                +

                Append to the stream.

                +

                The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream +is stopped this method cannot be called.

                +

                Args

                +
                +
                markdown_text
                +
                Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                AsyncSlackResponse if the buffer was flushed, None if buffering.

                +

                Raises

                +
                +
                SlackRequestError
                +
                If the stream is already completed.
                +
                +

                Example

                +
                streamer = client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +streamer.append(markdown_text="**hello wo")
                +streamer.append(markdown_text="rld!**")
                +streamer.stop()
                +
                +
                +
                +async def stop(self,
                *,
                markdown_text: str | None = None,
                blocks: str | Sequence[Dict | Block] | None = None,
                metadata: Dict | Metadata | None = None,
                **kwargs) ‑> AsyncSlackResponse
                +
                +
                +
                + +Expand source code + +
                async def stop(
                +    self,
                +    *,
                +    markdown_text: Optional[str] = None,
                +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +    metadata: Optional[Union[Dict, Metadata]] = None,
                +    **kwargs,
                +) -> AsyncSlackResponse:
                +    """Stop the stream and finalize the message.
                +
                +    Args:
                +        blocks: A list of blocks that will be rendered at the bottom of the finalized message.
                +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +          what will be appended to the message received so far.
                +        metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
                +          post to Slack is accessible to any app or user who is a member of that workspace.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        AsyncSlackResponse from the chat.stopStream API call.
                +
                +    Raises:
                +        SlackRequestError: If the stream is already completed.
                +
                +    Example:
                +        ```python
                +        streamer = client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        streamer.append(markdown_text="**hello wo")
                +        streamer.append(markdown_text="rld!**")
                +        streamer.stop()
                +        ```
                +    """
                +    if self._state == "completed":
                +        raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
                +    if kwargs.get("token"):
                +        self._token = kwargs.pop("token")
                +    if markdown_text:
                +        self._buffer += markdown_text
                +    if not self._stream_ts:
                +        response = await self._client.chat_startStream(
                +            **self._stream_args,
                +            token=self._token,
                +        )
                +        if not response.get("ts"):
                +            raise e.SlackRequestError("Failed to stop stream: stream not started")
                +        self._stream_ts = str(response["ts"])
                +        self._state = "in_progress"
                +    response = await self._client.chat_stopStream(
                +        token=self._token,
                +        channel=self._stream_args["channel"],
                +        ts=self._stream_ts,
                +        blocks=blocks,
                +        markdown_text=self._buffer,
                +        metadata=metadata,
                +        **kwargs,
                +    )
                +    self._state = "completed"
                +    return response
                +
                +

                Stop the stream and finalize the message.

                +

                Args

                +
                +
                blocks
                +
                A list of blocks that will be rendered at the bottom of the finalized message.
                +
                markdown_text
                +
                Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
                +
                metadata
                +
                JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you +post to Slack is accessible to any app or user who is a member of that workspace.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                AsyncSlackResponse from the chat.stopStream API call.

                +

                Raises

                +
                +
                SlackRequestError
                +
                If the stream is already completed.
                +
                +

                Example

                +
                streamer = client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +streamer.append(markdown_text="**hello wo")
                +streamer.append(markdown_text="rld!**")
                +streamer.stop()
                +
                +
                +
                +
                +
                +
                +
                + +
                + + + diff --git a/docs/reference/web/async_client.html b/docs/reference/web/async_client.html index 430319ea6..6e9c712fe 100644 --- a/docs/reference/web/async_client.html +++ b/docs/reference/web/async_client.html @@ -2097,13 +2097,17 @@

                Classes

                channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """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}) - return await self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("assistant.threads.setStatus", json=kwargs) async def assistant_threads_setTitle( self, @@ -2113,7 +2117,7 @@

                Classes

                title: str, **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2128,7 +2132,7 @@

                Classes

                prompts: List[Dict[str, str]], **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2642,6 +2646,27 @@

                Classes

                # -------------------------- + async def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> AsyncSlackResponse: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("chat.appendStream", json=kwargs) + async def chat_delete( self, *, @@ -2846,6 +2871,146 @@

                Classes

                # NOTE: intentionally using json over params for the API methods using blocks/attachments return await self.api_call("chat.scheduleMessage", json=kwargs) + async def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return await self.api_call("chat.scheduledMessages.list", params=kwargs) + + async def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("chat.startStream", json=kwargs) + + async def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return await self.api_call("chat.stopStream", json=kwargs) + + async def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> AsyncChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = await client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + await streamer.append(markdown_text="**hello wo") + await streamer.append(markdown_text="rld!**") + await streamer.stop() + ``` + """ + return AsyncChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + async def chat_unfurl( self, *, @@ -2926,32 +3091,6 @@

                Classes

                # NOTE: intentionally using json over params for API methods using blocks/attachments return await self.api_call("chat.update", json=kwargs) - async def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> AsyncSlackResponse: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return await self.api_call("chat.scheduledMessages.list", params=kwargs) - async def conversations_acceptSharedInvite( self, *, @@ -8786,7 +8925,7 @@

                Methods

                https://docs.slack.dev/reference/methods/apps.uninstall

                -async def assistant_threads_setStatus(self, *, channel_id: str, thread_ts: str, status: str, **kwargs) ‑> AsyncSlackResponse +async def assistant_threads_setStatus(self,
                *,
                channel_id: str,
                thread_ts: str,
                status: str,
                loading_messages: List[str] | None = None,
                **kwargs) ‑> AsyncSlackResponse
                @@ -8799,15 +8938,19 @@

                Methods

                channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """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}) - return await self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("assistant.threads.setStatus", json=kwargs)
                -

                Revokes a token. +

                @@ -8827,7 +8970,7 @@

                Methods

                prompts: List[Dict[str, str]], **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -8835,7 +8978,7 @@

                Methods

                kwargs.update({"title": title}) return await self.api_call("assistant.threads.setSuggestedPrompts", json=kwargs)
                -

                Revokes a token. +

                Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts

                @@ -8854,13 +8997,13 @@

                Methods

                title: str, **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) return await self.api_call("assistant.threads.setTitle", params=kwargs)
                -

                Revokes a token. +

                Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle

                @@ -9731,6 +9874,38 @@

                Methods

                Unarchives a channel.

                +
                +async def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> AsyncSlackResponse +
                +
                +
                + +Expand source code + +
                async def chat_appendStream(
                +    self,
                +    *,
                +    channel: str,
                +    ts: str,
                +    markdown_text: str,
                +    **kwargs,
                +) -> AsyncSlackResponse:
                +    """Appends text to an existing streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.appendStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "ts": ts,
                +            "markdown_text": markdown_text,
                +        }
                +    )
                +    kwargs = _remove_none_values(kwargs)
                +    return await self.api_call("chat.appendStream", json=kwargs)
                +
                +

                Appends text to an existing streaming conversation. +https://docs.slack.dev/reference/methods/chat.appendStream

                +
                async def chat_delete(self, *, channel: str, ts: str, as_user: bool | None = None, **kwargs) ‑> AsyncSlackResponse
                @@ -10049,6 +10224,195 @@

                Methods

                +
                +async def chat_startStream(self,
                *,
                channel: str,
                thread_ts: str,
                markdown_text: str | None = None,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs) ‑> AsyncSlackResponse
                +
                +
                +
                + +Expand source code + +
                async def chat_startStream(
                +    self,
                +    *,
                +    channel: str,
                +    thread_ts: str,
                +    markdown_text: Optional[str] = None,
                +    recipient_team_id: Optional[str] = None,
                +    recipient_user_id: Optional[str] = None,
                +    **kwargs,
                +) -> AsyncSlackResponse:
                +    """Starts a new streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.startStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "thread_ts": thread_ts,
                +            "markdown_text": markdown_text,
                +            "recipient_team_id": recipient_team_id,
                +            "recipient_user_id": recipient_user_id,
                +        }
                +    )
                +    kwargs = _remove_none_values(kwargs)
                +    return await self.api_call("chat.startStream", json=kwargs)
                +
                +

                Starts a new streaming conversation. +https://docs.slack.dev/reference/methods/chat.startStream

                +
                +
                +async def chat_stopStream(self,
                *,
                channel: str,
                ts: str,
                markdown_text: str | None = None,
                blocks: str | Sequence[Dict | Block] | None = None,
                metadata: Dict | Metadata | None = None,
                **kwargs) ‑> AsyncSlackResponse
                +
                +
                +
                + +Expand source code + +
                async def chat_stopStream(
                +    self,
                +    *,
                +    channel: str,
                +    ts: str,
                +    markdown_text: Optional[str] = None,
                +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +    metadata: Optional[Union[Dict, Metadata]] = None,
                +    **kwargs,
                +) -> AsyncSlackResponse:
                +    """Stops a streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.stopStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "ts": ts,
                +            "markdown_text": markdown_text,
                +            "blocks": blocks,
                +            "metadata": metadata,
                +        }
                +    )
                +    _parse_web_class_objects(kwargs)
                +    kwargs = _remove_none_values(kwargs)
                +    return await self.api_call("chat.stopStream", json=kwargs)
                +
                + +
                +
                +async def chat_stream(self,
                *,
                buffer_size: int = 256,
                channel: str,
                thread_ts: str,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs) ‑> AsyncChatStream
                +
                +
                +
                + +Expand source code + +
                async def chat_stream(
                +    self,
                +    *,
                +    buffer_size: int = 256,
                +    channel: str,
                +    thread_ts: str,
                +    recipient_team_id: Optional[str] = None,
                +    recipient_user_id: Optional[str] = None,
                +    **kwargs,
                +) -> AsyncChatStream:
                +    """Stream markdown text into a conversation.
                +
                +    This method starts a new chat stream in a conversation that can be appended to. After appending an entire message,
                +    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
                +
                +    The following methods are used:
                +
                +    - chat.startStream: Starts a new streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
                +    - chat.appendStream: Appends text to an existing streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
                +    - chat.stopStream: Stops a streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
                +
                +    Args:
                +        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
                +          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
                +          limits. Default: 256.
                +        channel: An encoded ID that represents a channel, private group, or DM.
                +        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
                +          request.
                +        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
                +          streaming to channels.
                +        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        ChatStream instance for managing the stream
                +
                +    Example:
                +        ```python
                +        streamer = await client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        await streamer.append(markdown_text="**hello wo")
                +        await streamer.append(markdown_text="rld!**")
                +        await streamer.stop()
                +        ```
                +    """
                +    return AsyncChatStream(
                +        self,
                +        logger=self._logger,
                +        channel=channel,
                +        thread_ts=thread_ts,
                +        recipient_team_id=recipient_team_id,
                +        recipient_user_id=recipient_user_id,
                +        buffer_size=buffer_size,
                +        **kwargs,
                +    )
                +
                +

                Stream markdown text into a conversation.

                +

                This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

                +

                The following methods are used:

                +
                  +
                • chat.startStream: Starts a new streaming conversation. +Reference.
                • +
                • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
                • +
                • chat.stopStream: Stops a streaming conversation. +Reference.
                • +
                +

                Args

                +
                +
                buffer_size
                +
                The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
                +
                channel
                +
                An encoded ID that represents a channel, private group, or DM.
                +
                thread_ts
                +
                Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
                +
                recipient_team_id
                +
                The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
                +
                recipient_user_id
                +
                The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                ChatStream instance for managing the stream

                +

                Example

                +
                streamer = await client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +await streamer.append(markdown_text="**hello wo")
                +await streamer.append(markdown_text="rld!**")
                +await streamer.stop()
                +
                +
                async def chat_unfurl(self,
                *,
                channel: str | None = None,
                ts: str | None = None,
                source: str | None = None,
                unfurl_id: str | None = None,
                unfurls: Dict[str, Dict] | None = None,
                user_auth_blocks: str | Sequence[Dict | Block] | None = None,
                user_auth_message: str | None = None,
                user_auth_required: bool | None = None,
                user_auth_url: str | None = None,
                **kwargs) ‑> AsyncSlackResponse
                @@ -14555,6 +14919,7 @@

                channels_setPurpose
              • channels_setTopic
              • channels_unarchive
              • +
              • chat_appendStream
              • chat_delete
              • chat_deleteScheduledMessage
              • chat_getPermalink
              • @@ -14563,6 +14928,9 @@

                chat_postMessage
              • chat_scheduleMessage
              • chat_scheduledMessages_list
              • +
              • chat_startStream
              • +
              • chat_stopStream
              • +
              • chat_stream
              • chat_unfurl
              • chat_update
              • conversations_acceptSharedInvite
              • diff --git a/docs/reference/web/chat_stream.html b/docs/reference/web/chat_stream.html new file mode 100644 index 000000000..94d96e5eb --- /dev/null +++ b/docs/reference/web/chat_stream.html @@ -0,0 +1,506 @@ + + + + + + +slack_sdk.web.chat_stream API documentation + + + + + + + + + + + +
                +
                +
                +

                Module slack_sdk.web.chat_stream

                +
                +
                +
                +
                +
                +
                +
                +
                +
                +
                +

                Classes

                +
                +
                +class ChatStream +(client: WebClient,
                *,
                channel: str,
                logger: logging.Logger,
                thread_ts: str,
                buffer_size: int,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs)
                +
                +
                +
                + +Expand source code + +
                class ChatStream:
                +    """A helper class for streaming markdown text into a conversation using the chat streaming APIs.
                +
                +    This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API
                +    methods, with automatic buffering and state management.
                +    """
                +
                +    def __init__(
                +        self,
                +        client: "WebClient",
                +        *,
                +        channel: str,
                +        logger: logging.Logger,
                +        thread_ts: str,
                +        buffer_size: int,
                +        recipient_team_id: Optional[str] = None,
                +        recipient_user_id: Optional[str] = None,
                +        **kwargs,
                +    ):
                +        """Initialize a new ChatStream instance.
                +
                +        The __init__ method creates a unique ChatStream instance that keeps track of one chat stream.
                +
                +        Args:
                +            client: The WebClient instance to use for API calls.
                +            channel: An encoded ID that represents a channel, private group, or DM.
                +            logger: A logging channel for outputs.
                +            thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
                +              request.
                +            recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
                +              streaming to channels.
                +            recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +            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.
                +        """
                +        self._client = client
                +        self._logger = logger
                +        self._token: Optional[str] = kwargs.pop("token", None)
                +        self._stream_args = {
                +            "channel": channel,
                +            "thread_ts": thread_ts,
                +            "recipient_team_id": recipient_team_id,
                +            "recipient_user_id": recipient_user_id,
                +            **kwargs,
                +        }
                +        self._buffer = ""
                +        self._state = "starting"
                +        self._stream_ts: Optional[str] = None
                +        self._buffer_size = buffer_size
                +
                +    def append(
                +        self,
                +        *,
                +        markdown_text: str,
                +        **kwargs,
                +    ) -> Optional[SlackResponse]:
                +        """Append to the stream.
                +
                +        The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
                +        is stopped this method cannot be called.
                +
                +        Args:
                +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +              what will be appended to the message received so far.
                +            **kwargs: Additional arguments passed to the underlying API calls.
                +
                +        Returns:
                +            SlackResponse if the buffer was flushed, None if buffering.
                +
                +        Raises:
                +            SlackRequestError: If the stream is already completed.
                +
                +        Example:
                +            ```python
                +            streamer = client.chat_stream(
                +                channel="C0123456789",
                +                thread_ts="1700000001.123456",
                +                recipient_team_id="T0123456789",
                +                recipient_user_id="U0123456789",
                +            )
                +            streamer.append(markdown_text="**hello wo")
                +            streamer.append(markdown_text="rld!**")
                +            streamer.stop()
                +            ```
                +        """
                +        if self._state == "completed":
                +            raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
                +        if kwargs.get("token"):
                +            self._token = kwargs.pop("token")
                +        self._buffer += markdown_text
                +        if len(self._buffer) >= self._buffer_size:
                +            return self._flush_buffer(**kwargs)
                +        details = {
                +            "buffer_length": len(self._buffer),
                +            "buffer_size": self._buffer_size,
                +            "channel": self._stream_args.get("channel"),
                +            "recipient_team_id": self._stream_args.get("recipient_team_id"),
                +            "recipient_user_id": self._stream_args.get("recipient_user_id"),
                +            "thread_ts": self._stream_args.get("thread_ts"),
                +        }
                +        self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
                +        return None
                +
                +    def stop(
                +        self,
                +        *,
                +        markdown_text: Optional[str] = None,
                +        blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +        metadata: Optional[Union[Dict, Metadata]] = None,
                +        **kwargs,
                +    ) -> SlackResponse:
                +        """Stop the stream and finalize the message.
                +
                +        Args:
                +            blocks: A list of blocks that will be rendered at the bottom of the finalized message.
                +            markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +              what will be appended to the message received so far.
                +            metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
                +              post to Slack is accessible to any app or user who is a member of that workspace.
                +            **kwargs: Additional arguments passed to the underlying API calls.
                +
                +        Returns:
                +            SlackResponse from the chat.stopStream API call.
                +
                +        Raises:
                +            SlackRequestError: If the stream is already completed.
                +
                +        Example:
                +            ```python
                +            streamer = client.chat_stream(
                +                channel="C0123456789",
                +                thread_ts="1700000001.123456",
                +                recipient_team_id="T0123456789",
                +                recipient_user_id="U0123456789",
                +            )
                +            streamer.append(markdown_text="**hello wo")
                +            streamer.append(markdown_text="rld!**")
                +            streamer.stop()
                +            ```
                +        """
                +        if self._state == "completed":
                +            raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
                +        if kwargs.get("token"):
                +            self._token = kwargs.pop("token")
                +        if markdown_text:
                +            self._buffer += markdown_text
                +        if not self._stream_ts:
                +            response = self._client.chat_startStream(
                +                **self._stream_args,
                +                token=self._token,
                +            )
                +            if not response.get("ts"):
                +                raise e.SlackRequestError("Failed to stop stream: stream not started")
                +            self._stream_ts = str(response["ts"])
                +            self._state = "in_progress"
                +        response = self._client.chat_stopStream(
                +            token=self._token,
                +            channel=self._stream_args["channel"],
                +            ts=self._stream_ts,
                +            blocks=blocks,
                +            markdown_text=self._buffer,
                +            metadata=metadata,
                +            **kwargs,
                +        )
                +        self._state = "completed"
                +        return response
                +
                +    def _flush_buffer(self, **kwargs) -> SlackResponse:
                +        """Flush the internal buffer by making appropriate API calls."""
                +        if not self._stream_ts:
                +            response = self._client.chat_startStream(
                +                **self._stream_args,
                +                token=self._token,
                +                **kwargs,
                +                markdown_text=self._buffer,
                +            )
                +            self._stream_ts = response.get("ts")
                +            self._state = "in_progress"
                +        else:
                +            response = self._client.chat_appendStream(
                +                token=self._token,
                +                channel=self._stream_args["channel"],
                +                ts=self._stream_ts,
                +                **kwargs,
                +                markdown_text=self._buffer,
                +            )
                +        self._buffer = ""
                +        return response
                +
                +

                A helper class for streaming markdown text into a conversation using the chat streaming APIs.

                +

                This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API +methods, with automatic buffering and state management.

                +

                Initialize a new ChatStream instance.

                +

                The init method creates a unique ChatStream instance that keeps track of one chat stream.

                +

                Args

                +
                +
                client
                +
                The WebClient instance to use for API calls.
                +
                channel
                +
                An encoded ID that represents a channel, private group, or DM.
                +
                logger
                +
                A logging channel for outputs.
                +
                thread_ts
                +
                Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
                +
                recipient_team_id
                +
                The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
                +
                recipient_user_id
                +
                The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +
                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.
                +
                +

                Methods

                +
                +
                +def append(self, *, markdown_text: str, **kwargs) ‑> SlackResponse | None +
                +
                +
                + +Expand source code + +
                def append(
                +    self,
                +    *,
                +    markdown_text: str,
                +    **kwargs,
                +) -> Optional[SlackResponse]:
                +    """Append to the stream.
                +
                +    The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream
                +    is stopped this method cannot be called.
                +
                +    Args:
                +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +          what will be appended to the message received so far.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        SlackResponse if the buffer was flushed, None if buffering.
                +
                +    Raises:
                +        SlackRequestError: If the stream is already completed.
                +
                +    Example:
                +        ```python
                +        streamer = client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        streamer.append(markdown_text="**hello wo")
                +        streamer.append(markdown_text="rld!**")
                +        streamer.stop()
                +        ```
                +    """
                +    if self._state == "completed":
                +        raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}")
                +    if kwargs.get("token"):
                +        self._token = kwargs.pop("token")
                +    self._buffer += markdown_text
                +    if len(self._buffer) >= self._buffer_size:
                +        return self._flush_buffer(**kwargs)
                +    details = {
                +        "buffer_length": len(self._buffer),
                +        "buffer_size": self._buffer_size,
                +        "channel": self._stream_args.get("channel"),
                +        "recipient_team_id": self._stream_args.get("recipient_team_id"),
                +        "recipient_user_id": self._stream_args.get("recipient_user_id"),
                +        "thread_ts": self._stream_args.get("thread_ts"),
                +    }
                +    self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}")
                +    return None
                +
                +

                Append to the stream.

                +

                The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream +is stopped this method cannot be called.

                +

                Args

                +
                +
                markdown_text
                +
                Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                SlackResponse if the buffer was flushed, None if buffering.

                +

                Raises

                +
                +
                SlackRequestError
                +
                If the stream is already completed.
                +
                +

                Example

                +
                streamer = client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +streamer.append(markdown_text="**hello wo")
                +streamer.append(markdown_text="rld!**")
                +streamer.stop()
                +
                +
                +
                +def stop(self,
                *,
                markdown_text: str | None = None,
                blocks: str | Sequence[Dict | Block] | None = None,
                metadata: Dict | Metadata | None = None,
                **kwargs) ‑> SlackResponse
                +
                +
                +
                + +Expand source code + +
                def stop(
                +    self,
                +    *,
                +    markdown_text: Optional[str] = None,
                +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +    metadata: Optional[Union[Dict, Metadata]] = None,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Stop the stream and finalize the message.
                +
                +    Args:
                +        blocks: A list of blocks that will be rendered at the bottom of the finalized message.
                +        markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is
                +          what will be appended to the message received so far.
                +        metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you
                +          post to Slack is accessible to any app or user who is a member of that workspace.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        SlackResponse from the chat.stopStream API call.
                +
                +    Raises:
                +        SlackRequestError: If the stream is already completed.
                +
                +    Example:
                +        ```python
                +        streamer = client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        streamer.append(markdown_text="**hello wo")
                +        streamer.append(markdown_text="rld!**")
                +        streamer.stop()
                +        ```
                +    """
                +    if self._state == "completed":
                +        raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}")
                +    if kwargs.get("token"):
                +        self._token = kwargs.pop("token")
                +    if markdown_text:
                +        self._buffer += markdown_text
                +    if not self._stream_ts:
                +        response = self._client.chat_startStream(
                +            **self._stream_args,
                +            token=self._token,
                +        )
                +        if not response.get("ts"):
                +            raise e.SlackRequestError("Failed to stop stream: stream not started")
                +        self._stream_ts = str(response["ts"])
                +        self._state = "in_progress"
                +    response = self._client.chat_stopStream(
                +        token=self._token,
                +        channel=self._stream_args["channel"],
                +        ts=self._stream_ts,
                +        blocks=blocks,
                +        markdown_text=self._buffer,
                +        metadata=metadata,
                +        **kwargs,
                +    )
                +    self._state = "completed"
                +    return response
                +
                +

                Stop the stream and finalize the message.

                +

                Args

                +
                +
                blocks
                +
                A list of blocks that will be rendered at the bottom of the finalized message.
                +
                markdown_text
                +
                Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is +what will be appended to the message received so far.
                +
                metadata
                +
                JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you +post to Slack is accessible to any app or user who is a member of that workspace.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                SlackResponse from the chat.stopStream API call.

                +

                Raises

                +
                +
                SlackRequestError
                +
                If the stream is already completed.
                +
                +

                Example

                +
                streamer = client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +streamer.append(markdown_text="**hello wo")
                +streamer.append(markdown_text="rld!**")
                +streamer.stop()
                +
                +
                +
                +
                +
                +
                +
                + +
                + + + diff --git a/docs/reference/web/client.html b/docs/reference/web/client.html index 3a315c83d..6be773f23 100644 --- a/docs/reference/web/client.html +++ b/docs/reference/web/client.html @@ -2097,13 +2097,17 @@

                Classes

                channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs) def assistant_threads_setTitle( self, @@ -2113,7 +2117,7 @@

                Classes

                title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2128,7 +2132,7 @@

                Classes

                prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2642,6 +2646,27 @@

                Classes

                # -------------------------- + def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> SlackResponse: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.appendStream", json=kwargs) + def chat_delete( self, *, @@ -2846,6 +2871,146 @@

                Classes

                # NOTE: intentionally using json over params for the API methods using blocks/attachments return self.api_call("chat.scheduleMessage", json=kwargs) + def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return self.api_call("chat.scheduledMessages.list", params=kwargs) + + def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.startStream", json=kwargs) + + def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> SlackResponse: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.stopStream", json=kwargs) + + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -2926,32 +3091,6 @@

                Classes

                # NOTE: intentionally using json over params for API methods using blocks/attachments return self.api_call("chat.update", json=kwargs) - def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> SlackResponse: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return self.api_call("chat.scheduledMessages.list", params=kwargs) - def conversations_acceptSharedInvite( self, *, @@ -8786,7 +8925,7 @@

                Methods

                https://docs.slack.dev/reference/methods/apps.uninstall

                -def assistant_threads_setStatus(self, *, channel_id: str, thread_ts: str, status: str, **kwargs) ‑> SlackResponse +def assistant_threads_setStatus(self,
                *,
                channel_id: str,
                thread_ts: str,
                status: str,
                loading_messages: List[str] | None = None,
                **kwargs) ‑> SlackResponse
                @@ -8799,15 +8938,19 @@

                Methods

                channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs)
                + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs)
                -

                Revokes a token. +

                @@ -8827,7 +8970,7 @@

                Methods

                prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -8835,7 +8978,7 @@

                Methods

                kwargs.update({"title": title}) return self.api_call("assistant.threads.setSuggestedPrompts", json=kwargs)
                -

                Revokes a token. +

                Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts

                @@ -8854,13 +8997,13 @@

                Methods

                title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) return self.api_call("assistant.threads.setTitle", params=kwargs)
                -

                Revokes a token. +

                Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle

                @@ -9731,6 +9874,38 @@

                Methods

                Unarchives a channel.

                +
                +def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +
                +
                +
                + +Expand source code + +
                def chat_appendStream(
                +    self,
                +    *,
                +    channel: str,
                +    ts: str,
                +    markdown_text: str,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Appends text to an existing streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.appendStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "ts": ts,
                +            "markdown_text": markdown_text,
                +        }
                +    )
                +    kwargs = _remove_none_values(kwargs)
                +    return self.api_call("chat.appendStream", json=kwargs)
                +
                +

                Appends text to an existing streaming conversation. +https://docs.slack.dev/reference/methods/chat.appendStream

                +
                def chat_delete(self, *, channel: str, ts: str, as_user: bool | None = None, **kwargs) ‑> SlackResponse
                @@ -10049,6 +10224,195 @@

                Methods

                +
                +def chat_startStream(self,
                *,
                channel: str,
                thread_ts: str,
                markdown_text: str | None = None,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs) ‑> SlackResponse
                +
                +
                +
                + +Expand source code + +
                def chat_startStream(
                +    self,
                +    *,
                +    channel: str,
                +    thread_ts: str,
                +    markdown_text: Optional[str] = None,
                +    recipient_team_id: Optional[str] = None,
                +    recipient_user_id: Optional[str] = None,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Starts a new streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.startStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "thread_ts": thread_ts,
                +            "markdown_text": markdown_text,
                +            "recipient_team_id": recipient_team_id,
                +            "recipient_user_id": recipient_user_id,
                +        }
                +    )
                +    kwargs = _remove_none_values(kwargs)
                +    return self.api_call("chat.startStream", json=kwargs)
                +
                +

                Starts a new streaming conversation. +https://docs.slack.dev/reference/methods/chat.startStream

                +
                +
                +def chat_stopStream(self,
                *,
                channel: str,
                ts: str,
                markdown_text: str | None = None,
                blocks: str | Sequence[Dict | Block] | None = None,
                metadata: Dict | Metadata | None = None,
                **kwargs) ‑> SlackResponse
                +
                +
                +
                + +Expand source code + +
                def chat_stopStream(
                +    self,
                +    *,
                +    channel: str,
                +    ts: str,
                +    markdown_text: Optional[str] = None,
                +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +    metadata: Optional[Union[Dict, Metadata]] = None,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Stops a streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.stopStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "ts": ts,
                +            "markdown_text": markdown_text,
                +            "blocks": blocks,
                +            "metadata": metadata,
                +        }
                +    )
                +    _parse_web_class_objects(kwargs)
                +    kwargs = _remove_none_values(kwargs)
                +    return self.api_call("chat.stopStream", json=kwargs)
                +
                + +
                +
                +def chat_stream(self,
                *,
                buffer_size: int = 256,
                channel: str,
                thread_ts: str,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs) ‑> ChatStream
                +
                +
                +
                + +Expand source code + +
                def chat_stream(
                +    self,
                +    *,
                +    buffer_size: int = 256,
                +    channel: str,
                +    thread_ts: str,
                +    recipient_team_id: Optional[str] = None,
                +    recipient_user_id: Optional[str] = None,
                +    **kwargs,
                +) -> ChatStream:
                +    """Stream markdown text into a conversation.
                +
                +    This method starts a new chat stream in a conversation that can be appended to. After appending an entire message,
                +    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
                +
                +    The following methods are used:
                +
                +    - chat.startStream: Starts a new streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
                +    - chat.appendStream: Appends text to an existing streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
                +    - chat.stopStream: Stops a streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
                +
                +    Args:
                +        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
                +          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
                +          limits. Default: 256.
                +        channel: An encoded ID that represents a channel, private group, or DM.
                +        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
                +          request.
                +        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
                +          streaming to channels.
                +        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        ChatStream instance for managing the stream
                +
                +    Example:
                +        ```python
                +        streamer = client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        streamer.append(markdown_text="**hello wo")
                +        streamer.append(markdown_text="rld!**")
                +        streamer.stop()
                +        ```
                +    """
                +    return ChatStream(
                +        self,
                +        logger=self._logger,
                +        channel=channel,
                +        thread_ts=thread_ts,
                +        recipient_team_id=recipient_team_id,
                +        recipient_user_id=recipient_user_id,
                +        buffer_size=buffer_size,
                +        **kwargs,
                +    )
                +
                +

                Stream markdown text into a conversation.

                +

                This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

                +

                The following methods are used:

                +
                  +
                • chat.startStream: Starts a new streaming conversation. +Reference.
                • +
                • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
                • +
                • chat.stopStream: Stops a streaming conversation. +Reference.
                • +
                +

                Args

                +
                +
                buffer_size
                +
                The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
                +
                channel
                +
                An encoded ID that represents a channel, private group, or DM.
                +
                thread_ts
                +
                Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
                +
                recipient_team_id
                +
                The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
                +
                recipient_user_id
                +
                The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                ChatStream instance for managing the stream

                +

                Example

                +
                streamer = client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +streamer.append(markdown_text="**hello wo")
                +streamer.append(markdown_text="rld!**")
                +streamer.stop()
                +
                +
                def chat_unfurl(self,
                *,
                channel: str | None = None,
                ts: str | None = None,
                source: str | None = None,
                unfurl_id: str | None = None,
                unfurls: Dict[str, Dict] | None = None,
                user_auth_blocks: str | Sequence[Dict | Block] | None = None,
                user_auth_message: str | None = None,
                user_auth_required: bool | None = None,
                user_auth_url: str | None = None,
                **kwargs) ‑> SlackResponse
                @@ -14554,6 +14918,7 @@

                channels_setPurpose
              • channels_setTopic
              • channels_unarchive
              • +
              • chat_appendStream
              • chat_delete
              • chat_deleteScheduledMessage
              • chat_getPermalink
              • @@ -14562,6 +14927,9 @@

                chat_postMessage
              • chat_scheduleMessage
              • chat_scheduledMessages_list
              • +
              • chat_startStream
              • +
              • chat_stopStream
              • +
              • chat_stream
              • chat_unfurl
              • chat_update
              • conversations_acceptSharedInvite
              • diff --git a/docs/reference/web/index.html b/docs/reference/web/index.html index ee73d3ba2..f4f7e2f2b 100644 --- a/docs/reference/web/index.html +++ b/docs/reference/web/index.html @@ -47,6 +47,10 @@

                Sub-modules

                +
                slack_sdk.web.async_chat_stream
                +
                +
                +
                slack_sdk.web.async_client

                A Python module for interacting with Slack's Web API.

                @@ -63,6 +67,10 @@

                Sub-modules

                A Python module for interacting with Slack's Web API.

                +
                slack_sdk.web.chat_stream
                +
                +
                +
                slack_sdk.web.client

                A Python module for interacting with Slack's Web API.

                @@ -2458,13 +2466,17 @@

                Raises

                channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs) def assistant_threads_setTitle( self, @@ -2474,7 +2486,7 @@

                Raises

                title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2489,7 +2501,7 @@

                Raises

                prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -3003,6 +3015,27 @@

                Raises

                # -------------------------- + def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> SlackResponse: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.appendStream", json=kwargs) + def chat_delete( self, *, @@ -3207,6 +3240,146 @@

                Raises

                # NOTE: intentionally using json over params for the API methods using blocks/attachments return self.api_call("chat.scheduleMessage", json=kwargs) + def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return self.api_call("chat.scheduledMessages.list", params=kwargs) + + def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.startStream", json=kwargs) + + def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> SlackResponse: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.stopStream", json=kwargs) + + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -3287,32 +3460,6 @@

                Raises

                # NOTE: intentionally using json over params for API methods using blocks/attachments return self.api_call("chat.update", json=kwargs) - def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> SlackResponse: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return self.api_call("chat.scheduledMessages.list", params=kwargs) - def conversations_acceptSharedInvite( self, *, @@ -9147,7 +9294,7 @@

                Methods

                https://docs.slack.dev/reference/methods/apps.uninstall

                -def assistant_threads_setStatus(self, *, channel_id: str, thread_ts: str, status: str, **kwargs) ‑> SlackResponse +def assistant_threads_setStatus(self,
                *,
                channel_id: str,
                thread_ts: str,
                status: str,
                loading_messages: List[str] | None = None,
                **kwargs) ‑> SlackResponse
                @@ -9160,15 +9307,19 @@

                Methods

                channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs)
                + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs)
                -

                Revokes a token. +

                @@ -9188,7 +9339,7 @@

                Methods

                prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -9196,7 +9347,7 @@

                Methods

                kwargs.update({"title": title}) return self.api_call("assistant.threads.setSuggestedPrompts", json=kwargs)
                -

                Revokes a token. +

                Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts

                @@ -9215,13 +9366,13 @@

                Methods

                title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) return self.api_call("assistant.threads.setTitle", params=kwargs)
                -

                Revokes a token. +

                Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle

                @@ -10092,6 +10243,38 @@

                Methods

                Unarchives a channel.

                +
                +def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> SlackResponse +
                +
                +
                + +Expand source code + +
                def chat_appendStream(
                +    self,
                +    *,
                +    channel: str,
                +    ts: str,
                +    markdown_text: str,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Appends text to an existing streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.appendStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "ts": ts,
                +            "markdown_text": markdown_text,
                +        }
                +    )
                +    kwargs = _remove_none_values(kwargs)
                +    return self.api_call("chat.appendStream", json=kwargs)
                +
                +

                Appends text to an existing streaming conversation. +https://docs.slack.dev/reference/methods/chat.appendStream

                +
                def chat_delete(self, *, channel: str, ts: str, as_user: bool | None = None, **kwargs) ‑> SlackResponse
                @@ -10410,6 +10593,195 @@

                Methods

                +
                +def chat_startStream(self,
                *,
                channel: str,
                thread_ts: str,
                markdown_text: str | None = None,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs) ‑> SlackResponse
                +
                +
                +
                + +Expand source code + +
                def chat_startStream(
                +    self,
                +    *,
                +    channel: str,
                +    thread_ts: str,
                +    markdown_text: Optional[str] = None,
                +    recipient_team_id: Optional[str] = None,
                +    recipient_user_id: Optional[str] = None,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Starts a new streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.startStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "thread_ts": thread_ts,
                +            "markdown_text": markdown_text,
                +            "recipient_team_id": recipient_team_id,
                +            "recipient_user_id": recipient_user_id,
                +        }
                +    )
                +    kwargs = _remove_none_values(kwargs)
                +    return self.api_call("chat.startStream", json=kwargs)
                +
                +

                Starts a new streaming conversation. +https://docs.slack.dev/reference/methods/chat.startStream

                +
                +
                +def chat_stopStream(self,
                *,
                channel: str,
                ts: str,
                markdown_text: str | None = None,
                blocks: str | Sequence[Dict | Block] | None = None,
                metadata: Dict | Metadata | None = None,
                **kwargs) ‑> SlackResponse
                +
                +
                +
                + +Expand source code + +
                def chat_stopStream(
                +    self,
                +    *,
                +    channel: str,
                +    ts: str,
                +    markdown_text: Optional[str] = None,
                +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                +    metadata: Optional[Union[Dict, Metadata]] = None,
                +    **kwargs,
                +) -> SlackResponse:
                +    """Stops a streaming conversation.
                +    https://docs.slack.dev/reference/methods/chat.stopStream
                +    """
                +    kwargs.update(
                +        {
                +            "channel": channel,
                +            "ts": ts,
                +            "markdown_text": markdown_text,
                +            "blocks": blocks,
                +            "metadata": metadata,
                +        }
                +    )
                +    _parse_web_class_objects(kwargs)
                +    kwargs = _remove_none_values(kwargs)
                +    return self.api_call("chat.stopStream", json=kwargs)
                +
                + +
                +
                +def chat_stream(self,
                *,
                buffer_size: int = 256,
                channel: str,
                thread_ts: str,
                recipient_team_id: str | None = None,
                recipient_user_id: str | None = None,
                **kwargs) ‑> ChatStream
                +
                +
                +
                + +Expand source code + +
                def chat_stream(
                +    self,
                +    *,
                +    buffer_size: int = 256,
                +    channel: str,
                +    thread_ts: str,
                +    recipient_team_id: Optional[str] = None,
                +    recipient_user_id: Optional[str] = None,
                +    **kwargs,
                +) -> ChatStream:
                +    """Stream markdown text into a conversation.
                +
                +    This method starts a new chat stream in a conversation that can be appended to. After appending an entire message,
                +    the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.
                +
                +    The following methods are used:
                +
                +    - chat.startStream: Starts a new streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.startStream).
                +    - chat.appendStream: Appends text to an existing streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.appendStream).
                +    - chat.stopStream: Stops a streaming conversation.
                +      [Reference](https://docs.slack.dev/reference/methods/chat.stopStream).
                +
                +    Args:
                +        buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this
                +          value decreases the number of method calls made for the same amount of text, which is useful to avoid rate
                +          limits. Default: 256.
                +        channel: An encoded ID that represents a channel, private group, or DM.
                +        thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user
                +          request.
                +        recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when
                +          streaming to channels.
                +        recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +        **kwargs: Additional arguments passed to the underlying API calls.
                +
                +    Returns:
                +        ChatStream instance for managing the stream
                +
                +    Example:
                +        ```python
                +        streamer = client.chat_stream(
                +            channel="C0123456789",
                +            thread_ts="1700000001.123456",
                +            recipient_team_id="T0123456789",
                +            recipient_user_id="U0123456789",
                +        )
                +        streamer.append(markdown_text="**hello wo")
                +        streamer.append(markdown_text="rld!**")
                +        streamer.stop()
                +        ```
                +    """
                +    return ChatStream(
                +        self,
                +        logger=self._logger,
                +        channel=channel,
                +        thread_ts=thread_ts,
                +        recipient_team_id=recipient_team_id,
                +        recipient_user_id=recipient_user_id,
                +        buffer_size=buffer_size,
                +        **kwargs,
                +    )
                +
                +

                Stream markdown text into a conversation.

                +

                This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, +the stream can be stopped with concluding arguments such as "blocks" for gathering feedback.

                +

                The following methods are used:

                +
                  +
                • chat.startStream: Starts a new streaming conversation. +Reference.
                • +
                • chat.appendStream: Appends text to an existing streaming conversation. +Reference.
                • +
                • chat.stopStream: Stops a streaming conversation. +Reference.
                • +
                +

                Args

                +
                +
                buffer_size
                +
                The length of markdown_text to buffer in-memory before calling a stream method. Increasing this +value decreases the number of method calls made for the same amount of text, which is useful to avoid rate +limits. Default: 256.
                +
                channel
                +
                An encoded ID that represents a channel, private group, or DM.
                +
                thread_ts
                +
                Provide another message's ts value to reply to. Streamed messages should always be replies to a user +request.
                +
                recipient_team_id
                +
                The encoded ID of the team the user receiving the streaming text belongs to. Required when +streaming to channels.
                +
                recipient_user_id
                +
                The encoded ID of the user to receive the streaming text. Required when streaming to channels.
                +
                **kwargs
                +
                Additional arguments passed to the underlying API calls.
                +
                +

                Returns

                +

                ChatStream instance for managing the stream

                +

                Example

                +
                streamer = client.chat_stream(
                +    channel="C0123456789",
                +    thread_ts="1700000001.123456",
                +    recipient_team_id="T0123456789",
                +    recipient_user_id="U0123456789",
                +)
                +streamer.append(markdown_text="**hello wo")
                +streamer.append(markdown_text="rld!**")
                +streamer.stop()
                +
                +
                def chat_unfurl(self,
                *,
                channel: str | None = None,
                ts: str | None = None,
                source: str | None = None,
                unfurl_id: str | None = None,
                unfurls: Dict[str, Dict] | None = None,
                user_auth_blocks: str | Sequence[Dict | Block] | None = None,
                user_auth_message: str | None = None,
                user_auth_required: bool | None = None,
                user_auth_url: str | None = None,
                **kwargs) ‑> SlackResponse
                @@ -14768,10 +15140,12 @@

                Inherited members

              • Sub-modules

                • slack_sdk.web.async_base_client
                • +
                • slack_sdk.web.async_chat_stream
                • slack_sdk.web.async_client
                • slack_sdk.web.async_internal_utils
                • slack_sdk.web.async_slack_response
                • slack_sdk.web.base_client
                • +
                • slack_sdk.web.chat_stream
                • slack_sdk.web.client
                • slack_sdk.web.deprecation
                • slack_sdk.web.file_upload_v2_result
                • @@ -14939,6 +15313,7 @@

                  Web
                • channels_setPurpose
                • channels_setTopic
                • channels_unarchive
                • +
                • chat_appendStream
                • chat_delete
                • chat_deleteScheduledMessage
                • chat_getPermalink
                • @@ -14947,6 +15322,9 @@

                  Web
                • chat_postMessage
                • chat_scheduleMessage
                • chat_scheduledMessages_list
                • +
                • chat_startStream
                • +
                • chat_stopStream
                • +
                • chat_stream
                • chat_unfurl
                • chat_update
                • conversations_acceptSharedInvite
                • diff --git a/docs/reference/web/legacy_client.html b/docs/reference/web/legacy_client.html index 0ac5ac6b9..70cc6988d 100644 --- a/docs/reference/web/legacy_client.html +++ b/docs/reference/web/legacy_client.html @@ -2096,13 +2096,17 @@

                  Classes

                  channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs) def assistant_threads_setTitle( self, @@ -2112,7 +2116,7 @@

                  Classes

                  title: str, **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2127,7 +2131,7 @@

                  Classes

                  prompts: List[Dict[str, str]], **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2641,6 +2645,27 @@

                  Classes

                  # -------------------------- + def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.appendStream", json=kwargs) + def chat_delete( self, *, @@ -2845,6 +2870,83 @@

                  Classes

                  # NOTE: intentionally using json over params for the API methods using blocks/attachments return self.api_call("chat.scheduleMessage", json=kwargs) + def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return self.api_call("chat.scheduledMessages.list", params=kwargs) + + def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.startStream", json=kwargs) + + def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.stopStream", json=kwargs) + def chat_unfurl( self, *, @@ -2925,32 +3027,6 @@

                  Classes

                  # NOTE: intentionally using json over params for API methods using blocks/attachments return self.api_call("chat.update", json=kwargs) - def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> Union[Future, SlackResponse]: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return self.api_call("chat.scheduledMessages.list", params=kwargs) - def conversations_acceptSharedInvite( self, *, @@ -8785,7 +8861,7 @@

                  Methods

                  https://docs.slack.dev/reference/methods/apps.uninstall

                  -def assistant_threads_setStatus(self, *, channel_id: str, thread_ts: str, status: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +def assistant_threads_setStatus(self,
                  *,
                  channel_id: str,
                  thread_ts: str,
                  status: str,
                  loading_messages: List[str] | None = None,
                  **kwargs) ‑> _asyncio.Future | LegacySlackResponse
                  @@ -8798,15 +8874,19 @@

                  Methods

                  channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs)
                  + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs)
                  -

                  Revokes a token. +

                  @@ -8826,7 +8906,7 @@

                  Methods

                  prompts: List[Dict[str, str]], **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -8834,7 +8914,7 @@

                  Methods

                  kwargs.update({"title": title}) return self.api_call("assistant.threads.setSuggestedPrompts", json=kwargs)
                  -

                  Revokes a token. +

                  Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts

                  @@ -8853,13 +8933,13 @@

                  Methods

                  title: str, **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) return self.api_call("assistant.threads.setTitle", params=kwargs)
                  -

                  Revokes a token. +

                  Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle

                  @@ -9730,6 +9810,38 @@

                  Methods

                  Unarchives a channel.

                  +
                  +def chat_appendStream(self, *, channel: str, ts: str, markdown_text: str, **kwargs) ‑> _asyncio.Future | LegacySlackResponse +
                  +
                  +
                  + +Expand source code + +
                  def chat_appendStream(
                  +    self,
                  +    *,
                  +    channel: str,
                  +    ts: str,
                  +    markdown_text: str,
                  +    **kwargs,
                  +) -> Union[Future, SlackResponse]:
                  +    """Appends text to an existing streaming conversation.
                  +    https://docs.slack.dev/reference/methods/chat.appendStream
                  +    """
                  +    kwargs.update(
                  +        {
                  +            "channel": channel,
                  +            "ts": ts,
                  +            "markdown_text": markdown_text,
                  +        }
                  +    )
                  +    kwargs = _remove_none_values(kwargs)
                  +    return self.api_call("chat.appendStream", json=kwargs)
                  +
                  +

                  Appends text to an existing streaming conversation. +https://docs.slack.dev/reference/methods/chat.appendStream

                  +
                  def chat_delete(self, *, channel: str, ts: str, as_user: bool | None = None, **kwargs) ‑> _asyncio.Future | LegacySlackResponse
                  @@ -10048,6 +10160,79 @@

                  Methods

                  +
                  +def chat_startStream(self,
                  *,
                  channel: str,
                  thread_ts: str,
                  markdown_text: str | None = None,
                  recipient_team_id: str | None = None,
                  recipient_user_id: str | None = None,
                  **kwargs) ‑> _asyncio.Future | LegacySlackResponse
                  +
                  +
                  +
                  + +Expand source code + +
                  def chat_startStream(
                  +    self,
                  +    *,
                  +    channel: str,
                  +    thread_ts: str,
                  +    markdown_text: Optional[str] = None,
                  +    recipient_team_id: Optional[str] = None,
                  +    recipient_user_id: Optional[str] = None,
                  +    **kwargs,
                  +) -> Union[Future, SlackResponse]:
                  +    """Starts a new streaming conversation.
                  +    https://docs.slack.dev/reference/methods/chat.startStream
                  +    """
                  +    kwargs.update(
                  +        {
                  +            "channel": channel,
                  +            "thread_ts": thread_ts,
                  +            "markdown_text": markdown_text,
                  +            "recipient_team_id": recipient_team_id,
                  +            "recipient_user_id": recipient_user_id,
                  +        }
                  +    )
                  +    kwargs = _remove_none_values(kwargs)
                  +    return self.api_call("chat.startStream", json=kwargs)
                  +
                  +

                  Starts a new streaming conversation. +https://docs.slack.dev/reference/methods/chat.startStream

                  +
                  +
                  +def chat_stopStream(self,
                  *,
                  channel: str,
                  ts: str,
                  markdown_text: str | None = None,
                  blocks: str | Sequence[Dict | Block] | None = None,
                  metadata: Dict | Metadata | None = None,
                  **kwargs) ‑> _asyncio.Future | LegacySlackResponse
                  +
                  +
                  +
                  + +Expand source code + +
                  def chat_stopStream(
                  +    self,
                  +    *,
                  +    channel: str,
                  +    ts: str,
                  +    markdown_text: Optional[str] = None,
                  +    blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None,
                  +    metadata: Optional[Union[Dict, Metadata]] = None,
                  +    **kwargs,
                  +) -> Union[Future, SlackResponse]:
                  +    """Stops a streaming conversation.
                  +    https://docs.slack.dev/reference/methods/chat.stopStream
                  +    """
                  +    kwargs.update(
                  +        {
                  +            "channel": channel,
                  +            "ts": ts,
                  +            "markdown_text": markdown_text,
                  +            "blocks": blocks,
                  +            "metadata": metadata,
                  +        }
                  +    )
                  +    _parse_web_class_objects(kwargs)
                  +    kwargs = _remove_none_values(kwargs)
                  +    return self.api_call("chat.stopStream", json=kwargs)
                  +
                  + +
                  def chat_unfurl(self,
                  *,
                  channel: str | None = None,
                  ts: str | None = None,
                  source: str | None = None,
                  unfurl_id: str | None = None,
                  unfurls: Dict[str, Dict] | None = None,
                  user_auth_blocks: str | Sequence[Dict | Block] | None = None,
                  user_auth_message: str | None = None,
                  user_auth_required: bool | None = None,
                  user_auth_url: str | None = None,
                  **kwargs) ‑> _asyncio.Future | LegacySlackResponse
                  @@ -14552,6 +14737,7 @@

                  channels_setPurpose
                • channels_setTopic
                • channels_unarchive
                • +
                • chat_appendStream
                • chat_delete
                • chat_deleteScheduledMessage
                • chat_getPermalink
                • @@ -14560,6 +14746,8 @@

                  chat_postMessage
                • chat_scheduleMessage
                • chat_scheduledMessages_list
                • +
                • chat_startStream
                • +
                • chat_stopStream
                • chat_unfurl
                • chat_update
                • conversations_acceptSharedInvite
                • diff --git a/scripts/codegen.py b/scripts/codegen.py index f58de8bd1..3633faf35 100644 --- a/scripts/codegen.py +++ b/scripts/codegen.py @@ -1,5 +1,5 @@ -import sys import argparse +import sys parser = argparse.ArgumentParser() parser.add_argument("-p", "--path", help="Path to the project source code.", type=str) @@ -35,7 +35,6 @@ "from .async_base_client import AsyncBaseClient, AsyncSlackResponse", async_source, ) - # from slack_sdk import WebClient async_source = re.sub( r"class WebClient\(BaseClient\):", "class AsyncWebClient(AsyncBaseClient):", @@ -47,6 +46,28 @@ async_source, ) async_source = re.sub(r"= WebClient\(", "= AsyncWebClient(", async_source) + async_source = re.sub( + "from slack_sdk.web.chat_stream import ChatStream", + "from slack_sdk.web.async_chat_stream import AsyncChatStream", + async_source, + ) + async_source = re.sub(r"ChatStream:", "AsyncChatStream:", async_source) + async_source = re.sub(r"ChatStream\(", "AsyncChatStream(", async_source) + async_source = re.sub( + r" client.chat_stream\(", + " await client.chat_stream(", + async_source, + ) + async_source = re.sub( + r" streamer.append\(", + " await streamer.append(", + async_source, + ) + async_source = re.sub( + r" streamer.stop\(", + " await streamer.stop(", + async_source, + ) async_source = re.sub( r" self.files_getUploadURLExternal\(", " await self.files_getUploadURLExternal(", @@ -98,5 +119,35 @@ legacy_source, ) legacy_source = re.sub(r"= WebClient\(", "= LegacyWebClient(", legacy_source) + legacy_source = re.sub(r"^from slack_sdk.web.chat_stream import ChatStream\n", "", legacy_source, flags=re.MULTILINE) + legacy_source = re.sub(r"(?s)def chat_stream.*?(?=def)", "", legacy_source) with open(f"{args.path}/slack_sdk/web/legacy_client.py", "w") as output: output.write(legacy_source) + +with open(f"{args.path}/slack_sdk/web/chat_stream.py", "r") as original: + source = original.read() + import re + + async_source = header + source + async_source = re.sub( + "from slack_sdk.web.slack_response import SlackResponse", + "from slack_sdk.web.async_slack_response import AsyncSlackResponse", + async_source, + ) + async_source = re.sub( + r"from slack_sdk import WebClient", + "from slack_sdk.web.async_client import AsyncWebClient", + async_source, + ) + async_source = re.sub("class ChatStream", "class AsyncChatStream", async_source) + async_source = re.sub('"WebClient"', '"AsyncWebClient"', async_source) + async_source = re.sub(r"Optional\[SlackResponse\]", "Optional[AsyncSlackResponse]", async_source) + async_source = re.sub(r"SlackResponse ", "AsyncSlackResponse ", async_source) + async_source = re.sub(r"SlackResponse:", "AsyncSlackResponse:", async_source) + async_source = re.sub(r"def append\(", "async def append(", async_source) + async_source = re.sub(r"def stop\(", "async def stop(", async_source) + async_source = re.sub(r"def _flush_buffer\(", "async def _flush_buffer(", async_source) + async_source = re.sub("self._client.chat_", "await self._client.chat_", async_source) + async_source = re.sub("self._flush_buffer", "await self._flush_buffer", async_source) + with open(f"{args.path}/slack_sdk/web/async_chat_stream.py", "w") as output: + output.write(async_source) diff --git a/slack_sdk/models/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index ecaa2e996..334f55c40 100644 --- a/slack_sdk/models/blocks/__init__.py +++ b/slack_sdk/models/blocks/__init__.py @@ -6,67 +6,79 @@ * https://docs.slack.dev/reference/block-kit/blocks * https://app.slack.com/block-kit-builder """ -from .basic_components import ButtonStyles -from .basic_components import ConfirmObject -from .basic_components import DynamicSelectElementTypes -from .basic_components import MarkdownTextObject -from .basic_components import Option -from .basic_components import OptionGroup -from .basic_components import PlainTextObject -from .basic_components import TextObject -from .block_elements import BlockElement -from .block_elements import ButtonElement -from .block_elements import ChannelMultiSelectElement -from .block_elements import ChannelSelectElement -from .block_elements import CheckboxesElement -from .block_elements import ConversationFilter -from .block_elements import ConversationMultiSelectElement -from .block_elements import ConversationSelectElement -from .block_elements import DatePickerElement -from .block_elements import TimePickerElement -from .block_elements import DateTimePickerElement -from .block_elements import ExternalDataMultiSelectElement -from .block_elements import ExternalDataSelectElement -from .block_elements import ImageElement -from .block_elements import InputInteractiveElement -from .block_elements import InteractiveElement -from .block_elements import LinkButtonElement -from .block_elements import OverflowMenuElement -from .block_elements import RichTextInputElement -from .block_elements import PlainTextInputElement -from .block_elements import EmailInputElement -from .block_elements import UrlInputElement -from .block_elements import NumberInputElement -from .block_elements import RadioButtonsElement -from .block_elements import SelectElement -from .block_elements import StaticMultiSelectElement -from .block_elements import StaticSelectElement -from .block_elements import UserMultiSelectElement -from .block_elements import UserSelectElement -from .block_elements import RichTextElement -from .block_elements import RichTextElementParts -from .block_elements import RichTextListElement -from .block_elements import RichTextPreformattedElement -from .block_elements import RichTextQuoteElement -from .block_elements import RichTextSectionElement -from .blocks import ActionsBlock -from .blocks import Block -from .blocks import CallBlock -from .blocks import ContextBlock -from .blocks import DividerBlock -from .blocks import FileBlock -from .blocks import HeaderBlock -from .blocks import ImageBlock -from .blocks import InputBlock -from .blocks import MarkdownBlock -from .blocks import SectionBlock -from .blocks import VideoBlock -from .blocks import RichTextBlock + +from .basic_components import ( + ButtonStyles, + ConfirmObject, + DynamicSelectElementTypes, + FeedbackButtonObject, + MarkdownTextObject, + Option, + OptionGroup, + PlainTextObject, + TextObject, +) +from .block_elements import ( + BlockElement, + ButtonElement, + ChannelMultiSelectElement, + ChannelSelectElement, + CheckboxesElement, + ConversationFilter, + ConversationMultiSelectElement, + ConversationSelectElement, + DatePickerElement, + DateTimePickerElement, + EmailInputElement, + ExternalDataMultiSelectElement, + ExternalDataSelectElement, + FeedbackButtonsElement, + IconButtonElement, + ImageElement, + InputInteractiveElement, + InteractiveElement, + LinkButtonElement, + NumberInputElement, + OverflowMenuElement, + PlainTextInputElement, + RadioButtonsElement, + RichTextElement, + RichTextElementParts, + RichTextInputElement, + RichTextListElement, + RichTextPreformattedElement, + RichTextQuoteElement, + RichTextSectionElement, + SelectElement, + StaticMultiSelectElement, + StaticSelectElement, + TimePickerElement, + UrlInputElement, + UserMultiSelectElement, + UserSelectElement, +) +from .blocks import ( + ActionsBlock, + Block, + CallBlock, + ContextActionsBlock, + ContextBlock, + DividerBlock, + FileBlock, + HeaderBlock, + ImageBlock, + InputBlock, + MarkdownBlock, + RichTextBlock, + SectionBlock, + VideoBlock, +) __all__ = [ "ButtonStyles", "ConfirmObject", "DynamicSelectElementTypes", + "FeedbackButtonObject", "MarkdownTextObject", "Option", "OptionGroup", @@ -85,6 +97,8 @@ "DateTimePickerElement", "ExternalDataMultiSelectElement", "ExternalDataSelectElement", + "FeedbackButtonsElement", + "IconButtonElement", "ImageElement", "InputInteractiveElement", "InteractiveElement", @@ -110,6 +124,7 @@ "ActionsBlock", "Block", "CallBlock", + "ContextActionsBlock", "ContextBlock", "DividerBlock", "FileBlock", diff --git a/slack_sdk/models/blocks/basic_components.py b/slack_sdk/models/blocks/basic_components.py index 3570656bc..f118c5a7f 100644 --- a/slack_sdk/models/blocks/basic_components.py +++ b/slack_sdk/models/blocks/basic_components.py @@ -1,13 +1,10 @@ import copy import logging import warnings -from typing import List, Optional, Set, Union, Sequence, Dict, Any +from typing import Any, Dict, List, Optional, Sequence, Set, Union from slack_sdk.models import show_unknown_key_warning -from slack_sdk.models.basic_objects import ( - JsonObject, - JsonValidator, -) +from slack_sdk.models.basic_objects import JsonObject, JsonValidator from slack_sdk.models.messages import Link ButtonStyles = {"danger", "primary"} @@ -526,6 +523,66 @@ def to_dict(self) -> Dict[str, Any]: return json +class FeedbackButtonObject(JsonObject): + attributes: Set[str] = set() + + text_max_length = 75 + value_max_length = 2000 + + @classmethod + def parse(cls, feedback_button: Union["FeedbackButtonObject", Dict[str, Any]]): + if feedback_button: + if isinstance(feedback_button, FeedbackButtonObject): + return feedback_button + elif isinstance(feedback_button, dict): + return FeedbackButtonObject(**feedback_button) + else: + # Not yet implemented: show some warning here + return None + return None + + def __init__( + self, + *, + text: Union[str, Dict[str, Any], PlainTextObject], + accessibility_label: Optional[str] = None, + value: str, + **others: Dict[str, Any], + ): + """ + A feedback button element object for either positive or negative feedback. + + Args: + text (required): An object containing some text. Maximum length for this field is 75 characters. + accessibility_label: A label for longer descriptive text about a button element. This label will be read out by + screen readers instead of the button `text` object. + value (required): The button value. Maximum length for this field is 2000 characters. + """ + self._text: Optional[TextObject] = PlainTextObject.parse(text, default_type=PlainTextObject.type) + self._accessibility_label: Optional[str] = accessibility_label + self._value: Optional[str] = value + show_unknown_key_warning(self, others) + + @JsonValidator(f"text attribute cannot exceed {text_max_length} characters") + def text_length(self) -> bool: + return self._text is None or len(self._text.text) <= self.text_max_length + + @JsonValidator(f"value attribute cannot exceed {value_max_length} characters") + def value_length(self) -> bool: + return self._value is None or len(self._value) <= self.value_max_length + + def to_dict(self) -> Dict[str, Any]: + self.validate_json() + json: Dict[str, Union[str, dict]] = {} + if self._text: + json["text"] = self._text.to_dict() + if self._accessibility_label: + json["accessibility_label"] = self._accessibility_label + if self._value: + json["value"] = self._value + return json + + class WorkflowTrigger(JsonObject): attributes = {"trigger"} diff --git a/slack_sdk/models/blocks/block_elements.py b/slack_sdk/models/blocks/block_elements.py index bcef28abe..b38cf21e0 100644 --- a/slack_sdk/models/blocks/block_elements.py +++ b/slack_sdk/models/blocks/block_elements.py @@ -3,23 +3,24 @@ import re import warnings from abc import ABCMeta -from typing import Iterator, List, Optional, Set, Type, Union, Sequence, Dict, Any +from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Type, Union from slack_sdk.models import show_unknown_key_warning -from slack_sdk.models.basic_objects import ( - JsonObject, - JsonValidator, - EnumValidator, +from slack_sdk.models.basic_objects import EnumValidator, JsonObject, JsonValidator + +from .basic_components import ( + ButtonStyles, + ConfirmObject, + DispatchActionConfig, + FeedbackButtonObject, + MarkdownTextObject, + Option, + OptionGroup, + PlainTextObject, + SlackFile, + TextObject, + Workflow, ) -from .basic_components import ButtonStyles, Workflow, SlackFile -from .basic_components import ConfirmObject -from .basic_components import DispatchActionConfig -from .basic_components import MarkdownTextObject -from .basic_components import Option -from .basic_components import OptionGroup -from .basic_components import PlainTextObject -from .basic_components import TextObject - # ------------------------------------------------- # Block Elements @@ -539,6 +540,43 @@ def _validate_initial_date_time_valid(self) -> bool: return self.initial_date_time is None or (0 <= self.initial_date_time <= 9999999999) +# ------------------------------------------------- +# Feedback Buttons Element +# ------------------------------------------------- + + +class FeedbackButtonsElement(InteractiveElement): + type = "feedback_buttons" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"positive_button", "negative_button"}) + + def __init__( + self, + *, + action_id: Optional[str] = None, + positive_button: Union[dict, FeedbackButtonObject], + negative_button: Union[dict, FeedbackButtonObject], + **others: dict, + ): + """Buttons to indicate positive or negative feedback. + + Args: + action_id (required): An identifier for this action. + You can use this when you receive an interaction payload to identify the source of the action. + Should be unique among all other action_ids in the containing block. + Maximum length for this field is 255 characters. + positive_button (required): A button to indicate positive feedback. + negative_button (required): A button to indicate negative feedback. + """ + super().__init__(action_id=action_id, type=self.type) + show_unknown_key_warning(self, others) + + self.positive_button = FeedbackButtonObject.parse(positive_button) + self.negative_button = FeedbackButtonObject.parse(negative_button) + + # ------------------------------------------------- # Image # ------------------------------------------------- @@ -587,6 +625,59 @@ def _validate_alt_text_length(self) -> bool: return len(self.alt_text) <= self.alt_text_max_length # type: ignore[arg-type] +# ------------------------------------------------- +# Icon Button Element +# ------------------------------------------------- + + +class IconButtonElement(InteractiveElement): + type = "icon_button" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"icon", "text", "accessibility_label", "value", "visible_to_user_ids", "confirm"}) + + def __init__( + self, + *, + action_id: Optional[str] = None, + icon: str, + text: Union[str, dict, TextObject], + accessibility_label: Optional[str] = None, + value: Optional[str] = None, + visible_to_user_ids: Optional[List[str]] = None, + confirm: Optional[Union[dict, ConfirmObject]] = None, + **others: dict, + ): + """An icon button to perform actions. + + Args: + action_id: An identifier for this action. + You can use this when you receive an interaction payload to identify the source of the action. + Should be unique among all other action_ids in the containing block. + Maximum length for this field is 255 characters. + icon (required): The icon to show (e.g., 'trash'). + text (required): Defines an object containing some text. + accessibility_label: A label for longer descriptive text about a button element. + This label will be read out by screen readers instead of the button text object. + Maximum length for this field is 75 characters. + value: The button value. + Maximum length for this field is 2000 characters. + visible_to_user_ids: User IDs for which the icon appears. + Maximum length for this field is 10 user IDs. + confirm: A confirm object that defines an optional confirmation dialog after the button is clicked. + """ + super().__init__(action_id=action_id, type=self.type) + show_unknown_key_warning(self, others) + + self.icon = icon + self.text = TextObject.parse(text, PlainTextObject.type) + self.accessibility_label = accessibility_label + self.value = value + self.visible_to_user_ids = visible_to_user_ids + self.confirm = ConfirmObject.parse(confirm) if confirm else None + + # ------------------------------------------------- # Static Select # ------------------------------------------------- diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index a32c2c326..69200be25 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -4,19 +4,19 @@ from typing import Any, Dict, List, Optional, Sequence, Set, Union from slack_sdk.models import show_unknown_key_warning -from slack_sdk.models.basic_objects import ( - JsonObject, - JsonValidator, -) -from .basic_components import MarkdownTextObject, SlackFile -from .basic_components import PlainTextObject -from .basic_components import TextObject -from .block_elements import BlockElement, RichTextElement -from .block_elements import ImageElement -from .block_elements import InputInteractiveElement -from .block_elements import InteractiveElement -from ...errors import SlackObjectFormationError +from slack_sdk.models.basic_objects import JsonObject, JsonValidator +from ...errors import SlackObjectFormationError +from .basic_components import MarkdownTextObject, PlainTextObject, SlackFile, TextObject +from .block_elements import ( + BlockElement, + FeedbackButtonsElement, + IconButtonElement, + ImageElement, + InputInteractiveElement, + InteractiveElement, + RichTextElement, +) # ------------------------------------------------- # Base Classes @@ -79,6 +79,8 @@ def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]: return ActionsBlock(**block) elif type == ContextBlock.type: return ContextBlock(**block) + elif type == ContextActionsBlock.type: + return ContextActionsBlock(**block) elif type == InputBlock.type: return InputBlock(**block) elif type == FileBlock.type: @@ -357,6 +359,44 @@ def _validate_elements_length(self): return self.elements is None or len(self.elements) <= self.elements_max_length +class ContextActionsBlock(Block): + type = "context_actions" + elements_max_length = 5 + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union({"elements"}) + + def __init__( + self, + *, + elements: Sequence[Union[dict, FeedbackButtonsElement, IconButtonElement]], + block_id: Optional[str] = None, + **others: dict, + ): + """Displays actions as contextual info, which can include both feedback buttons and icon buttons. + + Args: + elements (required): An array of feedback_buttons or icon_button block elements. Maximum number of items is 5. + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.elements = BlockElement.parse_all(elements) + + @JsonValidator("elements attribute must be specified") + def _validate_elements(self): + return self.elements is None or len(self.elements) > 0 + + @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements") + def _validate_elements_length(self): + return self.elements is None or len(self.elements) <= self.elements_max_length + + class InputBlock(Block): type = "input" label_max_length = 2000 diff --git a/slack_sdk/web/async_chat_stream.py b/slack_sdk/web/async_chat_stream.py new file mode 100644 index 000000000..4661f19dd --- /dev/null +++ b/slack_sdk/web/async_chat_stream.py @@ -0,0 +1,212 @@ +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# +# *** DO NOT EDIT THIS FILE *** +# +# 1) Modify slack_sdk/web/client.py +# 2) Run `python scripts/codegen.py` +# 3) Run `black slack_sdk/` +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +import json +import logging +from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union + +import slack_sdk.errors as e +from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.metadata import Metadata +from slack_sdk.web.async_slack_response import AsyncSlackResponse + +if TYPE_CHECKING: + from slack_sdk.web.async_client import AsyncWebClient + + +class AsyncChatStream: + """A helper class for streaming markdown text into a conversation using the chat streaming APIs. + + This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API + methods, with automatic buffering and state management. + """ + + def __init__( + self, + client: "AsyncWebClient", + *, + channel: str, + logger: logging.Logger, + thread_ts: str, + buffer_size: int, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ): + """Initialize a new ChatStream instance. + + The __init__ method creates a unique ChatStream instance that keeps track of one chat stream. + + Args: + client: The WebClient instance to use for API calls. + channel: An encoded ID that represents a channel, private group, or DM. + logger: A logging channel for outputs. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + 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. + """ + self._client = client + self._logger = logger + self._token: Optional[str] = kwargs.pop("token", None) + self._stream_args = { + "channel": channel, + "thread_ts": thread_ts, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + **kwargs, + } + self._buffer = "" + self._state = "starting" + self._stream_ts: Optional[str] = None + self._buffer_size = buffer_size + + async def append( + self, + *, + markdown_text: str, + **kwargs, + ) -> Optional[AsyncSlackResponse]: + """Append to the stream. + + The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream + is stopped this method cannot be called. + + Args: + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + AsyncSlackResponse if the buffer was flushed, None if buffering. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size: + return await self._flush_buffer(**kwargs) + details = { + "buffer_length": len(self._buffer), + "buffer_size": self._buffer_size, + "channel": self._stream_args.get("channel"), + "recipient_team_id": self._stream_args.get("recipient_team_id"), + "recipient_user_id": self._stream_args.get("recipient_user_id"), + "thread_ts": self._stream_args.get("thread_ts"), + } + self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}") + return None + + async def stop( + self, + *, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Stop the stream and finalize the message. + + Args: + blocks: A list of blocks that will be rendered at the bottom of the finalized message. + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you + post to Slack is accessible to any app or user who is a member of that workspace. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + AsyncSlackResponse from the chat.stopStream API call. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + if markdown_text: + self._buffer += markdown_text + if not self._stream_ts: + response = await self._client.chat_startStream( + **self._stream_args, + token=self._token, + ) + if not response.get("ts"): + raise e.SlackRequestError("Failed to stop stream: stream not started") + self._stream_ts = str(response["ts"]) + self._state = "in_progress" + response = await self._client.chat_stopStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + blocks=blocks, + markdown_text=self._buffer, + metadata=metadata, + **kwargs, + ) + self._state = "completed" + return response + + async def _flush_buffer(self, **kwargs) -> AsyncSlackResponse: + """Flush the internal buffer by making appropriate API calls.""" + if not self._stream_ts: + response = await self._client.chat_startStream( + **self._stream_args, + token=self._token, + **kwargs, + markdown_text=self._buffer, + ) + self._stream_ts = response.get("ts") + self._state = "in_progress" + else: + response = await self._client.chat_appendStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + **kwargs, + markdown_text=self._buffer, + ) + self._buffer = "" + return response diff --git a/slack_sdk/web/async_client.py b/slack_sdk/web/async_client.py index d70a62267..7b82a0923 100644 --- a/slack_sdk/web/async_client.py +++ b/slack_sdk/web/async_client.py @@ -18,6 +18,7 @@ import slack_sdk.errors as e from slack_sdk.models.views import View +from slack_sdk.web.async_chat_stream import AsyncChatStream from ..models.attachments import Attachment from ..models.blocks import Block @@ -2075,13 +2076,17 @@ async def assistant_threads_setStatus( channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """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}) - return await self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("assistant.threads.setStatus", json=kwargs) async def assistant_threads_setTitle( self, @@ -2091,7 +2096,7 @@ async def assistant_threads_setTitle( title: str, **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2106,7 +2111,7 @@ async def assistant_threads_setSuggestedPrompts( prompts: List[Dict[str, str]], **kwargs, ) -> AsyncSlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2620,6 +2625,27 @@ async def channels_unarchive( # -------------------------- + async def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> AsyncSlackResponse: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("chat.appendStream", json=kwargs) + async def chat_delete( self, *, @@ -2824,6 +2850,146 @@ async def chat_scheduleMessage( # NOTE: intentionally using json over params for the API methods using blocks/attachments return await self.api_call("chat.scheduleMessage", json=kwargs) + async def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return await self.api_call("chat.scheduledMessages.list", params=kwargs) + + async def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return await self.api_call("chat.startStream", json=kwargs) + + async def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> AsyncSlackResponse: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return await self.api_call("chat.stopStream", json=kwargs) + + async def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> AsyncChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = await client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + await streamer.append(markdown_text="**hello wo") + await streamer.append(markdown_text="rld!**") + await streamer.stop() + ``` + """ + return AsyncChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + async def chat_unfurl( self, *, @@ -2904,32 +3070,6 @@ async def chat_update( # NOTE: intentionally using json over params for API methods using blocks/attachments return await self.api_call("chat.update", json=kwargs) - async def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> AsyncSlackResponse: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return await self.api_call("chat.scheduledMessages.list", params=kwargs) - async def conversations_acceptSharedInvite( self, *, diff --git a/slack_sdk/web/chat_stream.py b/slack_sdk/web/chat_stream.py new file mode 100644 index 000000000..1a379c9cb --- /dev/null +++ b/slack_sdk/web/chat_stream.py @@ -0,0 +1,202 @@ +import json +import logging +from typing import TYPE_CHECKING, Dict, Optional, Sequence, Union + +import slack_sdk.errors as e +from slack_sdk.models.blocks.blocks import Block +from slack_sdk.models.metadata import Metadata +from slack_sdk.web.slack_response import SlackResponse + +if TYPE_CHECKING: + from slack_sdk import WebClient + + +class ChatStream: + """A helper class for streaming markdown text into a conversation using the chat streaming APIs. + + This class provides a convenient interface for the chat.startStream, chat.appendStream, and chat.stopStream API + methods, with automatic buffering and state management. + """ + + def __init__( + self, + client: "WebClient", + *, + channel: str, + logger: logging.Logger, + thread_ts: str, + buffer_size: int, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ): + """Initialize a new ChatStream instance. + + The __init__ method creates a unique ChatStream instance that keeps track of one chat stream. + + Args: + client: The WebClient instance to use for API calls. + channel: An encoded ID that represents a channel, private group, or DM. + logger: A logging channel for outputs. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + 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. + """ + self._client = client + self._logger = logger + self._token: Optional[str] = kwargs.pop("token", None) + self._stream_args = { + "channel": channel, + "thread_ts": thread_ts, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + **kwargs, + } + self._buffer = "" + self._state = "starting" + self._stream_ts: Optional[str] = None + self._buffer_size = buffer_size + + def append( + self, + *, + markdown_text: str, + **kwargs, + ) -> Optional[SlackResponse]: + """Append to the stream. + + The "append" method appends to the chat stream being used. This method can be called multiple times. After the stream + is stopped this method cannot be called. + + Args: + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + SlackResponse if the buffer was flushed, None if buffering. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot append to stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + self._buffer += markdown_text + if len(self._buffer) >= self._buffer_size: + return self._flush_buffer(**kwargs) + details = { + "buffer_length": len(self._buffer), + "buffer_size": self._buffer_size, + "channel": self._stream_args.get("channel"), + "recipient_team_id": self._stream_args.get("recipient_team_id"), + "recipient_user_id": self._stream_args.get("recipient_user_id"), + "thread_ts": self._stream_args.get("thread_ts"), + } + self._logger.debug(f"ChatStream appended to buffer: {json.dumps(details)}") + return None + + def stop( + self, + *, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> SlackResponse: + """Stop the stream and finalize the message. + + Args: + blocks: A list of blocks that will be rendered at the bottom of the finalized message. + markdown_text: Accepts message text formatted in markdown. Limit this field to 12,000 characters. This text is + what will be appended to the message received so far. + metadata: JSON object with event_type and event_payload fields, presented as a URL-encoded string. Metadata you + post to Slack is accessible to any app or user who is a member of that workspace. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + SlackResponse from the chat.stopStream API call. + + Raises: + SlackRequestError: If the stream is already completed. + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + if self._state == "completed": + raise e.SlackRequestError(f"Cannot stop stream: stream state is {self._state}") + if kwargs.get("token"): + self._token = kwargs.pop("token") + if markdown_text: + self._buffer += markdown_text + if not self._stream_ts: + response = self._client.chat_startStream( + **self._stream_args, + token=self._token, + ) + if not response.get("ts"): + raise e.SlackRequestError("Failed to stop stream: stream not started") + self._stream_ts = str(response["ts"]) + self._state = "in_progress" + response = self._client.chat_stopStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + blocks=blocks, + markdown_text=self._buffer, + metadata=metadata, + **kwargs, + ) + self._state = "completed" + return response + + def _flush_buffer(self, **kwargs) -> SlackResponse: + """Flush the internal buffer by making appropriate API calls.""" + if not self._stream_ts: + response = self._client.chat_startStream( + **self._stream_args, + token=self._token, + **kwargs, + markdown_text=self._buffer, + ) + self._stream_ts = response.get("ts") + self._state = "in_progress" + else: + response = self._client.chat_appendStream( + token=self._token, + channel=self._stream_args["channel"], + ts=self._stream_ts, + **kwargs, + markdown_text=self._buffer, + ) + self._buffer = "" + return response diff --git a/slack_sdk/web/client.py b/slack_sdk/web/client.py index f51514010..8410ffee2 100644 --- a/slack_sdk/web/client.py +++ b/slack_sdk/web/client.py @@ -8,6 +8,7 @@ import slack_sdk.errors as e from slack_sdk.models.views import View +from slack_sdk.web.chat_stream import ChatStream from ..models.attachments import Attachment from ..models.blocks import Block @@ -2065,13 +2066,17 @@ def assistant_threads_setStatus( channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> SlackResponse: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs) def assistant_threads_setTitle( self, @@ -2081,7 +2086,7 @@ def assistant_threads_setTitle( title: str, **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2096,7 +2101,7 @@ def assistant_threads_setSuggestedPrompts( prompts: List[Dict[str, str]], **kwargs, ) -> SlackResponse: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2610,6 +2615,27 @@ def channels_unarchive( # -------------------------- + def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> SlackResponse: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.appendStream", json=kwargs) + def chat_delete( self, *, @@ -2814,6 +2840,146 @@ def chat_scheduleMessage( # NOTE: intentionally using json over params for the API methods using blocks/attachments return self.api_call("chat.scheduleMessage", json=kwargs) + def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return self.api_call("chat.scheduledMessages.list", params=kwargs) + + def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> SlackResponse: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.startStream", json=kwargs) + + def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> SlackResponse: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.stopStream", json=kwargs) + + def chat_stream( + self, + *, + buffer_size: int = 256, + channel: str, + thread_ts: str, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Stream markdown text into a conversation. + + This method starts a new chat stream in a conversation that can be appended to. After appending an entire message, + the stream can be stopped with concluding arguments such as "blocks" for gathering feedback. + + The following methods are used: + + - chat.startStream: Starts a new streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.startStream). + - chat.appendStream: Appends text to an existing streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.appendStream). + - chat.stopStream: Stops a streaming conversation. + [Reference](https://docs.slack.dev/reference/methods/chat.stopStream). + + Args: + buffer_size: The length of markdown_text to buffer in-memory before calling a stream method. Increasing this + value decreases the number of method calls made for the same amount of text, which is useful to avoid rate + limits. Default: 256. + channel: An encoded ID that represents a channel, private group, or DM. + thread_ts: Provide another message's ts value to reply to. Streamed messages should always be replies to a user + request. + recipient_team_id: The encoded ID of the team the user receiving the streaming text belongs to. Required when + streaming to channels. + recipient_user_id: The encoded ID of the user to receive the streaming text. Required when streaming to channels. + **kwargs: Additional arguments passed to the underlying API calls. + + Returns: + ChatStream instance for managing the stream + + Example: + ```python + streamer = client.chat_stream( + channel="C0123456789", + thread_ts="1700000001.123456", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="**hello wo") + streamer.append(markdown_text="rld!**") + streamer.stop() + ``` + """ + return ChatStream( + self, + logger=self._logger, + channel=channel, + thread_ts=thread_ts, + recipient_team_id=recipient_team_id, + recipient_user_id=recipient_user_id, + buffer_size=buffer_size, + **kwargs, + ) + def chat_unfurl( self, *, @@ -2894,32 +3060,6 @@ def chat_update( # NOTE: intentionally using json over params for API methods using blocks/attachments return self.api_call("chat.update", json=kwargs) - def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> SlackResponse: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return self.api_call("chat.scheduledMessages.list", params=kwargs) - def conversations_acceptSharedInvite( self, *, diff --git a/slack_sdk/web/legacy_client.py b/slack_sdk/web/legacy_client.py index d6b7789d1..88c0fcf1a 100644 --- a/slack_sdk/web/legacy_client.py +++ b/slack_sdk/web/legacy_client.py @@ -2077,13 +2077,17 @@ def assistant_threads_setStatus( channel_id: str, thread_ts: str, status: str, + loading_messages: Optional[List[str]] = None, **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """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}) - return self.api_call("assistant.threads.setStatus", params=kwargs) + kwargs.update( + {"channel_id": channel_id, "thread_ts": thread_ts, "status": status, "loading_messages": loading_messages} + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("assistant.threads.setStatus", json=kwargs) def assistant_threads_setTitle( self, @@ -2093,7 +2097,7 @@ def assistant_threads_setTitle( title: str, **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """Set the title for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setTitle """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "title": title}) @@ -2108,7 +2112,7 @@ def assistant_threads_setSuggestedPrompts( prompts: List[Dict[str, str]], **kwargs, ) -> Union[Future, SlackResponse]: - """Revokes a token. + """Set suggested prompts for the given assistant thread. https://docs.slack.dev/reference/methods/assistant.threads.setSuggestedPrompts """ kwargs.update({"channel_id": channel_id, "thread_ts": thread_ts, "prompts": prompts}) @@ -2622,6 +2626,27 @@ def channels_unarchive( # -------------------------- + def chat_appendStream( + self, + *, + channel: str, + ts: str, + markdown_text: str, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Appends text to an existing streaming conversation. + https://docs.slack.dev/reference/methods/chat.appendStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.appendStream", json=kwargs) + def chat_delete( self, *, @@ -2826,6 +2851,83 @@ def chat_scheduleMessage( # NOTE: intentionally using json over params for the API methods using blocks/attachments return self.api_call("chat.scheduleMessage", json=kwargs) + def chat_scheduledMessages_list( + self, + *, + channel: Optional[str] = None, + cursor: Optional[str] = None, + latest: Optional[str] = None, + limit: Optional[int] = None, + oldest: Optional[str] = None, + team_id: Optional[str] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Lists all scheduled messages. + https://docs.slack.dev/reference/methods/chat.scheduledMessages.list + """ + kwargs.update( + { + "channel": channel, + "cursor": cursor, + "latest": latest, + "limit": limit, + "oldest": oldest, + "team_id": team_id, + } + ) + return self.api_call("chat.scheduledMessages.list", params=kwargs) + + def chat_startStream( + self, + *, + channel: str, + thread_ts: str, + markdown_text: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Starts a new streaming conversation. + https://docs.slack.dev/reference/methods/chat.startStream + """ + kwargs.update( + { + "channel": channel, + "thread_ts": thread_ts, + "markdown_text": markdown_text, + "recipient_team_id": recipient_team_id, + "recipient_user_id": recipient_user_id, + } + ) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.startStream", json=kwargs) + + def chat_stopStream( + self, + *, + channel: str, + ts: str, + markdown_text: Optional[str] = None, + blocks: Optional[Union[str, Sequence[Union[Dict, Block]]]] = None, + metadata: Optional[Union[Dict, Metadata]] = None, + **kwargs, + ) -> Union[Future, SlackResponse]: + """Stops a streaming conversation. + https://docs.slack.dev/reference/methods/chat.stopStream + """ + kwargs.update( + { + "channel": channel, + "ts": ts, + "markdown_text": markdown_text, + "blocks": blocks, + "metadata": metadata, + } + ) + _parse_web_class_objects(kwargs) + kwargs = _remove_none_values(kwargs) + return self.api_call("chat.stopStream", json=kwargs) + def chat_unfurl( self, *, @@ -2906,32 +3008,6 @@ def chat_update( # NOTE: intentionally using json over params for API methods using blocks/attachments return self.api_call("chat.update", json=kwargs) - def chat_scheduledMessages_list( - self, - *, - channel: Optional[str] = None, - cursor: Optional[str] = None, - latest: Optional[str] = None, - limit: Optional[int] = None, - oldest: Optional[str] = None, - team_id: Optional[str] = None, - **kwargs, - ) -> Union[Future, SlackResponse]: - """Lists all scheduled messages. - https://docs.slack.dev/reference/methods/chat.scheduledMessages.list - """ - kwargs.update( - { - "channel": channel, - "cursor": cursor, - "latest": latest, - "limit": limit, - "oldest": oldest, - "team_id": team_id, - } - ) - return self.api_call("chat.scheduledMessages.list", params=kwargs) - def conversations_acceptSharedInvite( self, *, diff --git a/tests/slack_sdk/models/test_blocks.py b/tests/slack_sdk/models/test_blocks.py index f8753cccc..adacf1427 100644 --- a/tests/slack_sdk/models/test_blocks.py +++ b/tests/slack_sdk/models/test_blocks.py @@ -4,33 +4,35 @@ from slack_sdk.errors import SlackObjectFormationError from slack_sdk.models.blocks import ( ActionsBlock, + Block, + ButtonElement, + CallBlock, + ContextActionsBlock, ContextBlock, DividerBlock, - ImageBlock, - SectionBlock, - InputBlock, FileBlock, - Block, - CallBlock, - ButtonElement, - StaticSelectElement, - OverflowMenuElement, + HeaderBlock, + ImageBlock, ImageElement, + InputBlock, LinkButtonElement, - PlainTextObject, - MarkdownTextObject, - HeaderBlock, MarkdownBlock, - VideoBlock, + MarkdownTextObject, Option, + OverflowMenuElement, + PlainTextObject, RichTextBlock, - RichTextSectionElement, + RichTextElementParts, RichTextListElement, - RichTextQuoteElement, RichTextPreformattedElement, - RichTextElementParts, + RichTextQuoteElement, + RichTextSectionElement, + SectionBlock, + StaticSelectElement, + VideoBlock, ) -from slack_sdk.models.blocks.basic_components import SlackFile +from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, SlackFile +from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement from . import STRING_3001_CHARS @@ -526,6 +528,54 @@ def test_element_parsing(self): self.assertDictEqual(original.to_dict(), parsed.to_dict()) +# ---------------------------------------------- +# ContextActionsBlock +# ---------------------------------------------- + + +class ContextActionsBlockTests(unittest.TestCase): + def test_document(self): + input = { + "type": "context_actions", + "block_id": "context-actions-1", + "elements": [ + { + "type": "feedback_buttons", + "action_id": "feedback-action", + "positive_button": {"text": {"type": "plain_text", "text": "+1"}, "value": "positive"}, + "negative_button": {"text": {"type": "plain_text", "text": "-1"}, "value": "negative"}, + }, + { + "type": "icon_button", + "action_id": "delete-action", + "icon": "trash", + "text": {"type": "plain_text", "text": "Delete"}, + "value": "delete", + }, + ], + } + self.assertDictEqual(input, ContextActionsBlock(**input).to_dict()) + self.assertDictEqual(input, Block.parse(input).to_dict()) + + def test_with_feedback_buttons(self): + feedback_buttons = FeedbackButtonsElement( + action_id="feedback-action", + positive_button=FeedbackButtonObject(text="Good", value="positive"), + negative_button=FeedbackButtonObject(text="Bad", value="negative"), + ) + block = ContextActionsBlock(elements=[feedback_buttons]) + self.assertEqual(len(block.elements), 1) + self.assertEqual(block.elements[0].type, "feedback_buttons") + + def test_with_icon_button(self): + icon_button = IconButtonElement( + action_id="icon-action", icon="star", text=PlainTextObject(text="Favorite"), value="favorite" + ) + block = ContextActionsBlock(elements=[icon_button]) + self.assertEqual(len(block.elements), 1) + self.assertEqual(block.elements[0].type, "icon_button") + + # ---------------------------------------------- # Context # ---------------------------------------------- diff --git a/tests/slack_sdk/models/test_elements.py b/tests/slack_sdk/models/test_elements.py index 9aa3aa474..2985228a2 100644 --- a/tests/slack_sdk/models/test_elements.py +++ b/tests/slack_sdk/models/test_elements.py @@ -4,44 +4,47 @@ from slack_sdk.models.blocks import ( BlockElement, ButtonElement, + ChannelMultiSelectElement, + ChannelSelectElement, + CheckboxesElement, + ConfirmObject, + ConversationMultiSelectElement, + ConversationSelectElement, DatePickerElement, - TimePickerElement, + ExternalDataMultiSelectElement, ExternalDataSelectElement, + FeedbackButtonsElement, + IconButtonElement, ImageElement, + InputInteractiveElement, + InteractiveElement, LinkButtonElement, - UserSelectElement, - StaticSelectElement, - CheckboxesElement, - StaticMultiSelectElement, - ExternalDataMultiSelectElement, - UserMultiSelectElement, - ConversationMultiSelectElement, - ChannelMultiSelectElement, + Option, OverflowMenuElement, PlainTextInputElement, - RadioButtonsElement, - ConversationSelectElement, - ChannelSelectElement, - ConfirmObject, - Option, - InputInteractiveElement, - InteractiveElement, PlainTextObject, + RadioButtonsElement, RichTextBlock, + StaticMultiSelectElement, + StaticSelectElement, + TimePickerElement, + UserMultiSelectElement, + UserSelectElement, ) from slack_sdk.models.blocks.basic_components import SlackFile from slack_sdk.models.blocks.block_elements import ( DateTimePickerElement, EmailInputElement, + FileInputElement, NumberInputElement, - UrlInputElement, - WorkflowButtonElement, + RichTextElementParts, RichTextInputElement, - FileInputElement, RichTextSectionElement, - RichTextElementParts, + UrlInputElement, + WorkflowButtonElement, ) -from . import STRING_3001_CHARS, STRING_301_CHARS + +from . import STRING_301_CHARS, STRING_3001_CHARS class BlockElementTests(unittest.TestCase): @@ -443,6 +446,67 @@ def test_focus_on_load(self): self.assertDictEqual(input, DateTimePickerElement(**input).to_dict()) +# ---------------------------------------------- +# FeedbackButtons +# ---------------------------------------------- + + +class FeedbackButtonsTests(unittest.TestCase): + def test_document(self): + input = { + "type": "feedback_buttons", + "action_id": "feedback-123", + "positive_button": { + "text": {"type": "plain_text", "text": "+1"}, + "accessibility_label": "Positive feedback", + "value": "positive", + }, + "negative_button": { + "text": {"type": "plain_text", "text": "-1"}, + "accessibility_label": "Negative feedback", + "value": "negative", + }, + } + self.assertDictEqual(input, FeedbackButtonsElement(**input).to_dict()) + + +# ---------------------------------------------- +# IconButton +# ---------------------------------------------- + + +class IconButtonTests(unittest.TestCase): + def test_document(self): + input = { + "type": "icon_button", + "action_id": "icon-123", + "icon": "trash", + "text": {"type": "plain_text", "text": "Delete"}, + "accessibility_label": "Delete item", + "value": "delete_item", + "visible_to_user_ids": ["U123456", "U789012"], + } + self.assertDictEqual(input, IconButtonElement(**input).to_dict()) + + def test_with_confirm(self): + input = { + "type": "icon_button", + "action_id": "icon-456", + "icon": "trash", + "text": {"type": "plain_text", "text": "Delete"}, + "value": "trash", + "confirm": { + "title": {"type": "plain_text", "text": "Are you sure?"}, + "text": {"type": "plain_text", "text": "This will send a warning."}, + "confirm": {"type": "plain_text", "text": "Yes"}, + "deny": {"type": "plain_text", "text": "No"}, + }, + } + icon_button = IconButtonElement(**input) + self.assertIsNotNone(icon_button.confirm) + self.assertDictEqual(input, icon_button.to_dict()) + + # ------------------------------------------------- # Image # ------------------------------------------------- diff --git a/tests/slack_sdk/models/test_objects.py b/tests/slack_sdk/models/test_objects.py index 383abee7c..30bcb7002 100644 --- a/tests/slack_sdk/models/test_objects.py +++ b/tests/slack_sdk/models/test_objects.py @@ -1,26 +1,14 @@ import copy import unittest -from typing import Optional, List, Union +from typing import List, Optional, Union from slack_sdk.errors import SlackObjectFormationError from slack_sdk.models import JsonObject, JsonValidator -from slack_sdk.models.blocks import ( - ConfirmObject, - MarkdownTextObject, - Option, - OptionGroup, - PlainTextObject, -) -from slack_sdk.models.blocks.basic_components import Workflow, WorkflowTrigger -from slack_sdk.models.messages import ( - ChannelLink, - DateLink, - EveryoneLink, - HereLink, - Link, - ObjectLink, -) -from . import STRING_301_CHARS, STRING_51_CHARS +from slack_sdk.models.blocks import ConfirmObject, MarkdownTextObject, Option, OptionGroup, PlainTextObject +from slack_sdk.models.blocks.basic_components import FeedbackButtonObject, Workflow, WorkflowTrigger +from slack_sdk.models.messages import ChannelLink, DateLink, EveryoneLink, HereLink, Link, ObjectLink + +from . import STRING_51_CHARS, STRING_301_CHARS class SimpleJsonObject(JsonObject): @@ -374,6 +362,58 @@ def test_deny_length(self): ConfirmObject(title="title", text="Are you sure?", deny=STRING_51_CHARS).to_dict() +class FeedbackButtonObjectTests(unittest.TestCase): + def test_basic_json(self): + feedback_button = FeedbackButtonObject(text="+1", value="positive") + expected = {"text": {"emoji": True, "text": "+1", "type": "plain_text"}, "value": "positive"} + self.assertDictEqual(expected, feedback_button.to_dict()) + + def test_with_accessibility_label(self): + feedback_button = FeedbackButtonObject(text="+1", value="positive", accessibility_label="Positive feedback button") + expected = { + "text": {"emoji": True, "text": "+1", "type": "plain_text"}, + "value": "positive", + "accessibility_label": "Positive feedback button", + } + self.assertDictEqual(expected, feedback_button.to_dict()) + + def test_with_plain_text_object(self): + text_obj = PlainTextObject(text="-1", emoji=False) + feedback_button = FeedbackButtonObject(text=text_obj, value="negative") + expected = { + "text": {"emoji": False, "text": "-1", "type": "plain_text"}, + "value": "negative", + } + self.assertDictEqual(expected, feedback_button.to_dict()) + + def test_text_length_validation(self): + with self.assertRaises(SlackObjectFormationError): + FeedbackButtonObject(text="a" * 76, value="test").to_dict() + + def test_value_length_validation(self): + with self.assertRaises(SlackObjectFormationError): + FeedbackButtonObject(text="+1", value="a" * 2001).to_dict() + + def test_parse_from_dict(self): + data = {"text": "+1", "value": "positive", "accessibility_label": "Positive feedback"} + parsed = FeedbackButtonObject.parse(data) + self.assertIsInstance(parsed, FeedbackButtonObject) + expected = { + "text": {"emoji": True, "text": "+1", "type": "plain_text"}, + "value": "positive", + "accessibility_label": "Positive feedback", + } + self.assertDictEqual(expected, parsed.to_dict()) + + def test_parse_from_existing_object(self): + original = FeedbackButtonObject(text="-1", value="negative") + parsed = FeedbackButtonObject.parse(original) + self.assertIs(original, parsed) + + def test_parse_none(self): + self.assertIsNone(FeedbackButtonObject.parse(None)) + + class OptionTests(unittest.TestCase): def setUp(self) -> None: self.common = Option(label="an option", value="option_1") diff --git a/tests/slack_sdk/web/test_chat_stream.py b/tests/slack_sdk/web/test_chat_stream.py new file mode 100644 index 000000000..75c13c8c2 --- /dev/null +++ b/tests/slack_sdk/web/test_chat_stream.py @@ -0,0 +1,188 @@ +import json +import unittest +from urllib.parse import parse_qs, urlparse + +from slack_sdk import WebClient +from slack_sdk.errors import SlackRequestError +from slack_sdk.models.blocks.basic_components import FeedbackButtonObject +from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement +from slack_sdk.models.blocks.blocks import ContextActionsBlock +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.slack_sdk.web.mock_web_api_handler import MockHandler + + +class ChatStreamMockHandler(MockHandler): + """Extended mock handler that captures request bodies for chat stream methods""" + + def _handle(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + + # Standard auth and validation from parent + if self.is_valid_token() and self.is_valid_user_agent(): + token = self.headers["authorization"].split(" ")[1] + parsed_path = urlparse(self.path) + len_header = self.headers.get("Content-Length") or 0 + content_len = int(len_header) + post_body = self.rfile.read(content_len) + request_body = None + if post_body: + try: + post_body = post_body.decode("utf-8") + if post_body.startswith("{"): + request_body = json.loads(post_body) + else: + request_body = {k: v[0] for k, v in parse_qs(post_body).items()} + except UnicodeDecodeError: + pass + else: + if parsed_path and parsed_path.query: + request_body = {k: v[0] for k, v in parse_qs(parsed_path.query).items()} + + # Store request body for chat stream endpoints + if self.path in ["/chat.startStream", "/chat.appendStream", "/chat.stopStream"] and request_body: + if not hasattr(self.server, "chat_stream_requests"): + self.server.chat_stream_requests = {} + self.server.chat_stream_requests[self.path] = { + "token": token, + **request_body, + } + + # Load response file + pattern = str(token).split("xoxb-", 1)[1] + with open(f"tests/slack_sdk_fixture/web_response_{pattern}.json") as file: + body = json.load(file) + + else: + body = self.invalid_auth + + if not body: + body = self.not_found + + self.send_response(200) + self.set_common_headers() + self.wfile.write(json.dumps(body).encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + +class TestChatStream(unittest.TestCase): + def setUp(self): + setup_mock_web_api_server(self, ChatStreamMockHandler) + self.client = WebClient( + token="xoxb-chat_stream_test", + base_url="http://localhost:8888", + ) + + def tearDown(self): + cleanup_mock_web_api_server(self) + + def test_streams_a_short_message(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + streamer.append(markdown_text="nice!") + streamer.stop() + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 0) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("ts"), "123.123") + self.assertEqual(stop_request.get("markdown_text"), "nice!") + + def test_streams_a_long_message(self): + streamer = self.client.chat_stream( + buffer_size=5, + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + ) + streamer.append(markdown_text="**this messag") + streamer.append(markdown_text="e is", token="xoxb-chat_stream_test_token1") + streamer.append(markdown_text=" bold!") + streamer.append(markdown_text="*") + streamer.stop( + blocks=[ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + positive_button=FeedbackButtonObject(text="good", value="+1"), + negative_button=FeedbackButtonObject(text="bad", value="-1"), + ), + IconButtonElement( + icon="trash", + text="delete", + ), + ], + ) + ], + markdown_text="*", + token="xoxb-chat_stream_test_token2", + ) + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("markdown_text"), "**this messag") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("markdown_text"), "e is bold!") + self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") + self.assertEqual(append_request.get("ts"), "123.123") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual( + json.dumps(stop_request.get("blocks")), + '[{"elements": [{"negative_button": {"text": {"emoji": true, "text": "bad", "type": "plain_text"}, "value": "-1"}, "positive_button": {"text": {"emoji": true, "text": "good", "type": "plain_text"}, "value": "+1"}, "type": "feedback_buttons"}, {"icon": "trash", "text": {"emoji": true, "text": "delete", "type": "plain_text"}, "type": "icon_button"}], "type": "context_actions"}]', + ) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("markdown_text"), "**") + self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") + self.assertEqual(stop_request.get("ts"), "123.123") + + def test_streams_errors_when_appending_to_an_unstarted_stream(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + token="xoxb-chat_stream_test_missing_ts", + ) + with self.assertRaisesRegex(SlackRequestError, r"^Failed to stop stream: stream not started$"): + streamer.stop() + + def test_streams_errors_when_appending_to_a_completed_stream(self): + streamer = self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + ) + streamer.append(markdown_text="nice!") + streamer.stop() + with self.assertRaisesRegex(SlackRequestError, r"^Cannot append to stream: stream state is completed$"): + streamer.append(markdown_text="more...") + with self.assertRaisesRegex(SlackRequestError, r"^Cannot stop stream: stream state is completed$"): + streamer.stop() diff --git a/tests/slack_sdk_async/web/test_async_chat_stream.py b/tests/slack_sdk_async/web/test_async_chat_stream.py new file mode 100644 index 000000000..212fee1e2 --- /dev/null +++ b/tests/slack_sdk_async/web/test_async_chat_stream.py @@ -0,0 +1,193 @@ +import json +import unittest +from urllib.parse import parse_qs, urlparse + +from slack_sdk.errors import SlackRequestError +from slack_sdk.models.blocks.basic_components import FeedbackButtonObject +from slack_sdk.models.blocks.block_elements import FeedbackButtonsElement, IconButtonElement +from slack_sdk.models.blocks.blocks import ContextActionsBlock +from slack_sdk.web.async_client import AsyncWebClient +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.slack_sdk_async.helpers import async_test + + +class ChatStreamMockHandler(MockHandler): + """Extended mock handler that captures request bodies for chat stream methods""" + + def _handle(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + + # Standard auth and validation from parent + if self.is_valid_token() and self.is_valid_user_agent(): + token = self.headers["authorization"].split(" ")[1] + parsed_path = urlparse(self.path) + len_header = self.headers.get("Content-Length") or 0 + content_len = int(len_header) + post_body = self.rfile.read(content_len) + request_body = None + if post_body: + try: + post_body = post_body.decode("utf-8") + if post_body.startswith("{"): + request_body = json.loads(post_body) + else: + request_body = {k: v[0] for k, v in parse_qs(post_body).items()} + except UnicodeDecodeError: + pass + else: + if parsed_path and parsed_path.query: + request_body = {k: v[0] for k, v in parse_qs(parsed_path.query).items()} + + # Store request body for chat stream endpoints + if self.path in ["/chat.startStream", "/chat.appendStream", "/chat.stopStream"] and request_body: + if not hasattr(self.server, "chat_stream_requests"): + self.server.chat_stream_requests = {} + self.server.chat_stream_requests[self.path] = { + "token": token, + **request_body, + } + + # Load response file + pattern = str(token).split("xoxb-", 1)[1] + with open(f"tests/slack_sdk_fixture/web_response_{pattern}.json") as file: + body = json.load(file) + + else: + body = self.invalid_auth + + if not body: + body = self.not_found + + self.send_response(200) + self.set_common_headers() + self.wfile.write(json.dumps(body).encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + +class TestAsyncChatStream(unittest.TestCase): + def setUp(self): + setup_mock_web_api_server(self, ChatStreamMockHandler) + self.client = AsyncWebClient( + token="xoxb-chat_stream_test", + base_url="http://localhost:8888", + ) + + def tearDown(self): + cleanup_mock_web_api_server(self) + + @async_test + async def test_streams_a_short_message(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + ) + await streamer.append(markdown_text="nice!") + await streamer.stop() + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 0) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("ts"), "123.123") + self.assertEqual(stop_request.get("markdown_text"), "nice!") + + @async_test + async def test_streams_a_long_message(self): + streamer = await self.client.chat_stream( + buffer_size=5, + channel="C0123456789", + recipient_team_id="T0123456789", + recipient_user_id="U0123456789", + thread_ts="123.000", + ) + await streamer.append(markdown_text="**this messag") + await streamer.append(markdown_text="e is", token="xoxb-chat_stream_test_token1") + await streamer.append(markdown_text=" bold!") + await streamer.append(markdown_text="*") + await streamer.stop( + blocks=[ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + positive_button=FeedbackButtonObject(text="good", value="+1"), + negative_button=FeedbackButtonObject(text="bad", value="-1"), + ), + IconButtonElement( + icon="trash", + text="delete", + ), + ], + ) + ], + markdown_text="*", + token="xoxb-chat_stream_test_token2", + ) + + self.assertEqual(self.received_requests.get("/chat.startStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.appendStream", 0), 1) + self.assertEqual(self.received_requests.get("/chat.stopStream", 0), 1) + + if hasattr(self.thread.server, "chat_stream_requests"): + start_request = self.thread.server.chat_stream_requests.get("/chat.startStream", {}) + self.assertEqual(start_request.get("channel"), "C0123456789") + self.assertEqual(start_request.get("thread_ts"), "123.000") + self.assertEqual(start_request.get("markdown_text"), "**this messag") + self.assertEqual(start_request.get("recipient_team_id"), "T0123456789") + self.assertEqual(start_request.get("recipient_user_id"), "U0123456789") + + append_request = self.thread.server.chat_stream_requests.get("/chat.appendStream", {}) + self.assertEqual(append_request.get("channel"), "C0123456789") + self.assertEqual(append_request.get("markdown_text"), "e is bold!") + self.assertEqual(append_request.get("token"), "xoxb-chat_stream_test_token1") + self.assertEqual(append_request.get("ts"), "123.123") + + stop_request = self.thread.server.chat_stream_requests.get("/chat.stopStream", {}) + self.assertEqual( + json.dumps(stop_request.get("blocks")), + '[{"elements": [{"negative_button": {"text": {"emoji": true, "text": "bad", "type": "plain_text"}, "value": "-1"}, "positive_button": {"text": {"emoji": true, "text": "good", "type": "plain_text"}, "value": "+1"}, "type": "feedback_buttons"}, {"icon": "trash", "text": {"emoji": true, "text": "delete", "type": "plain_text"}, "type": "icon_button"}], "type": "context_actions"}]', + ) + self.assertEqual(stop_request.get("channel"), "C0123456789") + self.assertEqual(stop_request.get("markdown_text"), "**") + self.assertEqual(stop_request.get("token"), "xoxb-chat_stream_test_token2") + self.assertEqual(stop_request.get("ts"), "123.123") + + @async_test + async def test_streams_errors_when_appending_to_an_unstarted_stream(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + token="xoxb-chat_stream_test_missing_ts", + ) + with self.assertRaisesRegex(SlackRequestError, r"^Failed to stop stream: stream not started$"): + await streamer.stop() + + @async_test + async def test_streams_errors_when_appending_to_a_completed_stream(self): + streamer = await self.client.chat_stream( + channel="C0123456789", + thread_ts="123.000", + ) + await streamer.append(markdown_text="nice!") + await streamer.stop() + with self.assertRaisesRegex(SlackRequestError, r"^Cannot append to stream: stream state is completed$"): + await streamer.append(markdown_text="more...") + with self.assertRaisesRegex(SlackRequestError, r"^Cannot stop stream: stream state is completed$"): + await streamer.stop() 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 f6410315e..af6d92236 100644 --- a/tests/slack_sdk_async/web/test_web_client_coverage.py +++ b/tests/slack_sdk_async/web/test_web_client_coverage.py @@ -13,9 +13,9 @@ class TestWebClientCoverage(unittest.TestCase): - # 292 endpoints as of July 22, 2025 + # 295 endpoints as of September 17, 2025 # Can be fetched by running `var methodNames = [].slice.call(document.getElementsByClassName('apiReferenceFilterableList__listItemLink')).map(e => e.href.replace("https://api.slack.com/methods/", ""));console.log(methodNames.toString());console.log(methodNames.length);` on https://api.slack.com/methods - all_api_methods = "admin.analytics.getFile,admin.apps.activities.list,admin.apps.approve,admin.apps.clearResolution,admin.apps.restrict,admin.apps.uninstall,admin.apps.approved.list,admin.apps.config.lookup,admin.apps.config.set,admin.apps.requests.cancel,admin.apps.requests.list,admin.apps.restricted.list,admin.audit.anomaly.allow.getItem,admin.audit.anomaly.allow.updateItem,admin.auth.policy.assignEntities,admin.auth.policy.getEntities,admin.auth.policy.removeEntities,admin.barriers.create,admin.barriers.delete,admin.barriers.list,admin.barriers.update,admin.conversations.archive,admin.conversations.bulkArchive,admin.conversations.bulkDelete,admin.conversations.bulkMove,admin.conversations.convertToPrivate,admin.conversations.convertToPublic,admin.conversations.create,admin.conversations.createForObjects,admin.conversations.delete,admin.conversations.disconnectShared,admin.conversations.getConversationPrefs,admin.conversations.getCustomRetention,admin.conversations.getTeams,admin.conversations.invite,admin.conversations.linkObjects,admin.conversations.lookup,admin.conversations.removeCustomRetention,admin.conversations.rename,admin.conversations.search,admin.conversations.setConversationPrefs,admin.conversations.setCustomRetention,admin.conversations.setTeams,admin.conversations.unarchive,admin.conversations.unlinkObjects,admin.conversations.ekm.listOriginalConnectedChannelInfo,admin.conversations.restrictAccess.addGroup,admin.conversations.restrictAccess.listGroups,admin.conversations.restrictAccess.removeGroup,admin.emoji.add,admin.emoji.addAlias,admin.emoji.list,admin.emoji.remove,admin.emoji.rename,admin.functions.list,admin.functions.permissions.lookup,admin.functions.permissions.set,admin.inviteRequests.approve,admin.inviteRequests.deny,admin.inviteRequests.list,admin.inviteRequests.approved.list,admin.inviteRequests.denied.list,admin.roles.addAssignments,admin.roles.listAssignments,admin.roles.removeAssignments,admin.teams.admins.list,admin.teams.create,admin.teams.list,admin.teams.owners.list,admin.teams.settings.info,admin.teams.settings.setDefaultChannels,admin.teams.settings.setDescription,admin.teams.settings.setDiscoverability,admin.teams.settings.setIcon,admin.teams.settings.setName,admin.usergroups.addChannels,admin.usergroups.addTeams,admin.usergroups.listChannels,admin.usergroups.removeChannels,admin.users.assign,admin.users.invite,admin.users.list,admin.users.remove,admin.users.setAdmin,admin.users.setExpiration,admin.users.setOwner,admin.users.setRegular,admin.users.session.clearSettings,admin.users.session.getSettings,admin.users.session.invalidate,admin.users.session.list,admin.users.session.reset,admin.users.session.resetBulk,admin.users.session.setSettings,admin.users.unsupportedVersions.export,admin.workflows.collaborators.add,admin.workflows.collaborators.remove,admin.workflows.permissions.lookup,admin.workflows.search,admin.workflows.unpublish,admin.workflows.triggers.types.permissions.lookup,admin.workflows.triggers.types.permissions.set,api.test,apps.activities.list,apps.auth.external.delete,apps.auth.external.get,apps.connections.open,apps.uninstall,apps.datastore.bulkDelete,apps.datastore.bulkGet,apps.datastore.bulkPut,apps.datastore.count,apps.datastore.delete,apps.datastore.get,apps.datastore.put,apps.datastore.query,apps.datastore.update,apps.event.authorizations.list,apps.manifest.create,apps.manifest.delete,apps.manifest.export,apps.manifest.update,apps.manifest.validate,assistant.search.context,assistant.threads.setStatus,assistant.threads.setSuggestedPrompts,assistant.threads.setTitle,auth.revoke,auth.test,auth.teams.list,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,bots.info,calls.add,calls.end,calls.info,calls.update,calls.participants.add,calls.participants.remove,canvases.access.delete,canvases.access.set,canvases.create,canvases.delete,canvases.edit,canvases.sections.lookup,channels.mark,chat.delete,chat.deleteScheduledMessage,chat.getPermalink,chat.meMessage,chat.postEphemeral,chat.postMessage,chat.scheduleMessage,chat.unfurl,chat.update,chat.scheduledMessages.list,conversations.acceptSharedInvite,conversations.approveSharedInvite,conversations.archive,conversations.close,conversations.create,conversations.declineSharedInvite,conversations.history,conversations.info,conversations.invite,conversations.inviteShared,conversations.join,conversations.kick,conversations.leave,conversations.list,conversations.listConnectInvites,conversations.mark,conversations.members,conversations.open,conversations.rename,conversations.replies,conversations.setPurpose,conversations.setTopic,conversations.unarchive,conversations.canvases.create,conversations.externalInvitePermissions.set,conversations.requestSharedInvite.approve,conversations.requestSharedInvite.deny,conversations.requestSharedInvite.list,dialog.open,dnd.endDnd,dnd.endSnooze,dnd.info,dnd.setSnooze,dnd.teamInfo,emoji.list,files.completeUploadExternal,files.delete,files.getUploadURLExternal,files.info,files.list,files.revokePublicURL,files.sharedPublicURL,files.upload,files.comments.delete,files.remote.add,files.remote.info,files.remote.list,files.remote.remove,files.remote.share,files.remote.update,functions.completeError,functions.completeSuccess,functions.distributions.permissions.add,functions.distributions.permissions.list,functions.distributions.permissions.remove,functions.distributions.permissions.set,functions.workflows.steps.list,functions.workflows.steps.responses.export,groups.mark,migration.exchange,oauth.access,oauth.v2.access,oauth.v2.exchange,openid.connect.token,openid.connect.userInfo,pins.add,pins.list,pins.remove,reactions.add,reactions.get,reactions.list,reactions.remove,reminders.add,reminders.complete,reminders.delete,reminders.info,reminders.list,rtm.connect,rtm.start,search.all,search.files,search.messages,stars.add,stars.list,stars.remove,team.accessLogs,team.billableInfo,team.info,team.integrationLogs,team.billing.info,team.externalTeams.disconnect,team.externalTeams.list,team.preferences.list,team.profile.get,tooling.tokens.rotate,usergroups.create,usergroups.disable,usergroups.enable,usergroups.list,usergroups.update,usergroups.users.list,usergroups.users.update,users.conversations,users.deletePhoto,users.getPresence,users.identity,users.info,users.list,users.lookupByEmail,users.setActive,users.setPhoto,users.setPresence,users.discoverableContacts.lookup,users.profile.get,users.profile.set,views.open,views.publish,views.push,views.update,workflows.featured.add,workflows.featured.list,workflows.featured.remove,workflows.featured.set,workflows.stepCompleted,workflows.stepFailed,workflows.updateStep,workflows.triggers.permissions.add,workflows.triggers.permissions.list,workflows.triggers.permissions.remove,workflows.triggers.permissions.set,im.list,im.mark,mpim.list,mpim.mark".split( + all_api_methods = "admin.analytics.getFile,admin.apps.activities.list,admin.apps.approve,admin.apps.clearResolution,admin.apps.restrict,admin.apps.uninstall,admin.apps.approved.list,admin.apps.config.lookup,admin.apps.config.set,admin.apps.requests.cancel,admin.apps.requests.list,admin.apps.restricted.list,admin.audit.anomaly.allow.getItem,admin.audit.anomaly.allow.updateItem,admin.auth.policy.assignEntities,admin.auth.policy.getEntities,admin.auth.policy.removeEntities,admin.barriers.create,admin.barriers.delete,admin.barriers.list,admin.barriers.update,admin.conversations.archive,admin.conversations.bulkArchive,admin.conversations.bulkDelete,admin.conversations.bulkMove,admin.conversations.convertToPrivate,admin.conversations.convertToPublic,admin.conversations.create,admin.conversations.createForObjects,admin.conversations.delete,admin.conversations.disconnectShared,admin.conversations.getConversationPrefs,admin.conversations.getCustomRetention,admin.conversations.getTeams,admin.conversations.invite,admin.conversations.linkObjects,admin.conversations.lookup,admin.conversations.removeCustomRetention,admin.conversations.rename,admin.conversations.search,admin.conversations.setConversationPrefs,admin.conversations.setCustomRetention,admin.conversations.setTeams,admin.conversations.unarchive,admin.conversations.unlinkObjects,admin.conversations.ekm.listOriginalConnectedChannelInfo,admin.conversations.restrictAccess.addGroup,admin.conversations.restrictAccess.listGroups,admin.conversations.restrictAccess.removeGroup,admin.emoji.add,admin.emoji.addAlias,admin.emoji.list,admin.emoji.remove,admin.emoji.rename,admin.functions.list,admin.functions.permissions.lookup,admin.functions.permissions.set,admin.inviteRequests.approve,admin.inviteRequests.deny,admin.inviteRequests.list,admin.inviteRequests.approved.list,admin.inviteRequests.denied.list,admin.roles.addAssignments,admin.roles.listAssignments,admin.roles.removeAssignments,admin.teams.admins.list,admin.teams.create,admin.teams.list,admin.teams.owners.list,admin.teams.settings.info,admin.teams.settings.setDefaultChannels,admin.teams.settings.setDescription,admin.teams.settings.setDiscoverability,admin.teams.settings.setIcon,admin.teams.settings.setName,admin.usergroups.addChannels,admin.usergroups.addTeams,admin.usergroups.listChannels,admin.usergroups.removeChannels,admin.users.assign,admin.users.invite,admin.users.list,admin.users.remove,admin.users.setAdmin,admin.users.setExpiration,admin.users.setOwner,admin.users.setRegular,admin.users.session.clearSettings,admin.users.session.getSettings,admin.users.session.invalidate,admin.users.session.list,admin.users.session.reset,admin.users.session.resetBulk,admin.users.session.setSettings,admin.users.unsupportedVersions.export,admin.workflows.collaborators.add,admin.workflows.collaborators.remove,admin.workflows.permissions.lookup,admin.workflows.search,admin.workflows.unpublish,admin.workflows.triggers.types.permissions.lookup,admin.workflows.triggers.types.permissions.set,api.test,apps.activities.list,apps.auth.external.delete,apps.auth.external.get,apps.connections.open,apps.uninstall,apps.datastore.bulkDelete,apps.datastore.bulkGet,apps.datastore.bulkPut,apps.datastore.count,apps.datastore.delete,apps.datastore.get,apps.datastore.put,apps.datastore.query,apps.datastore.update,apps.event.authorizations.list,apps.manifest.create,apps.manifest.delete,apps.manifest.export,apps.manifest.update,apps.manifest.validate,assistant.search.context,assistant.threads.setStatus,assistant.threads.setSuggestedPrompts,assistant.threads.setTitle,auth.revoke,auth.test,auth.teams.list,bookmarks.add,bookmarks.edit,bookmarks.list,bookmarks.remove,bots.info,calls.add,calls.end,calls.info,calls.update,calls.participants.add,calls.participants.remove,canvases.access.delete,canvases.access.set,canvases.create,canvases.delete,canvases.edit,canvases.sections.lookup,channels.mark,chat.appendStream,chat.delete,chat.deleteScheduledMessage,chat.getPermalink,chat.meMessage,chat.postEphemeral,chat.postMessage,chat.scheduleMessage,chat.scheduledMessages.list,chat.startStream,chat.stopStream,chat.unfurl,chat.update,conversations.acceptSharedInvite,conversations.approveSharedInvite,conversations.archive,conversations.close,conversations.create,conversations.declineSharedInvite,conversations.history,conversations.info,conversations.invite,conversations.inviteShared,conversations.join,conversations.kick,conversations.leave,conversations.list,conversations.listConnectInvites,conversations.mark,conversations.members,conversations.open,conversations.rename,conversations.replies,conversations.setPurpose,conversations.setTopic,conversations.unarchive,conversations.canvases.create,conversations.externalInvitePermissions.set,conversations.requestSharedInvite.approve,conversations.requestSharedInvite.deny,conversations.requestSharedInvite.list,dialog.open,dnd.endDnd,dnd.endSnooze,dnd.info,dnd.setSnooze,dnd.teamInfo,emoji.list,files.completeUploadExternal,files.delete,files.getUploadURLExternal,files.info,files.list,files.revokePublicURL,files.sharedPublicURL,files.upload,files.comments.delete,files.remote.add,files.remote.info,files.remote.list,files.remote.remove,files.remote.share,files.remote.update,functions.completeError,functions.completeSuccess,functions.distributions.permissions.add,functions.distributions.permissions.list,functions.distributions.permissions.remove,functions.distributions.permissions.set,functions.workflows.steps.list,functions.workflows.steps.responses.export,groups.mark,migration.exchange,oauth.access,oauth.v2.access,oauth.v2.exchange,openid.connect.token,openid.connect.userInfo,pins.add,pins.list,pins.remove,reactions.add,reactions.get,reactions.list,reactions.remove,reminders.add,reminders.complete,reminders.delete,reminders.info,reminders.list,rtm.connect,rtm.start,search.all,search.files,search.messages,stars.add,stars.list,stars.remove,team.accessLogs,team.billableInfo,team.info,team.integrationLogs,team.billing.info,team.externalTeams.disconnect,team.externalTeams.list,team.preferences.list,team.profile.get,tooling.tokens.rotate,usergroups.create,usergroups.disable,usergroups.enable,usergroups.list,usergroups.update,usergroups.users.list,usergroups.users.update,users.conversations,users.deletePhoto,users.getPresence,users.identity,users.info,users.list,users.lookupByEmail,users.setActive,users.setPhoto,users.setPresence,users.discoverableContacts.lookup,users.profile.get,users.profile.set,views.open,views.publish,views.push,views.update,workflows.featured.add,workflows.featured.list,workflows.featured.remove,workflows.featured.set,workflows.stepCompleted,workflows.stepFailed,workflows.updateStep,workflows.triggers.permissions.add,workflows.triggers.permissions.list,workflows.triggers.permissions.remove,workflows.triggers.permissions.set,im.list,im.mark,mpim.list,mpim.mark".split( "," ) @@ -425,7 +425,19 @@ async def run_method(self, method_name, method, async_method): self.api_methods_to_call.remove( method(channel_id="D111", thread_ts="111.222", status="is typing...")["method"] ) + method( + channel_id="D111", + thread_ts="111.222", + status="is typing...", + loading_states=["Thinking...", "Writing..."], + ) await async_method(channel_id="D111", thread_ts="111.222", status="is typing...") + await async_method( + channel_id="D111", + thread_ts="111.222", + status="is typing...", + loading_states=["Thinking...", "Writing..."], + ) 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") @@ -540,6 +552,9 @@ async def run_method(self, method_name, method, async_method): elif method_name == "canvases_sections_lookup": self.api_methods_to_call.remove(method(canvas_id="F123", criteria={})["method"]) await async_method(canvas_id="F123", criteria={}) + elif method_name == "chat_appendStream": + self.api_methods_to_call.remove(method(channel="C123", ts="123.123", markdown_text="**bold**")["method"]) + await async_method(channel="C123", ts="123.123", markdown_text="**bold**") elif method_name == "chat_delete": self.api_methods_to_call.remove(method(channel="C123", ts="123.123")["method"]) await async_method(channel="C123", ts="123.123") @@ -561,6 +576,19 @@ async def run_method(self, method_name, method, async_method): elif method_name == "chat_scheduleMessage": self.api_methods_to_call.remove(method(channel="C123", post_at=123, text="Hi")["method"]) await async_method(channel="C123", post_at=123, text="Hi") + elif method_name == "chat_scheduledMessages_list": + self.api_methods_to_call.remove(method()["method"]) + await async_method() + elif method_name == "chat_startStream": + self.api_methods_to_call.remove(method(channel="C123", thread_ts="123.123")["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") + elif method_name == "chat_stopStream": + self.api_methods_to_call.remove( + method(channel="C123", ts="123.123", blocks=[{"type": "markdown", "text": "**twelve**"}])["method"] + ) + await async_method(channel="C123", ts="123.123", blocks=[{"type": "markdown", "text": "**twelve**"}]) elif method_name == "chat_unfurl": self.api_methods_to_call.remove( method( diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test.json b/tests/slack_sdk_fixture/web_response_chat_stream_test.json new file mode 100644 index 000000000..2b5f29d01 --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test.json @@ -0,0 +1,4 @@ +{ + "ok": true, + "ts": "123.123" +} diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test_missing_ts.json b/tests/slack_sdk_fixture/web_response_chat_stream_test_missing_ts.json new file mode 100644 index 000000000..0287aedde --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test_missing_ts.json @@ -0,0 +1,3 @@ +{ + "ok": true +} diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test_token1.json b/tests/slack_sdk_fixture/web_response_chat_stream_test_token1.json new file mode 100644 index 000000000..0287aedde --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test_token1.json @@ -0,0 +1,3 @@ +{ + "ok": true +} diff --git a/tests/slack_sdk_fixture/web_response_chat_stream_test_token2.json b/tests/slack_sdk_fixture/web_response_chat_stream_test_token2.json new file mode 100644 index 000000000..0287aedde --- /dev/null +++ b/tests/slack_sdk_fixture/web_response_chat_stream_test_token2.json @@ -0,0 +1,3 @@ +{ + "ok": true +}