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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
|
|
5
|
+
from siada.support.slash_commands import SwitchEvent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TurnType(Enum):
|
|
9
|
+
"""Types of interaction turns"""
|
|
10
|
+
|
|
11
|
+
COMMAND = "command" # Slash commands (/help, /edit, etc.)
|
|
12
|
+
CONVERSATION = "conversation" # Regular AI conversation
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class TurnInput:
|
|
17
|
+
"""Input data for a turn"""
|
|
18
|
+
|
|
19
|
+
use_input: str # Raw user input
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class TurnOutput:
|
|
24
|
+
"""Output data from a turn"""
|
|
25
|
+
|
|
26
|
+
output: str | SwitchEvent # Response content
|
|
27
|
+
metadata: Dict[str, Any] # Response metadata
|
|
28
|
+
next_action: Optional[str] # Suggested next action
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import fields
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
from prompt_toolkit.completion import Completer
|
|
9
|
+
|
|
10
|
+
from siada.config.config_loader import Config, load_conf
|
|
11
|
+
from siada.entrypoint.args_parser.args import get_parser
|
|
12
|
+
from siada.entrypoint.interaction.config import RunningConfig
|
|
13
|
+
from siada.entrypoint.interaction.controller import Controller
|
|
14
|
+
from siada.entrypoint.interaction.nointeractive_controller import NoInteractiveController
|
|
15
|
+
from siada.foundation.logging import toggle_console_output, logger
|
|
16
|
+
from siada.io.color_settings import RunningConfigColorSettings
|
|
17
|
+
from siada.models.model_run_config import ModelRunConfig
|
|
18
|
+
from siada.models.model_base_config import ModelBaseConfig
|
|
19
|
+
from siada.support.completer import AutoCompleter
|
|
20
|
+
from siada.support.slash_commands import SlashCommands, SwitchEvent
|
|
21
|
+
from siada.support.envprocessor import load_dotenv_files
|
|
22
|
+
from siada.support.repo import get_git_root
|
|
23
|
+
from siada.utils import SettingsUtils
|
|
24
|
+
from siada.io.io import InputOutput
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import git
|
|
28
|
+
except ImportError:
|
|
29
|
+
git = None
|
|
30
|
+
|
|
31
|
+
import shtab
|
|
32
|
+
from dotenv import load_dotenv
|
|
33
|
+
from prompt_toolkit.enums import EditingMode
|
|
34
|
+
from siada.services.model_info_service import ModelInfoService
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _suppress_third_party_warnings():
|
|
38
|
+
"""Suppress harmless warnings from third-party libraries"""
|
|
39
|
+
# Suppress pydub ffmpeg/avconv warning - not relevant for Siada as we don't use audio features
|
|
40
|
+
warnings.filterwarnings(
|
|
41
|
+
"ignore",
|
|
42
|
+
message="Couldn't find ffmpeg or avconv.*",
|
|
43
|
+
category=RuntimeWarning
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Suppress all SyntaxWarning from pydub - use message pattern to catch invalid escape sequences
|
|
47
|
+
warnings.filterwarnings(
|
|
48
|
+
"ignore",
|
|
49
|
+
message="invalid escape sequence.*",
|
|
50
|
+
category=SyntaxWarning
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _configure_litellm_logging():
|
|
55
|
+
"""Configure LiteLLM global logging settings to suppress debug logs"""
|
|
56
|
+
try:
|
|
57
|
+
import litellm
|
|
58
|
+
|
|
59
|
+
# Configure litellm global properties
|
|
60
|
+
litellm.set_verbose = False
|
|
61
|
+
litellm.turn_off_message_logging = True
|
|
62
|
+
litellm.suppress_debug_info = True
|
|
63
|
+
litellm.drop_params = True
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Try to disable internal debug logging
|
|
67
|
+
try:
|
|
68
|
+
litellm._logging._disable_debugging()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass # Ignore if method doesn't exist
|
|
71
|
+
|
|
72
|
+
# Disable message logging and tracing
|
|
73
|
+
litellm.turn_off_message_logging = True
|
|
74
|
+
litellm.success_callback = []
|
|
75
|
+
litellm.failure_callback = []
|
|
76
|
+
|
|
77
|
+
logger.debug("LiteLLM logging configuration completed")
|
|
78
|
+
|
|
79
|
+
except ImportError:
|
|
80
|
+
logger.debug("LiteLLM not installed, skipping logging configuration")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.debug(f"Error configuring LiteLLM logging: {e}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _parse_args_and_setup_environment(argv):
|
|
86
|
+
"""
|
|
87
|
+
Parse command line arguments and set up environment
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
argv: Command line argument list
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
tuple: (args, unknown, loaded_dotenvs, git_root, workspace, parser) parsed arguments, unknown arguments, loaded environment variable files, git root directory, workspace path and parser
|
|
94
|
+
"""
|
|
95
|
+
# workspace is specific for development and needs to be parsed early
|
|
96
|
+
import argparse
|
|
97
|
+
|
|
98
|
+
temp_parser = argparse.ArgumentParser(add_help=False)
|
|
99
|
+
temp_parser.add_argument("--workspace", default=None)
|
|
100
|
+
temp_args, _ = temp_parser.parse_known_args(argv)
|
|
101
|
+
|
|
102
|
+
# Now get git root from the specified workspace or current directory
|
|
103
|
+
if git is None:
|
|
104
|
+
git_root = None
|
|
105
|
+
else:
|
|
106
|
+
git_root = get_git_root(temp_args.workspace)
|
|
107
|
+
|
|
108
|
+
parser = get_parser(git_root=git_root, default_config_files=[])
|
|
109
|
+
try:
|
|
110
|
+
args, unknown = parser.parse_known_args(argv)
|
|
111
|
+
except AttributeError as e:
|
|
112
|
+
raise e
|
|
113
|
+
|
|
114
|
+
# Configure console output based on parsed arguments
|
|
115
|
+
if hasattr(args, 'disable_console_output') and args.disable_console_output:
|
|
116
|
+
toggle_console_output(False)
|
|
117
|
+
else:
|
|
118
|
+
toggle_console_output(True)
|
|
119
|
+
|
|
120
|
+
loaded_dotenvs = load_dotenv_files(git_root, args.env_file, args.encoding)
|
|
121
|
+
|
|
122
|
+
if args.verbose:
|
|
123
|
+
for fname in loaded_dotenvs:
|
|
124
|
+
logger.info(f"Loaded {fname}")
|
|
125
|
+
|
|
126
|
+
return args, unknown, loaded_dotenvs, git_root, temp_args.workspace, parser
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_io(args, pretty=None):
|
|
130
|
+
"""
|
|
131
|
+
Create InputOutput instance with complete IO configuration
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
args: Parsed command line arguments
|
|
135
|
+
pretty: Whether to enable pretty mode, defaults to args.pretty
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
InputOutput: Configured IO instance
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: When theme configuration is invalid
|
|
142
|
+
"""
|
|
143
|
+
from siada.io.color_settings import ColorSettings
|
|
144
|
+
|
|
145
|
+
# Configure color settings
|
|
146
|
+
color_settings = ColorSettings.from_theme(args.theme)
|
|
147
|
+
running_color_settings = RunningConfigColorSettings(color_settings=color_settings, pretty=args.pretty)
|
|
148
|
+
color_settings.apply_to_args(args)
|
|
149
|
+
if args.verbose:
|
|
150
|
+
print(f"Applied color theme: {args.theme}")
|
|
151
|
+
|
|
152
|
+
# Configure editing mode
|
|
153
|
+
editing_mode = EditingMode.VI if args.vim else EditingMode.EMACS
|
|
154
|
+
|
|
155
|
+
return InputOutput(
|
|
156
|
+
pretty=args.pretty,
|
|
157
|
+
running_color_settings=running_color_settings,
|
|
158
|
+
encoding=args.encoding,
|
|
159
|
+
line_endings=getattr(args, "line_endings", "platform"),
|
|
160
|
+
editingmode=editing_mode,
|
|
161
|
+
fancy_input=args.fancy_input,
|
|
162
|
+
multiline_mode=False,
|
|
163
|
+
notifications=True,
|
|
164
|
+
), running_color_settings
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def set_env(args, io):
|
|
168
|
+
"""
|
|
169
|
+
Set environment variables, including general environment variables and API keys
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
args: Parsed command line arguments
|
|
173
|
+
io: InputOutput instance for printing error messages
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
int: 0 for success, 1 for error
|
|
177
|
+
"""
|
|
178
|
+
# Set general environment variables
|
|
179
|
+
if args.set_env:
|
|
180
|
+
for env_setting in args.set_env:
|
|
181
|
+
try:
|
|
182
|
+
name, value = env_setting.split("=", 1)
|
|
183
|
+
os.environ[name.strip()] = value.strip()
|
|
184
|
+
except ValueError:
|
|
185
|
+
io.print_error(f"Invalid --set-env format: {env_setting}")
|
|
186
|
+
io.print_info("Format should be: ENV_VAR_NAME=value")
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_workspace(workspace_arg, git_root):
|
|
193
|
+
"""
|
|
194
|
+
Get and set workspace directory
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
workspace_arg: User-specified workspace path
|
|
198
|
+
git_root: Git root directory path
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
str: Workspace path
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
SystemExit: When workspace directory does not exist or is not a directory
|
|
205
|
+
"""
|
|
206
|
+
# Set workspace - prioritize user-specified workspace, then git root, then current directory
|
|
207
|
+
if workspace_arg:
|
|
208
|
+
workspace = os.path.abspath(workspace_arg)
|
|
209
|
+
# Ensure the workspace directory exists
|
|
210
|
+
if not os.path.exists(workspace):
|
|
211
|
+
logger.error(f"Workspace directory does not exist: {workspace}")
|
|
212
|
+
sys.exit(1)
|
|
213
|
+
if not os.path.isdir(workspace):
|
|
214
|
+
logger.error(f"Workspace path is not a directory: {workspace}")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
# Change to the specified workspace directory
|
|
217
|
+
os.chdir(workspace)
|
|
218
|
+
logger.debug(f"Changed to workspace directory: {workspace}")
|
|
219
|
+
else:
|
|
220
|
+
workspace = git_root if git_root else os.getcwd()
|
|
221
|
+
logger.debug(f"Using default workspace: {workspace}")
|
|
222
|
+
|
|
223
|
+
return workspace
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def show_banner(io):
|
|
227
|
+
"""
|
|
228
|
+
Display SIADA HUB banner with error handling
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
io: InputOutput instance
|
|
232
|
+
|
|
233
|
+
Raises:
|
|
234
|
+
Exception: When banner display fails
|
|
235
|
+
"""
|
|
236
|
+
# Show SIADA HUB banner with gradient effect
|
|
237
|
+
from siada.io.banner import show_siada_banner
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
io.rule()
|
|
241
|
+
show_siada_banner(pretty=io.pretty, console=io.console)
|
|
242
|
+
except UnicodeEncodeError as err:
|
|
243
|
+
io.print_error("Terminal does not support pretty output (UnicodeDecodeError)")
|
|
244
|
+
sys.exit(1)
|
|
245
|
+
except Exception as err:
|
|
246
|
+
io.print_error(f"Error showing banner: {err}")
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def get_config(args, io, conf: Config = None):
|
|
251
|
+
"""
|
|
252
|
+
Configure and create model instance
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
args: Parsed command line arguments
|
|
256
|
+
io: InputOutput instance for displaying information
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
ModelRunConfig: Configured model instance, returns None if exit is needed
|
|
260
|
+
"""
|
|
261
|
+
# Configuration priority: args > config file > defaults
|
|
262
|
+
config = ModelRunConfig.get_default_config()
|
|
263
|
+
|
|
264
|
+
# Determine final values using priority order
|
|
265
|
+
final_model = args.model or (conf.llm_config.model if conf and conf.llm_config else None)
|
|
266
|
+
final_provider = args.provider or (conf.llm_config.provider if conf and conf.llm_config else None)
|
|
267
|
+
|
|
268
|
+
# Apply final configuration
|
|
269
|
+
if final_model is not None:
|
|
270
|
+
config.model_name = final_model
|
|
271
|
+
config.configure_model_settings(config.model_name)
|
|
272
|
+
|
|
273
|
+
if final_provider is not None:
|
|
274
|
+
config.provider = final_provider
|
|
275
|
+
|
|
276
|
+
# Check if provider is set
|
|
277
|
+
if config.provider is None:
|
|
278
|
+
io.print_error("No provider specified. Please set provider in agent_config.yaml or use --provider option")
|
|
279
|
+
sys.exit(1)
|
|
280
|
+
|
|
281
|
+
if config.provider == "openrouter":
|
|
282
|
+
## check the openrouter api key is set
|
|
283
|
+
if os.getenv("OPENROUTER_API_KEY") is None:
|
|
284
|
+
io.print_error("OPENROUTER_API_KEY is not set for openrouter provider")
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
|
|
287
|
+
# Set reasoning effort and thinking tokens if specified
|
|
288
|
+
if args.reasoning_effort is not None:
|
|
289
|
+
if (
|
|
290
|
+
not config.supports_extra_params
|
|
291
|
+
or "reasoning_effort" not in config.supports_extra_params
|
|
292
|
+
):
|
|
293
|
+
io.print_error(f"Model {config.model_name} does not support reasoning effort")
|
|
294
|
+
sys.exit(1)
|
|
295
|
+
else:
|
|
296
|
+
config.set_reasoning_effort(args.reasoning_effort)
|
|
297
|
+
|
|
298
|
+
if args.thinking_tokens is not None:
|
|
299
|
+
if (
|
|
300
|
+
not config.supports_extra_params
|
|
301
|
+
or "thinking_tokens" not in config.supports_extra_params
|
|
302
|
+
):
|
|
303
|
+
io.print_error(f"Model {config.model_name} does not support thinking tokens")
|
|
304
|
+
sys.exit(1)
|
|
305
|
+
else:
|
|
306
|
+
config.set_thinking_tokens(args.thinking_tokens)
|
|
307
|
+
|
|
308
|
+
# Display model settings in verbose mode
|
|
309
|
+
if args.verbose:
|
|
310
|
+
io.print_info("Model settings:")
|
|
311
|
+
for attr in sorted(fields(ModelRunConfig), key=lambda x: x.name):
|
|
312
|
+
value = getattr(config, attr.name)
|
|
313
|
+
if value is None:
|
|
314
|
+
val_str = "None"
|
|
315
|
+
else:
|
|
316
|
+
val_str = json.dumps(value, indent=4)
|
|
317
|
+
io.print_info(f"{attr.name}: {val_str}")
|
|
318
|
+
|
|
319
|
+
return config
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def main():
|
|
323
|
+
# Suppress harmless warnings from third-party libraries
|
|
324
|
+
_suppress_third_party_warnings()
|
|
325
|
+
|
|
326
|
+
# Configure litellm globally to suppress debug logs
|
|
327
|
+
_configure_litellm_logging()
|
|
328
|
+
|
|
329
|
+
conf: Config = load_conf()
|
|
330
|
+
|
|
331
|
+
argv = sys.argv[1:]
|
|
332
|
+
|
|
333
|
+
args, _, _, git_root, workspace_arg, parser = _parse_args_and_setup_environment(argv)
|
|
334
|
+
|
|
335
|
+
interactive_mode = True
|
|
336
|
+
if args.prompt:
|
|
337
|
+
interactive_mode = False
|
|
338
|
+
args.pretty = False
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
io, running_color_settings = get_io(args)
|
|
342
|
+
except ValueError as e:
|
|
343
|
+
print(f"Invalid theme configuration: {e}")
|
|
344
|
+
return 1
|
|
345
|
+
|
|
346
|
+
if args.list_models:
|
|
347
|
+
models = ModelInfoService.get_model_names()
|
|
348
|
+
io.print_info("\n".join(f"- {model}" for model in models))
|
|
349
|
+
return 0
|
|
350
|
+
|
|
351
|
+
# Configure model
|
|
352
|
+
model = get_config(args, io, conf)
|
|
353
|
+
# Display banner
|
|
354
|
+
|
|
355
|
+
# Set environment variables
|
|
356
|
+
if set_env(args, io) != 0:
|
|
357
|
+
return 1
|
|
358
|
+
|
|
359
|
+
# Get workspace
|
|
360
|
+
workspace = get_workspace(workspace_arg, git_root)
|
|
361
|
+
|
|
362
|
+
if args.verbose:
|
|
363
|
+
io.print_info(f"Using agent: {args.agent}")
|
|
364
|
+
io.print_info(f"Workspace: {workspace}")
|
|
365
|
+
|
|
366
|
+
if args.verbose:
|
|
367
|
+
show = SettingsUtils.format_settings(parser, args)
|
|
368
|
+
io.print_info(show)
|
|
369
|
+
|
|
370
|
+
# Show command line in verbose mode only
|
|
371
|
+
cmd_line = " ".join(sys.argv)
|
|
372
|
+
io.print_info(f"Command: {cmd_line}")
|
|
373
|
+
|
|
374
|
+
if model is None:
|
|
375
|
+
return 0
|
|
376
|
+
|
|
377
|
+
commands = SlashCommands(
|
|
378
|
+
io=io,
|
|
379
|
+
verbose=args.verbose,
|
|
380
|
+
editor=args.editor,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
completer: Completer = AutoCompleter(
|
|
384
|
+
root=workspace,
|
|
385
|
+
commands=commands,
|
|
386
|
+
encoding=args.encoding,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
running_config = RunningConfig(
|
|
390
|
+
llm_config=model,
|
|
391
|
+
io=io,
|
|
392
|
+
workspace=workspace,
|
|
393
|
+
agent_name=args.agent,
|
|
394
|
+
completer=completer,
|
|
395
|
+
running_color_settings=running_color_settings,
|
|
396
|
+
console_output=not args.disable_console_output if interactive_mode else True,
|
|
397
|
+
interactive=interactive_mode,
|
|
398
|
+
)
|
|
399
|
+
show_banner(io)
|
|
400
|
+
|
|
401
|
+
if not interactive_mode:
|
|
402
|
+
controller = NoInteractiveController(running_config)
|
|
403
|
+
controller.run(args.prompt)
|
|
404
|
+
return 0
|
|
405
|
+
|
|
406
|
+
controller = Controller(running_config, commands)
|
|
407
|
+
controller.show_announcements()
|
|
408
|
+
controller.run()
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
if __name__ == "__main__":
|
|
412
|
+
status = main()
|
|
413
|
+
sys.exit(status)
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
|
+
|
|
4
|
+
from siada.session.session_models import RunningSession
|
|
5
|
+
from typing import List
|
|
6
|
+
from agents import TResponseInputItem
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CodeAgentContext(BaseModel):
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
13
|
+
|
|
14
|
+
session: Optional[RunningSession] = None
|
|
15
|
+
|
|
16
|
+
root_dir: str | None = None
|
|
17
|
+
|
|
18
|
+
provider: str | None = None
|
|
19
|
+
|
|
20
|
+
# 交互模式标识,True为交互模式,False为非交互模式
|
|
21
|
+
interactive_mode: bool = True
|
|
22
|
+
|
|
23
|
+
# 完整的消息历史列表
|
|
24
|
+
message_history: List[TResponseInputItem] = Field(default_factory=list)
|
|
25
|
+
|
|
26
|
+
def add_message(self, message: TResponseInputItem) -> None:
|
|
27
|
+
self.message_history.append(message)
|
|
28
|
+
|
|
29
|
+
def add_messages(self, messages: List[TResponseInputItem]) -> None:
|
|
30
|
+
self.message_history.extend(messages)
|
|
31
|
+
|
|
32
|
+
def remove_old_messages(self, remove_count: int) -> List[TResponseInputItem]:
|
|
33
|
+
"""删除旧消息,返回剩余的消息列表,永远保留第一条消息"""
|
|
34
|
+
if remove_count <= 0:
|
|
35
|
+
return self.message_history.copy()
|
|
36
|
+
|
|
37
|
+
# 如果历史记录为空或只有一条消息,不删除任何消息
|
|
38
|
+
if len(self.message_history) <= 1:
|
|
39
|
+
return self.message_history.copy()
|
|
40
|
+
|
|
41
|
+
# 计算实际可删除的消息数量(保留第一条消息)
|
|
42
|
+
max_removable = len(self.message_history) - 1
|
|
43
|
+
actual_remove_count = min(remove_count, max_removable)
|
|
44
|
+
|
|
45
|
+
# 删除第1条消息之后的N条消息(索引1到1+actual_remove_count)
|
|
46
|
+
# 保留第一条消息和剩余的消息
|
|
47
|
+
self.message_history = [self.message_history[0]] + self.message_history[1 + actual_remove_count:]
|
|
48
|
+
return self.message_history.copy()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import agents
|
|
2
|
+
from pydantic_settings import BaseSettings
|
|
3
|
+
from typing import Optional, ClassVar
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Settings(BaseSettings):
|
|
7
|
+
"""
|
|
8
|
+
应用配置类
|
|
9
|
+
|
|
10
|
+
使用pydantic_settings管理配置,支持从环境变量加载
|
|
11
|
+
"""
|
|
12
|
+
# 应用基本信息
|
|
13
|
+
APP_NAME: str = "Siada API"
|
|
14
|
+
APP_VERSION: str = "0.1.0"
|
|
15
|
+
APP_DESCRIPTION: str = "提供Siada agent对外的RPC能力"
|
|
16
|
+
|
|
17
|
+
# API配置
|
|
18
|
+
API_PREFIX: str = "/api/v1"
|
|
19
|
+
|
|
20
|
+
# 服务器配置
|
|
21
|
+
HOST: str = "0.0.0.0"
|
|
22
|
+
PORT: int = 8000
|
|
23
|
+
RELOAD: bool = True
|
|
24
|
+
|
|
25
|
+
# 日志配置
|
|
26
|
+
LOG_LEVEL: str = "INFO"
|
|
27
|
+
|
|
28
|
+
# OpenAI API配置
|
|
29
|
+
OPENAI_API_KEY: Optional[str] = None
|
|
30
|
+
OPENAI_API_BASE: Optional[str] = None
|
|
31
|
+
OPENAI_API_VERSION: Optional[str] = None
|
|
32
|
+
OPENAI_API_TYPE: Optional[str] = None
|
|
33
|
+
OPENAI_ORGANIZATION: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
# Agent配置
|
|
36
|
+
Claude_4_0_SONNET: str = "claude-sonnet-4"
|
|
37
|
+
Gemini_2_5_PRO: str = "gemini-2.5-pro"
|
|
38
|
+
Deepseek_V3_0324: str = "deepseek-v3-0324"
|
|
39
|
+
Deepseek_R1_0528: str = "deepseek-r1-0528"
|
|
40
|
+
O1_MINI: str = "o1-mini"
|
|
41
|
+
MAX_TURNS: int = 200
|
|
42
|
+
DEFAULT_MODEL: str = Claude_4_0_SONNET
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# 将RunConfig设置为ClassVar,这样它不会被包含在模型验证中
|
|
46
|
+
_DEFAULT_RUN_CONFIG: ClassVar[agents.RunConfig] = None
|
|
47
|
+
|
|
48
|
+
class Config:
|
|
49
|
+
env_file = ".env"
|
|
50
|
+
case_sensitive = True
|
|
51
|
+
|
|
52
|
+
class Constants:
|
|
53
|
+
# Agent名称
|
|
54
|
+
JANK_PROBLEM_AGENT_NAME: str = "JankProblemAgent"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# 创建全局设置对象
|
|
58
|
+
settings = Settings()
|
|
59
|
+
settings.model_rebuild() # 确保模型完全构建
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
上下文管理模块
|
|
3
|
+
|
|
4
|
+
提供全局上下文管理功能,类似于Java中的ThreadLocal
|
|
5
|
+
支持存储多种类型的上下文变量
|
|
6
|
+
"""
|
|
7
|
+
import contextvars
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
# 创建上下文变量字典
|
|
11
|
+
context_dict_var = contextvars.ContextVar('context_dict', default={})
|
|
12
|
+
|
|
13
|
+
def set_context_var(key: str, value: Any) -> None:
|
|
14
|
+
"""
|
|
15
|
+
设置上下文变量
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
key: 变量名
|
|
19
|
+
value: 变量值
|
|
20
|
+
"""
|
|
21
|
+
context_dict = context_dict_var.get()
|
|
22
|
+
new_dict = dict(context_dict) # 创建副本以避免修改原始字典
|
|
23
|
+
new_dict[key] = value
|
|
24
|
+
context_dict_var.set(new_dict)
|
|
25
|
+
|
|
26
|
+
def get_context_var(key: str, default: Any = None) -> Any:
|
|
27
|
+
"""
|
|
28
|
+
获取上下文变量
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
key: 变量名
|
|
32
|
+
default: 默认值,如果变量不存在则返回此值
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
上下文变量值,如果不存在则返回默认值
|
|
36
|
+
"""
|
|
37
|
+
context_dict = context_dict_var.get()
|
|
38
|
+
return context_dict.get(key, default)
|
|
39
|
+
|
|
40
|
+
def remove_context_var(key: str) -> None:
|
|
41
|
+
"""
|
|
42
|
+
移除上下文变量
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
key: 变量名
|
|
46
|
+
"""
|
|
47
|
+
context_dict = context_dict_var.get()
|
|
48
|
+
new_dict = dict(context_dict) # 创建副本以避免修改原始字典
|
|
49
|
+
if key in new_dict:
|
|
50
|
+
del new_dict[key]
|
|
51
|
+
context_dict_var.set(new_dict)
|
|
52
|
+
|
|
53
|
+
def clear_context() -> None:
|
|
54
|
+
"""
|
|
55
|
+
清空所有上下文变量
|
|
56
|
+
"""
|
|
57
|
+
context_dict_var.set({})
|
|
58
|
+
|
|
59
|
+
# 为了保持向后兼容性,提供session_id的专用方法
|
|
60
|
+
def set_session_id(session_id: str) -> None:
|
|
61
|
+
"""
|
|
62
|
+
设置当前上下文的session_id
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
session_id: 会话ID
|
|
66
|
+
"""
|
|
67
|
+
set_context_var('session_id', session_id)
|
|
68
|
+
|
|
69
|
+
def get_session_id() -> Optional[str]:
|
|
70
|
+
"""
|
|
71
|
+
获取当前上下文的session_id
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
当前上下文的session_id,如果不存在则返回None
|
|
75
|
+
"""
|
|
76
|
+
return get_context_var('session_id')
|