pipecatcloud 0.0.6__py3-none-any.whl

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.
@@ -0,0 +1,426 @@
1
+ import base64
2
+ import re
3
+
4
+ import aiohttp
5
+ import questionary
6
+ import typer
7
+ from loguru import logger
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ from pipecatcloud._utils.async_utils import synchronizer
13
+ from pipecatcloud._utils.auth_utils import requires_login
14
+ from pipecatcloud._utils.http_utils import construct_api_url
15
+ from pipecatcloud.cli import PANEL_TITLE_ERROR, PANEL_TITLE_SUCCESS
16
+
17
+ secrets_cli = typer.Typer(
18
+ name="secrets", help="Secret management.", no_args_is_help=True
19
+ )
20
+
21
+ console = Console()
22
+
23
+
24
+ def validate_secrets(secrets: dict):
25
+ valid_name_pattern = re.compile(r'^[a-zA-Z0-9_-]+$')
26
+
27
+ for key, value in secrets.items():
28
+ if not key or not value:
29
+ console.print(
30
+ "[red]Error: Secrets must be provided as key-value pairs. Please reference --help for more information.[/red]")
31
+ return typer.Exit(1)
32
+
33
+ if len(key) > 64:
34
+ console.print(
35
+ "[red]Error: Secret names must not exceed 64 characters in length.[/red]")
36
+ return typer.Exit(1)
37
+
38
+ if not valid_name_pattern.match(key):
39
+ console.print(
40
+ "[red]Error: Secret names must contain only alphanumeric characters, underscores, and hyphens.[/red]")
41
+ return typer.Exit(1)
42
+
43
+
44
+ async def _get_secret_set(name: str, org: str, token: str):
45
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}/{name}"
46
+ logger.debug(f"Requesting secret set information from {request_url}")
47
+
48
+ async with aiohttp.ClientSession() as session:
49
+ response = await session.get(request_url, headers={"Authorization": f"Bearer {token}"})
50
+ if response.status != 200:
51
+ return None
52
+ return await response.json()
53
+
54
+
55
+ async def _create_secret(name: str, secret_key: str, secret_value: str, org: str, token: str):
56
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}/{name}"
57
+ async with aiohttp.ClientSession() as session:
58
+ response = await session.put(request_url, headers={"Authorization": f"Bearer {token}"}, json={
59
+ "name": name,
60
+ "isImagePullSecret": False,
61
+ "secretKey": secret_key,
62
+ "secretValue": secret_value
63
+ })
64
+ response.raise_for_status()
65
+
66
+
67
+ async def _delete_secret_set(name: str, org: str, token: str):
68
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}/{name}"
69
+ logger.debug(f"Deleting secret set {name} from {request_url}")
70
+
71
+ async with aiohttp.ClientSession() as session:
72
+ response = await session.delete(request_url, headers={"Authorization": f"Bearer {token}"})
73
+ response.raise_for_status()
74
+
75
+
76
+ async def _delete_secret(name: str, secret_name: str, org: str, token: str):
77
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}/{name}/{secret_name}"
78
+ logger.debug(f"Deleting secret {secret_name} from {request_url}")
79
+
80
+ async with aiohttp.ClientSession() as session:
81
+ response = await session.delete(request_url, headers={"Authorization": f"Bearer {token}"})
82
+ response.raise_for_status()
83
+
84
+
85
+ async def _create_image_pull_secret(name: str, host: str, credentials: str, org: str, token: str):
86
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}/{name}"
87
+ async with aiohttp.ClientSession() as session:
88
+ response = await session.put(request_url, headers={"Authorization": f"Bearer {token}"}, json={
89
+ "isImagePullSecret": True,
90
+ "secretValue": credentials,
91
+ "host": host
92
+ })
93
+ response.raise_for_status()
94
+
95
+
96
+ @secrets_cli.command(name="set", help="Create a new secret set for active organization")
97
+ @synchronizer.create_blocking
98
+ @requires_login
99
+ async def set(
100
+ ctx: typer.Context,
101
+ name: str = typer.Argument(
102
+ None,
103
+ help="Name of the secret set to create e.g. 'my-secret-set'"
104
+ ),
105
+ secrets: list[str] = typer.Argument(
106
+ None,
107
+ help="List of secret key-value pairs e.g. 'KEY1=value1 KEY2=\"value with spaces\"'",
108
+ )
109
+ ):
110
+ # Parse secrets
111
+ if not secrets:
112
+ console.print(
113
+ "[red]Error: Secrets must be provided as key-value pairs. Please reference --help for more information.[/red]")
114
+ return typer.Exit(1)
115
+
116
+ secrets_dict = {}
117
+ for secret in secrets:
118
+ if '=' not in secret:
119
+ console.print(
120
+ "[red]Error: Secrets must be provided as key-value pairs using '=' separator. Example: KEY=value[/red]")
121
+ return typer.Exit(1)
122
+
123
+ key, value = secret.split('=', 1) # Split on first = only
124
+ key = key.strip()
125
+
126
+ # Handle quoted values while preserving quotes within the value
127
+ value = value.strip()
128
+ if value.startswith('"') and value.endswith('"'):
129
+ # Remove only the enclosing quotes
130
+ value = value[1:-1]
131
+
132
+ if not key or not value:
133
+ console.print(
134
+ "[red]Error: Both key and value must be provided for each secret.[/red]")
135
+ return typer.Exit(1)
136
+
137
+ secrets_dict[key] = value
138
+
139
+ validate_secrets(secrets_dict)
140
+
141
+ # Prompt for set name if not provided
142
+ if not name:
143
+ name = typer.prompt("Enter the name for your secret set")
144
+
145
+ # Confirm if we are sure we want to create a new secret set (if one doesn't already exist)
146
+ existing_set = None
147
+ with console.status(f"Retrieving secret set [bold]'{name}'[/bold]", spinner="dots"):
148
+ try:
149
+ existing_set = await _get_secret_set(name, ctx.obj["org"], ctx.obj["token"])
150
+ except Exception as e:
151
+ console.print(Panel(
152
+ f"[red]Unable to retrieve secret set. Operation failed with error: {e}[/red]",
153
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
154
+ title_align="left",
155
+ border_style="red",
156
+ ))
157
+ return typer.Exit(1)
158
+
159
+ # Check for overlapping secret names
160
+ if existing_set:
161
+ existing_secret_names = {secret['fieldName'] for secret in existing_set['secrets']}
162
+ overlapping_secrets = existing_secret_names.intersection(secrets_dict.keys())
163
+
164
+ if overlapping_secrets:
165
+ create = await questionary.confirm(
166
+ f"The following secrets already exist and will be overwritten: {', '.join(overlapping_secrets)}. Would you like to continue?").ask_async()
167
+ if not create:
168
+ console.print("[bold red]Secret set creation cancelled[/bold red]")
169
+ return typer.Exit(1)
170
+ else:
171
+ create = await questionary.confirm(
172
+ f"Secret set with name '{name}' does not exist. Would you like to create it?").ask_async()
173
+ if not create:
174
+ console.print("[bold red]Secret set creation cancelled[/bold red]")
175
+ return typer.Exit(1)
176
+
177
+ try:
178
+ with console.status(f"{'Modifying' if existing_set else 'Creating'} secret set [bold]'{name}'[/bold]", spinner="dots"):
179
+ for key, value in secrets_dict.items():
180
+ await _create_secret(name, key, value, ctx.obj["org"], ctx.obj["token"])
181
+ except Exception as e:
182
+ console.print(Panel(
183
+ f"[red]Unable to create secret set. Operation failed with error: {e}[/red]",
184
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
185
+ title_align="left",
186
+ border_style="red",
187
+ ))
188
+ return typer.Exit(1)
189
+
190
+ action = "created" if not existing_set else "modified"
191
+ console.print(
192
+ Panel(
193
+ f"[bold green]Secret set '{name}' {action} successfully[/bold green]",
194
+ title=f"[green]{PANEL_TITLE_SUCCESS}[/green]",
195
+ title_align="left",
196
+ border_style="green",
197
+ ))
198
+
199
+
200
+ @secrets_cli.command(name="unset", help="Delete a secret within specified secret set")
201
+ @synchronizer.create_blocking
202
+ @requires_login
203
+ async def unset(
204
+ ctx: typer.Context,
205
+ name: str = typer.Argument(
206
+ None,
207
+ help="Name of the secret set to delete a secret from e.g. 'my-secret-set'"
208
+ ),
209
+ secret_key: str = typer.Argument(
210
+ None,
211
+ help="Name of the secret to delete e.g. 'my-secret'",
212
+ )
213
+ ):
214
+ if not name or not secret_key:
215
+ console.print(
216
+ "[red]Error: Secret set name and secret name must be provided. Please reference --help for more information.[/red]")
217
+ return typer.Exit(1)
218
+
219
+ try:
220
+ with console.status(f"Deleting secret [bold]'{secret_key}'[/bold] from secret set [bold]'{name}'[/bold]", spinner="dots"):
221
+ await _delete_secret(name, secret_key, ctx.obj["org"], ctx.obj["token"])
222
+ except Exception:
223
+ console.print(
224
+ Panel(
225
+ f"[red]Unable to delete secret '{secret_key}' from secret set '{name}'. Are you sure it exists?[/red]",
226
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
227
+ title_align="left",
228
+ border_style="red",
229
+ ))
230
+ return typer.Exit(1)
231
+
232
+ console.print(
233
+ Panel(
234
+ f"[bold green]Secret '{secret_key}' deleted successfully from secret set '{name}'[/bold green]",
235
+ title=f"[green]{PANEL_TITLE_SUCCESS}[/green]",
236
+ title_align="left",
237
+ border_style="green",
238
+ ))
239
+
240
+
241
+ @secrets_cli.command(name="list", help="List secrets for active organization")
242
+ @synchronizer.create_blocking
243
+ @requires_login
244
+ async def list(ctx: typer.Context, name: str = typer.Argument(
245
+ None,
246
+ help="Name of the secret set to list secrets from e.g. 'my-secret-set'"
247
+ )):
248
+ token = ctx.obj["token"]
249
+ org = ctx.obj["org"]
250
+
251
+ if not name:
252
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}"
253
+ else:
254
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}/{name}"
255
+
256
+ logger.debug(f"Requesting secrets from {request_url}")
257
+
258
+ try:
259
+ with console.status("Retrieving secret sets for organization", spinner="dots"):
260
+ async with aiohttp.ClientSession() as session:
261
+ response = await session.get(request_url, headers={"Authorization": f"Bearer {token}"})
262
+ response.raise_for_status()
263
+ data = await response.json()
264
+
265
+ if name:
266
+ for s in data["secrets"]:
267
+ table = Table(
268
+ border_style="dim",
269
+ show_edge=True,
270
+ show_lines=False)
271
+ table.add_column(name, style="white")
272
+ table.add_row(s["fieldName"])
273
+
274
+ console.print(table)
275
+ else:
276
+ console.print(f"[bold green]Secret sets for organization {org}:[/bold green]")
277
+ table = Table(
278
+ border_style="dim",
279
+ show_edge=True,
280
+ show_lines=False)
281
+ table.add_column("Secret Set Name", style="white")
282
+ table.add_column("Type", style="white")
283
+ for s in data["sets"]:
284
+ if s["type"] == "imagePullSecret":
285
+ table.add_row(s["name"], "Image Pull Secret")
286
+ else:
287
+ table.add_row(s["name"], "Secret")
288
+
289
+ console.print(table)
290
+
291
+ except Exception:
292
+ console.print(Panel(
293
+ "[red]Unable to retrieve secret sets. Please contact support.[/red]",
294
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
295
+ title_align="left",
296
+ border_style="red",
297
+ ))
298
+
299
+
300
+ @secrets_cli.command(name="delete", help="Delete a secret set from active organization")
301
+ @synchronizer.create_blocking
302
+ @requires_login
303
+ async def delete(
304
+ ctx: typer.Context,
305
+ name: str = typer.Argument(
306
+ None,
307
+ help="Name of the secret set to delete e.g. 'my-secret-set'"
308
+ ),
309
+ ):
310
+ if not name:
311
+ console.print(
312
+ "[red]Error: Secret set name must be provided. Please reference --help for more information.[/red]")
313
+ return typer.Exit(1)
314
+
315
+ # Confirm to proceed
316
+ confirm = await questionary.confirm(
317
+ f"Are you sure you want to delete secret set '{name}'?").ask_async()
318
+ if not confirm:
319
+ console.print("[bold red]Secret deletion cancelled[/bold red]")
320
+ return typer.Exit(1)
321
+
322
+ try:
323
+ with console.status(f"Deleting secret set [bold]'{name}'[/bold]", spinner="dots"):
324
+ await _delete_secret_set(name, ctx.obj["org"], ctx.obj["token"])
325
+ except Exception:
326
+ console.print(Panel(
327
+ f"[red]Unable to delete secret set '{name}'. Are you sure it exists?[/red]",
328
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
329
+ title_align="left",
330
+ border_style="red",
331
+ ))
332
+ return typer.Exit(1)
333
+
334
+ console.print(
335
+ Panel(
336
+ f"[bold green]Secret set '{name}' deleted successfully[/bold green]",
337
+ title=f"[green]{PANEL_TITLE_SUCCESS}[/green]",
338
+ title_align="left",
339
+ border_style="green",
340
+ ))
341
+
342
+
343
+ @secrets_cli.command(name="image-pull-secret",
344
+ help="Create an image pull secret for active organization. See https://docs.pipecat.cloud/deployment/agent-images for more information.")
345
+ @synchronizer.create_blocking
346
+ @requires_login
347
+ async def image_pull_secret(
348
+ ctx: typer.Context,
349
+ name: str = typer.Argument(
350
+ None,
351
+ help="Name of the image pull secret to reference in deployment e.g. 'my-image-pull-secret'"
352
+ ),
353
+ host: str = typer.Argument(
354
+ None,
355
+ help="Host address of the image repository e.g. https://index.docker.io/v1/"
356
+ ),
357
+ credentials: str = typer.Argument(
358
+ None,
359
+ help="Credentials of the image repository e.g. 'username:password'"
360
+ ),
361
+ base64encode: bool = typer.Option(
362
+ True,
363
+ "--base64encode",
364
+ help="Encode credentials in base64 format"
365
+ )
366
+ ):
367
+ org = ctx.obj["org"]
368
+ token = ctx.obj["token"]
369
+
370
+ if not name or not host:
371
+ console.print(
372
+ "[red]Error: Name and host must be provided. Please reference --help for more information.[/red]")
373
+ return typer.Exit(1)
374
+
375
+ if not credentials:
376
+ username = await questionary.text(
377
+ f"Enter username for image repository '{host}'").ask_async()
378
+ password = await questionary.password(
379
+ f"Enter password for image repository '{host}'").ask_async()
380
+ if not username or not password:
381
+ console.print("[bold red]Image pull secret creation cancelled[/bold red]")
382
+ return typer.Exit(1)
383
+ credentials = f"{username}:{password}"
384
+
385
+ if base64encode:
386
+ credentials = base64.b64encode(credentials.encode()).decode()
387
+
388
+ # Check if secret already exists
389
+ with console.status(f"Checking if image pull secret '{name}' already exists", spinner="dots"):
390
+ request_url = f"{construct_api_url('secrets_path').format(org=org)}"
391
+ async with aiohttp.ClientSession() as session:
392
+ response = await session.get(request_url, headers={"Authorization": f"Bearer {token}"})
393
+ response.raise_for_status()
394
+ data = await response.json()
395
+ existing_secret = next(
396
+ (s for s in data["sets"] if s["name"] == name and s["type"] == "imagePullSecret"), None)
397
+ if existing_secret:
398
+ console.print(
399
+ Panel(
400
+ f"[red]Image pull secret '{name}' already exists. Please choose a different name.[/red]",
401
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
402
+ title_align="left",
403
+ border_style="red",
404
+ ))
405
+ return typer.Exit(1)
406
+
407
+ try:
408
+ with console.status(f"Creating image pull secret [bold]'{name}'[/bold]", spinner="dots"):
409
+ await _create_image_pull_secret(name, host, credentials, org, token)
410
+ except Exception as e:
411
+ console.print(
412
+ Panel(
413
+ f"[red]Unable to create image pull secret '{name}'. Operation failed with error: {e}[/red]",
414
+ title=f"[red]{PANEL_TITLE_ERROR}[/red]",
415
+ title_align="left",
416
+ border_style="red",
417
+ ))
418
+ return typer.Exit(1)
419
+
420
+ console.print(
421
+ Panel(
422
+ f"[bold green]Image pull secret '{name}' for {host} created successfully[/bold green]",
423
+ title=f"[green]{PANEL_TITLE_SUCCESS}[/green]",
424
+ title_align="left",
425
+ border_style="green",
426
+ ))
pipecatcloud/config.py ADDED
@@ -0,0 +1,158 @@
1
+ import os
2
+ import typing
3
+ from typing import Optional
4
+
5
+ from pipecatcloud import PIPECAT_CREDENTIALS_PATH, PIPECAT_DEPLOY_CONFIG_PATH
6
+ from pipecatcloud.exception import ConfigError
7
+
8
+ user_config_path: str = os.environ.get("PIPECAT_CONFIG_PATH") or os.path.expanduser(
9
+ PIPECAT_CREDENTIALS_PATH
10
+ )
11
+
12
+ deploy_config_path: str = os.environ.get("PIPECAT_DEPLOY_CONFIG_PATH") or os.path.expanduser(
13
+ PIPECAT_DEPLOY_CONFIG_PATH
14
+ )
15
+
16
+ api_host: str = os.environ.get("PIPECAT_API_HOST") or "https://api.pipecat.cloud"
17
+
18
+
19
+ def _read_user_config():
20
+ config_data = {}
21
+ if os.path.exists(user_config_path):
22
+ import toml
23
+ try:
24
+ with open(user_config_path) as f:
25
+ config_data = toml.load(f)
26
+ except Exception as exc:
27
+ config_problem = str(exc)
28
+ else:
29
+ if not all(isinstance(e, dict) for e in config_data.values()):
30
+ raise ConfigError(
31
+ "Pipecat Cloud config file is not valid TOML. Please log out and log back in.")
32
+ else:
33
+ config_problem = ""
34
+ if config_problem:
35
+ raise ConfigError(config_problem)
36
+ return config_data
37
+
38
+
39
+ # Initialize _user_config first
40
+ _user_config = _read_user_config()
41
+
42
+
43
+ def config_profiles():
44
+ """List the available profiles in the toml file."""
45
+ return _user_config.keys()
46
+
47
+
48
+ def _config_active_profile() -> typing.Optional[str]:
49
+ if not _user_config:
50
+ return None
51
+ return next(iter(_user_config.keys()))
52
+
53
+
54
+ def _write_user_config(user_config):
55
+ import toml
56
+
57
+ with open(user_config_path, "w") as f:
58
+ toml.dump(user_config, f)
59
+
60
+
61
+ def _remove_user_config():
62
+ os.remove(user_config_path)
63
+
64
+
65
+ def _store_user_config(token: str, org: str, additional_data: Optional[dict] = None):
66
+ # @TODO: Make method more robust
67
+ if not org:
68
+ raise ValueError("Account organization is required")
69
+ if not token:
70
+ raise ValueError("Token is required")
71
+ config_data = {
72
+ org: {
73
+ "token": token,
74
+ }
75
+ }
76
+ if additional_data is not None:
77
+ config_data[org].update(additional_data)
78
+
79
+ _write_user_config(config_data)
80
+
81
+
82
+ # Set _profile after _config_active_profile is defined
83
+ _profile = os.environ.get("PIPECAT_PROFILE") or _config_active_profile()
84
+
85
+
86
+ class _Setting(typing.NamedTuple):
87
+ default: typing.Any = None
88
+ transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
89
+
90
+
91
+ _SETTINGS = {
92
+ "server_url": _Setting(api_host),
93
+ "onboarding_path": _Setting("/v1/onboarding"),
94
+ "login_path": _Setting("/auth/login"),
95
+ "login_status_path": _Setting("/auth/status"),
96
+ "whoami_path": _Setting("/v1/users"),
97
+ "organization_path": _Setting("/v1/organizations"),
98
+ "services_path": _Setting("/v1/organizations/{org}/services"),
99
+ "services_logs_path": _Setting("/v1/organizations/{org}/services/{service}/logs"),
100
+ "services_deployments_path": _Setting("/v1/organizations/{org}/services/{service}/deployments"),
101
+ "start_path": _Setting("/v1/public/{service}/proxy"),
102
+ "api_keys_path": _Setting("/v1/organizations/{org}/apiKeys"),
103
+ "secrets_path": _Setting("/v1/organizations/{org}/secrets"),
104
+ "user_config_path": _Setting(user_config_path),
105
+ "token": _Setting(),
106
+ "org": _Setting(),
107
+ "default_public_key": _Setting(),
108
+ "default_public_key_name": _Setting(),
109
+ "cli_log_level": _Setting("INFO"),
110
+ }
111
+
112
+
113
+ class Config:
114
+ """Singleton that holds configuration used by PipecatCloud internally."""
115
+
116
+ def __init__(self):
117
+ pass
118
+
119
+ def get(self, key, profile=None, use_env=True):
120
+ """Looks up a configuration value.
121
+
122
+ Will check (in decreasing order of priority):
123
+ 1. Any environment variable of the form PIPECAT_FOO_BAR (when use_env is True)
124
+ 2. Settings in the user's .toml configuration file
125
+ 3. The default value of the setting
126
+ """
127
+ if profile is None:
128
+ profile = _profile
129
+
130
+ s = _SETTINGS[key]
131
+ env_var_key = "PIPECAT_" + key.upper()
132
+ if use_env and env_var_key in os.environ:
133
+ return s.transform(os.environ[env_var_key])
134
+ elif profile is not None and profile in _user_config and key in _user_config[profile]:
135
+ return s.transform(_user_config[profile][key])
136
+ elif key == "org":
137
+ return profile
138
+ else:
139
+ return s.default
140
+
141
+ def override_locally(self, key: str, value: str):
142
+ try:
143
+ self.get(key)
144
+ os.environ["PIPECAT_" + key.upper()] = value
145
+ except KeyError:
146
+ os.environ[key.upper()] = value
147
+
148
+ def __getitem__(self, key):
149
+ return self.get(key)
150
+
151
+ def __repr__(self):
152
+ return repr(self.to_dict())
153
+
154
+ def to_dict(self):
155
+ return {key: self.get(key) for key in _SETTINGS.keys()}
156
+
157
+
158
+ config = Config()
pipecatcloud/errors.py ADDED
@@ -0,0 +1,11 @@
1
+ ERROR_CODES = {
2
+ "401": "Unauthorized / token expired. Please run `pipecat auth login` to login again.",
3
+ "404": "API endpoint not found / agent deployment not found",
4
+ "PCC-1000": "Unable to start agent",
5
+ "PCC-1001": "Attempt to start agent when deployment is not in ready state",
6
+ "PCC-1002": "Attempt to start agent without public api key",
7
+ "PCC-1003": "Unknown error occured. Please check logs for more information.",
8
+ "PCC-1004": "Billing credentials not set. Please set billing credentials via the Pipecat Cloud dashboard.",
9
+ "PCC-1005": "Agent deployment with name not found",
10
+ "PCC-1006": "Not authorized / invalid API key",
11
+ }
@@ -0,0 +1,55 @@
1
+ from typing import Optional
2
+
3
+ from pipecatcloud.errors import ERROR_CODES
4
+
5
+
6
+ class Error(Exception):
7
+ """Base class for exceptions in this module."""
8
+
9
+ pass
10
+
11
+
12
+ class AuthError(Error):
13
+ """Exception raised for authentication errors."""
14
+
15
+ def __init__(
16
+ self,
17
+ message: str = "Unauthorized / token expired. Please run `pipecat auth login` to login again."):
18
+ self.message = message
19
+ super().__init__(self.message)
20
+
21
+
22
+ class InvalidError(Error):
23
+ """Raised when user does something invalid."""
24
+
25
+
26
+ class ConfigError(Error):
27
+ """Raised when user does something invalid."""
28
+
29
+
30
+ class AgentNotHealthyError(Error):
31
+ """Raised when agent is not healthy and cannot be started."""
32
+
33
+ def __init__(
34
+ self,
35
+ message: str = "Agent deployment is not in a ready state and cannot be started.",
36
+ error_code: Optional[str] = None):
37
+ self.message = f"{message} (Error code: {error_code})"
38
+ self.error_code = error_code
39
+ super().__init__(self.message)
40
+
41
+
42
+ class AgentStartError(Error):
43
+ """Raised when agent start request fails."""
44
+
45
+ def __init__(
46
+ self,
47
+ message: str = "Agent start request failed.",
48
+ error_code: Optional[str] = None):
49
+
50
+ error_message = message if not error_code else ERROR_CODES.get(
51
+ error_code, message)
52
+
53
+ self.message = f"{error_message} (Error code: {error_code})"
54
+ self.error_code = error_code
55
+ super().__init__(self.message)
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.2
2
+ Name: pipecatcloud
3
+ Version: 0.0.6
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: aiohttp~=3.10.10
6
+ Requires-Dist: fastapi~=0.115.5
7
+ Requires-Dist: loguru~=0.7.2
8
+ Requires-Dist: python-dotenv~=1.0.1
9
+ Requires-Dist: uvicorn~=0.32.0
10
+ Requires-Dist: typer~=0.13.0
11
+ Requires-Dist: synchronicity~=0.7.6
12
+ Requires-Dist: toml~=0.10.2
13
+ Requires-Dist: questionary~=2.1.0