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,128 @@
1
+ """Bug Hunter data models for error analysis and code remediation.
2
+
3
+ This module provides data structures for the Bug Hunter system, which analyzes
4
+ error logs, identifies bugs in code, and generates remediation suggestions. The models
5
+ support the full workflow from parsing errors to generating validated code modifications.
6
+
7
+ The Bug Hunter system uses these models to:
8
+ - Parse and categorize errors from various sources (logs, static analysis, runtime)
9
+ - Generate targeted code modifications to address identified issues
10
+ - Validate proposed changes for safety and correctness
11
+ - Provide comprehensive analysis results with confidence scoring
12
+
13
+ Classes:
14
+ ErrorType: Enumeration of detectable error types across languages
15
+ ParsedError: Structured representation of parsed errors with context
16
+ CodeFix: Proposed code modification with explanation
17
+ BugAnalysisResult: Complete analysis including errors and remediation
18
+ FixValidationResult: Safety validation for proposed modifications
19
+
20
+ Example:
21
+ ```python
22
+ from bug_models import ErrorType, ParsedError, CodeFix, BugAnalysisResult
23
+
24
+ # Parse an error
25
+ error = ParsedError(
26
+ error_type=ErrorType.IMPORT_ERROR,
27
+ message="No module named 'requests'",
28
+ file_path="app/main.py",
29
+ line_number=5,
30
+ function_name="fetch_data",
31
+ stack_trace=[],
32
+ confidence=0.95
33
+ )
34
+
35
+ # Generate a remediation
36
+ fix = CodeFix(
37
+ file_path="requirements.txt",
38
+ line_number=None,
39
+ original_code="",
40
+ fixed_code="requests>=2.28.0\\n",
41
+ explanation="Add missing 'requests' dependency",
42
+ confidence=0.9,
43
+ test_suggestion="Run 'pip install -r requirements.txt' to verify"
44
+ )
45
+
46
+ # Complete analysis result
47
+ result = BugAnalysisResult(
48
+ errors=[error],
49
+ resolves=[fix],
50
+ root_cause_analysis="Missing dependency declaration",
51
+ fix_summary="Add requests to requirements.txt",
52
+ estimated_effort="Low - single dependency addition",
53
+ risk_assessment="Minimal risk - standard library addition"
54
+ )
55
+ ```
56
+ """
57
+
58
+ from __future__ import annotations
59
+
60
+ from enum import Enum
61
+ from typing import Any
62
+
63
+ from pydantic.dataclasses import dataclass
64
+
65
+
66
+ class ErrorType(Enum):
67
+ """Enumeration of error types that can be detected and resolved."""
68
+
69
+ SYNTAX_ERROR = "syntax_error"
70
+ IMPORT_ERROR = "import_error"
71
+ NAME_ERROR = "name_error"
72
+ TYPE_ERROR = "type_error"
73
+ ATTRIBUTE_ERROR = "attribute_error"
74
+ INDEX_ERROR = "index_error"
75
+ KEY_ERROR = "key_error"
76
+ VALUE_ERROR = "value_error"
77
+ RUNTIME_ERROR = "runtime_error"
78
+ LOGIC_ERROR = "logic_error"
79
+ PERFORMANCE_ERROR = "performance_error"
80
+
81
+
82
+ @dataclass
83
+ class ParsedError:
84
+ """Structured representation of a parsed error from logs or code analysis."""
85
+
86
+ error_type: ErrorType
87
+ message: str
88
+ file_path: str | None
89
+ line_number: int | None
90
+ function_name: str | None
91
+ stack_trace: list[dict[str, Any]]
92
+ confidence: float
93
+
94
+
95
+ @dataclass
96
+ class CodeFix:
97
+ """Represents a proposed resolve for a code issue."""
98
+
99
+ file_path: str
100
+ line_number: int | None
101
+ original_code: str
102
+ fixed_code: str
103
+ explanation: str
104
+ confidence: float
105
+ test_suggestion: str | None = None
106
+
107
+
108
+ @dataclass
109
+ class BugAnalysisResult:
110
+ """Complete result of bug analysis including errors, resolves, and assessments."""
111
+
112
+ errors: list[ParsedError]
113
+ resolves: list[CodeFix]
114
+ root_cause_analysis: str
115
+ fix_summary: str
116
+ estimated_effort: str
117
+ risk_assessment: str
118
+
119
+
120
+ @dataclass
121
+ class BugFixValidationResult:
122
+ """Result of validating a proposed resolve for safety and correctness."""
123
+
124
+ is_safe: bool
125
+ confidence: float
126
+ warnings: list[str]
127
+ suggestions: list[str]
128
+ risk_level: str
@@ -0,0 +1,317 @@
1
+ """Pydantic models for code graph representations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
8
+
9
+ # Request Models
10
+
11
+
12
+ class CodebaseInitRequest(BaseModel):
13
+ """Request model for initializing a new Code Graph for a codebase."""
14
+
15
+ container_name: str = Field(
16
+ ...,
17
+ min_length=1,
18
+ description="Container name from which to initialize the graph",
19
+ pattern=r"^[a-zA-Z][a-zA-Z0-9\-_]{0,62}$",
20
+ json_schema_extra={"example": "codegraph-my-project"},
21
+ )
22
+ codebase_path: str | None = Field(
23
+ None,
24
+ min_length=1,
25
+ description="Path to the codebase directory (internal to the container). If not provided, will be automatically resolved from the container configuration.",
26
+ pattern=r"^/[a-zA-Z0-9/_\-\.]+$",
27
+ json_schema_extra={"example": "/app/repositories/my-project"},
28
+ )
29
+ include_tests: bool | None = Field(
30
+ None,
31
+ description="Whether to include test files in the graph analysis. If not specified, uses the default system setting.",
32
+ )
33
+ exclude_patterns: list[str] | None = Field(
34
+ None,
35
+ description="Additional patterns to exclude from analysis (e.g., ['**/legacy/**', 'temp_*']). These are added to the default ignore patterns.",
36
+ json_schema_extra={"example": ["**/node_modules/**", "**/dist/**"]},
37
+ )
38
+ include_patterns: list[str] | None = Field(
39
+ None,
40
+ description="Specific patterns to include even if they would normally be excluded (e.g., ['important_test_*.py']). These override exclude patterns.",
41
+ json_schema_extra={"example": ["**/important_*.py"]},
42
+ )
43
+ force_rebuild: bool | None = Field(
44
+ False,
45
+ description="Force rebuild of the graph even if it already exists. This will delete the existing database and create a new one.",
46
+ )
47
+
48
+ @field_validator("codebase_path")
49
+ @classmethod
50
+ def validate_codebase_path(cls, v: str | None) -> str | None:
51
+ """Validate codebase path is safe and not a system directory."""
52
+ if v is None:
53
+ return v
54
+
55
+ if not v or not v.strip():
56
+ raise ValueError("Codebase path cannot be empty")
57
+
58
+ v = v.strip()
59
+
60
+ # Prevent dangerous paths
61
+ dangerous_paths = [
62
+ "/",
63
+ "/etc",
64
+ "/usr",
65
+ "/bin",
66
+ "/sbin",
67
+ "/var",
68
+ "/sys",
69
+ "/proc",
70
+ "/dev",
71
+ "/boot",
72
+ "/root",
73
+ ]
74
+ if v in dangerous_paths or any(v.startswith(f"{p}/") for p in dangerous_paths[:5]):
75
+ raise ValueError(f"Invalid codebase path: {v}. Cannot analyze system directories.")
76
+
77
+ # Ensure path doesn't contain suspicious patterns
78
+ if ".." in v or "~" in v:
79
+ raise ValueError("Codebase path cannot contain '..' or '~'")
80
+
81
+ return v
82
+
83
+
84
+ class CodeGraphAnalysisRequest(BaseModel):
85
+ """Request model for triggering analysis on a Code Graph."""
86
+
87
+ analysis_depth: str = Field(
88
+ "full",
89
+ description="Depth of analysis to perform",
90
+ examples=["basic_structure", "python_ast", "full"],
91
+ )
92
+
93
+ @field_validator("analysis_depth")
94
+ @classmethod
95
+ def validate_analysis_depth(cls, v: str) -> str:
96
+ """Validate analysis depth value."""
97
+ valid_depths = ["basic_structure", "python_ast", "full"]
98
+ if v not in valid_depths:
99
+ raise ValueError(f"analysis_depth must be one of: {valid_depths}")
100
+ return v
101
+
102
+
103
+ class PatternSearchRequest(BaseModel):
104
+ """Request model for searching code patterns."""
105
+
106
+ pattern: str = Field(..., description="Regular expression or AST pattern to search for")
107
+ language: str | None = Field(None, description="Programming language for AST-based search")
108
+ file_paths: list[str] | None = Field(
109
+ None,
110
+ description="Specific files to search (if not provided, searches all files)",
111
+ )
112
+
113
+
114
+ class EntityQueryRequest(BaseModel):
115
+ """Request model for querying entities in the Code Graph."""
116
+
117
+ entity_type: str = Field(..., description="Type of entity to query (e.g., 'function', 'class')")
118
+ name_pattern: str | None = Field(None, description="Optional pattern to match entity names")
119
+ limit: int = Field(100, description="Maximum number of results to return")
120
+ offset: int = Field(0, description="Offset for pagination")
121
+
122
+
123
+ class RelatedNodesRequest(BaseModel):
124
+ """Request model for retrieving nodes related to a specific node."""
125
+
126
+ node_id: str = Field(..., description="ID of the node to find related nodes for")
127
+ relationship_type: str | None = Field(None, description="Optional filter by relationship type")
128
+ direction: str = Field("all", description="'outgoing', 'incoming', or 'all'")
129
+
130
+
131
+ class RawQueryRequest(BaseModel):
132
+ """Request model for executing a raw SQL query against the Code Graph."""
133
+
134
+ sql_query: str = Field(..., description="SQL query to execute (use with caution)")
135
+
136
+
137
+ class NodeQuery(BaseModel):
138
+ """Model for querying nodes in the knowledge graph."""
139
+
140
+ type: str | None = Field(None, description="Filter by node type")
141
+ properties: dict[str, Any] | None = Field(None, description="Filter by node properties")
142
+ text_search: str | None = Field(None, description="Full-text search in properties")
143
+ with_centrality: bool | None = Field(False, description="Include centrality metrics")
144
+ with_incoming: bool | None = Field(False, description="Include incoming edges count")
145
+ with_outgoing: bool | None = Field(False, description="Include outgoing edges count")
146
+ sort_by: str | None = Field(None, description="Property to sort by")
147
+ sort_direction: str | None = Field("desc", description="Sort direction")
148
+ limit: int | None = Field(100, description="Limit for pagination", gt=0, le=10000)
149
+ offset: int | None = Field(0, description="Offset for pagination", ge=0)
150
+
151
+ @field_validator("sort_direction")
152
+ @classmethod
153
+ def validate_sort_direction(cls, v: str | None) -> str:
154
+ """Validate sort direction."""
155
+ if v and v.lower() not in ["asc", "desc"]:
156
+ raise ValueError("sort_direction must be 'asc' or 'desc'")
157
+ return v.lower() if v else "desc"
158
+
159
+ @field_validator("type")
160
+ @classmethod
161
+ def validate_type(cls, v: str | None) -> str | None:
162
+ """Validate node type is not empty string."""
163
+ if v is not None and not v.strip():
164
+ raise ValueError("Node type cannot be empty string")
165
+ return v.strip() if v else None
166
+
167
+
168
+ # Response Models
169
+
170
+
171
+ class NodeResponse(BaseModel):
172
+ """Response model for a node in the Code Graph."""
173
+
174
+ id: str
175
+ type: str
176
+ properties: dict[str, Any]
177
+ centrality: dict[str, float] | None = None # Centrality metrics (e.g., pagerank, degree)
178
+ incoming_edges: int | None = None # Count of incoming edges
179
+ outgoing_edges: int | None = None # Count of outgoing edges
180
+
181
+ model_config = ConfigDict(
182
+ json_schema_extra={
183
+ "example": {
184
+ "id": "node-123",
185
+ "type": "function",
186
+ "properties": {"name": "process_data", "complexity": 5},
187
+ "centrality": {"pagerank": 0.23, "degree": 0.15},
188
+ }
189
+ }
190
+ )
191
+
192
+
193
+ class EdgeResponse(BaseModel):
194
+ """Response model for an edge in the Code Graph."""
195
+
196
+ source: str
197
+ target: str
198
+ relationship: str
199
+ properties: dict[str, Any]
200
+ direction: str | None = None
201
+
202
+
203
+ class InitResponse(BaseModel):
204
+ """Response model for Code Graph initialization."""
205
+
206
+ message: str
207
+ container_name: str
208
+ graph_db_path: str
209
+ nodes_created: int
210
+ edges_created: int
211
+ status: str
212
+
213
+
214
+ class CodeGraphAnalysisResponse(BaseModel):
215
+ """Response model for Code Graph analysis triggering."""
216
+
217
+ code_graph_id: str
218
+ status: str
219
+ analysis_depth: str
220
+
221
+
222
+ class PatternSearchResponse(BaseModel):
223
+ """Response model for pattern search results."""
224
+
225
+ matches: list[dict[str, Any]]
226
+ count: int
227
+ search_time_ms: float | None = None
228
+ code_graph_id: str | None = None
229
+ error: str | None = None
230
+
231
+
232
+ class EntityQueryResponse(BaseModel):
233
+ """Response model for entity query results."""
234
+
235
+ entities: list[NodeResponse]
236
+ total: int
237
+ limit: int
238
+ offset: int
239
+
240
+
241
+ class RelatedNodesResponse(BaseModel):
242
+ """Response model for related nodes query."""
243
+
244
+ nodes: list[NodeResponse]
245
+ edges: list[EdgeResponse]
246
+
247
+
248
+ class RawQueryResponse(BaseModel):
249
+ """Response model for raw SQL query results."""
250
+
251
+ results: list[dict[str, Any]]
252
+ row_count: int
253
+
254
+
255
+ # Architecture Extension Models
256
+
257
+
258
+ class ArchitectureNodeResponse(NodeResponse):
259
+ """Extended node response with architecture data."""
260
+
261
+ architecture_data: dict[str, Any] | None = None
262
+ hotspot_score: float | None = None
263
+ quality_issues: list[dict[str, Any]] | None = None
264
+ dependencies: list[str] | None = None
265
+ component_id: str | None = None
266
+ layer: str | None = None
267
+
268
+
269
+ class RuntimeEdgeResponse(EdgeResponse):
270
+ """Extended edge response with runtime data."""
271
+
272
+ runtime_data: dict[str, Any] | None = None
273
+ call_count: int | None = None
274
+ avg_duration_ms: float | None = None
275
+ error_rate: float | None = None
276
+ confidence: float | None = None
277
+
278
+
279
+ class ArchitectureCodeGraphResponse(BaseModel):
280
+ """Response model for architecture code graph."""
281
+
282
+ nodes: list[ArchitectureNodeResponse]
283
+ edges: list[RuntimeEdgeResponse]
284
+ architecture_metadata: dict[str, Any] | None = None
285
+ quality_summary: dict[str, Any] | None = None
286
+ dependency_summary: dict[str, Any] | None = None
287
+
288
+
289
+ class RuntimeGraphResponse(BaseModel):
290
+ """Response model for runtime code graph."""
291
+
292
+ nodes: list[NodeResponse]
293
+ edges: list[RuntimeEdgeResponse]
294
+ runtime_metadata: dict[str, Any] | None = None
295
+ trace_summary: dict[str, Any] | None = None
296
+
297
+
298
+ class CodeGraphIntegrationRequest(BaseModel):
299
+ """Request model for integrating architecture data with code graph."""
300
+
301
+ include_architecture: bool = Field(True, description="Include architecture analysis data")
302
+ include_dependencies: bool = Field(True, description="Include dependency analysis data")
303
+ include_quality: bool = Field(True, description="Include quality analysis data")
304
+ include_runtime: bool = Field(False, description="Include runtime trace data")
305
+ quality_threshold: float | None = Field(None, description="Minimum quality score to include")
306
+ hotspot_threshold: float | None = Field(None, description="Minimum hotspot score to include")
307
+
308
+
309
+ # Rebuild all models to resolve forward references
310
+ CodebaseInitRequest.model_rebuild()
311
+ CodeGraphAnalysisRequest.model_rebuild()
312
+ PatternSearchRequest.model_rebuild()
313
+ EntityQueryRequest.model_rebuild()
314
+ RelatedNodesRequest.model_rebuild()
315
+ RawQueryRequest.model_rebuild()
316
+ CodeGraphIntegrationRequest.model_rebuild()
317
+ InitResponse.model_rebuild()
@@ -0,0 +1,241 @@
1
+ """Module for defining container models used in the application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from enum import StrEnum
7
+ from typing import Any, Literal
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
10
+
11
+
12
+ class ContainerStatus(StrEnum):
13
+ """Container status enumeration"""
14
+
15
+ CREATING = "creating"
16
+ RUNNING = "running"
17
+ STOPPED = "stopped"
18
+ ERROR = "error"
19
+ REMOVING = "removing"
20
+
21
+
22
+ class AnalysisConfig(BaseModel):
23
+ """Configuration for code analysis during provisioning."""
24
+
25
+ include_tests: bool | None = Field(
26
+ None,
27
+ description="Whether to include test files in code graph analysis",
28
+ )
29
+ language_hint: str | None = Field(
30
+ None,
31
+ description="Primary programming language hint for better analysis",
32
+ )
33
+ exclude_patterns: list[str] | None = Field(
34
+ None, description="Additional patterns to exclude from analysis"
35
+ )
36
+ include_patterns: list[str] | None = Field(
37
+ None, description="Patterns to include despite other exclusions"
38
+ )
39
+
40
+
41
+ class GitHubRepoRequest(BaseModel):
42
+ """Request model for GitHub repository operations."""
43
+
44
+ model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True, extra="forbid")
45
+ repo_url: str = Field(
46
+ ...,
47
+ min_length=1,
48
+ description="GitHub repository URL",
49
+ pattern=r"^https://github\.com/[A-Za-z0-9\-_.]+/[A-Za-z0-9\-_.]+(?:/pull/\d+)?(?:\.git)?/?$",
50
+ json_schema_extra={"example": "https://github.com/owner/repository"},
51
+ )
52
+ branch: str = Field(
53
+ "main",
54
+ min_length=1,
55
+ description="Git branch to clone",
56
+ json_schema_extra={"example": "main"},
57
+ )
58
+ container_name: str | None = Field(
59
+ None,
60
+ description="Optional container name",
61
+ pattern=r"^[a-zA-Z][a-zA-Z0-9\-_]{0,62}$",
62
+ json_schema_extra={"example": "my-analysis-container"},
63
+ )
64
+ description: str | None = Field(None, description="Optional description", max_length=500)
65
+ access_token: str | None = Field(
66
+ None,
67
+ description="GitHub Personal Access Token for private repositories",
68
+ json_schema_extra={"example": "<github-personal-access-token>"},
69
+ )
70
+ user_id: str | None = Field(
71
+ None,
72
+ description="User ID for multi-account workflow (defaults to anonymous if not provided)",
73
+ pattern=r"^[a-zA-Z0-9\-_\.@]+$",
74
+ max_length=128,
75
+ json_schema_extra={"example": "user-123"},
76
+ )
77
+ force_rebuild: bool | None = Field(
78
+ False, description="Force recreation if container already exists"
79
+ )
80
+ analysis_config: AnalysisConfig | None = Field(
81
+ None, description="Configuration for code analysis during provisioning"
82
+ )
83
+ pr_number: int | None = Field(
84
+ None, description="Pull request number to provision", ge=1, le=999999
85
+ )
86
+ provision_type: Literal["branch", "pull_request"] = Field(
87
+ "branch", description="Type of provisioning"
88
+ )
89
+ secret_config_id: str | None = Field(
90
+ None,
91
+ description="Client ID for retrieving environment variables from secrets store",
92
+ pattern=r"^[a-zA-Z0-9\-_\.]+$",
93
+ max_length=128,
94
+ json_schema_extra={"example": "repo-myorg-myproject"},
95
+ )
96
+
97
+ @field_validator("repo_url")
98
+ @classmethod
99
+ def validate_repo_url(cls, v):
100
+ """Validate repo URL is not empty and follows GitHub URL format."""
101
+ if not v or not v.strip():
102
+ raise ValueError("Repository URL cannot be empty")
103
+
104
+ v = v.strip()
105
+
106
+ # Basic URL format validation for GitHub URLs
107
+
108
+ # Check if it's a PR URL
109
+ pr_url_pattern = re.compile(
110
+ r"^https://github\.com/[A-Za-z0-9\-_.]+/[A-Za-z0-9\-_.]+/pull/\d+(?:/.*)?$"
111
+ )
112
+
113
+ if pr_url_pattern.match(v):
114
+ # This is a PR URL - we'll handle parsing in the model validator
115
+ return v
116
+
117
+ # Standard repo URL pattern
118
+ github_url_pattern = re.compile(
119
+ r"^https://github\.com/[A-Za-z0-9\-_.]+/[A-Za-z0-9\-_.]+(?:\.git)?/?$"
120
+ )
121
+
122
+ # Additional check for obviously invalid URLs like single characters
123
+ if len(v) < 10 or not v.startswith("https://"):
124
+ raise ValueError(
125
+ "Repository URL must be a valid GitHub URL (e.g., https://github.com/owner/repo)"
126
+ )
127
+
128
+ if not github_url_pattern.match(v):
129
+ raise ValueError(
130
+ "Repository URL must be a valid GitHub URL format (e.g., https://github.com/owner/repo)"
131
+ )
132
+
133
+ return v
134
+
135
+ @field_validator("branch")
136
+ @classmethod
137
+ def validate_branch(cls, v):
138
+ """Validate branch is not empty."""
139
+ if not v or not v.strip():
140
+ raise ValueError("Branch name cannot be empty")
141
+ return v.strip()
142
+
143
+ @field_validator("pr_number")
144
+ @classmethod
145
+ def validate_pr_number(cls, v):
146
+ """Validate PR number if provided."""
147
+ if v is not None and v <= 0:
148
+ raise ValueError("PR number must be positive if provided")
149
+ return v
150
+
151
+ @model_validator(mode="after")
152
+ def parse_pr_url(self):
153
+ """Parse PR URL and extract repository URL and PR number if applicable."""
154
+
155
+ # Check if repo_url is a PR URL
156
+ pr_url_pattern = re.compile(
157
+ r"^https://github\.com/([A-Za-z0-9\-_.]+)/([A-Za-z0-9\-_.]+)/pull/(\d+)(?:/.*)?$"
158
+ )
159
+
160
+ match = pr_url_pattern.match(self.repo_url)
161
+ if match:
162
+ # Extract components from PR URL
163
+ owner = match.group(1)
164
+ repo = match.group(2)
165
+ pr_number = int(match.group(3))
166
+
167
+ # Update fields
168
+ self.repo_url = f"https://github.com/{owner}/{repo}"
169
+ self.pr_number = pr_number
170
+ self.provision_type = "pull_request"
171
+
172
+ return self
173
+
174
+
175
+ class ContainerInfo(BaseModel):
176
+ """Information model for container instances."""
177
+
178
+ container_name: (
179
+ str # Primary identifier - same as Docker container name, used to access code graphs
180
+ )
181
+ api_endpoint: str
182
+ code_graph_id: str = "" # Code graph identifier for this container
183
+ codebase_path: str
184
+ repo_url: str
185
+ branch: str
186
+ status: ContainerStatus
187
+ description: str | None = None
188
+ pr_number: int | None = None
189
+ provision_type: str = "branch"
190
+ github_access_token: str | None = Field(
191
+ None,
192
+ description="GitHub access token for this container's repository operations (deprecated - use user_token_id)",
193
+ )
194
+ user_token_id: str | None = Field(
195
+ None,
196
+ description="Reference to encrypted user GitHub token in SecureTokenManager",
197
+ )
198
+ user_id: str | None = Field(
199
+ None,
200
+ description="User ID who provisioned this container",
201
+ )
202
+ original_repo_owner: str | None = Field(
203
+ None,
204
+ description="Original repository owner (for creating upstream PRs)",
205
+ )
206
+ original_repo_name: str | None = Field(
207
+ None,
208
+ description="Original repository name (for creating upstream PRs)",
209
+ )
210
+ system_fork_owner: str | None = Field(
211
+ None,
212
+ description="System account owner where fork resides (e.g., 'nonagenticai')",
213
+ )
214
+ graph_initialized: bool = Field(
215
+ False,
216
+ description="Whether the code graph has been initialized for this container",
217
+ )
218
+ tenant_id: str | None = Field(
219
+ None,
220
+ description="Tenant identifier for multi-tenant data isolation",
221
+ )
222
+
223
+
224
+ class ContainerSpec(BaseModel):
225
+ """Specification model for container setup."""
226
+
227
+ name: str = Field(..., description="Container name")
228
+ image: str = Field(..., description="Docker image to use")
229
+ port: int = Field(..., description="Host port to bind to")
230
+ command: str | None = Field(None, description="Command to run in container")
231
+ working_dir: str | None = Field(None, description="Working directory")
232
+ environment: dict[str, Any] = Field(default_factory=dict, description="Environment variables")
233
+ volumes: dict[str, Any] = Field(default_factory=dict, description="Volume mounts")
234
+
235
+ @field_validator("name")
236
+ @classmethod
237
+ def validate_name(cls, v):
238
+ """Validate container name is not empty."""
239
+ if not v or not v.strip():
240
+ raise ValueError("Container name cannot be empty")
241
+ return v.strip()