pipecatcloud 0.1.1__tar.gz → 0.1.3__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 (45) hide show
  1. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/CHANGELOG.md +36 -0
  2. {pipecatcloud-0.1.1/src/pipecatcloud.egg-info → pipecatcloud-0.1.3}/PKG-INFO +1 -1
  3. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/pyproject.toml +1 -1
  4. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/__init__.py +18 -7
  5. pipecatcloud-0.1.3/src/pipecatcloud/__version__.py +16 -0
  6. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/console_utils.py +4 -3
  7. pipecatcloud-0.1.3/src/pipecatcloud/agent.py +52 -0
  8. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/api.py +21 -5
  9. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/agent.py +93 -21
  10. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/deploy.py +41 -27
  11. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/exception.py +3 -1
  12. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/session.py +15 -0
  13. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3/src/pipecatcloud.egg-info}/PKG-INFO +1 -1
  14. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
  15. pipecatcloud-0.1.1/src/pipecatcloud/__version__.py +0 -12
  16. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.github/workflows/publish-pypi.yml +0 -0
  17. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.github/workflows/publish-test.yml +0 -0
  18. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.github/workflows/python-publish.yml +0 -0
  19. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.gitignore +0 -0
  20. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/LICENSE +0 -0
  21. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/README.md +0 -0
  22. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/dev-requirements.txt +0 -0
  23. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/pipecat-cloud.png +0 -0
  24. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/setup.cfg +0 -0
  25. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/__main__.py +0 -0
  26. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/__init__.py +0 -0
  27. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/async_utils.py +0 -0
  28. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  29. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  30. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/local_runner.py +0 -0
  31. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/__init__.py +0 -0
  32. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/api.py +0 -0
  33. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  34. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/auth.py +0 -0
  35. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/init.py +0 -0
  36. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  37. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/run.py +0 -0
  38. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  39. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/config.py +0 -0
  40. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/entry_point.py +0 -0
  41. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/config.py +0 -0
  42. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  43. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  44. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/requires.txt +0 -0
  45. {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/top_level.txt +0 -0
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+
12
+ - `deploy` now correctly handles error states returned by the API
13
+
14
+ - `deploy` checks revision status vs. general service ready status (when updating)
15
+
16
+ ### Added
17
+
18
+ - `agent logs` now accepts a `--deployment_id` / `-d` argument for filtering
19
+ by specific deployment ID
20
+
21
+ - `agent start` now accepts `--daily-properties` / `-p` for customizing Daily
22
+ room settings when used with `--use-daily`.
23
+
24
+ - Added `daily_room_properties` to `SessionParams` in SDK for configuring Daily
25
+ rooms when creating sessions.
26
+
27
+ - Added an export for the `PipecatSessionArguments` class.
28
+
29
+ ### Fixed
30
+
31
+ - Fix an issue where custom `data` resulted in an agent not starting.
32
+
33
+ - Fixed an issue where the link returned by `pcc agent start` was not clickable
34
+ in IDEs.
35
+
36
+ ## [0.1.2] - 2025-03-12
37
+
38
+ ### Added
39
+
40
+ - `agent.py` data classes for use in base images, providing guidance on params.
41
+
42
+ ### Fixed
43
+
44
+ - Lookup issue when passing an image pull secret to the `deploy` command.
45
+
10
46
  ### Changed
11
47
 
12
48
  - Change the of deploy checks from 30 to 18, reducing the overall time for a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pipecatcloud
3
- Version: 0.1.1
3
+ Version: 0.1.3
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/
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pipecatcloud"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  requires-python = ">=3.10"
9
9
  description = "Cloud hosting for Pipecat AI applications"
10
10
  license = { text = "BSD 2-Clause License" }
@@ -9,7 +9,13 @@ import sys
9
9
 
10
10
  from loguru import logger
11
11
 
12
- from .exception import (
12
+ from pipecatcloud.agent import (
13
+ DailySessionArguments,
14
+ PipecatSessionArguments,
15
+ SessionArguments,
16
+ WebSocketSessionArguments,
17
+ )
18
+ from pipecatcloud.exception import (
13
19
  AgentNotHealthyError,
14
20
  AgentStartError,
15
21
  AuthError,
@@ -18,22 +24,27 @@ from .exception import (
18
24
  Error,
19
25
  InvalidError,
20
26
  )
21
- from .session import Session, SessionParams
27
+ from pipecatcloud.session import Session, SessionParams
22
28
 
23
29
  logger.remove()
24
30
  logger.add(sys.stderr, level=str(os.getenv("PCC_LOG_LEVEL", "INFO")))
25
31
 
26
32
 
27
33
  __all__ = [
34
+ # Agent classes
35
+ "DailySessionArguments",
36
+ "PipecatSessionArguments",
37
+ "SessionArguments",
38
+ "WebSocketSessionArguments",
28
39
  # Session classes
29
40
  "Session",
30
41
  "SessionParams",
31
42
  # Exception classes
32
- "Error",
33
- "ConfigFileError",
34
- "AuthError",
35
- "InvalidError",
36
- "ConfigError",
37
43
  "AgentNotHealthyError",
38
44
  "AgentStartError",
45
+ "AuthError",
46
+ "ConfigError",
47
+ "ConfigFileError",
48
+ "Error",
49
+ "InvalidError",
39
50
  ]
@@ -0,0 +1,16 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ try:
8
+ from importlib.metadata import version as get_version
9
+ version = get_version("pipecatcloud")
10
+ except ImportError:
11
+ # For Python < 3.8
12
+ try:
13
+ from importlib_metadata import version as get_version
14
+ version = get_version("pipecatcloud")
15
+ except ImportError:
16
+ version = "Unknown"
@@ -67,8 +67,7 @@ class PipecatConsole(Console):
67
67
  title_align="left",
68
68
  subtitle_align="left",
69
69
  border_style="red",
70
- )
71
- )
70
+ ))
72
71
 
73
72
  def api_error(
74
73
  self,
@@ -79,7 +78,9 @@ class PipecatConsole(Console):
79
78
  DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
80
79
 
81
80
  if isinstance(error_code, dict):
82
- error_message = error_code.get("error", DEFAULT_ERROR_MESSAGE)
81
+ error_message = error_code.get(
82
+ "error", None) or error_code.get(
83
+ "message", None) or DEFAULT_ERROR_MESSAGE
83
84
  code = error_code.get("code")
84
85
  else:
85
86
  error_message = str(error_code) if error_code else DEFAULT_ERROR_MESSAGE
@@ -0,0 +1,52 @@
1
+ #
2
+ # Copyright (c) 2025, Daily
3
+ #
4
+ # SPDX-License-Identifier: BSD 2-Clause License
5
+ #
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Optional
9
+
10
+ from fastapi import WebSocket
11
+
12
+
13
+ @dataclass
14
+ class SessionArguments:
15
+ """Base class for common agent session arguments. The arguments are received
16
+ by the bot() entry point.
17
+
18
+ """
19
+
20
+ session_id: Optional[str]
21
+
22
+
23
+ @dataclass
24
+ class PipecatSessionArguments(SessionArguments):
25
+ """Standard Pipecat Cloud agent session arguments. The arguments are
26
+ received by the bot() entry point.
27
+
28
+ """
29
+
30
+ body: Any
31
+
32
+
33
+ @dataclass
34
+ class DailySessionArguments(SessionArguments):
35
+ """Daily based agent session arguments. The arguments are received by the
36
+ bot() entry point.
37
+
38
+ """
39
+
40
+ room_url: str
41
+ token: str
42
+ body: Any
43
+
44
+
45
+ @dataclass
46
+ class WebSocketSessionArguments(SessionArguments):
47
+ """Websocket based agent session arguments. The arguments are received by
48
+ the bot() entry point.
49
+
50
+ """
51
+
52
+ websocket: WebSocket
@@ -4,8 +4,9 @@
4
4
  # SPDX-License-Identifier: BSD 2-Clause License
5
5
  #
6
6
 
7
+ import json
7
8
  from functools import wraps
8
- from typing import Callable, List, Optional
9
+ from typing import Callable, List, Optional, Union
9
10
 
10
11
  import aiohttp
11
12
  from loguru import logger
@@ -368,13 +369,24 @@ class _API:
368
369
  return self.create_api_method(self._agents)
369
370
 
370
371
  async def _start_agent(
371
- self, agent_name: str, api_key: str, use_daily: bool, data: Optional[str] = None
372
+ self,
373
+ agent_name: str,
374
+ api_key: str,
375
+ use_daily: bool,
376
+ data: Optional[str] = None,
377
+ daily_properties: Optional[str] = None,
372
378
  ) -> dict | None:
373
379
  url = f"{self.construct_api_url('start_path').format(service=agent_name)}"
374
380
 
375
381
  payload: dict = {"createDailyRoom": use_daily}
382
+
383
+ # Add data to payload if provided
376
384
  if data is not None:
377
- payload["body"] = data
385
+ payload["body"] = json.loads(data)
386
+
387
+ # Add Daily room properties only if use_daily is True
388
+ if use_daily and daily_properties:
389
+ payload["dailyRoomProperties"] = json.loads(daily_properties)
378
390
 
379
391
  return await self._base_request(
380
392
  "POST", url, override_token=api_key, json=payload, not_found_is_empty=True
@@ -392,9 +404,13 @@ class _API:
392
404
  def agent_delete(self):
393
405
  return self.create_api_method(self._agent_delete)
394
406
 
395
- async def _agent_logs(self, agent_name: str, org: str, limit: int = 100) -> dict | None:
407
+ async def _agent_logs(self, agent_name: str, org: str, limit: int = 100,
408
+ deployment_id: Optional[str] = None) -> dict | None:
396
409
  url = f"{self.construct_api_url('services_logs_path').format(org=org, service=agent_name)}"
397
- return await self._base_request("GET", url, params={"limit": limit})
410
+ params: dict[str, Union[int, str]] = {"limit": limit}
411
+ if deployment_id:
412
+ params["deploymentId"] = deployment_id
413
+ return await self._base_request("GET", url, params=params)
398
414
 
399
415
  @property
400
416
  def agent_logs(self):
@@ -42,16 +42,19 @@ async def list(
42
42
  ):
43
43
  org = organization or config.get("org")
44
44
 
45
- with console.status(f"[dim]Fetching agents for organization: [bold]'{org}'[/bold][/dim]", spinner="dots"):
45
+ with console.status(
46
+ f"[dim]Fetching agents for organization: [bold]'{org}'[/bold][/dim]", spinner="dots"
47
+ ):
46
48
  data, error = await API.agents(org=org)
47
49
 
48
50
  if error:
49
- typer.Exit()
51
+ return typer.Exit()
50
52
 
51
53
  if not data or len(data) == 0:
52
54
  console.error(
53
55
  f"[red]No agents found for namespace / organization '{org}'[/red]\n\n"
54
- f"[dim]Please deploy an agent first using[/dim] [bold cyan]{PIPECAT_CLI_NAME} deploy[/bold cyan]")
56
+ f"[dim]Please deploy an agent first using[/dim] [bold cyan]{PIPECAT_CLI_NAME} deploy[/bold cyan]"
57
+ )
55
58
  return typer.Exit(1)
56
59
 
57
60
  else:
@@ -92,6 +95,8 @@ async def status(
92
95
  ) as live:
93
96
  data, error = await API.agent(agent_name=agent_name, org=org, live=live)
94
97
 
98
+ logger.debug(f"Agent status: {data}")
99
+
95
100
  live.stop()
96
101
 
97
102
  if error:
@@ -147,6 +152,25 @@ async def status(
147
152
  border_style="dim",
148
153
  )
149
154
 
155
+ # Error status
156
+ error_panel = None
157
+ errors = data.get("errors", [])
158
+ if errors and len(errors) > 0:
159
+ error_table = Table(show_header=False, show_lines=False, box=box.SIMPLE)
160
+ error_table.add_column("Code")
161
+ error_table.add_column("Message")
162
+ for error in errors:
163
+ error_table.add_row(
164
+ f"[bold red]{error['code']}[/bold red]",
165
+ f"[red]{error.get('message', None) or error.get('error', 'Unknown error')}[/red]",
166
+ )
167
+ error_panel = Panel(
168
+ error_table,
169
+ title="[bold red]Agent errors:[/bold red]",
170
+ title_align="left",
171
+ border_style="red",
172
+ )
173
+
150
174
  color = "bold green" if data["ready"] else "bold yellow"
151
175
  subtitle = (
152
176
  f"[dim]Start a new active session with[/dim] [bold cyan]{PIPECAT_CLI_NAME} agent start {agent_name}[/bold cyan]"
@@ -163,6 +187,7 @@ async def status(
163
187
  border_style="green" if data["ready"] else "yellow",
164
188
  expand=False,
165
189
  ),
190
+ error_panel if error_panel else "",
166
191
  ),
167
192
  title=f"Status for agent [bold]{agent_name}[/bold]",
168
193
  title_align="left",
@@ -246,30 +271,51 @@ class LogLevelColors(str, Enum):
246
271
  @synchronizer.create_blocking
247
272
  @requires_login
248
273
  async def logs(
249
- agent_name: str,
250
- organization: str = typer.Option(
251
- None, "--organization", "-o", help="Organization to get status of agent for"
252
- ),
253
- level: LogLevel = typer.Option(None, "--level", "-l", help="Level of logs to get"),
254
- format: LogFormat = typer.Option(LogFormat.TEXT, "--format", "-f", help="Logs format"),
255
- limit: int = typer.Option(100, "--limit", "-n", help="Number of logs to get"),
274
+ agent_name: str,
275
+ organization: str = typer.Option(
276
+ None,
277
+ "--organization",
278
+ "-o",
279
+ help="Organization to get status of agent for"),
280
+ level: LogLevel = typer.Option(
281
+ None,
282
+ "--level",
283
+ "-l",
284
+ help="Level of logs to get"),
285
+ format: LogFormat = typer.Option(
286
+ LogFormat.TEXT,
287
+ "--format",
288
+ "-f",
289
+ help="Logs format"),
290
+ limit: int = typer.Option(
291
+ 100,
292
+ "--limit",
293
+ "-n",
294
+ help="Number of logs to get"),
295
+ deployment_id: str = typer.Option(
296
+ None,
297
+ "--deployment",
298
+ "-d",
299
+ help="Filter logs by deployment ID"),
256
300
  ):
257
301
  org = organization or config.get("org")
258
302
 
303
+ status_text = "agent" if not deployment_id else f"deployment ({deployment_id})"
304
+
259
305
  with console.status(
260
- f"[dim]Fetching logs for agent: [bold]'{agent_name}'[/bold] with severity: [bold cyan]{level.value if level else 'ALL'}[/bold cyan][/dim]",
306
+ f"[dim]Fetching logs for {status_text}: [bold]'{agent_name}'[/bold] with severity: [bold cyan]{level.value if level else 'ALL'}[/bold cyan][/dim]",
261
307
  spinner="dots",
262
308
  ):
263
- data, error = await API.agent_logs(agent_name=agent_name, org=org, limit=limit)
309
+ data, error = await API.agent_logs(agent_name=agent_name, org=org, limit=limit, deployment_id=deployment_id)
264
310
 
265
311
  if not data or not data.get("logs"):
266
312
  console.print("[dim]No logs found for agent[/dim]")
267
313
  return typer.Exit(1)
268
314
 
269
- for l in data["logs"]:
270
- log_data = l.get("log", "")
315
+ for log in data["logs"]:
316
+ log_data = log.get("log", "")
271
317
  if log_data:
272
- timestamp = format_timestamp(l.get("timestamp", ""))
318
+ timestamp = format_timestamp(log.get("timestamp", ""))
273
319
  severity = LogLevel.INFO
274
320
  for log_severity in LogLevel:
275
321
  if log_severity.value in log_data.upper():
@@ -282,9 +328,9 @@ async def logs(
282
328
  if format == LogFormat.TEXT:
283
329
  color = getattr(LogLevelColors, severity, LogLevelColors.DEBUG).value
284
330
  console.print(Text(timestamp, style="bold dim"), end=" ")
285
- console.print(Text(l.get("log", ""), style=color))
331
+ console.print(Text(log_data, style=color))
286
332
  elif format == LogFormat.JSON:
287
- line = {"timestamp": timestamp, "log": l.get("log", "")}
333
+ line = {"timestamp": timestamp, "log": log_data}
288
334
  console.print(Text(json.dumps(line, ensure_ascii=False), style="gray"))
289
335
 
290
336
 
@@ -360,7 +406,6 @@ async def deployments(
360
406
  response.raise_for_status()
361
407
 
362
408
  data = await response.json()
363
- print(data)
364
409
 
365
410
  table = Table(
366
411
  show_header=True,
@@ -428,6 +473,13 @@ async def start(
428
473
  help="Create a Daily WebRTC session for the agent",
429
474
  rich_help_panel="Start Configuration",
430
475
  ),
476
+ daily_properties: str = typer.Option(
477
+ None,
478
+ "--daily-properties",
479
+ "-p",
480
+ help="Daily room properties (stringified JSON)",
481
+ rich_help_panel="Start Configuration",
482
+ ),
431
483
  organization: str = typer.Option(
432
484
  None,
433
485
  "--organization",
@@ -450,22 +502,39 @@ async def start(
450
502
  title="Public API Key Required",
451
503
  title_align="left",
452
504
  border_style="yellow",
453
- ))
505
+ )
506
+ )
454
507
 
455
508
  return typer.Exit(1)
456
509
 
510
+ # Validate daily_properties JSON if provided
511
+ if use_daily and daily_properties:
512
+ try:
513
+ json.loads(daily_properties)
514
+ except json.JSONDecodeError as e:
515
+ console.error(f"Invalid JSON format for Daily room properties: {daily_properties}")
516
+ console.print(f"[dim]JSON error: {str(e)}[/dim]")
517
+ return typer.Exit(1)
518
+
457
519
  # Confirm start request
458
520
  if not force:
521
+ daily_props_display = daily_properties or "None"
522
+ # Truncate display of daily properties if too long
523
+ if daily_properties and len(daily_properties) > 80:
524
+ daily_props_display = daily_properties[:77] + "..."
525
+
459
526
  console.print(
460
527
  Panel(
461
528
  f"Agent Name: {agent_name}\n"
462
529
  f"Public API Key: {default_public_api_key_name} [dim]{default_public_api_key}[/dim]\n"
463
530
  f"Use Daily: {use_daily}\n"
531
+ f"Daily Properties: {daily_props_display}\n"
464
532
  f"Data: {data}",
465
533
  title=f"[bold]Start Request for agent: {agent_name}[/bold]",
466
534
  title_align="left",
467
535
  border_style="yellow",
468
- ))
536
+ )
537
+ )
469
538
  if not await questionary.confirm(
470
539
  "Are you sure you want to start an active session for this agent?"
471
540
  ).ask_async():
@@ -495,10 +564,13 @@ async def start(
495
564
  api_key=default_public_api_key,
496
565
  use_daily=use_daily,
497
566
  data=data,
567
+ daily_properties=daily_properties,
498
568
  live=live,
499
569
  )
500
570
 
501
571
  if error:
572
+ live.stop()
573
+ # Error is displayed from start_agent create_api_method wrapper
502
574
  return typer.Exit(1)
503
575
 
504
576
  live.stop()
@@ -510,4 +582,4 @@ async def start(
510
582
  if daily_room:
511
583
  url = f"{daily_room}?t={daily_token}"
512
584
  console.print("\nJoin your session by visiting the link below:")
513
- console.print(f"[link='{url}']{url}[/link]")
585
+ console.print(f"[link={url}]{url}[/link]")
@@ -7,6 +7,7 @@
7
7
  import asyncio
8
8
 
9
9
  import typer
10
+ from loguru import logger
10
11
  from rich.console import Group
11
12
  from rich.live import Live
12
13
  from rich.panel import Panel
@@ -94,7 +95,7 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
94
95
  )
95
96
 
96
97
  if error:
97
- if error == 400:
98
+ if error.get("code") == "400":
98
99
  creds_exist = True
99
100
  else:
100
101
  API.print_error()
@@ -131,7 +132,6 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
131
132
  # 3. Poll status until healthy
132
133
  """
133
134
  active_deployment_id = None
134
- has_failed = False
135
135
  is_ready = False
136
136
  checks_performed = 0
137
137
 
@@ -145,23 +145,44 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
145
145
  ) as status:
146
146
  try:
147
147
  while checks_performed < MAX_ALIVE_CHECKS:
148
+ logger.debug("Polling for deployment status")
149
+
148
150
  # Get deployment status
149
151
  agent_status, error = await API.agent(
150
152
  agent_name=params.agent_name, org=org, live=None
151
153
  )
152
154
 
155
+ logger.debug(f"Deployment status: {agent_status}")
156
+
157
+ # Look for any error messages in the agent status
158
+ # Exit out of the polling loop if we find an error
159
+ status_errors = agent_status.get("errors", [])
160
+ if status_errors and len(status_errors) > 0:
161
+ status.stop()
162
+ # Pluck the first error message
163
+ error_message = status_errors[0]
164
+ if "code" in error_message and "message" in error_message:
165
+ console.api_error(error_message, "Agent deployment failed")
166
+ else:
167
+ console.error(f"Deployment failed with an unknown error: {status_errors}")
168
+ return typer.Exit()
169
+
153
170
  if error:
154
171
  status.stop()
155
172
  console.error("Error checking deployment status")
156
173
  return typer.Exit()
157
174
 
158
- # Update deployment ID if received (silently)
175
+ # Update deployment ID if received
159
176
  if not active_deployment_id and agent_status.get("activeDeploymentId"):
160
177
  active_deployment_id = agent_status["activeDeploymentId"]
161
178
  deployment_status_message = f"[dim]Waiting for deployment to become ready (deployment ID: {active_deployment_id})...[/dim]"
179
+ status.update(deployment_status_message)
180
+
181
+ # If we have an active deployment ID, start tailing the log output
182
+ # @TODO - Implement this
162
183
 
163
184
  # Check if deployment is ready
164
- if agent_status.get("ready"):
185
+ if agent_status.get("activeDeploymentReady", False):
165
186
  is_ready = True
166
187
  break
167
188
 
@@ -176,24 +197,24 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
176
197
  )
177
198
  return typer.Exit()
178
199
 
179
- # Final result handling
180
- if has_failed:
181
- console.print(has_failed["message"])
182
- console.error("Deployment failed with the above error")
183
- return typer.Exit()
184
-
185
200
  if is_ready:
201
+ public_api_key = config.get("default_public_key")
202
+ extra_message = ""
203
+ if not public_api_key:
204
+ extra_message = "\n\n[yellow]Note: if you have not already created a public API key (required to start a session), you can do so by running:\n[/yellow]"
205
+ extra_message += f"[bold yellow]`{PIPECAT_CLI_NAME} organizations keys create`[/bold yellow]"
206
+
186
207
  console.success(
187
208
  f"Agent deployment [bold]'{params.agent_name}'[/bold] is ready\n\n"
188
- f"[dim]Start a session with your new agent by running:\n[/dim]"
189
- f"[bold]`{PIPECAT_CLI_NAME} agent start {params.agent_name}`[/bold]",
209
+ f"[white]Start a session with your new agent by running:\n[/white]"
210
+ f"[bold]`{PIPECAT_CLI_NAME} agent start {params.agent_name}`[/bold]"
211
+ f"{extra_message}",
190
212
  title_extra=f"{'Update' if existing_agent else 'Deployment'} complete",
191
213
  )
192
214
  else:
193
215
  console.error(
194
216
  f"Deployment did not enter ready state within {MAX_ALIVE_CHECKS * ALIVE_CHECK_SLEEP} seconds. "
195
- f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`"
196
- )
217
+ f"Please check logs with `{PIPECAT_CLI_NAME} agent logs {params.agent_name}`")
197
218
 
198
219
  return typer.Exit()
199
220
 
@@ -306,24 +327,17 @@ def create_deploy_command(app: typer.Typer):
306
327
  (f"[bold white]Agent name:[/bold white] [green]{partial_config.agent_name}[/green]"),
307
328
  (f"[bold white]Image:[/bold white] [green]{partial_config.image}[/green]"),
308
329
  (f"[bold white]Organization:[/bold white] [green]{org}[/green]"),
309
- (
310
- f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"
311
- ),
312
- (
313
- f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"
314
- ),
330
+ (f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"),
331
+ (f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"),
315
332
  "\n[dim]Scaling configuration:[/dim]",
316
333
  table,
317
- *(
318
- []
319
- if partial_config.scaling.min_instances
320
- else [
334
+ *
335
+ (
336
+ [] if partial_config.scaling.min_instances else [
321
337
  Text(
322
338
  "Note: Deploying with 0 minimum instances may result in cold starts",
323
339
  style="red",
324
- )
325
- ]
326
- ),
340
+ )]),
327
341
  )
328
342
 
329
343
  console.print(
@@ -6,6 +6,8 @@
6
6
 
7
7
  from typing import Optional, Union
8
8
 
9
+ from pipecatcloud.cli import PIPECAT_CLI_NAME
10
+
9
11
 
10
12
  class Error(Exception):
11
13
  """Base class for exceptions in this module."""
@@ -24,7 +26,7 @@ class AuthError(Error):
24
26
 
25
27
  def __init__(
26
28
  self,
27
- message: str = "Unauthorized / token expired. Please run `pcc auth login` to login again.",
29
+ message: str = f"Unauthorized / token expired. Please run `{PIPECAT_CLI_NAME} auth login` to login again.",
28
30
  ):
29
31
  self.message = message
30
32
  super().__init__(self.message)
@@ -21,10 +21,14 @@ class SessionParams:
21
21
  Args:
22
22
  data: Optional dictionary of data to pass to the agent.
23
23
  use_daily: If True, creates a Daily WebRTC room for the session.
24
+ daily_room_properties: Optional dictionary of properties to configure the Daily room.
25
+ Only used when use_daily=True. See Daily.co API documentation for available properties:
26
+ https://docs.daily.co/reference/rest-api/rooms/config
24
27
  """
25
28
 
26
29
  data: Optional[Dict[str, Any]] = None
27
30
  use_daily: Optional[bool] = False
31
+ daily_room_properties: Optional[Dict[str, Any]] = None
28
32
 
29
33
 
30
34
  class Session:
@@ -90,12 +94,23 @@ class Session:
90
94
  # If it's already a string or other type, use as is
91
95
  data_param = self.params.data
92
96
 
97
+ # Only process daily_room_properties if use_daily is True
98
+ daily_properties_param = None
99
+ if self.params.use_daily and self.params.daily_room_properties:
100
+ # Convert dictionary to JSON string
101
+ if isinstance(self.params.daily_room_properties, dict):
102
+ daily_properties_param = json.dumps(self.params.daily_room_properties)
103
+ else:
104
+ # If it's already a string, use as is
105
+ daily_properties_param = self.params.daily_room_properties
106
+
93
107
  # Call the method similar to how the CLI does it
94
108
  result, error = await api.start_agent(
95
109
  agent_name=self.agent_name,
96
110
  api_key=self.api_key,
97
111
  use_daily=bool(self.params.use_daily),
98
112
  data=data_param,
113
+ daily_properties=daily_properties_param,
99
114
  )
100
115
 
101
116
  if error:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pipecatcloud
3
- Version: 0.1.1
3
+ Version: 0.1.3
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/
@@ -11,6 +11,7 @@ pyproject.toml
11
11
  src/pipecatcloud/__init__.py
12
12
  src/pipecatcloud/__main__.py
13
13
  src/pipecatcloud/__version__.py
14
+ src/pipecatcloud/agent.py
14
15
  src/pipecatcloud/api.py
15
16
  src/pipecatcloud/config.py
16
17
  src/pipecatcloud/exception.py
@@ -1,12 +0,0 @@
1
- #
2
- # Copyright (c) 2025, Daily
3
- #
4
- # SPDX-License-Identifier: BSD 2-Clause License
5
- #
6
-
7
- from pathlib import Path
8
-
9
- import toml
10
-
11
- pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
12
- version = toml.load(pyproject_path)["project"]["version"]
File without changes
File without changes
File without changes
File without changes