pipecatcloud 0.1.6__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.6 → pipecatcloud-0.1.7}/CHANGELOG.md +10 -0
  2. {pipecatcloud-0.1.6/src/pipecatcloud.egg-info → pipecatcloud-0.1.7}/PKG-INFO +2 -2
  3. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/pyproject.toml +2 -2
  4. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/console_utils.py +63 -0
  5. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/api.py +8 -0
  6. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/agent.py +112 -18
  7. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/config.py +1 -0
  8. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7/src/pipecatcloud.egg-info}/PKG-INFO +2 -2
  9. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/requires.txt +1 -1
  10. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/.github/workflows/publish-pypi.yml +0 -0
  11. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/.github/workflows/publish-test.yml +0 -0
  12. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/.github/workflows/python-publish.yml +0 -0
  13. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/.gitignore +0 -0
  14. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/LICENSE +0 -0
  15. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/README.md +0 -0
  16. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/dev-requirements.txt +0 -0
  17. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/pipecat-cloud.png +0 -0
  18. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/setup.cfg +0 -0
  19. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/__init__.py +0 -0
  20. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/__main__.py +0 -0
  21. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/__version__.py +0 -0
  22. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/__init__.py +0 -0
  23. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/async_utils.py +0 -0
  24. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/auth_utils.py +0 -0
  25. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/deploy_utils.py +0 -0
  26. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/_utils/local_runner.py +0 -0
  27. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/agent.py +0 -0
  28. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/__init__.py +0 -0
  29. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/api.py +0 -0
  30. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/__init__.py +0 -0
  31. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/auth.py +0 -0
  32. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/deploy.py +0 -0
  33. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/init.py +0 -0
  34. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/organizations.py +0 -0
  35. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/run.py +0 -0
  36. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/commands/secrets.py +0 -0
  37. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/config.py +0 -0
  38. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/cli/entry_point.py +0 -0
  39. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/exception.py +0 -0
  40. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud/session.py +0 -0
  41. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/SOURCES.txt +0 -0
  42. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/dependency_links.txt +0 -0
  43. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/entry_points.txt +0 -0
  44. {pipecatcloud-0.1.6 → pipecatcloud-0.1.7}/src/pipecatcloud.egg-info/top_level.txt +0 -0
@@ -7,6 +7,16 @@ 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
+
10
20
  ## [0.1.6] - 2025-04-25
11
21
 
12
22
  # Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipecatcloud
3
- Version: 0.1.6
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.6"
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
@@ -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,7 +22,7 @@ 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
@@ -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")
@@ -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.6
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