coding-client 1.21.2__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.
Files changed (109) hide show
  1. coding_client/__init__.py +1 -0
  2. coding_client/api/__init__.py +19 -0
  3. coding_client/api/exception_handlers.py +158 -0
  4. coding_client/api/logging_setup.py +44 -0
  5. coding_client/api/metrics.py +40 -0
  6. coding_client/api/openapi_config.py +124 -0
  7. coding_client/api/request_logging.py +60 -0
  8. coding_client/api/validation.py +150 -0
  9. coding_client/auth/__init__.py +78 -0
  10. coding_client/auth/_default_tenant.py +132 -0
  11. coding_client/auth/_header_trust.py +98 -0
  12. coding_client/auth/_onbehalf.py +315 -0
  13. coding_client/auth/_trusted_networks.py +94 -0
  14. coding_client/auth/dependencies.py +61 -0
  15. coding_client/auth/middleware.py +445 -0
  16. coding_client/auth/models.py +83 -0
  17. coding_client/auth/outbound.py +137 -0
  18. coding_client/auth/rbac.py +21 -0
  19. coding_client/auth/rbac_coding.py +5 -0
  20. coding_client/auth/tenant.py +650 -0
  21. coding_client/auth/websocket.py +148 -0
  22. coding_client/clients/__init__.py +24 -0
  23. coding_client/clients/base.py +131 -0
  24. coding_client/clients/coding_ai.py +33 -0
  25. coding_client/clients/coding_analysis.py +36 -0
  26. coding_client/clients/coding_containers.py +45 -0
  27. coding_client/clients/coding_github.py +127 -0
  28. coding_client/clients/coding_realtime.py +127 -0
  29. coding_client/clients/coding_security.py +51 -0
  30. coding_client/clients/coding_testing.py +31 -0
  31. coding_client/clients/coding_workspaces.py +265 -0
  32. coding_client/config/__init__.py +12 -0
  33. coding_client/config/base_settings.py +61 -0
  34. coding_client/config/yaml_config_loader.py +172 -0
  35. coding_client/db/__init__.py +42 -0
  36. coding_client/db/db_types.py +122 -0
  37. coding_client/db/exceptions.py +59 -0
  38. coding_client/db/postgres_connection.py +572 -0
  39. coding_client/db/tenant_query_helper.py +417 -0
  40. coding_client/db_init.py +49 -0
  41. coding_client/exceptions/__init__.py +67 -0
  42. coding_client/exceptions/api_exceptions.py +395 -0
  43. coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
  44. coding_client/interfaces/__init__.py +22 -0
  45. coding_client/interfaces/cache_interface.py +179 -0
  46. coding_client/interfaces/container_interface.py +49 -0
  47. coding_client/interfaces/docker_interface.py +283 -0
  48. coding_client/interfaces/graph_interface.py +28 -0
  49. coding_client/interfaces/security_interface.py +72 -0
  50. coding_client/interfaces/webhook_interface.py +67 -0
  51. coding_client/llm/__init__.py +54 -0
  52. coding_client/llm/base_agent.py +195 -0
  53. coding_client/llm/context.py +121 -0
  54. coding_client/llm/responses.py +102 -0
  55. coding_client/llm/routing.py +82 -0
  56. coding_client/llm/settings.py +102 -0
  57. coding_client/models/__init__.py +62 -0
  58. coding_client/models/agent.py +38 -0
  59. coding_client/models/ai_test_failure.py +745 -0
  60. coding_client/models/analysis.py +330 -0
  61. coding_client/models/architecture.py +685 -0
  62. coding_client/models/bug.py +128 -0
  63. coding_client/models/code_graph.py +317 -0
  64. coding_client/models/container.py +241 -0
  65. coding_client/models/core.py +878 -0
  66. coding_client/models/docker_container.py +104 -0
  67. coding_client/models/github.py +293 -0
  68. coding_client/models/logging.py +29 -0
  69. coding_client/models/package_manager.py +257 -0
  70. coding_client/models/pr_validation.py +440 -0
  71. coding_client/models/pydantic_ai.py +395 -0
  72. coding_client/models/repo_scanner.py +249 -0
  73. coding_client/models/responses.py +431 -0
  74. coding_client/models/security.py +185 -0
  75. coding_client/models/static_analysis.py +508 -0
  76. coding_client/models/system.py +48 -0
  77. coding_client/models/test_executor.py +204 -0
  78. coding_client/models/testing.py +673 -0
  79. coding_client/models/testing_config.py +200 -0
  80. coding_client/models/validation.py +127 -0
  81. coding_client/models_init.py +272 -0
  82. coding_client/telemetry.py +195 -0
  83. coding_client/testing.py +180 -0
  84. coding_client/types/__init__.py +130 -0
  85. coding_client/types/constraint_types.py +276 -0
  86. coding_client/types/core_types.py +112 -0
  87. coding_client/types/state_machine.py +443 -0
  88. coding_client/utils/__init__.py +86 -0
  89. coding_client/utils/archive_utils.py +387 -0
  90. coding_client/utils/ast_debug.py +45 -0
  91. coding_client/utils/async_timeout.py +364 -0
  92. coding_client/utils/constants.py +518 -0
  93. coding_client/utils/content_processing.py +326 -0
  94. coding_client/utils/database.py +916 -0
  95. coding_client/utils/encoding_helper.py +222 -0
  96. coding_client/utils/enum_helpers.py +166 -0
  97. coding_client/utils/file_operations.py +177 -0
  98. coding_client/utils/filesystem_ops.py +528 -0
  99. coding_client/utils/http_client.py +562 -0
  100. coding_client/utils/path_utils.py +471 -0
  101. coding_client/utils/profiling.py +421 -0
  102. coding_client/utils/response_builder.py +240 -0
  103. coding_client/utils/retry_helper.py +712 -0
  104. coding_client/utils/security_validation.py +423 -0
  105. coding_client-1.21.2.dist-info/METADATA +234 -0
  106. coding_client-1.21.2.dist-info/RECORD +109 -0
  107. coding_client-1.21.2.dist-info/WHEEL +5 -0
  108. coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
  109. coding_client-1.21.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,417 @@
1
+ """
2
+ Tenant Query Helper for Database Operations.
3
+
4
+ This module provides utilities for adding tenant isolation to database queries.
5
+ It ensures that all queries properly filter by tenant_id to prevent BOLA vulnerabilities.
6
+
7
+ Usage:
8
+ from db.tenant_query_helper import (
9
+ add_tenant_filter,
10
+ build_tenant_query,
11
+ TenantQueryBuilder,
12
+ )
13
+
14
+ # Simple case - add tenant filter to existing query
15
+ query = add_tenant_filter("SELECT * FROM nodes", "tenant-123")
16
+ # Result: "SELECT * FROM nodes WHERE tenant_id = ?"
17
+
18
+ # Complex case - with existing WHERE clause
19
+ query = add_tenant_filter(
20
+ "SELECT * FROM nodes WHERE type = ?",
21
+ "tenant-123"
22
+ )
23
+ # Result: "SELECT * FROM nodes WHERE tenant_id = ? AND type = ?"
24
+
25
+ # Using the builder for more control
26
+ builder = TenantQueryBuilder("nodes", "tenant-123")
27
+ query, params = builder.select().where("type = ?", "function").build()
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import logging
33
+ import re
34
+ from typing import Any, Protocol, runtime_checkable
35
+
36
+
37
+ @runtime_checkable
38
+ class _HasTenantId(Protocol):
39
+ """Protocol for objects that provide at minimum a tenant_id attribute."""
40
+
41
+ @property
42
+ def tenant_id(self) -> str: ...
43
+
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+
48
+ def _extract_tenant_id(tenant: str) -> str:
49
+ """Extract tenant_id string from a string value.
50
+
51
+ Args:
52
+ tenant: A tenant_id string.
53
+
54
+ Returns:
55
+ The tenant_id string.
56
+
57
+ Raises:
58
+ ValueError: If tenant_id is empty or None.
59
+ """
60
+ if isinstance(tenant, str):
61
+ if not tenant:
62
+ raise ValueError("tenant_id cannot be empty")
63
+ return tenant
64
+
65
+ # For TenantContext-like objects, try to get tenant_id attribute
66
+ if hasattr(tenant, "tenant_id"):
67
+ return tenant.tenant_id
68
+
69
+ raise ValueError(f"Cannot extract tenant_id from {type(tenant)}")
70
+
71
+
72
+ # Default tenant ID for legacy data (matches migration 003)
73
+ LEGACY_TENANT_ID = "legacy"
74
+
75
+
76
+ def add_tenant_filter(
77
+ query: str,
78
+ tenant: str,
79
+ table_alias: str = "",
80
+ param_placeholder: str = "?",
81
+ ) -> tuple[str, list[Any]]:
82
+ """
83
+ Add tenant_id filter to an existing SQL query.
84
+
85
+ This function handles different query structures:
86
+ - Queries without WHERE clause: adds WHERE tenant_id = ?
87
+ - Queries with WHERE clause: adds AND tenant_id = ? after WHERE
88
+ - Queries with ORDER BY/LIMIT: inserts filter before those clauses
89
+
90
+ Args:
91
+ query: The SQL query to modify
92
+ tenant: The tenant ID string
93
+ table_alias: Optional table alias prefix (e.g., "n" for "n.tenant_id")
94
+ param_placeholder: Placeholder for parameters (default "?" for SQLite)
95
+
96
+ Returns:
97
+ Tuple of (modified_query, [tenant_id]) for use with parameterized queries
98
+
99
+ Example:
100
+ >>> query, params = add_tenant_filter("SELECT * FROM nodes", "tenant-123")
101
+ >>> cursor.execute(query, params)
102
+ """
103
+ tenant_id = _extract_tenant_id(tenant)
104
+ if not tenant_id:
105
+ logger.warning("add_tenant_filter called with empty tenant_id")
106
+ return query, []
107
+
108
+ # Build the tenant column reference
109
+ tenant_col = f"{table_alias}.tenant_id" if table_alias else "tenant_id"
110
+
111
+ # Normalize query for analysis
112
+ query_upper = query.upper()
113
+
114
+ # Find the position of WHERE if it exists
115
+ where_pos = -1
116
+ where_match = re.search(r"\bWHERE\b", query_upper)
117
+ if where_match:
118
+ where_pos = where_match.start()
119
+
120
+ # Find positions of ORDER BY, GROUP BY, LIMIT, etc.
121
+ end_markers = ["ORDER BY", "GROUP BY", "LIMIT", "OFFSET", "HAVING"]
122
+ end_pos = len(query)
123
+ for marker in end_markers:
124
+ marker_match = re.search(rf"\b{marker}\b", query_upper)
125
+ if marker_match and marker_match.start() < end_pos:
126
+ # Only consider markers after WHERE
127
+ if where_pos == -1 or marker_match.start() > where_pos:
128
+ end_pos = marker_match.start()
129
+
130
+ # Build the tenant filter
131
+ tenant_filter = f"{tenant_col} = {param_placeholder}"
132
+
133
+ if where_pos == -1:
134
+ # No WHERE clause - insert WHERE before end markers
135
+ modified_query = query[:end_pos].rstrip() + f" WHERE {tenant_filter} " + query[end_pos:]
136
+ else:
137
+ # WHERE clause exists - insert AND after WHERE
138
+ # Find the end of "WHERE " to insert after it
139
+ assert where_match is not None # guaranteed by where_pos != -1
140
+ where_end = where_match.end()
141
+ # Insert the tenant filter right after WHERE, with AND connecting to existing conditions
142
+ modified_query = query[:where_end] + f" {tenant_filter} AND " + query[where_end:].lstrip()
143
+
144
+ return modified_query.strip(), [tenant_id]
145
+
146
+
147
+ def build_tenant_query(
148
+ base_query: str,
149
+ tenant: str,
150
+ existing_params: list[Any] | None = None,
151
+ table_alias: str = "",
152
+ ) -> tuple[str, list[Any]]:
153
+ """
154
+ Build a complete query with tenant filtering and combine parameters.
155
+
156
+ Args:
157
+ base_query: The SQL query to modify
158
+ tenant: The tenant ID string
159
+ existing_params: Existing query parameters
160
+ table_alias: Optional table alias prefix
161
+
162
+ Returns:
163
+ Tuple of (modified_query, combined_params)
164
+
165
+ Example:
166
+ >>> query, params = build_tenant_query(
167
+ ... "SELECT * FROM nodes WHERE type = ?",
168
+ ... "tenant-123",
169
+ ... existing_params=["function"]
170
+ ... )
171
+ >>> # query = "SELECT * FROM nodes WHERE tenant_id = ? AND type = ?"
172
+ >>> # params = ["tenant-123", "function"]
173
+ """
174
+ if existing_params is None:
175
+ existing_params = []
176
+
177
+ modified_query, tenant_params = add_tenant_filter(base_query, tenant, table_alias)
178
+
179
+ # Tenant ID should be the first parameter for WHERE tenant_id = ? AND ...
180
+ combined_params = tenant_params + list(existing_params)
181
+
182
+ return modified_query, combined_params
183
+
184
+
185
+ class TenantQueryBuilder:
186
+ """
187
+ Fluent query builder with automatic tenant isolation.
188
+
189
+ This builder ensures all queries include proper tenant filtering
190
+ and helps construct complex queries with correct parameter ordering.
191
+
192
+ Example:
193
+ builder = TenantQueryBuilder("nodes", "tenant-123")
194
+ query, params = (
195
+ builder
196
+ .select("id", "type", "properties")
197
+ .where("type = ?", "function")
198
+ .where("json_extract(properties, '$.name') LIKE ?", "%handler%")
199
+ .order_by("created_at DESC")
200
+ .limit(50)
201
+ .offset(0)
202
+ .build()
203
+ )
204
+ """
205
+
206
+ def __init__(
207
+ self,
208
+ table_name: str,
209
+ tenant: str,
210
+ table_alias: str = "",
211
+ ):
212
+ """
213
+ Initialize the query builder.
214
+
215
+ Args:
216
+ table_name: Name of the primary table
217
+ tenant: Tenant ID string for isolation
218
+ table_alias: Optional alias for the table
219
+ """
220
+ self.table_name = table_name
221
+ self.tenant_id = _extract_tenant_id(tenant)
222
+ self.table_alias = table_alias
223
+
224
+ self._columns: list[str] = ["*"]
225
+ self._joins: list[str] = []
226
+ self._where_conditions: list[str] = []
227
+ self._where_params: list[Any] = []
228
+ self._group_by: str | None = None
229
+ self._order_by: str | None = None
230
+ self._limit: int | None = None
231
+ self._offset: int | None = None
232
+
233
+ def select(self, *columns: str) -> TenantQueryBuilder:
234
+ """Specify columns to select."""
235
+ if columns:
236
+ self._columns = list(columns)
237
+ return self
238
+
239
+ def join(self, join_clause: str) -> TenantQueryBuilder:
240
+ """Add a JOIN clause."""
241
+ self._joins.append(join_clause)
242
+ return self
243
+
244
+ def where(self, condition: str, *params: Any) -> TenantQueryBuilder:
245
+ """Add a WHERE condition with parameters."""
246
+ self._where_conditions.append(condition)
247
+ self._where_params.extend(params)
248
+ return self
249
+
250
+ def group_by(self, *columns: str) -> TenantQueryBuilder:
251
+ """Add GROUP BY clause."""
252
+ self._group_by = ", ".join(columns)
253
+ return self
254
+
255
+ def order_by(self, *columns: str) -> TenantQueryBuilder:
256
+ """Add ORDER BY clause."""
257
+ self._order_by = ", ".join(columns)
258
+ return self
259
+
260
+ def limit(self, limit: int) -> TenantQueryBuilder:
261
+ """Add LIMIT clause."""
262
+ self._limit = limit
263
+ return self
264
+
265
+ def offset(self, offset: int) -> TenantQueryBuilder:
266
+ """Add OFFSET clause."""
267
+ self._offset = offset
268
+ return self
269
+
270
+ def build(self) -> tuple[str, list[Any]]:
271
+ """
272
+ Build the complete query with tenant isolation.
273
+
274
+ Returns:
275
+ Tuple of (query_string, parameters_list)
276
+ """
277
+ # Build SELECT clause
278
+ columns_str = ", ".join(self._columns)
279
+ table_ref = f"{self.table_name} {self.table_alias}" if self.table_alias else self.table_name
280
+ query = f"SELECT {columns_str} FROM {table_ref}"
281
+
282
+ # Add JOINs
283
+ if self._joins:
284
+ query += " " + " ".join(self._joins)
285
+
286
+ # Build WHERE clause with tenant isolation first
287
+ tenant_col = f"{self.table_alias}.tenant_id" if self.table_alias else "tenant_id"
288
+
289
+ where_parts = [f"{tenant_col} = ?"]
290
+ params: list[Any] = [self.tenant_id]
291
+
292
+ for condition in self._where_conditions:
293
+ where_parts.append(condition)
294
+ params.extend(self._where_params)
295
+
296
+ query += " WHERE " + " AND ".join(where_parts)
297
+
298
+ # Add GROUP BY
299
+ if self._group_by:
300
+ query += f" GROUP BY {self._group_by}"
301
+
302
+ # Add ORDER BY
303
+ if self._order_by:
304
+ query += f" ORDER BY {self._order_by}"
305
+
306
+ # Add LIMIT
307
+ if self._limit is not None:
308
+ query += f" LIMIT {self._limit}"
309
+
310
+ # Add OFFSET
311
+ if self._offset is not None:
312
+ query += f" OFFSET {self._offset}"
313
+
314
+ return query, params
315
+
316
+ def build_count(self) -> tuple[str, list[Any]]:
317
+ """
318
+ Build a COUNT(*) query with tenant isolation.
319
+
320
+ Returns:
321
+ Tuple of (query_string, parameters_list)
322
+ """
323
+ table_ref = f"{self.table_name} {self.table_alias}" if self.table_alias else self.table_name
324
+ query = f"SELECT COUNT(*) FROM {table_ref}"
325
+
326
+ # Add JOINs
327
+ if self._joins:
328
+ query += " " + " ".join(self._joins)
329
+
330
+ # Build WHERE clause with tenant isolation
331
+ tenant_col = f"{self.table_alias}.tenant_id" if self.table_alias else "tenant_id"
332
+
333
+ where_parts = [f"{tenant_col} = ?"]
334
+ params: list[Any] = [self.tenant_id]
335
+
336
+ for condition in self._where_conditions:
337
+ where_parts.append(condition)
338
+ params.extend(self._where_params)
339
+
340
+ query += " WHERE " + " AND ".join(where_parts)
341
+
342
+ # Add GROUP BY for count queries if needed
343
+ if self._group_by:
344
+ query += f" GROUP BY {self._group_by}"
345
+
346
+ return query, params
347
+
348
+ def build_delete(self) -> tuple[str, list[Any]]:
349
+ """
350
+ Build a DELETE query with tenant isolation.
351
+
352
+ Returns:
353
+ Tuple of (query_string, parameters_list)
354
+ """
355
+ query = f"DELETE FROM {self.table_name}"
356
+
357
+ # Build WHERE clause with tenant isolation
358
+ where_parts = ["tenant_id = ?"]
359
+ params: list[Any] = [self.tenant_id]
360
+
361
+ for condition in self._where_conditions:
362
+ where_parts.append(condition)
363
+ params.extend(self._where_params)
364
+
365
+ query += " WHERE " + " AND ".join(where_parts)
366
+
367
+ return query, params
368
+
369
+
370
+ def validate_tenant_access(
371
+ resource_tenant_id: str | None,
372
+ user_tenant: str | _HasTenantId,
373
+ allow_legacy: bool | None = None,
374
+ ) -> bool:
375
+ """
376
+ Validate that a user has access to a resource based on tenant IDs.
377
+
378
+ Args:
379
+ resource_tenant_id: The tenant_id of the resource
380
+ user_tenant: The user's tenant_id string or an object with a tenant_id attribute
381
+ allow_legacy: Whether to allow access to legacy (NULL) tenant data.
382
+ Defaults to True if not specified.
383
+
384
+ Returns:
385
+ True if access is allowed, False otherwise
386
+ """
387
+ user_tenant_id: str
388
+ # Handle objects with tenant_id attribute (e.g. TenantContext)
389
+ if isinstance(user_tenant, _HasTenantId):
390
+ if allow_legacy is None:
391
+ allow_legacy_attr: Any = getattr(user_tenant, "allow_legacy_access", None)
392
+ if allow_legacy_attr is not None:
393
+ allow_legacy = bool(allow_legacy_attr)
394
+ can_access_method: Any = getattr(user_tenant, "can_access", None)
395
+ if callable(can_access_method):
396
+ result: bool = can_access_method(resource_tenant_id)
397
+ return result
398
+ user_tenant_id = user_tenant.tenant_id
399
+ else:
400
+ user_tenant_id = user_tenant
401
+
402
+ if allow_legacy is None:
403
+ allow_legacy = True
404
+
405
+ if not user_tenant_id:
406
+ return False
407
+
408
+ if resource_tenant_id is None:
409
+ # NULL tenant_id - allow if legacy access is permitted
410
+ return allow_legacy
411
+
412
+ if resource_tenant_id == LEGACY_TENANT_ID:
413
+ # Legacy tenant data - allow if legacy access is permitted
414
+ return allow_legacy
415
+
416
+ # Normal case - must match exactly
417
+ return resource_tenant_id == user_tenant_id
@@ -0,0 +1,49 @@
1
+ """
2
+ Database configuration and session management for code_graph_helper.
3
+
4
+ This module defines a global SQLAlchemy async engine for PostgreSQL.
5
+ The database URL is pulled from the DATABASE_URL environment variable.
6
+ """
7
+
8
+ import os
9
+ from collections.abc import AsyncGenerator
10
+
11
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
12
+ from sqlalchemy.orm import declarative_base
13
+
14
+ # Grab the database URL from the environment.
15
+ #
16
+ # ⛔ NO DEFAULT. This used to fall back to
17
+ # ``postgres:postgres@localhost:5432/code_graph_helper`` -- a host Postgres that no
18
+ # longer exists (George ruling 2026-10-21: no service or test defaults to a host
19
+ # :5432). Importing this module without DATABASE_URL (or TEST_DATABASE_URL) now
20
+ # refuses with a message naming the variable, instead of building an engine that
21
+ # points at nothing. (As of 2026-10-21 no consumer of the coding-client package
22
+ # imports this module; coding-repo-jobs, the only one, uses its own engine.)
23
+ DATABASE_URL = os.getenv("DATABASE_URL") or os.getenv("TEST_DATABASE_URL")
24
+
25
+ if not DATABASE_URL:
26
+ raise RuntimeError(
27
+ "DATABASE_URL is not set -- coding_client.db_init has no default database. "
28
+ "Set DATABASE_URL=postgresql+psycopg://<user>:<pw>@<host>:<port>/<db> "
29
+ "(or TEST_DATABASE_URL in tests)."
30
+ )
31
+
32
+ # Convert postgresql:// to postgresql+psycopg:// for async psycopg3 driver
33
+ if DATABASE_URL.startswith("postgresql://"):
34
+ DATABASE_URL = DATABASE_URL.replace("postgresql://", "postgresql+psycopg://", 1)
35
+
36
+ # Create the async engine
37
+ engine = create_async_engine(DATABASE_URL, future=True, echo=False)
38
+
39
+ # Factory for creating new async sessions
40
+ AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
41
+
42
+ # Base class for declarative models
43
+ Base = declarative_base()
44
+
45
+
46
+ async def get_session() -> AsyncGenerator[AsyncSession]:
47
+ """Yield a database session for dependency injection."""
48
+ async with AsyncSessionLocal() as session:
49
+ yield session
@@ -0,0 +1,67 @@
1
+ """Custom exceptions for coding-client.
2
+
3
+ Provides:
4
+ - API exceptions (HTTP-based, auto-convert to error responses)
5
+ - PydanticAI exceptions (for AI analysis operations)
6
+ """
7
+
8
+ from .api_exceptions import (
9
+ AgentConfigurationError,
10
+ AITimeoutError,
11
+ AIValidationError,
12
+ AuthenticationException,
13
+ BaseAPIException,
14
+ ConflictException,
15
+ ContainerNotFoundException,
16
+ InternalServerException,
17
+ NotFoundException,
18
+ PermissionException,
19
+ ProcessingException,
20
+ RateLimitException,
21
+ ServiceUnavailableException,
22
+ ValidationException,
23
+ )
24
+ from .pydantic_ai_exceptions import (
25
+ AnalysisError,
26
+ APIKeyError,
27
+ LLMEmptyResponseError,
28
+ ModelNotFoundError,
29
+ ModelProviderError,
30
+ PydanticAIException,
31
+ RateLimitError,
32
+ TokenLimitError,
33
+ create_error_response,
34
+ )
35
+
36
+ # Avoid importing AgentConfigurationError from pydantic_ai_exceptions
37
+ # as it conflicts with the one from api_exceptions. Import directly
38
+ # from the submodule if needed:
39
+ # from coding_client.exceptions.pydantic_ai_exceptions import AgentConfigurationError as PydanticAIAgentConfigError
40
+
41
+ __all__ = [
42
+ # API Exceptions
43
+ "BaseAPIException",
44
+ "AgentConfigurationError",
45
+ "AITimeoutError",
46
+ "AIValidationError",
47
+ "AuthenticationException",
48
+ "ConflictException",
49
+ "ContainerNotFoundException",
50
+ "InternalServerException",
51
+ "NotFoundException",
52
+ "PermissionException",
53
+ "ProcessingException",
54
+ "RateLimitException",
55
+ "ServiceUnavailableException",
56
+ "ValidationException",
57
+ # PydanticAI Exceptions
58
+ "AnalysisError",
59
+ "APIKeyError",
60
+ "LLMEmptyResponseError",
61
+ "ModelNotFoundError",
62
+ "ModelProviderError",
63
+ "PydanticAIException",
64
+ "RateLimitError",
65
+ "TokenLimitError",
66
+ "create_error_response",
67
+ ]