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.
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/CHANGELOG.md +36 -0
- {pipecatcloud-0.1.1/src/pipecatcloud.egg-info → pipecatcloud-0.1.3}/PKG-INFO +1 -1
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/pyproject.toml +1 -1
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/__init__.py +18 -7
- pipecatcloud-0.1.3/src/pipecatcloud/__version__.py +16 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/console_utils.py +4 -3
- pipecatcloud-0.1.3/src/pipecatcloud/agent.py +52 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/api.py +21 -5
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/agent.py +93 -21
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/deploy.py +41 -27
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/exception.py +3 -1
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/session.py +15 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3/src/pipecatcloud.egg-info}/PKG-INFO +1 -1
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
- pipecatcloud-0.1.1/src/pipecatcloud/__version__.py +0 -12
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.github/workflows/python-publish.yml +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/.gitignore +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/LICENSE +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/README.md +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/dev-requirements.txt +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/setup.cfg +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/_utils/local_runner.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/init.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/run.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/commands/secrets.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.1.1 → pipecatcloud-0.1.3}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {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
|
|
@@ -9,7 +9,13 @@ import sys
|
|
|
9
9
|
|
|
10
10
|
from loguru import logger
|
|
11
11
|
|
|
12
|
-
from .
|
|
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(
|
|
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,
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
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
|
|
270
|
-
log_data =
|
|
315
|
+
for log in data["logs"]:
|
|
316
|
+
log_data = log.get("log", "")
|
|
271
317
|
if log_data:
|
|
272
|
-
timestamp = format_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(
|
|
331
|
+
console.print(Text(log_data, style=color))
|
|
286
332
|
elif format == LogFormat.JSON:
|
|
287
|
-
line = {"timestamp": timestamp, "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=
|
|
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
|
|
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("
|
|
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"[
|
|
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
|
-
|
|
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 `
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|