pipecatcloud 0.0.9__tar.gz → 0.0.10__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.0.9 → pipecatcloud-0.0.10}/CHANGELOG.md +15 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/PKG-INFO +21 -11
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/README.md +20 -10
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/pyproject.toml +1 -1
- pipecatcloud-0.0.10/src/pipecatcloud/__init__.py +33 -0
- pipecatcloud-0.0.10/src/pipecatcloud/_utils/console_utils.py +124 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/api.py +22 -5
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/agent.py +80 -71
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/deploy.py +2 -32
- pipecatcloud-0.0.10/src/pipecatcloud/cli/commands/init.py +112 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/secrets.py +0 -7
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/config.py +2 -2
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/exception.py +13 -11
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/session.py +19 -10
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud.egg-info/PKG-INFO +21 -11
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud.egg-info/SOURCES.txt +0 -2
- pipecatcloud-0.0.9/src/pipecatcloud/__init__.py +0 -7
- pipecatcloud-0.0.9/src/pipecatcloud/_utils/agent_utils.py +0 -9
- pipecatcloud-0.0.9/src/pipecatcloud/_utils/console_utils.py +0 -78
- pipecatcloud-0.0.9/src/pipecatcloud/cli/commands/init.py +0 -16
- pipecatcloud-0.0.9/src/pipecatcloud/errors.py +0 -11
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/.github/workflows/python-publish.yml +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/.gitignore +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/LICENSE +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/dev-requirements.txt +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/publish-pypi.yml +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/publish-test.yml +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/setup.cfg +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/_utils/local_runner.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/commands/run.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {pipecatcloud-0.0.9 → pipecatcloud-0.0.10}/src/pipecatcloud.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,21 @@ 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.0.10] - 2025-03-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `init` convenience command will now populate the working directory with files from the starter project
|
|
12
|
+
- `agent log` allows for optional severity level filtering
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- `agent status` and `deploy` no longer show superfluous data
|
|
16
|
+
- `session.py` now correctly handles errors when starting agents
|
|
17
|
+
- `secrets set` no longer prompts twice for confirmation if the secret set does not exist
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
- `errors.py` removed as redundant (error message and code returned via API)
|
|
21
|
+
- `agent_utils.py` removed as redundant (error message and code returned via API)
|
|
22
|
+
|
|
8
23
|
## [0.0.9] - 2025-02-27
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
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/
|
|
@@ -70,16 +70,26 @@ pcc auth login
|
|
|
70
70
|
|
|
71
71
|
### Usage in Python scripts
|
|
72
72
|
|
|
73
|
-
If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.
|
|
73
|
+
If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.session` module.
|
|
74
74
|
|
|
75
75
|
```python
|
|
76
|
-
from pipecatcloud.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
from pipecatcloud.session import Session
|
|
77
|
+
from pipecatcloud.exception import AgentStartError
|
|
78
|
+
import asyncio
|
|
79
|
+
|
|
80
|
+
async def main():
|
|
81
|
+
session = Session(
|
|
82
|
+
agent_name="your-agent-name",
|
|
83
|
+
api_key="pk_...",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
await session.start()
|
|
88
|
+
except AgentStartError as e:
|
|
89
|
+
print(e)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise (e)
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
asyncio.run(main())
|
|
85
95
|
```
|
|
@@ -50,16 +50,26 @@ pcc auth login
|
|
|
50
50
|
|
|
51
51
|
### Usage in Python scripts
|
|
52
52
|
|
|
53
|
-
If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.
|
|
53
|
+
If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.session` module.
|
|
54
54
|
|
|
55
55
|
```python
|
|
56
|
-
from pipecatcloud.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
from pipecatcloud.session import Session
|
|
57
|
+
from pipecatcloud.exception import AgentStartError
|
|
58
|
+
import asyncio
|
|
59
|
+
|
|
60
|
+
async def main():
|
|
61
|
+
session = Session(
|
|
62
|
+
agent_name="your-agent-name",
|
|
63
|
+
api_key="pk_...",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
await session.start()
|
|
68
|
+
except AgentStartError as e:
|
|
69
|
+
print(e)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise (e)
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
asyncio.run(main())
|
|
65
75
|
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
from .exception import (
|
|
7
|
+
AgentNotHealthyError,
|
|
8
|
+
AgentStartError,
|
|
9
|
+
AuthError,
|
|
10
|
+
ConfigError,
|
|
11
|
+
ConfigFileError,
|
|
12
|
+
Error,
|
|
13
|
+
InvalidError,
|
|
14
|
+
)
|
|
15
|
+
from .session import Session, SessionParams
|
|
16
|
+
|
|
17
|
+
logger.remove()
|
|
18
|
+
logger.add(sys.stderr, level=str(os.getenv("PCC_LOG_LEVEL", "INFO")))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Session classes
|
|
23
|
+
"Session",
|
|
24
|
+
"SessionParams",
|
|
25
|
+
# Exception classes
|
|
26
|
+
"Error",
|
|
27
|
+
"ConfigFileError",
|
|
28
|
+
"AuthError",
|
|
29
|
+
"InvalidError",
|
|
30
|
+
"ConfigError",
|
|
31
|
+
"AgentNotHealthyError",
|
|
32
|
+
"AgentStartError",
|
|
33
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from typing import Optional, Union
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
from pipecatcloud.cli import PANEL_TITLE_ERROR, PANEL_TITLE_SUCCESS, PIPECAT_CLI_NAME
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PipecatConsole(Console):
|
|
10
|
+
def success(
|
|
11
|
+
self,
|
|
12
|
+
message,
|
|
13
|
+
title: Optional[str] = None,
|
|
14
|
+
title_extra: Optional[str] = None,
|
|
15
|
+
subtitle: Optional[str] = None):
|
|
16
|
+
|
|
17
|
+
if not title:
|
|
18
|
+
title = f"{PANEL_TITLE_SUCCESS}{f' - {title_extra}' if title_extra is not None else ''}"
|
|
19
|
+
|
|
20
|
+
self.print(
|
|
21
|
+
Panel(
|
|
22
|
+
message,
|
|
23
|
+
title=f"[bold green]{title}[/bold green]",
|
|
24
|
+
subtitle=subtitle,
|
|
25
|
+
title_align="left",
|
|
26
|
+
subtitle_align="left",
|
|
27
|
+
border_style="green"))
|
|
28
|
+
|
|
29
|
+
def error(
|
|
30
|
+
self,
|
|
31
|
+
message,
|
|
32
|
+
title: Optional[str] = None,
|
|
33
|
+
title_extra: Optional[str] = None,
|
|
34
|
+
subtitle: Optional[str] = None):
|
|
35
|
+
|
|
36
|
+
if not title:
|
|
37
|
+
title = f"{PANEL_TITLE_ERROR}{f' - {title_extra}' if title_extra is not None else ''}"
|
|
38
|
+
|
|
39
|
+
self.print(
|
|
40
|
+
Panel(
|
|
41
|
+
message,
|
|
42
|
+
title=f"[bold red]{title}[/bold red]",
|
|
43
|
+
subtitle=subtitle,
|
|
44
|
+
title_align="left",
|
|
45
|
+
subtitle_align="left",
|
|
46
|
+
border_style="red"))
|
|
47
|
+
|
|
48
|
+
def cancel(self):
|
|
49
|
+
self.print("[yellow]Cancelled by user[/yellow]")
|
|
50
|
+
|
|
51
|
+
def unauthorized(self):
|
|
52
|
+
self.print(
|
|
53
|
+
Panel(
|
|
54
|
+
f"Unauthorized request / invalid user token.\n\nPlease log in again using [bold cyan]{PIPECAT_CLI_NAME} auth login[/bold cyan]",
|
|
55
|
+
title=f"[bold red]{PANEL_TITLE_ERROR} - Unauthorized (401)[/bold red]",
|
|
56
|
+
subtitle="",
|
|
57
|
+
title_align="left",
|
|
58
|
+
subtitle_align="left",
|
|
59
|
+
border_style="red"))
|
|
60
|
+
|
|
61
|
+
def api_error(
|
|
62
|
+
self,
|
|
63
|
+
error_code: Optional[Union[str, dict]] = None,
|
|
64
|
+
title: Optional[str] = "API Error",
|
|
65
|
+
hide_subtitle: bool = False):
|
|
66
|
+
DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
|
|
67
|
+
|
|
68
|
+
if isinstance(error_code, dict):
|
|
69
|
+
error_message = error_code.get("error", DEFAULT_ERROR_MESSAGE)
|
|
70
|
+
code = error_code.get("code")
|
|
71
|
+
else:
|
|
72
|
+
error_message = str(error_code) if error_code else DEFAULT_ERROR_MESSAGE
|
|
73
|
+
code = None
|
|
74
|
+
|
|
75
|
+
if not error_message:
|
|
76
|
+
hide_subtitle = True
|
|
77
|
+
|
|
78
|
+
self.print(
|
|
79
|
+
Panel(
|
|
80
|
+
f"[red]{title}[/red]\n\n"
|
|
81
|
+
f"[dim]Error message:[/dim]\n{error_message}",
|
|
82
|
+
title=f"[bold red]{PANEL_TITLE_ERROR}{f' - {code}' if code else ''}[/bold red]",
|
|
83
|
+
subtitle=f"[dim]Docs: https://docs.pipecat.daily.co/agents/error-codes#{code}[/dim]" if not hide_subtitle and code else None,
|
|
84
|
+
title_align="left",
|
|
85
|
+
subtitle_align="left",
|
|
86
|
+
border_style="red"))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
console = PipecatConsole()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def format_timestamp(timestamp: str) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Format a timestamp string to a more readable format.
|
|
95
|
+
Handles timestamps with variable microsecond precision.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
timestamp (str): The timestamp string in ISO format with microseconds (e.g. "2024-01-01T12:34:56.789Z")
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
str: The formatted timestamp string
|
|
102
|
+
"""
|
|
103
|
+
from datetime import datetime
|
|
104
|
+
|
|
105
|
+
# First try parsing the timestamp directly
|
|
106
|
+
try:
|
|
107
|
+
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S")
|
|
108
|
+
except ValueError:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
# Handle case where microseconds have higher precision
|
|
112
|
+
try:
|
|
113
|
+
parts = timestamp.split(".")
|
|
114
|
+
if len(parts) == 2 and parts[1].endswith("Z"):
|
|
115
|
+
# Truncate microseconds to 6 digits
|
|
116
|
+
microseconds = parts[1][:-1][:6].ljust(6, '0')
|
|
117
|
+
normalized = f"{parts[0]}.{microseconds}Z"
|
|
118
|
+
return datetime.strptime(normalized,
|
|
119
|
+
"%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M:%S")
|
|
120
|
+
except (ValueError, IndexError):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
# Return original if parsing fails
|
|
124
|
+
return timestamp
|
|
@@ -62,7 +62,18 @@ class _API():
|
|
|
62
62
|
if not response.ok:
|
|
63
63
|
if not_found_is_empty and response.status == 404:
|
|
64
64
|
return None
|
|
65
|
-
|
|
65
|
+
|
|
66
|
+
# Extract PCC error code, where applicable
|
|
67
|
+
if response.status == 400:
|
|
68
|
+
try:
|
|
69
|
+
error_data = await response.json()
|
|
70
|
+
self.error = error_data
|
|
71
|
+
except Exception as e:
|
|
72
|
+
# Fallback structure matching API format
|
|
73
|
+
self.error = {"error": "Bad Request", "code": str(response.status)}
|
|
74
|
+
else:
|
|
75
|
+
# Match API error format for non-400 errors
|
|
76
|
+
self.error = {"error": response.reason, "code": str(response.status)}
|
|
66
77
|
response.raise_for_status()
|
|
67
78
|
|
|
68
79
|
return await response.json()
|
|
@@ -91,14 +102,12 @@ class _API():
|
|
|
91
102
|
def print_error(self):
|
|
92
103
|
from pipecatcloud._utils.console_utils import console
|
|
93
104
|
|
|
94
|
-
# @TODO: handle coded errors here
|
|
95
|
-
|
|
96
105
|
if not self.error:
|
|
97
106
|
return
|
|
98
|
-
if self.error == 401:
|
|
107
|
+
if isinstance(self.error, dict) and self.error.get("code", "400") == "401":
|
|
99
108
|
console.unauthorized()
|
|
100
109
|
else:
|
|
101
|
-
console.api_error(
|
|
110
|
+
console.api_error(self.error)
|
|
102
111
|
|
|
103
112
|
def bubble_error(self):
|
|
104
113
|
self.bubble_next = True
|
|
@@ -362,3 +371,11 @@ class _API():
|
|
|
362
371
|
@property
|
|
363
372
|
def agent_delete(self):
|
|
364
373
|
return self.create_api_method(self._agent_delete)
|
|
374
|
+
|
|
375
|
+
async def _agent_logs(self, agent_name: str, org: str, limit: int = 100) -> dict | None:
|
|
376
|
+
url = f"{self.construct_api_url('services_logs_path').format(org=org, service=agent_name)}"
|
|
377
|
+
return await self._base_request("GET", url, params={"limit": limit})
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def agent_logs(self):
|
|
381
|
+
return self.create_api_method(self._agent_logs)
|
|
@@ -3,15 +3,17 @@ import questionary
|
|
|
3
3
|
import typer
|
|
4
4
|
from loguru import logger
|
|
5
5
|
from rich import box
|
|
6
|
-
from rich.console import
|
|
6
|
+
from rich.console import Group
|
|
7
|
+
from rich.columns import Columns
|
|
7
8
|
from rich.live import Live
|
|
8
9
|
from rich.panel import Panel
|
|
9
10
|
from rich.table import Table
|
|
11
|
+
from rich.text import Text
|
|
10
12
|
|
|
11
|
-
from
|
|
13
|
+
from enum import Enum
|
|
12
14
|
from pipecatcloud._utils.async_utils import synchronizer
|
|
13
15
|
from pipecatcloud._utils.auth_utils import requires_login
|
|
14
|
-
from pipecatcloud._utils.console_utils import console
|
|
16
|
+
from pipecatcloud._utils.console_utils import console, format_timestamp
|
|
15
17
|
from pipecatcloud.cli import PIPECAT_CLI_NAME
|
|
16
18
|
from pipecatcloud.cli.api import API
|
|
17
19
|
from pipecatcloud.cli.config import config
|
|
@@ -104,29 +106,6 @@ async def status(
|
|
|
104
106
|
console.error(f"No deployment data found for agent with name '{agent_name}'")
|
|
105
107
|
return typer.Exit()
|
|
106
108
|
|
|
107
|
-
# Conditions
|
|
108
|
-
conditions = data["conditions"]
|
|
109
|
-
table = Table(
|
|
110
|
-
show_header=True,
|
|
111
|
-
show_lines=True,
|
|
112
|
-
border_style="dim",
|
|
113
|
-
box=box.SIMPLE
|
|
114
|
-
)
|
|
115
|
-
table.add_column("Status")
|
|
116
|
-
table.add_column("Type")
|
|
117
|
-
table.add_column("Message")
|
|
118
|
-
table.add_column("Reason")
|
|
119
|
-
table.add_column("Date")
|
|
120
|
-
|
|
121
|
-
for condition in conditions:
|
|
122
|
-
table.add_row(
|
|
123
|
-
DEPLOY_STATUS_MAP.get(condition["status"], "[dim]Unknown[/dim]"),
|
|
124
|
-
condition['type'],
|
|
125
|
-
condition.get('message', 'No message'),
|
|
126
|
-
condition.get('reason', 'No reason'),
|
|
127
|
-
condition['lastTransitionTime']
|
|
128
|
-
)
|
|
129
|
-
|
|
130
109
|
# Deployment info
|
|
131
110
|
|
|
132
111
|
deployment_table = Table(
|
|
@@ -136,14 +115,14 @@ async def status(
|
|
|
136
115
|
)
|
|
137
116
|
deployment_table.add_column("Key")
|
|
138
117
|
deployment_table.add_column("Value")
|
|
139
|
-
deployment_table.add_row(
|
|
140
|
-
"[bold]Image:[/bold]",
|
|
141
|
-
str(data.get("deployment", {}).get("manifest", {}).get("spec", {}).get("image", "N/A")),
|
|
142
|
-
)
|
|
143
118
|
deployment_table.add_row(
|
|
144
119
|
"[bold]Active Session Count:[/bold]",
|
|
145
120
|
str(data.get("activeSessionCount", "N/A")),
|
|
146
121
|
)
|
|
122
|
+
deployment_table.add_row(
|
|
123
|
+
"[bold]Image:[/bold]",
|
|
124
|
+
str(data.get("deployment", {}).get("manifest", {}).get("spec", {}).get("image", "N/A")),
|
|
125
|
+
)
|
|
147
126
|
deployment_table.add_row(
|
|
148
127
|
"[bold]Active Deployment ID:[/bold]",
|
|
149
128
|
str(data.get("activeDeploymentId", "N/A")),
|
|
@@ -160,24 +139,17 @@ async def status(
|
|
|
160
139
|
# Autoscaling info
|
|
161
140
|
autoscaling_data = data.get("autoScaling", None)
|
|
162
141
|
if autoscaling_data:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)
|
|
175
|
-
autoscaling_table.add_row(
|
|
176
|
-
"[bold]Min instances:[/bold]",
|
|
177
|
-
str(autoscaling_data.get("minReplicas", 0)),
|
|
178
|
-
)
|
|
179
|
-
autoscaling_panel = Panel(
|
|
180
|
-
autoscaling_table,
|
|
142
|
+
scaling_renderables = [
|
|
143
|
+
Panel(
|
|
144
|
+
f"[bold]Minimum Instances[/bold]\n{autoscaling_data.get('minReplicas', 0)}",
|
|
145
|
+
expand=True),
|
|
146
|
+
Panel(
|
|
147
|
+
f"[bold]Maximum Instances[/bold]\n{autoscaling_data.get('maxReplicas', 0)}",
|
|
148
|
+
expand=True)]
|
|
149
|
+
scaling_panel = Panel(
|
|
150
|
+
Columns(
|
|
151
|
+
scaling_renderables
|
|
152
|
+
),
|
|
181
153
|
title="[bold]Scaling configuration:[/bold]",
|
|
182
154
|
title_align="left",
|
|
183
155
|
border_style="dim",
|
|
@@ -190,8 +162,7 @@ async def status(
|
|
|
190
162
|
Panel(
|
|
191
163
|
Group(
|
|
192
164
|
deployment_table,
|
|
193
|
-
|
|
194
|
-
table,
|
|
165
|
+
scaling_panel if scaling_panel else "",
|
|
195
166
|
Panel(
|
|
196
167
|
f"[{color}]Health: {'Ready' if data['ready'] else 'Stopped'}[/]",
|
|
197
168
|
border_style="green" if data['ready'] else "yellow",
|
|
@@ -252,31 +223,69 @@ async def scale():
|
|
|
252
223
|
console.error("Not implemented")
|
|
253
224
|
|
|
254
225
|
|
|
226
|
+
class LogLevel(str, Enum):
|
|
227
|
+
DEBUG = "DEBUG"
|
|
228
|
+
INFO = "INFO"
|
|
229
|
+
WARNING = "WARNING"
|
|
230
|
+
ERROR = "ERROR"
|
|
231
|
+
CRITICAL = "CRITICAL"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class LogLevelColors(str, Enum):
|
|
235
|
+
DEBUG = "dim white"
|
|
236
|
+
INFO = "white"
|
|
237
|
+
WARNING = "yellow"
|
|
238
|
+
ERROR = "red"
|
|
239
|
+
CRITICAL = "bold red"
|
|
240
|
+
|
|
241
|
+
|
|
255
242
|
@agent_cli.command(name="logs", help="Get logs for the given agent.")
|
|
256
243
|
@synchronizer.create_blocking
|
|
257
244
|
@requires_login
|
|
258
|
-
async def logs(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
245
|
+
async def logs(
|
|
246
|
+
agent_name: str,
|
|
247
|
+
organization: str = typer.Option(
|
|
248
|
+
None,
|
|
249
|
+
"--organization",
|
|
250
|
+
"-o",
|
|
251
|
+
help="Organization to get status of agent for"
|
|
252
|
+
),
|
|
253
|
+
level: LogLevel = typer.Option(
|
|
254
|
+
None,
|
|
255
|
+
"--level", "-l",
|
|
256
|
+
help="Level of logs to get"
|
|
257
|
+
),
|
|
258
|
+
limit: int = typer.Option(
|
|
259
|
+
100,
|
|
260
|
+
"--limit", "-n",
|
|
261
|
+
help="Number of logs to get"
|
|
262
|
+
),
|
|
263
|
+
):
|
|
264
264
|
org = organization or config.get("org")
|
|
265
|
-
token = config.get("token")
|
|
266
265
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
266
|
+
with console.status(f"[dim]Fetching logs for agent: [bold]'{agent_name}'[/bold] with severity: [bold cyan]{level.value if level else 'ALL'}[/bold cyan][/dim]", spinner="dots"):
|
|
267
|
+
data, error = await API.agent_logs(agent_name=agent_name, org=org, limit=limit)
|
|
268
|
+
|
|
269
|
+
if not data or not data.get("logs"):
|
|
270
|
+
console.print("[dim]No logs found for agent[/dim]")
|
|
271
|
+
return typer.Exit(1)
|
|
272
|
+
|
|
273
|
+
for l in data["logs"]:
|
|
274
|
+
log_data = l.get("log", "")
|
|
275
|
+
if log_data:
|
|
276
|
+
timestamp = format_timestamp(l.get("timestamp", ""))
|
|
277
|
+
severity = LogLevel.INFO
|
|
278
|
+
for log_severity in LogLevel:
|
|
279
|
+
if log_severity.value in log_data.upper():
|
|
280
|
+
severity = log_severity
|
|
281
|
+
break
|
|
282
|
+
# filter out any messages that do not match our log level
|
|
283
|
+
if level and severity.value != level.value:
|
|
284
|
+
continue
|
|
285
|
+
color = getattr(LogLevelColors, severity, LogLevelColors.DEBUG).value
|
|
286
|
+
console.print(Text(timestamp, style="bold dim"))
|
|
287
|
+
console.print(Text(l.get("log", ""), style=color))
|
|
288
|
+
console.rule(style="dim")
|
|
280
289
|
|
|
281
290
|
|
|
282
291
|
@agent_cli.command(name="delete", help="Delete an agent.")
|
|
@@ -486,7 +495,7 @@ async def start(
|
|
|
486
495
|
daily_room = data.get("dailyRoom")
|
|
487
496
|
daily_token = data.get("dailyToken")
|
|
488
497
|
if daily_room:
|
|
489
|
-
message += f"\n\nDaily room: [link={daily_room}?t={daily_token}]{daily_room}?t={daily_token}[/link]"
|
|
498
|
+
message += f"\n\nDaily room: [link={daily_room}?t={daily_token}]{daily_room}?t={daily_token}[/link]\n"
|
|
490
499
|
return console.success(
|
|
491
500
|
message,
|
|
492
501
|
subtitle=f"[white dim]Join the session:[/white dim] [link={daily_room}?t={daily_token}]click here[/link]")
|
|
@@ -32,7 +32,7 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
32
32
|
existing_agent = False
|
|
33
33
|
|
|
34
34
|
# Check for an existing deployment with this agent name
|
|
35
|
-
with Live(console.status("[dim]Checking for
|
|
35
|
+
with Live(console.status("[dim]Checking for existing agent deployment...[/dim]", spinner="dots")) as live:
|
|
36
36
|
data, error = await API.agent(agent_name=params.agent_name, org=org, live=live)
|
|
37
37
|
|
|
38
38
|
if error:
|
|
@@ -106,7 +106,6 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
106
106
|
# 3. Poll status until healthy
|
|
107
107
|
"""
|
|
108
108
|
attempts = 0
|
|
109
|
-
condition_table = None
|
|
110
109
|
active_deployment_id = None
|
|
111
110
|
has_failed = False
|
|
112
111
|
is_ready = False
|
|
@@ -117,8 +116,7 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
117
116
|
Padding(
|
|
118
117
|
console.status(
|
|
119
118
|
f"[dim]Watching for deployment status (attempt: {attempts + 1})[dim]" if not active_deployment_id else f"Waiting for deployment to enter ready state [dim](attempt: {attempts + 1})[/dim]",
|
|
120
|
-
spinner="bouncingBar"), (1, 0))
|
|
121
|
-
*([] if condition_table is None else [condition_table])
|
|
119
|
+
spinner="bouncingBar"), (1, 0))
|
|
122
120
|
),
|
|
123
121
|
title=f"[bold]{'Deploying' if not existing_agent else 'Updating'} agent[/bold]",
|
|
124
122
|
border_style="dim" if not active_deployment_id else "white",
|
|
@@ -141,34 +139,6 @@ async def _deploy(params: DeployConfigParams, org, force: bool = False):
|
|
|
141
139
|
attempts = 0
|
|
142
140
|
active_deployment_id = status["activeDeploymentId"]
|
|
143
141
|
|
|
144
|
-
# Update status display
|
|
145
|
-
conditions = status["conditions"]
|
|
146
|
-
table = Table(
|
|
147
|
-
show_header=False,
|
|
148
|
-
show_lines=True,
|
|
149
|
-
border_style="dim",
|
|
150
|
-
box=box.MINIMAL
|
|
151
|
-
)
|
|
152
|
-
table.add_column("Status")
|
|
153
|
-
table.add_column("Type")
|
|
154
|
-
table.add_column("Reason")
|
|
155
|
-
|
|
156
|
-
for condition in conditions:
|
|
157
|
-
c_status = DEPLOY_STATUS_MAP.get(condition["status"], "[dim]Unknown[/dim]")
|
|
158
|
-
if condition.get('reason') == "RevisionFailed":
|
|
159
|
-
c_status = "[red]Failded[/red]"
|
|
160
|
-
has_failed = condition
|
|
161
|
-
if condition["type"] == "Ready":
|
|
162
|
-
continue
|
|
163
|
-
table.add_row(
|
|
164
|
-
c_status,
|
|
165
|
-
condition['type'],
|
|
166
|
-
condition.get(
|
|
167
|
-
'reason',
|
|
168
|
-
'No reason'),
|
|
169
|
-
)
|
|
170
|
-
condition_table = table
|
|
171
|
-
|
|
172
142
|
# Deployment is ready
|
|
173
143
|
if status["ready"]:
|
|
174
144
|
live.update("")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import questionary
|
|
3
|
+
import aiohttp
|
|
4
|
+
import zipfile
|
|
5
|
+
import os
|
|
6
|
+
from typing import Set
|
|
7
|
+
|
|
8
|
+
from pipecatcloud._utils.async_utils import synchronizer
|
|
9
|
+
from pipecatcloud._utils.console_utils import console
|
|
10
|
+
from pipecatcloud.config import config
|
|
11
|
+
|
|
12
|
+
# ----- Init
|
|
13
|
+
|
|
14
|
+
FILES_TO_EXTRACT = {
|
|
15
|
+
"pipecat-cloud-starter-main/bot.py",
|
|
16
|
+
"pipecat-cloud-starter-main/Dockerfile",
|
|
17
|
+
"pipecat-cloud-starter-main/requirements.txt",
|
|
18
|
+
"pipecat-cloud-starter-main/pcc-deploy.toml",
|
|
19
|
+
"pipecat-cloud-starter-main/README.md",
|
|
20
|
+
"pipecat-cloud-starter-main/pipecat-cloud-starter-main/.gitignore",
|
|
21
|
+
"pipecat-cloud-starter-main/local_runner.py",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_existing_files() -> Set[str]:
|
|
26
|
+
"""Check which target files already exist in the current directory."""
|
|
27
|
+
return {os.path.basename(file)
|
|
28
|
+
for file in FILES_TO_EXTRACT if os.path.exists(os.path.basename(file))}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_init_command(app: typer.Typer):
|
|
32
|
+
@app.command(name="init", help="Initialize project directory with template files")
|
|
33
|
+
@synchronizer.create_blocking
|
|
34
|
+
async def init():
|
|
35
|
+
if not await questionary.confirm(
|
|
36
|
+
"This will download the latest starter project to the current directory. Continue?"
|
|
37
|
+
).ask_async():
|
|
38
|
+
console.print("[bold]Aborting init request[/bold]")
|
|
39
|
+
return typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
# Check existing files more efficiently
|
|
42
|
+
existing_files = check_existing_files()
|
|
43
|
+
if existing_files:
|
|
44
|
+
files_list = "\n".join(f"- {f}" for f in existing_files)
|
|
45
|
+
if not await questionary.confirm(
|
|
46
|
+
f"The following files already exist and will be overwritten:\n{files_list}\nDo you want to continue?"
|
|
47
|
+
).ask_async():
|
|
48
|
+
console.print("[bold]Aborting init request[/bold]")
|
|
49
|
+
return typer.Exit(1)
|
|
50
|
+
|
|
51
|
+
zip_path = "init.zip"
|
|
52
|
+
try:
|
|
53
|
+
with console.status("[dim]Downloading starter project...[/dim]", spinner="dots"):
|
|
54
|
+
zip_url = config.get("init_zip_url")
|
|
55
|
+
if not zip_url:
|
|
56
|
+
raise ValueError("No starter project URL found in configuration")
|
|
57
|
+
|
|
58
|
+
async with aiohttp.ClientSession() as session:
|
|
59
|
+
async with session.get(zip_url) as response:
|
|
60
|
+
if response.status != 200:
|
|
61
|
+
raise aiohttp.ClientResponseError(
|
|
62
|
+
response.request_info,
|
|
63
|
+
response.history,
|
|
64
|
+
status=response.status,
|
|
65
|
+
message=f"Failed to download starter project: {response.reason}"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Stream the download to a file
|
|
69
|
+
with open(zip_path, "wb") as f:
|
|
70
|
+
async for chunk in response.content.iter_chunked(8192):
|
|
71
|
+
f.write(chunk)
|
|
72
|
+
|
|
73
|
+
# Extract files
|
|
74
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
75
|
+
for file in FILES_TO_EXTRACT:
|
|
76
|
+
try:
|
|
77
|
+
source = zip_ref.read(file)
|
|
78
|
+
target = os.path.basename(file)
|
|
79
|
+
with open(target, "wb") as f:
|
|
80
|
+
f.write(source)
|
|
81
|
+
except KeyError:
|
|
82
|
+
console.print(
|
|
83
|
+
f"[yellow]Warning:[/yellow] File {file} not found in starter project")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise Exception(f"Failed to extract {file}: {str(e)}")
|
|
86
|
+
|
|
87
|
+
console.success(
|
|
88
|
+
"You can now start building your agent in [bold]bot.py[/bold]"
|
|
89
|
+
"\n\n"
|
|
90
|
+
"Follow the steps in the [bold]README.md[/bold] file to get started.",
|
|
91
|
+
title="Project files downloaded successfully"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
except aiohttp.ClientError as e:
|
|
95
|
+
console.error(f"Network error while downloading starter project: {str(e)}")
|
|
96
|
+
return typer.Exit(1)
|
|
97
|
+
except zipfile.BadZipFile:
|
|
98
|
+
console.error("Downloaded file is not a valid zip archive")
|
|
99
|
+
return typer.Exit(1)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
console.error(f"Error during initialization: {str(e)}")
|
|
102
|
+
return typer.Exit(1)
|
|
103
|
+
finally:
|
|
104
|
+
# Cleanup the temporary zip file
|
|
105
|
+
if os.path.exists(zip_path):
|
|
106
|
+
try:
|
|
107
|
+
os.remove(zip_path)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
console.print(
|
|
110
|
+
f"[yellow]Warning:[/yellow] Failed to cleanup temporary file {zip_path}: {str(e)}")
|
|
111
|
+
|
|
112
|
+
return init
|
|
@@ -212,13 +212,6 @@ async def set(
|
|
|
212
212
|
if not create:
|
|
213
213
|
console.print("[bold red]Secret set creation cancelled[/bold red]")
|
|
214
214
|
return typer.Exit(1)
|
|
215
|
-
else:
|
|
216
|
-
if not skip_confirm:
|
|
217
|
-
create = await questionary.confirm(
|
|
218
|
-
f"Secret set with name '{name}' does not exist. Would you like to create it?").ask_async()
|
|
219
|
-
if not create:
|
|
220
|
-
console.print("[bold red]Secret set creation cancelled[/bold red]")
|
|
221
|
-
return typer.Exit(1)
|
|
222
215
|
|
|
223
216
|
with console.status(f"[dim]{'Modifying' if existing_set else 'Creating'} secret set [bold]'{name}'[/bold][/dim]", spinner="dots"):
|
|
224
217
|
for key, value in secrets_dict.items():
|
|
@@ -12,6 +12,7 @@ class _Setting(typing.NamedTuple):
|
|
|
12
12
|
_SETTINGS = {
|
|
13
13
|
"api_host": _Setting("https://api.pipecat.daily.co"),
|
|
14
14
|
"dashboard_host": _Setting("https://pipecat.daily.co"),
|
|
15
|
+
"init_zip_url": _Setting("https://github.com/daily-co/pipecat-cloud-starter/archive/refs/heads/main.zip"),
|
|
15
16
|
"onboarding_path": _Setting("/v1/onboarding"),
|
|
16
17
|
"login_path": _Setting("/auth/login"),
|
|
17
18
|
"login_status_path": _Setting("/auth/status"),
|
|
@@ -22,8 +23,7 @@ _SETTINGS = {
|
|
|
22
23
|
"services_deployments_path": _Setting("/v1/organizations/{org}/services/{service}/deployments"),
|
|
23
24
|
"start_path": _Setting("/v1/public/{service}/start"),
|
|
24
25
|
"api_keys_path": _Setting("/v1/organizations/{org}/apiKeys"),
|
|
25
|
-
"secrets_path": _Setting("/v1/organizations/{org}/secrets")
|
|
26
|
-
}
|
|
26
|
+
"secrets_path": _Setting("/v1/organizations/{org}/secrets")}
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Config:
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from pipecatcloud.errors import ERROR_CODES
|
|
1
|
+
from typing import Optional, Union
|
|
4
2
|
|
|
5
3
|
|
|
6
4
|
class Error(Exception):
|
|
@@ -48,12 +46,16 @@ class AgentStartError(Error):
|
|
|
48
46
|
|
|
49
47
|
def __init__(
|
|
50
48
|
self,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
error: Optional[Union[str, dict]] = None):
|
|
50
|
+
|
|
51
|
+
if isinstance(error, dict):
|
|
52
|
+
error_message = error.get("error", "Unknown error. Please contact support.")
|
|
53
|
+
code = error.get("code")
|
|
54
|
+
else:
|
|
55
|
+
error_message = str(
|
|
56
|
+
error) if error else "Unknown error. Please contact support."
|
|
57
|
+
code = None
|
|
58
|
+
|
|
59
|
+
self.message = f"{code} - {error_message}"
|
|
60
|
+
self.error_code = code
|
|
59
61
|
super().__init__(self.message)
|
|
@@ -4,8 +4,7 @@ from typing import Optional
|
|
|
4
4
|
import aiohttp
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
|
-
from pipecatcloud.
|
|
8
|
-
from pipecatcloud.api import API
|
|
7
|
+
from pipecatcloud.api import _API as API
|
|
9
8
|
from pipecatcloud.exception import AgentStartError
|
|
10
9
|
|
|
11
10
|
|
|
@@ -32,28 +31,38 @@ class Session:
|
|
|
32
31
|
|
|
33
32
|
async def start(self) -> bool:
|
|
34
33
|
if not self.api_key:
|
|
35
|
-
raise AgentStartError(
|
|
34
|
+
raise AgentStartError({"code": "PCC-1002", "error": "No API key provided"})
|
|
36
35
|
|
|
37
36
|
logger.debug(f"Starting agent {self.agent_name}")
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
start_error = None
|
|
39
|
+
|
|
40
|
+
payload: dict = {"createDailyRoom": bool(self.params.use_daily)}
|
|
41
|
+
if self.params.data is not None:
|
|
42
|
+
payload["body"] = self.params.data
|
|
43
|
+
|
|
40
44
|
try:
|
|
41
45
|
async with aiohttp.ClientSession() as session:
|
|
42
46
|
response = await session.post(
|
|
43
47
|
f"{API.construct_api_url('start_path').format(service=self.agent_name)}",
|
|
44
48
|
headers={"Authorization": f"Bearer {self.api_key}"},
|
|
45
|
-
json=
|
|
46
|
-
"createDailyRoom": bool(self.params.use_daily),
|
|
47
|
-
"body": self.params.data
|
|
48
|
-
}
|
|
49
|
+
json=payload
|
|
49
50
|
)
|
|
51
|
+
|
|
50
52
|
if response.status != 200:
|
|
51
|
-
|
|
53
|
+
# Attempt to parse the error code from the response
|
|
54
|
+
try:
|
|
55
|
+
error_data = await response.json()
|
|
56
|
+
start_error = error_data
|
|
57
|
+
except Exception:
|
|
58
|
+
start_error = {"code": "PCC-1000",
|
|
59
|
+
"error": "Unknown error occurred. Please contact support."}
|
|
60
|
+
|
|
52
61
|
response.raise_for_status()
|
|
53
62
|
else:
|
|
54
63
|
logger.debug(f"Agent {self.agent_name} started successfully")
|
|
55
64
|
except Exception as e:
|
|
56
65
|
logger.error(f"Error starting agent {self.agent_name}: {e}")
|
|
57
|
-
raise AgentStartError(
|
|
66
|
+
raise AgentStartError(error=start_error)
|
|
58
67
|
|
|
59
68
|
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pipecatcloud
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
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/
|
|
@@ -70,16 +70,26 @@ pcc auth login
|
|
|
70
70
|
|
|
71
71
|
### Usage in Python scripts
|
|
72
72
|
|
|
73
|
-
If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.
|
|
73
|
+
If want to programmatically start an agent within a Python script, you can use the `pipecatcloud.session` module.
|
|
74
74
|
|
|
75
75
|
```python
|
|
76
|
-
from pipecatcloud.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
from pipecatcloud.session import Session
|
|
77
|
+
from pipecatcloud.exception import AgentStartError
|
|
78
|
+
import asyncio
|
|
79
|
+
|
|
80
|
+
async def main():
|
|
81
|
+
session = Session(
|
|
82
|
+
agent_name="your-agent-name",
|
|
83
|
+
api_key="pk_...",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
await session.start()
|
|
88
|
+
except AgentStartError as e:
|
|
89
|
+
print(e)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise (e)
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
asyncio.run(main())
|
|
85
95
|
```
|
|
@@ -15,7 +15,6 @@ src/pipecatcloud/__main__.py
|
|
|
15
15
|
src/pipecatcloud/__version__.py
|
|
16
16
|
src/pipecatcloud/api.py
|
|
17
17
|
src/pipecatcloud/config.py
|
|
18
|
-
src/pipecatcloud/errors.py
|
|
19
18
|
src/pipecatcloud/exception.py
|
|
20
19
|
src/pipecatcloud/session.py
|
|
21
20
|
src/pipecatcloud.egg-info/PKG-INFO
|
|
@@ -25,7 +24,6 @@ src/pipecatcloud.egg-info/entry_points.txt
|
|
|
25
24
|
src/pipecatcloud.egg-info/requires.txt
|
|
26
25
|
src/pipecatcloud.egg-info/top_level.txt
|
|
27
26
|
src/pipecatcloud/_utils/__init__.py
|
|
28
|
-
src/pipecatcloud/_utils/agent_utils.py
|
|
29
27
|
src/pipecatcloud/_utils/async_utils.py
|
|
30
28
|
src/pipecatcloud/_utils/auth_utils.py
|
|
31
29
|
src/pipecatcloud/_utils/console_utils.py
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from rich.console import Console
|
|
4
|
-
from rich.panel import Panel
|
|
5
|
-
|
|
6
|
-
from pipecatcloud.cli import PANEL_TITLE_ERROR, PANEL_TITLE_SUCCESS
|
|
7
|
-
from pipecatcloud.errors import ERROR_CODES
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class PipecatConsole(Console):
|
|
11
|
-
def success(
|
|
12
|
-
self,
|
|
13
|
-
message,
|
|
14
|
-
title: Optional[str] = None,
|
|
15
|
-
title_extra: Optional[str] = None,
|
|
16
|
-
subtitle: Optional[str] = None):
|
|
17
|
-
|
|
18
|
-
if not title:
|
|
19
|
-
title = f"{PANEL_TITLE_SUCCESS}{f' - {title_extra}' if title_extra is not None else ''}"
|
|
20
|
-
|
|
21
|
-
self.print(
|
|
22
|
-
Panel(
|
|
23
|
-
message,
|
|
24
|
-
title=f"[bold green]{title}[/bold green]",
|
|
25
|
-
subtitle=subtitle,
|
|
26
|
-
title_align="left",
|
|
27
|
-
subtitle_align="left",
|
|
28
|
-
border_style="green"))
|
|
29
|
-
|
|
30
|
-
def error(
|
|
31
|
-
self,
|
|
32
|
-
message,
|
|
33
|
-
title: Optional[str] = None,
|
|
34
|
-
title_extra: Optional[str] = None,
|
|
35
|
-
subtitle: Optional[str] = None):
|
|
36
|
-
|
|
37
|
-
if not title:
|
|
38
|
-
title = f"{PANEL_TITLE_ERROR}{f' - {title_extra}' if title_extra is not None else ''}"
|
|
39
|
-
|
|
40
|
-
self.print(
|
|
41
|
-
Panel(
|
|
42
|
-
message,
|
|
43
|
-
title=f"[bold red]{title}[/bold red]",
|
|
44
|
-
subtitle=subtitle,
|
|
45
|
-
title_align="left",
|
|
46
|
-
subtitle_align="left",
|
|
47
|
-
border_style="red"))
|
|
48
|
-
|
|
49
|
-
def cancel(self):
|
|
50
|
-
self.print("[yellow]Cancelled by user[/yellow]")
|
|
51
|
-
|
|
52
|
-
def unauthorized(self):
|
|
53
|
-
self.api_error("401", "Unauthorized", hide_subtitle=True)
|
|
54
|
-
|
|
55
|
-
def api_error(
|
|
56
|
-
self,
|
|
57
|
-
error_code: Optional[str],
|
|
58
|
-
title: Optional[str] = "API Error",
|
|
59
|
-
hide_subtitle: bool = False):
|
|
60
|
-
DEFAULT_ERROR_MESSAGE = "Unknown error. Please contact support."
|
|
61
|
-
ERROR_MESSAGE = ERROR_CODES.get(error_code, None) if error_code else None
|
|
62
|
-
|
|
63
|
-
if not ERROR_MESSAGE:
|
|
64
|
-
hide_subtitle = True
|
|
65
|
-
|
|
66
|
-
error_message = ERROR_MESSAGE if ERROR_MESSAGE else DEFAULT_ERROR_MESSAGE
|
|
67
|
-
self.print(
|
|
68
|
-
Panel(
|
|
69
|
-
f"[red]{title}[/red]\n\n"
|
|
70
|
-
f"[dim]Error message:[/dim]\n{error_message}",
|
|
71
|
-
title=f"[bold red]{PANEL_TITLE_ERROR} - {error_code}[/bold red]",
|
|
72
|
-
subtitle=f"[dim]Docs: https://docs.pipecat.daily.co/agents/error-codes#{error_code}[/dim]" if not hide_subtitle else None,
|
|
73
|
-
title_align="left",
|
|
74
|
-
subtitle_align="left",
|
|
75
|
-
border_style="red"))
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console = PipecatConsole()
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import typer
|
|
2
|
-
|
|
3
|
-
from pipecatcloud._utils.async_utils import synchronizer
|
|
4
|
-
from pipecatcloud._utils.console_utils import console
|
|
5
|
-
|
|
6
|
-
# ----- Run
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def create_init_command(app: typer.Typer):
|
|
10
|
-
@app.command(name="init", help="Initialize project directory with template files")
|
|
11
|
-
@synchronizer.create_blocking
|
|
12
|
-
async def init(
|
|
13
|
-
|
|
14
|
-
):
|
|
15
|
-
console.error("Not yet implemented")
|
|
16
|
-
return init
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
ERROR_CODES = {
|
|
2
|
-
"401": "Unauthorized request to API. Please run `pcc auth login` to login again.\nIf you are attempting to start an agent, please ensure the public API key used as part of the request is valid.",
|
|
3
|
-
"404": "API endpoint not found / namespace or organization not found.",
|
|
4
|
-
"PCC-1000": "Unable to start agent.",
|
|
5
|
-
"PCC-1001": "Attempt to start agent when deployment is not in ready state",
|
|
6
|
-
"PCC-1002": "Attempt to start agent without public api key. Try running `pcc organizations keys use`.",
|
|
7
|
-
"PCC-1003": "Unknown error occurred. Please check logs for more information.",
|
|
8
|
-
"PCC-1004": "Billing credentials not set. Please set billing credentials via the Pipecat Cloud dashboard.",
|
|
9
|
-
"PCC-1005": "Agent deployment with name not found.",
|
|
10
|
-
"PCC-1006": "Not authorized / invalid API key.",
|
|
11
|
-
}
|
|
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
|