pipecatcloud 0.2.4__tar.gz → 0.2.5__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.2.4/.github/workflows/publish-test.yml → pipecatcloud-0.2.5/.github/workflows/publish-pypi.yml +30 -25
- pipecatcloud-0.2.5/.github/workflows/publish-test.yml +51 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/CHANGELOG.md +17 -0
- {pipecatcloud-0.2.4/src/pipecatcloud.egg-info → pipecatcloud-0.2.5}/PKG-INFO +1 -1
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/pyproject.toml +5 -1
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/__init__.py +4 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/_utils/deploy_utils.py +3 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/agent.py +26 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/api.py +1 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/agent.py +13 -1
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/deploy.py +8 -0
- pipecatcloud-0.2.5/src/pipecatcloud/smallwebrtc/session_manager.py +87 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5/src/pipecatcloud.egg-info}/PKG-INFO +1 -1
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud.egg-info/SOURCES.txt +3 -2
- pipecatcloud-0.2.5/tests/test_managed_keys.py +437 -0
- pipecatcloud-0.2.4/.github/workflows/publish-pypi.yml +0 -84
- pipecatcloud-0.2.4/.github/workflows/python-publish.yml +0 -40
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/.gitignore +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/LICENSE +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/README.md +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/pytest.ini +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/setup.cfg +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/_utils/console_utils.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/_utils/local_runner.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/docker.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/init.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/run.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/commands/secrets.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/tests/__init__.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-0.2.4 → pipecatcloud-0.2.5}/tests/test_docker_commands.py +0 -0
|
@@ -1,61 +1,67 @@
|
|
|
1
|
-
name: Publish to
|
|
1
|
+
name: Publish to PyPI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_dispatch:
|
|
5
5
|
inputs:
|
|
6
|
-
|
|
7
|
-
description: 'Version to publish (must match pyproject.toml, e.g. 0.0.4)'
|
|
8
|
-
required: true
|
|
6
|
+
gitref:
|
|
9
7
|
type: string
|
|
8
|
+
description: 'what git tag to build (e.g. v0.0.74)'
|
|
9
|
+
required: true
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
12
|
build:
|
|
13
13
|
name: 'Build and upload wheels'
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
|
-
permissions:
|
|
16
|
-
contents: write
|
|
17
15
|
steps:
|
|
18
|
-
- name: Checkout
|
|
16
|
+
- name: Checkout repo
|
|
19
17
|
uses: actions/checkout@v4
|
|
20
18
|
with:
|
|
21
|
-
|
|
22
|
-
fetch-depth: 100
|
|
19
|
+
ref: ${{ github.event.inputs.gitref }}
|
|
23
20
|
|
|
24
21
|
- name: Install uv
|
|
25
22
|
uses: astral-sh/setup-uv@v3
|
|
26
23
|
with:
|
|
27
24
|
version: 'latest'
|
|
28
|
-
|
|
29
25
|
- name: Set up Python
|
|
30
26
|
run: uv python install 3.12
|
|
31
|
-
|
|
32
|
-
- name: Verify version matches
|
|
33
|
-
run: |
|
|
34
|
-
PROJECT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
35
|
-
if [ "$PROJECT_VERSION" != "${{ github.event.inputs.version }}" ]; then
|
|
36
|
-
echo "Error: Input version (${{ github.event.inputs.version }}) does not match pyproject.toml version ($PROJECT_VERSION)"
|
|
37
|
-
exit 1
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
27
|
- name: Install development dependencies
|
|
41
28
|
run: uv sync --group dev
|
|
42
|
-
|
|
43
29
|
- name: Build project
|
|
44
30
|
run: uv build
|
|
45
|
-
|
|
46
31
|
- name: Upload wheels
|
|
47
32
|
uses: actions/upload-artifact@v4
|
|
48
33
|
with:
|
|
49
34
|
name: wheels
|
|
50
35
|
path: ./dist
|
|
51
36
|
|
|
37
|
+
publish-to-pypi:
|
|
38
|
+
name: 'Publish to PyPI'
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
needs: [build]
|
|
41
|
+
environment:
|
|
42
|
+
name: pypi
|
|
43
|
+
url: https://pypi.org/p/pipecatcloud
|
|
44
|
+
permissions:
|
|
45
|
+
id-token: write
|
|
46
|
+
steps:
|
|
47
|
+
- name: Download wheels
|
|
48
|
+
uses: actions/download-artifact@v4
|
|
49
|
+
with:
|
|
50
|
+
name: wheels
|
|
51
|
+
path: ./dist
|
|
52
|
+
- name: Publish to PyPI
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
54
|
+
with:
|
|
55
|
+
verbose: true
|
|
56
|
+
print-hash: true
|
|
57
|
+
|
|
52
58
|
publish-to-test-pypi:
|
|
53
59
|
name: 'Publish to Test PyPI'
|
|
54
60
|
runs-on: ubuntu-latest
|
|
55
61
|
needs: [build]
|
|
56
62
|
environment:
|
|
57
63
|
name: testpypi
|
|
58
|
-
url: https://test.pypi.org/
|
|
64
|
+
url: https://test.pypi.org/p/pipecatcloud
|
|
59
65
|
permissions:
|
|
60
66
|
id-token: write
|
|
61
67
|
steps:
|
|
@@ -64,10 +70,9 @@ jobs:
|
|
|
64
70
|
with:
|
|
65
71
|
name: wheels
|
|
66
72
|
path: ./dist
|
|
67
|
-
|
|
68
|
-
- name: Publish to TestPyPI
|
|
73
|
+
- name: Publish to Test PyPI
|
|
69
74
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
70
75
|
with:
|
|
71
|
-
repository-url: https://test.pypi.org/legacy/
|
|
72
76
|
verbose: true
|
|
73
77
|
print-hash: true
|
|
78
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Publish to TEST PyPI
|
|
2
|
+
|
|
3
|
+
on: workflow_dispatch
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
name: 'Build and upload wheels'
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- name: Checkout repo
|
|
11
|
+
uses: actions/checkout@v4
|
|
12
|
+
with:
|
|
13
|
+
fetch-tags: true
|
|
14
|
+
fetch-depth: 100
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v3
|
|
17
|
+
with:
|
|
18
|
+
version: 'latest'
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
run: uv python install 3.12
|
|
21
|
+
- name: Install development dependencies
|
|
22
|
+
run: uv sync --group dev
|
|
23
|
+
- name: Build project
|
|
24
|
+
run: uv build
|
|
25
|
+
- name: Upload wheels
|
|
26
|
+
uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: wheels
|
|
29
|
+
path: ./dist
|
|
30
|
+
|
|
31
|
+
publish-to-test-pypi:
|
|
32
|
+
name: 'Publish to Test PyPI'
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
needs: [build]
|
|
35
|
+
environment:
|
|
36
|
+
name: testpypi
|
|
37
|
+
url: https://test.pypi.org/p/pipecatcloud
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write
|
|
40
|
+
steps:
|
|
41
|
+
- name: Download wheels
|
|
42
|
+
uses: actions/download-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: wheels
|
|
45
|
+
path: ./dist
|
|
46
|
+
- name: Publish to Test PyPI
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
48
|
+
with:
|
|
49
|
+
verbose: true
|
|
50
|
+
print-hash: true
|
|
51
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -5,6 +5,23 @@ 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.2.5] - 2025-10-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added a `body` parameter to the fallback type, `WebsocketRunnerArguments`.
|
|
13
|
+
|
|
14
|
+
- Added `SmallWebRTCSessionManager`, a Session Manager to simplify bot creation
|
|
15
|
+
in Pipecat Cloud using `SmallWebRTCTransport`.
|
|
16
|
+
- Added a `SmallWebRTCSessionArguments` dataclass.
|
|
17
|
+
|
|
18
|
+
- `deploy` command now accepts a `--enable-managed-keys` flag which enables
|
|
19
|
+
properly configured agents to use Daily's API keys for supported upstream
|
|
20
|
+
services. This can also be enabled via `pcc-deploy.toml` by adding the
|
|
21
|
+
`enable_managed_keys` attribute and a boolean value.
|
|
22
|
+
|
|
23
|
+
- `agent status` now reflects the state of the managed keys feature.
|
|
24
|
+
|
|
8
25
|
## [0.2.4] - 2025-08-26
|
|
9
26
|
|
|
10
27
|
### Added
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pipecatcloud"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Cloud hosting for Pipecat AI applications"
|
|
10
10
|
license = { text = "BSD 2-Clause License" }
|
|
@@ -58,3 +58,7 @@ line-length = 100
|
|
|
58
58
|
|
|
59
59
|
[project.urls]
|
|
60
60
|
Website = "https://docs.pipecat.daily.co/"
|
|
61
|
+
|
|
62
|
+
[tool.setuptools_scm]
|
|
63
|
+
local_scheme = "no-local-version"
|
|
64
|
+
fallback_version = "0.0.0-dev"
|
|
@@ -13,6 +13,7 @@ from pipecatcloud.agent import (
|
|
|
13
13
|
DailySessionArguments,
|
|
14
14
|
PipecatSessionArguments,
|
|
15
15
|
SessionArguments,
|
|
16
|
+
SmallWebRTCRunnerArguments,
|
|
16
17
|
WebSocketSessionArguments,
|
|
17
18
|
)
|
|
18
19
|
from pipecatcloud.exception import (
|
|
@@ -25,6 +26,7 @@ from pipecatcloud.exception import (
|
|
|
25
26
|
InvalidError,
|
|
26
27
|
)
|
|
27
28
|
from pipecatcloud.session import Session, SessionParams
|
|
29
|
+
from pipecatcloud.smallwebrtc.session_manager import SmallWebRTCSessionManager
|
|
28
30
|
|
|
29
31
|
logger.remove()
|
|
30
32
|
logger.add(sys.stderr, level=str(os.getenv("PCC_LOG_LEVEL", "INFO")))
|
|
@@ -35,10 +37,12 @@ __all__ = [
|
|
|
35
37
|
"DailySessionArguments",
|
|
36
38
|
"PipecatSessionArguments",
|
|
37
39
|
"SessionArguments",
|
|
40
|
+
"SmallWebRTCRunnerArguments",
|
|
38
41
|
"WebSocketSessionArguments",
|
|
39
42
|
# Session classes
|
|
40
43
|
"Session",
|
|
41
44
|
"SessionParams",
|
|
45
|
+
"SmallWebRTCSessionManager",
|
|
42
46
|
# Exception classes
|
|
43
47
|
"AgentNotHealthyError",
|
|
44
48
|
"AgentStartError",
|
|
@@ -64,6 +64,7 @@ class DeployConfigParams:
|
|
|
64
64
|
secret_set: Optional[str] = None
|
|
65
65
|
scaling: ScalingParams = ScalingParams()
|
|
66
66
|
enable_krisp: bool = False
|
|
67
|
+
enable_managed_keys: bool = False
|
|
67
68
|
docker_config: dict = field(factory=dict)
|
|
68
69
|
|
|
69
70
|
def __attrs_post_init__(self):
|
|
@@ -78,6 +79,7 @@ class DeployConfigParams:
|
|
|
78
79
|
"secret_set": self.secret_set,
|
|
79
80
|
"scaling": self.scaling.to_dict() if self.scaling else None,
|
|
80
81
|
"enable_krisp": self.enable_krisp,
|
|
82
|
+
"enable_managed_keys": self.enable_managed_keys,
|
|
81
83
|
"docker_config": self.docker_config,
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -117,6 +119,7 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
|
|
|
117
119
|
"secret_set",
|
|
118
120
|
"scaling",
|
|
119
121
|
"enable_krisp",
|
|
122
|
+
"enable_managed_keys",
|
|
120
123
|
"docker",
|
|
121
124
|
}
|
|
122
125
|
unexpected_keys = set(config_data.keys()) - expected_keys
|
|
@@ -16,6 +16,7 @@ try:
|
|
|
16
16
|
DailyRunnerArguments,
|
|
17
17
|
RunnerArguments,
|
|
18
18
|
WebSocketRunnerArguments,
|
|
19
|
+
SmallWebRTCRunnerArguments,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
_PIPECAT_RUNNER_TYPES_AVAILABLE = True
|
|
@@ -62,6 +63,17 @@ except ImportError:
|
|
|
62
63
|
"""
|
|
63
64
|
|
|
64
65
|
websocket: WebSocket
|
|
66
|
+
body: Any
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class SmallWebRTCRunnerArguments(RunnerArguments):
|
|
70
|
+
"""Fallback Small WebRTC transport session arguments for the runner.
|
|
71
|
+
|
|
72
|
+
Parameters:
|
|
73
|
+
webrtc_connection: Pre-configured WebRTC peer connection
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
webrtc_connection: Any
|
|
65
77
|
|
|
66
78
|
|
|
67
79
|
def _warn_standalone_usage():
|
|
@@ -131,3 +143,17 @@ class WebSocketSessionArguments(WebSocketRunnerArguments, SessionArguments):
|
|
|
131
143
|
def __post_init__(self):
|
|
132
144
|
WebSocketRunnerArguments.__post_init__(self)
|
|
133
145
|
_warn_standalone_usage()
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class SmallWebRTCSessionArguments(SmallWebRTCRunnerArguments, SessionArguments):
|
|
149
|
+
"""SmallWebRTCTransport based agent session arguments.
|
|
150
|
+
|
|
151
|
+
Inherits from SmallWebRTCRunnerArguments for compatibility with pipecat-ai runner.
|
|
152
|
+
When pipecat-ai is not installed, uses a fallback implementation (deprecated).
|
|
153
|
+
|
|
154
|
+
For best compatibility, install: pip install pipecatcloud[pipecat]
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __post_init__(self):
|
|
158
|
+
SmallWebRTCRunnerArguments.__post_init__(self)
|
|
159
|
+
_warn_standalone_usage()
|
|
@@ -321,6 +321,7 @@ class _API:
|
|
|
321
321
|
"maxAgents": deploy_config.scaling.max_agents,
|
|
322
322
|
},
|
|
323
323
|
"enableKrisp": deploy_config.enable_krisp,
|
|
324
|
+
"enableIntegratedKeysProxy": deploy_config.enable_managed_keys, # API expects this field name
|
|
324
325
|
}
|
|
325
326
|
|
|
326
327
|
# Remove None values recursively
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
#
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
import statistics
|
|
9
8
|
from enum import Enum
|
|
10
9
|
|
|
11
10
|
import aiohttp
|
|
@@ -132,6 +131,19 @@ async def status(
|
|
|
132
131
|
"[bold]Updated At:[/bold]",
|
|
133
132
|
str(data.get("updatedAt", "N/A")),
|
|
134
133
|
)
|
|
134
|
+
|
|
135
|
+
# Check for Managed Keys status
|
|
136
|
+
# API returns integratedKeysProxy but we display as "Managed Keys"
|
|
137
|
+
integrated_keys = data.get("deployment", {}).get("manifest", {}).get("spec", {}).get("integratedKeysProxy", {})
|
|
138
|
+
if isinstance(integrated_keys, dict):
|
|
139
|
+
integrated_keys_enabled = integrated_keys.get("enabled", False)
|
|
140
|
+
else:
|
|
141
|
+
integrated_keys_enabled = bool(integrated_keys)
|
|
142
|
+
|
|
143
|
+
deployment_table.add_row(
|
|
144
|
+
"[bold]Managed Keys:[/bold]",
|
|
145
|
+
"[green]Enabled[/green]" if integrated_keys_enabled else "[dim]Disabled[/dim]",
|
|
146
|
+
)
|
|
135
147
|
|
|
136
148
|
# Autoscaling info
|
|
137
149
|
autoscaling_data = data.get("autoScaling", None)
|
|
@@ -279,6 +279,12 @@ def create_deploy_command(app: typer.Typer):
|
|
|
279
279
|
help="Enable Krisp integration for this deployment",
|
|
280
280
|
rich_help_panel="Deployment Configuration",
|
|
281
281
|
),
|
|
282
|
+
managed_keys: bool = typer.Option(
|
|
283
|
+
False,
|
|
284
|
+
"--enable-managed-keys",
|
|
285
|
+
help="Enable Managed Keys for this deployment",
|
|
286
|
+
rich_help_panel="Deployment Configuration",
|
|
287
|
+
),
|
|
282
288
|
skip_confirm: bool = typer.Option(
|
|
283
289
|
False,
|
|
284
290
|
"--force",
|
|
@@ -345,6 +351,7 @@ def create_deploy_command(app: typer.Typer):
|
|
|
345
351
|
else partial_config.scaling.max_agents,
|
|
346
352
|
)
|
|
347
353
|
partial_config.enable_krisp = krisp or partial_config.enable_krisp
|
|
354
|
+
partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
|
|
348
355
|
|
|
349
356
|
# Assert agent name and image are provided
|
|
350
357
|
if not partial_config.agent_name:
|
|
@@ -382,6 +389,7 @@ def create_deploy_command(app: typer.Typer):
|
|
|
382
389
|
(f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"),
|
|
383
390
|
(f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"),
|
|
384
391
|
(f"[bold white]Krisp:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"),
|
|
392
|
+
(f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}"),
|
|
385
393
|
"\n[dim]Scaling configuration:[/dim]",
|
|
386
394
|
table,
|
|
387
395
|
*
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SmallWebRTCSessionManager:
|
|
6
|
+
"""
|
|
7
|
+
Manages the waiting logic between PipecatSession and SmallWebRTCSession.
|
|
8
|
+
|
|
9
|
+
This class provides:
|
|
10
|
+
- A mechanism to wait for a WebRTC connection within a timeout period.
|
|
11
|
+
- Automatic cancellation if no connection is established in time.
|
|
12
|
+
- A way to complete the waiting future once the session has successfully started.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, timeout_seconds: int = 120):
|
|
16
|
+
self._pending_future: Optional[asyncio.Future] = None
|
|
17
|
+
self._timeout_task: Optional[asyncio.Task] = None
|
|
18
|
+
self._timeout_seconds = timeout_seconds
|
|
19
|
+
|
|
20
|
+
async def wait_for_webrtc(self) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Creates and waits for a future to be completed when WebRTC connection arrives.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
TimeoutError: If WebRTC connection is not received within the timeout period
|
|
26
|
+
RuntimeError: If another waiting operation is already in progress
|
|
27
|
+
"""
|
|
28
|
+
if self._pending_future is not None:
|
|
29
|
+
raise RuntimeError("Already waiting for WebRTC connection")
|
|
30
|
+
|
|
31
|
+
self._pending_future = asyncio.Future()
|
|
32
|
+
|
|
33
|
+
async def timeout_handler():
|
|
34
|
+
await asyncio.sleep(self._timeout_seconds)
|
|
35
|
+
if self._pending_future and not self._pending_future.done():
|
|
36
|
+
self._pending_future.set_exception(
|
|
37
|
+
TimeoutError(f"WebRTC connection not received within {self._timeout_seconds} seconds")
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Create and store the timeout task
|
|
41
|
+
self._timeout_task = asyncio.create_task(timeout_handler())
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
await self._pending_future
|
|
45
|
+
finally:
|
|
46
|
+
self._cleanup()
|
|
47
|
+
|
|
48
|
+
def complete_session(self) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
Completes the waiting future.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if the future was found and completed, False otherwise
|
|
54
|
+
"""
|
|
55
|
+
if self._pending_future and not self._pending_future.done():
|
|
56
|
+
# Cancel the timeout task first
|
|
57
|
+
self.cancel_timeout()
|
|
58
|
+
# Complete the future
|
|
59
|
+
self._pending_future.set_result(True)
|
|
60
|
+
return True
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
def cancel_timeout(self) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Cancels the timeout handler without completing the future.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
bool: True if the timeout task was found and cancelled, False otherwise
|
|
69
|
+
"""
|
|
70
|
+
if self._timeout_task and not self._timeout_task.done():
|
|
71
|
+
self._timeout_task.cancel()
|
|
72
|
+
return True
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def _cleanup(self) -> None:
|
|
76
|
+
"""Cleans up all resources."""
|
|
77
|
+
self._pending_future = None
|
|
78
|
+
self._timeout_task = None
|
|
79
|
+
|
|
80
|
+
def is_waiting(self) -> bool:
|
|
81
|
+
"""
|
|
82
|
+
Checks if currently waiting for WebRTC connection.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
bool: True if waiting for connection, False otherwise
|
|
86
|
+
"""
|
|
87
|
+
return self._pending_future is not None and not self._pending_future.done()
|
|
@@ -7,7 +7,6 @@ pyproject.toml
|
|
|
7
7
|
pytest.ini
|
|
8
8
|
.github/workflows/publish-pypi.yml
|
|
9
9
|
.github/workflows/publish-test.yml
|
|
10
|
-
.github/workflows/python-publish.yml
|
|
11
10
|
.github/workflows/tests.yml
|
|
12
11
|
src/pipecatcloud/__init__.py
|
|
13
12
|
src/pipecatcloud/__main__.py
|
|
@@ -43,6 +42,8 @@ src/pipecatcloud/cli/commands/init.py
|
|
|
43
42
|
src/pipecatcloud/cli/commands/organizations.py
|
|
44
43
|
src/pipecatcloud/cli/commands/run.py
|
|
45
44
|
src/pipecatcloud/cli/commands/secrets.py
|
|
45
|
+
src/pipecatcloud/smallwebrtc/session_manager.py
|
|
46
46
|
tests/__init__.py
|
|
47
47
|
tests/test_agent_sessions.py
|
|
48
|
-
tests/test_docker_commands.py
|
|
48
|
+
tests/test_docker_commands.py
|
|
49
|
+
tests/test_managed_keys.py
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for managed keys feature.
|
|
3
|
+
|
|
4
|
+
Tests follow AAA pattern and focus on behaviors/outcomes rather than implementation details.
|
|
5
|
+
Covers data model, TOML parsing, CLI commands, and API integration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
import toml
|
|
15
|
+
|
|
16
|
+
from src.pipecatcloud._utils.deploy_utils import DeployConfigParams, load_deploy_config_file
|
|
17
|
+
from src.pipecatcloud.api import _API
|
|
18
|
+
from src.pipecatcloud.exception import ConfigFileError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestDeployConfigDataModel:
|
|
22
|
+
"""Test the DeployConfigParams data model with managed keys support."""
|
|
23
|
+
|
|
24
|
+
def test_default_keys_is_disabled(self):
|
|
25
|
+
"""When not specified, managed keys should default to disabled."""
|
|
26
|
+
# Arrange & Act
|
|
27
|
+
config = DeployConfigParams()
|
|
28
|
+
|
|
29
|
+
# Assert
|
|
30
|
+
assert config.enable_managed_keys is False
|
|
31
|
+
|
|
32
|
+
def test_can_enable_keys(self):
|
|
33
|
+
"""Integrated keys can be explicitly enabled."""
|
|
34
|
+
# Arrange & Act
|
|
35
|
+
config = DeployConfigParams(enable_managed_keys=True)
|
|
36
|
+
|
|
37
|
+
# Assert
|
|
38
|
+
assert config.enable_managed_keys is True
|
|
39
|
+
|
|
40
|
+
def test_keys_included_in_dict_representation(self):
|
|
41
|
+
"""Dictionary representation should include managed keys setting."""
|
|
42
|
+
# Arrange
|
|
43
|
+
config = DeployConfigParams(
|
|
44
|
+
agent_name="test-agent",
|
|
45
|
+
enable_managed_keys=True
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Act
|
|
49
|
+
result = config.to_dict()
|
|
50
|
+
|
|
51
|
+
# Assert
|
|
52
|
+
assert "enable_managed_keys" in result
|
|
53
|
+
assert result["enable_managed_keys"] is True
|
|
54
|
+
|
|
55
|
+
def test_keys_preserves_other_settings(self):
|
|
56
|
+
"""Adding managed keys should not affect other configuration settings."""
|
|
57
|
+
# Arrange
|
|
58
|
+
config = DeployConfigParams(
|
|
59
|
+
agent_name="test-agent",
|
|
60
|
+
image="test:latest",
|
|
61
|
+
enable_krisp=True,
|
|
62
|
+
enable_managed_keys=True
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Act
|
|
66
|
+
result = config.to_dict()
|
|
67
|
+
|
|
68
|
+
# Assert
|
|
69
|
+
assert result["agent_name"] == "test-agent"
|
|
70
|
+
assert result["image"] == "test:latest"
|
|
71
|
+
assert result["enable_krisp"] is True
|
|
72
|
+
assert result["enable_managed_keys"] is True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TestTOMLConfiguration:
|
|
76
|
+
"""Test TOML configuration file parsing with managed keys."""
|
|
77
|
+
|
|
78
|
+
@pytest.fixture
|
|
79
|
+
def temp_config_file(self, tmp_path):
|
|
80
|
+
"""Create a temporary TOML config file."""
|
|
81
|
+
config_path = tmp_path / "pcc-deploy.toml"
|
|
82
|
+
return config_path
|
|
83
|
+
|
|
84
|
+
def test_loads_keys_proxy_from_toml_when_enabled(self, temp_config_file):
|
|
85
|
+
"""TOML file with managed keys enabled should be parsed correctly."""
|
|
86
|
+
# Arrange
|
|
87
|
+
config_content = """
|
|
88
|
+
agent_name = "test-agent"
|
|
89
|
+
image = "test:latest"
|
|
90
|
+
enable_managed_keys = true
|
|
91
|
+
"""
|
|
92
|
+
temp_config_file.write_text(config_content)
|
|
93
|
+
|
|
94
|
+
# Act
|
|
95
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
96
|
+
config = load_deploy_config_file()
|
|
97
|
+
|
|
98
|
+
# Assert
|
|
99
|
+
assert config is not None
|
|
100
|
+
assert config.enable_managed_keys is True
|
|
101
|
+
|
|
102
|
+
def test_loads_keys_proxy_from_toml_when_disabled(self, temp_config_file):
|
|
103
|
+
"""TOML file with managed keys explicitly disabled should be parsed correctly."""
|
|
104
|
+
# Arrange
|
|
105
|
+
config_content = """
|
|
106
|
+
agent_name = "test-agent"
|
|
107
|
+
image = "test:latest"
|
|
108
|
+
enable_managed_keys = false
|
|
109
|
+
"""
|
|
110
|
+
temp_config_file.write_text(config_content)
|
|
111
|
+
|
|
112
|
+
# Act
|
|
113
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
114
|
+
config = load_deploy_config_file()
|
|
115
|
+
|
|
116
|
+
# Assert
|
|
117
|
+
assert config is not None
|
|
118
|
+
assert config.enable_managed_keys is False
|
|
119
|
+
|
|
120
|
+
def test_defaults_keys_proxy_when_not_in_toml(self, temp_config_file):
|
|
121
|
+
"""TOML file without managed keys setting should default to disabled."""
|
|
122
|
+
# Arrange
|
|
123
|
+
config_content = """
|
|
124
|
+
agent_name = "test-agent"
|
|
125
|
+
image = "test:latest"
|
|
126
|
+
"""
|
|
127
|
+
temp_config_file.write_text(config_content)
|
|
128
|
+
|
|
129
|
+
# Act
|
|
130
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
131
|
+
config = load_deploy_config_file()
|
|
132
|
+
|
|
133
|
+
# Assert
|
|
134
|
+
assert config is not None
|
|
135
|
+
assert config.enable_managed_keys is False
|
|
136
|
+
|
|
137
|
+
def test_preserves_other_settings_with_keys_proxy(self, temp_config_file):
|
|
138
|
+
"""Keys proxy setting should not interfere with other TOML settings."""
|
|
139
|
+
# Arrange
|
|
140
|
+
config_content = """
|
|
141
|
+
agent_name = "test-agent"
|
|
142
|
+
image = "test:latest"
|
|
143
|
+
enable_krisp = true
|
|
144
|
+
enable_managed_keys = true
|
|
145
|
+
secret_set = "my-secrets"
|
|
146
|
+
|
|
147
|
+
[scaling]
|
|
148
|
+
min_agents = 2
|
|
149
|
+
max_agents = 10
|
|
150
|
+
"""
|
|
151
|
+
temp_config_file.write_text(config_content)
|
|
152
|
+
|
|
153
|
+
# Act
|
|
154
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
155
|
+
config = load_deploy_config_file()
|
|
156
|
+
|
|
157
|
+
# Assert
|
|
158
|
+
assert config.agent_name == "test-agent"
|
|
159
|
+
assert config.image == "test:latest"
|
|
160
|
+
assert config.enable_krisp is True
|
|
161
|
+
assert config.enable_managed_keys is True
|
|
162
|
+
assert config.secret_set == "my-secrets"
|
|
163
|
+
assert config.scaling.min_agents == 2
|
|
164
|
+
assert config.scaling.max_agents == 10
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TestAPIIntegration:
|
|
169
|
+
"""Test API client integration with managed keys."""
|
|
170
|
+
|
|
171
|
+
@pytest.fixture
|
|
172
|
+
def api_client(self):
|
|
173
|
+
"""Create an API client instance."""
|
|
174
|
+
return _API(token="test-token", is_cli=True)
|
|
175
|
+
|
|
176
|
+
@pytest.mark.asyncio
|
|
177
|
+
async def test_deploy_payload_includes_keys_proxy(self, api_client):
|
|
178
|
+
"""Deploy payload should include enableIntegratedKeys field."""
|
|
179
|
+
# Arrange
|
|
180
|
+
config = DeployConfigParams(
|
|
181
|
+
agent_name="test-agent",
|
|
182
|
+
image="test:latest",
|
|
183
|
+
enable_managed_keys=True
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
187
|
+
mock_request.return_value = {"success": True}
|
|
188
|
+
|
|
189
|
+
# Act
|
|
190
|
+
await api_client._deploy(config, "test-org", update=False)
|
|
191
|
+
|
|
192
|
+
# Assert
|
|
193
|
+
mock_request.assert_called_once()
|
|
194
|
+
payload = mock_request.call_args[1]["json"]
|
|
195
|
+
assert "enableIntegratedKeysProxy" in payload
|
|
196
|
+
assert payload["enableIntegratedKeysProxy"] is True
|
|
197
|
+
|
|
198
|
+
@pytest.mark.asyncio
|
|
199
|
+
async def test_update_payload_includes_keys_proxy(self, api_client):
|
|
200
|
+
"""Update payload should include enableIntegratedKeys field."""
|
|
201
|
+
# Arrange
|
|
202
|
+
config = DeployConfigParams(
|
|
203
|
+
agent_name="test-agent",
|
|
204
|
+
image="test:latest",
|
|
205
|
+
enable_managed_keys=True
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
209
|
+
mock_request.return_value = {"success": True}
|
|
210
|
+
|
|
211
|
+
# Act
|
|
212
|
+
await api_client._deploy(config, "test-org", update=True)
|
|
213
|
+
|
|
214
|
+
# Assert
|
|
215
|
+
mock_request.assert_called_once()
|
|
216
|
+
payload = mock_request.call_args[1]["json"]
|
|
217
|
+
assert "enableIntegratedKeysProxy" in payload
|
|
218
|
+
assert payload["enableIntegratedKeysProxy"] is True
|
|
219
|
+
|
|
220
|
+
@pytest.mark.asyncio
|
|
221
|
+
async def test_disabled_keys_proxy_sends_false(self, api_client):
|
|
222
|
+
"""When managed keys is disabled, API should send false value."""
|
|
223
|
+
# Arrange
|
|
224
|
+
config = DeployConfigParams(
|
|
225
|
+
agent_name="test-agent",
|
|
226
|
+
image="test:latest",
|
|
227
|
+
enable_managed_keys=False
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
231
|
+
mock_request.return_value = {"success": True}
|
|
232
|
+
|
|
233
|
+
# Act
|
|
234
|
+
await api_client._deploy(config, "test-org", update=False)
|
|
235
|
+
|
|
236
|
+
# Assert
|
|
237
|
+
mock_request.assert_called_once()
|
|
238
|
+
payload = mock_request.call_args[1]["json"]
|
|
239
|
+
assert "enableIntegratedKeysProxy" in payload
|
|
240
|
+
assert payload["enableIntegratedKeysProxy"] is False
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class TestAgentStatusDisplay:
|
|
244
|
+
"""Test agent status command displays managed keys status."""
|
|
245
|
+
|
|
246
|
+
@pytest.fixture
|
|
247
|
+
def mock_api(self):
|
|
248
|
+
"""Mock the API agent method."""
|
|
249
|
+
with patch("src.pipecatcloud.cli.commands.agent.API") as mock_api:
|
|
250
|
+
yield mock_api
|
|
251
|
+
|
|
252
|
+
def test_displays_enabled_keys_proxy(self, mock_api, capsys):
|
|
253
|
+
"""Agent status should display when managed keys is enabled."""
|
|
254
|
+
# Arrange
|
|
255
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
256
|
+
|
|
257
|
+
mock_api.agent = AsyncMock(return_value=({
|
|
258
|
+
"ready": True,
|
|
259
|
+
"deployment": {
|
|
260
|
+
"manifest": {
|
|
261
|
+
"spec": {
|
|
262
|
+
"integratedKeysProxy": {
|
|
263
|
+
"enabled": True
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"autoScaling": {"minAgents": 0, "maxAgents": 1} # Avoid undefined variable bug
|
|
269
|
+
}, None))
|
|
270
|
+
|
|
271
|
+
# Act
|
|
272
|
+
status(agent_name="test-agent", organization="test-org")
|
|
273
|
+
|
|
274
|
+
# Assert
|
|
275
|
+
captured = capsys.readouterr()
|
|
276
|
+
assert "Managed Keys" in captured.out
|
|
277
|
+
assert "Enabled" in captured.out
|
|
278
|
+
|
|
279
|
+
def test_displays_disabled_keys_proxy(self, mock_api, capsys):
|
|
280
|
+
"""Agent status should display when managed keys is disabled."""
|
|
281
|
+
# Arrange
|
|
282
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
283
|
+
|
|
284
|
+
mock_api.agent = AsyncMock(return_value=({
|
|
285
|
+
"ready": True,
|
|
286
|
+
"deployment": {
|
|
287
|
+
"manifest": {
|
|
288
|
+
"spec": {}
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
"autoScaling": {"minAgents": 0, "maxAgents": 1} # Avoid undefined variable bug
|
|
292
|
+
}, None))
|
|
293
|
+
|
|
294
|
+
# Act
|
|
295
|
+
status(agent_name="test-agent", organization="test-org")
|
|
296
|
+
|
|
297
|
+
# Assert
|
|
298
|
+
captured = capsys.readouterr()
|
|
299
|
+
assert "Managed Keys" in captured.out
|
|
300
|
+
assert "Disabled" in captured.out
|
|
301
|
+
|
|
302
|
+
def test_handles_boolean_keys_proxy_value(self, mock_api, capsys):
|
|
303
|
+
"""Agent status should handle boolean value for backward compatibility."""
|
|
304
|
+
# Arrange
|
|
305
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
306
|
+
|
|
307
|
+
mock_api.agent = AsyncMock(return_value=({
|
|
308
|
+
"ready": True,
|
|
309
|
+
"deployment": {
|
|
310
|
+
"manifest": {
|
|
311
|
+
"spec": {
|
|
312
|
+
"integratedKeysProxy": True
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
"autoScaling": {"minAgents": 0, "maxAgents": 1} # Avoid undefined variable bug
|
|
317
|
+
}, None))
|
|
318
|
+
|
|
319
|
+
# Act
|
|
320
|
+
status(agent_name="test-agent", organization="test-org")
|
|
321
|
+
|
|
322
|
+
# Assert
|
|
323
|
+
captured = capsys.readouterr()
|
|
324
|
+
assert "Managed Keys" in captured.out
|
|
325
|
+
assert "Enabled" in captured.out
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class TestBackwardCompatibility:
|
|
329
|
+
"""Test backward compatibility with existing deployments."""
|
|
330
|
+
|
|
331
|
+
def test_existing_config_without_keys_proxy_works(self):
|
|
332
|
+
"""Existing configs without managed keys field should work unchanged."""
|
|
333
|
+
# Arrange
|
|
334
|
+
config = DeployConfigParams(
|
|
335
|
+
agent_name="test-agent",
|
|
336
|
+
image="test:latest",
|
|
337
|
+
enable_krisp=True
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Act
|
|
341
|
+
result = config.to_dict()
|
|
342
|
+
|
|
343
|
+
# Assert
|
|
344
|
+
assert result["enable_krisp"] is True
|
|
345
|
+
assert result["enable_managed_keys"] is False
|
|
346
|
+
|
|
347
|
+
def test_api_response_without_keys_proxy_doesnt_crash(self):
|
|
348
|
+
"""Agent status should handle API responses without managed keys field."""
|
|
349
|
+
# Arrange
|
|
350
|
+
data = {
|
|
351
|
+
"ready": True,
|
|
352
|
+
"deployment": {
|
|
353
|
+
"manifest": {
|
|
354
|
+
"spec": {
|
|
355
|
+
"image": "test:latest"
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Act - simulate the logic from agent.py
|
|
362
|
+
integrated_keys = data.get("deployment", {}).get("manifest", {}).get("spec", {}).get("integratedKeysProxy", {})
|
|
363
|
+
if isinstance(integrated_keys, dict):
|
|
364
|
+
keys_proxy_enabled = integrated_keys.get("enabled", False)
|
|
365
|
+
else:
|
|
366
|
+
keys_proxy_enabled = bool(integrated_keys)
|
|
367
|
+
|
|
368
|
+
# Assert
|
|
369
|
+
assert keys_proxy_enabled is False
|
|
370
|
+
|
|
371
|
+
@pytest.fixture
|
|
372
|
+
def temp_legacy_config(self, tmp_path):
|
|
373
|
+
"""Create a legacy TOML config without managed keys."""
|
|
374
|
+
config_path = tmp_path / "pcc-deploy.toml"
|
|
375
|
+
config_content = """
|
|
376
|
+
agent_name = "legacy-agent"
|
|
377
|
+
image = "legacy:latest"
|
|
378
|
+
enable_krisp = true
|
|
379
|
+
|
|
380
|
+
[scaling]
|
|
381
|
+
min_instances = 1
|
|
382
|
+
max_instances = 5
|
|
383
|
+
"""
|
|
384
|
+
config_path.write_text(config_content)
|
|
385
|
+
return config_path
|
|
386
|
+
|
|
387
|
+
def test_legacy_toml_loads_with_defaults(self, temp_legacy_config):
|
|
388
|
+
"""Legacy TOML files should load with managed keys defaulting to disabled."""
|
|
389
|
+
# Arrange & Act
|
|
390
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_legacy_config)):
|
|
391
|
+
config = load_deploy_config_file()
|
|
392
|
+
|
|
393
|
+
# Assert
|
|
394
|
+
assert config is not None
|
|
395
|
+
assert config.agent_name == "legacy-agent"
|
|
396
|
+
assert config.enable_krisp is True
|
|
397
|
+
assert config.enable_managed_keys is False
|
|
398
|
+
assert config.scaling.min_agents == 1 # Converted from min_instances
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class TestErrorHandling:
|
|
402
|
+
"""Test error handling and edge cases."""
|
|
403
|
+
|
|
404
|
+
@pytest.mark.asyncio
|
|
405
|
+
async def test_api_error_during_deploy_with_keys_proxy(self):
|
|
406
|
+
"""API errors during deployment should be handled properly."""
|
|
407
|
+
# Arrange
|
|
408
|
+
api = _API(token="test-token", is_cli=True)
|
|
409
|
+
config = DeployConfigParams(
|
|
410
|
+
agent_name="test-agent",
|
|
411
|
+
image="test:latest",
|
|
412
|
+
enable_managed_keys=True
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
with patch.object(api, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
416
|
+
mock_request.side_effect = Exception("Network error")
|
|
417
|
+
|
|
418
|
+
# Act & Assert
|
|
419
|
+
with pytest.raises(Exception, match="Network error"):
|
|
420
|
+
await api._deploy(config, "test-org", update=False)
|
|
421
|
+
|
|
422
|
+
def test_simultaneous_krisp_and_keys_proxy(self):
|
|
423
|
+
"""Both Krisp and Keys Proxy can be enabled simultaneously."""
|
|
424
|
+
# Arrange
|
|
425
|
+
config = DeployConfigParams(
|
|
426
|
+
agent_name="test-agent",
|
|
427
|
+
image="test:latest",
|
|
428
|
+
enable_krisp=True,
|
|
429
|
+
enable_managed_keys=True
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Act
|
|
433
|
+
result = config.to_dict()
|
|
434
|
+
|
|
435
|
+
# Assert
|
|
436
|
+
assert result["enable_krisp"] is True
|
|
437
|
+
assert result["enable_managed_keys"] is True
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
name: Publish to PyPI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
workflow_dispatch:
|
|
5
|
-
inputs:
|
|
6
|
-
version:
|
|
7
|
-
description: 'Version to publish (must match pyproject.toml, e.g. 0.0.4)'
|
|
8
|
-
required: true
|
|
9
|
-
type: string
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
build:
|
|
13
|
-
name: 'Build and upload wheels'
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
steps:
|
|
16
|
-
- name: Checkout code
|
|
17
|
-
uses: actions/checkout@v4
|
|
18
|
-
with:
|
|
19
|
-
fetch-tags: true
|
|
20
|
-
fetch-depth: 100
|
|
21
|
-
|
|
22
|
-
- name: Install uv
|
|
23
|
-
uses: astral-sh/setup-uv@v3
|
|
24
|
-
with:
|
|
25
|
-
version: 'latest'
|
|
26
|
-
|
|
27
|
-
- name: Set up Python
|
|
28
|
-
run: uv python install 3.12
|
|
29
|
-
|
|
30
|
-
- name: Verify version matches
|
|
31
|
-
run: |
|
|
32
|
-
PROJECT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
33
|
-
if [ "$PROJECT_VERSION" != "${{ github.event.inputs.version }}" ]; then
|
|
34
|
-
echo "Error: Input version (${{ github.event.inputs.version }}) does not match pyproject.toml version ($PROJECT_VERSION)"
|
|
35
|
-
exit 1
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
- name: Install development dependencies
|
|
39
|
-
run: uv sync --group dev
|
|
40
|
-
|
|
41
|
-
- name: Build project
|
|
42
|
-
run: uv build
|
|
43
|
-
|
|
44
|
-
- name: Upload wheels
|
|
45
|
-
uses: actions/upload-artifact@v4
|
|
46
|
-
with:
|
|
47
|
-
name: wheels
|
|
48
|
-
path: ./dist
|
|
49
|
-
|
|
50
|
-
publish-to-pypi:
|
|
51
|
-
name: 'Publish to PyPI'
|
|
52
|
-
runs-on: ubuntu-latest
|
|
53
|
-
needs: [build]
|
|
54
|
-
environment:
|
|
55
|
-
name: pypi
|
|
56
|
-
url: https://pypi.org/p/pipecat-cloud
|
|
57
|
-
permissions:
|
|
58
|
-
id-token: write
|
|
59
|
-
contents: write # Needed for tagging
|
|
60
|
-
steps:
|
|
61
|
-
- name: Checkout code
|
|
62
|
-
uses: actions/checkout@v4
|
|
63
|
-
with:
|
|
64
|
-
fetch-tags: true
|
|
65
|
-
fetch-depth: 100
|
|
66
|
-
|
|
67
|
-
- name: Create release tag
|
|
68
|
-
run: |
|
|
69
|
-
git config --local user.email "action@github.com"
|
|
70
|
-
git config --local user.name "GitHub Action"
|
|
71
|
-
git tag -a "v${{ github.event.inputs.version }}" -m "Version ${{ github.event.inputs.version }}"
|
|
72
|
-
git push origin "v${{ github.event.inputs.version }}"
|
|
73
|
-
|
|
74
|
-
- name: Download wheels
|
|
75
|
-
uses: actions/download-artifact@v4
|
|
76
|
-
with:
|
|
77
|
-
name: wheels
|
|
78
|
-
path: ./dist
|
|
79
|
-
|
|
80
|
-
- name: Publish to PyPI
|
|
81
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
82
|
-
with:
|
|
83
|
-
verbose: true
|
|
84
|
-
print-hash: true
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
name: Build and publish Python package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
|
|
7
|
-
permissions:
|
|
8
|
-
contents: read
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
release-build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v4
|
|
16
|
-
|
|
17
|
-
- uses: actions/setup-python@v5
|
|
18
|
-
with:
|
|
19
|
-
python-version: '3.10'
|
|
20
|
-
- name: Setup virtual environment
|
|
21
|
-
run: |
|
|
22
|
-
python -m venv .venv
|
|
23
|
-
- name: Install basic Python dependencies
|
|
24
|
-
run: |
|
|
25
|
-
source .venv/bin/activate
|
|
26
|
-
python -m pip install --upgrade pip
|
|
27
|
-
pip install -r dev-requirements.txt
|
|
28
|
-
- name: Build project
|
|
29
|
-
run: |
|
|
30
|
-
source .venv/bin/activate
|
|
31
|
-
python -m build
|
|
32
|
-
- name: Install project and other Python dependencies
|
|
33
|
-
run: |
|
|
34
|
-
source .venv/bin/activate
|
|
35
|
-
pip install --editable .
|
|
36
|
-
- name: Upload distributions
|
|
37
|
-
uses: actions/upload-artifact@v4
|
|
38
|
-
with:
|
|
39
|
-
name: release-dists
|
|
40
|
-
path: dist/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|