amd-gaia 0.15.0__py3-none-any.whl → 0.15.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.
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
- amd_gaia-0.15.2.dist-info/RECORD +182 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2132 -2177
- gaia/agents/base/api_agent.py +119 -120
- gaia/agents/base/console.py +1967 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +88 -83
- gaia/agents/blender/__init__.py +7 -0
- gaia/agents/blender/agent.py +553 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +809 -835
- gaia/agents/chat/app.py +1065 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1744 -1729
- gaia/agents/chat/tools/shell_tools.py +437 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2034 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +643 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1504 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1972 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +184 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +428 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5659 -5632
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/installer/__init__.py +23 -0
- gaia/installer/init_command.py +1275 -0
- gaia/installer/lemonade_installer.py +619 -0
- gaia/llm/__init__.py +10 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3421 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +118 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +183 -163
- gaia/talk/app.py +287 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.15.0.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -723
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
|
@@ -1,319 +1,319 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""Code formatting tools mixin for Code Agent."""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import shlex
|
|
7
|
-
import subprocess
|
|
8
|
-
import tempfile
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Any, Dict
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class CodeFormattingMixin:
|
|
14
|
-
"""Mixin providing code formatting and linting tools for Python code.
|
|
15
|
-
|
|
16
|
-
This mixin provides tools for:
|
|
17
|
-
- Formatting Python code with Black
|
|
18
|
-
- Combined linting and formatting analysis
|
|
19
|
-
- Automatic code quality improvements
|
|
20
|
-
|
|
21
|
-
Tools provided:
|
|
22
|
-
- format_with_black: Format code using Black formatter
|
|
23
|
-
- lint_and_format: Combined pylint analysis and Black formatting
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def register_code_formatting_tools(self) -> None:
|
|
27
|
-
"""Register code formatting tools."""
|
|
28
|
-
from gaia.agents.base.tools import tool
|
|
29
|
-
|
|
30
|
-
@tool
|
|
31
|
-
def format_with_black(
|
|
32
|
-
file_path: str = None,
|
|
33
|
-
code: str = None,
|
|
34
|
-
line_length: int = 88,
|
|
35
|
-
check_only: bool = False,
|
|
36
|
-
) -> Dict[str, Any]:
|
|
37
|
-
"""Format Python code with Black.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
file_path: Path to Python file to format (optional)
|
|
41
|
-
code: Python code string to format (optional)
|
|
42
|
-
line_length: Maximum line length (default: 88)
|
|
43
|
-
check_only: Only check if formatting is needed, don't modify (default: False)
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
Dictionary with formatting result
|
|
47
|
-
"""
|
|
48
|
-
try:
|
|
49
|
-
# Determine source
|
|
50
|
-
if file_path:
|
|
51
|
-
path = Path(file_path)
|
|
52
|
-
if not path.exists():
|
|
53
|
-
return {
|
|
54
|
-
"status": "error",
|
|
55
|
-
"error": f"File not found: {file_path}",
|
|
56
|
-
}
|
|
57
|
-
target_file = str(path)
|
|
58
|
-
original_content = path.read_text(encoding="utf-8")
|
|
59
|
-
elif code:
|
|
60
|
-
# Write code to temporary file
|
|
61
|
-
with tempfile.NamedTemporaryFile(
|
|
62
|
-
mode="w", suffix=".py", delete=False
|
|
63
|
-
) as f:
|
|
64
|
-
f.write(code)
|
|
65
|
-
target_file = f.name
|
|
66
|
-
original_content = code
|
|
67
|
-
else:
|
|
68
|
-
return {
|
|
69
|
-
"status": "error",
|
|
70
|
-
"error": "Either file_path or code must be provided",
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
# Run black
|
|
74
|
-
cmd = [
|
|
75
|
-
"python",
|
|
76
|
-
"-m",
|
|
77
|
-
"black",
|
|
78
|
-
f"--line-length={line_length}",
|
|
79
|
-
"--quiet",
|
|
80
|
-
]
|
|
81
|
-
|
|
82
|
-
if check_only:
|
|
83
|
-
cmd.append("--check")
|
|
84
|
-
cmd.append("--diff")
|
|
85
|
-
|
|
86
|
-
cmd.append(target_file)
|
|
87
|
-
|
|
88
|
-
result = subprocess.run(
|
|
89
|
-
cmd, capture_output=True, text=True, timeout=30, check=False
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# Read the formatted content
|
|
93
|
-
formatted_content = original_content
|
|
94
|
-
if not check_only and result.returncode == 0:
|
|
95
|
-
if file_path:
|
|
96
|
-
formatted_content = Path(target_file).read_text(
|
|
97
|
-
encoding="utf-8"
|
|
98
|
-
)
|
|
99
|
-
else:
|
|
100
|
-
with open(target_file, "r", encoding="utf-8") as f:
|
|
101
|
-
formatted_content = f.read()
|
|
102
|
-
|
|
103
|
-
# Clean up temp file if created
|
|
104
|
-
if code and os.path.exists(target_file):
|
|
105
|
-
os.unlink(target_file)
|
|
106
|
-
|
|
107
|
-
# Check if formatting was needed
|
|
108
|
-
needs_formatting = (
|
|
109
|
-
result.returncode != 0
|
|
110
|
-
if check_only
|
|
111
|
-
else original_content != formatted_content
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# Generate diff if content changed
|
|
115
|
-
diff = None
|
|
116
|
-
if needs_formatting and not check_only:
|
|
117
|
-
diff = self._generate_unified_diff(
|
|
118
|
-
original_content, formatted_content, "original", "formatted"
|
|
119
|
-
)
|
|
120
|
-
elif check_only and result.stdout:
|
|
121
|
-
diff = result.stdout
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
"status": "success",
|
|
125
|
-
"formatted": not check_only and needs_formatting,
|
|
126
|
-
"needs_formatting": needs_formatting,
|
|
127
|
-
"formatted_code": formatted_content if not check_only else None,
|
|
128
|
-
"diff": diff,
|
|
129
|
-
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
130
|
-
"stdout": result.stdout,
|
|
131
|
-
"stderr": result.stderr,
|
|
132
|
-
"return_code": result.returncode,
|
|
133
|
-
"message": (
|
|
134
|
-
"Code formatted successfully"
|
|
135
|
-
if needs_formatting and not check_only
|
|
136
|
-
else (
|
|
137
|
-
"Code needs formatting"
|
|
138
|
-
if needs_formatting
|
|
139
|
-
else "Code is already properly formatted"
|
|
140
|
-
)
|
|
141
|
-
),
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
except subprocess.TimeoutExpired:
|
|
145
|
-
return {"status": "error", "error": "Black formatting timed out"}
|
|
146
|
-
except subprocess.CalledProcessError as e:
|
|
147
|
-
return {"status": "error", "error": f"Black failed: {e.stderr}"}
|
|
148
|
-
except ImportError:
|
|
149
|
-
return {
|
|
150
|
-
"status": "error",
|
|
151
|
-
"error": "black is not installed. Install with: uv pip install black",
|
|
152
|
-
}
|
|
153
|
-
except Exception as e:
|
|
154
|
-
return {"status": "error", "error": str(e)}
|
|
155
|
-
|
|
156
|
-
@tool
|
|
157
|
-
def lint_and_format(
|
|
158
|
-
file_path: str = None,
|
|
159
|
-
code: str = None,
|
|
160
|
-
fix: bool = False,
|
|
161
|
-
line_length: int = 88,
|
|
162
|
-
) -> Dict[str, Any]:
|
|
163
|
-
"""Combined linting and formatting analysis.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
file_path: Path to Python file (optional)
|
|
167
|
-
code: Python code string (optional)
|
|
168
|
-
fix: Apply black formatting if needed (default: False)
|
|
169
|
-
line_length: Maximum line length for black (default: 88)
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
Dictionary with combined linting and formatting results
|
|
173
|
-
"""
|
|
174
|
-
try:
|
|
175
|
-
results = {"status": "success", "file_path": file_path}
|
|
176
|
-
|
|
177
|
-
# First, check syntax
|
|
178
|
-
if code:
|
|
179
|
-
syntax_check = self._validate_python_syntax(code)
|
|
180
|
-
elif file_path:
|
|
181
|
-
content = Path(file_path).read_text(encoding="utf-8")
|
|
182
|
-
syntax_check = self._validate_python_syntax(content)
|
|
183
|
-
else:
|
|
184
|
-
return {
|
|
185
|
-
"status": "error",
|
|
186
|
-
"error": "Either file_path or code must be provided",
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
results["syntax_valid"] = syntax_check["is_valid"]
|
|
190
|
-
|
|
191
|
-
if not syntax_check["is_valid"]:
|
|
192
|
-
results["syntax_errors"] = syntax_check.get("errors", [])
|
|
193
|
-
results["message"] = (
|
|
194
|
-
"Code has syntax errors - fix these before linting"
|
|
195
|
-
)
|
|
196
|
-
return results
|
|
197
|
-
|
|
198
|
-
# Run pylint analysis
|
|
199
|
-
pylint_result = self._execute_tool(
|
|
200
|
-
"analyze_with_pylint", {"file_path": file_path, "code": code}
|
|
201
|
-
)
|
|
202
|
-
results["pylint"] = {
|
|
203
|
-
"total_issues": pylint_result.get("total_issues", 0),
|
|
204
|
-
"errors": pylint_result.get("errors", 0),
|
|
205
|
-
"warnings": pylint_result.get("warnings", 0),
|
|
206
|
-
"conventions": pylint_result.get("conventions", 0),
|
|
207
|
-
"issues": pylint_result.get("issues", [])[:10], # First 10 issues
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
# Check/apply black formatting
|
|
211
|
-
black_result = format_with_black(
|
|
212
|
-
file_path=file_path,
|
|
213
|
-
code=code,
|
|
214
|
-
line_length=line_length,
|
|
215
|
-
check_only=not fix,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
results["formatting"] = {
|
|
219
|
-
"needs_formatting": black_result.get("needs_formatting", False),
|
|
220
|
-
"formatted": black_result.get("formatted", False),
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if fix and black_result.get("formatted_code"):
|
|
224
|
-
results["formatted_code"] = black_result["formatted_code"]
|
|
225
|
-
if file_path:
|
|
226
|
-
# Write the formatted code back
|
|
227
|
-
Path(file_path).write_text(
|
|
228
|
-
black_result["formatted_code"], encoding="utf-8"
|
|
229
|
-
)
|
|
230
|
-
results["file_updated"] = True
|
|
231
|
-
|
|
232
|
-
# If there are linting issues, try to fix them
|
|
233
|
-
if results["pylint"]["total_issues"] > 0 and fix:
|
|
234
|
-
# Call the tool via the execution framework
|
|
235
|
-
fix_lint_result = self._execute_tool(
|
|
236
|
-
"fix_linting_errors",
|
|
237
|
-
{
|
|
238
|
-
"file_path": file_path,
|
|
239
|
-
"lint_issues": results["pylint"]["issues"],
|
|
240
|
-
},
|
|
241
|
-
)
|
|
242
|
-
if fix_lint_result.get("file_modified"):
|
|
243
|
-
results["lint_fixes_applied"] = fix_lint_result.get(
|
|
244
|
-
"fixes_applied", []
|
|
245
|
-
)
|
|
246
|
-
results["file_updated"] = True
|
|
247
|
-
|
|
248
|
-
# Re-run pylint to check remaining issues
|
|
249
|
-
pylint_recheck = self._execute_tool(
|
|
250
|
-
"analyze_with_pylint", {"file_path": file_path}
|
|
251
|
-
)
|
|
252
|
-
results["pylint_after_fixes"] = {
|
|
253
|
-
"total_issues": pylint_recheck.get("total_issues", 0),
|
|
254
|
-
"errors": pylint_recheck.get("errors", 0),
|
|
255
|
-
"warnings": pylint_recheck.get("warnings", 0),
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if black_result.get("diff"):
|
|
259
|
-
results["formatting_diff"] = black_result["diff"]
|
|
260
|
-
|
|
261
|
-
# Overall assessment
|
|
262
|
-
is_clean = (
|
|
263
|
-
results["pylint"]["total_issues"] == 0
|
|
264
|
-
and not results["formatting"]["needs_formatting"]
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
results["clean"] = is_clean
|
|
268
|
-
results["message"] = (
|
|
269
|
-
"Code is clean and properly formatted"
|
|
270
|
-
if is_clean
|
|
271
|
-
else f"Found {results['pylint']['total_issues']} linting issues"
|
|
272
|
-
+ (
|
|
273
|
-
" and formatting issues"
|
|
274
|
-
if results["formatting"]["needs_formatting"]
|
|
275
|
-
else ""
|
|
276
|
-
)
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
return results
|
|
280
|
-
|
|
281
|
-
except Exception as e:
|
|
282
|
-
return {"status": "error", "error": str(e)}
|
|
283
|
-
|
|
284
|
-
def _generate_unified_diff(
|
|
285
|
-
self,
|
|
286
|
-
original: str,
|
|
287
|
-
modified: str,
|
|
288
|
-
original_name: str = "original",
|
|
289
|
-
modified_name: str = "modified",
|
|
290
|
-
context_lines: int = 3,
|
|
291
|
-
) -> str:
|
|
292
|
-
"""Generate a unified diff between two strings.
|
|
293
|
-
|
|
294
|
-
Args:
|
|
295
|
-
original: Original content
|
|
296
|
-
modified: Modified content
|
|
297
|
-
original_name: Name for original in diff header
|
|
298
|
-
modified_name: Name for modified in diff header
|
|
299
|
-
context_lines: Number of context lines
|
|
300
|
-
|
|
301
|
-
Returns:
|
|
302
|
-
Unified diff as string
|
|
303
|
-
"""
|
|
304
|
-
import difflib
|
|
305
|
-
|
|
306
|
-
original_lines = original.splitlines(keepends=True)
|
|
307
|
-
modified_lines = modified.splitlines(keepends=True)
|
|
308
|
-
|
|
309
|
-
# Generate the diff
|
|
310
|
-
diff = difflib.unified_diff(
|
|
311
|
-
original_lines,
|
|
312
|
-
modified_lines,
|
|
313
|
-
fromfile=original_name,
|
|
314
|
-
tofile=modified_name,
|
|
315
|
-
n=context_lines,
|
|
316
|
-
lineterm="",
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
return "".join(diff)
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Code formatting tools mixin for Code Agent."""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shlex
|
|
7
|
+
import subprocess
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CodeFormattingMixin:
|
|
14
|
+
"""Mixin providing code formatting and linting tools for Python code.
|
|
15
|
+
|
|
16
|
+
This mixin provides tools for:
|
|
17
|
+
- Formatting Python code with Black
|
|
18
|
+
- Combined linting and formatting analysis
|
|
19
|
+
- Automatic code quality improvements
|
|
20
|
+
|
|
21
|
+
Tools provided:
|
|
22
|
+
- format_with_black: Format code using Black formatter
|
|
23
|
+
- lint_and_format: Combined pylint analysis and Black formatting
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def register_code_formatting_tools(self) -> None:
|
|
27
|
+
"""Register code formatting tools."""
|
|
28
|
+
from gaia.agents.base.tools import tool
|
|
29
|
+
|
|
30
|
+
@tool
|
|
31
|
+
def format_with_black(
|
|
32
|
+
file_path: str = None,
|
|
33
|
+
code: str = None,
|
|
34
|
+
line_length: int = 88,
|
|
35
|
+
check_only: bool = False,
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
"""Format Python code with Black.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
file_path: Path to Python file to format (optional)
|
|
41
|
+
code: Python code string to format (optional)
|
|
42
|
+
line_length: Maximum line length (default: 88)
|
|
43
|
+
check_only: Only check if formatting is needed, don't modify (default: False)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Dictionary with formatting result
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
# Determine source
|
|
50
|
+
if file_path:
|
|
51
|
+
path = Path(file_path)
|
|
52
|
+
if not path.exists():
|
|
53
|
+
return {
|
|
54
|
+
"status": "error",
|
|
55
|
+
"error": f"File not found: {file_path}",
|
|
56
|
+
}
|
|
57
|
+
target_file = str(path)
|
|
58
|
+
original_content = path.read_text(encoding="utf-8")
|
|
59
|
+
elif code:
|
|
60
|
+
# Write code to temporary file
|
|
61
|
+
with tempfile.NamedTemporaryFile(
|
|
62
|
+
mode="w", suffix=".py", delete=False
|
|
63
|
+
) as f:
|
|
64
|
+
f.write(code)
|
|
65
|
+
target_file = f.name
|
|
66
|
+
original_content = code
|
|
67
|
+
else:
|
|
68
|
+
return {
|
|
69
|
+
"status": "error",
|
|
70
|
+
"error": "Either file_path or code must be provided",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Run black
|
|
74
|
+
cmd = [
|
|
75
|
+
"python",
|
|
76
|
+
"-m",
|
|
77
|
+
"black",
|
|
78
|
+
f"--line-length={line_length}",
|
|
79
|
+
"--quiet",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
if check_only:
|
|
83
|
+
cmd.append("--check")
|
|
84
|
+
cmd.append("--diff")
|
|
85
|
+
|
|
86
|
+
cmd.append(target_file)
|
|
87
|
+
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
cmd, capture_output=True, text=True, timeout=30, check=False
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Read the formatted content
|
|
93
|
+
formatted_content = original_content
|
|
94
|
+
if not check_only and result.returncode == 0:
|
|
95
|
+
if file_path:
|
|
96
|
+
formatted_content = Path(target_file).read_text(
|
|
97
|
+
encoding="utf-8"
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
with open(target_file, "r", encoding="utf-8") as f:
|
|
101
|
+
formatted_content = f.read()
|
|
102
|
+
|
|
103
|
+
# Clean up temp file if created
|
|
104
|
+
if code and os.path.exists(target_file):
|
|
105
|
+
os.unlink(target_file)
|
|
106
|
+
|
|
107
|
+
# Check if formatting was needed
|
|
108
|
+
needs_formatting = (
|
|
109
|
+
result.returncode != 0
|
|
110
|
+
if check_only
|
|
111
|
+
else original_content != formatted_content
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Generate diff if content changed
|
|
115
|
+
diff = None
|
|
116
|
+
if needs_formatting and not check_only:
|
|
117
|
+
diff = self._generate_unified_diff(
|
|
118
|
+
original_content, formatted_content, "original", "formatted"
|
|
119
|
+
)
|
|
120
|
+
elif check_only and result.stdout:
|
|
121
|
+
diff = result.stdout
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"status": "success",
|
|
125
|
+
"formatted": not check_only and needs_formatting,
|
|
126
|
+
"needs_formatting": needs_formatting,
|
|
127
|
+
"formatted_code": formatted_content if not check_only else None,
|
|
128
|
+
"diff": diff,
|
|
129
|
+
"command": " ".join(shlex.quote(str(c)) for c in cmd),
|
|
130
|
+
"stdout": result.stdout,
|
|
131
|
+
"stderr": result.stderr,
|
|
132
|
+
"return_code": result.returncode,
|
|
133
|
+
"message": (
|
|
134
|
+
"Code formatted successfully"
|
|
135
|
+
if needs_formatting and not check_only
|
|
136
|
+
else (
|
|
137
|
+
"Code needs formatting"
|
|
138
|
+
if needs_formatting
|
|
139
|
+
else "Code is already properly formatted"
|
|
140
|
+
)
|
|
141
|
+
),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
except subprocess.TimeoutExpired:
|
|
145
|
+
return {"status": "error", "error": "Black formatting timed out"}
|
|
146
|
+
except subprocess.CalledProcessError as e:
|
|
147
|
+
return {"status": "error", "error": f"Black failed: {e.stderr}"}
|
|
148
|
+
except ImportError:
|
|
149
|
+
return {
|
|
150
|
+
"status": "error",
|
|
151
|
+
"error": "black is not installed. Install with: uv pip install black",
|
|
152
|
+
}
|
|
153
|
+
except Exception as e:
|
|
154
|
+
return {"status": "error", "error": str(e)}
|
|
155
|
+
|
|
156
|
+
@tool
|
|
157
|
+
def lint_and_format(
|
|
158
|
+
file_path: str = None,
|
|
159
|
+
code: str = None,
|
|
160
|
+
fix: bool = False,
|
|
161
|
+
line_length: int = 88,
|
|
162
|
+
) -> Dict[str, Any]:
|
|
163
|
+
"""Combined linting and formatting analysis.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
file_path: Path to Python file (optional)
|
|
167
|
+
code: Python code string (optional)
|
|
168
|
+
fix: Apply black formatting if needed (default: False)
|
|
169
|
+
line_length: Maximum line length for black (default: 88)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dictionary with combined linting and formatting results
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
results = {"status": "success", "file_path": file_path}
|
|
176
|
+
|
|
177
|
+
# First, check syntax
|
|
178
|
+
if code:
|
|
179
|
+
syntax_check = self._validate_python_syntax(code)
|
|
180
|
+
elif file_path:
|
|
181
|
+
content = Path(file_path).read_text(encoding="utf-8")
|
|
182
|
+
syntax_check = self._validate_python_syntax(content)
|
|
183
|
+
else:
|
|
184
|
+
return {
|
|
185
|
+
"status": "error",
|
|
186
|
+
"error": "Either file_path or code must be provided",
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
results["syntax_valid"] = syntax_check["is_valid"]
|
|
190
|
+
|
|
191
|
+
if not syntax_check["is_valid"]:
|
|
192
|
+
results["syntax_errors"] = syntax_check.get("errors", [])
|
|
193
|
+
results["message"] = (
|
|
194
|
+
"Code has syntax errors - fix these before linting"
|
|
195
|
+
)
|
|
196
|
+
return results
|
|
197
|
+
|
|
198
|
+
# Run pylint analysis
|
|
199
|
+
pylint_result = self._execute_tool(
|
|
200
|
+
"analyze_with_pylint", {"file_path": file_path, "code": code}
|
|
201
|
+
)
|
|
202
|
+
results["pylint"] = {
|
|
203
|
+
"total_issues": pylint_result.get("total_issues", 0),
|
|
204
|
+
"errors": pylint_result.get("errors", 0),
|
|
205
|
+
"warnings": pylint_result.get("warnings", 0),
|
|
206
|
+
"conventions": pylint_result.get("conventions", 0),
|
|
207
|
+
"issues": pylint_result.get("issues", [])[:10], # First 10 issues
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Check/apply black formatting
|
|
211
|
+
black_result = format_with_black(
|
|
212
|
+
file_path=file_path,
|
|
213
|
+
code=code,
|
|
214
|
+
line_length=line_length,
|
|
215
|
+
check_only=not fix,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
results["formatting"] = {
|
|
219
|
+
"needs_formatting": black_result.get("needs_formatting", False),
|
|
220
|
+
"formatted": black_result.get("formatted", False),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if fix and black_result.get("formatted_code"):
|
|
224
|
+
results["formatted_code"] = black_result["formatted_code"]
|
|
225
|
+
if file_path:
|
|
226
|
+
# Write the formatted code back
|
|
227
|
+
Path(file_path).write_text(
|
|
228
|
+
black_result["formatted_code"], encoding="utf-8"
|
|
229
|
+
)
|
|
230
|
+
results["file_updated"] = True
|
|
231
|
+
|
|
232
|
+
# If there are linting issues, try to fix them
|
|
233
|
+
if results["pylint"]["total_issues"] > 0 and fix:
|
|
234
|
+
# Call the tool via the execution framework
|
|
235
|
+
fix_lint_result = self._execute_tool(
|
|
236
|
+
"fix_linting_errors",
|
|
237
|
+
{
|
|
238
|
+
"file_path": file_path,
|
|
239
|
+
"lint_issues": results["pylint"]["issues"],
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
if fix_lint_result.get("file_modified"):
|
|
243
|
+
results["lint_fixes_applied"] = fix_lint_result.get(
|
|
244
|
+
"fixes_applied", []
|
|
245
|
+
)
|
|
246
|
+
results["file_updated"] = True
|
|
247
|
+
|
|
248
|
+
# Re-run pylint to check remaining issues
|
|
249
|
+
pylint_recheck = self._execute_tool(
|
|
250
|
+
"analyze_with_pylint", {"file_path": file_path}
|
|
251
|
+
)
|
|
252
|
+
results["pylint_after_fixes"] = {
|
|
253
|
+
"total_issues": pylint_recheck.get("total_issues", 0),
|
|
254
|
+
"errors": pylint_recheck.get("errors", 0),
|
|
255
|
+
"warnings": pylint_recheck.get("warnings", 0),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if black_result.get("diff"):
|
|
259
|
+
results["formatting_diff"] = black_result["diff"]
|
|
260
|
+
|
|
261
|
+
# Overall assessment
|
|
262
|
+
is_clean = (
|
|
263
|
+
results["pylint"]["total_issues"] == 0
|
|
264
|
+
and not results["formatting"]["needs_formatting"]
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
results["clean"] = is_clean
|
|
268
|
+
results["message"] = (
|
|
269
|
+
"Code is clean and properly formatted"
|
|
270
|
+
if is_clean
|
|
271
|
+
else f"Found {results['pylint']['total_issues']} linting issues"
|
|
272
|
+
+ (
|
|
273
|
+
" and formatting issues"
|
|
274
|
+
if results["formatting"]["needs_formatting"]
|
|
275
|
+
else ""
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return results
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
return {"status": "error", "error": str(e)}
|
|
283
|
+
|
|
284
|
+
def _generate_unified_diff(
|
|
285
|
+
self,
|
|
286
|
+
original: str,
|
|
287
|
+
modified: str,
|
|
288
|
+
original_name: str = "original",
|
|
289
|
+
modified_name: str = "modified",
|
|
290
|
+
context_lines: int = 3,
|
|
291
|
+
) -> str:
|
|
292
|
+
"""Generate a unified diff between two strings.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
original: Original content
|
|
296
|
+
modified: Modified content
|
|
297
|
+
original_name: Name for original in diff header
|
|
298
|
+
modified_name: Name for modified in diff header
|
|
299
|
+
context_lines: Number of context lines
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Unified diff as string
|
|
303
|
+
"""
|
|
304
|
+
import difflib
|
|
305
|
+
|
|
306
|
+
original_lines = original.splitlines(keepends=True)
|
|
307
|
+
modified_lines = modified.splitlines(keepends=True)
|
|
308
|
+
|
|
309
|
+
# Generate the diff
|
|
310
|
+
diff = difflib.unified_diff(
|
|
311
|
+
original_lines,
|
|
312
|
+
modified_lines,
|
|
313
|
+
fromfile=original_name,
|
|
314
|
+
tofile=modified_name,
|
|
315
|
+
n=context_lines,
|
|
316
|
+
lineterm="",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return "".join(diff)
|