Added Daily SIP room creation utility (#2560)

* Added Daily SIP room creation utility to configure() function

* Add sip_codecs to the DailyRoomSipParams
This commit is contained in:
Mark Backman
2025-09-02 11:12:04 -07:00
committed by GitHub
parent 0d8ab7abca
commit 0386599163
3 changed files with 137 additions and 29 deletions

View File

@@ -51,6 +51,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Added `sip_codecs` to the `DailyRoomSipParams`.
- Updated the `configure()` function in `pipecat.runner.daily` to include new
args to create SIP-enabled rooms. Additionally, added new args to control the
room and token expiration durations.
- `pipecat.frames.frames.KeypadEntry` is deprecated and has been moved to - `pipecat.frames.frames.KeypadEntry` is deprecated and has been moved to
`pipecat.audio.dtmf.types.KeypadEntry`. `pipecat.audio.dtmf.types.KeypadEntry`.

View File

@@ -10,6 +10,10 @@ This module provides helper functions for creating and configuring Daily rooms
and authentication tokens. It automatically creates temporary rooms for and authentication tokens. It automatically creates temporary rooms for
development or uses existing rooms specified via environment variables. development or uses existing rooms specified via environment variables.
Functions:
- configure(): Create a standard or SIP-enabled Daily room, returning a DailyRoomConfig object.
Environment variables: Environment variables:
- DAILY_API_KEY - Daily API key for room/token creation (required) - DAILY_API_KEY - Daily API key for room/token creation (required)
@@ -22,39 +26,95 @@ Example::
from pipecat.runner.daily import configure from pipecat.runner.daily import configure
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
# Standard room
room_url, token = await configure(session) room_url, token = await configure(session)
# Use room_url and token with DailyTransport
# SIP-enabled room for phone calls
config = await configure(session, sip_caller_phone="+15551234567")
# config contains: room_url, token, sip_endpoint
""" """
import os import os
import time import time
import uuid import uuid
from typing import Tuple from typing import Dict, List, Optional
import aiohttp import aiohttp
from loguru import logger
from pydantic import BaseModel
from pipecat.transports.services.helpers.daily_rest import ( from pipecat.transports.services.helpers.daily_rest import (
DailyRESTHelper, DailyRESTHelper,
DailyRoomParams, DailyRoomParams,
DailyRoomProperties, DailyRoomProperties,
DailyRoomSipParams,
) )
async def configure(aiohttp_session: aiohttp.ClientSession) -> Tuple[str, str]: class DailyRoomConfig(BaseModel):
"""Configure Daily room URL and token from environment variables. """Configuration returned when creating a Daily room.
Attributes:
room_url: The Daily room URL for joining the meeting.
token: Authentication token for the bot to join the room.
sip_endpoint: SIP endpoint URI for phone connections (None for standard rooms).
"""
room_url: str
token: str
sip_endpoint: Optional[str] = None
def __iter__(self):
"""Enable tuple unpacking for backward compatibility.
Allows: room_url, token = await configure(session)
"""
yield self.room_url
yield self.token
async def configure(
aiohttp_session: aiohttp.ClientSession,
*,
room_exp_duration: Optional[float] = 2.0,
token_exp_duration: Optional[float] = 2.0,
sip_caller_phone: Optional[str] = None,
sip_enable_video: Optional[bool] = False,
sip_num_endpoints: Optional[int] = 1,
sip_codecs: Optional[Dict[str, List[str]]] = None,
) -> DailyRoomConfig:
"""Configure Daily room URL and token with optional SIP capabilities.
This function will either: This function will either:
1. Use an existing room URL from DAILY_SAMPLE_ROOM_URL environment variable 1. Use an existing room URL from DAILY_SAMPLE_ROOM_URL environment variable (standard mode only)
2. Create a new temporary room automatically if no URL is provided 2. Create a new temporary room automatically if no URL is provided
Args: Args:
aiohttp_session: HTTP session for making API requests. aiohttp_session: HTTP session for making API requests.
room_exp_duration: Room expiration time in hours.
token_exp_duration: Token expiration time in hours.
sip_caller_phone: Phone number or identifier for SIP display name.
When provided, enables SIP functionality and returns SipRoomConfig.
sip_enable_video: Whether video is enabled for SIP.
sip_num_endpoints: Number of allowed SIP endpoints.
sip_codecs: Codecs to support for audio and video. If None, uses Daily defaults.
Example: {"audio": ["OPUS"], "video": ["H264"]}
Returns: Returns:
Tuple containing the room URL and authentication token. DailyRoomConfig: Object with room_url, token, and optional sip_endpoint.
Supports tuple unpacking for backward compatibility: room_url, token = await configure(session)
Raises: Raises:
Exception: If DAILY_API_KEY is not provided in environment variables. Exception: If DAILY_API_KEY is not provided in environment variables.
Examples::
# Standard room
room_url, token = await configure(session)
# SIP-enabled room
sip_config = await configure(session, sip_caller_phone="+15551234567")
print(f"SIP endpoint: {sip_config.sip_endpoint}")
""" """
# Check for required API key # Check for required API key
api_key = os.getenv("DAILY_API_KEY") api_key = os.getenv("DAILY_API_KEY")
@@ -64,8 +124,8 @@ async def configure(aiohttp_session: aiohttp.ClientSession) -> Tuple[str, str]:
"Get your API key from https://dashboard.daily.co/developers" "Get your API key from https://dashboard.daily.co/developers"
) )
# Check for existing room URL # Determine if SIP mode is enabled
existing_room_url = os.getenv("DAILY_SAMPLE_ROOM_URL") sip_enabled = sip_caller_phone is not None
daily_rest_helper = DailyRESTHelper( daily_rest_helper = DailyRESTHelper(
daily_api_key=api_key, daily_api_key=api_key,
@@ -73,36 +133,75 @@ async def configure(aiohttp_session: aiohttp.ClientSession) -> Tuple[str, str]:
aiohttp_session=aiohttp_session, aiohttp_session=aiohttp_session,
) )
if existing_room_url: # Check for existing room URL (only in standard mode)
# Use existing room existing_room_url = os.getenv("DAILY_SAMPLE_ROOM_URL")
print(f"Using existing Daily room: {existing_room_url}") if existing_room_url and not sip_enabled:
# Use existing room (standard mode only)
logger.info(f"Using existing Daily room: {existing_room_url}")
room_url = existing_room_url room_url = existing_room_url
else:
# Create a new temporary room
room_name = f"pipecat-{uuid.uuid4().hex[:8]}"
print(f"Creating new Daily room: {room_name}")
# Calculate expiration time: current time + 2 hours # Create token and return standard format
expiration_time = time.time() + (2 * 60 * 60) # 2 hours from now expiry_time: float = token_exp_duration * 60 * 60
token = await daily_rest_helper.get_token(room_url, expiry_time)
return DailyRoomConfig(room_url=room_url, token=token)
# Create room properties with absolute timestamp # Create a new room
room_properties = DailyRoomProperties( room_prefix = "pipecat-sip" if sip_enabled else "pipecat"
exp=expiration_time, # Absolute Unix timestamp room_name = f"{room_prefix}-{uuid.uuid4().hex[:8]}"
eject_at_room_exp=True, logger.info(f"Creating new Daily room: {room_name}")
# Calculate expiration time
expiration_time = time.time() + (room_exp_duration * 60 * 60)
# Create room properties
room_properties = DailyRoomProperties(
exp=expiration_time,
eject_at_room_exp=True,
)
# Add SIP configuration if enabled
if sip_enabled:
sip_params = DailyRoomSipParams(
display_name=sip_caller_phone,
video=sip_enable_video,
sip_mode="dial-in",
num_endpoints=sip_num_endpoints,
codecs=sip_codecs,
) )
room_properties.sip = sip_params
room_properties.enable_dialout = True # Enable outbound calls if needed
room_properties.start_video_off = not sip_enable_video # Voice-only by default
# Create room parameters # Create room parameters
room_params = DailyRoomParams(name=room_name, properties=room_properties) room_params = DailyRoomParams(name=room_name, properties=room_properties)
try:
room_response = await daily_rest_helper.create_room(room_params) room_response = await daily_rest_helper.create_room(room_params)
room_url = room_response.url room_url = room_response.url
print(f"Created Daily room: {room_url}") logger.info(f"Created Daily room: {room_url}")
# Create a meeting token for the room with an expiration 2 hours in the future # Create meeting token
expiry_time: float = 2 * 60 * 60 token_expiry_seconds = token_exp_duration * 60 * 60
token = await daily_rest_helper.get_token(room_url, expiry_time) token = await daily_rest_helper.get_token(room_url, token_expiry_seconds)
return (room_url, token) if sip_enabled:
# Return SIP configuration object
sip_endpoint = room_response.config.sip_endpoint
logger.info(f"SIP endpoint: {sip_endpoint}")
return DailyRoomConfig(
room_url=room_url,
token=token,
sip_endpoint=sip_endpoint,
)
else:
# Return standard configuration
return DailyRoomConfig(room_url=room_url, token=token)
except Exception as e:
error_msg = f"Error creating Daily room: {e}"
logger.error(error_msg)
raise
# Keep this for backwards compatibility, but mark as deprecated # Keep this for backwards compatibility, but mark as deprecated

View File

@@ -10,7 +10,7 @@ Methods that wrap the Daily API to create rooms, check room URLs, and get meetin
""" """
import time import time
from typing import Literal, Optional from typing import Dict, List, Literal, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
import aiohttp import aiohttp
@@ -25,12 +25,15 @@ class DailyRoomSipParams(BaseModel):
video: Whether video is enabled for SIP. video: Whether video is enabled for SIP.
sip_mode: SIP connection mode, typically 'dial-in'. sip_mode: SIP connection mode, typically 'dial-in'.
num_endpoints: Number of allowed SIP endpoints. num_endpoints: Number of allowed SIP endpoints.
codecs: Codecs to support for audio and video. If None, uses Daily defaults.
Example: {"audio": ["OPUS"], "video": ["H264"]}
""" """
display_name: str = "sw-sip-dialin" display_name: str = "sw-sip-dialin"
video: bool = False video: bool = False
sip_mode: str = "dial-in" sip_mode: str = "dial-in"
num_endpoints: int = 1 num_endpoints: int = 1
codecs: Optional[Dict[str, List[str]]] = None
class RecordingsBucketConfig(BaseModel): class RecordingsBucketConfig(BaseModel):