pipecatcloud 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. pipecatcloud-0.6.0/.claude/settings.json +5 -0
  2. pipecatcloud-0.6.0/.pre-commit-config.yaml +13 -0
  3. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/CHANGELOG.md +25 -0
  4. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/PKG-INFO +1 -1
  5. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/pyproject.toml +13 -0
  6. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/auth_utils.py +1 -2
  7. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/build_utils.py +40 -14
  8. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/console_utils.py +8 -9
  9. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/deploy_utils.py +37 -28
  10. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/regions.py +3 -5
  11. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/agent.py +4 -4
  12. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/api.py +43 -29
  13. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/agent.py +20 -5
  14. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/auth.py +10 -10
  15. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/build.py +6 -8
  16. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/deploy.py +29 -8
  17. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/docker.py +2 -3
  18. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/organizations.py +60 -17
  19. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/secrets.py +5 -7
  20. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/config.py +7 -8
  21. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/exception.py +2 -3
  22. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/session.py +5 -5
  23. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/smallwebrtc/session_manager.py +2 -3
  24. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud.egg-info/PKG-INFO +1 -1
  25. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud.egg-info/SOURCES.txt +3 -1
  26. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_api_client.py +4 -5
  27. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_auth.py +3 -5
  28. pipecatcloud-0.6.0/tests/test_build_utils.py +151 -0
  29. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_deployment_status.py +10 -8
  30. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_krisp_viva.py +3 -4
  31. pipecatcloud-0.6.0/tests/test_max_session_duration.py +154 -0
  32. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_pat_auth.py +3 -4
  33. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/uv.lock +3 -3
  34. pipecatcloud-0.5.0/.pre-commit-config.yaml +0 -8
  35. pipecatcloud-0.5.0/scripts/pre-commit.sh +0 -27
  36. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/.github/workflows/format.yml +0 -0
  37. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/.github/workflows/publish-pypi.yml +0 -0
  38. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/.github/workflows/publish-test.yml +0 -0
  39. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/.github/workflows/tests.yml +0 -0
  40. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/.gitignore +0 -0
  41. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/LICENSE +0 -0
  42. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/README.md +0 -0
  43. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/pipecat-cloud.png +0 -0
  44. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/pytest.ini +0 -0
  45. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/scripts/fix-ruff.sh +0 -0
  46. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/setup.cfg +0 -0
  47. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/__init__.py +0 -0
  48. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/__main__.py +0 -0
  49. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/__version__.py +0 -0
  50. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/__init__.py +0 -0
  51. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/_utils/async_utils.py +0 -0
  52. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/__init__.py +0 -0
  53. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/api.py +0 -0
  54. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  55. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/commands/regions.py +0 -0
  56. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/cli/entry_point.py +0 -0
  57. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/config.py +0 -0
  58. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/constants.py +0 -0
  59. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud/py.typed +0 -0
  60. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  61. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  62. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud.egg-info/requires.txt +0 -0
  63. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  64. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/__init__.py +0 -0
  65. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/conftest.py +0 -0
  66. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_agent_sessions.py +0 -0
  67. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_agent_stop.py +0 -0
  68. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_config_file.py +0 -0
  69. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_docker_commands.py +0 -0
  70. {pipecatcloud-0.5.0 → pipecatcloud-0.6.0}/tests/test_regions.py +0 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "attribution": {
3
+ "commit": ""
4
+ }
5
+ }
@@ -0,0 +1,13 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: ruff
5
+ name: ruff
6
+ entry: uv run ruff check --fix
7
+ language: system
8
+ types: [python]
9
+ - id: ruff-format
10
+ name: ruff-format
11
+ entry: uv run ruff format
12
+ language: system
13
+ types: [python]
@@ -5,6 +5,31 @@ All notable changes to **Pipecat Cloud** will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2026-04-22
9
+
10
+ ### Added
11
+
12
+ - `websocket_auth` config option in `pcc-deploy.toml` to control authentication
13
+ mode for the generic WebSocket handler (`/ws/generic`). Valid values are
14
+ `"none"` (no authentication required) and `"token"` (requires a session token
15
+ obtained via `/start`). New services default to `"token"` if not specified.
16
+
17
+ - `--max-session-duration` flag on `pcc deploy` for configuring the
18
+ per-service maximum session duration in seconds. Accepts values
19
+ between 60 and 14400 (4 hours), defaulting to 7200 (2 hours) when
20
+ unset. Also exposed in `pcc agent status` output and as
21
+ `max_session_duration` in `pcc-deploy.toml`.
22
+
23
+ ### Fixed
24
+
25
+ - `.dockerignore` patterns written with Docker's conventional trailing
26
+ slash (e.g. `.venv/`, `node_modules/`) and leading `./` anchors
27
+ (e.g. `./dist`) now match correctly in cloud builds. Previously the
28
+ CLI compared patterns with `fnmatch`, which treats both forms as
29
+ literal text, so these entries silently excluded nothing and large
30
+ directories like `.venv/` could leak into the build context and
31
+ trip the 500 MB limit.
32
+
8
33
  ## [0.5.0] - 2026-04-17
9
34
 
10
35
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Cloud hosting for Pipecat AI applications
5
5
  License: BSD 2-Clause License
6
6
  Project-URL: Website, https://docs.pipecat.daily.co/
@@ -59,6 +59,19 @@ pipecatcloud = ["py.typed"]
59
59
  exclude = ["*_pb2.py"]
60
60
  line-length = 100
61
61
 
62
+ [tool.ruff.lint]
63
+ select = [
64
+ "I", # Import rules
65
+ "UP", # Modern Python
66
+ ]
67
+
68
+ [tool.ruff.lint.per-file-ignores]
69
+ # Skip docstring checks for non-source code
70
+ "tests/**/*.py" = ["D"]
71
+ "scripts/**/*.py" = ["D"]
72
+ # Skip D104 (missing docstring in public package) for __init__.py files
73
+ "**/__init__.py" = ["D104"]
74
+
62
75
  [project.urls]
63
76
  Website = "https://docs.pipecat.daily.co/"
64
77
 
@@ -5,7 +5,6 @@
5
5
  #
6
6
 
7
7
  import functools
8
- from typing import Optional
9
8
 
10
9
  import aiohttp
11
10
 
@@ -15,7 +14,7 @@ from pipecatcloud.cli import PIPECAT_CLI_NAME
15
14
  from pipecatcloud.cli.config import config
16
15
 
17
16
 
18
- async def _resolve_default_org(token: str) -> Optional[str]:
17
+ async def _resolve_default_org(token: str) -> str | None:
19
18
  """Fetch the user's default organization using the given token."""
20
19
  from pipecatcloud.config import config as base_config
21
20
 
@@ -19,15 +19,15 @@ import io
19
19
  import os
20
20
  import tarfile
21
21
  import time
22
+ from collections.abc import Callable
22
23
  from dataclasses import dataclass
23
24
  from pathlib import Path
24
- from typing import Callable, List, Optional, Set, Tuple
25
25
 
26
26
  import aiohttp
27
27
  from loguru import logger
28
28
 
29
29
  # Default patterns to exclude from build context
30
- DEFAULT_EXCLUSIONS: Set[str] = {
30
+ DEFAULT_EXCLUSIONS: set[str] = {
31
31
  # Version control
32
32
  ".git",
33
33
  ".gitignore",
@@ -116,7 +116,7 @@ class BuildStatus:
116
116
  return status in cls.TERMINAL_STATUSES
117
117
 
118
118
 
119
- def _should_exclude(path: Path, exclusions: Set[str], base_path: Path) -> bool:
119
+ def _should_exclude(path: Path, exclusions: set[str], base_path: Path) -> bool:
120
120
  """
121
121
  Check if a path should be excluded from the build context.
122
122
 
@@ -143,7 +143,27 @@ def _should_exclude(path: Path, exclusions: Set[str], base_path: Path) -> bool:
143
143
  return False
144
144
 
145
145
 
146
- def load_dockerignore(context_dir: Path) -> Optional[Set[str]]:
146
+ def _normalize_pattern(pattern: str) -> str:
147
+ """Normalize a single ``.dockerignore`` pattern for ``fnmatch`` matching.
148
+
149
+ The matcher in ``_should_exclude`` walks the tree and compares each
150
+ path component against every pattern with ``fnmatch.fnmatch``, which
151
+ treats the pattern as literal text. ``.dockerignore`` (and the
152
+ companion ``.gitignore`` grammar users pattern after) allows a few
153
+ pieces of sugar that ``fnmatch`` does not understand: a trailing
154
+ slash meaning "directory only", and a leading ``./`` anchoring to
155
+ the context root. Both forms are common in the wild and routinely
156
+ silently match nothing under a raw ``fnmatch`` implementation, so
157
+ we strip them here to make the pattern intent actually take effect.
158
+ """
159
+ normalized = pattern.strip()
160
+ if normalized.startswith("./"):
161
+ normalized = normalized[2:]
162
+ normalized = normalized.rstrip("/")
163
+ return normalized
164
+
165
+
166
+ def load_dockerignore(context_dir: Path) -> set[str] | None:
147
167
  """
148
168
  Load patterns from .dockerignore file if it exists.
149
169
 
@@ -159,12 +179,15 @@ def load_dockerignore(context_dir: Path) -> Optional[Set[str]]:
159
179
 
160
180
  patterns = set()
161
181
  try:
162
- with open(dockerignore_path, "r") as f:
182
+ with open(dockerignore_path) as f:
163
183
  for line in f:
164
- line = line.strip()
184
+ stripped = line.strip()
165
185
  # Skip comments and empty lines
166
- if line and not line.startswith("#"):
167
- patterns.add(line)
186
+ if not stripped or stripped.startswith("#"):
187
+ continue
188
+ normalized = _normalize_pattern(stripped)
189
+ if normalized:
190
+ patterns.add(normalized)
168
191
  except Exception as e:
169
192
  logger.warning(f"Failed to read .dockerignore: {e}")
170
193
  return None
@@ -172,7 +195,7 @@ def load_dockerignore(context_dir: Path) -> Optional[Set[str]]:
172
195
  return patterns
173
196
 
174
197
 
175
- def get_exclusions(context_dir: Path, extra_patterns: Optional[List[str]] = None) -> Set[str]:
198
+ def get_exclusions(context_dir: Path, extra_patterns: list[str] | None = None) -> set[str]:
176
199
  """
177
200
  Get the set of exclusion patterns to use.
178
201
 
@@ -195,13 +218,16 @@ def get_exclusions(context_dir: Path, extra_patterns: Optional[List[str]] = None
195
218
  # Use defaults + extras
196
219
  exclusions = DEFAULT_EXCLUSIONS.copy()
197
220
  if extra_patterns:
198
- exclusions.update(extra_patterns)
221
+ for extra in extra_patterns:
222
+ normalized = _normalize_pattern(extra)
223
+ if normalized:
224
+ exclusions.add(normalized)
199
225
  return exclusions
200
226
 
201
227
 
202
228
  def create_deterministic_tarball(
203
229
  context_dir: str,
204
- exclusions: Set[str],
230
+ exclusions: set[str],
205
231
  dockerfile_path: str = "Dockerfile",
206
232
  ) -> BuildContext:
207
233
  """
@@ -235,7 +261,7 @@ def create_deterministic_tarball(
235
261
  raise FileNotFoundError(f"Dockerfile not found: {dockerfile_full}")
236
262
 
237
263
  # Collect all files, sorted alphabetically
238
- files_to_add: List[Tuple[Path, str]] = []
264
+ files_to_add: list[tuple[Path, str]] = []
239
265
 
240
266
  for root, dirs, files in os.walk(base_path):
241
267
  root_path = Path(root)
@@ -363,8 +389,8 @@ async def poll_build_status(
363
389
  api_client,
364
390
  poll_interval: float = 3.0,
365
391
  max_duration: float = 600.0, # 10 minutes
366
- status_callback: Optional[Callable[[dict], None]] = None,
367
- ) -> Tuple[bool, dict]:
392
+ status_callback: Callable[[dict], None] | None = None,
393
+ ) -> tuple[bool, dict]:
368
394
  """
369
395
  Poll build status until completion or timeout.
370
396
 
@@ -6,7 +6,6 @@
6
6
 
7
7
  import statistics
8
8
  from datetime import datetime
9
- from typing import Optional, Union
10
9
 
11
10
  from rich.console import Console
12
11
  from rich.panel import Panel
@@ -18,9 +17,9 @@ class PipecatConsole(Console):
18
17
  def success(
19
18
  self,
20
19
  message,
21
- title: Optional[str] = None,
22
- title_extra: Optional[str] = None,
23
- subtitle: Optional[str] = None,
20
+ title: str | None = None,
21
+ title_extra: str | None = None,
22
+ subtitle: str | None = None,
24
23
  ):
25
24
  if not title:
26
25
  title = f"{PANEL_TITLE_SUCCESS}{f' - {title_extra}' if title_extra is not None else ''}"
@@ -39,9 +38,9 @@ class PipecatConsole(Console):
39
38
  def error(
40
39
  self,
41
40
  message,
42
- title: Optional[str] = None,
43
- title_extra: Optional[str] = None,
44
- subtitle: Optional[str] = None,
41
+ title: str | None = None,
42
+ title_extra: str | None = None,
43
+ subtitle: str | None = None,
45
44
  ):
46
45
  if not title:
47
46
  title = f"{PANEL_TITLE_ERROR}{f' - {title_extra}' if title_extra is not None else ''}"
@@ -74,8 +73,8 @@ class PipecatConsole(Console):
74
73
 
75
74
  def api_error(
76
75
  self,
77
- error_code: Optional[Union[str, dict]] = None,
78
- title: Optional[str] = "API Error",
76
+ error_code: str | dict | None = None,
77
+ title: str | None = "API Error",
79
78
  hide_subtitle: bool = False,
80
79
  ):
81
80
  DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
@@ -6,10 +6,9 @@
6
6
 
7
7
  import functools
8
8
  import os
9
- from datetime import datetime, timezone
9
+ from collections.abc import Callable
10
+ from datetime import UTC, datetime
10
11
  from enum import Enum
11
- from typing import Callable, List, Optional
12
-
13
12
 
14
13
  import toml
15
14
  import typer
@@ -42,12 +41,12 @@ class DeploymentStatus:
42
41
  status_message: str
43
42
  is_available: bool = False
44
43
  is_ready: bool = False
45
- degraded_reason: Optional[str] = None
46
- current_revision: Optional[dict] = None
47
- previous_revision: Optional[dict] = None
44
+ degraded_reason: str | None = None
45
+ current_revision: dict | None = None
46
+ previous_revision: dict | None = None
48
47
 
49
48
 
50
- def _find_condition(conditions: list, condition_type: str) -> Optional[dict]:
49
+ def _find_condition(conditions: list, condition_type: str) -> dict | None:
51
50
  """Find a condition by type from the conditions array."""
52
51
  for c in conditions:
53
52
  if c.get("type") == condition_type:
@@ -55,13 +54,13 @@ def _find_condition(conditions: list, condition_type: str) -> Optional[dict]:
55
54
  return None
56
55
 
57
56
 
58
- def _format_elapsed(phase_started_at: Optional[str]) -> str:
57
+ def _format_elapsed(phase_started_at: str | None) -> str:
59
58
  """Format elapsed time since phaseStartedAt as a human-readable string."""
60
59
  if not phase_started_at:
61
60
  return ""
62
61
  try:
63
62
  started = datetime.fromisoformat(phase_started_at.replace("Z", "+00:00"))
64
- elapsed = datetime.now(timezone.utc) - started
63
+ elapsed = datetime.now(UTC) - started
65
64
  total_seconds = int(elapsed.total_seconds())
66
65
  if total_seconds < 0:
67
66
  return ""
@@ -149,7 +148,7 @@ def _build_status_message(headline: str, current_rev=None, previous_rev=None) ->
149
148
 
150
149
  def interpret_deployment_status(
151
150
  agent_status: dict,
152
- desired_deployment_id: Optional[str] = None,
151
+ desired_deployment_id: str | None = None,
153
152
  ) -> DeploymentStatus:
154
153
  """Interpret the raw API response into a structured deployment status.
155
154
 
@@ -253,12 +252,12 @@ def interpret_deployment_status(
253
252
 
254
253
  @dataclass
255
254
  class ScalingParams:
256
- min_agents: Optional[int] = 0
257
- max_agents: Optional[int] = None
255
+ min_agents: int | None = 0
256
+ max_agents: int | None = None
258
257
  # @deprecated
259
- min_instances: Optional[int] = field(default=None, metadata={"deprecated": True})
258
+ min_instances: int | None = field(default=None, metadata={"deprecated": True})
260
259
  # @deprecated
261
- max_instances: Optional[int] = field(default=None, metadata={"deprecated": True})
260
+ max_instances: int | None = field(default=None, metadata={"deprecated": True})
262
261
 
263
262
  def __attrs_post_init__(self):
264
263
  # Handle deprecated fields
@@ -288,7 +287,7 @@ class ScalingParams:
288
287
 
289
288
  @dataclass
290
289
  class KrispVivaConfig:
291
- audio_filter: Optional[KrispVivaAudioFilter] = None
290
+ audio_filter: KrispVivaAudioFilter | None = None
292
291
 
293
292
  def __attrs_post_init__(self):
294
293
  # Validation against known models
@@ -309,7 +308,7 @@ class BuildConfig:
309
308
 
310
309
  context_dir: str = "."
311
310
  dockerfile: str = "Dockerfile"
312
- exclude_patterns: List[str] = field(factory=list)
311
+ exclude_patterns: list[str] = field(factory=list)
313
312
 
314
313
  def to_dict(self):
315
314
  return {
@@ -321,19 +320,21 @@ class BuildConfig:
321
320
 
322
321
  @dataclass
323
322
  class DeployConfigParams:
324
- agent_name: Optional[str] = None
325
- image: Optional[str] = None
326
- build_id: Optional[str] = None # For cloud builds
327
- image_credentials: Optional[str] = None
328
- secret_set: Optional[str] = None
329
- region: Optional[str] = None
323
+ agent_name: str | None = None
324
+ image: str | None = None
325
+ build_id: str | None = None # For cloud builds
326
+ image_credentials: str | None = None
327
+ secret_set: str | None = None
328
+ region: str | None = None
330
329
  scaling: ScalingParams = ScalingParams()
331
330
  enable_krisp: bool = False
332
331
  docker_config: dict = field(factory=dict)
333
332
  build_config: BuildConfig = field(factory=BuildConfig) # Cloud build configuration
334
- agent_profile: Optional[str] = None
333
+ agent_profile: str | None = None
335
334
  krisp_viva: KrispVivaConfig = field(factory=KrispVivaConfig)
336
335
  force_redeploy: bool = False
336
+ websocket_auth: str | None = None
337
+ max_session_duration: int | None = None
337
338
 
338
339
  def __attrs_post_init__(self):
339
340
  if self.image is not None and ":" not in self.image:
@@ -341,6 +342,8 @@ class DeployConfigParams:
341
342
  # Cannot specify both image and build_id
342
343
  if self.image is not None and self.build_id is not None:
343
344
  raise ValueError("Cannot specify both 'image' and 'build_id'")
345
+ if self.max_session_duration is not None and not 60 <= self.max_session_duration <= 14400:
346
+ raise ValueError("max_session_duration must be between 60 and 14400 seconds")
344
347
 
345
348
  def to_dict(self):
346
349
  return {
@@ -356,17 +359,19 @@ class DeployConfigParams:
356
359
  "build_config": self.build_config.to_dict() if self.build_config else None,
357
360
  "agent_profile": self.agent_profile,
358
361
  "krisp_viva": self.krisp_viva.to_dict() if self.krisp_viva else None,
362
+ "websocket_auth": self.websocket_auth,
363
+ "max_session_duration": self.max_session_duration,
359
364
  }
360
365
 
361
366
 
362
- def load_deploy_config_file() -> Optional[DeployConfigParams]:
367
+ def load_deploy_config_file() -> DeployConfigParams | None:
363
368
  from pipecatcloud.cli.config import deploy_config_path
364
369
 
365
370
  logger.debug(f"Deploy config path: {deploy_config_path}")
366
371
  logger.debug(f"Deploy config path exists: {os.path.exists(deploy_config_path)}")
367
372
 
368
373
  try:
369
- with open(deploy_config_path, "r") as f:
374
+ with open(deploy_config_path) as f:
370
375
  config_data = toml.load(f)
371
376
  except Exception:
372
377
  return None
@@ -415,6 +420,8 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
415
420
  "build",
416
421
  "agent_profile",
417
422
  "krisp_viva",
423
+ "websocket_auth",
424
+ "max_session_duration",
418
425
  }
419
426
  unexpected_keys = set(config_data.keys()) - expected_keys
420
427
  if unexpected_keys:
@@ -427,7 +434,7 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
427
434
  raise ConfigFileError(str(e))
428
435
 
429
436
 
430
- CONFIG_FILE_OPTION: Optional[str] = typer.Option(
437
+ CONFIG_FILE_OPTION: str | None = typer.Option(
431
438
  None,
432
439
  "--config-file",
433
440
  help=f"Path to deploy config file (default: {PIPECAT_DEPLOY_CONFIG_PATH})",
@@ -459,8 +466,10 @@ def with_deploy_config(func: Callable) -> Callable:
459
466
  deploy_config = load_deploy_config_file()
460
467
  kwargs["deploy_config"] = deploy_config
461
468
  except Exception as e:
462
- logger.error(f"Error loading deploy config: {e}")
463
- raise ConfigFileError(str(e))
469
+ from pipecatcloud._utils.console_utils import console
470
+
471
+ console.error(f"Error loading deploy config: {e}")
472
+ raise typer.Exit(1)
464
473
  return func(*args, **kwargs)
465
474
 
466
475
  return wrapper
@@ -10,16 +10,14 @@ Region utilities for Pipecat Cloud CLI.
10
10
  Provides functions to fetch and cache available regions from the API.
11
11
  """
12
12
 
13
- from typing import Dict, List, Optional
14
-
15
13
  from pipecatcloud.cli.api import API
16
14
  from pipecatcloud.cli.config import config
17
15
 
18
16
  # Module-level cache for regions
19
- _regions_cache: Optional[List[Dict[str, str]]] = None
17
+ _regions_cache: list[dict[str, str]] | None = None
20
18
 
21
19
 
22
- async def get_regions() -> List[Dict[str, str]]:
20
+ async def get_regions() -> list[dict[str, str]]:
23
21
  """
24
22
  Fetch available regions from the API with caching.
25
23
 
@@ -45,7 +43,7 @@ async def get_regions() -> List[Dict[str, str]]:
45
43
  return _regions_cache
46
44
 
47
45
 
48
- async def get_region_codes() -> List[str]:
46
+ async def get_region_codes() -> list[str]:
49
47
  """
50
48
  Get list of region codes only.
51
49
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  import warnings
8
8
  from dataclasses import dataclass, field
9
- from typing import Any, Optional
9
+ from typing import Any
10
10
 
11
11
  from fastapi import WebSocket
12
12
 
@@ -36,7 +36,7 @@ except ImportError:
36
36
  handle_sigint: bool = field(init=False, kw_only=True)
37
37
  handle_sigterm: bool = field(init=False, kw_only=True)
38
38
  pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
39
- body: Optional[Any] = field(default_factory=dict, kw_only=True)
39
+ body: Any | None = field(default_factory=dict, kw_only=True)
40
40
 
41
41
  def __post_init__(self):
42
42
  self.handle_sigint = False
@@ -94,11 +94,11 @@ class SessionArguments:
94
94
  The arguments are received by the bot() entry point.
95
95
 
96
96
  Parameters:
97
- session_id (Optional[str]): The unique identifier for the session.
97
+ session_id (str | None): The unique identifier for the session.
98
98
  This is used to track the session across requests.
99
99
  """
100
100
 
101
- session_id: Optional[str]
101
+ session_id: str | None
102
102
 
103
103
 
104
104
  @dataclass