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

import os
import typing
import urllib.parse
from json.decoder import JSONDecodeError

import httpx

from .batches.client import AsyncBatchesClient, BatchesClient
from .calls.client import AsyncCallsClient, CallsClient
from .commons.errors.server_error import ServerError
from .commons.errors.unauthorized_error import UnauthorizedError
from .commons.types.error_body import ErrorBody
from .commons.types.phone_number import PhoneNumber
from .commons.types.tools import Tools
from .commons.types.voice_id import VoiceId
from .commons.types.webhook import Webhook
from .core.api_error import ApiError
from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from .core.jsonable_encoder import jsonable_encoder
from .core.pydantic_utilities import pydantic_v1
from .core.remove_none_from_dict import remove_none_from_dict
from .core.request_options import RequestOptions
from .environment import BlandAIEnvironment
from .inbound_numbers.client import AsyncInboundNumbersClient, InboundNumbersClient
from .outbound_numbers.client import AsyncOutboundNumbersClient, OutboundNumbersClient
from .sms.client import AsyncSmsClient, SmsClient
from .types.dynamic_data import DynamicData
from .types.interruption_threshold import InterruptionThreshold
from .types.model_enum import ModelEnum
from .types.prounciation_guide import ProunciationGuide
from .types.send_call_response import SendCallResponse
from .types.temperature import Temperature
from .types.transfer_list import TransferList
from .types.voicemail_action import VoicemailAction
from .voices.client import AsyncVoicesClient, VoicesClient
from .web_agents.client import AsyncWebAgentsClient, WebAgentsClient

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


class BlandAI:
    """
    Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propogate to these functions.

    Parameters
    ----------
    base_url : typing.Optional[str]
        The base url to use for requests from the client.

    environment : BlandAIEnvironment
        The environment to use for requests from the client. from .environment import BlandAIEnvironment



        Defaults to BlandAIEnvironment.PRODUCTION



    api_key : typing.Optional[str]
    timeout : typing.Optional[float]
        The timeout to be used, in seconds, for requests by default the timeout is 60 seconds, unless a custom httpx client is used, in which case a default is not set.

    follow_redirects : typing.Optional[bool]
        Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in.

    httpx_client : typing.Optional[httpx.Client]
        The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.

    Examples
    --------
    from bland.client import BlandAI

    client = BlandAI(
        api_key="YOUR_API_KEY",
    )
    """

    def __init__(
        self,
        *,
        base_url: typing.Optional[str] = None,
        environment: BlandAIEnvironment = BlandAIEnvironment.PRODUCTION,
        api_key: typing.Optional[str] = os.getenv("BLAND_API_KEY"),
        timeout: typing.Optional[float] = None,
        follow_redirects: typing.Optional[bool] = True,
        httpx_client: typing.Optional[httpx.Client] = None,
    ):
        _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None
        if api_key is None:
            raise ApiError(body="The client must be instantiated be either passing in api_key or setting BLAND_API_KEY")
        self._client_wrapper = SyncClientWrapper(
            base_url=_get_base_url(base_url=base_url, environment=environment),
            api_key=api_key,
            httpx_client=httpx_client
            if httpx_client is not None
            else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
            if follow_redirects is not None
            else httpx.Client(timeout=_defaulted_timeout),
            timeout=_defaulted_timeout,
        )
        self.batches = BatchesClient(client_wrapper=self._client_wrapper)
        self.calls = CallsClient(client_wrapper=self._client_wrapper)
        self.inbound_numbers = InboundNumbersClient(client_wrapper=self._client_wrapper)
        self.outbound_numbers = OutboundNumbersClient(client_wrapper=self._client_wrapper)
        self.sms = SmsClient(client_wrapper=self._client_wrapper)
        self.voices = VoicesClient(client_wrapper=self._client_wrapper)
        self.web_agents = WebAgentsClient(client_wrapper=self._client_wrapper)

    def call(
        self,
        *,
        phone_number: PhoneNumber,
        task: str,
        temperature: Temperature,
        tools: Tools,
        interruption_threshold: InterruptionThreshold,
        transfer_list: typing.Optional[TransferList] = OMIT,
        model: typing.Optional[ModelEnum] = OMIT,
        pronunciation_guide: typing.Optional[ProunciationGuide] = OMIT,
        transfer_phone_number: typing.Optional[PhoneNumber] = OMIT,
        answered_by_enabled: typing.Optional[bool] = OMIT,
        from_: typing.Optional[str] = OMIT,
        reduce_latency: typing.Optional[bool] = OMIT,
        voice_id: typing.Optional[VoiceId] = OMIT,
        voice_preset_id: typing.Optional[str] = OMIT,
        start_time: typing.Optional[str] = OMIT,
        webhook: typing.Optional[Webhook] = OMIT,
        wait_for_greeting: typing.Optional[bool] = OMIT,
        first_sentence: typing.Optional[str] = OMIT,
        record: typing.Optional[bool] = OMIT,
        voice_settings: typing.Optional[bool] = OMIT,
        language: typing.Optional[str] = OMIT,
        max_duration: typing.Optional[int] = OMIT,
        voicemail_action: typing.Optional[VoicemailAction] = OMIT,
        amd: typing.Optional[bool] = OMIT,
        request_data: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        dynamic_data: typing.Optional[typing.Sequence[DynamicData]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> SendCallResponse:
        """
        Send an AI phone call with a custom objective and actions.

        Parameters
        ----------
        phone_number : PhoneNumber
            The phone number to call. Country code defaults to `+1` (US) if not specified.

            Formatting is flexible, however for the most predictable results use the **[E.164](https://www.twilio.com/docs/glossary/what-e164#examples-of-e164-numbers)** format.

        task : str
            Provide instructions, relevant information, and examples of the ideal conversation flow.

        temperature : Temperature
            A value between 0 and 1 that controls the randomness of the LLM. 0 will cause more deterministic outputs while 1 will cause more random.

        tools : Tools

        interruption_threshold : InterruptionThreshold
            When you increase the interruption latency, you force the AI phone agent to listen longer before responding. In practice, increasing the threshold results in less interruptions and more latency.

            Try setting the threshold to `500` milliseconds. You'll encounter higher latency, but you'll be interrupted much less frequently.

        transfer_list : typing.Optional[TransferList]
            Give your agent the ability to transfer calls to a set of phone numbers.

            Overrides `transfer_phone_number` if a `transfer_list.default` is specified.

            Will default to `transfer_list.default`, or the chosen phone number.

        model : typing.Optional[ModelEnum]
            Select a model to use for your call.

            In nearly all cases, `enhanced` is the best choice for now.

        pronunciation_guide : typing.Optional[ProunciationGuide]
            The pronunciation guide is an `array` of `objects` that guides the LLM on how to say specific words. This is great for situations with complicated terms or names.

        transfer_phone_number : typing.Optional[PhoneNumber]
            A phone number that the agent can transfer to under specific conditions - such as being asked to speak to a human or supervisor.

        answered_by_enabled : typing.Optional[bool]
            Enables machine detection when the call starts to determine whether the call was answered by a person or a voicemail.

            Best Practices (when enabled) -

                - Since the determination is made at the beginning of the call, use `wait_for_greeting` to try and coax a human response.

                - If combined with first_sentence, try wording it so the person answering says something back - ex. `"Hello?"`

            Price - `$0.02` per call, however there is no charge for unanswered calls or calls that failed to send.

        from_ : typing.Optional[str]
            Specify a purchased Outbound Number to call from. Country code is required, spaces or parentheses must be excluded.

            By default, calls are initiated from a separate pool of numbers owned by Bland.

        reduce_latency : typing.Optional[bool]
            Reducing latency means that the agent will generate responses more quickly and have less of a delay. This must be set to `true` when using Voice Clones.

        voice_id : typing.Optional[VoiceId]
            Determines the voice of the AI agent, in conjunction with `reduce_latency`.

            Use the `GET /v1/voices endpoint` to see a full list of your available voices.

            To create your own Voice Clone, visit our [Custom Voice Cloning page](https://app.bland.ai/home?page=voice-clone).

        voice_preset_id : typing.Optional[str]
            Use a voice preset instead of specifying individual voice settings.

            To create a voice preset, see [Create a Voice Preset](https://docs.bland.ai/api-v1/post/voices).

        start_time : typing.Optional[str]
            The time you want the call to start. If you don't specify a time (or the time is in the past), the call will send immediately.

            Set your time in the format `YYYY-MM-DD HH:MM:SS -HH:MM` (ex. `2021-01-01 12:00:00 -05:00`).

            The timezone is optional, and defaults to `UTC` if not specified.

            Note - Scheduled calls can be cancelled with the `POST /v1/calls/:call_id/stop` endpoint.

        webhook : typing.Optional[Webhook]
            The webhook should be a http / https callback url. We will send the call_id and transcript to this URL after the call completes. This can be useful if you want to have real time notifications when calls finish.

        wait_for_greeting : typing.Optional[bool]
            Should the AI speak first or wait for someone else to talk?

            Creates more realistic conversations when answered with “Hello?”, “This is {name} speaking.” and so on.

                - When `false`, The AI starts speaking shortly after the call is answered.

                - When `true`, The AI will wait for the answerer to speak.

        first_sentence : typing.Optional[str]
            A phrase that your call will start with instead of a generating one on the fly. This works both with and without `wait_for_greeting`. Can be more than one sentence, but must be less than 200 characters.

        record : typing.Optional[bool]
            To record your phone call, set `record` to true. When your call completes, you can access through the `recording_url` field in the call details or your webhook.

        voice_settings : typing.Optional[bool]
            Should the AI speak first or wait for someone else to talk? Creates more realistic conversations when answered with “Hello?”, “This is {name} speaking.” and so on. When `false` - The AI starts speaking shortly after the call is answered. When `true` - The AI will wait for the answerer to speak.

        language : typing.Optional[str]
            Select a supported language of your choice. Optimizes every part of our API for that language - transcription, speech, and other inner workings.

            Supported Languages and their codes -

            - English - `eng`
            - Spanish - `esp`
            - French - `fre`
            - Polish - `pol`

        max_duration : typing.Optional[int]
            Set the longest you want the call to possibly go in minutes. After the max_duration minutes have passed, the call will automatically end. Example Values - `20, 2`

        voicemail_action : typing.Optional[VoicemailAction]
            Voicemail action tells the AI what to do when encountering a voicemail. This has 96% accuracy. There is no such thing as a perfect VM detection, but this gets close.

            The default value is hangup to save money and keep most users in compliance.

            Note - **Leaving voicemails is strongly discouraged.**

        amd : typing.Optional[bool]
            AMD mode helps our AI navigate phone trees and IVR systems. If you know your call will hit an automated system you should switch it on.

            NOTE - AMD mode causes increased delay for the first response, even if answered by a human. Highly recommended to set to `false` in the majority of cases.

        request_data : typing.Optional[typing.Dict[str, typing.Any]]
            When you want your AI to “know” a specific fact - like the caller's name or other relevant context.

            The AI agent will be aware of both the key names as well as their corresponding values.

        dynamic_data : typing.Optional[typing.Sequence[DynamicData]]
            Make dynamic requests to external APIs and use the data in your AI's responses.

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

        Returns
        -------
        SendCallResponse

        Examples
        --------
        from bland import PronunciationObject
        from bland.client import BlandAI

        client = BlandAI(
            api_key="YOUR_API_KEY",
        )
        client.call(
            phone_number="29382721828",
            task="Would love for you to check out our AI API!",
            temperature=0.5,
            transfer_list={
                "default": "+1234567890",
                "sales": "+1234567890",
                "support": "+1234567890",
                "billing": "+1234567890",
            },
            model="enhanced",
            pronunciation_guide=[
                PronunciationObject(
                    word="API",
                    pronunciation="A P I",
                ),
                PronunciationObject(
                    word="AI",
                    pronunciation="A I",
                ),
            ],
        )
        """
        _request: typing.Dict[str, typing.Any] = {
            "phone_number": phone_number,
            "task": task,
            "temperature": temperature,
            "tools": tools,
            "interruption_threshold": interruption_threshold,
        }
        if transfer_list is not OMIT:
            _request["transfer_list"] = transfer_list
        if model is not OMIT:
            _request["model"] = model
        if pronunciation_guide is not OMIT:
            _request["pronunciation_guide"] = pronunciation_guide
        if transfer_phone_number is not OMIT:
            _request["transfer_phone_number"] = transfer_phone_number
        if answered_by_enabled is not OMIT:
            _request["answered_by_enabled"] = answered_by_enabled
        if from_ is not OMIT:
            _request["from"] = from_
        if reduce_latency is not OMIT:
            _request["reduce_latency"] = reduce_latency
        if voice_id is not OMIT:
            _request["voice_id"] = voice_id
        if voice_preset_id is not OMIT:
            _request["voice_preset_id"] = voice_preset_id
        if start_time is not OMIT:
            _request["start_time"] = start_time
        if webhook is not OMIT:
            _request["webhook"] = webhook
        if wait_for_greeting is not OMIT:
            _request["wait_for_greeting"] = wait_for_greeting
        if first_sentence is not OMIT:
            _request["first_sentence"] = first_sentence
        if record is not OMIT:
            _request["record"] = record
        if voice_settings is not OMIT:
            _request["voice_settings"] = voice_settings
        if language is not OMIT:
            _request["language"] = language
        if max_duration is not OMIT:
            _request["max_duration"] = max_duration
        if voicemail_action is not OMIT:
            _request["voicemail_action"] = voicemail_action
        if amd is not OMIT:
            _request["amd"] = amd
        if request_data is not OMIT:
            _request["request_data"] = request_data
        if dynamic_data is not OMIT:
            _request["dynamic_data"] = dynamic_data
        _response = self._client_wrapper.httpx_client.request(
            method="POST",
            url=urllib.parse.urljoin(f"{self._client_wrapper.get_base_url()}/", "v1/calls"),
            params=jsonable_encoder(
                request_options.get("additional_query_parameters") if request_options is not None else None
            ),
            json=jsonable_encoder(_request)
            if request_options is None or request_options.get("additional_body_parameters") is None
            else {
                **jsonable_encoder(_request),
                **(jsonable_encoder(remove_none_from_dict(request_options.get("additional_body_parameters", {})))),
            },
            headers=jsonable_encoder(
                remove_none_from_dict(
                    {
                        **self._client_wrapper.get_headers(),
                        **(request_options.get("additional_headers", {}) if request_options is not None else {}),
                    }
                )
            ),
            timeout=request_options.get("timeout_in_seconds")
            if request_options is not None and request_options.get("timeout_in_seconds") is not None
            else self._client_wrapper.get_timeout(),
            retries=0,
            max_retries=request_options.get("max_retries") if request_options is not None else 0,  # type: ignore
        )
        if 200 <= _response.status_code < 300:
            return pydantic_v1.parse_obj_as(SendCallResponse, _response.json())  # type: ignore
        if _response.status_code == 500:
            raise ServerError(pydantic_v1.parse_obj_as(ErrorBody, _response.json()))  # type: ignore
        if _response.status_code == 401:
            raise UnauthorizedError(pydantic_v1.parse_obj_as(ErrorBody, _response.json()))  # type: ignore
        try:
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)


class AsyncBlandAI:
    """
    Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propogate to these functions.

    Parameters
    ----------
    base_url : typing.Optional[str]
        The base url to use for requests from the client.

    environment : BlandAIEnvironment
        The environment to use for requests from the client. from .environment import BlandAIEnvironment



        Defaults to BlandAIEnvironment.PRODUCTION



    api_key : typing.Optional[str]
    timeout : typing.Optional[float]
        The timeout to be used, in seconds, for requests by default the timeout is 60 seconds, unless a custom httpx client is used, in which case a default is not set.

    follow_redirects : typing.Optional[bool]
        Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in.

    httpx_client : typing.Optional[httpx.AsyncClient]
        The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.

    Examples
    --------
    from bland.client import AsyncBlandAI

    client = AsyncBlandAI(
        api_key="YOUR_API_KEY",
    )
    """

    def __init__(
        self,
        *,
        base_url: typing.Optional[str] = None,
        environment: BlandAIEnvironment = BlandAIEnvironment.PRODUCTION,
        api_key: typing.Optional[str] = os.getenv("BLAND_API_KEY"),
        timeout: typing.Optional[float] = None,
        follow_redirects: typing.Optional[bool] = True,
        httpx_client: typing.Optional[httpx.AsyncClient] = None,
    ):
        _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None
        if api_key is None:
            raise ApiError(body="The client must be instantiated be either passing in api_key or setting BLAND_API_KEY")
        self._client_wrapper = AsyncClientWrapper(
            base_url=_get_base_url(base_url=base_url, environment=environment),
            api_key=api_key,
            httpx_client=httpx_client
            if httpx_client is not None
            else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
            if follow_redirects is not None
            else httpx.AsyncClient(timeout=_defaulted_timeout),
            timeout=_defaulted_timeout,
        )
        self.batches = AsyncBatchesClient(client_wrapper=self._client_wrapper)
        self.calls = AsyncCallsClient(client_wrapper=self._client_wrapper)
        self.inbound_numbers = AsyncInboundNumbersClient(client_wrapper=self._client_wrapper)
        self.outbound_numbers = AsyncOutboundNumbersClient(client_wrapper=self._client_wrapper)
        self.sms = AsyncSmsClient(client_wrapper=self._client_wrapper)
        self.voices = AsyncVoicesClient(client_wrapper=self._client_wrapper)
        self.web_agents = AsyncWebAgentsClient(client_wrapper=self._client_wrapper)

    async def call(
        self,
        *,
        phone_number: PhoneNumber,
        task: str,
        temperature: Temperature,
        tools: Tools,
        interruption_threshold: InterruptionThreshold,
        transfer_list: typing.Optional[TransferList] = OMIT,
        model: typing.Optional[ModelEnum] = OMIT,
        pronunciation_guide: typing.Optional[ProunciationGuide] = OMIT,
        transfer_phone_number: typing.Optional[PhoneNumber] = OMIT,
        answered_by_enabled: typing.Optional[bool] = OMIT,
        from_: typing.Optional[str] = OMIT,
        reduce_latency: typing.Optional[bool] = OMIT,
        voice_id: typing.Optional[VoiceId] = OMIT,
        voice_preset_id: typing.Optional[str] = OMIT,
        start_time: typing.Optional[str] = OMIT,
        webhook: typing.Optional[Webhook] = OMIT,
        wait_for_greeting: typing.Optional[bool] = OMIT,
        first_sentence: typing.Optional[str] = OMIT,
        record: typing.Optional[bool] = OMIT,
        voice_settings: typing.Optional[bool] = OMIT,
        language: typing.Optional[str] = OMIT,
        max_duration: typing.Optional[int] = OMIT,
        voicemail_action: typing.Optional[VoicemailAction] = OMIT,
        amd: typing.Optional[bool] = OMIT,
        request_data: typing.Optional[typing.Dict[str, typing.Any]] = OMIT,
        dynamic_data: typing.Optional[typing.Sequence[DynamicData]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> SendCallResponse:
        """
        Send an AI phone call with a custom objective and actions.

        Parameters
        ----------
        phone_number : PhoneNumber
            The phone number to call. Country code defaults to `+1` (US) if not specified.

            Formatting is flexible, however for the most predictable results use the **[E.164](https://www.twilio.com/docs/glossary/what-e164#examples-of-e164-numbers)** format.

        task : str
            Provide instructions, relevant information, and examples of the ideal conversation flow.

        temperature : Temperature
            A value between 0 and 1 that controls the randomness of the LLM. 0 will cause more deterministic outputs while 1 will cause more random.

        tools : Tools

        interruption_threshold : InterruptionThreshold
            When you increase the interruption latency, you force the AI phone agent to listen longer before responding. In practice, increasing the threshold results in less interruptions and more latency.

            Try setting the threshold to `500` milliseconds. You'll encounter higher latency, but you'll be interrupted much less frequently.

        transfer_list : typing.Optional[TransferList]
            Give your agent the ability to transfer calls to a set of phone numbers.

            Overrides `transfer_phone_number` if a `transfer_list.default` is specified.

            Will default to `transfer_list.default`, or the chosen phone number.

        model : typing.Optional[ModelEnum]
            Select a model to use for your call.

            In nearly all cases, `enhanced` is the best choice for now.

        pronunciation_guide : typing.Optional[ProunciationGuide]
            The pronunciation guide is an `array` of `objects` that guides the LLM on how to say specific words. This is great for situations with complicated terms or names.

        transfer_phone_number : typing.Optional[PhoneNumber]
            A phone number that the agent can transfer to under specific conditions - such as being asked to speak to a human or supervisor.

        answered_by_enabled : typing.Optional[bool]
            Enables machine detection when the call starts to determine whether the call was answered by a person or a voicemail.

            Best Practices (when enabled) -

                - Since the determination is made at the beginning of the call, use `wait_for_greeting` to try and coax a human response.

                - If combined with first_sentence, try wording it so the person answering says something back - ex. `"Hello?"`

            Price - `$0.02` per call, however there is no charge for unanswered calls or calls that failed to send.

        from_ : typing.Optional[str]
            Specify a purchased Outbound Number to call from. Country code is required, spaces or parentheses must be excluded.

            By default, calls are initiated from a separate pool of numbers owned by Bland.

        reduce_latency : typing.Optional[bool]
            Reducing latency means that the agent will generate responses more quickly and have less of a delay. This must be set to `true` when using Voice Clones.

        voice_id : typing.Optional[VoiceId]
            Determines the voice of the AI agent, in conjunction with `reduce_latency`.

            Use the `GET /v1/voices endpoint` to see a full list of your available voices.

            To create your own Voice Clone, visit our [Custom Voice Cloning page](https://app.bland.ai/home?page=voice-clone).

        voice_preset_id : typing.Optional[str]
            Use a voice preset instead of specifying individual voice settings.

            To create a voice preset, see [Create a Voice Preset](https://docs.bland.ai/api-v1/post/voices).

        start_time : typing.Optional[str]
            The time you want the call to start. If you don't specify a time (or the time is in the past), the call will send immediately.

            Set your time in the format `YYYY-MM-DD HH:MM:SS -HH:MM` (ex. `2021-01-01 12:00:00 -05:00`).

            The timezone is optional, and defaults to `UTC` if not specified.

            Note - Scheduled calls can be cancelled with the `POST /v1/calls/:call_id/stop` endpoint.

        webhook : typing.Optional[Webhook]
            The webhook should be a http / https callback url. We will send the call_id and transcript to this URL after the call completes. This can be useful if you want to have real time notifications when calls finish.

        wait_for_greeting : typing.Optional[bool]
            Should the AI speak first or wait for someone else to talk?

            Creates more realistic conversations when answered with “Hello?”, “This is {name} speaking.” and so on.

                - When `false`, The AI starts speaking shortly after the call is answered.

                - When `true`, The AI will wait for the answerer to speak.

        first_sentence : typing.Optional[str]
            A phrase that your call will start with instead of a generating one on the fly. This works both with and without `wait_for_greeting`. Can be more than one sentence, but must be less than 200 characters.

        record : typing.Optional[bool]
            To record your phone call, set `record` to true. When your call completes, you can access through the `recording_url` field in the call details or your webhook.

        voice_settings : typing.Optional[bool]
            Should the AI speak first or wait for someone else to talk? Creates more realistic conversations when answered with “Hello?”, “This is {name} speaking.” and so on. When `false` - The AI starts speaking shortly after the call is answered. When `true` - The AI will wait for the answerer to speak.

        language : typing.Optional[str]
            Select a supported language of your choice. Optimizes every part of our API for that language - transcription, speech, and other inner workings.

            Supported Languages and their codes -

            - English - `eng`
            - Spanish - `esp`
            - French - `fre`
            - Polish - `pol`

        max_duration : typing.Optional[int]
            Set the longest you want the call to possibly go in minutes. After the max_duration minutes have passed, the call will automatically end. Example Values - `20, 2`

        voicemail_action : typing.Optional[VoicemailAction]
            Voicemail action tells the AI what to do when encountering a voicemail. This has 96% accuracy. There is no such thing as a perfect VM detection, but this gets close.

            The default value is hangup to save money and keep most users in compliance.

            Note - **Leaving voicemails is strongly discouraged.**

        amd : typing.Optional[bool]
            AMD mode helps our AI navigate phone trees and IVR systems. If you know your call will hit an automated system you should switch it on.

            NOTE - AMD mode causes increased delay for the first response, even if answered by a human. Highly recommended to set to `false` in the majority of cases.

        request_data : typing.Optional[typing.Dict[str, typing.Any]]
            When you want your AI to “know” a specific fact - like the caller's name or other relevant context.

            The AI agent will be aware of both the key names as well as their corresponding values.

        dynamic_data : typing.Optional[typing.Sequence[DynamicData]]
            Make dynamic requests to external APIs and use the data in your AI's responses.

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

        Returns
        -------
        SendCallResponse

        Examples
        --------
        from bland import PronunciationObject
        from bland.client import AsyncBlandAI

        client = AsyncBlandAI(
            api_key="YOUR_API_KEY",
        )
        await client.call(
            phone_number="29382721828",
            task="Would love for you to check out our AI API!",
            temperature=0.5,
            transfer_list={
                "default": "+1234567890",
                "sales": "+1234567890",
                "support": "+1234567890",
                "billing": "+1234567890",
            },
            model="enhanced",
            pronunciation_guide=[
                PronunciationObject(
                    word="API",
                    pronunciation="A P I",
                ),
                PronunciationObject(
                    word="AI",
                    pronunciation="A I",
                ),
            ],
        )
        """
        _request: typing.Dict[str, typing.Any] = {
            "phone_number": phone_number,
            "task": task,
            "temperature": temperature,
            "tools": tools,
            "interruption_threshold": interruption_threshold,
        }
        if transfer_list is not OMIT:
            _request["transfer_list"] = transfer_list
        if model is not OMIT:
            _request["model"] = model
        if pronunciation_guide is not OMIT:
            _request["pronunciation_guide"] = pronunciation_guide
        if transfer_phone_number is not OMIT:
            _request["transfer_phone_number"] = transfer_phone_number
        if answered_by_enabled is not OMIT:
            _request["answered_by_enabled"] = answered_by_enabled
        if from_ is not OMIT:
            _request["from"] = from_
        if reduce_latency is not OMIT:
            _request["reduce_latency"] = reduce_latency
        if voice_id is not OMIT:
            _request["voice_id"] = voice_id
        if voice_preset_id is not OMIT:
            _request["voice_preset_id"] = voice_preset_id
        if start_time is not OMIT:
            _request["start_time"] = start_time
        if webhook is not OMIT:
            _request["webhook"] = webhook
        if wait_for_greeting is not OMIT:
            _request["wait_for_greeting"] = wait_for_greeting
        if first_sentence is not OMIT:
            _request["first_sentence"] = first_sentence
        if record is not OMIT:
            _request["record"] = record
        if voice_settings is not OMIT:
            _request["voice_settings"] = voice_settings
        if language is not OMIT:
            _request["language"] = language
        if max_duration is not OMIT:
            _request["max_duration"] = max_duration
        if voicemail_action is not OMIT:
            _request["voicemail_action"] = voicemail_action
        if amd is not OMIT:
            _request["amd"] = amd
        if request_data is not OMIT:
            _request["request_data"] = request_data
        if dynamic_data is not OMIT:
            _request["dynamic_data"] = dynamic_data
        _response = await self._client_wrapper.httpx_client.request(
            method="POST",
            url=urllib.parse.urljoin(f"{self._client_wrapper.get_base_url()}/", "v1/calls"),
            params=jsonable_encoder(
                request_options.get("additional_query_parameters") if request_options is not None else None
            ),
            json=jsonable_encoder(_request)
            if request_options is None or request_options.get("additional_body_parameters") is None
            else {
                **jsonable_encoder(_request),
                **(jsonable_encoder(remove_none_from_dict(request_options.get("additional_body_parameters", {})))),
            },
            headers=jsonable_encoder(
                remove_none_from_dict(
                    {
                        **self._client_wrapper.get_headers(),
                        **(request_options.get("additional_headers", {}) if request_options is not None else {}),
                    }
                )
            ),
            timeout=request_options.get("timeout_in_seconds")
            if request_options is not None and request_options.get("timeout_in_seconds") is not None
            else self._client_wrapper.get_timeout(),
            retries=0,
            max_retries=request_options.get("max_retries") if request_options is not None else 0,  # type: ignore
        )
        if 200 <= _response.status_code < 300:
            return pydantic_v1.parse_obj_as(SendCallResponse, _response.json())  # type: ignore
        if _response.status_code == 500:
            raise ServerError(pydantic_v1.parse_obj_as(ErrorBody, _response.json()))  # type: ignore
        if _response.status_code == 401:
            raise UnauthorizedError(pydantic_v1.parse_obj_as(ErrorBody, _response.json()))  # type: ignore
        try:
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)


def _get_base_url(*, base_url: typing.Optional[str] = None, environment: BlandAIEnvironment) -> str:
    if base_url is not None:
        return base_url
    elif environment is not None:
        return environment.value
    else:
        raise Exception("Please pass in either base_url or environment to construct the client")
