siada-cli 0.0.1__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.
- agent_config.yaml +34 -0
- siada/__init__.py +13 -0
- siada/agent_hub/__init__.py +10 -0
- siada/agent_hub/coder/bug_fix_agent.py +398 -0
- siada/agent_hub/coder/code_gen_agent.py +235 -0
- siada/agent_hub/coder/fe_gen_agent.py +37 -0
- siada/agent_hub/coder/issue_review_agent.py +68 -0
- siada/agent_hub/coder/prompt/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
- siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
- siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
- siada/agent_hub/coder/prompt/base/rules.py +40 -0
- siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
- siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
- siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
- siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
- siada/agent_hub/coder/tracing/__init__.py +22 -0
- siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
- siada/agent_hub/hooks/__init__.py +0 -0
- siada/agent_hub/hooks/io_hooks.py +30 -0
- siada/agent_hub/siada_agent.py +214 -0
- siada/config/__init__.py +0 -0
- siada/config/config_loader.py +67 -0
- siada/entrypoint/__init__.py +3 -0
- siada/entrypoint/args_parser/__init__.py +0 -0
- siada/entrypoint/args_parser/args.py +195 -0
- siada/entrypoint/interaction/__init__.py +0 -0
- siada/entrypoint/interaction/base_turn.py +107 -0
- siada/entrypoint/interaction/config.py +30 -0
- siada/entrypoint/interaction/controller.py +122 -0
- siada/entrypoint/interaction/nointeractive_controller.py +27 -0
- siada/entrypoint/interaction/turn/interface.py +116 -0
- siada/entrypoint/interaction/turn/models.py +34 -0
- siada/entrypoint/interaction/turn/run_turn.py +641 -0
- siada/entrypoint/interaction/turn_factory.py +35 -0
- siada/entrypoint/interaction/turn_models.py +28 -0
- siada/entrypoint/siadahub.py +413 -0
- siada/foundation/__init__.py +0 -0
- siada/foundation/code_agent_context.py +48 -0
- siada/foundation/config.py +59 -0
- siada/foundation/constants.py +2 -0
- siada/foundation/context.py +76 -0
- siada/foundation/logging.py +341 -0
- siada/foundation/tools/__init__.py +0 -0
- siada/foundation/tools/get_git_diff.py +105 -0
- siada/io/__init__.py +0 -0
- siada/io/banner.py +148 -0
- siada/io/color_settings.py +207 -0
- siada/io/color_utils.py +16 -0
- siada/io/components/__init__.py +0 -0
- siada/io/components/mdstream.py +204 -0
- siada/io/console_printer.py +68 -0
- siada/io/io.py +661 -0
- siada/io/key_bindings.py +129 -0
- siada/io/notification_command.py +40 -0
- siada/io/test_banner.py +64 -0
- siada/models/__init__.py +0 -0
- siada/models/agent.py +47 -0
- siada/models/coder/__init__.py +0 -0
- siada/models/converter.py +27 -0
- siada/models/model_base_config.py +121 -0
- siada/models/model_run_config.py +153 -0
- siada/provider/__init__.py +0 -0
- siada/provider/client_factory.py +112 -0
- siada/provider/llm_client.py +11 -0
- siada/provider/openrouter/__init__.py +0 -0
- siada/provider/openrouter/coverter.py +17 -0
- siada/provider/openrouter/openroute_provider.py +33 -0
- siada/provider/provider_factory.py +53 -0
- siada/queries/tree-sitter-language-pack/README.md +7 -0
- siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- siada/queries/tree-sitter-languages/README.md +23 -0
- siada/queries/tree-sitter-languages/c-tags.scm +9 -0
- siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
- siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
- siada/queries/tree-sitter-languages/go-tags.scm +30 -0
- siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- siada/queries/tree-sitter-languages/java-tags.scm +20 -0
- siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-languages/php-tags.scm +26 -0
- siada/queries/tree-sitter-languages/python-tags.scm +12 -0
- siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
- siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
- siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
- siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- siada/services/__init__.py +30 -0
- siada/services/at_file_recommendation_design_document.md +1213 -0
- siada/services/code_context_manager.py +136 -0
- siada/services/enhanced_fix_result_check.py +546 -0
- siada/services/execution_trace_collector.py +207 -0
- siada/services/file_recommendation/__init__.py +136 -0
- siada/services/file_recommendation/core/__init__.py +17 -0
- siada/services/file_recommendation/core/completion_engine.py +351 -0
- siada/services/file_recommendation/core/config.py +62 -0
- siada/services/file_recommendation/core/file_discovery.py +353 -0
- siada/services/file_recommendation/core/suggestion.py +110 -0
- siada/services/file_recommendation/utils/__init__.py +22 -0
- siada/services/file_recommendation/utils/path_utils.py +138 -0
- siada/services/file_recommendation/utils/text_utils.py +253 -0
- siada/services/fix_result_check.py +377 -0
- siada/services/handle_at_command/README.md +247 -0
- siada/services/handle_at_command/__init__.py +115 -0
- siada/services/handle_at_command/exceptions.py +46 -0
- siada/services/handle_at_command/models.py +92 -0
- siada/services/handle_at_command/parser.py +189 -0
- siada/services/handle_at_command/processor.py +321 -0
- siada/services/handle_at_command/resolver.py +318 -0
- siada/services/handle_at_command/utils.py +345 -0
- siada/services/model_info_service.py +103 -0
- siada/services/siada_runner.py +170 -0
- siada/session/__init__.py +29 -0
- siada/session/session_manager.py +66 -0
- siada/session/session_models.py +43 -0
- siada/support/__init__.py +0 -0
- siada/support/completer.py +204 -0
- siada/support/editor.py +146 -0
- siada/support/envprocessor.py +54 -0
- siada/support/repo.py +51 -0
- siada/support/slash_commands.py +410 -0
- siada/support/spinner.py +320 -0
- siada/tools/__init__.py +0 -0
- siada/tools/ast/README.md +345 -0
- siada/tools/ast/ast_tool.py +341 -0
- siada/tools/ast/models.py +11 -0
- siada/tools/coder/__init__.py +0 -0
- siada/tools/coder/ask_followup_question.py +70 -0
- siada/tools/coder/cmd_runner.py +132 -0
- siada/tools/coder/file_operator.py +284 -0
- siada/tools/coder/file_search/README.md +223 -0
- siada/tools/coder/file_search/__init__.py +10 -0
- siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
- siada/tools/coder/file_search/bin/rg.exe +0 -0
- siada/tools/coder/file_search/search.py +437 -0
- siada/tools/coder/file_search/setup_package.py +150 -0
- siada/tools/coder/file_search/test_search.py +142 -0
- siada/tools/coder/files.py +147 -0
- siada/tools/coder/fix_attempt_completion.py +58 -0
- siada/tools/coder/issue_review_completion.py +29 -0
- siada/tools/coder/observation/__init__.py +0 -0
- siada/tools/coder/observation/error.py +22 -0
- siada/tools/coder/observation/file_observation.py +216 -0
- siada/tools/coder/observation/observation.py +71 -0
- siada/tools/coder/repo_map/__init__.py +157 -0
- siada/tools/coder/repo_map/dump.py +29 -0
- siada/tools/coder/repo_map/io.py +226 -0
- siada/tools/coder/repo_map/repo_map.py +881 -0
- siada/tools/coder/repo_map/special.py +203 -0
- siada/tools/coder/repo_map/token_counter.py +154 -0
- siada/tools/coder/repo_map/waiting.py +221 -0
- siada/tools/coder/reproduce_completion.py +39 -0
- siada/tools/coder/run_cmd.py +60 -0
- siada/tools/coder/test_completion.py +25 -0
- siada/tools/coder/tool_docs.py +44 -0
- siada/tools/compression_tool.py +200 -0
- siada/tools/read_many_files/README.md +220 -0
- siada/tools/read_many_files/__init__.py +25 -0
- siada/tools/read_many_files/file_processor.py +486 -0
- siada/tools/read_many_files/filters.py +326 -0
- siada/tools/read_many_files/formatters.py +204 -0
- siada/tools/read_many_files/models.py +189 -0
- siada/tools/read_many_files_tool.py +233 -0
- siada/tools/tool_call_format/__init__.py +55 -0
- siada/tools/tool_call_format/file_to_language.py +231 -0
- siada/tools/tool_call_format/formatter_factory.py +80 -0
- siada/tools/tool_call_format/formatters.py +282 -0
- siada/tools/tool_call_format/tool_call_formatter.py +54 -0
- siada/tools/web/__init__.py +0 -0
- siada/tools/web/web_crawl.py +208 -0
- siada/utils.py +194 -0
- siada_cli-0.0.1.dist-info/LICENSE +201 -0
- siada_cli-0.0.1.dist-info/METADATA +457 -0
- siada_cli-0.0.1.dist-info/RECORD +215 -0
- siada_cli-0.0.1.dist-info/WHEEL +4 -0
- siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
agent_config.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Agent Configuration File
|
|
2
|
+
# Defines the mapping from agent names to Agent classes
|
|
3
|
+
|
|
4
|
+
agents:
|
|
5
|
+
# Bug Fix Agent
|
|
6
|
+
bugfix:
|
|
7
|
+
class: "siada.agent_hub.coder.bug_fix_agent.BugFixAgent"
|
|
8
|
+
description: "Specialized agent for code bug fixing"
|
|
9
|
+
enabled: true
|
|
10
|
+
|
|
11
|
+
# Code Development Agent (not implemented yet)
|
|
12
|
+
coder:
|
|
13
|
+
class: "siada.agent_hub.coder.code_gen_agent.CodeGenAgent"
|
|
14
|
+
description: "General-purpose code development agent"
|
|
15
|
+
enabled: true
|
|
16
|
+
|
|
17
|
+
# Frontend Generation Agent
|
|
18
|
+
fegen:
|
|
19
|
+
class: "siada.agent_hub.coder.fe_gen_agent.FeGenAgent"
|
|
20
|
+
description: "Frontend code generation agent"
|
|
21
|
+
enabled: false
|
|
22
|
+
|
|
23
|
+
# LLM Configuration
|
|
24
|
+
llm_config:
|
|
25
|
+
provider: "openrouter" # Optional:“openrouter”
|
|
26
|
+
model_name: "claude-sonnet-4"
|
|
27
|
+
repo_map_tokens: 8192 # repo map max token
|
|
28
|
+
repo_map_mul_no_files: 16 # Multiple without chat files
|
|
29
|
+
repo_verbose: true # Enable detailed output or not
|
|
30
|
+
|
|
31
|
+
# Configuration Options:
|
|
32
|
+
# - class: Full import path of the Agent class, null means not implemented yet
|
|
33
|
+
# - description: Description of the agent
|
|
34
|
+
# - enabled: Whether the agent is enabled, false means temporarily disabled
|
siada/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from importlib.metadata import version
|
|
3
|
+
except ImportError:
|
|
4
|
+
# Python < 3.8 compatibility
|
|
5
|
+
from importlib_metadata import version
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("siada-cli")
|
|
9
|
+
except Exception:
|
|
10
|
+
# Fallback for development environment
|
|
11
|
+
__version__ = "dev"
|
|
12
|
+
|
|
13
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from agents import RunContextWrapper, RunConfig, RunResult, RunResultStreaming, Runner
|
|
6
|
+
|
|
7
|
+
from siada.agent_hub.coder.code_gen_agent import CodeGenAgent
|
|
8
|
+
from siada.agent_hub.coder.issue_review_agent import IssueReviewAgent
|
|
9
|
+
from siada.agent_hub.coder.prompt.bug_prompt import bug_fix_prompt
|
|
10
|
+
from siada.foundation.code_agent_context import CodeAgentContext
|
|
11
|
+
from siada.foundation.config import settings
|
|
12
|
+
from siada.foundation.tools.get_git_diff import GitDiffUtil
|
|
13
|
+
from siada.services.fix_result_check import FixResultChecker
|
|
14
|
+
from siada.services.execution_trace_collector import ExecutionTrace, ModelCall, ToolCall
|
|
15
|
+
from siada.tools.ast.ast_tool import list_code_definition_names
|
|
16
|
+
from siada.tools.coder.file_operator import edit
|
|
17
|
+
from siada.tools.coder.file_search import regex_search_files
|
|
18
|
+
from siada.tools.coder.run_cmd import run_cmd
|
|
19
|
+
from siada.tools.coder.fix_attempt_completion import fix_attempt_completion
|
|
20
|
+
from siada.services.enhanced_fix_result_check import EnhancedFixResultChecker
|
|
21
|
+
from typing import Optional, List, Dict, Any
|
|
22
|
+
from openai.types.responses import ResponseFunctionToolCall, ResponseOutputMessage
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BugFixAgent(CodeGenAgent):
|
|
26
|
+
fix_result_checker: FixResultChecker
|
|
27
|
+
enhanced_fix_result_checker: EnhancedFixResultChecker
|
|
28
|
+
issue_review_agent: IssueReviewAgent # Forward declaration for type hinting
|
|
29
|
+
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
|
|
32
|
+
self.fix_result_checker = FixResultChecker()
|
|
33
|
+
self.issue_review_agent = IssueReviewAgent()
|
|
34
|
+
self.enhanced_fix_result_checker = EnhancedFixResultChecker()
|
|
35
|
+
|
|
36
|
+
super().__init__(
|
|
37
|
+
name="BugFixAgent",
|
|
38
|
+
tools=[edit, regex_search_files, run_cmd, fix_attempt_completion, list_code_definition_names],
|
|
39
|
+
tool_use_behavior={
|
|
40
|
+
"stop_at_tool_names": ["fix_attempt_completion"],
|
|
41
|
+
},
|
|
42
|
+
*args,
|
|
43
|
+
**kwargs,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
async def get_system_prompt(
|
|
47
|
+
self, run_context: RunContextWrapper[CodeAgentContext]
|
|
48
|
+
) -> str | None:
|
|
49
|
+
root_dir = run_context.context.root_dir
|
|
50
|
+
interactive_mode = run_context.context.interactive_mode
|
|
51
|
+
system_prompt = bug_fix_prompt.get_system_prompt(root_dir, interactive_mode)
|
|
52
|
+
return system_prompt
|
|
53
|
+
|
|
54
|
+
async def get_context(self) -> CodeAgentContext:
|
|
55
|
+
current_working_dir = os.getcwd()
|
|
56
|
+
interactive_mode = self.get_interactive_mode()
|
|
57
|
+
|
|
58
|
+
context = CodeAgentContext(
|
|
59
|
+
root_dir=current_working_dir,
|
|
60
|
+
interactive_mode=interactive_mode
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 将 context 值赋给 model 对象
|
|
64
|
+
if hasattr(self, "model") and hasattr(self.model, "context"):
|
|
65
|
+
self.model.context = context
|
|
66
|
+
|
|
67
|
+
return context
|
|
68
|
+
|
|
69
|
+
async def run(self, user_input: str, context: CodeAgentContext) -> RunResult:
|
|
70
|
+
"""
|
|
71
|
+
Execute bug fixing task.
|
|
72
|
+
Use reproduce_agent to reproduce the issue, then use current Agent to fix it.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
user_input: User-described bug problem, including error messages, related file paths, etc.
|
|
76
|
+
context: Context object for providing contextual information
|
|
77
|
+
Returns:
|
|
78
|
+
Fix result, including final output, execution rounds, and other information
|
|
79
|
+
"""
|
|
80
|
+
#config = RunConfig(tracing_disabled=False)
|
|
81
|
+
#set_trace_processors([create_detailed_logger()])
|
|
82
|
+
input_with_env = self.assemble_user_input(user_input, context)
|
|
83
|
+
|
|
84
|
+
max_turns = 3
|
|
85
|
+
current_turn = 0
|
|
86
|
+
task_message = {"content": input_with_env, "role": "user"}
|
|
87
|
+
input_list = [task_message]
|
|
88
|
+
|
|
89
|
+
run_config, _ = await self.prepare_run_environment(context)
|
|
90
|
+
|
|
91
|
+
while current_turn < max_turns:
|
|
92
|
+
# Run BugFixAgent for fixing
|
|
93
|
+
result = await Runner.run(
|
|
94
|
+
starting_agent=self,
|
|
95
|
+
input=input_list,
|
|
96
|
+
max_turns=settings.MAX_TURNS,
|
|
97
|
+
run_config=run_config,
|
|
98
|
+
context=context
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# input_list = result.to_input_list()
|
|
102
|
+
|
|
103
|
+
# Check if the issue is fixed using run_checker
|
|
104
|
+
try:
|
|
105
|
+
check_result = await self.run_checker(user_input, context)
|
|
106
|
+
enhance_check_result = await self.run_enhanced_checker(
|
|
107
|
+
user_input, context, result
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if check_result.get("is_fixed", False):
|
|
111
|
+
# Issue is fixed, break the loop
|
|
112
|
+
print(
|
|
113
|
+
f"Fix_check_result, Issue fixed: {check_result.get('check_summary', 'Fix verified')}"
|
|
114
|
+
)
|
|
115
|
+
break
|
|
116
|
+
else:
|
|
117
|
+
# Issue not fixed, add the check_summary to input_list for next iteration
|
|
118
|
+
check_summary = check_result.get(
|
|
119
|
+
"check_summary", "Fix verification failed"
|
|
120
|
+
)
|
|
121
|
+
print(
|
|
122
|
+
f"Fix_check_result, Issue not fixed, continue fixing (round {current_turn + 1}): {check_summary}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Add the unfixed check_summary to input_list for next round
|
|
126
|
+
feedback_message = {
|
|
127
|
+
"content": self._build_enhanced_feedback(
|
|
128
|
+
current_turn,
|
|
129
|
+
result,
|
|
130
|
+
check_result,
|
|
131
|
+
check_summary,
|
|
132
|
+
enhance_check_result,
|
|
133
|
+
),
|
|
134
|
+
"role": "user",
|
|
135
|
+
}
|
|
136
|
+
# feedback_message = {
|
|
137
|
+
# "content": f"Here is the previous fix logic:\n{result.final_output}"
|
|
138
|
+
# f"Here is the current code diff:\n{check_result.get('code_diff', '')}"
|
|
139
|
+
# f"But previous fix attempt was not sufficient. Reason: {check_summary}.\n"
|
|
140
|
+
# f"**Please continue fixing.**",
|
|
141
|
+
# "role": "user",
|
|
142
|
+
# }
|
|
143
|
+
input_list = [task_message, feedback_message]
|
|
144
|
+
except Exception as e:
|
|
145
|
+
# If checker fails, log error and continue to next round
|
|
146
|
+
print(f"Fix result checker failed: {e}, stopping verification.")
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
current_turn += 1
|
|
150
|
+
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
async def run_checker_by_agent(self, user_input: str, context: CodeAgentContext):
|
|
154
|
+
|
|
155
|
+
result = await self.issue_review_agent.run(user_input, context)
|
|
156
|
+
|
|
157
|
+
output = ast.literal_eval(result.final_output)
|
|
158
|
+
return output
|
|
159
|
+
|
|
160
|
+
async def run_checker(self, user_input: str, context: CodeAgentContext) -> dict:
|
|
161
|
+
|
|
162
|
+
diff_patch = GitDiffUtil.get_git_diff_exclude_test_files(context.root_dir)
|
|
163
|
+
|
|
164
|
+
check_result = await self.fix_result_checker.check(
|
|
165
|
+
issue_desc=user_input,
|
|
166
|
+
fix_code=diff_patch,
|
|
167
|
+
context=context
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
check_result["code_diff"] = diff_patch
|
|
171
|
+
return check_result
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async def run_enhanced_checker(
|
|
175
|
+
self,
|
|
176
|
+
user_input: str,
|
|
177
|
+
context: CodeAgentContext,
|
|
178
|
+
run_result: Optional[RunResult] = None,
|
|
179
|
+
) -> dict:
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
user_input: problem description provided by the user
|
|
184
|
+
context: to get the root directory and other context informatio
|
|
185
|
+
run_result: for extracting execution trace if available
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
the enhanced check result, which includes:
|
|
189
|
+
- check_summary: Summary of the fix result check
|
|
190
|
+
- execution_stats: Statistics about model and tool calls during the fix
|
|
191
|
+
- strategy_suggestions: Suggestions for improving the fixing strategy
|
|
192
|
+
- trace_analysis: Analysis of the execution trace if available
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
diff_patch = GitDiffUtil.get_git_diff_exclude_test_files(context.root_dir)
|
|
196
|
+
execution_trace = None
|
|
197
|
+
if run_result:
|
|
198
|
+
try:
|
|
199
|
+
execution_trace = self._extract_execution_trace_from_run_result(run_result)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"Failed to extract execution trace: {e}")
|
|
202
|
+
execution_trace = None
|
|
203
|
+
|
|
204
|
+
enhanced_result = await self.enhanced_fix_result_checker.check_with_trace(
|
|
205
|
+
issue_desc=user_input,
|
|
206
|
+
fix_code=diff_patch,
|
|
207
|
+
context=context,
|
|
208
|
+
execution_trace=execution_trace,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
enhanced_result["code_diff"] = diff_patch
|
|
212
|
+
return enhanced_result
|
|
213
|
+
|
|
214
|
+
def run_streamed(
|
|
215
|
+
self, user_input: str, context: CodeAgentContext
|
|
216
|
+
) -> RunResultStreaming:
|
|
217
|
+
"""
|
|
218
|
+
Execute bug fixing task in streaming mode
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
user_input: User-described bug problem, including error messages, related file paths, etc.
|
|
222
|
+
context: Context object for providing contextual information
|
|
223
|
+
Returns:
|
|
224
|
+
Fix result, including final output, execution rounds, and other information
|
|
225
|
+
"""
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
def _extract_execution_trace_from_run_result(
|
|
229
|
+
self, run_result: RunResult
|
|
230
|
+
) -> Optional[Dict[str, Any]]:
|
|
231
|
+
"""
|
|
232
|
+
Extract execution trace from RunResult if available
|
|
233
|
+
"""
|
|
234
|
+
model_calls = []
|
|
235
|
+
tool_calls = []
|
|
236
|
+
|
|
237
|
+
if hasattr(run_result, "new_items"):
|
|
238
|
+
new_items = run_result.new_items
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError("RunResult does not have 'new_items' attribute")
|
|
241
|
+
|
|
242
|
+
for i in range(len(new_items)):
|
|
243
|
+
item = new_items[i]
|
|
244
|
+
|
|
245
|
+
if hasattr(item, "type"):
|
|
246
|
+
item_type = item.type
|
|
247
|
+
else:
|
|
248
|
+
raise ValueError("Item does not have 'type' attribute")
|
|
249
|
+
|
|
250
|
+
if item_type == "message_output_item":
|
|
251
|
+
raw_item = item.raw_item
|
|
252
|
+
model_calls.append(
|
|
253
|
+
self._extract_model_call_from_item(len(model_calls) + 1, raw_item)
|
|
254
|
+
)
|
|
255
|
+
elif item_type == "tool_call_output_item":
|
|
256
|
+
tool_call_output_raw_item = item.raw_item
|
|
257
|
+
if i > 0 and new_items[i - 1].type == "tool_call_item":
|
|
258
|
+
tool_calls_raw_item = new_items[i - 1].raw_item
|
|
259
|
+
else:
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"ToolCallOutputItem does not have a preceding ToolCallItem"
|
|
262
|
+
)
|
|
263
|
+
tool_calls.append(
|
|
264
|
+
self._extract_tool_call_from_item(
|
|
265
|
+
len(tool_calls) + 1,
|
|
266
|
+
tool_call_output_raw_item,
|
|
267
|
+
tool_calls_raw_item,
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return ExecutionTrace(
|
|
272
|
+
trace_id=f"trace_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
|
273
|
+
workflow_name="bug_fix",
|
|
274
|
+
start_time=datetime.now(),
|
|
275
|
+
model_calls=model_calls,
|
|
276
|
+
tool_calls=tool_calls,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def _extract_model_call_from_item(
|
|
280
|
+
self, call_id: int, raw_item: ResponseOutputMessage
|
|
281
|
+
) -> ModelCall:
|
|
282
|
+
model_call = ModelCall(
|
|
283
|
+
call_id=call_id,
|
|
284
|
+
model=None,
|
|
285
|
+
input_messages="",
|
|
286
|
+
output_messages=[{"role": "assistant", "content": str(raw_item.content)}],
|
|
287
|
+
usage=None,
|
|
288
|
+
timestamp=datetime.now(),
|
|
289
|
+
duration_ms=0
|
|
290
|
+
)
|
|
291
|
+
return model_call
|
|
292
|
+
|
|
293
|
+
def _extract_tool_call_from_item(
|
|
294
|
+
self,
|
|
295
|
+
call_id: int,
|
|
296
|
+
tool_call_output_raw_item: dict,
|
|
297
|
+
tool_call_raw_item: ResponseFunctionToolCall,
|
|
298
|
+
) -> ModelCall:
|
|
299
|
+
tool_call = ToolCall(
|
|
300
|
+
call_id=call_id,
|
|
301
|
+
tool_name="",
|
|
302
|
+
timestamp=datetime.now(),
|
|
303
|
+
duration_ms=0,
|
|
304
|
+
success=True,
|
|
305
|
+
error_message=None,
|
|
306
|
+
input_args=tool_call_raw_item.arguments,
|
|
307
|
+
output_result=(
|
|
308
|
+
tool_call_output_raw_item.get("output", None)
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return tool_call
|
|
313
|
+
|
|
314
|
+
def _build_enhanced_feedback(
|
|
315
|
+
self,
|
|
316
|
+
current_turn: int,
|
|
317
|
+
result: dict,
|
|
318
|
+
check_result: dict,
|
|
319
|
+
check_summary: str,
|
|
320
|
+
enhanced_check_result: dict,
|
|
321
|
+
) -> str:
|
|
322
|
+
"""
|
|
323
|
+
Build enhanced feedback message for the next round of fixing
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
current_turn: Current turn number
|
|
327
|
+
result: Current run result
|
|
328
|
+
check_result: Result from the fix result checker
|
|
329
|
+
check_summary: Summary of the fix result check
|
|
330
|
+
enhanced_check_result: Result from the enhanced fix result checker
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Formatted feedback message for the next round
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
enhanced_feedback_content = f"""## Previous Fix Attempt (Round {current_turn + 1})
|
|
337
|
+
**Fix Logic:**
|
|
338
|
+
{result.final_output}
|
|
339
|
+
|
|
340
|
+
**Current Code Diff:**
|
|
341
|
+
{check_result.get('code_diff', 'No changes detected')}
|
|
342
|
+
|
|
343
|
+
## Primary Check Result
|
|
344
|
+
**Fix Status:** ❌ Not Fixed
|
|
345
|
+
**Primary Analysis:** {check_summary}
|
|
346
|
+
|
|
347
|
+
## Enhanced Analysis"""
|
|
348
|
+
|
|
349
|
+
overall_score = enhanced_check_result.get("overall_score")
|
|
350
|
+
if overall_score is not None:
|
|
351
|
+
enhanced_feedback_content += f"\n**Quality Score:** {overall_score:.1f}/10"
|
|
352
|
+
|
|
353
|
+
enhanced_summary = enhanced_check_result.get("check_summary")
|
|
354
|
+
if enhanced_summary:
|
|
355
|
+
enhanced_feedback_content += f"\n**Enhanced Summary:** {enhanced_summary}"
|
|
356
|
+
|
|
357
|
+
execution_stats = enhanced_check_result.get("execution_stats")
|
|
358
|
+
if execution_stats:
|
|
359
|
+
enhanced_feedback_content += f"""
|
|
360
|
+
|
|
361
|
+
**Execution Statistics:**
|
|
362
|
+
- Model calls: {execution_stats.get('model_calls', 0)}
|
|
363
|
+
- Tool calls: {execution_stats.get('tool_calls', 0)}"""
|
|
364
|
+
|
|
365
|
+
strategy_suggestions = self._extract_strategy_suggestions(enhanced_check_result)
|
|
366
|
+
if strategy_suggestions:
|
|
367
|
+
enhanced_feedback_content += "\n\n**Strategy Improvement Suggestions:"
|
|
368
|
+
for i, suggestion in enumerate(strategy_suggestions[:3], 1):
|
|
369
|
+
enhanced_feedback_content += f"\n{i}. {suggestion}"
|
|
370
|
+
|
|
371
|
+
trace_analysis = enhanced_check_result.get("trace_analysis")
|
|
372
|
+
if trace_analysis:
|
|
373
|
+
enhanced_feedback_content += (
|
|
374
|
+
f"\n\n**Execution Trace Analysis:**\n{trace_analysis}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
enhanced_feedback_content += f"""
|
|
378
|
+
|
|
379
|
+
## Next Steps
|
|
380
|
+
The previous fix attempt was not sufficient. Please analyze the above feedback and continue fixing the issue.
|
|
381
|
+
**Primary Focus:** {check_summary}"""
|
|
382
|
+
|
|
383
|
+
fix_analysis = enhanced_check_result.get("fix_analysis")
|
|
384
|
+
if fix_analysis:
|
|
385
|
+
enhanced_feedback_content += f"\n**Additional Focus:** {fix_analysis}"
|
|
386
|
+
|
|
387
|
+
return enhanced_feedback_content
|
|
388
|
+
|
|
389
|
+
def _extract_strategy_suggestions(self, enhanced_check_result: dict) -> List[str]:
|
|
390
|
+
prof_rec = enhanced_check_result.get("professional_recommendations", {})
|
|
391
|
+
strategic_improvements = prof_rec.get("strategic_improvements", [])
|
|
392
|
+
|
|
393
|
+
if not strategic_improvements:
|
|
394
|
+
strategic_improvements = enhanced_check_result.get(
|
|
395
|
+
"strategy_suggestions", []
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
return strategic_improvements
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Generation Agent Module
|
|
3
|
+
|
|
4
|
+
Provides specialized Agent implementation for code generation tasks.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from agents import RunContextWrapper, RunResult, RunResultStreaming
|
|
9
|
+
from siada.foundation.code_agent_context import CodeAgentContext
|
|
10
|
+
from siada.agent_hub.siada_agent import SiadaAgent
|
|
11
|
+
from siada.tools.ast.ast_tool import list_code_definition_names
|
|
12
|
+
from siada.tools.coder.file_operator import edit
|
|
13
|
+
from siada.tools.coder.file_search import regex_search_files
|
|
14
|
+
from siada.tools.coder.run_cmd import run_cmd
|
|
15
|
+
from siada.foundation.config import settings
|
|
16
|
+
from siada.agent_hub.coder.prompt import code_gen_prompt
|
|
17
|
+
from siada.services.handle_at_command import handle_at_command
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logging.getLogger().setLevel(logging.INFO)
|
|
22
|
+
|
|
23
|
+
class CodeGenAgent(SiadaAgent[CodeAgentContext]):
|
|
24
|
+
"""
|
|
25
|
+
Code Generation Agent
|
|
26
|
+
|
|
27
|
+
Specialized Agent implementation for code generation tasks.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
|
|
32
|
+
if 'name' not in kwargs:
|
|
33
|
+
kwargs['name'] = "CodeGenAgent"
|
|
34
|
+
|
|
35
|
+
if 'tools' not in kwargs:
|
|
36
|
+
kwargs['tools'] = [edit, regex_search_files, run_cmd, list_code_definition_names]
|
|
37
|
+
|
|
38
|
+
super().__init__(
|
|
39
|
+
*args,
|
|
40
|
+
**kwargs
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async def get_system_prompt(self, run_context: RunContextWrapper[CodeAgentContext]) -> str | None:
|
|
44
|
+
root_dir = run_context.context.root_dir
|
|
45
|
+
interactive_mode = run_context.context.interactive_mode
|
|
46
|
+
system_prompt = code_gen_prompt.get_system_prompt(root_dir, interactive_mode)
|
|
47
|
+
return system_prompt
|
|
48
|
+
|
|
49
|
+
async def get_context(self) -> CodeAgentContext:
|
|
50
|
+
current_working_dir = os.getcwd()
|
|
51
|
+
interactive_mode = self.get_interactive_mode()
|
|
52
|
+
|
|
53
|
+
context = CodeAgentContext(
|
|
54
|
+
root_dir=current_working_dir,
|
|
55
|
+
interactive_mode=interactive_mode
|
|
56
|
+
)
|
|
57
|
+
return context
|
|
58
|
+
|
|
59
|
+
async def process_at_commands(self, user_input: str, context: CodeAgentContext) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Process @ commands in user input and return processed input
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
user_input: Original user input that may contain @ commands
|
|
65
|
+
context: Code agent context
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Processed user input with @ command content injected
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
# Check if input contains @ commands
|
|
72
|
+
if '@' not in user_input:
|
|
73
|
+
return user_input
|
|
74
|
+
|
|
75
|
+
# Create configuration object for at command processing
|
|
76
|
+
class AtCommandConfig:
|
|
77
|
+
def __init__(self, root_dir: str):
|
|
78
|
+
self.root_dir = root_dir
|
|
79
|
+
|
|
80
|
+
config = AtCommandConfig(context.root_dir)
|
|
81
|
+
|
|
82
|
+
# Create callback functions
|
|
83
|
+
def add_item(item, message_id):
|
|
84
|
+
# Log the item for debugging
|
|
85
|
+
logging.debug(f"AtCommand item added: {item}")
|
|
86
|
+
|
|
87
|
+
def on_debug_message(message):
|
|
88
|
+
# Log debug messages
|
|
89
|
+
logging.debug(f"AtCommand debug: {message}")
|
|
90
|
+
|
|
91
|
+
# Process at commands
|
|
92
|
+
result = await handle_at_command(
|
|
93
|
+
query=user_input,
|
|
94
|
+
config=config,
|
|
95
|
+
add_item=add_item,
|
|
96
|
+
on_debug_message=on_debug_message,
|
|
97
|
+
message_id=1
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if result.should_proceed and result.processed_query:
|
|
101
|
+
# Combine all text parts from processed query
|
|
102
|
+
processed_text = ""
|
|
103
|
+
for part in result.processed_query:
|
|
104
|
+
if isinstance(part, dict) and 'text' in part:
|
|
105
|
+
processed_text += part['text']
|
|
106
|
+
|
|
107
|
+
return processed_text.strip() if processed_text else user_input
|
|
108
|
+
else:
|
|
109
|
+
# If processing failed, return original input
|
|
110
|
+
return user_input
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
# If any error occurs, log it and return original input
|
|
114
|
+
logging.warning(f"Failed to process @ commands: {e}")
|
|
115
|
+
return user_input
|
|
116
|
+
|
|
117
|
+
async def run(self, user_input: str, context: CodeAgentContext) -> RunResult:
|
|
118
|
+
"""
|
|
119
|
+
Execute code generation task.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
user_input: User's code generation request with requirements and specifications
|
|
123
|
+
context: Context object providing project information
|
|
124
|
+
Returns:
|
|
125
|
+
Generation result containing final output and execution details
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
# Process @ commands first
|
|
129
|
+
processed_input = await self.process_at_commands(user_input, context)
|
|
130
|
+
|
|
131
|
+
input_with_env = self.assemble_user_input(processed_input, context)
|
|
132
|
+
result = await self.run_impl(
|
|
133
|
+
starting_agent=self,
|
|
134
|
+
input=input_with_env,
|
|
135
|
+
max_turns=settings.MAX_TURNS,
|
|
136
|
+
context=context,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
async def run_streamed(
|
|
142
|
+
self, user_input: str, context: CodeAgentContext
|
|
143
|
+
) -> RunResultStreaming:
|
|
144
|
+
"""
|
|
145
|
+
Execute code generation task with streaming output.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
user_input: User's code generation request with requirements and specifications
|
|
149
|
+
context: Context object providing project information
|
|
150
|
+
Returns:
|
|
151
|
+
A streaming result of the generation, containing final output and execution details.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
# Process @ commands first
|
|
155
|
+
processed_input = await self.process_at_commands(user_input, context)
|
|
156
|
+
|
|
157
|
+
input_with_env = self.assemble_user_input(processed_input, context)
|
|
158
|
+
result = await self.run_streamed_impl(
|
|
159
|
+
starting_agent=self,
|
|
160
|
+
input=input_with_env,
|
|
161
|
+
context=context,
|
|
162
|
+
max_turns=settings.MAX_TURNS,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
def assemble_user_input(self, user_input: str, context: CodeAgentContext) -> str:
|
|
168
|
+
task = f'<task>\n{user_input}\n</task>'
|
|
169
|
+
return task
|
|
170
|
+
# repo_map_content = self.generate_repo_map(context)
|
|
171
|
+
|
|
172
|
+
# if repo_map_content:
|
|
173
|
+
# project_structure = f"Repository Map:\n{repo_map_content}"
|
|
174
|
+
# else:
|
|
175
|
+
# project_structure = "Repository Map: Unable to generate repository map"
|
|
176
|
+
|
|
177
|
+
# environment_details = f'<environment_details>\n{project_structure}\n</environment_details>'
|
|
178
|
+
# return task + '\n' + environment_details
|
|
179
|
+
|
|
180
|
+
def generate_repo_map(self, context: CodeAgentContext) -> str:
|
|
181
|
+
"""
|
|
182
|
+
Generate repository map for project structure analysis.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
context: Code agent context containing project information
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Repository map content as string
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
if not context.root_dir:
|
|
192
|
+
return ""
|
|
193
|
+
|
|
194
|
+
repo_map = self.get_repo_map_instance(context.root_dir)
|
|
195
|
+
if not repo_map:
|
|
196
|
+
return ""
|
|
197
|
+
|
|
198
|
+
python_files = []
|
|
199
|
+
for root, dirs, files in os.walk(context.root_dir):
|
|
200
|
+
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in [
|
|
201
|
+
'__pycache__', 'node_modules', '.git', '.venv', 'venv', 'env'
|
|
202
|
+
]]
|
|
203
|
+
|
|
204
|
+
for file in files:
|
|
205
|
+
if file.endswith('.py') and not file.startswith('.'):
|
|
206
|
+
filepath = os.path.join(root, file)
|
|
207
|
+
python_files.append(filepath)
|
|
208
|
+
|
|
209
|
+
substantial_files = []
|
|
210
|
+
for filepath in python_files:
|
|
211
|
+
try:
|
|
212
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
213
|
+
content = f.read().strip()
|
|
214
|
+
if len(content) > 100:
|
|
215
|
+
lines = [line.strip() for line in content.split('\n') if line.strip()]
|
|
216
|
+
non_comment_lines = [line for line in lines if not line.startswith('#')]
|
|
217
|
+
if len(non_comment_lines) > 5:
|
|
218
|
+
substantial_files.append(filepath)
|
|
219
|
+
except Exception:
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
if len(substantial_files) > 50:
|
|
223
|
+
substantial_files = substantial_files[:50]
|
|
224
|
+
|
|
225
|
+
result = repo_map.get_repo_map(
|
|
226
|
+
chat_files=[],
|
|
227
|
+
other_files=substantial_files,
|
|
228
|
+
mentioned_fnames=set(),
|
|
229
|
+
mentioned_idents=set(['class', 'def', 'function'])
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return result or ""
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
return f"Generate repo map failed: {str(e)}"
|