Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 51 additions & 38 deletions voice/src/vonage_voice/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@
TtsLanguageCode,
)
from .input_types import Dtmf, Speech
from .ncco import Connect, Conversation, Input, NccoAction, Notify, Record, Stream, Talk
from .ncco import (
Connect,
Conversation,
Input,
NccoAction,
Notify,
Record,
Stream,
Talk,
Transfer,
Wait,
)
from .requests import (
AudioStreamOptions,
CreateCallRequest,
Expand All @@ -33,41 +44,43 @@
)

__all__ = [
'AdvancedMachineDetection',
'AppEndpoint',
'AudioStreamOptions',
'CallInfo',
'CallList',
'CallMessage',
'CallState',
'Channel',
'Connect',
'ConnectEndpointType',
'Conversation',
'CreateCallRequest',
'CreateCallResponse',
'Dtmf',
'Embedded',
'Input',
'ListCallsFilter',
'HalLinks',
'NccoAction',
'NccoActionType',
'Notify',
'OnAnswer',
'Phone',
'PhoneEndpoint',
'Record',
'Sip',
'SipEndpoint',
'Speech',
'Stream',
'Talk',
'ToPhone',
'TtsLanguageCode',
'TtsStreamOptions',
'Vbc',
'VbcEndpoint',
'Websocket',
'WebsocketEndpoint',
"AdvancedMachineDetection",
"AppEndpoint",
"AudioStreamOptions",
"CallInfo",
"CallList",
"CallMessage",
"CallState",
"Channel",
"Connect",
"ConnectEndpointType",
"Conversation",
"CreateCallRequest",
"CreateCallResponse",
"Dtmf",
"Embedded",
"Input",
"ListCallsFilter",
"HalLinks",
"NccoAction",
"NccoActionType",
"Notify",
"OnAnswer",
"Phone",
"PhoneEndpoint",
"Record",
"Sip",
"SipEndpoint",
"Speech",
"Stream",
"Talk",
"Transfer",
"Wait",
"ToPhone",
"TtsLanguageCode",
"TtsStreamOptions",
"Vbc",
"VbcEndpoint",
"Websocket",
"WebsocketEndpoint",
]
2 changes: 2 additions & 0 deletions voice/src/vonage_voice/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class NccoActionType(str, Enum):
STREAM = 'stream'
INPUT = 'input'
NOTIFY = 'notify'
WAIT = 'wait'
TRANSFER = 'transfer'


class ConnectEndpointType(str, Enum):
Expand Down
63 changes: 63 additions & 0 deletions voice/src/vonage_voice/models/ncco.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,66 @@ class Notify(NccoAction):
eventUrl: list[str]
eventMethod: Optional[str] = None
action: NccoActionType = NccoActionType.NOTIFY


class Wait(NccoAction):
"""Use the Wait action to add a pause to an NCCO.

The wait period starts when the action is executed and ends after the provided
or default timeout value. Execution of the NCCO then resumes with the next action.

Args:
timeout (Optional[float]): Duration of the wait period in seconds. Valid values
are from 0.1 to 7200. Values below 0.1 are treated as 0.1; values above
7200 are treated as 7200. If not specified, defaults to 10 seconds.
"""

timeout: Optional[float] = 10.0
action: NccoActionType = NccoActionType.WAIT

@model_validator(mode='after')
def clamp_timeout(self):
if self.timeout is None:
self.timeout = 10.0
elif self.timeout < 0.1:
self.timeout = 0.1
elif self.timeout > 7200:
self.timeout = 7200.0
return self


class Transfer(NccoAction):
"""Use the Transfer action to move all legs from the current conversation into another
existing conversation.

The transfer action is synchronous and terminal for the current conversation.
The target conversation's NCCO continues to control its behaviour.

Args:
conversationId (str): The target conversation ID.
canHear (Optional[list[str]]): Leg UUIDs this participant can hear. If not
provided, the participant can hear everyone. If an empty list is provided,
the participant will not hear any other participants.
canSpeak (Optional[list[str]]): Leg UUIDs this participant can be heard by. If
not provided, the participant can be heard by everyone. If an empty list is
provided, the participant will not be heard by anyone.
mute (Optional[bool]): Set to `True` to mute the participant. When using
`canSpeak`, the `mute` parameter is not supported.

Raises:
NccoActionError: If the `mute` option is used with the `canSpeak` option.
"""

conversationId: str
canHear: Optional[list[str]] = None
canSpeak: Optional[list[str]] = None
mute: Optional[bool] = None
action: NccoActionType = NccoActionType.TRANSFER

@model_validator(mode='after')
def validate_mute_and_can_speak(self):
if self.canSpeak and self.mute:
raise NccoActionError(
'Cannot use mute option if canSpeak option is specified.'
)
return self
59 changes: 59 additions & 0 deletions voice/tests/test_ncco_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,62 @@ def test_notify_options():
'eventMethod': 'POST',
'action': 'notify',
}


def test_wait_default_timeout():
wait = ncco.Wait()
assert wait.model_dump(by_alias=True, exclude_none=True) == {
'timeout': 10.0,
'action': 'wait',
}


def test_wait_custom_timeout():
wait = ncco.Wait(timeout=0.5)
assert wait.model_dump(by_alias=True, exclude_none=True) == {
'timeout': 0.5,
'action': 'wait',
}


def test_wait_timeout_clamped_min_max():
wait_min = ncco.Wait(timeout=0.01)
assert wait_min.timeout == 0.1

wait_max = ncco.Wait(timeout=10000)
assert wait_max.timeout == 7200.0


def test_transfer_basic():
transfer = ncco.Transfer(conversationId='CON-1234567890')
assert transfer.model_dump(by_alias=True, exclude_none=True) == {
'conversationId': 'CON-1234567890',
'action': 'transfer',
}


def test_transfer_options():
transfer = ncco.Transfer(
conversationId='CON-1234567890',
canHear=['leg-a'],
canSpeak=['leg-b', 'leg-c'],
mute=False,
)
assert transfer.model_dump(by_alias=True, exclude_none=True) == {
'conversationId': 'CON-1234567890',
'canHear': ['leg-a'],
'canSpeak': ['leg-b', 'leg-c'],
'mute': False,
'action': 'transfer',
}


def test_transfer_mute_with_canspeak_error():
with raises(NccoActionError) as e:
ncco.Transfer(
conversationId='CON-1234567890',
canSpeak=['leg-a'],
mute=True,
)

assert e.match('Cannot use mute option if canSpeak option is specified.')