pipecatcloud 0.2.7__tar.gz → 0.2.9__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.7 → pipecatcloud-0.2.9}/CHANGELOG.md +18 -0
- {pipecatcloud-0.2.7/src/pipecatcloud.egg-info → pipecatcloud-0.2.9}/PKG-INFO +1 -1
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/pyproject.toml +3 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/_utils/deploy_utils.py +26 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/api.py +3 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/agent.py +14 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/deploy.py +18 -2
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/docker.py +1 -1
- pipecatcloud-0.2.9/src/pipecatcloud/constants.py +22 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9/src/pipecatcloud.egg-info}/PKG-INFO +1 -1
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud.egg-info/SOURCES.txt +2 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud.egg-info/entry_points.txt +3 -0
- pipecatcloud-0.2.9/tests/test_krisp_viva.py +647 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/.github/workflows/publish-pypi.yml +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/.github/workflows/publish-test.yml +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/.github/workflows/tests.yml +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/.gitignore +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/LICENSE +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/README.md +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/pipecat-cloud.png +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/pytest.ini +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/setup.cfg +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/__init__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/__main__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/__version__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/_utils/__init__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/_utils/async_utils.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/_utils/auth_utils.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/_utils/console_utils.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/_utils/local_runner.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/agent.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/__init__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/api.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/__init__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/auth.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/init.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/organizations.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/run.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/commands/secrets.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/config.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/cli/entry_point.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/config.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/exception.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/py.typed +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/session.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud.egg-info/requires.txt +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/src/pipecatcloud.egg-info/top_level.txt +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/tests/__init__.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/tests/test_agent_sessions.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/tests/test_agent_stop.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/tests/test_docker_commands.py +0 -0
- {pipecatcloud-0.2.7 → pipecatcloud-0.2.9}/tests/test_managed_keys.py +0 -0
|
@@ -5,6 +5,24 @@ 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.9] - 2025-10-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added Krisp VIVA audio filtering support with the `--krisp-viva-audio-filter`
|
|
13
|
+
option to the `deploy` command. Supports `tel` (telephone) and `pro` (professional)
|
|
14
|
+
audio filter models. Can also be configured via `pcc-deploy.toml` by adding a
|
|
15
|
+
`[krisp_viva]` section with `audio_filter = "tel"` or `audio_filter = "pro"`.
|
|
16
|
+
|
|
17
|
+
- `agent status` now displays the Krisp VIVA configuration state, showing whether
|
|
18
|
+
it's enabled and which audio filter model is active.
|
|
19
|
+
|
|
20
|
+
## [0.2.8] - 2025-10-24
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Register pipecat cloud commands as `pipecat-cli` extensions.
|
|
25
|
+
|
|
8
26
|
## [0.2.7] - 2025-10-21
|
|
9
27
|
|
|
10
28
|
### Added
|
|
@@ -42,6 +42,9 @@ dev = [
|
|
|
42
42
|
pcc = "pipecatcloud.__main__:main"
|
|
43
43
|
pipecatcloud = "pipecatcloud.__main__:main"
|
|
44
44
|
|
|
45
|
+
[project.entry-points."pipecat_cli.extensions"]
|
|
46
|
+
cloud = "pipecatcloud.cli.entry_point:entrypoint_cli_typer"
|
|
47
|
+
|
|
45
48
|
[tool.setuptools]
|
|
46
49
|
package-dir = {"" = "src"}
|
|
47
50
|
|
|
@@ -12,6 +12,7 @@ import toml
|
|
|
12
12
|
from attr import dataclass, field
|
|
13
13
|
from loguru import logger
|
|
14
14
|
|
|
15
|
+
from pipecatcloud.constants import KRISP_VIVA_MODELS, KrispVivaAudioFilter
|
|
15
16
|
from pipecatcloud.exception import ConfigFileError
|
|
16
17
|
|
|
17
18
|
DEPLOY_STATUS_MAP = {
|
|
@@ -56,6 +57,23 @@ class ScalingParams:
|
|
|
56
57
|
return {"min_agents": self.min_agents, "max_agents": self.max_agents}
|
|
57
58
|
|
|
58
59
|
|
|
60
|
+
@dataclass
|
|
61
|
+
class KrispVivaConfig:
|
|
62
|
+
audio_filter: Optional[KrispVivaAudioFilter] = None
|
|
63
|
+
|
|
64
|
+
def __attrs_post_init__(self):
|
|
65
|
+
# Validation against known models
|
|
66
|
+
# IMPORTANT: KRISP_VIVA_MODELS must be kept in sync with API configuration
|
|
67
|
+
if self.audio_filter is not None:
|
|
68
|
+
if self.audio_filter not in KRISP_VIVA_MODELS:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"audio_filter must be one of {KRISP_VIVA_MODELS}, got '{self.audio_filter}'"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def to_dict(self):
|
|
74
|
+
return {"audio_filter": self.audio_filter}
|
|
75
|
+
|
|
76
|
+
|
|
59
77
|
@dataclass
|
|
60
78
|
class DeployConfigParams:
|
|
61
79
|
agent_name: Optional[str] = None
|
|
@@ -67,6 +85,7 @@ class DeployConfigParams:
|
|
|
67
85
|
enable_managed_keys: bool = False
|
|
68
86
|
docker_config: dict = field(factory=dict)
|
|
69
87
|
agent_profile: Optional[str] = None
|
|
88
|
+
krisp_viva: KrispVivaConfig = field(factory=KrispVivaConfig)
|
|
70
89
|
|
|
71
90
|
def __attrs_post_init__(self):
|
|
72
91
|
if self.image is not None and ":" not in self.image:
|
|
@@ -83,6 +102,7 @@ class DeployConfigParams:
|
|
|
83
102
|
"enable_managed_keys": self.enable_managed_keys,
|
|
84
103
|
"docker_config": self.docker_config,
|
|
85
104
|
"agent_profile": self.agent_profile,
|
|
105
|
+
"krisp_viva": self.krisp_viva.to_dict() if self.krisp_viva else None,
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
|
|
@@ -106,11 +126,16 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
|
|
|
106
126
|
# Extract docker configuration if present
|
|
107
127
|
docker_data = config_data.pop("docker", {})
|
|
108
128
|
|
|
129
|
+
# Extract krisp_viva configuration if present
|
|
130
|
+
krisp_viva_data = config_data.pop("krisp_viva", {})
|
|
131
|
+
krisp_viva_config = KrispVivaConfig(**krisp_viva_data)
|
|
132
|
+
|
|
109
133
|
# Create DeployConfigParams with validated data
|
|
110
134
|
validated_config = DeployConfigParams(
|
|
111
135
|
**config_data,
|
|
112
136
|
scaling=scaling_params,
|
|
113
137
|
docker_config=docker_data,
|
|
138
|
+
krisp_viva=krisp_viva_config,
|
|
114
139
|
)
|
|
115
140
|
|
|
116
141
|
# Check for unexpected keys
|
|
@@ -124,6 +149,7 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
|
|
|
124
149
|
"enable_managed_keys",
|
|
125
150
|
"docker",
|
|
126
151
|
"agent_profile",
|
|
152
|
+
"krisp_viva",
|
|
127
153
|
}
|
|
128
154
|
unexpected_keys = set(config_data.keys()) - expected_keys
|
|
129
155
|
if unexpected_keys:
|
|
@@ -323,6 +323,9 @@ class _API:
|
|
|
323
323
|
"enableKrisp": deploy_config.enable_krisp,
|
|
324
324
|
"enableIntegratedKeysProxy": deploy_config.enable_managed_keys, # API expects this field name
|
|
325
325
|
"agentProfile": deploy_config.agent_profile,
|
|
326
|
+
"krispViva": {
|
|
327
|
+
"audioFilter": deploy_config.krisp_viva.audio_filter,
|
|
328
|
+
},
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
# Remove None values recursively
|
|
@@ -165,6 +165,20 @@ async def status(
|
|
|
165
165
|
"[green]Enabled[/green]" if integrated_keys_enabled else "[dim]Disabled[/dim]",
|
|
166
166
|
)
|
|
167
167
|
|
|
168
|
+
# Check for Krisp VIVA status (reverse-mapped by API)
|
|
169
|
+
krisp_viva = data.get("krispViva")
|
|
170
|
+
krisp_viva_status = "[dim]Disabled[/dim]"
|
|
171
|
+
|
|
172
|
+
if krisp_viva and isinstance(krisp_viva, dict):
|
|
173
|
+
audio_filter = krisp_viva.get("audioFilter")
|
|
174
|
+
if audio_filter:
|
|
175
|
+
krisp_viva_status = f"[green]Enabled ({audio_filter})[/green]"
|
|
176
|
+
|
|
177
|
+
deployment_table.add_row(
|
|
178
|
+
"[bold]Krisp VIVA:[/bold]",
|
|
179
|
+
krisp_viva_status,
|
|
180
|
+
)
|
|
181
|
+
|
|
168
182
|
# Autoscaling info
|
|
169
183
|
autoscaling_data = data.get("autoScaling", None)
|
|
170
184
|
if autoscaling_data:
|
|
@@ -19,9 +19,11 @@ from pipecatcloud._utils.auth_utils import requires_login
|
|
|
19
19
|
from pipecatcloud._utils.console_utils import console
|
|
20
20
|
from pipecatcloud._utils.deploy_utils import (
|
|
21
21
|
DeployConfigParams,
|
|
22
|
+
KrispVivaConfig,
|
|
22
23
|
ScalingParams,
|
|
23
24
|
with_deploy_config,
|
|
24
25
|
)
|
|
26
|
+
from pipecatcloud.constants import KRISP_VIVA_MODELS
|
|
25
27
|
from pipecatcloud.cli import PIPECAT_CLI_NAME
|
|
26
28
|
from pipecatcloud.cli.api import API
|
|
27
29
|
from pipecatcloud.cli.config import config
|
|
@@ -276,7 +278,7 @@ def create_deploy_command(app: typer.Typer):
|
|
|
276
278
|
False,
|
|
277
279
|
"--enable-krisp",
|
|
278
280
|
"-krisp",
|
|
279
|
-
help="Enable Krisp integration for this deployment",
|
|
281
|
+
help="[DEPRECATED] Enable Krisp integration for this deployment. Use --krisp-viva-audio-filter instead",
|
|
280
282
|
rich_help_panel="Deployment Configuration",
|
|
281
283
|
),
|
|
282
284
|
managed_keys: bool = typer.Option(
|
|
@@ -285,6 +287,12 @@ def create_deploy_command(app: typer.Typer):
|
|
|
285
287
|
help="Enable Managed Keys for this deployment",
|
|
286
288
|
rich_help_panel="Deployment Configuration",
|
|
287
289
|
),
|
|
290
|
+
krisp_viva_audio_filter: str = typer.Option(
|
|
291
|
+
None,
|
|
292
|
+
"--krisp-viva-audio-filter",
|
|
293
|
+
help=f"Enable Krisp VIVA with audio filter model ({' or '.join(KRISP_VIVA_MODELS)})",
|
|
294
|
+
rich_help_panel="Deployment Configuration",
|
|
295
|
+
),
|
|
288
296
|
profile: str = typer.Option(
|
|
289
297
|
None,
|
|
290
298
|
"--profile",
|
|
@@ -334,6 +342,9 @@ def create_deploy_command(app: typer.Typer):
|
|
|
334
342
|
logger.warning("max_instances is deprecated, use max_agents instead")
|
|
335
343
|
max_agents = max_instances
|
|
336
344
|
|
|
345
|
+
if krisp:
|
|
346
|
+
logger.warning("--enable-krisp is deprecated, use --krisp-viva-audio-filter instead for the latest Krisp VIVA models.")
|
|
347
|
+
|
|
337
348
|
org = organization or config.get("org")
|
|
338
349
|
|
|
339
350
|
# Compose deployment config from CLI options and config file (if provided)
|
|
@@ -361,6 +372,10 @@ def create_deploy_command(app: typer.Typer):
|
|
|
361
372
|
partial_config.enable_managed_keys = managed_keys or partial_config.enable_managed_keys
|
|
362
373
|
partial_config.agent_profile = profile or partial_config.agent_profile
|
|
363
374
|
|
|
375
|
+
# Handle Krisp VIVA configuration
|
|
376
|
+
if krisp_viva_audio_filter is not None:
|
|
377
|
+
partial_config.krisp_viva = KrispVivaConfig(audio_filter=krisp_viva_audio_filter)
|
|
378
|
+
|
|
364
379
|
# Assert agent name and image are provided
|
|
365
380
|
if not partial_config.agent_name:
|
|
366
381
|
console.error("Agent name is required")
|
|
@@ -397,7 +412,8 @@ def create_deploy_command(app: typer.Typer):
|
|
|
397
412
|
(f"[bold white]Secret set:[/bold white] {'[dim]None[/dim]' if not partial_config.secret_set else '[green] '+ partial_config.secret_set + '[/green]'}"),
|
|
398
413
|
(f"[bold white]Image pull secret:[/bold white] {'[dim]None[/dim]' if not partial_config.image_credentials else '[green]' + partial_config.image_credentials + '[/green]'}"),
|
|
399
414
|
(f"[bold white]Agent profile:[/bold white] {'[dim]None[/dim]' if not partial_config.agent_profile else '[green]' + partial_config.agent_profile + '[/green]'}"),
|
|
400
|
-
(f"[bold white]Krisp:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"),
|
|
415
|
+
(f"[bold white]Krisp (deprecated):[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_krisp else '[green]Enabled[/green]'}"),
|
|
416
|
+
(f"[bold white]Krisp VIVA:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.krisp_viva.audio_filter else '[green]Enabled (' + partial_config.krisp_viva.audio_filter + ')[/green]'}"),
|
|
401
417
|
(f"[bold white]Managed Keys:[/bold white] {'[dim]Disabled[/dim]' if not partial_config.enable_managed_keys else '[green]Enabled[/green]'}"),
|
|
402
418
|
"\n[dim]Scaling configuration:[/dim]",
|
|
403
419
|
table,
|
|
@@ -336,7 +336,7 @@ async def build_push(
|
|
|
336
336
|
console.print("\n[dim]To deploy this image, update your pcc-deploy.toml:[/dim]")
|
|
337
337
|
console.print(f' [bold]image = "{version_tag}"[/bold]')
|
|
338
338
|
console.print("\n[dim]Then run:[/dim]")
|
|
339
|
-
console.print(" [bold]
|
|
339
|
+
console.print(" [bold]pipecat cloud deploy[/bold]")
|
|
340
340
|
|
|
341
341
|
|
|
342
342
|
def create_docker_command(app: typer.Typer):
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2025, Daily
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Constants for Pipecat Cloud CLI.
|
|
9
|
+
|
|
10
|
+
IMPORTANT: These values must be kept in sync with API configuration.
|
|
11
|
+
When adding new models or options, update both the API ConfigMap and these constants.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Literal, get_args
|
|
15
|
+
|
|
16
|
+
# Krisp VIVA audio filter models
|
|
17
|
+
# These must match the keys in the API's KRISP_AUDIO_FILTER_MODELS ConfigMap
|
|
18
|
+
# Location: pipecat-cloud-sandbox/api/helm/chart/values.yaml -> krispConfig.audioFilterModels
|
|
19
|
+
KrispVivaAudioFilter = Literal["tel", "pro"]
|
|
20
|
+
|
|
21
|
+
# Derive runtime list from the Literal type for validation
|
|
22
|
+
KRISP_VIVA_MODELS = list(get_args(KrispVivaAudioFilter))
|
|
@@ -14,6 +14,7 @@ src/pipecatcloud/__version__.py
|
|
|
14
14
|
src/pipecatcloud/agent.py
|
|
15
15
|
src/pipecatcloud/api.py
|
|
16
16
|
src/pipecatcloud/config.py
|
|
17
|
+
src/pipecatcloud/constants.py
|
|
17
18
|
src/pipecatcloud/exception.py
|
|
18
19
|
src/pipecatcloud/py.typed
|
|
19
20
|
src/pipecatcloud/session.py
|
|
@@ -47,4 +48,5 @@ tests/__init__.py
|
|
|
47
48
|
tests/test_agent_sessions.py
|
|
48
49
|
tests/test_agent_stop.py
|
|
49
50
|
tests/test_docker_commands.py
|
|
51
|
+
tests/test_krisp_viva.py
|
|
50
52
|
tests/test_managed_keys.py
|
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for Krisp VIVA feature.
|
|
3
|
+
|
|
4
|
+
Tests follow AAA pattern and focus on behaviors/outcomes rather than implementation details.
|
|
5
|
+
Covers data model, TOML parsing, CLI commands, API integration, and validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from unittest.mock import AsyncMock, patch
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from src.pipecatcloud._utils.deploy_utils import (
|
|
13
|
+
DeployConfigParams,
|
|
14
|
+
KrispVivaConfig,
|
|
15
|
+
load_deploy_config_file,
|
|
16
|
+
)
|
|
17
|
+
from src.pipecatcloud.api import _API
|
|
18
|
+
from src.pipecatcloud.constants import KRISP_VIVA_MODELS
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestKrispVivaDataModel:
|
|
22
|
+
"""Test the KrispVivaConfig data model."""
|
|
23
|
+
|
|
24
|
+
def test_default_is_disabled(self):
|
|
25
|
+
"""When not specified, Krisp VIVA should default to disabled."""
|
|
26
|
+
# Arrange & Act
|
|
27
|
+
config = KrispVivaConfig()
|
|
28
|
+
|
|
29
|
+
# Assert
|
|
30
|
+
assert config.audio_filter is None
|
|
31
|
+
|
|
32
|
+
def test_can_set_tel_model(self):
|
|
33
|
+
"""Tel audio filter model can be explicitly set."""
|
|
34
|
+
# Arrange & Act
|
|
35
|
+
config = KrispVivaConfig(audio_filter="tel")
|
|
36
|
+
|
|
37
|
+
# Assert
|
|
38
|
+
assert config.audio_filter == "tel"
|
|
39
|
+
|
|
40
|
+
def test_can_set_pro_model(self):
|
|
41
|
+
"""Pro audio filter model can be explicitly set."""
|
|
42
|
+
# Arrange & Act
|
|
43
|
+
config = KrispVivaConfig(audio_filter="pro")
|
|
44
|
+
|
|
45
|
+
# Assert
|
|
46
|
+
assert config.audio_filter == "pro"
|
|
47
|
+
|
|
48
|
+
def test_rejects_invalid_model(self):
|
|
49
|
+
"""Invalid audio filter models should be rejected."""
|
|
50
|
+
# Arrange & Act & Assert
|
|
51
|
+
with pytest.raises(ValueError, match="audio_filter must be one of"):
|
|
52
|
+
KrispVivaConfig(audio_filter="invalid-model")
|
|
53
|
+
|
|
54
|
+
def test_validation_uses_constants(self):
|
|
55
|
+
"""Validation should use KRISP_VIVA_MODELS constant."""
|
|
56
|
+
# Arrange & Act & Assert
|
|
57
|
+
with pytest.raises(ValueError) as exc_info:
|
|
58
|
+
KrispVivaConfig(audio_filter="bad-model")
|
|
59
|
+
|
|
60
|
+
# Assert error message includes the models from constants
|
|
61
|
+
error_message = str(exc_info.value)
|
|
62
|
+
for model in KRISP_VIVA_MODELS:
|
|
63
|
+
assert model in error_message
|
|
64
|
+
|
|
65
|
+
def test_to_dict_when_disabled(self):
|
|
66
|
+
"""Dictionary representation when disabled should have null audio_filter."""
|
|
67
|
+
# Arrange
|
|
68
|
+
config = KrispVivaConfig()
|
|
69
|
+
|
|
70
|
+
# Act
|
|
71
|
+
result = config.to_dict()
|
|
72
|
+
|
|
73
|
+
# Assert
|
|
74
|
+
assert result == {"audio_filter": None}
|
|
75
|
+
|
|
76
|
+
def test_to_dict_when_enabled(self):
|
|
77
|
+
"""Dictionary representation when enabled should include the model."""
|
|
78
|
+
# Arrange
|
|
79
|
+
config = KrispVivaConfig(audio_filter="tel")
|
|
80
|
+
|
|
81
|
+
# Act
|
|
82
|
+
result = config.to_dict()
|
|
83
|
+
|
|
84
|
+
# Assert
|
|
85
|
+
assert result == {"audio_filter": "tel"}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestDeployConfigIntegration:
|
|
89
|
+
"""Test DeployConfigParams integration with Krisp VIVA."""
|
|
90
|
+
|
|
91
|
+
def test_default_krisp_viva_is_disabled(self):
|
|
92
|
+
"""When not specified, Krisp VIVA should default to disabled."""
|
|
93
|
+
# Arrange & Act
|
|
94
|
+
config = DeployConfigParams()
|
|
95
|
+
|
|
96
|
+
# Assert
|
|
97
|
+
assert config.krisp_viva is not None
|
|
98
|
+
assert config.krisp_viva.audio_filter is None
|
|
99
|
+
|
|
100
|
+
def test_can_enable_krisp_viva(self):
|
|
101
|
+
"""Krisp VIVA can be explicitly enabled with a model."""
|
|
102
|
+
# Arrange & Act
|
|
103
|
+
config = DeployConfigParams(krisp_viva=KrispVivaConfig(audio_filter="tel"))
|
|
104
|
+
|
|
105
|
+
# Assert
|
|
106
|
+
assert config.krisp_viva.audio_filter == "tel"
|
|
107
|
+
|
|
108
|
+
def test_krisp_viva_included_in_dict_representation(self):
|
|
109
|
+
"""Dictionary representation should include Krisp VIVA configuration."""
|
|
110
|
+
# Arrange
|
|
111
|
+
config = DeployConfigParams(
|
|
112
|
+
agent_name="test-agent", krisp_viva=KrispVivaConfig(audio_filter="pro")
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Act
|
|
116
|
+
result = config.to_dict()
|
|
117
|
+
|
|
118
|
+
# Assert
|
|
119
|
+
assert "krisp_viva" in result
|
|
120
|
+
assert result["krisp_viva"]["audio_filter"] == "pro"
|
|
121
|
+
|
|
122
|
+
def test_krisp_viva_preserves_other_settings(self):
|
|
123
|
+
"""Adding Krisp VIVA should not affect other configuration settings."""
|
|
124
|
+
# Arrange
|
|
125
|
+
config = DeployConfigParams(
|
|
126
|
+
agent_name="test-agent",
|
|
127
|
+
image="test:latest",
|
|
128
|
+
enable_krisp=True,
|
|
129
|
+
enable_managed_keys=True,
|
|
130
|
+
krisp_viva=KrispVivaConfig(audio_filter="tel"),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Act
|
|
134
|
+
result = config.to_dict()
|
|
135
|
+
|
|
136
|
+
# Assert
|
|
137
|
+
assert result["agent_name"] == "test-agent"
|
|
138
|
+
assert result["image"] == "test:latest"
|
|
139
|
+
assert result["enable_krisp"] is True
|
|
140
|
+
assert result["enable_managed_keys"] is True
|
|
141
|
+
assert result["krisp_viva"]["audio_filter"] == "tel"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestTOMLConfiguration:
|
|
145
|
+
"""Test TOML configuration file parsing with Krisp VIVA."""
|
|
146
|
+
|
|
147
|
+
@pytest.fixture
|
|
148
|
+
def temp_config_file(self, tmp_path):
|
|
149
|
+
"""Create a temporary TOML config file."""
|
|
150
|
+
config_path = tmp_path / "pcc-deploy.toml"
|
|
151
|
+
return config_path
|
|
152
|
+
|
|
153
|
+
def test_loads_krisp_viva_tel_from_toml(self, temp_config_file):
|
|
154
|
+
"""TOML file with Krisp VIVA tel model should be parsed correctly."""
|
|
155
|
+
# Arrange
|
|
156
|
+
config_content = """
|
|
157
|
+
agent_name = "test-agent"
|
|
158
|
+
image = "test:latest"
|
|
159
|
+
|
|
160
|
+
[krisp_viva]
|
|
161
|
+
audio_filter = "tel"
|
|
162
|
+
"""
|
|
163
|
+
temp_config_file.write_text(config_content)
|
|
164
|
+
|
|
165
|
+
# Act
|
|
166
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
167
|
+
config = load_deploy_config_file()
|
|
168
|
+
|
|
169
|
+
# Assert
|
|
170
|
+
assert config is not None
|
|
171
|
+
assert config.krisp_viva.audio_filter == "tel"
|
|
172
|
+
|
|
173
|
+
def test_loads_krisp_viva_pro_from_toml(self, temp_config_file):
|
|
174
|
+
"""TOML file with Krisp VIVA pro model should be parsed correctly."""
|
|
175
|
+
# Arrange
|
|
176
|
+
config_content = """
|
|
177
|
+
agent_name = "test-agent"
|
|
178
|
+
image = "test:latest"
|
|
179
|
+
|
|
180
|
+
[krisp_viva]
|
|
181
|
+
audio_filter = "pro"
|
|
182
|
+
"""
|
|
183
|
+
temp_config_file.write_text(config_content)
|
|
184
|
+
|
|
185
|
+
# Act
|
|
186
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
187
|
+
config = load_deploy_config_file()
|
|
188
|
+
|
|
189
|
+
# Assert
|
|
190
|
+
assert config is not None
|
|
191
|
+
assert config.krisp_viva.audio_filter == "pro"
|
|
192
|
+
|
|
193
|
+
def test_rejects_invalid_model_from_toml(self, temp_config_file):
|
|
194
|
+
"""TOML file with invalid Krisp VIVA model should raise error."""
|
|
195
|
+
# Arrange
|
|
196
|
+
config_content = """
|
|
197
|
+
agent_name = "test-agent"
|
|
198
|
+
image = "test:latest"
|
|
199
|
+
|
|
200
|
+
[krisp_viva]
|
|
201
|
+
audio_filter = "invalid-model"
|
|
202
|
+
"""
|
|
203
|
+
temp_config_file.write_text(config_content)
|
|
204
|
+
|
|
205
|
+
# Act & Assert
|
|
206
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
207
|
+
with pytest.raises(Exception): # ConfigFileError wraps ValueError
|
|
208
|
+
load_deploy_config_file()
|
|
209
|
+
|
|
210
|
+
def test_defaults_when_not_in_toml(self, temp_config_file):
|
|
211
|
+
"""TOML file without Krisp VIVA should default to disabled."""
|
|
212
|
+
# Arrange
|
|
213
|
+
config_content = """
|
|
214
|
+
agent_name = "test-agent"
|
|
215
|
+
image = "test:latest"
|
|
216
|
+
"""
|
|
217
|
+
temp_config_file.write_text(config_content)
|
|
218
|
+
|
|
219
|
+
# Act
|
|
220
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
221
|
+
config = load_deploy_config_file()
|
|
222
|
+
|
|
223
|
+
# Assert
|
|
224
|
+
assert config is not None
|
|
225
|
+
assert config.krisp_viva.audio_filter is None
|
|
226
|
+
|
|
227
|
+
def test_preserves_other_settings_with_krisp_viva(self, temp_config_file):
|
|
228
|
+
"""Krisp VIVA setting should not interfere with other TOML settings."""
|
|
229
|
+
# Arrange
|
|
230
|
+
config_content = """
|
|
231
|
+
agent_name = "test-agent"
|
|
232
|
+
image = "test:latest"
|
|
233
|
+
enable_krisp = true
|
|
234
|
+
enable_managed_keys = true
|
|
235
|
+
secret_set = "my-secrets"
|
|
236
|
+
|
|
237
|
+
[scaling]
|
|
238
|
+
min_agents = 2
|
|
239
|
+
max_agents = 10
|
|
240
|
+
|
|
241
|
+
[krisp_viva]
|
|
242
|
+
audio_filter = "pro"
|
|
243
|
+
"""
|
|
244
|
+
temp_config_file.write_text(config_content)
|
|
245
|
+
|
|
246
|
+
# Act
|
|
247
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_config_file)):
|
|
248
|
+
config = load_deploy_config_file()
|
|
249
|
+
|
|
250
|
+
# Assert
|
|
251
|
+
assert config.agent_name == "test-agent"
|
|
252
|
+
assert config.image == "test:latest"
|
|
253
|
+
assert config.enable_krisp is True
|
|
254
|
+
assert config.enable_managed_keys is True
|
|
255
|
+
assert config.secret_set == "my-secrets"
|
|
256
|
+
assert config.scaling.min_agents == 2
|
|
257
|
+
assert config.scaling.max_agents == 10
|
|
258
|
+
assert config.krisp_viva.audio_filter == "pro"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class TestAPIIntegration:
|
|
262
|
+
"""Test API client integration with Krisp VIVA."""
|
|
263
|
+
|
|
264
|
+
@pytest.fixture
|
|
265
|
+
def api_client(self):
|
|
266
|
+
"""Create an API client instance."""
|
|
267
|
+
return _API(token="test-token", is_cli=True)
|
|
268
|
+
|
|
269
|
+
@pytest.mark.asyncio
|
|
270
|
+
async def test_deploy_payload_includes_krisp_viva_tel(self, api_client):
|
|
271
|
+
"""Deploy payload should include krispViva with tel model."""
|
|
272
|
+
# Arrange
|
|
273
|
+
config = DeployConfigParams(
|
|
274
|
+
agent_name="test-agent",
|
|
275
|
+
image="test:latest",
|
|
276
|
+
krisp_viva=KrispVivaConfig(audio_filter="tel"),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
280
|
+
mock_request.return_value = {"success": True}
|
|
281
|
+
|
|
282
|
+
# Act
|
|
283
|
+
await api_client._deploy(config, "test-org", update=False)
|
|
284
|
+
|
|
285
|
+
# Assert
|
|
286
|
+
mock_request.assert_called_once()
|
|
287
|
+
payload = mock_request.call_args[1]["json"]
|
|
288
|
+
assert "krispViva" in payload
|
|
289
|
+
assert payload["krispViva"]["audioFilter"] == "tel"
|
|
290
|
+
|
|
291
|
+
@pytest.mark.asyncio
|
|
292
|
+
async def test_deploy_payload_includes_krisp_viva_pro(self, api_client):
|
|
293
|
+
"""Deploy payload should include krispViva with pro model."""
|
|
294
|
+
# Arrange
|
|
295
|
+
config = DeployConfigParams(
|
|
296
|
+
agent_name="test-agent",
|
|
297
|
+
image="test:latest",
|
|
298
|
+
krisp_viva=KrispVivaConfig(audio_filter="pro"),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
302
|
+
mock_request.return_value = {"success": True}
|
|
303
|
+
|
|
304
|
+
# Act
|
|
305
|
+
await api_client._deploy(config, "test-org", update=False)
|
|
306
|
+
|
|
307
|
+
# Assert
|
|
308
|
+
mock_request.assert_called_once()
|
|
309
|
+
payload = mock_request.call_args[1]["json"]
|
|
310
|
+
assert "krispViva" in payload
|
|
311
|
+
assert payload["krispViva"]["audioFilter"] == "pro"
|
|
312
|
+
|
|
313
|
+
@pytest.mark.asyncio
|
|
314
|
+
async def test_update_payload_includes_krisp_viva(self, api_client):
|
|
315
|
+
"""Update payload should include krispViva field."""
|
|
316
|
+
# Arrange
|
|
317
|
+
config = DeployConfigParams(
|
|
318
|
+
agent_name="test-agent",
|
|
319
|
+
image="test:latest",
|
|
320
|
+
krisp_viva=KrispVivaConfig(audio_filter="tel"),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
324
|
+
mock_request.return_value = {"success": True}
|
|
325
|
+
|
|
326
|
+
# Act
|
|
327
|
+
await api_client._deploy(config, "test-org", update=True)
|
|
328
|
+
|
|
329
|
+
# Assert
|
|
330
|
+
mock_request.assert_called_once()
|
|
331
|
+
payload = mock_request.call_args[1]["json"]
|
|
332
|
+
assert "krispViva" in payload
|
|
333
|
+
assert payload["krispViva"]["audioFilter"] == "tel"
|
|
334
|
+
|
|
335
|
+
@pytest.mark.asyncio
|
|
336
|
+
async def test_disabled_krisp_viva_sends_empty_object(self, api_client):
|
|
337
|
+
"""When Krisp VIVA is disabled, API should send empty krispViva object."""
|
|
338
|
+
# Arrange
|
|
339
|
+
config = DeployConfigParams(
|
|
340
|
+
agent_name="test-agent", image="test:latest", krisp_viva=KrispVivaConfig()
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
with patch.object(api_client, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
344
|
+
mock_request.return_value = {"success": True}
|
|
345
|
+
|
|
346
|
+
# Act
|
|
347
|
+
await api_client._deploy(config, "test-org", update=False)
|
|
348
|
+
|
|
349
|
+
# Assert
|
|
350
|
+
mock_request.assert_called_once()
|
|
351
|
+
payload = mock_request.call_args[1]["json"]
|
|
352
|
+
assert "krispViva" in payload
|
|
353
|
+
# None values are filtered out by remove_none_values, leaving empty dict
|
|
354
|
+
assert payload["krispViva"] == {}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class TestAgentStatusDisplay:
|
|
358
|
+
"""Test agent status command displays Krisp VIVA status."""
|
|
359
|
+
|
|
360
|
+
@pytest.fixture
|
|
361
|
+
def mock_api(self):
|
|
362
|
+
"""Mock the API agent method."""
|
|
363
|
+
with patch("src.pipecatcloud.cli.commands.agent.API") as mock_api:
|
|
364
|
+
yield mock_api
|
|
365
|
+
|
|
366
|
+
def test_displays_enabled_krisp_viva_tel(self, mock_api, capsys):
|
|
367
|
+
"""Agent status should display when Krisp VIVA is enabled with tel model."""
|
|
368
|
+
# Arrange
|
|
369
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
370
|
+
|
|
371
|
+
mock_api.agent = AsyncMock(
|
|
372
|
+
return_value=(
|
|
373
|
+
{
|
|
374
|
+
"ready": True,
|
|
375
|
+
"deployment": {"manifest": {"spec": {}}},
|
|
376
|
+
"krispViva": {"audioFilter": "tel"},
|
|
377
|
+
"autoScaling": {
|
|
378
|
+
"minReplicas": 0,
|
|
379
|
+
"maxReplicas": 1,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
None,
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Act
|
|
387
|
+
status(agent_name="test-agent", organization="test-org")
|
|
388
|
+
|
|
389
|
+
# Assert
|
|
390
|
+
captured = capsys.readouterr()
|
|
391
|
+
assert "Krisp VIVA" in captured.out
|
|
392
|
+
assert "Enabled" in captured.out
|
|
393
|
+
assert "tel" in captured.out
|
|
394
|
+
|
|
395
|
+
def test_displays_enabled_krisp_viva_pro(self, mock_api, capsys):
|
|
396
|
+
"""Agent status should display when Krisp VIVA is enabled with pro model."""
|
|
397
|
+
# Arrange
|
|
398
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
399
|
+
|
|
400
|
+
mock_api.agent = AsyncMock(
|
|
401
|
+
return_value=(
|
|
402
|
+
{
|
|
403
|
+
"ready": True,
|
|
404
|
+
"deployment": {"manifest": {"spec": {}}},
|
|
405
|
+
"krispViva": {"audioFilter": "pro"},
|
|
406
|
+
"autoScaling": {
|
|
407
|
+
"minReplicas": 0,
|
|
408
|
+
"maxReplicas": 1,
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
None,
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Act
|
|
416
|
+
status(agent_name="test-agent", organization="test-org")
|
|
417
|
+
|
|
418
|
+
# Assert
|
|
419
|
+
captured = capsys.readouterr()
|
|
420
|
+
assert "Krisp VIVA" in captured.out
|
|
421
|
+
assert "Enabled" in captured.out
|
|
422
|
+
assert "pro" in captured.out
|
|
423
|
+
|
|
424
|
+
def test_displays_disabled_krisp_viva(self, mock_api, capsys):
|
|
425
|
+
"""Agent status should display when Krisp VIVA is disabled."""
|
|
426
|
+
# Arrange
|
|
427
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
428
|
+
|
|
429
|
+
mock_api.agent = AsyncMock(
|
|
430
|
+
return_value=(
|
|
431
|
+
{
|
|
432
|
+
"ready": True,
|
|
433
|
+
"deployment": {"manifest": {"spec": {}}},
|
|
434
|
+
"autoScaling": {
|
|
435
|
+
"minReplicas": 0,
|
|
436
|
+
"maxReplicas": 1,
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
None,
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# Act
|
|
444
|
+
status(agent_name="test-agent", organization="test-org")
|
|
445
|
+
|
|
446
|
+
# Assert
|
|
447
|
+
captured = capsys.readouterr()
|
|
448
|
+
assert "Krisp VIVA" in captured.out
|
|
449
|
+
assert "Disabled" in captured.out
|
|
450
|
+
|
|
451
|
+
def test_handles_null_audio_filter(self, mock_api, capsys):
|
|
452
|
+
"""Agent status should handle krispViva with null audioFilter."""
|
|
453
|
+
# Arrange
|
|
454
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
455
|
+
|
|
456
|
+
mock_api.agent = AsyncMock(
|
|
457
|
+
return_value=(
|
|
458
|
+
{
|
|
459
|
+
"ready": True,
|
|
460
|
+
"deployment": {"manifest": {"spec": {}}},
|
|
461
|
+
"krispViva": {"audioFilter": None},
|
|
462
|
+
"autoScaling": {
|
|
463
|
+
"minReplicas": 0,
|
|
464
|
+
"maxReplicas": 1,
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
None,
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# Act
|
|
472
|
+
status(agent_name="test-agent", organization="test-org")
|
|
473
|
+
|
|
474
|
+
# Assert
|
|
475
|
+
captured = capsys.readouterr()
|
|
476
|
+
assert "Krisp VIVA" in captured.out
|
|
477
|
+
assert "Disabled" in captured.out
|
|
478
|
+
|
|
479
|
+
def test_handles_missing_krisp_viva_field(self, mock_api, capsys):
|
|
480
|
+
"""Agent status should handle responses without krispViva field."""
|
|
481
|
+
# Arrange
|
|
482
|
+
from src.pipecatcloud.cli.commands.agent import status
|
|
483
|
+
|
|
484
|
+
mock_api.agent = AsyncMock(
|
|
485
|
+
return_value=(
|
|
486
|
+
{
|
|
487
|
+
"ready": True,
|
|
488
|
+
"deployment": {"manifest": {"spec": {}}},
|
|
489
|
+
"autoScaling": {
|
|
490
|
+
"minReplicas": 0,
|
|
491
|
+
"maxReplicas": 1,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
None,
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# Act
|
|
499
|
+
status(agent_name="test-agent", organization="test-org")
|
|
500
|
+
|
|
501
|
+
# Assert
|
|
502
|
+
captured = capsys.readouterr()
|
|
503
|
+
assert "Krisp VIVA" in captured.out
|
|
504
|
+
assert "Disabled" in captured.out
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class TestBackwardCompatibility:
|
|
508
|
+
"""Test backward compatibility with existing deployments."""
|
|
509
|
+
|
|
510
|
+
def test_existing_config_without_krisp_viva_works(self):
|
|
511
|
+
"""Existing configs without Krisp VIVA field should work unchanged."""
|
|
512
|
+
# Arrange
|
|
513
|
+
config = DeployConfigParams(agent_name="test-agent", image="test:latest", enable_krisp=True)
|
|
514
|
+
|
|
515
|
+
# Act
|
|
516
|
+
result = config.to_dict()
|
|
517
|
+
|
|
518
|
+
# Assert
|
|
519
|
+
assert result["enable_krisp"] is True
|
|
520
|
+
assert result["krisp_viva"]["audio_filter"] is None
|
|
521
|
+
|
|
522
|
+
def test_api_response_without_krisp_viva_doesnt_crash(self):
|
|
523
|
+
"""Agent status should handle API responses without Krisp VIVA field."""
|
|
524
|
+
# Arrange
|
|
525
|
+
data = {"ready": True, "deployment": {"manifest": {"spec": {"image": "test:latest"}}}}
|
|
526
|
+
|
|
527
|
+
# Act - simulate the logic from agent.py
|
|
528
|
+
krisp_viva = data.get("krispViva")
|
|
529
|
+
krisp_viva_status = "[dim]Disabled[/dim]"
|
|
530
|
+
|
|
531
|
+
if krisp_viva and isinstance(krisp_viva, dict):
|
|
532
|
+
audio_filter = krisp_viva.get("audioFilter")
|
|
533
|
+
if audio_filter:
|
|
534
|
+
krisp_viva_status = f"[green]Enabled ({audio_filter})[/green]"
|
|
535
|
+
|
|
536
|
+
# Assert
|
|
537
|
+
assert krisp_viva_status == "[dim]Disabled[/dim]"
|
|
538
|
+
|
|
539
|
+
@pytest.fixture
|
|
540
|
+
def temp_legacy_config(self, tmp_path):
|
|
541
|
+
"""Create a legacy TOML config without Krisp VIVA."""
|
|
542
|
+
config_path = tmp_path / "pcc-deploy.toml"
|
|
543
|
+
config_content = """
|
|
544
|
+
agent_name = "legacy-agent"
|
|
545
|
+
image = "legacy:latest"
|
|
546
|
+
enable_krisp = true
|
|
547
|
+
enable_managed_keys = true
|
|
548
|
+
|
|
549
|
+
[scaling]
|
|
550
|
+
min_agents = 1
|
|
551
|
+
max_agents = 5
|
|
552
|
+
"""
|
|
553
|
+
config_path.write_text(config_content)
|
|
554
|
+
return config_path
|
|
555
|
+
|
|
556
|
+
def test_legacy_toml_loads_with_defaults(self, temp_legacy_config):
|
|
557
|
+
"""Legacy TOML files should load with Krisp VIVA defaulting to disabled."""
|
|
558
|
+
# Arrange & Act
|
|
559
|
+
with patch("pipecatcloud.cli.config.deploy_config_path", str(temp_legacy_config)):
|
|
560
|
+
config = load_deploy_config_file()
|
|
561
|
+
|
|
562
|
+
# Assert
|
|
563
|
+
assert config is not None
|
|
564
|
+
assert config.agent_name == "legacy-agent"
|
|
565
|
+
assert config.enable_krisp is True
|
|
566
|
+
assert config.enable_managed_keys is True
|
|
567
|
+
assert config.krisp_viva.audio_filter is None
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class TestErrorHandling:
|
|
571
|
+
"""Test error handling and edge cases."""
|
|
572
|
+
|
|
573
|
+
@pytest.mark.asyncio
|
|
574
|
+
async def test_api_error_during_deploy_with_krisp_viva(self):
|
|
575
|
+
"""API errors during deployment with Krisp VIVA should be handled properly."""
|
|
576
|
+
# Arrange
|
|
577
|
+
api = _API(token="test-token", is_cli=True)
|
|
578
|
+
config = DeployConfigParams(
|
|
579
|
+
agent_name="test-agent",
|
|
580
|
+
image="test:latest",
|
|
581
|
+
krisp_viva=KrispVivaConfig(audio_filter="tel"),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
with patch.object(api, "_base_request", new_callable=AsyncMock) as mock_request:
|
|
585
|
+
mock_request.side_effect = Exception("Network error")
|
|
586
|
+
|
|
587
|
+
# Act & Assert
|
|
588
|
+
with pytest.raises(Exception, match="Network error"):
|
|
589
|
+
await api._deploy(config, "test-org", update=False)
|
|
590
|
+
|
|
591
|
+
def test_simultaneous_krisp_and_krisp_viva(self):
|
|
592
|
+
"""Both Krisp and Krisp VIVA can be enabled simultaneously."""
|
|
593
|
+
# Arrange
|
|
594
|
+
config = DeployConfigParams(
|
|
595
|
+
agent_name="test-agent",
|
|
596
|
+
image="test:latest",
|
|
597
|
+
enable_krisp=True,
|
|
598
|
+
krisp_viva=KrispVivaConfig(audio_filter="tel"),
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# Act
|
|
602
|
+
result = config.to_dict()
|
|
603
|
+
|
|
604
|
+
# Assert
|
|
605
|
+
assert result["enable_krisp"] is True
|
|
606
|
+
assert result["krisp_viva"]["audio_filter"] == "tel"
|
|
607
|
+
|
|
608
|
+
def test_all_features_enabled_together(self):
|
|
609
|
+
"""Krisp, Managed Keys, and Krisp VIVA can all be enabled together."""
|
|
610
|
+
# Arrange
|
|
611
|
+
config = DeployConfigParams(
|
|
612
|
+
agent_name="test-agent",
|
|
613
|
+
image="test:latest",
|
|
614
|
+
enable_krisp=True,
|
|
615
|
+
enable_managed_keys=True,
|
|
616
|
+
krisp_viva=KrispVivaConfig(audio_filter="pro"),
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
# Act
|
|
620
|
+
result = config.to_dict()
|
|
621
|
+
|
|
622
|
+
# Assert
|
|
623
|
+
assert result["enable_krisp"] is True
|
|
624
|
+
assert result["enable_managed_keys"] is True
|
|
625
|
+
assert result["krisp_viva"]["audio_filter"] == "pro"
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class TestConstantsSynchronization:
|
|
629
|
+
"""Test that constants are properly synchronized and used."""
|
|
630
|
+
|
|
631
|
+
def test_all_models_in_constants_are_valid(self):
|
|
632
|
+
"""All models in KRISP_VIVA_MODELS should be accepted."""
|
|
633
|
+
# Arrange & Act & Assert
|
|
634
|
+
for model in KRISP_VIVA_MODELS:
|
|
635
|
+
config = KrispVivaConfig(audio_filter=model)
|
|
636
|
+
assert config.audio_filter == model
|
|
637
|
+
|
|
638
|
+
def test_constants_not_empty(self):
|
|
639
|
+
"""KRISP_VIVA_MODELS should not be empty."""
|
|
640
|
+
# Arrange & Act & Assert
|
|
641
|
+
assert len(KRISP_VIVA_MODELS) > 0
|
|
642
|
+
|
|
643
|
+
def test_constants_contain_expected_models(self):
|
|
644
|
+
"""KRISP_VIVA_MODELS should contain expected default models."""
|
|
645
|
+
# Arrange & Act & Assert
|
|
646
|
+
assert "tel" in KRISP_VIVA_MODELS
|
|
647
|
+
assert "pro" in KRISP_VIVA_MODELS
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|