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

import typing
from contextlib import asynccontextmanager, contextmanager
from json.decoder import JSONDecodeError

import httpx
import websockets.exceptions
import websockets.sync.client as websockets_sync_client
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 ..errors.bad_request_error import BadRequestError
from ..errors.conflict_error import ConflictError
from ..errors.forbidden_error import ForbiddenError
from ..errors.gateway_timeout_error import GatewayTimeoutError
from ..errors.internal_server_error import InternalServerError
from ..errors.not_found_error import NotFoundError
from ..errors.unauthorized_error import UnauthorizedError
from ..requests.outbound_call_config import OutboundCallConfigParams
from ..types.basic_error import BasicError
from ..types.conversation_evaluation_result import ConversationEvaluationResult
from ..types.error import Error
from .socket_client import AsyncConversationsSocketClient, ConversationsSocketClient
from .types.conversations_cancel_response import ConversationsCancelResponse
from .types.conversations_extract_data_response import ConversationsExtractDataResponse
from .types.conversations_get_analysis_response import ConversationsGetAnalysisResponse
from .types.conversations_get_response import ConversationsGetResponse
from .types.conversations_list_evaluations_response import ConversationsListEvaluationsResponse
from .types.conversations_list_extractions_response import ConversationsListExtractionsResponse
from .types.conversations_list_response import ConversationsListResponse
from .types.conversations_outbound_call_response import ConversationsOutboundCallResponse
from .types.conversations_sip_outbound_call_response import ConversationsSipOutboundCallResponse

try:
    from websockets.legacy.client import connect as websockets_client_connect  # type: ignore
except ImportError:
    from websockets import connect as websockets_client_connect  # type: ignore

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


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

    def list(
        self,
        *,
        project: typing.Optional[str] = None,
        external_id: typing.Optional[str] = None,
        duration_min: typing.Optional[int] = None,
        duration_max: typing.Optional[int] = None,
        started_at_min: typing.Optional[str] = None,
        started_at_max: typing.Optional[str] = None,
        before: typing.Optional[str] = None,
        after: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationsListResponse]:
        """
        Returns conversations with optional filtering.

        Parameters
        ----------
        project : typing.Optional[str]
            The name of the project to list conversations for.

        external_id : typing.Optional[str]
            Filter by external ID to get a specific conversation.

        duration_min : typing.Optional[int]
            Minimum duration in seconds.

        duration_max : typing.Optional[int]
            Maximum duration in seconds.

        started_at_min : typing.Optional[str]
            Minimum start date/time. Valid examples: `2025-04-17`, `2025-04-17T02:48:52.708Z`

        started_at_max : typing.Optional[str]
            Maximum start date/time. Valid examples: `2025-04-17`, `2025-04-17T02:48:52.708Z`

        before : typing.Optional[str]
            Cursor for pagination (before).

        after : typing.Optional[str]
            Cursor for pagination (after).

        limit : typing.Optional[int]
            Maximum number of conversations to return.

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

        Returns
        -------
        HttpResponse[ConversationsListResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            "conversations",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            params={
                "project": project,
                "external_id": external_id,
                "duration_min": duration_min,
                "duration_max": duration_max,
                "started_at_min": started_at_min,
                "started_at_max": started_at_max,
                "before": before,
                "after": after,
                "limit": limit,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsListResponse,
                    parse_obj_as(
                        type_=ConversationsListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationsGetResponse]:
        """
        Returns a conversation by ID.

        Parameters
        ----------
        id : str
            The ID of the conversation to get.

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

        Returns
        -------
        HttpResponse[ConversationsGetResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsGetResponse,
                    parse_obj_as(
                        type_=ConversationsGetResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 cancel(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationsCancelResponse]:
        """
        Cancels an active conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to cancel.

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

        Returns
        -------
        HttpResponse[ConversationsCancelResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/cancel",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsCancelResponse,
                    parse_obj_as(
                        type_=ConversationsCancelResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 409:
                raise ConflictError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 504:
                raise GatewayTimeoutError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        Error,
                        parse_obj_as(
                            type_=Error,  # 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_analysis(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationsGetAnalysisResponse]:
        """
        Returns an analysis of the specified conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to analyze.

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

        Returns
        -------
        HttpResponse[ConversationsGetAnalysisResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/analysis",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsGetAnalysisResponse,
                    parse_obj_as(
                        type_=ConversationsGetAnalysisResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 list_extractions(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationsListExtractionsResponse]:
        """
        Returns all extractions for a conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to get extractions for.

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

        Returns
        -------
        HttpResponse[ConversationsListExtractionsResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/extractions",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsListExtractionsResponse,
                    parse_obj_as(
                        type_=ConversationsListExtractionsResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 extract_data(
        self, id: str, *, schema_id: str, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationsExtractDataResponse]:
        """
        Extracts data from a conversation using a schema.

        Parameters
        ----------
        id : str
            The ID of the conversation to extract data from.

        schema_id : str
            ID of the extraction schema to use.

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

        Returns
        -------
        HttpResponse[ConversationsExtractDataResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/extractions",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            json={
                "schema_id": schema_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsExtractDataResponse,
                    parse_obj_as(
                        type_=ConversationsExtractDataResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 list_evaluations(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationsListEvaluationsResponse]:
        """
        Returns all evaluations for a conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to get evaluations for.

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

        Returns
        -------
        HttpResponse[ConversationsListEvaluationsResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/evals",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsListEvaluationsResponse,
                    parse_obj_as(
                        type_=ConversationsListEvaluationsResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 evaluate(
        self, id: str, *, prompt_id: str, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[ConversationEvaluationResult]:
        """
        Evaluates a conversation using an evaluation prompt.

        Parameters
        ----------
        id : str
            The ID of the conversation to evaluate.

        prompt_id : str
            ID of the evaluation prompt to use.

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

        Returns
        -------
        HttpResponse[ConversationEvaluationResult]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/evals",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            json={
                "prompt_id": prompt_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationEvaluationResult,
                    parse_obj_as(
                        type_=ConversationEvaluationResult,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 outbound_call(
        self,
        *,
        to_phone_number: str,
        config: typing.Optional[OutboundCallConfigParams] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationsOutboundCallResponse]:
        """
        Initiates a call to a given phone number using Phonic's Twilio account.

        Parameters
        ----------
        to_phone_number : str
            The phone number to call in E.164 format.

        config : typing.Optional[OutboundCallConfigParams]

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

        Returns
        -------
        HttpResponse[ConversationsOutboundCallResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            "conversations/outbound_call",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            json={
                "to_phone_number": to_phone_number,
                "config": convert_and_respect_annotation_metadata(
                    object_=config, annotation=OutboundCallConfigParams, direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsOutboundCallResponse,
                    parse_obj_as(
                        type_=ConversationsOutboundCallResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 sip_outbound_call(
        self,
        *,
        sip_address: str,
        from_phone_number: str,
        to_phone_number: str,
        token: typing.Optional[str] = None,
        downstream_websocket_url: typing.Optional[str] = None,
        sip_auth_username: typing.Optional[str] = None,
        sip_auth_password: typing.Optional[str] = None,
        config: typing.Optional[OutboundCallConfigParams] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[ConversationsSipOutboundCallResponse]:
        """
        Initiates a SIP outbound call using user-supplied SIP credentials in headers.

        Parameters
        ----------
        sip_address : str
            SIP address of the user's SIP trunk. Required.

        from_phone_number : str
            Caller ID phone number in E.164 format.

        to_phone_number : str
            Destination phone number in E.164 format.

        token : typing.Optional[str]
            Optional session token used for STS authorization.

        downstream_websocket_url : typing.Optional[str]
            Optional downstream STS WebSocket URL to override the default.

        sip_auth_username : typing.Optional[str]
            SIP auth username, if your provider requires it.

        sip_auth_password : typing.Optional[str]
            SIP auth password, if your provider requires it.

        config : typing.Optional[OutboundCallConfigParams]

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

        Returns
        -------
        HttpResponse[ConversationsSipOutboundCallResponse]
            Success response
        """
        _response = self._client_wrapper.httpx_client.request(
            "conversations/sip/outbound_call",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            params={
                "token": token,
                "downstream_websocket_url": downstream_websocket_url,
            },
            json={
                "from_phone_number": from_phone_number,
                "to_phone_number": to_phone_number,
                "config": convert_and_respect_annotation_metadata(
                    object_=config, annotation=OutboundCallConfigParams, direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
                "X-Sip-Address": str(sip_address) if sip_address is not None else None,
                "X-Sip-Auth-Username": str(sip_auth_username) if sip_auth_username is not None else None,
                "X-Sip-Auth-Password": str(sip_auth_password) if sip_auth_password is not None else None,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsSipOutboundCallResponse,
                    parse_obj_as(
                        type_=ConversationsSipOutboundCallResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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)

    @contextmanager
    def connect(
        self,
        *,
        downstream_websocket_url: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.Iterator[ConversationsSocketClient]:
        """
        Main STS WebSocket channel for real-time voice conversations

        Parameters
        ----------
        downstream_websocket_url : typing.Optional[str]
            Custom downstream WebSocket URL

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

        Returns
        -------
        ConversationsSocketClient
        """
        ws_url = self._client_wrapper.get_environment().production + "/v1/sts/ws"
        query_params = httpx.QueryParams()
        if downstream_websocket_url is not None:
            query_params = query_params.add("downstream_websocket_url", downstream_websocket_url)
        ws_url = ws_url + f"?{query_params}"
        headers = self._client_wrapper.get_headers()
        if request_options and "additional_headers" in request_options:
            headers.update(request_options["additional_headers"])
        try:
            with websockets_sync_client.connect(ws_url, additional_headers=headers) as protocol:
                yield ConversationsSocketClient(websocket=protocol)
        except websockets.exceptions.InvalidStatusCode as exc:
            status_code: int = exc.status_code
            if status_code == 401:
                raise ApiError(
                    status_code=status_code,
                    headers=dict(headers),
                    body="Websocket initialized with invalid credentials.",
                )
            raise ApiError(
                status_code=status_code,
                headers=dict(headers),
                body="Unexpected error when initializing websocket connection.",
            )


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

    async def list(
        self,
        *,
        project: typing.Optional[str] = None,
        external_id: typing.Optional[str] = None,
        duration_min: typing.Optional[int] = None,
        duration_max: typing.Optional[int] = None,
        started_at_min: typing.Optional[str] = None,
        started_at_max: typing.Optional[str] = None,
        before: typing.Optional[str] = None,
        after: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationsListResponse]:
        """
        Returns conversations with optional filtering.

        Parameters
        ----------
        project : typing.Optional[str]
            The name of the project to list conversations for.

        external_id : typing.Optional[str]
            Filter by external ID to get a specific conversation.

        duration_min : typing.Optional[int]
            Minimum duration in seconds.

        duration_max : typing.Optional[int]
            Maximum duration in seconds.

        started_at_min : typing.Optional[str]
            Minimum start date/time. Valid examples: `2025-04-17`, `2025-04-17T02:48:52.708Z`

        started_at_max : typing.Optional[str]
            Maximum start date/time. Valid examples: `2025-04-17`, `2025-04-17T02:48:52.708Z`

        before : typing.Optional[str]
            Cursor for pagination (before).

        after : typing.Optional[str]
            Cursor for pagination (after).

        limit : typing.Optional[int]
            Maximum number of conversations to return.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsListResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            "conversations",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            params={
                "project": project,
                "external_id": external_id,
                "duration_min": duration_min,
                "duration_max": duration_max,
                "started_at_min": started_at_min,
                "started_at_max": started_at_max,
                "before": before,
                "after": after,
                "limit": limit,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsListResponse,
                    parse_obj_as(
                        type_=ConversationsListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationsGetResponse]:
        """
        Returns a conversation by ID.

        Parameters
        ----------
        id : str
            The ID of the conversation to get.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsGetResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsGetResponse,
                    parse_obj_as(
                        type_=ConversationsGetResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 cancel(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationsCancelResponse]:
        """
        Cancels an active conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to cancel.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsCancelResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/cancel",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsCancelResponse,
                    parse_obj_as(
                        type_=ConversationsCancelResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 409:
                raise ConflictError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 504:
                raise GatewayTimeoutError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        Error,
                        parse_obj_as(
                            type_=Error,  # 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_analysis(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationsGetAnalysisResponse]:
        """
        Returns an analysis of the specified conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to analyze.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsGetAnalysisResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/analysis",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsGetAnalysisResponse,
                    parse_obj_as(
                        type_=ConversationsGetAnalysisResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 list_extractions(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationsListExtractionsResponse]:
        """
        Returns all extractions for a conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to get extractions for.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsListExtractionsResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/extractions",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsListExtractionsResponse,
                    parse_obj_as(
                        type_=ConversationsListExtractionsResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 extract_data(
        self, id: str, *, schema_id: str, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationsExtractDataResponse]:
        """
        Extracts data from a conversation using a schema.

        Parameters
        ----------
        id : str
            The ID of the conversation to extract data from.

        schema_id : str
            ID of the extraction schema to use.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsExtractDataResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/extractions",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            json={
                "schema_id": schema_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsExtractDataResponse,
                    parse_obj_as(
                        type_=ConversationsExtractDataResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 list_evaluations(
        self, id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationsListEvaluationsResponse]:
        """
        Returns all evaluations for a conversation.

        Parameters
        ----------
        id : str
            The ID of the conversation to get evaluations for.

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

        Returns
        -------
        AsyncHttpResponse[ConversationsListEvaluationsResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/evals",
            base_url=self._client_wrapper.get_environment().base,
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsListEvaluationsResponse,
                    parse_obj_as(
                        type_=ConversationsListEvaluationsResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 evaluate(
        self, id: str, *, prompt_id: str, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[ConversationEvaluationResult]:
        """
        Evaluates a conversation using an evaluation prompt.

        Parameters
        ----------
        id : str
            The ID of the conversation to evaluate.

        prompt_id : str
            ID of the evaluation prompt to use.

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

        Returns
        -------
        AsyncHttpResponse[ConversationEvaluationResult]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"conversations/{jsonable_encoder(id)}/evals",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            json={
                "prompt_id": prompt_id,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationEvaluationResult,
                    parse_obj_as(
                        type_=ConversationEvaluationResult,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise ForbiddenError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 outbound_call(
        self,
        *,
        to_phone_number: str,
        config: typing.Optional[OutboundCallConfigParams] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationsOutboundCallResponse]:
        """
        Initiates a call to a given phone number using Phonic's Twilio account.

        Parameters
        ----------
        to_phone_number : str
            The phone number to call in E.164 format.

        config : typing.Optional[OutboundCallConfigParams]

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

        Returns
        -------
        AsyncHttpResponse[ConversationsOutboundCallResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            "conversations/outbound_call",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            json={
                "to_phone_number": to_phone_number,
                "config": convert_and_respect_annotation_metadata(
                    object_=config, annotation=OutboundCallConfigParams, direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsOutboundCallResponse,
                    parse_obj_as(
                        type_=ConversationsOutboundCallResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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 sip_outbound_call(
        self,
        *,
        sip_address: str,
        from_phone_number: str,
        to_phone_number: str,
        token: typing.Optional[str] = None,
        downstream_websocket_url: typing.Optional[str] = None,
        sip_auth_username: typing.Optional[str] = None,
        sip_auth_password: typing.Optional[str] = None,
        config: typing.Optional[OutboundCallConfigParams] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[ConversationsSipOutboundCallResponse]:
        """
        Initiates a SIP outbound call using user-supplied SIP credentials in headers.

        Parameters
        ----------
        sip_address : str
            SIP address of the user's SIP trunk. Required.

        from_phone_number : str
            Caller ID phone number in E.164 format.

        to_phone_number : str
            Destination phone number in E.164 format.

        token : typing.Optional[str]
            Optional session token used for STS authorization.

        downstream_websocket_url : typing.Optional[str]
            Optional downstream STS WebSocket URL to override the default.

        sip_auth_username : typing.Optional[str]
            SIP auth username, if your provider requires it.

        sip_auth_password : typing.Optional[str]
            SIP auth password, if your provider requires it.

        config : typing.Optional[OutboundCallConfigParams]

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

        Returns
        -------
        AsyncHttpResponse[ConversationsSipOutboundCallResponse]
            Success response
        """
        _response = await self._client_wrapper.httpx_client.request(
            "conversations/sip/outbound_call",
            base_url=self._client_wrapper.get_environment().base,
            method="POST",
            params={
                "token": token,
                "downstream_websocket_url": downstream_websocket_url,
            },
            json={
                "from_phone_number": from_phone_number,
                "to_phone_number": to_phone_number,
                "config": convert_and_respect_annotation_metadata(
                    object_=config, annotation=OutboundCallConfigParams, direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
                "X-Sip-Address": str(sip_address) if sip_address is not None else None,
                "X-Sip-Auth-Username": str(sip_auth_username) if sip_auth_username is not None else None,
                "X-Sip-Auth-Password": str(sip_auth_password) if sip_auth_password is not None else None,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    ConversationsSipOutboundCallResponse,
                    parse_obj_as(
                        type_=ConversationsSipOutboundCallResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise BadRequestError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Optional[typing.Any],
                        parse_obj_as(
                            type_=typing.Optional[typing.Any],  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 500:
                raise InternalServerError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        BasicError,
                        parse_obj_as(
                            type_=BasicError,  # 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)

    @asynccontextmanager
    async def connect(
        self,
        *,
        downstream_websocket_url: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.AsyncIterator[AsyncConversationsSocketClient]:
        """
        Main STS WebSocket channel for real-time voice conversations

        Parameters
        ----------
        downstream_websocket_url : typing.Optional[str]
            Custom downstream WebSocket URL

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

        Returns
        -------
        AsyncConversationsSocketClient
        """
        ws_url = self._client_wrapper.get_environment().production + "/v1/sts/ws"
        query_params = httpx.QueryParams()
        if downstream_websocket_url is not None:
            query_params = query_params.add("downstream_websocket_url", downstream_websocket_url)
        ws_url = ws_url + f"?{query_params}"
        headers = self._client_wrapper.get_headers()
        if request_options and "additional_headers" in request_options:
            headers.update(request_options["additional_headers"])
        try:
            async with websockets_client_connect(ws_url, extra_headers=headers) as protocol:
                yield AsyncConversationsSocketClient(websocket=protocol)
        except websockets.exceptions.InvalidStatusCode as exc:
            status_code: int = exc.status_code
            if status_code == 401:
                raise ApiError(
                    status_code=status_code,
                    headers=dict(headers),
                    body="Websocket initialized with invalid credentials.",
                )
            raise ApiError(
                status_code=status_code,
                headers=dict(headers),
                body="Unexpected error when initializing websocket connection.",
            )
