pipecatcloud 0.2.6__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/CHANGELOG.md +17 -0
  2. {pipecatcloud-0.2.6/src/pipecatcloud.egg-info → pipecatcloud-0.2.7}/PKG-INFO +1 -1
  3. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/agent.py +9 -7
  4. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/api.py +14 -0
  5. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/agent.py +67 -0
  6. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/init.py +18 -1
  7. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7/src/pipecatcloud.egg-info}/PKG-INFO +1 -1
  8. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud.egg-info/SOURCES.txt +1 -0
  9. pipecatcloud-0.2.7/tests/test_agent_stop.py +99 -0
  10. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/.github/workflows/publish-pypi.yml +0 -0
  11. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/.github/workflows/publish-test.yml +0 -0
  12. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/.github/workflows/tests.yml +0 -0
  13. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/.gitignore +0 -0
  14. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/LICENSE +0 -0
  15. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/README.md +0 -0
  16. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/pipecat-cloud.png +0 -0
  17. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/pyproject.toml +0 -0
  18. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/pytest.ini +0 -0
  19. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/setup.cfg +0 -0
  20. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/__init__.py +0 -0
  21. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/__main__.py +0 -0
  22. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/__version__.py +0 -0
  23. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/_utils/__init__.py +0 -0
  24. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/_utils/async_utils.py +0 -0
  25. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  26. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/_utils/console_utils.py +0 -0
  27. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  28. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/_utils/local_runner.py +0 -0
  29. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/__init__.py +0 -0
  30. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/api.py +0 -0
  31. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  32. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/auth.py +0 -0
  33. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/deploy.py +0 -0
  34. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/docker.py +0 -0
  35. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  36. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/run.py +0 -0
  37. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  38. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/config.py +0 -0
  39. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/cli/entry_point.py +0 -0
  40. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/config.py +0 -0
  41. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/exception.py +0 -0
  42. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/py.typed +0 -0
  43. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/session.py +0 -0
  44. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud/smallwebrtc/session_manager.py +0 -0
  45. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  46. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  47. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud.egg-info/requires.txt +0 -0
  48. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/src/pipecatcloud.egg-info/top_level.txt +0 -0
  49. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/tests/__init__.py +0 -0
  50. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/tests/test_agent_sessions.py +0 -0
  51. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/tests/test_docker_commands.py +0 -0
  52. {pipecatcloud-0.2.6 → pipecatcloud-0.2.7}/tests/test_managed_keys.py +0 -0
@@ -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.7] - 2025-10-21
9
+
10
+ ### Added
11
+
12
+ - Added a `stop` command to `pcc agent` that allows stopping an ongoing Pipecat
13
+ Cloud session.
14
+
15
+ ### Changed
16
+
17
+ - Fallback `RunnerArguments` now include a `body` field in the base class.
18
+
19
+ ### Deprecated
20
+
21
+ - The `pcc init` command is now deprecated and will be removed in a future
22
+ version. Use the [Pipecat CLI](https://github.com/pipecat-ai/pipecat-cli)
23
+ instead.
24
+
8
25
  ## [0.2.6] - 2025-10-09
9
26
 
10
27
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.6
3
+ Version: 0.2.7
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/
@@ -15,8 +15,8 @@ try:
15
15
  from pipecat.runner.types import (
16
16
  DailyRunnerArguments,
17
17
  RunnerArguments,
18
- WebSocketRunnerArguments,
19
18
  SmallWebRTCRunnerArguments,
19
+ WebSocketRunnerArguments,
20
20
  )
21
21
 
22
22
  _PIPECAT_RUNNER_TYPES_AVAILABLE = True
@@ -33,9 +33,10 @@ except ImportError:
33
33
  will be removed in a future release.
34
34
  """
35
35
 
36
- handle_sigint: bool = field(init=False)
37
- handle_sigterm: bool = field(init=False)
38
- pipeline_idle_timeout_secs: int = field(init=False)
36
+ handle_sigint: bool = field(init=False, kw_only=True)
37
+ handle_sigterm: bool = field(init=False, kw_only=True)
38
+ pipeline_idle_timeout_secs: int = field(init=False, kw_only=True)
39
+ body: Optional[Any] = field(default_factory=dict, kw_only=True)
39
40
 
40
41
  def __post_init__(self):
41
42
  self.handle_sigint = False
@@ -52,7 +53,6 @@ except ImportError:
52
53
 
53
54
  room_url: str
54
55
  token: str
55
- body: Any
56
56
 
57
57
  @dataclass
58
58
  class WebSocketRunnerArguments(RunnerArguments):
@@ -63,7 +63,6 @@ except ImportError:
63
63
  """
64
64
 
65
65
  websocket: WebSocket
66
- body: Any
67
66
 
68
67
  @dataclass
69
68
  class SmallWebRTCRunnerArguments(RunnerArguments):
@@ -112,7 +111,9 @@ class PipecatSessionArguments(RunnerArguments, SessionArguments):
112
111
  For best compatibility, install: pip install pipecatcloud[pipecat]
113
112
  """
114
113
 
115
- body: Any
114
+ def __post_init__(self):
115
+ RunnerArguments.__post_init__(self)
116
+ _warn_standalone_usage()
116
117
 
117
118
 
118
119
  @dataclass
@@ -144,6 +145,7 @@ class WebSocketSessionArguments(WebSocketRunnerArguments, SessionArguments):
144
145
  WebSocketRunnerArguments.__post_init__(self)
145
146
  _warn_standalone_usage()
146
147
 
148
+
147
149
  @dataclass
148
150
  class SmallWebRTCSessionArguments(SmallWebRTCRunnerArguments, SessionArguments):
149
151
  """SmallWebRTCTransport based agent session arguments.
@@ -438,3 +438,17 @@ class _API:
438
438
  @property
439
439
  def agent_sessions(self):
440
440
  return self.create_api_method(self._agent_sessions)
441
+
442
+ async def _agent_session_terminate(self, agent_name: str, session_id: str, org: str) -> dict | None:
443
+ url = f"{self.construct_api_url('services_sessions_path').format(org=org, service=agent_name)}/{session_id}"
444
+ return await self._base_request("DELETE", url, not_found_is_empty=True)
445
+
446
+ @property
447
+ def agent_session_terminate(self):
448
+ """Terminate an active session for an agent.
449
+ Args:
450
+ agent_name: Name of the agent
451
+ session_id: ID of the session to terminate
452
+ org: Organization ID
453
+ """
454
+ return self.create_api_method(self._agent_session_terminate)
@@ -730,3 +730,70 @@ async def start(
730
730
  url = f"{daily_room}?t={daily_token}"
731
731
  console.print("\nJoin your session by visiting the link below:")
732
732
  console.print(f"[link={url}]{url}[/link]")
733
+
734
+
735
+ @agent_cli.command(name="stop", help="Stop an active agent session")
736
+ @synchronizer.create_blocking
737
+ @requires_login
738
+ @with_deploy_config
739
+ async def stop(
740
+ deploy_config=typer.Option(None, hidden=True),
741
+ agent_name: str = typer.Argument(None, help="Name of the agent e.g. 'my-agent'"),
742
+ session_id: str = typer.Option(
743
+ ...,
744
+ "--session-id",
745
+ "-s",
746
+ help="ID of the session to stop",
747
+ ),
748
+ organization: str = typer.Option(
749
+ None, "--organization", "-o", help="Organization which the agent belongs to"
750
+ ),
751
+ force: bool = typer.Option(
752
+ False,
753
+ "--force",
754
+ "-f",
755
+ help="Bypass prompt for confirmation",
756
+ ),
757
+ ):
758
+ org = organization or config.get("org")
759
+
760
+ # Load values from deployment config file (if one exists)
761
+ partial_config = deploy_config or DeployConfigParams()
762
+
763
+ # Get agent name from argument or deploy config
764
+ if not agent_name:
765
+ if partial_config and partial_config.agent_name:
766
+ agent_name = partial_config.agent_name
767
+ else:
768
+ console.error("No target agent name provided")
769
+ return typer.Exit(1)
770
+
771
+ # Confirm stop request
772
+ if not force:
773
+ console.print(
774
+ Panel(
775
+ f"Agent Name: {agent_name}\n"
776
+ f"Session ID: {session_id}",
777
+ title=f"[bold]Stop Session[/bold]",
778
+ title_align="left",
779
+ border_style="yellow",
780
+ )
781
+ )
782
+ if not await questionary.confirm(
783
+ "Are you sure you want to stop this session?"
784
+ ).ask_async():
785
+ console.print("[bold]Aborting stop request[/bold]")
786
+ return typer.Exit(1)
787
+
788
+ with console.status(
789
+ f"[dim]Stopping session [bold]'{session_id}'[/bold] for agent [bold]'{agent_name}'[/bold][/dim]",
790
+ spinner="dots",
791
+ ):
792
+ data, error = await API.agent_session_terminate(
793
+ agent_name=agent_name, session_id=session_id, org=org
794
+ )
795
+
796
+ if error:
797
+ return typer.Exit(1)
798
+
799
+ console.success(f"Session '{session_id}' stopped successfully")
@@ -5,6 +5,7 @@
5
5
  #
6
6
 
7
7
  import os
8
+ import warnings
8
9
  import zipfile
9
10
  from typing import Set
10
11
 
@@ -40,9 +41,25 @@ def check_existing_files() -> Set[str]:
40
41
 
41
42
 
42
43
  def create_init_command(app: typer.Typer):
43
- @app.command(name="init", help="Initialize project directory with template files")
44
+ @app.command(
45
+ name="init",
46
+ help="[DEPRECATED] Initialize project directory with template files",
47
+ deprecated=True,
48
+ )
44
49
  @synchronizer.create_blocking
45
50
  async def init():
51
+ warnings.warn(
52
+ "The 'pcc init' command is deprecated and will be removed in a future version. "
53
+ "Please use the Pipecat CLI instead: https://github.com/pipecat-ai/pipecat-cli",
54
+ DeprecationWarning,
55
+ stacklevel=2,
56
+ )
57
+ console.print(
58
+ "[yellow]Warning:[/yellow] The 'pcc init' command is deprecated and will be removed in a future version.\n"
59
+ "Please use the Pipecat CLI instead: "
60
+ "[link=https://github.com/pipecat-ai/pipecat-cli]https://github.com/pipecat-ai/pipecat-cli[/link]"
61
+ )
62
+
46
63
  if not await questionary.confirm(
47
64
  "This will download the latest starter project to the current directory. Continue?"
48
65
  ).ask_async():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.2.6
3
+ Version: 0.2.7
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/
@@ -45,5 +45,6 @@ src/pipecatcloud/cli/commands/secrets.py
45
45
  src/pipecatcloud/smallwebrtc/session_manager.py
46
46
  tests/__init__.py
47
47
  tests/test_agent_sessions.py
48
+ tests/test_agent_stop.py
48
49
  tests/test_docker_commands.py
49
50
  tests/test_managed_keys.py
@@ -0,0 +1,99 @@
1
+ """
2
+ Unit tests for the 'pcc agent stop' command.
3
+
4
+ Tests focus on core behaviors and edge cases, not implementation details.
5
+ """
6
+
7
+ import pytest
8
+ import typer
9
+ from unittest.mock import patch, AsyncMock, MagicMock
10
+
11
+ # Import the function under test from local source
12
+ from src.pipecatcloud.cli.commands.agent import stop
13
+
14
+ # Test constants
15
+ TEST_ORG = "test-org"
16
+ TEST_AGENT = "test-agent"
17
+ TEST_SESSION_ID = "session-123"
18
+
19
+
20
+ class TestAgentStopCommand:
21
+ """Test the 'pcc agent stop' command behaviors."""
22
+
23
+ def test_stop_respects_force_flag(self):
24
+ """Verify force flag skips confirmation when set to True."""
25
+ with patch('src.pipecatcloud.cli.commands.agent.console') as mock_console, \
26
+ patch('src.pipecatcloud.cli.commands.agent.config') as mock_config, \
27
+ patch('src.pipecatcloud.cli.commands.agent.questionary') as mock_questionary, \
28
+ patch('src.pipecatcloud.cli.commands.agent.DeployConfigParams') as mock_params:
29
+
30
+ mock_config.get.return_value = TEST_ORG
31
+ mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
32
+ # Mock questionary - should NOT be called when force=True
33
+ mock_questionary.confirm.return_value.ask_async = AsyncMock()
34
+
35
+ # Act with force=True
36
+ stop(
37
+ deploy_config=None,
38
+ agent_name=TEST_AGENT,
39
+ session_id=TEST_SESSION_ID,
40
+ organization=TEST_ORG,
41
+ force=True,
42
+ )
43
+
44
+ # Assert
45
+ # questionary.confirm should not be called when force=True
46
+ mock_questionary.confirm.assert_not_called()
47
+
48
+ def test_stop_shows_confirmation_without_force(self):
49
+ """Verify confirmation prompt is shown when force is False."""
50
+ with patch('src.pipecatcloud.cli.commands.agent.console') as mock_console, \
51
+ patch('src.pipecatcloud.cli.commands.agent.config') as mock_config, \
52
+ patch('src.pipecatcloud.cli.commands.agent.questionary') as mock_questionary, \
53
+ patch('src.pipecatcloud.cli.commands.agent.DeployConfigParams') as mock_params:
54
+
55
+ mock_config.get.return_value = TEST_ORG
56
+ mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
57
+ # User agrees to the confirmation
58
+ mock_questionary.confirm.return_value.ask_async = AsyncMock(return_value=True)
59
+
60
+ # Act with force=False
61
+ stop(
62
+ deploy_config=None,
63
+ agent_name=TEST_AGENT,
64
+ session_id=TEST_SESSION_ID,
65
+ organization=TEST_ORG,
66
+ force=False,
67
+ )
68
+
69
+ # Assert
70
+ # questionary.confirm should be called when force=False
71
+ mock_questionary.confirm.assert_called_once()
72
+
73
+ def test_stop_aborts_on_user_rejection(self):
74
+ """Verify command aborts when user rejects the confirmation."""
75
+ with patch('src.pipecatcloud.cli.commands.agent.console') as mock_console, \
76
+ patch('src.pipecatcloud.cli.commands.agent.config') as mock_config, \
77
+ patch('src.pipecatcloud.cli.commands.agent.questionary') as mock_questionary, \
78
+ patch('src.pipecatcloud.cli.commands.agent.DeployConfigParams') as mock_params:
79
+
80
+ mock_config.get.return_value = TEST_ORG
81
+ mock_params.return_value = MagicMock(agent_name=TEST_AGENT)
82
+ # User rejects the confirmation
83
+ mock_questionary.confirm.return_value.ask_async = AsyncMock(return_value=False)
84
+
85
+ # Act
86
+ result = stop(
87
+ deploy_config=None,
88
+ agent_name=TEST_AGENT,
89
+ session_id=TEST_SESSION_ID,
90
+ organization=TEST_ORG,
91
+ force=False,
92
+ )
93
+
94
+ # Assert
95
+ # Should exit with code 1 (abort)
96
+ assert isinstance(result, typer.Exit)
97
+ assert result.exit_code == 1
98
+ # Should display abort message
99
+ mock_console.print.assert_any_call("[bold]Aborting stop request[/bold]")
File without changes
File without changes
File without changes
File without changes
File without changes