From 799c2d14b8903ebc90c92a4d79fd6e20e9083eb3 Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Thu, 23 Jan 2025 21:40:42 +0530 Subject: [PATCH 1/9] adding meeting token v2 func --- CHANGELOG.md | 2 + .../transports/services/helpers/daily_rest.py | 117 +++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff694fc1..50cbec921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - It is now possible to specify the period of the `PipelineTask` heartbeat frames with `heartbeats_period_secs`. +- Added `DailyMeetingTokenProperties` and `DailyMeetingTokenParams` Pydantic models for meeting token creation. +- Added `get_token_v2` method to `DailyTransport` to create meeting tokens with `DailyMeetingTokenParams`. ### Changed diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index 65b6a1d62..dfacdf367 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -14,7 +14,7 @@ from typing import Literal, Optional from urllib.parse import urlparse import aiohttp -from pydantic import BaseModel, Field, ValidationError +from pydantic import BaseModel, Field, ValidationError, constr class DailyRoomSipParams(BaseModel): @@ -111,6 +111,75 @@ class DailyRoomObject(BaseModel): config: DailyRoomProperties +class DailyMeetingTokenProperties(BaseModel): + # https://docs.daily.co/reference/rest-api/meeting-tokens/create-meeting-token#properties + + room_name: Optional[str] = Field( + default=None, + description="The name of the room this token is for. If not provided, the token can be used for any room.", + ) + + eject_at_token_exp: Optional[bool] = Field( + default=None, + description="If `true`, the user will be ejected from the room when the token expires. Defaults to `false`.", + ) + eject_after_elapsed: Optional[int] = Field( + default=None, + description="The number of seconds after which the user will be ejected from the room. If not provided, the user will not be ejected based on elapsed time.", + ) + + nfb: Optional[int] = Field( + default=None, + description="Not before. This is a unix timestamp (seconds since the epoch.) Users cannot join a meeting in with this token before this time.", + ) + + exp: Optional[int] = Field( + default=60 * 60, + description="Expiration time for the token in seconds since the epoch. If not provided, the token will not expire.", + ) + is_owner: Optional[bool] = Field( + default=True, + description="If `true`, the token will grant owner privileges in the room. Defaults to `false`.", + ) + user_name: Optional[str] = Field( + default=None, + description="The name of the user. This will be added to the token payload.", + ) + user_id: Optional[str] = Field( + default=None, + description="A unique identifier for the user. This will be added to the token payload.", + ) + enable_screenshare: Optional[bool] = Field( + default=True, + description="If `true`, the user will be able to share their screen. Defaults to `true`.", + ) + start_video_off: Optional[bool] = Field( + default=None, + description="If `true`, the user's video will be turned off when they join the room. Defaults to `false`.", + ) + start_audio_off: Optional[bool] = Field( + default=None, + description="If `true`, the user's audio will be turned off when they join the room. Defaults to `false`.", + ) + enable_recording: Optional[Literal["cloud", "local", "raw-tracks"]] = Field( + default=None, + description="Recording settings for the token. Must be one of `cloud`, `local`, `raw-tracks` or `none`.", + ) + enable_prejoin_ui: Optional[bool] = Field( + default=None, + description="If `true`, the user will see the prejoin UI before joining the room.", + ) + start_cloud_recording: Optional[bool] = Field( + default=None, + description="Start cloud recording when the user joins the room. This can be used to always record and archive meetings, for example in a customer support context.", + ) + + +class DailyMeetingTokenParams(BaseModel): + # https://docs.daily.co/reference/rest-api/meeting-tokens/create-meeting-token#body-params + properties: DailyMeetingTokenProperties = Field(default_factory=DailyMeetingTokenProperties) + + class DailyRESTHelper: """Helper class for interacting with Daily's REST API. @@ -169,7 +238,7 @@ class DailyRESTHelper: Exception: If room creation fails or response is invalid """ headers = {"Authorization": f"Bearer {self.daily_api_key}"} - json = {**params.model_dump(exclude_none=True)} + json = params.model_dump(exclude_none=True) async with self.aiohttp_session.post( f"{self.daily_api_url}/rooms", headers=headers, json=json ) as r: @@ -224,6 +293,50 @@ class DailyRESTHelper: return data["token"] + async def get_token_v2( + self, + room_url: str, + params: DailyMeetingTokenParams, + expiry_time: float = 60 * 60, + ) -> str: + """Generate a meeting token for user to join a Daily room. + + Args: + room_url: Daily room URL + params: Meeting token properties + expiry_time: Token expiry time in seconds (default: 1 hour) + + Returns: + str: Meeting token + + Raises: + Exception: If token generation fails or room URL is missing + """ + if not room_url: + raise Exception( + "No Daily room specified. You must specify a Daily room in order a token to be generated." + ) + + expiration: int = int(time.time() + expiry_time) + + room_name = self.get_name_from_url(room_url) + + params.properties.room_name = room_name + params.properties.exp = expiration + + headers = {"Authorization": f"Bearer {self.daily_api_key}"} + json = params.model_dump(exclude_none=True) + async with self.aiohttp_session.post( + f"{self.daily_api_url}/meeting-tokens", headers=headers, json=json + ) as r: + if r.status != 200: + text = await r.text() + raise Exception(f"Failed to create meeting token (status: {r.status}): {text}") + + data = await r.json() + + return data["token"] + async def delete_room_by_url(self, room_url: str) -> bool: """Delete a room using its URL. From 6ab2404a985411b194fe0fea7a1b76c6b820821e Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 09:10:25 +0530 Subject: [PATCH 2/9] adding more properties to daily room --- src/pipecat/transports/services/helpers/daily_rest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index dfacdf367..c3aee3436 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -43,6 +43,8 @@ class DailyRoomProperties(BaseModel, extra="allow"): enable_emoji_reactions: Whether emoji reactions are enabled eject_at_room_exp: Whether to remove participants when room expires enable_dialout: Whether SIP dial-out is enabled + enable_recording: Recording settings ('cloud', 'local', 'raw-tracks') + geo: Geographic region for room max_participants: Maximum number of participants allowed in the room sip: SIP configuration parameters sip_uri: SIP URI information returned by Daily @@ -57,6 +59,8 @@ class DailyRoomProperties(BaseModel, extra="allow"): enable_emoji_reactions: bool = False eject_at_room_exp: bool = True enable_dialout: Optional[bool] = None + enable_recording: Optional[Literal["cloud", "local", "raw-tracks"]] = None + geo: Optional[str] = None max_participants: Optional[int] = None sip: Optional[DailyRoomSipParams] = None sip_uri: Optional[dict] = None From 40c1a8369a6ebecd09736d43a646a2e6c15a9feb Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 09:11:15 +0530 Subject: [PATCH 3/9] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50cbec921..d24d414d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 frames with `heartbeats_period_secs`. - Added `DailyMeetingTokenProperties` and `DailyMeetingTokenParams` Pydantic models for meeting token creation. - Added `get_token_v2` method to `DailyTransport` to create meeting tokens with `DailyMeetingTokenParams`. +- Added `enable_recording` and `geo` parameters to `DailyRoomProperties`. ### Changed From e106d7a215b575961f470b34a50a7a91eeed7070 Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 09:12:07 +0530 Subject: [PATCH 4/9] adding line space --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d24d414d1..06502ff5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - It is now possible to specify the period of the `PipelineTask` heartbeat frames with `heartbeats_period_secs`. + - Added `DailyMeetingTokenProperties` and `DailyMeetingTokenParams` Pydantic models for meeting token creation. + - Added `get_token_v2` method to `DailyTransport` to create meeting tokens with `DailyMeetingTokenParams`. + - Added `enable_recording` and `geo` parameters to `DailyRoomProperties`. ### Changed From c5faac1cf80fab386683012ea5eb992029627d22 Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 15:14:20 +0530 Subject: [PATCH 5/9] adding RecordingsBucketConfig --- CHANGELOG.md | 2 ++ src/pipecat/transports/services/helpers/daily_rest.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06502ff5f..03a9b1f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `enable_recording` and `geo` parameters to `DailyRoomProperties`. +- Added `RecordingsBucketConfig` to `DailyRoomProperties` to upload recordings to a custom AWS bucket. + ### Changed - Modified `TranscriptProcessor` to use TTS text frames for more accurate assistant diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index c3aee3436..49645bd4e 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -33,6 +33,14 @@ class DailyRoomSipParams(BaseModel): num_endpoints: int = 1 +class RecordingsBucketConfig(BaseModel): + # https://docs.daily.co/guides/products/live-streaming-recording/storing-recordings-in-a-custom-s3-bucket + bucket_name: str + bucket_region: str + assume_role_arn: str + allow_api_access: bool = False + + class DailyRoomProperties(BaseModel, extra="allow"): """Properties for configuring a Daily room. @@ -62,6 +70,7 @@ class DailyRoomProperties(BaseModel, extra="allow"): enable_recording: Optional[Literal["cloud", "local", "raw-tracks"]] = None geo: Optional[str] = None max_participants: Optional[int] = None + recordings_bucket: Optional[RecordingsBucketConfig] = None sip: Optional[DailyRoomSipParams] = None sip_uri: Optional[dict] = None start_video_off: bool = False From d5818fad5b91db08c202b331a46d4c36e431ed8b Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 22:46:54 +0530 Subject: [PATCH 6/9] addressing comments --- CHANGELOG.md | 5 +- .../transports/services/helpers/daily_rest.py | 93 ++++++++----------- 2 files changed, 43 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a9b1f0c..de31085b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - It is now possible to specify the period of the `PipelineTask` heartbeat frames with `heartbeats_period_secs`. -- Added `DailyMeetingTokenProperties` and `DailyMeetingTokenParams` Pydantic models for meeting token creation. - -- Added `get_token_v2` method to `DailyTransport` to create meeting tokens with `DailyMeetingTokenParams`. +- Added `DailyMeetingTokenProperties` and `DailyMeetingTokenParams` Pydantic models + for meeting token creation in `get_token` method of `DailyRESTHelper`. - Added `enable_recording` and `geo` parameters to `DailyRoomProperties`. diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index 49645bd4e..90d90fd61 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -14,7 +14,7 @@ from typing import Literal, Optional from urllib.parse import urlparse import aiohttp -from pydantic import BaseModel, Field, ValidationError, constr +from pydantic import BaseModel, Field, ValidationError class DailyRoomSipParams(BaseModel): @@ -34,7 +34,12 @@ class DailyRoomSipParams(BaseModel): class RecordingsBucketConfig(BaseModel): - # https://docs.daily.co/guides/products/live-streaming-recording/storing-recordings-in-a-custom-s3-bucket + """Configuration for storing Daily recordings in a custom S3 bucket. + + Refer to the Daily API documentation for more information: + https://docs.daily.co/guides/products/live-streaming-recording/storing-recordings-in-a-custom-s3-bucket + """ + bucket_name: str bucket_region: str assume_role_arn: str @@ -125,11 +130,15 @@ class DailyRoomObject(BaseModel): class DailyMeetingTokenProperties(BaseModel): - # https://docs.daily.co/reference/rest-api/meeting-tokens/create-meeting-token#properties + """Properties for configuring a Daily meeting token. + + Refer to the Daily API documentation for more information: + https://docs.daily.co/reference/rest-api/meeting-tokens/create-meeting-token#properties + """ room_name: Optional[str] = Field( default=None, - description="The name of the room this token is for. If not provided, the token can be used for any room.", + description="The room for which this token is valid. If not set, the token is valid for all rooms in your domain. You should always set room_name if using this token to control meeting access.", ) eject_at_token_exp: Optional[bool] = Field( @@ -141,17 +150,17 @@ class DailyMeetingTokenProperties(BaseModel): description="The number of seconds after which the user will be ejected from the room. If not provided, the user will not be ejected based on elapsed time.", ) - nfb: Optional[int] = Field( + nbf: Optional[int] = Field( default=None, description="Not before. This is a unix timestamp (seconds since the epoch.) Users cannot join a meeting in with this token before this time.", ) exp: Optional[int] = Field( - default=60 * 60, - description="Expiration time for the token in seconds since the epoch. If not provided, the token will not expire.", + default=None, + description="Expiration time (unix timestamp in seconds). We strongly recommend setting this value for security. If not set, the token will not expire. Refer docs for more info.", ) is_owner: Optional[bool] = Field( - default=True, + default=None, description="If `true`, the token will grant owner privileges in the room. Defaults to `false`.", ) user_name: Optional[str] = Field( @@ -163,7 +172,7 @@ class DailyMeetingTokenProperties(BaseModel): description="A unique identifier for the user. This will be added to the token payload.", ) enable_screenshare: Optional[bool] = Field( - default=True, + default=None, description="If `true`, the user will be able to share their screen. Defaults to `true`.", ) start_video_off: Optional[bool] = Field( @@ -189,7 +198,12 @@ class DailyMeetingTokenProperties(BaseModel): class DailyMeetingTokenParams(BaseModel): - # https://docs.daily.co/reference/rest-api/meeting-tokens/create-meeting-token#body-params + """Parameters for creating a Daily meeting token. + + Refer to the Daily API documentation for more information: + https://docs.daily.co/reference/rest-api/meeting-tokens/create-meeting-token#body-params + """ + properties: DailyMeetingTokenProperties = Field(default_factory=DailyMeetingTokenProperties) @@ -211,6 +225,9 @@ class DailyRESTHelper: daily_api_url: str = "https://api.daily.co/v1", aiohttp_session: aiohttp.ClientSession, ): + """ + Initialize the Daily REST helper. + """ self.daily_api_key = daily_api_key self.daily_api_url = daily_api_url self.aiohttp_session = aiohttp_session @@ -269,7 +286,11 @@ class DailyRESTHelper: return room async def get_token( - self, room_url: str, expiry_time: float = 60 * 60, owner: bool = True + self, + room_url: str, + expiry_time: float = 60 * 60, + owner: bool = True, + params: DailyMeetingTokenParams = None, ) -> str: """Generate a meeting token for user to join a Daily room. @@ -277,6 +298,7 @@ class DailyRESTHelper: room_url: Daily room URL expiry_time: Token validity duration in seconds (default: 1 hour) owner: Whether token has owner privileges + params:Parameters for creating a Daily meeting token Returns: str: Meeting token @@ -294,51 +316,18 @@ class DailyRESTHelper: room_name = self.get_name_from_url(room_url) headers = {"Authorization": f"Bearer {self.daily_api_key}"} - json = {"properties": {"room_name": room_name, "is_owner": owner, "exp": expiration}} - async with self.aiohttp_session.post( - f"{self.daily_api_url}/meeting-tokens", headers=headers, json=json - ) as r: - if r.status != 200: - text = await r.text() - raise Exception(f"Failed to create meeting token (status: {r.status}): {text}") - data = await r.json() - - return data["token"] - - async def get_token_v2( - self, - room_url: str, - params: DailyMeetingTokenParams, - expiry_time: float = 60 * 60, - ) -> str: - """Generate a meeting token for user to join a Daily room. - - Args: - room_url: Daily room URL - params: Meeting token properties - expiry_time: Token expiry time in seconds (default: 1 hour) - - Returns: - str: Meeting token - - Raises: - Exception: If token generation fails or room URL is missing - """ - if not room_url: - raise Exception( - "No Daily room specified. You must specify a Daily room in order a token to be generated." + if params is None: + params = DailyMeetingTokenParams( + **{"properties": {"room_name": room_name, "is_owner": owner, "exp": expiration}} ) + else: + params.properties.room_name = room_name + params.properties.exp = int(expiration) + params.properties.is_owner = owner - expiration: int = int(time.time() + expiry_time) - - room_name = self.get_name_from_url(room_url) - - params.properties.room_name = room_name - params.properties.exp = expiration - - headers = {"Authorization": f"Bearer {self.daily_api_key}"} json = params.model_dump(exclude_none=True) + async with self.aiohttp_session.post( f"{self.daily_api_url}/meeting-tokens", headers=headers, json=json ) as r: From ef02ece66223bb261f85995f5511195e5bde9dda Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 22:47:40 +0530 Subject: [PATCH 7/9] doc string --- src/pipecat/transports/services/helpers/daily_rest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index 90d90fd61..9389e9315 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -225,9 +225,7 @@ class DailyRESTHelper: daily_api_url: str = "https://api.daily.co/v1", aiohttp_session: aiohttp.ClientSession, ): - """ - Initialize the Daily REST helper. - """ + """Initialize the Daily REST helper.""" self.daily_api_key = daily_api_key self.daily_api_url = daily_api_url self.aiohttp_session = aiohttp_session From 71c2dc3d05d60c1a94cfee93931efe3ded479f28 Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 23:38:44 +0530 Subject: [PATCH 8/9] minor typing change --- src/pipecat/transports/services/helpers/daily_rest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index 9389e9315..58e34f3c1 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -288,7 +288,7 @@ class DailyRESTHelper: room_url: str, expiry_time: float = 60 * 60, owner: bool = True, - params: DailyMeetingTokenParams = None, + params: Optional[DailyMeetingTokenParams] = None, ) -> str: """Generate a meeting token for user to join a Daily room. @@ -296,7 +296,7 @@ class DailyRESTHelper: room_url: Daily room URL expiry_time: Token validity duration in seconds (default: 1 hour) owner: Whether token has owner privileges - params:Parameters for creating a Daily meeting token + params: Parameters for creating a Daily meeting token Returns: str: Meeting token From 1f1e2dac2ba5f2e4dbdae6213e3cb92cf96b1767 Mon Sep 17 00:00:00 2001 From: Vaibhav159 Date: Fri, 24 Jan 2025 23:44:23 +0530 Subject: [PATCH 9/9] wrapping things up --- src/pipecat/transports/services/helpers/daily_rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipecat/transports/services/helpers/daily_rest.py b/src/pipecat/transports/services/helpers/daily_rest.py index 58e34f3c1..fa775ff42 100644 --- a/src/pipecat/transports/services/helpers/daily_rest.py +++ b/src/pipecat/transports/services/helpers/daily_rest.py @@ -185,7 +185,7 @@ class DailyMeetingTokenProperties(BaseModel): ) enable_recording: Optional[Literal["cloud", "local", "raw-tracks"]] = Field( default=None, - description="Recording settings for the token. Must be one of `cloud`, `local`, `raw-tracks` or `none`.", + description="Recording settings for the token. Must be one of `cloud`, `local` or `raw-tracks`.", ) enable_prejoin_ui: Optional[bool] = Field( default=None,