pipecatcloud 0.1.5__tar.gz → 0.1.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 (44) hide show
  1. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/CHANGELOG.md +16 -0
  2. {pipecatcloud-0.1.5/src/pipecatcloud.egg-info → pipecatcloud-0.1.7}/PKG-INFO +2 -2
  3. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/pyproject.toml +2 -2
  4. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/console_utils.py +63 -0
  5. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/deploy_utils.py +45 -12
  6. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/api.py +10 -2
  7. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/agent.py +119 -29
  8. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/deploy.py +51 -27
  9. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/config.py +1 -0
  10. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7/src/pipecatcloud.egg-info}/PKG-INFO +2 -2
  11. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/requires.txt +1 -1
  12. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/.github/workflows/publish-pypi.yml +0 -0
  13. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/.github/workflows/publish-test.yml +0 -0
  14. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/.github/workflows/python-publish.yml +0 -0
  15. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/.gitignore +0 -0
  16. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/LICENSE +0 -0
  17. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/README.md +0 -0
  18. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/dev-requirements.txt +0 -0
  19. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/pipecat-cloud.png +0 -0
  20. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/setup.cfg +0 -0
  21. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/__init__.py +0 -0
  22. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/__main__.py +0 -0
  23. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/__version__.py +0 -0
  24. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/__init__.py +0 -0
  25. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/async_utils.py +0 -0
  26. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  27. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/local_runner.py +0 -0
  28. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/agent.py +0 -0
  29. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/__init__.py +0 -0
  30. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/api.py +0 -0
  31. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  32. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/auth.py +0 -0
  33. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/init.py +0 -0
  34. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  35. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/run.py +0 -0
  36. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  37. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/config.py +0 -0
  38. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/entry_point.py +0 -0
  39. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/exception.py +0 -0
  40. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud/session.py +0 -0
  41. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/SOURCES.txt +0 -0
  42. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  43. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  44. {pipecatcloud-0.1.5 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/top_level.txt +0 -0
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.7] - 2025-06-12
11
+
12
+ # Added
13
+
14
+ - `agent sessions` command lists session history and various statistics (avg. start times, cold starts etc.)
15
+
16
+ # Fixed
17
+
18
+ - `typer` bumped to `0.15` which was causing errors on some setups
19
+
20
+ ## [0.1.6] - 2025-04-25
21
+
22
+ # Changed
23
+
24
+ - `min-instances` and `max-instances` has been changed to reflect API terminology changes.
25
+
10
26
  ## [0.1.5] - 2025-04-09
11
27
 
12
28
  # Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.1.5
3
+ Version: 0.1.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/
@@ -13,7 +13,7 @@ Requires-Dist: fastapi~=0.115.6
13
13
  Requires-Dist: loguru~=0.7.3
14
14
  Requires-Dist: python-dotenv~=1.0.1
15
15
  Requires-Dist: uvicorn~=0.32.0
16
- Requires-Dist: typer~=0.13.0
16
+ Requires-Dist: typer~=0.15.3
17
17
  Requires-Dist: synchronicity~=0.7.6
18
18
  Requires-Dist: toml~=0.10.2
19
19
  Requires-Dist: questionary~=2.1.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pipecatcloud"
7
- version = "0.1.5"
7
+ version = "0.1.7"
8
8
  requires-python = ">=3.10"
9
9
  description = "Cloud hosting for Pipecat AI applications"
10
10
  license = { text = "BSD 2-Clause License" }
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "loguru~=0.7.3",
17
17
  "python-dotenv~=1.0.1",
18
18
  "uvicorn~=0.32.0",
19
- "typer~=0.13.0",
19
+ "typer~=0.15.3",
20
20
  "synchronicity~=0.7.6",
21
21
  "toml~=0.10.2",
22
22
  "questionary~=2.1.0"
@@ -5,6 +5,8 @@
5
5
  #
6
6
 
7
7
  from typing import Optional, Union
8
+ from datetime import datetime
9
+ import statistics
8
10
 
9
11
  from rich.console import Console
10
12
  from rich.panel import Panel
@@ -140,3 +142,64 @@ def format_timestamp(timestamp: str) -> str:
140
142
 
141
143
  # Return original if parsing fails
142
144
  return timestamp
145
+
146
+
147
+ def format_duration(created_at_str: str, ended_at_str: str) -> str | None:
148
+ """Calculate and format session duration as HH:MM:SS"""
149
+ if not ended_at_str:
150
+ return None
151
+
152
+ try:
153
+ created_at = datetime.fromisoformat(created_at_str.replace('Z', '+00:00'))
154
+ ended_at = datetime.fromisoformat(ended_at_str.replace('Z', '+00:00'))
155
+ duration = ended_at - created_at
156
+
157
+ # Convert to total seconds
158
+ total_seconds = int(duration.total_seconds())
159
+
160
+ # Calculate hours, minutes, seconds
161
+ hours = total_seconds // 3600
162
+ minutes = (total_seconds % 3600) // 60
163
+ seconds = total_seconds % 60
164
+
165
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
166
+ except Exception:
167
+ return None
168
+
169
+
170
+ def calculate_percentiles(data: list[float]) -> tuple[float, float, float] | None:
171
+ """
172
+ Calculate average, 5th percentile, and 95th percentile for a list of numeric data.
173
+
174
+ Args:
175
+ data: List of float values to analyze
176
+
177
+ Returns:
178
+ Tuple of (average, p5, p95) if data is provided, None if empty list.
179
+ For single values, p5 and p95 will equal the single value.
180
+ """
181
+ if not data:
182
+ return None
183
+
184
+ avg = statistics.mean(data)
185
+
186
+ if len(data) == 1:
187
+ return avg, data[0], data[0]
188
+
189
+ sorted_data = sorted(data)
190
+
191
+ def percentile(data_list, p):
192
+ if not data_list:
193
+ return 0
194
+ k = (len(data_list) - 1) * p / 100
195
+ f = int(k)
196
+ c = k - f
197
+ if f + 1 < len(data_list):
198
+ return data_list[f] * (1 - c) + data_list[f + 1] * c
199
+ else:
200
+ return data_list[f]
201
+
202
+ p5 = percentile(sorted_data, 5)
203
+ p95 = percentile(sorted_data, 95)
204
+
205
+ return avg, p5, p95
@@ -5,10 +5,11 @@
5
5
  #
6
6
 
7
7
  import os
8
- from typing import Optional
8
+ import functools
9
+ from typing import Optional, Callable, Any
9
10
 
10
11
  import toml
11
- from attr import dataclass
12
+ from attr import dataclass, field
12
13
  from loguru import logger
13
14
 
14
15
  from pipecatcloud.exception import ConfigFileError
@@ -22,23 +23,37 @@ DEPLOY_STATUS_MAP = {
22
23
 
23
24
  @dataclass
24
25
  class ScalingParams:
25
- min_instances: Optional[int] = 0
26
- max_instances: Optional[int] = 10
26
+ min_agents: Optional[int] = 0
27
+ max_agents: Optional[int] = None
28
+ # @deprecated
29
+ min_instances: Optional[int] = field(default=None, metadata={"deprecated": True})
30
+ # @deprecated
31
+ max_instances: Optional[int] = field(default=None, metadata={"deprecated": True})
27
32
 
28
33
  def __attrs_post_init__(self):
34
+ # Handle deprecated fields
29
35
  if self.min_instances is not None:
30
- if self.min_instances < 0:
31
- raise ValueError("min_instances must be greater than or equal to 0")
36
+ logger.warning("min_instances is deprecated, use min_agents instead")
37
+ self.min_agents = self.min_instances
32
38
 
33
39
  if self.max_instances is not None:
34
- if self.max_instances < 1:
35
- raise ValueError("max_instances must be greater than 0")
40
+ logger.warning("max_instances is deprecated, use max_agents instead")
41
+ self.max_agents = self.max_instances
36
42
 
37
- if self.min_instances is not None and self.max_instances < self.min_instances:
38
- raise ValueError("max_instances must be greater than or equal to min_instances")
43
+ # Validation
44
+ if self.min_agents is not None:
45
+ if self.min_agents < 0:
46
+ raise ValueError("min_agents must be greater than or equal to 0")
47
+
48
+ if self.max_agents is not None:
49
+ if self.max_agents < 1:
50
+ raise ValueError("max_agents must be greater than 0")
51
+
52
+ if self.min_agents is not None and self.max_agents < self.min_agents:
53
+ raise ValueError("max_agents must be greater than or equal to min_agents")
39
54
 
40
55
  def to_dict(self):
41
- return {"min_instances": self.min_instances, "max_instances": self.max_instances}
56
+ return {"min_agents": self.min_agents, "max_agents": self.max_agents}
42
57
 
43
58
 
44
59
  @dataclass
@@ -95,7 +110,8 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
95
110
  "image_credentials",
96
111
  "secret_set",
97
112
  "scaling",
98
- "enable_krisp"}
113
+ "enable_krisp",
114
+ }
99
115
  unexpected_keys = set(config_data.keys()) - expected_keys
100
116
  if unexpected_keys:
101
117
  raise ConfigFileError(f"Unexpected keys in config file: {unexpected_keys}")
@@ -105,3 +121,20 @@ def load_deploy_config_file() -> Optional[DeployConfigParams]:
105
121
  except Exception as e:
106
122
  logger.debug(e)
107
123
  raise ConfigFileError(str(e))
124
+
125
+
126
+ def with_deploy_config(func: Callable) -> Callable:
127
+ """
128
+ Decorator that loads the deploy config file and injects it into the function.
129
+ If the config file exists, it will be loaded and passed to the function as `deploy_config`.
130
+ """
131
+ @functools.wraps(func)
132
+ def wrapper(*args, **kwargs):
133
+ try:
134
+ deploy_config = load_deploy_config_file()
135
+ kwargs['deploy_config'] = deploy_config
136
+ except Exception as e:
137
+ logger.error(f"Error loading deploy config: {e}")
138
+ raise ConfigFileError(str(e))
139
+ return func(*args, **kwargs)
140
+ return wrapper
@@ -317,8 +317,8 @@ class _API:
317
317
  "imagePullSecretSet": deploy_config.image_credentials,
318
318
  "secretSet": deploy_config.secret_set,
319
319
  "autoScaling": {
320
- "minReplicas": deploy_config.scaling.min_instances,
321
- "maxReplicas": deploy_config.scaling.max_instances,
320
+ "minAgents": deploy_config.scaling.min_agents,
321
+ "maxAgents": deploy_config.scaling.max_agents,
322
322
  },
323
323
  "enableKrisp": deploy_config.enable_krisp,
324
324
  }
@@ -428,3 +428,11 @@ class _API:
428
428
  @property
429
429
  def agent_logs(self):
430
430
  return self.create_api_method(self._agent_logs)
431
+
432
+ async def _agent_sessions(self, agent_name: str, org: str) -> dict | None:
433
+ url = f"{self.construct_api_url('services_sessions_path').format(org=org, service=agent_name)}"
434
+ return await self._base_request("GET", url) or {}
435
+
436
+ @property
437
+ def agent_sessions(self):
438
+ return self.create_api_method(self._agent_sessions)
@@ -5,6 +5,7 @@
5
5
  #
6
6
 
7
7
  import json
8
+ import statistics
8
9
  from enum import Enum
9
10
 
10
11
  import aiohttp
@@ -21,11 +22,11 @@ from rich.text import Text
21
22
 
22
23
  from pipecatcloud._utils.async_utils import synchronizer
23
24
  from pipecatcloud._utils.auth_utils import requires_login
24
- from pipecatcloud._utils.console_utils import console, format_timestamp
25
+ from pipecatcloud._utils.console_utils import console, format_duration, format_timestamp, calculate_percentiles
25
26
  from pipecatcloud.cli import PIPECAT_CLI_NAME
26
27
  from pipecatcloud.cli.api import API
27
28
  from pipecatcloud.cli.config import config
28
- from pipecatcloud._utils.deploy_utils import load_deploy_config_file, DeployConfigParams
29
+ from pipecatcloud._utils.deploy_utils import with_deploy_config, DeployConfigParams
29
30
 
30
31
  agent_cli = typer.Typer(name="agent", help="Agent management", no_args_is_help=True)
31
32
 
@@ -137,11 +138,11 @@ async def status(
137
138
  if autoscaling_data:
138
139
  scaling_renderables = [
139
140
  Panel(
140
- f"[bold]Minimum Instances[/bold]\n{autoscaling_data.get('minReplicas', 0)}",
141
+ f"[bold]Minimum Agents[/bold]\n{autoscaling_data.get('minAgents', 0)}",
141
142
  expand=True,
142
143
  ),
143
144
  Panel(
144
- f"[bold]Maximum Instances[/bold]\n{autoscaling_data.get('maxReplicas', 0)}",
145
+ f"[bold]Maximum Agents[/bold]\n{autoscaling_data.get('maxAgents', 0)}",
145
146
  expand=True,
146
147
  ),
147
148
  ]
@@ -200,18 +201,31 @@ async def status(
200
201
  @agent_cli.command(name="sessions", help="List active sessions for an agent")
201
202
  @synchronizer.create_blocking
202
203
  @requires_login
204
+ @with_deploy_config
203
205
  async def sessions(
204
- agent_name: str,
206
+ deploy_config=typer.Option(None, hidden=True),
207
+ agent_name: str = typer.Argument(
208
+ None,
209
+ help="Name of the agent to list sessions for e.g. 'my-agent'",
210
+ show_default=False
211
+ ),
212
+ session_id: str = typer.Option(
213
+ None,
214
+ "--id",
215
+ "-i",
216
+ help="Session ID to filter by",
217
+ ),
205
218
  organization: str = typer.Option(
206
219
  None, "--organization", "-o", help="Organization to list sessions for"
207
220
  ),
208
221
  ):
209
222
  org = organization or config.get("org")
223
+ agent_name = agent_name or deploy_config.agent_name
210
224
 
211
225
  with Live(
212
226
  console.status(f"[dim]Looking up agent with name '{agent_name}'[/dim]", spinner="dots")
213
227
  ) as live:
214
- data, error = await API.agent(agent_name=agent_name, org=org, live=live)
228
+ data, error = await API.agent_sessions(agent_name=agent_name, org=org, live=live)
215
229
 
216
230
  live.stop()
217
231
 
@@ -219,24 +233,104 @@ async def sessions(
219
233
  return typer.Exit()
220
234
 
221
235
  if not data:
222
- console.error(f"No deployment data found for agent with name '{agent_name}'")
236
+ console.error(f"No session data found for agent with name '{agent_name}'")
223
237
  return typer.Exit()
224
238
 
225
- console.print(
226
- "[yellow]Please note: this method is currently work in progress and will be updated in the future with more information[/yellow]"
227
- )
239
+ sessions_list = data.get("sessions", [])
240
+ total_sessions = len(sessions_list)
228
241
 
229
- if data.get("activeSessionCount", 0) > 0:
230
- console.success(
231
- f"{data.get('activeSessionCount', 0)}",
232
- title=f"Active session count for agent {agent_name} [dim]({org})[/dim]",
233
- )
234
- else:
235
- console.error(
236
- f"No active sessions found for agent {agent_name}",
237
- title=f"Active session count for agent {agent_name} [dim]({org})[/dim]",
238
- subtitle=f"[white dim]Start a new session with[/white dim] [bold cyan]{PIPECAT_CLI_NAME} agent start {agent_name}[/bold cyan]",
239
- )
242
+ completed_sessions = [s for s in sessions_list if s.get("endedAt")]
243
+
244
+ durations = []
245
+ for session in completed_sessions:
246
+ try:
247
+ from datetime import datetime
248
+ created_at = datetime.fromisoformat(session["createdAt"].replace('Z', '+00:00'))
249
+ ended_at = datetime.fromisoformat(session["endedAt"].replace('Z', '+00:00'))
250
+ duration_seconds = (ended_at - created_at).total_seconds()
251
+ durations.append(duration_seconds)
252
+ except BaseException:
253
+ continue
254
+
255
+ bot_start_times = [s["botStartSeconds"]
256
+ for s in sessions_list if s.get("botStartSeconds") is not None]
257
+ bot_start_metrics = calculate_percentiles(bot_start_times)
258
+ duration_metrics = calculate_percentiles(durations)
259
+ cold_starts_count = sum(1 for s in sessions_list if s.get("coldStart") is True)
260
+ cold_start_percent = cold_starts_count / total_sessions * 100
261
+
262
+ if duration_metrics and bot_start_metrics and total_sessions > 0:
263
+ metric_renderables = [
264
+ Panel(
265
+ f"[bold]Total Sessions:[/bold]\n{total_sessions}\n ",
266
+ expand=True,
267
+ ),
268
+ Panel(
269
+ f"[bold]Average Duration:[/bold]\n{duration_metrics[0]:.1f}s\n[dim](p5: {duration_metrics[1]:.1f}s, p95: {duration_metrics[2]:.1f}s)[/dim]",
270
+ expand=True,
271
+ ),
272
+ Panel(
273
+ f"[bold]Bot Start Time:[/bold]\n{bot_start_metrics[0]:.1f}s\n[dim](p5: {bot_start_metrics[1]:.1f}s, p95: {bot_start_metrics[2]:.1f}s)[/dim]",
274
+ expand=True,
275
+ ),
276
+ Panel(
277
+ f"[bold]Cold Starts:[/bold]\n{cold_starts_count}/{total_sessions}\n[dim]({cold_start_percent:.1f}%)[/dim]",
278
+ expand=True,
279
+ ),
280
+ ]
281
+
282
+ table = Table(show_header=True, show_lines=True, border_style="dim", box=box.SIMPLE)
283
+ table.add_column("Session ID")
284
+ table.add_column("Created At")
285
+ table.add_column("Ended At")
286
+ table.add_column("Duration")
287
+ table.add_column("Status")
288
+ table.add_column("Bot Start Time")
289
+ table.add_column("Cold Start")
290
+
291
+ for session in data.get("sessions", []):
292
+ if session_id and session["sessionId"] != session_id:
293
+ continue
294
+
295
+ session_duration = format_duration(
296
+ session["createdAt"], session["endedAt"]) or "[dim]N/A[/dim]"
297
+ status = session.get("completionStatus", "")
298
+ if session["endedAt"]:
299
+ if status == "500":
300
+ status_display = "[red]Error (500)[/red]"
301
+ else:
302
+ status_display = "Complete"
303
+ else:
304
+ status_display = "[yellow]Active[/yellow]"
305
+
306
+ is_cold_start = session["coldStart"] is True
307
+ row_style = "on red" if is_cold_start else ""
308
+
309
+ row_data = [
310
+ session["sessionId"],
311
+ format_timestamp(
312
+ session["createdAt"]),
313
+ format_timestamp(
314
+ session["endedAt"]) if session["endedAt"] else "[dim]N/A[/dim]",
315
+ session_duration,
316
+ status_display,
317
+ f"{session['botStartSeconds']}s" if session["botStartSeconds"] is not None else "[dim]N/A[/dim]",
318
+ "[red]Yes[/red]" if session["coldStart"] is True else "No" if session["coldStart"] is False else "[dim]N/A[/dim]",
319
+ ]
320
+
321
+ if is_cold_start:
322
+ row_data = [f"[{row_style}]{cell}[/]" for cell in row_data]
323
+
324
+ table.add_row(*row_data)
325
+
326
+ console.success(
327
+ Group(
328
+ Columns(
329
+ metric_renderables,
330
+ equal=True) if metric_renderables and not session_id else "",
331
+ table),
332
+ title=f"Session data for agent {agent_name} [dim]({org})[/dim]",
333
+ )
240
334
 
241
335
 
242
336
  @agent_cli.command(name="scale", help="Modify agent runtime configuration")
@@ -443,7 +537,9 @@ async def deployments(
443
537
  @agent_cli.command(name="start", help="Start an agent instance")
444
538
  @synchronizer.create_blocking
445
539
  @requires_login
540
+ @with_deploy_config
446
541
  async def start(
542
+ deploy_config=typer.Option(None, hidden=True),
447
543
  agent_name: str = typer.Argument(None, help="Name of the agent to start e.g. 'my-agent'"),
448
544
  force: bool = typer.Option(
449
545
  False,
@@ -495,20 +591,14 @@ async def start(
495
591
  )
496
592
 
497
593
  # Load values from deployment config file (if one exists)
498
- partial_config = DeployConfigParams()
499
- try:
500
- if deploy_config := load_deploy_config_file():
501
- partial_config = deploy_config
502
- except Exception as e:
503
- console.error(str(e))
504
- return typer.Exit()
594
+ partial_config = deploy_config or DeployConfigParams()
505
595
 
506
596
  # Get agent name from pcc-deploy.toml if not provided
507
597
  if not agent_name:
508
598
  default_agent_name = partial_config.agent_name
509
599
 
510
600
  if not default_agent_name:
511
- console.error("No agent name provided and no default agent name found in pcc-deploy.toml")
601
+ console.error("No target agent name provided")
512
602
  return typer.Exit(1)
513
603
 
514
604
  agent_name = default_agent_name
@@ -20,7 +20,7 @@ from pipecatcloud._utils.console_utils import console
20
20
  from pipecatcloud._utils.deploy_utils import (
21
21
  DeployConfigParams,
22
22
  ScalingParams,
23
- load_deploy_config_file,
23
+ with_deploy_config,
24
24
  )
25
25
  from pipecatcloud.cli import PIPECAT_CLI_NAME
26
26
  from pipecatcloud.cli.api import API
@@ -223,26 +223,28 @@ def create_deploy_command(app: typer.Typer):
223
223
  @app.command(name="deploy", help="Deploy agent to Pipecat Cloud")
224
224
  @synchronizer.create_blocking
225
225
  @requires_login
226
+ @with_deploy_config
226
227
  async def deploy(
228
+ deploy_config=typer.Option(None, hidden=True),
227
229
  agent_name: str = typer.Argument(
228
230
  None, help="Name of the agent to deploy e.g. 'my-agent'", show_default=False
229
231
  ),
230
232
  image: str = typer.Argument(
231
233
  None, help="Docker image location e.g. 'my-image:latest'", show_default=False
232
234
  ),
233
- min_instances: int = typer.Option(
235
+ min_agents: int = typer.Option(
234
236
  None,
235
- "--min-instances",
237
+ "--min-agents",
236
238
  "-min",
237
- help="Minimum number of instances to keep warm",
239
+ help="Minimum number of agents to keep warm",
238
240
  rich_help_panel="Deployment Configuration",
239
241
  min=0,
240
242
  ),
241
- max_instances: int = typer.Option(
243
+ max_agents: int = typer.Option(
242
244
  None,
243
- "--max-instances",
245
+ "--max-agents",
244
246
  "-max",
245
- help="Maximum number of allowed instances",
247
+ help="Maximum number of allowed agents",
246
248
  rich_help_panel="Deployment Configuration",
247
249
  min=1,
248
250
  max=50,
@@ -281,7 +283,34 @@ def create_deploy_command(app: typer.Typer):
281
283
  "-f",
282
284
  help="Force deployment / skip confirmation",
283
285
  ),
286
+ # @deprecated
287
+ min_instances: int = typer.Option(
288
+ None,
289
+ "--min-instances",
290
+ help="[Deprecated] Use --min-agents instead",
291
+ hidden=True,
292
+ min=0,
293
+ ),
294
+ # @deprecated
295
+ max_instances: int = typer.Option(
296
+ None,
297
+ "--max-instances",
298
+ help="[Deprecated] Use --max-agents instead",
299
+ hidden=True,
300
+ min=1,
301
+ max=50,
302
+ ),
284
303
  ):
304
+
305
+ # Handle @deprecated options
306
+ if min_instances is not None:
307
+ logger.warning("min_instances is deprecated, use min_agents instead")
308
+ min_agents = min_instances
309
+
310
+ if max_instances is not None:
311
+ logger.warning("max_instances is deprecated, use max_agents instead")
312
+ max_agents = max_instances
313
+
285
314
  org = organization or config.get("org")
286
315
 
287
316
  # Compose deployment config from CLI options and config file (if provided)
@@ -290,15 +319,7 @@ def create_deploy_command(app: typer.Typer):
290
319
  # 2. Values from the config toml file
291
320
  # 3. CLI command defaults
292
321
 
293
- partial_config = DeployConfigParams()
294
-
295
- # Load values from deployment config file (if one exists)
296
- try:
297
- if deploy_config := load_deploy_config_file():
298
- partial_config = deploy_config
299
- except Exception as e:
300
- console.error(str(e))
301
- return typer.Exit()
322
+ partial_config = deploy_config or DeployConfigParams()
302
323
 
303
324
  # Override any local config values from passed CLI arguments
304
325
  partial_config.agent_name = agent_name or partial_config.agent_name
@@ -306,12 +327,12 @@ def create_deploy_command(app: typer.Typer):
306
327
  partial_config.image_credentials = credentials or partial_config.image_credentials
307
328
  partial_config.secret_set = secret_set or partial_config.secret_set
308
329
  partial_config.scaling = ScalingParams(
309
- min_instances=min_instances
310
- if min_instances is not None
311
- else partial_config.scaling.min_instances,
312
- max_instances=max_instances
313
- if max_instances is not None
314
- else partial_config.scaling.max_instances,
330
+ min_agents=min_agents
331
+ if min_agents is not None
332
+ else partial_config.scaling.min_agents,
333
+ max_agents=max_agents
334
+ if max_agents is not None
335
+ else partial_config.scaling.max_agents,
315
336
  )
316
337
  partial_config.enable_krisp = krisp or partial_config.enable_krisp
317
338
 
@@ -328,8 +349,11 @@ def create_deploy_command(app: typer.Typer):
328
349
  table = Table(show_header=False, border_style="dim", show_edge=True, show_lines=True)
329
350
  table.add_column("Property", style="cyan")
330
351
  table.add_column("Value", style="green")
331
- table.add_row("Min instances", str(partial_config.scaling.min_instances))
332
- table.add_row("Max instances", str(partial_config.scaling.max_instances))
352
+ table.add_row("Min agents", str(partial_config.scaling.min_agents))
353
+ if partial_config.scaling.max_agents:
354
+ table.add_row("Max agents", str(partial_config.scaling.max_agents))
355
+ else:
356
+ table.add_row("Max agents", "[dim]Use existing or default[/dim]")
333
357
 
334
358
  content = Group(
335
359
  (f"[bold white]Agent name:[/bold white] [green]{partial_config.agent_name}[/green]"),
@@ -344,11 +368,11 @@ def create_deploy_command(app: typer.Typer):
344
368
  (
345
369
  [
346
370
  Text(
347
- f"Note: Usage costs will apply for {partial_config.scaling.min_instances} reserved instance(s). Please see: https://www.daily.co/pricing/pipecat-cloud/",
371
+ f"Note: Usage costs will apply for {partial_config.scaling.min_agents} reserved agent(s). Please see: https://www.daily.co/pricing/pipecat-cloud/",
348
372
  style="red",
349
- )] if partial_config.scaling.min_instances else [
373
+ )] if partial_config.scaling.min_agents else [
350
374
  Text(
351
- "Note: Deploying with 0 minimum instances may result in cold starts",
375
+ "Note: Deploying with 0 minimum agents may result in cold starts",
352
376
  style="red",
353
377
  )]),
354
378
  )
@@ -30,6 +30,7 @@ _SETTINGS = {
30
30
  "services_path": _Setting("/v1/organizations/{org}/services"),
31
31
  "services_logs_path": _Setting("/v1/organizations/{org}/services/{service}/logs"),
32
32
  "services_deployments_path": _Setting("/v1/organizations/{org}/services/{service}/deployments"),
33
+ "services_sessions_path": _Setting("/v1/organizations/{org}/services/{service}/sessions"),
33
34
  "start_path": _Setting("/v1/public/{service}/start"),
34
35
  "api_keys_path": _Setting("/v1/organizations/{org}/apiKeys"),
35
36
  "secrets_path": _Setting("/v1/organizations/{org}/secrets"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.1.5
3
+ Version: 0.1.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/
@@ -13,7 +13,7 @@ Requires-Dist: fastapi~=0.115.6
13
13
  Requires-Dist: loguru~=0.7.3
14
14
  Requires-Dist: python-dotenv~=1.0.1
15
15
  Requires-Dist: uvicorn~=0.32.0
16
- Requires-Dist: typer~=0.13.0
16
+ Requires-Dist: typer~=0.15.3
17
17
  Requires-Dist: synchronicity~=0.7.6
18
18
  Requires-Dist: toml~=0.10.2
19
19
  Requires-Dist: questionary~=2.1.0
@@ -3,7 +3,7 @@ fastapi~=0.115.6
3
3
  loguru~=0.7.3
4
4
  python-dotenv~=1.0.1
5
5
  uvicorn~=0.32.0
6
- typer~=0.13.0
6
+ typer~=0.15.3
7
7
  synchronicity~=0.7.6
8
8
  toml~=0.10.2
9
9
  questionary~=2.1.0
File without changes
File without changes
File without changes
File without changes