# This file was auto-generated by Fern from our API Definition.

import contextlib
import datetime as dt
import json
import typing
from json.decoder import JSONDecodeError

import httpx_sse
from .. import core
from ..commons.errors.bad_request_error import BadRequestError
from ..commons.errors.not_found_error import NotFoundError
from ..commons.errors.server_error import ServerError
from ..commons.types.attachment_request import AttachmentRequest
from ..commons.types.conversation_response import ConversationResponse
from ..commons.types.entity_id_base import EntityIdBase
from ..commons.types.error_message import ErrorMessage
from ..commons.types.feedback import Feedback
from ..commons.types.feedback_type import FeedbackType
from ..commons.types.response_config import ResponseConfig
from ..commons.types.simulation_context import SimulationContext
from ..core.api_error import ApiError
from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.http_response import AsyncHttpResponse, HttpResponse
from ..core.jsonable_encoder import jsonable_encoder
from ..core.pydantic_utilities import parse_obj_as
from ..core.request_options import RequestOptions
from ..core.serialization import convert_and_respect_annotation_metadata
from .types.action_form_request_param_value import ActionFormRequestParamValue
from .types.categorization_response import CategorizationResponse
from .types.conversation_field import ConversationField
from .types.conversation_filter import ConversationFilter
from .types.conversation_message_request import ConversationMessageRequest
from .types.conversation_metadata import ConversationMetadata
from .types.conversations_response import ConversationsResponse
from .types.deliver_message_request import DeliverMessageRequest
from .types.deliver_message_response import DeliverMessageResponse
from .types.object_stream_response import ObjectStreamResponse
from .types.stream_response import StreamResponse

# this is used as the default value for optional parameters
OMIT = typing.cast(typing.Any, ...)


class RawConversationClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._client_wrapper = client_wrapper

    def initialize(
        self,
        *,
        conversation_id: EntityIdBase,
        messages: typing.Sequence[ConversationMessageRequest],
        simulation_context: typing.Optional[SimulationContext] = OMIT,
        response_config: typing.Optional[ResponseConfig] = OMIT,
        subject: typing.Optional[str] = OMIT,
        url: typing.Optional[str] = OMIT,
        created_at: typing.Optional[dt.datetime] = OMIT,
        updated_at: typing.Optional[dt.datetime] = OMIT,
        tags: typing.Optional[typing.Set[str]] = OMIT,
        metadata: typing.Optional[typing.Dict[str, str]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationResponse]:
        """
        Initialize a new conversation.
        Only required if the ask request wishes to supply conversation level data or when syncing to external systems.

        Conversations can not be modified using this API. If the conversation already exists then the existing conversation will be returned.

        After initialization,
        - metadata can be changed using the `updateConversationMetadata` API.
        - messages can be added to the conversation with the `appendNewMessages` or `ask` APIs.

        Parameters
        ----------
        conversation_id : EntityIdBase
            An externally supplied ID to uniquely identify this conversation

        messages : typing.Sequence[ConversationMessageRequest]
            The messages in the conversation

        simulation_context : typing.Optional[SimulationContext]
            Additional context used for simulation runs. When provided, this conversation will be treated as a simulation and
            may only be created by apps with the appropriate permission. Simulation conversations are excluded from normal
            search results unless explicitly included via the `simulationFilter` field.

        response_config : typing.Optional[ResponseConfig]
            Optional configurations for responses to this conversation

        subject : typing.Optional[str]
            The subject of the conversation

        url : typing.Optional[str]
            The url of the conversation

        created_at : typing.Optional[dt.datetime]
            The date and time the conversation was created

        updated_at : typing.Optional[dt.datetime]
            The date and time the conversation was last updated

        tags : typing.Optional[typing.Set[str]]
            The tags of the conversation. Used for filtering in Agent Designer.

        metadata : typing.Optional[typing.Dict[str, str]]
            The metadata of the conversation supplied by the app which created the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationResponse]
            Initialized conversation
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/conversations",
            method="POST",
            json={
                "conversationId": convert_and_respect_annotation_metadata(
                    object_=conversation_id, annotation=EntityIdBase, direction="write"
                ),
                "simulationContext": convert_and_respect_annotation_metadata(
                    object_=simulation_context, annotation=SimulationContext, direction="write"
                ),
                "responseConfig": convert_and_respect_annotation_metadata(
                    object_=response_config, annotation=ResponseConfig, direction="write"
                ),
                "subject": subject,
                "url": url,
                "createdAt": created_at,
                "updatedAt": updated_at,
                "tags": tags,
                "metadata": metadata,
                "messages": convert_and_respect_annotation_metadata(
                    object_=messages, annotation=typing.Sequence[ConversationMessageRequest], direction="write"
                ),
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def patch(
        self,
        conversation_id: str,
        *,
        app_id: typing.Optional[str] = OMIT,
        open: typing.Optional[bool] = OMIT,
        llm_enabled: typing.Optional[bool] = OMIT,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationResponse]:
        """
        Update mutable conversation fields.

        The `appId` field can be provided to update a conversation owned by a different app.
        All other fields will overwrite the existing value on the conversation only if provided.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to patch

        app_id : typing.Optional[str]
            The App ID of the conversation to patch. If not provided the ID of the calling app will be used.

        open : typing.Optional[bool]
            Whether the conversation is able to receive asynchronous messages. Only valid for conversations with the `ASYNC` capability.

        llm_enabled : typing.Optional[bool]
            Whether the LLM is enabled for this conversation.

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            A list of attachments to add to the conversation. Attachments can only be appended. Removal is not allowed.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationResponse]
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}",
            method="PATCH",
            json={
                "appId": app_id,
                "open": open,
                "llmEnabled": llm_enabled,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def get(
        self,
        conversation_id: str,
        *,
        app_id: typing.Optional[str] = None,
        translation_language: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationResponse]:
        """
        Get a conversation

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to get

        app_id : typing.Optional[str]
            The App ID of the conversation to get. If not provided the ID of the calling app will be used.

        translation_language : typing.Optional[str]
            The language to translate the conversation analysis into

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationResponse]
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}",
            method="GET",
            params={
                "appId": app_id,
                "translationLanguage": translation_language,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def delete(
        self,
        conversation_id: str,
        *,
        reason: str,
        app_id: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[None]:
        """
        Wipes a conversation of all user data.
        The conversation ID will still exist and non-user specific data will still be retained.
        Attempts to modify or add messages to the conversation will throw an error.

        Simulation conversations will no longer be visible in search results nor metrics.
        Non-simulation conversations will remain visible - they can not be fully removed from the system.

        <Warning>This is a destructive operation and cannot be undone. <br/><br/>
        The exact fields cleared include: the conversation subject, userRequest, agentResponse.
        As well as the text response, followup questions, and backend LLM prompt of all messages.</Warning>

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to delete

        reason : str
            The reason for deleting the conversation. This message will replace all user messages in the conversation.

        app_id : typing.Optional[str]
            The App ID of the conversation to delete. If not provided the ID of the calling app will be used.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[None]
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}",
            method="DELETE",
            params={
                "appId": app_id,
                "reason": reason,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return HttpResponse(response=_response, data=None)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def append_new_messages(
        self,
        conversation_id: str,
        *,
        request: typing.Sequence[ConversationMessageRequest],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationResponse]:
        """
        Append messages to an existing conversation. The conversation must be initialized first. If a message with the same ID already exists, it will be ignored. Messages do not allow modification.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to append messages to

        request : typing.Sequence[ConversationMessageRequest]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationResponse]
            Updated Conversation
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/messages",
            method="POST",
            json=convert_and_respect_annotation_metadata(
                object_=request, annotation=typing.Sequence[ConversationMessageRequest], direction="write"
            ),
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def ask(
        self,
        conversation_id: str,
        *,
        conversation_message_id: EntityIdBase,
        user_id: EntityIdBase,
        text: str,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        timezone: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationResponse]:
        """
        Get an answer from Maven for a given user question. If the user question or its answer already exists,
        they will be reused and will not be updated. Messages do not allow modification once generated.

        Concurrency Behavior:
        - If another API call is made for the same user question while a response is mid-stream, partial answers may be returned.
        - The second caller will receive a truncated or partial response depending on where the first stream is in its processing. The first caller's stream will remain unaffected and continue delivering the full response.

        Known Limitation:
        - The API does not currently expose metadata indicating whether a response or message is incomplete. This will be addressed in a future update.

        Parameters
        ----------
        conversation_id : str
            The ID of a new or existing conversation to use as context for the question

        conversation_message_id : EntityIdBase
            Externally supplied ID to uniquely identify this message within the conversation. If a message with this ID already exists it will be reused and will not be updated.

        user_id : EntityIdBase
            Externally supplied ID to uniquely identify the user that created this message

        text : str
            The text of the message

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            The attachments to the message. Image attachments will be sent to the LLM as additional data.
            Non-image attachments can be stored and downloaded from the API but will not be sent to the LLM.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken by this ask request. For example, one may put in user tokens as transient data.

        timezone : typing.Optional[str]
            IANA timezone identifier (e.g. "America/New_York", "Europe/London") to be used for time-based operations in the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationResponse]
            Updated Conversation with a user message of the question and a bot message with the response.
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/ask",
            method="POST",
            json={
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "text": text,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
                "transientData": transient_data,
                "timezone": timezone,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    @contextlib.contextmanager
    def ask_stream(
        self,
        conversation_id: str,
        *,
        conversation_message_id: EntityIdBase,
        user_id: EntityIdBase,
        text: str,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        timezone: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.Iterator[HttpResponse[typing.Iterator[StreamResponse]]]:
        """
        Get an answer from Maven for a given user question with a streaming response. The response will be sent as a stream of events.
        The text portions of stream responses should be concatenated to form the full response text.
        Action and metadata events should overwrite past data and do not need concatenation.

        If the user question or its answer already exists, they will be reused and will not be updated.
        Messages do not allow modification once generated.

        Concurrency Behavior:
        - If another API call is made for the same user question while a response is mid-stream, partial answers may be returned.
        - The second caller will receive a truncated or partial response depending on where the first stream is in its processing. The first caller's stream will remain unaffected and continue delivering the full response.

        Known Limitation:
        - The API does not currently expose metadata indicating whether a response or message is incomplete. This will be addressed in a future update.

        Parameters
        ----------
        conversation_id : str
            The ID of a new or existing conversation to use as context for the question

        conversation_message_id : EntityIdBase
            Externally supplied ID to uniquely identify this message within the conversation. If a message with this ID already exists it will be reused and will not be updated.

        user_id : EntityIdBase
            Externally supplied ID to uniquely identify the user that created this message

        text : str
            The text of the message

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            The attachments to the message. Image attachments will be sent to the LLM as additional data.
            Non-image attachments can be stored and downloaded from the API but will not be sent to the LLM.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken by this ask request. For example, one may put in user tokens as transient data.

        timezone : typing.Optional[str]
            IANA timezone identifier (e.g. "America/New_York", "Europe/London") to be used for time-based operations in the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Yields
        ------
        typing.Iterator[HttpResponse[typing.Iterator[StreamResponse]]]
        """
        with self._client_wrapper.httpx_client.stream(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/ask_stream",
            method="POST",
            json={
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "text": text,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
                "transientData": transient_data,
                "timezone": timezone,
            },
            request_options=request_options,
            omit=OMIT,
        ) as _response:

            def _stream() -> HttpResponse[typing.Iterator[StreamResponse]]:
                try:
                    if 200 <= _response.status_code < 300:

                        def _iter():
                            _event_source = httpx_sse.EventSource(_response)
                            for _sse in _event_source.iter_sse():
                                if _sse.data == None:
                                    return
                                try:
                                    yield typing.cast(
                                        StreamResponse,
                                        parse_obj_as(
                                            type_=StreamResponse,  # type: ignore
                                            object_=json.loads(_sse.data),
                                        ),
                                    )
                                except Exception:
                                    pass
                            return

                        return HttpResponse(response=_response, data=_iter())
                    _response.read()
                    if _response.status_code == 404:
                        raise NotFoundError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 400:
                        raise BadRequestError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 500:
                        raise ServerError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    _response_json = _response.json()
                except JSONDecodeError:
                    raise ApiError(
                        status_code=_response.status_code, headers=dict(_response.headers), body=_response.text
                    )
                raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

            yield _stream()

    @contextlib.contextmanager
    def ask_object_stream(
        self,
        conversation_id: str,
        *,
        schema: str,
        conversation_message_id: EntityIdBase,
        user_id: EntityIdBase,
        text: str,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        timezone: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.Iterator[HttpResponse[typing.Iterator[ObjectStreamResponse]]]:
        """
        Generate a structured object response based on a provided schema and user prompt with a streaming response.
        The response will be sent as a stream of events containing text, start, and end events.
        The text portions of stream responses should be concatenated to form the full response text.

        If the user question and object response already exist, they will be reused and not updated.

        Concurrency Behavior:
        - If another API call is made for the same user question while a response is mid-stream, partial answers may be returned.
        - The second caller will receive a truncated or partial response depending on where the first stream is in its processing. The first caller's stream will remain unaffected and continue delivering the full response.

        Known Limitations:
        - Schema enforcement is best-effort and may not guarantee exact conformity.
        - The API does not currently expose metadata indicating whether a response or message is incomplete. This will be addressed in a future update.

        Parameters
        ----------
        conversation_id : str
            The ID of a new or existing conversation to use as context for the object generation request

        schema : str
            JSON schema string defining the expected object shape.

        conversation_message_id : EntityIdBase
            Externally supplied ID to uniquely identify this message within the conversation. If a message with this ID already exists it will be reused and will not be updated.

        user_id : EntityIdBase
            Externally supplied ID to uniquely identify the user that created this message

        text : str
            The text of the message

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            The attachments to the message. Image attachments will be sent to the LLM as additional data.
            Non-image attachments can be stored and downloaded from the API but will not be sent to the LLM.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken by this ask request. For example, one may put in user tokens as transient data.

        timezone : typing.Optional[str]
            IANA timezone identifier (e.g. "America/New_York", "Europe/London") to be used for time-based operations in the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Yields
        ------
        typing.Iterator[HttpResponse[typing.Iterator[ObjectStreamResponse]]]
        """
        with self._client_wrapper.httpx_client.stream(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/ask_object_stream",
            method="POST",
            json={
                "schema": schema,
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "text": text,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
                "transientData": transient_data,
                "timezone": timezone,
            },
            request_options=request_options,
            omit=OMIT,
        ) as _response:

            def _stream() -> HttpResponse[typing.Iterator[ObjectStreamResponse]]:
                try:
                    if 200 <= _response.status_code < 300:

                        def _iter():
                            _event_source = httpx_sse.EventSource(_response)
                            for _sse in _event_source.iter_sse():
                                if _sse.data == None:
                                    return
                                try:
                                    yield typing.cast(
                                        ObjectStreamResponse,
                                        parse_obj_as(
                                            type_=ObjectStreamResponse,  # type: ignore
                                            object_=json.loads(_sse.data),
                                        ),
                                    )
                                except Exception:
                                    pass
                            return

                        return HttpResponse(response=_response, data=_iter())
                    _response.read()
                    if _response.status_code == 404:
                        raise NotFoundError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 400:
                        raise BadRequestError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 500:
                        raise ServerError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    _response_json = _response.json()
                except JSONDecodeError:
                    raise ApiError(
                        status_code=_response.status_code, headers=dict(_response.headers), body=_response.text
                    )
                raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

            yield _stream()

    def categorize(
        self, conversation_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[CategorizationResponse]:
        """
        Uses an LLM flow to categorize the conversation. Experimental.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to categorize

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[CategorizationResponse]
            The conversation categorization
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/categorize",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CategorizationResponse,
                    parse_obj_as(
                        type_=CategorizationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def create_feedback(
        self,
        *,
        feedback_id: EntityIdBase,
        conversation_id: EntityIdBase,
        conversation_message_id: EntityIdBase,
        type: FeedbackType,
        user_id: typing.Optional[EntityIdBase] = OMIT,
        text: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[Feedback]:
        """
        Update feedback or create it if it doesn't exist

        Parameters
        ----------
        feedback_id : EntityIdBase
            The ID that uniquely identifies this feedback

        conversation_id : EntityIdBase
            The ID that uniquely identifies the the conversation the feedback is about

        conversation_message_id : EntityIdBase
            The ID that uniquely identifies the message within the conversation the feedback is about

        type : FeedbackType
            The type of feedback

        user_id : typing.Optional[EntityIdBase]
            The ID of the user who is creating the feedback

        text : typing.Optional[str]
            The feedback text

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[Feedback]
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/conversations/feedback",
            method="POST",
            json={
                "feedbackId": convert_and_respect_annotation_metadata(
                    object_=feedback_id, annotation=EntityIdBase, direction="write"
                ),
                "conversationId": convert_and_respect_annotation_metadata(
                    object_=conversation_id, annotation=EntityIdBase, direction="write"
                ),
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "type": type,
                "text": text,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    Feedback,
                    parse_obj_as(
                        type_=Feedback,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def submit_action_form(
        self,
        conversation_id: str,
        *,
        action_form_id: str,
        parameters: typing.Dict[str, ActionFormRequestParamValue],
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationResponse]:
        """
        Submit a filled out action form.
        Action forms can not be submitted more than once, attempting to do so will result in an error.

        Additionally, form submission is only allowed when the form is the last message in the conversation.
        Forms should be disabled in surface UI if a conversation continues and they remain unsubmitted.

        Parameters
        ----------
        conversation_id : str
            The ID of a conversation the form being submitted belongs to

        action_form_id : str

        parameters : typing.Dict[str, ActionFormRequestParamValue]
            Map of parameter IDs to values provided by the user. All required action fields must be provided.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken. For example, one may put in user tokens as transient data.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationResponse]
            Updated Conversation with a new BOT_RESPONSE after taking the action
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/submit-form",
            method="POST",
            json={
                "actionFormId": action_form_id,
                "parameters": convert_and_respect_annotation_metadata(
                    object_=parameters, annotation=typing.Dict[str, ActionFormRequestParamValue], direction="write"
                ),
                "transientData": transient_data,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def add_conversation_metadata(
        self,
        conversation_id: str,
        *,
        request: typing.Dict[str, str],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[typing.Dict[str, str]]:
        """
        Replaced by `updateConversationMetadata`.

        Adds metadata to an existing conversation. If a metadata field already exists, it will be overwritten.

        Parameters
        ----------
        conversation_id : str
            The ID of a conversation the metadata being added belongs to

        request : typing.Dict[str, str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[typing.Dict[str, str]]
            The metadata of the conversation
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/metadata",
            method="POST",
            json=request,
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    typing.Dict[str, str],
                    parse_obj_as(
                        type_=typing.Dict[str, str],  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def update_conversation_metadata(
        self,
        conversation_id: str,
        *,
        values: typing.Dict[str, str],
        app_id: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationMetadata]:
        """
        Update metadata supplied by the calling application for an existing conversation.
        Does not modify metadata saved by other apps.

        If a metadata field already exists for the calling app, it will be overwritten.
        If it does not exist, it will be added. Will not remove metadata fields.

        Returns all metadata saved by any app on the conversation.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to modify metadata for

        values : typing.Dict[str, str]
            The metadata values to add to the conversation.

        app_id : typing.Optional[str]
            The App ID of the conversation to modify metadata for. If not provided the ID of the calling app will be used.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationMetadata]
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/metadata",
            method="PUT",
            json={
                "appId": app_id,
                "values": values,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationMetadata,
                    parse_obj_as(
                        type_=ConversationMetadata,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def search(
        self,
        *,
        sort: typing.Optional[ConversationField] = OMIT,
        filter: typing.Optional[ConversationFilter] = OMIT,
        page: typing.Optional[int] = OMIT,
        size: typing.Optional[int] = OMIT,
        sort_desc: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationsResponse]:
        """
        Search conversations

        Parameters
        ----------
        sort : typing.Optional[ConversationField]

        filter : typing.Optional[ConversationFilter]

        page : typing.Optional[int]
            Page number to return, defaults to 0

        size : typing.Optional[int]
            The size of the page to return, defaults to 20

        sort_desc : typing.Optional[bool]
            Whether to sort descending, defaults to true

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[ConversationsResponse]
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/conversations/search",
            method="POST",
            json={
                "sort": sort,
                "filter": convert_and_respect_annotation_metadata(
                    object_=filter, annotation=ConversationFilter, direction="write"
                ),
                "page": page,
                "size": size,
                "sortDesc": sort_desc,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsResponse,
                    parse_obj_as(
                        type_=ConversationsResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    @contextlib.contextmanager
    def export(
        self,
        *,
        sort: typing.Optional[ConversationField] = OMIT,
        filter: typing.Optional[ConversationFilter] = OMIT,
        page: typing.Optional[int] = OMIT,
        size: typing.Optional[int] = OMIT,
        sort_desc: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]:
        """
        Export conversations to a CSV file.

        This will output a summary of each conversation that matches the supplied filter. A maximum of 10,000 conversations can be exported at a time.

        For most use cases it is recommended to use the `search` API instead and convert the JSON response to your desired format.
        The CSV format may change over time and should not be relied upon by code consumers.

        Parameters
        ----------
        sort : typing.Optional[ConversationField]

        filter : typing.Optional[ConversationFilter]

        page : typing.Optional[int]
            Page number to return, defaults to 0

        size : typing.Optional[int]
            The size of the page to return, defaults to 20

        sort_desc : typing.Optional[bool]
            Whether to sort descending, defaults to true

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response.

        Returns
        -------
        typing.Iterator[HttpResponse[typing.Iterator[bytes]]]
            A CSV containing one conversation per row
        """
        with self._client_wrapper.httpx_client.stream(
            "v1/conversations/export",
            method="POST",
            json={
                "sort": sort,
                "filter": convert_and_respect_annotation_metadata(
                    object_=filter, annotation=ConversationFilter, direction="write"
                ),
                "page": page,
                "size": size,
                "sortDesc": sort_desc,
            },
            request_options=request_options,
            omit=OMIT,
        ) as _response:

            def _stream() -> HttpResponse[typing.Iterator[bytes]]:
                try:
                    if 200 <= _response.status_code < 300:
                        _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None
                        return HttpResponse(
                            response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size))
                        )
                    _response.read()
                    if _response.status_code == 404:
                        raise NotFoundError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 400:
                        raise BadRequestError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 500:
                        raise ServerError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    _response_json = _response.json()
                except JSONDecodeError:
                    raise ApiError(
                        status_code=_response.status_code, headers=dict(_response.headers), body=_response.text
                    )
                raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

            yield _stream()

    def import_simulations(
        self,
        *,
        file: core.File,
        response_config: typing.Optional[ResponseConfig] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[None]:
        """
        Import simulation conversations from a CSV file.

        This CSV format is very simple and only allows for one column: `question`. A header containing this column is required.
        Each row will generate one simulation conversation, using the provided response config, if present.

        This API is offered for backwards compatibility.
        Most API callers should create simulations programmatically to allow for more flexibility.

        Parameters
        ----------
        file : core.File
            See core.File for more documentation

        response_config : typing.Optional[ResponseConfig]
            The response config to use for all of the created simulations.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[None]
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/conversations/import_simulations",
            method="POST",
            data={
                "responseConfig": response_config,
            },
            files={
                "file": file,
            },
            request_options=request_options,
            omit=OMIT,
            force_multipart=True,
        )
        try:
            if 200 <= _response.status_code < 300:
                return HttpResponse(response=_response, data=None)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def deliver_message(
        self, *, request: DeliverMessageRequest, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[DeliverMessageResponse]:
        """
        Deliver a message to a user or conversation.

        <Warning>
        Currently, messages can only be successfully delivered to conversations with the `ASYNC` capability that are `open`.
        User message delivery is not yet supported.
        </Warning>

        Parameters
        ----------
        request : DeliverMessageRequest

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[DeliverMessageResponse]
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/conversations/deliver-message",
            method="POST",
            json=convert_and_respect_annotation_metadata(
                object_=request, annotation=DeliverMessageRequest, direction="write"
            ),
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    DeliverMessageResponse,
                    parse_obj_as(
                        type_=DeliverMessageResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)


class AsyncRawConversationClient:
    def __init__(self, *, client_wrapper: AsyncClientWrapper):
        self._client_wrapper = client_wrapper

    async def initialize(
        self,
        *,
        conversation_id: EntityIdBase,
        messages: typing.Sequence[ConversationMessageRequest],
        simulation_context: typing.Optional[SimulationContext] = OMIT,
        response_config: typing.Optional[ResponseConfig] = OMIT,
        subject: typing.Optional[str] = OMIT,
        url: typing.Optional[str] = OMIT,
        created_at: typing.Optional[dt.datetime] = OMIT,
        updated_at: typing.Optional[dt.datetime] = OMIT,
        tags: typing.Optional[typing.Set[str]] = OMIT,
        metadata: typing.Optional[typing.Dict[str, str]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationResponse]:
        """
        Initialize a new conversation.
        Only required if the ask request wishes to supply conversation level data or when syncing to external systems.

        Conversations can not be modified using this API. If the conversation already exists then the existing conversation will be returned.

        After initialization,
        - metadata can be changed using the `updateConversationMetadata` API.
        - messages can be added to the conversation with the `appendNewMessages` or `ask` APIs.

        Parameters
        ----------
        conversation_id : EntityIdBase
            An externally supplied ID to uniquely identify this conversation

        messages : typing.Sequence[ConversationMessageRequest]
            The messages in the conversation

        simulation_context : typing.Optional[SimulationContext]
            Additional context used for simulation runs. When provided, this conversation will be treated as a simulation and
            may only be created by apps with the appropriate permission. Simulation conversations are excluded from normal
            search results unless explicitly included via the `simulationFilter` field.

        response_config : typing.Optional[ResponseConfig]
            Optional configurations for responses to this conversation

        subject : typing.Optional[str]
            The subject of the conversation

        url : typing.Optional[str]
            The url of the conversation

        created_at : typing.Optional[dt.datetime]
            The date and time the conversation was created

        updated_at : typing.Optional[dt.datetime]
            The date and time the conversation was last updated

        tags : typing.Optional[typing.Set[str]]
            The tags of the conversation. Used for filtering in Agent Designer.

        metadata : typing.Optional[typing.Dict[str, str]]
            The metadata of the conversation supplied by the app which created the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationResponse]
            Initialized conversation
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/conversations",
            method="POST",
            json={
                "conversationId": convert_and_respect_annotation_metadata(
                    object_=conversation_id, annotation=EntityIdBase, direction="write"
                ),
                "simulationContext": convert_and_respect_annotation_metadata(
                    object_=simulation_context, annotation=SimulationContext, direction="write"
                ),
                "responseConfig": convert_and_respect_annotation_metadata(
                    object_=response_config, annotation=ResponseConfig, direction="write"
                ),
                "subject": subject,
                "url": url,
                "createdAt": created_at,
                "updatedAt": updated_at,
                "tags": tags,
                "metadata": metadata,
                "messages": convert_and_respect_annotation_metadata(
                    object_=messages, annotation=typing.Sequence[ConversationMessageRequest], direction="write"
                ),
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def patch(
        self,
        conversation_id: str,
        *,
        app_id: typing.Optional[str] = OMIT,
        open: typing.Optional[bool] = OMIT,
        llm_enabled: typing.Optional[bool] = OMIT,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationResponse]:
        """
        Update mutable conversation fields.

        The `appId` field can be provided to update a conversation owned by a different app.
        All other fields will overwrite the existing value on the conversation only if provided.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to patch

        app_id : typing.Optional[str]
            The App ID of the conversation to patch. If not provided the ID of the calling app will be used.

        open : typing.Optional[bool]
            Whether the conversation is able to receive asynchronous messages. Only valid for conversations with the `ASYNC` capability.

        llm_enabled : typing.Optional[bool]
            Whether the LLM is enabled for this conversation.

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            A list of attachments to add to the conversation. Attachments can only be appended. Removal is not allowed.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationResponse]
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}",
            method="PATCH",
            json={
                "appId": app_id,
                "open": open,
                "llmEnabled": llm_enabled,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def get(
        self,
        conversation_id: str,
        *,
        app_id: typing.Optional[str] = None,
        translation_language: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationResponse]:
        """
        Get a conversation

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to get

        app_id : typing.Optional[str]
            The App ID of the conversation to get. If not provided the ID of the calling app will be used.

        translation_language : typing.Optional[str]
            The language to translate the conversation analysis into

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationResponse]
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}",
            method="GET",
            params={
                "appId": app_id,
                "translationLanguage": translation_language,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def delete(
        self,
        conversation_id: str,
        *,
        reason: str,
        app_id: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[None]:
        """
        Wipes a conversation of all user data.
        The conversation ID will still exist and non-user specific data will still be retained.
        Attempts to modify or add messages to the conversation will throw an error.

        Simulation conversations will no longer be visible in search results nor metrics.
        Non-simulation conversations will remain visible - they can not be fully removed from the system.

        <Warning>This is a destructive operation and cannot be undone. <br/><br/>
        The exact fields cleared include: the conversation subject, userRequest, agentResponse.
        As well as the text response, followup questions, and backend LLM prompt of all messages.</Warning>

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to delete

        reason : str
            The reason for deleting the conversation. This message will replace all user messages in the conversation.

        app_id : typing.Optional[str]
            The App ID of the conversation to delete. If not provided the ID of the calling app will be used.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[None]
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}",
            method="DELETE",
            params={
                "appId": app_id,
                "reason": reason,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return AsyncHttpResponse(response=_response, data=None)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def append_new_messages(
        self,
        conversation_id: str,
        *,
        request: typing.Sequence[ConversationMessageRequest],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationResponse]:
        """
        Append messages to an existing conversation. The conversation must be initialized first. If a message with the same ID already exists, it will be ignored. Messages do not allow modification.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to append messages to

        request : typing.Sequence[ConversationMessageRequest]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationResponse]
            Updated Conversation
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/messages",
            method="POST",
            json=convert_and_respect_annotation_metadata(
                object_=request, annotation=typing.Sequence[ConversationMessageRequest], direction="write"
            ),
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def ask(
        self,
        conversation_id: str,
        *,
        conversation_message_id: EntityIdBase,
        user_id: EntityIdBase,
        text: str,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        timezone: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationResponse]:
        """
        Get an answer from Maven for a given user question. If the user question or its answer already exists,
        they will be reused and will not be updated. Messages do not allow modification once generated.

        Concurrency Behavior:
        - If another API call is made for the same user question while a response is mid-stream, partial answers may be returned.
        - The second caller will receive a truncated or partial response depending on where the first stream is in its processing. The first caller's stream will remain unaffected and continue delivering the full response.

        Known Limitation:
        - The API does not currently expose metadata indicating whether a response or message is incomplete. This will be addressed in a future update.

        Parameters
        ----------
        conversation_id : str
            The ID of a new or existing conversation to use as context for the question

        conversation_message_id : EntityIdBase
            Externally supplied ID to uniquely identify this message within the conversation. If a message with this ID already exists it will be reused and will not be updated.

        user_id : EntityIdBase
            Externally supplied ID to uniquely identify the user that created this message

        text : str
            The text of the message

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            The attachments to the message. Image attachments will be sent to the LLM as additional data.
            Non-image attachments can be stored and downloaded from the API but will not be sent to the LLM.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken by this ask request. For example, one may put in user tokens as transient data.

        timezone : typing.Optional[str]
            IANA timezone identifier (e.g. "America/New_York", "Europe/London") to be used for time-based operations in the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationResponse]
            Updated Conversation with a user message of the question and a bot message with the response.
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/ask",
            method="POST",
            json={
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "text": text,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
                "transientData": transient_data,
                "timezone": timezone,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    @contextlib.asynccontextmanager
    async def ask_stream(
        self,
        conversation_id: str,
        *,
        conversation_message_id: EntityIdBase,
        user_id: EntityIdBase,
        text: str,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        timezone: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[StreamResponse]]]:
        """
        Get an answer from Maven for a given user question with a streaming response. The response will be sent as a stream of events.
        The text portions of stream responses should be concatenated to form the full response text.
        Action and metadata events should overwrite past data and do not need concatenation.

        If the user question or its answer already exists, they will be reused and will not be updated.
        Messages do not allow modification once generated.

        Concurrency Behavior:
        - If another API call is made for the same user question while a response is mid-stream, partial answers may be returned.
        - The second caller will receive a truncated or partial response depending on where the first stream is in its processing. The first caller's stream will remain unaffected and continue delivering the full response.

        Known Limitation:
        - The API does not currently expose metadata indicating whether a response or message is incomplete. This will be addressed in a future update.

        Parameters
        ----------
        conversation_id : str
            The ID of a new or existing conversation to use as context for the question

        conversation_message_id : EntityIdBase
            Externally supplied ID to uniquely identify this message within the conversation. If a message with this ID already exists it will be reused and will not be updated.

        user_id : EntityIdBase
            Externally supplied ID to uniquely identify the user that created this message

        text : str
            The text of the message

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            The attachments to the message. Image attachments will be sent to the LLM as additional data.
            Non-image attachments can be stored and downloaded from the API but will not be sent to the LLM.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken by this ask request. For example, one may put in user tokens as transient data.

        timezone : typing.Optional[str]
            IANA timezone identifier (e.g. "America/New_York", "Europe/London") to be used for time-based operations in the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Yields
        ------
        typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[StreamResponse]]]
        """
        async with self._client_wrapper.httpx_client.stream(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/ask_stream",
            method="POST",
            json={
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "text": text,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
                "transientData": transient_data,
                "timezone": timezone,
            },
            request_options=request_options,
            omit=OMIT,
        ) as _response:

            async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[StreamResponse]]:
                try:
                    if 200 <= _response.status_code < 300:

                        async def _iter():
                            _event_source = httpx_sse.EventSource(_response)
                            async for _sse in _event_source.aiter_sse():
                                if _sse.data == None:
                                    return
                                try:
                                    yield typing.cast(
                                        StreamResponse,
                                        parse_obj_as(
                                            type_=StreamResponse,  # type: ignore
                                            object_=json.loads(_sse.data),
                                        ),
                                    )
                                except Exception:
                                    pass
                            return

                        return AsyncHttpResponse(response=_response, data=_iter())
                    await _response.aread()
                    if _response.status_code == 404:
                        raise NotFoundError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 400:
                        raise BadRequestError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 500:
                        raise ServerError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    _response_json = _response.json()
                except JSONDecodeError:
                    raise ApiError(
                        status_code=_response.status_code, headers=dict(_response.headers), body=_response.text
                    )
                raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

            yield await _stream()

    @contextlib.asynccontextmanager
    async def ask_object_stream(
        self,
        conversation_id: str,
        *,
        schema: str,
        conversation_message_id: EntityIdBase,
        user_id: EntityIdBase,
        text: str,
        attachments: typing.Optional[typing.Sequence[AttachmentRequest]] = OMIT,
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        timezone: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[ObjectStreamResponse]]]:
        """
        Generate a structured object response based on a provided schema and user prompt with a streaming response.
        The response will be sent as a stream of events containing text, start, and end events.
        The text portions of stream responses should be concatenated to form the full response text.

        If the user question and object response already exist, they will be reused and not updated.

        Concurrency Behavior:
        - If another API call is made for the same user question while a response is mid-stream, partial answers may be returned.
        - The second caller will receive a truncated or partial response depending on where the first stream is in its processing. The first caller's stream will remain unaffected and continue delivering the full response.

        Known Limitations:
        - Schema enforcement is best-effort and may not guarantee exact conformity.
        - The API does not currently expose metadata indicating whether a response or message is incomplete. This will be addressed in a future update.

        Parameters
        ----------
        conversation_id : str
            The ID of a new or existing conversation to use as context for the object generation request

        schema : str
            JSON schema string defining the expected object shape.

        conversation_message_id : EntityIdBase
            Externally supplied ID to uniquely identify this message within the conversation. If a message with this ID already exists it will be reused and will not be updated.

        user_id : EntityIdBase
            Externally supplied ID to uniquely identify the user that created this message

        text : str
            The text of the message

        attachments : typing.Optional[typing.Sequence[AttachmentRequest]]
            The attachments to the message. Image attachments will be sent to the LLM as additional data.
            Non-image attachments can be stored and downloaded from the API but will not be sent to the LLM.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken by this ask request. For example, one may put in user tokens as transient data.

        timezone : typing.Optional[str]
            IANA timezone identifier (e.g. "America/New_York", "Europe/London") to be used for time-based operations in the conversation.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Yields
        ------
        typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[ObjectStreamResponse]]]
        """
        async with self._client_wrapper.httpx_client.stream(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/ask_object_stream",
            method="POST",
            json={
                "schema": schema,
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "text": text,
                "attachments": convert_and_respect_annotation_metadata(
                    object_=attachments, annotation=typing.Sequence[AttachmentRequest], direction="write"
                ),
                "transientData": transient_data,
                "timezone": timezone,
            },
            request_options=request_options,
            omit=OMIT,
        ) as _response:

            async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[ObjectStreamResponse]]:
                try:
                    if 200 <= _response.status_code < 300:

                        async def _iter():
                            _event_source = httpx_sse.EventSource(_response)
                            async for _sse in _event_source.aiter_sse():
                                if _sse.data == None:
                                    return
                                try:
                                    yield typing.cast(
                                        ObjectStreamResponse,
                                        parse_obj_as(
                                            type_=ObjectStreamResponse,  # type: ignore
                                            object_=json.loads(_sse.data),
                                        ),
                                    )
                                except Exception:
                                    pass
                            return

                        return AsyncHttpResponse(response=_response, data=_iter())
                    await _response.aread()
                    if _response.status_code == 404:
                        raise NotFoundError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 400:
                        raise BadRequestError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 500:
                        raise ServerError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    _response_json = _response.json()
                except JSONDecodeError:
                    raise ApiError(
                        status_code=_response.status_code, headers=dict(_response.headers), body=_response.text
                    )
                raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

            yield await _stream()

    async def categorize(
        self, conversation_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[CategorizationResponse]:
        """
        Uses an LLM flow to categorize the conversation. Experimental.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to categorize

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[CategorizationResponse]
            The conversation categorization
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/categorize",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CategorizationResponse,
                    parse_obj_as(
                        type_=CategorizationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def create_feedback(
        self,
        *,
        feedback_id: EntityIdBase,
        conversation_id: EntityIdBase,
        conversation_message_id: EntityIdBase,
        type: FeedbackType,
        user_id: typing.Optional[EntityIdBase] = OMIT,
        text: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[Feedback]:
        """
        Update feedback or create it if it doesn't exist

        Parameters
        ----------
        feedback_id : EntityIdBase
            The ID that uniquely identifies this feedback

        conversation_id : EntityIdBase
            The ID that uniquely identifies the the conversation the feedback is about

        conversation_message_id : EntityIdBase
            The ID that uniquely identifies the message within the conversation the feedback is about

        type : FeedbackType
            The type of feedback

        user_id : typing.Optional[EntityIdBase]
            The ID of the user who is creating the feedback

        text : typing.Optional[str]
            The feedback text

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[Feedback]
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/conversations/feedback",
            method="POST",
            json={
                "feedbackId": convert_and_respect_annotation_metadata(
                    object_=feedback_id, annotation=EntityIdBase, direction="write"
                ),
                "conversationId": convert_and_respect_annotation_metadata(
                    object_=conversation_id, annotation=EntityIdBase, direction="write"
                ),
                "conversationMessageId": convert_and_respect_annotation_metadata(
                    object_=conversation_message_id, annotation=EntityIdBase, direction="write"
                ),
                "userId": convert_and_respect_annotation_metadata(
                    object_=user_id, annotation=EntityIdBase, direction="write"
                ),
                "type": type,
                "text": text,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    Feedback,
                    parse_obj_as(
                        type_=Feedback,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def submit_action_form(
        self,
        conversation_id: str,
        *,
        action_form_id: str,
        parameters: typing.Dict[str, ActionFormRequestParamValue],
        transient_data: typing.Optional[typing.Dict[str, str]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationResponse]:
        """
        Submit a filled out action form.
        Action forms can not be submitted more than once, attempting to do so will result in an error.

        Additionally, form submission is only allowed when the form is the last message in the conversation.
        Forms should be disabled in surface UI if a conversation continues and they remain unsubmitted.

        Parameters
        ----------
        conversation_id : str
            The ID of a conversation the form being submitted belongs to

        action_form_id : str

        parameters : typing.Dict[str, ActionFormRequestParamValue]
            Map of parameter IDs to values provided by the user. All required action fields must be provided.

        transient_data : typing.Optional[typing.Dict[str, str]]
            Transient data which the Maven platform will not persist. This data will only be forwarded to actions taken. For example, one may put in user tokens as transient data.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationResponse]
            Updated Conversation with a new BOT_RESPONSE after taking the action
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/submit-form",
            method="POST",
            json={
                "actionFormId": action_form_id,
                "parameters": convert_and_respect_annotation_metadata(
                    object_=parameters, annotation=typing.Dict[str, ActionFormRequestParamValue], direction="write"
                ),
                "transientData": transient_data,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationResponse,
                    parse_obj_as(
                        type_=ConversationResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def add_conversation_metadata(
        self,
        conversation_id: str,
        *,
        request: typing.Dict[str, str],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[typing.Dict[str, str]]:
        """
        Replaced by `updateConversationMetadata`.

        Adds metadata to an existing conversation. If a metadata field already exists, it will be overwritten.

        Parameters
        ----------
        conversation_id : str
            The ID of a conversation the metadata being added belongs to

        request : typing.Dict[str, str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[typing.Dict[str, str]]
            The metadata of the conversation
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/metadata",
            method="POST",
            json=request,
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    typing.Dict[str, str],
                    parse_obj_as(
                        type_=typing.Dict[str, str],  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def update_conversation_metadata(
        self,
        conversation_id: str,
        *,
        values: typing.Dict[str, str],
        app_id: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationMetadata]:
        """
        Update metadata supplied by the calling application for an existing conversation.
        Does not modify metadata saved by other apps.

        If a metadata field already exists for the calling app, it will be overwritten.
        If it does not exist, it will be added. Will not remove metadata fields.

        Returns all metadata saved by any app on the conversation.

        Parameters
        ----------
        conversation_id : str
            The ID of the conversation to modify metadata for

        values : typing.Dict[str, str]
            The metadata values to add to the conversation.

        app_id : typing.Optional[str]
            The App ID of the conversation to modify metadata for. If not provided the ID of the calling app will be used.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationMetadata]
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/conversations/{jsonable_encoder(conversation_id)}/metadata",
            method="PUT",
            json={
                "appId": app_id,
                "values": values,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationMetadata,
                    parse_obj_as(
                        type_=ConversationMetadata,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def search(
        self,
        *,
        sort: typing.Optional[ConversationField] = OMIT,
        filter: typing.Optional[ConversationFilter] = OMIT,
        page: typing.Optional[int] = OMIT,
        size: typing.Optional[int] = OMIT,
        sort_desc: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationsResponse]:
        """
        Search conversations

        Parameters
        ----------
        sort : typing.Optional[ConversationField]

        filter : typing.Optional[ConversationFilter]

        page : typing.Optional[int]
            Page number to return, defaults to 0

        size : typing.Optional[int]
            The size of the page to return, defaults to 20

        sort_desc : typing.Optional[bool]
            Whether to sort descending, defaults to true

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[ConversationsResponse]
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/conversations/search",
            method="POST",
            json={
                "sort": sort,
                "filter": convert_and_respect_annotation_metadata(
                    object_=filter, annotation=ConversationFilter, direction="write"
                ),
                "page": page,
                "size": size,
                "sortDesc": sort_desc,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsResponse,
                    parse_obj_as(
                        type_=ConversationsResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    @contextlib.asynccontextmanager
    async def export(
        self,
        *,
        sort: typing.Optional[ConversationField] = OMIT,
        filter: typing.Optional[ConversationFilter] = OMIT,
        page: typing.Optional[int] = OMIT,
        size: typing.Optional[int] = OMIT,
        sort_desc: typing.Optional[bool] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]:
        """
        Export conversations to a CSV file.

        This will output a summary of each conversation that matches the supplied filter. A maximum of 10,000 conversations can be exported at a time.

        For most use cases it is recommended to use the `search` API instead and convert the JSON response to your desired format.
        The CSV format may change over time and should not be relied upon by code consumers.

        Parameters
        ----------
        sort : typing.Optional[ConversationField]

        filter : typing.Optional[ConversationFilter]

        page : typing.Optional[int]
            Page number to return, defaults to 0

        size : typing.Optional[int]
            The size of the page to return, defaults to 20

        sort_desc : typing.Optional[bool]
            Whether to sort descending, defaults to true

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response.

        Returns
        -------
        typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]
            A CSV containing one conversation per row
        """
        async with self._client_wrapper.httpx_client.stream(
            "v1/conversations/export",
            method="POST",
            json={
                "sort": sort,
                "filter": convert_and_respect_annotation_metadata(
                    object_=filter, annotation=ConversationFilter, direction="write"
                ),
                "page": page,
                "size": size,
                "sortDesc": sort_desc,
            },
            request_options=request_options,
            omit=OMIT,
        ) as _response:

            async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]:
                try:
                    if 200 <= _response.status_code < 300:
                        _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None
                        return AsyncHttpResponse(
                            response=_response,
                            data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)),
                        )
                    await _response.aread()
                    if _response.status_code == 404:
                        raise NotFoundError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 400:
                        raise BadRequestError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    if _response.status_code == 500:
                        raise ServerError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                ErrorMessage,
                                parse_obj_as(
                                    type_=ErrorMessage,  # type: ignore
                                    object_=_response.json(),
                                ),
                            ),
                        )
                    _response_json = _response.json()
                except JSONDecodeError:
                    raise ApiError(
                        status_code=_response.status_code, headers=dict(_response.headers), body=_response.text
                    )
                raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

            yield await _stream()

    async def import_simulations(
        self,
        *,
        file: core.File,
        response_config: typing.Optional[ResponseConfig] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[None]:
        """
        Import simulation conversations from a CSV file.

        This CSV format is very simple and only allows for one column: `question`. A header containing this column is required.
        Each row will generate one simulation conversation, using the provided response config, if present.

        This API is offered for backwards compatibility.
        Most API callers should create simulations programmatically to allow for more flexibility.

        Parameters
        ----------
        file : core.File
            See core.File for more documentation

        response_config : typing.Optional[ResponseConfig]
            The response config to use for all of the created simulations.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[None]
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/conversations/import_simulations",
            method="POST",
            data={
                "responseConfig": response_config,
            },
            files={
                "file": file,
            },
            request_options=request_options,
            omit=OMIT,
            force_multipart=True,
        )
        try:
            if 200 <= _response.status_code < 300:
                return AsyncHttpResponse(response=_response, data=None)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def deliver_message(
        self, *, request: DeliverMessageRequest, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[DeliverMessageResponse]:
        """
        Deliver a message to a user or conversation.

        <Warning>
        Currently, messages can only be successfully delivered to conversations with the `ASYNC` capability that are `open`.
        User message delivery is not yet supported.
        </Warning>

        Parameters
        ----------
        request : DeliverMessageRequest

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[DeliverMessageResponse]
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/conversations/deliver-message",
            method="POST",
            json=convert_and_respect_annotation_metadata(
                object_=request, annotation=DeliverMessageRequest, direction="write"
            ),
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    DeliverMessageResponse,
                    parse_obj_as(
                        type_=DeliverMessageResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise ServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        ErrorMessage,
                        parse_obj_as(
                            type_=ErrorMessage,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
