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.
Files changed (215) hide show
  1. agent_config.yaml +34 -0
  2. siada/__init__.py +13 -0
  3. siada/agent_hub/__init__.py +10 -0
  4. siada/agent_hub/coder/bug_fix_agent.py +398 -0
  5. siada/agent_hub/coder/code_gen_agent.py +235 -0
  6. siada/agent_hub/coder/fe_gen_agent.py +37 -0
  7. siada/agent_hub/coder/issue_review_agent.py +68 -0
  8. siada/agent_hub/coder/prompt/__init__.py +0 -0
  9. siada/agent_hub/coder/prompt/base/__init__.py +0 -0
  10. siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
  11. siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
  12. siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
  13. siada/agent_hub/coder/prompt/base/rules.py +40 -0
  14. siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
  15. siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
  16. siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
  17. siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
  18. siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
  19. siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
  20. siada/agent_hub/coder/tracing/__init__.py +22 -0
  21. siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
  22. siada/agent_hub/hooks/__init__.py +0 -0
  23. siada/agent_hub/hooks/io_hooks.py +30 -0
  24. siada/agent_hub/siada_agent.py +214 -0
  25. siada/config/__init__.py +0 -0
  26. siada/config/config_loader.py +67 -0
  27. siada/entrypoint/__init__.py +3 -0
  28. siada/entrypoint/args_parser/__init__.py +0 -0
  29. siada/entrypoint/args_parser/args.py +195 -0
  30. siada/entrypoint/interaction/__init__.py +0 -0
  31. siada/entrypoint/interaction/base_turn.py +107 -0
  32. siada/entrypoint/interaction/config.py +30 -0
  33. siada/entrypoint/interaction/controller.py +122 -0
  34. siada/entrypoint/interaction/nointeractive_controller.py +27 -0
  35. siada/entrypoint/interaction/turn/interface.py +116 -0
  36. siada/entrypoint/interaction/turn/models.py +34 -0
  37. siada/entrypoint/interaction/turn/run_turn.py +641 -0
  38. siada/entrypoint/interaction/turn_factory.py +35 -0
  39. siada/entrypoint/interaction/turn_models.py +28 -0
  40. siada/entrypoint/siadahub.py +413 -0
  41. siada/foundation/__init__.py +0 -0
  42. siada/foundation/code_agent_context.py +48 -0
  43. siada/foundation/config.py +59 -0
  44. siada/foundation/constants.py +2 -0
  45. siada/foundation/context.py +76 -0
  46. siada/foundation/logging.py +341 -0
  47. siada/foundation/tools/__init__.py +0 -0
  48. siada/foundation/tools/get_git_diff.py +105 -0
  49. siada/io/__init__.py +0 -0
  50. siada/io/banner.py +148 -0
  51. siada/io/color_settings.py +207 -0
  52. siada/io/color_utils.py +16 -0
  53. siada/io/components/__init__.py +0 -0
  54. siada/io/components/mdstream.py +204 -0
  55. siada/io/console_printer.py +68 -0
  56. siada/io/io.py +661 -0
  57. siada/io/key_bindings.py +129 -0
  58. siada/io/notification_command.py +40 -0
  59. siada/io/test_banner.py +64 -0
  60. siada/models/__init__.py +0 -0
  61. siada/models/agent.py +47 -0
  62. siada/models/coder/__init__.py +0 -0
  63. siada/models/converter.py +27 -0
  64. siada/models/model_base_config.py +121 -0
  65. siada/models/model_run_config.py +153 -0
  66. siada/provider/__init__.py +0 -0
  67. siada/provider/client_factory.py +112 -0
  68. siada/provider/llm_client.py +11 -0
  69. siada/provider/openrouter/__init__.py +0 -0
  70. siada/provider/openrouter/coverter.py +17 -0
  71. siada/provider/openrouter/openroute_provider.py +33 -0
  72. siada/provider/provider_factory.py +53 -0
  73. siada/queries/tree-sitter-language-pack/README.md +7 -0
  74. siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  91. siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  92. siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  93. siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  94. siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  95. siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  96. siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  97. siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  98. siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  99. siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  100. siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  101. siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  102. siada/queries/tree-sitter-languages/README.md +23 -0
  103. siada/queries/tree-sitter-languages/c-tags.scm +9 -0
  104. siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  105. siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  106. siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
  107. siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  108. siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  109. siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
  110. siada/queries/tree-sitter-languages/go-tags.scm +30 -0
  111. siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  112. siada/queries/tree-sitter-languages/java-tags.scm +20 -0
  113. siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  114. siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  115. siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  116. siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  117. siada/queries/tree-sitter-languages/php-tags.scm +26 -0
  118. siada/queries/tree-sitter-languages/python-tags.scm +12 -0
  119. siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
  120. siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  121. siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
  122. siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
  123. siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  124. siada/services/__init__.py +30 -0
  125. siada/services/at_file_recommendation_design_document.md +1213 -0
  126. siada/services/code_context_manager.py +136 -0
  127. siada/services/enhanced_fix_result_check.py +546 -0
  128. siada/services/execution_trace_collector.py +207 -0
  129. siada/services/file_recommendation/__init__.py +136 -0
  130. siada/services/file_recommendation/core/__init__.py +17 -0
  131. siada/services/file_recommendation/core/completion_engine.py +351 -0
  132. siada/services/file_recommendation/core/config.py +62 -0
  133. siada/services/file_recommendation/core/file_discovery.py +353 -0
  134. siada/services/file_recommendation/core/suggestion.py +110 -0
  135. siada/services/file_recommendation/utils/__init__.py +22 -0
  136. siada/services/file_recommendation/utils/path_utils.py +138 -0
  137. siada/services/file_recommendation/utils/text_utils.py +253 -0
  138. siada/services/fix_result_check.py +377 -0
  139. siada/services/handle_at_command/README.md +247 -0
  140. siada/services/handle_at_command/__init__.py +115 -0
  141. siada/services/handle_at_command/exceptions.py +46 -0
  142. siada/services/handle_at_command/models.py +92 -0
  143. siada/services/handle_at_command/parser.py +189 -0
  144. siada/services/handle_at_command/processor.py +321 -0
  145. siada/services/handle_at_command/resolver.py +318 -0
  146. siada/services/handle_at_command/utils.py +345 -0
  147. siada/services/model_info_service.py +103 -0
  148. siada/services/siada_runner.py +170 -0
  149. siada/session/__init__.py +29 -0
  150. siada/session/session_manager.py +66 -0
  151. siada/session/session_models.py +43 -0
  152. siada/support/__init__.py +0 -0
  153. siada/support/completer.py +204 -0
  154. siada/support/editor.py +146 -0
  155. siada/support/envprocessor.py +54 -0
  156. siada/support/repo.py +51 -0
  157. siada/support/slash_commands.py +410 -0
  158. siada/support/spinner.py +320 -0
  159. siada/tools/__init__.py +0 -0
  160. siada/tools/ast/README.md +345 -0
  161. siada/tools/ast/ast_tool.py +341 -0
  162. siada/tools/ast/models.py +11 -0
  163. siada/tools/coder/__init__.py +0 -0
  164. siada/tools/coder/ask_followup_question.py +70 -0
  165. siada/tools/coder/cmd_runner.py +132 -0
  166. siada/tools/coder/file_operator.py +284 -0
  167. siada/tools/coder/file_search/README.md +223 -0
  168. siada/tools/coder/file_search/__init__.py +10 -0
  169. siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
  170. siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
  171. siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
  172. siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
  173. siada/tools/coder/file_search/bin/rg.exe +0 -0
  174. siada/tools/coder/file_search/search.py +437 -0
  175. siada/tools/coder/file_search/setup_package.py +150 -0
  176. siada/tools/coder/file_search/test_search.py +142 -0
  177. siada/tools/coder/files.py +147 -0
  178. siada/tools/coder/fix_attempt_completion.py +58 -0
  179. siada/tools/coder/issue_review_completion.py +29 -0
  180. siada/tools/coder/observation/__init__.py +0 -0
  181. siada/tools/coder/observation/error.py +22 -0
  182. siada/tools/coder/observation/file_observation.py +216 -0
  183. siada/tools/coder/observation/observation.py +71 -0
  184. siada/tools/coder/repo_map/__init__.py +157 -0
  185. siada/tools/coder/repo_map/dump.py +29 -0
  186. siada/tools/coder/repo_map/io.py +226 -0
  187. siada/tools/coder/repo_map/repo_map.py +881 -0
  188. siada/tools/coder/repo_map/special.py +203 -0
  189. siada/tools/coder/repo_map/token_counter.py +154 -0
  190. siada/tools/coder/repo_map/waiting.py +221 -0
  191. siada/tools/coder/reproduce_completion.py +39 -0
  192. siada/tools/coder/run_cmd.py +60 -0
  193. siada/tools/coder/test_completion.py +25 -0
  194. siada/tools/coder/tool_docs.py +44 -0
  195. siada/tools/compression_tool.py +200 -0
  196. siada/tools/read_many_files/README.md +220 -0
  197. siada/tools/read_many_files/__init__.py +25 -0
  198. siada/tools/read_many_files/file_processor.py +486 -0
  199. siada/tools/read_many_files/filters.py +326 -0
  200. siada/tools/read_many_files/formatters.py +204 -0
  201. siada/tools/read_many_files/models.py +189 -0
  202. siada/tools/read_many_files_tool.py +233 -0
  203. siada/tools/tool_call_format/__init__.py +55 -0
  204. siada/tools/tool_call_format/file_to_language.py +231 -0
  205. siada/tools/tool_call_format/formatter_factory.py +80 -0
  206. siada/tools/tool_call_format/formatters.py +282 -0
  207. siada/tools/tool_call_format/tool_call_formatter.py +54 -0
  208. siada/tools/web/__init__.py +0 -0
  209. siada/tools/web/web_crawl.py +208 -0
  210. siada/utils.py +194 -0
  211. siada_cli-0.0.1.dist-info/LICENSE +201 -0
  212. siada_cli-0.0.1.dist-info/METADATA +457 -0
  213. siada_cli-0.0.1.dist-info/RECORD +215 -0
  214. siada_cli-0.0.1.dist-info/WHEEL +4 -0
  215. siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,129 @@
1
+ import functools
2
+ import signal
3
+
4
+ from prompt_toolkit.enums import EditingMode
5
+ from prompt_toolkit.filters import Condition, is_searching, has_completions
6
+ from prompt_toolkit.key_binding import KeyBindings
7
+ from prompt_toolkit.key_binding.vi_state import InputMode
8
+ from prompt_toolkit.keys import Keys
9
+
10
+ from siada.support.editor import pipe_editor
11
+
12
+
13
+ class KeyBindingsFactory:
14
+ """A factory for creating key bindings for the IO instance."""
15
+ def __init__(self, io_instance):
16
+ self.io = io_instance
17
+
18
+ def create_key_bindings(self):
19
+ kb = KeyBindings()
20
+
21
+ def suspend_to_bg(event):
22
+ """Suspend currently running application."""
23
+ event.app.suspend_to_background()
24
+
25
+ @kb.add(Keys.ControlZ, filter=Condition(lambda: hasattr(signal, "SIGTSTP")))
26
+ def _(event):
27
+ "Suspend to background with ctrl-z"
28
+ suspend_to_bg(event)
29
+
30
+ @kb.add("c-space")
31
+ def _(event):
32
+ "Ignore Ctrl when pressing space bar"
33
+ event.current_buffer.insert_text(" ")
34
+
35
+ @kb.add("c-up")
36
+ def _(event):
37
+ "Navigate backward through history"
38
+ event.current_buffer.history_backward()
39
+
40
+ @kb.add("c-down")
41
+ def _(event):
42
+ "Navigate forward through history"
43
+ event.current_buffer.history_forward()
44
+
45
+ @kb.add("c-x", "c-e")
46
+ def _(event):
47
+ "Edit current input in external editor (like Bash)"
48
+ buffer = event.current_buffer
49
+ current_text = buffer.text
50
+
51
+ # Open the editor with the current text
52
+ edited_text = pipe_editor(input_data=current_text, suffix="md")
53
+
54
+ # Replace the buffer with the edited text, strip any trailing newlines
55
+ buffer.text = edited_text.rstrip("\n")
56
+
57
+ # Move cursor to the end of the text
58
+ buffer.cursor_position = len(buffer.text)
59
+
60
+ @kb.add("enter", eager=True, filter=~is_searching & ~has_completions)
61
+ def _(event):
62
+ "Handle Enter key press when no completions are shown"
63
+ if self.io.multiline_mode and not (
64
+ self.io.editingmode == EditingMode.VI
65
+ and event.app.vi_state.input_mode == InputMode.NAVIGATION
66
+ ):
67
+ # In multiline mode and if not in vi-mode or vi navigation/normal mode,
68
+ # Enter adds a newline
69
+ event.current_buffer.insert_text("\n")
70
+ else:
71
+ # In normal mode, Enter submits
72
+ event.current_buffer.validate_and_handle()
73
+
74
+ @kb.add("enter", eager=True, filter=~is_searching & has_completions)
75
+ def _(event):
76
+ "Handle Enter key press when completions are shown"
77
+ buffer = event.current_buffer
78
+
79
+ # Check if this is an @ command completion using cursor position (consistent with completer.py)
80
+ text_before_cursor = buffer.document.text_before_cursor
81
+ at_pos = text_before_cursor.rfind("@")
82
+ is_at_command = at_pos != -1
83
+
84
+ if buffer.complete_state and buffer.complete_state.completions:
85
+ # Ensure first completion is selected by default if none is currently selected
86
+ if buffer.complete_state.current_completion is None:
87
+ # Move to first completion to ensure it's highlighted
88
+ buffer.complete_next()
89
+
90
+ # Accept the currently selected completion
91
+ if buffer.complete_state.current_completion:
92
+ buffer.apply_completion(buffer.complete_state.current_completion)
93
+
94
+ # For @ commands, only accept completion without submitting
95
+ # For other commands (like /), accept completion and submit
96
+ if not is_at_command:
97
+ # For non-@ commands, also submit after accepting completion
98
+ if self.io.multiline_mode and not (
99
+ self.io.editingmode == EditingMode.VI
100
+ and event.app.vi_state.input_mode == InputMode.NAVIGATION
101
+ ):
102
+ # In multiline mode, Enter adds a newline after accepting completion
103
+ buffer.insert_text("\n")
104
+ else:
105
+ # In normal mode, Enter submits after accepting completion
106
+ buffer.validate_and_handle()
107
+ else:
108
+ # No completions available, just handle enter normally
109
+ if self.io.multiline_mode and not (
110
+ self.io.editingmode == EditingMode.VI
111
+ and event.app.vi_state.input_mode == InputMode.NAVIGATION
112
+ ):
113
+ # In multiline mode, Enter adds a newline
114
+ buffer.insert_text("\n")
115
+ else:
116
+ # In normal mode, Enter submits
117
+ buffer.validate_and_handle()
118
+
119
+ @kb.add("escape", "enter", eager=True, filter=~is_searching) # This is Alt+Enter
120
+ def _(event):
121
+ "Handle Alt+Enter key press"
122
+ if self.io.multiline_mode:
123
+ # In multiline mode, Alt+Enter submits
124
+ event.current_buffer.validate_and_handle()
125
+ else:
126
+ # In normal mode, Alt+Enter adds a newline
127
+ event.current_buffer.insert_text("\n")
128
+
129
+ return kb
@@ -0,0 +1,40 @@
1
+
2
+ import shutil
3
+ import platform
4
+
5
+
6
+ class NotificationCommandUtil:
7
+ """A utility class for handling notification commands."""
8
+
9
+ NOTIFICATION_MESSAGE = "Siada is waiting for your input"
10
+
11
+ @staticmethod
12
+ def get_default_notification_command():
13
+ """Return a default notification command based on the operating system."""
14
+ system = platform.system()
15
+
16
+ if system == "Darwin": # macOS
17
+ # Check for terminal-notifier first
18
+ if shutil.which("terminal-notifier"):
19
+ return f"terminal-notifier -title 'Siada' -message '{NotificationCommandUtil.NOTIFICATION_MESSAGE}'"
20
+ # Fall back to osascript
21
+ return f'osascript -e \'display notification "{NotificationCommandUtil.NOTIFICATION_MESSAGE}" with title "Siada"\''
22
+ elif system == "Linux":
23
+ # Check for common Linux notification tools
24
+ for cmd in ["notify-send", "zenity"]:
25
+ if shutil.which(cmd):
26
+ if cmd == "notify-send":
27
+ return f"notify-send 'Siada' '{NotificationCommandUtil.NOTIFICATION_MESSAGE}'"
28
+ elif cmd == "zenity":
29
+ return f"zenity --notification --text='{NotificationCommandUtil.NOTIFICATION_MESSAGE}'"
30
+ return None # No known notification tool found
31
+ elif system == "Windows":
32
+ # PowerShell notification
33
+ return (
34
+ "powershell -command"
35
+ " \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');"
36
+ f" [System.Windows.Forms.MessageBox]::Show('{NotificationCommandUtil.NOTIFICATION_MESSAGE}',"
37
+ " 'Siada')\""
38
+ )
39
+
40
+ return None # Unknown system
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for banner display functionality.
4
+
5
+ Usage:
6
+ python test_banner.py [--pretty|--plain|--both]
7
+ """
8
+
9
+ import argparse
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ # Add parent directory to path so we can import siada modules
14
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
15
+
16
+ from siada.io.banner import show_siada_banner, get_banner_text
17
+ from rich.console import Console
18
+
19
+
20
+ def test_pretty_banner():
21
+ """Test colorful banner with gradient."""
22
+ print("=== PRETTY BANNER (Left-to-Right Gradient) ===")
23
+ show_siada_banner(pretty=True)
24
+ print()
25
+
26
+
27
+ def test_plain_banner():
28
+ """Test plain text banner."""
29
+ print("=== PLAIN BANNER ===")
30
+ show_siada_banner(pretty=False)
31
+ print()
32
+
33
+
34
+ def test_text_banner():
35
+ """Test banner as string."""
36
+ print("=== BANNER AS TEXT ===")
37
+ print(get_banner_text())
38
+
39
+
40
+ def main():
41
+ parser = argparse.ArgumentParser(description="Test SIADA HUB banner display")
42
+ parser.add_argument("--pretty", action="store_true", help="Show only pretty banner")
43
+ parser.add_argument("--plain", action="store_true", help="Show only plain banner")
44
+ parser.add_argument("--both", action="store_true", help="Show both versions")
45
+
46
+ args = parser.parse_args()
47
+
48
+ if args.pretty:
49
+ test_pretty_banner()
50
+ elif args.plain:
51
+ test_plain_banner()
52
+ elif args.both:
53
+ test_pretty_banner()
54
+ test_plain_banner()
55
+ test_text_banner()
56
+ else:
57
+ # Default: show all versions
58
+ test_pretty_banner()
59
+ test_plain_banner()
60
+ test_text_banner()
61
+
62
+
63
+ if __name__ == "__main__":
64
+ main()
File without changes
siada/models/agent.py ADDED
@@ -0,0 +1,47 @@
1
+ """
2
+ Agent数据模型
3
+
4
+ 定义与Agent相关的请求和响应模型
5
+ """
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class Tool(BaseModel):
12
+ """工具模型"""
13
+ name: str = Field(..., description="工具名称")
14
+ description: str = Field(..., description="工具描述")
15
+ function: str = Field(..., description="工具函数代码")
16
+
17
+
18
+ class CreateAgentRequest(BaseModel):
19
+ """创建Agent的请求模型"""
20
+ name: str = Field(..., description="Agent名称")
21
+ instructions: str = Field(..., description="Agent指令")
22
+ tools: Optional[List[Tool]] = Field(None, description="Agent可以使用的工具列表")
23
+
24
+
25
+ class AgentResponse(BaseModel):
26
+ """Agent响应模型"""
27
+ id: str = Field(..., description="Agent ID")
28
+ name: str = Field(..., description="Agent名称")
29
+ instructions: str = Field(..., description="Agent指令")
30
+ model: str = Field(..., description="使用的模型名称")
31
+
32
+
33
+ class RunAgentRequest(BaseModel):
34
+ """运行Agent的请求模型"""
35
+ agent_name: str = Field(..., description="Agent 名称")
36
+ input: str = Field(..., description="输入文本")
37
+ model: Optional[str] = Field("gpt-4o", description="使用的模型名称")
38
+ max_turns: Optional[int] = Field(10, description="最大运行轮数")
39
+ session_id: Optional[str] = Field(None, description="会话ID")
40
+
41
+
42
+ class RunAgentResponse(BaseModel):
43
+ """运行Agent的响应模型"""
44
+ final_output: str = Field(..., description="最终输出")
45
+ turns: int = Field(..., description="运行轮数")
46
+ completed: bool = Field(..., description="是否完成")
47
+ trace_id: Optional[str] = Field(None, description="跟踪ID")
File without changes
@@ -0,0 +1,27 @@
1
+ from agents import ModelSettings
2
+ from litellm import Reasoning
3
+ from siada.models.model_base_config import is_gemini_model
4
+ from siada.models.model_run_config import ModelRunConfig
5
+
6
+
7
+ class ModelSettingsConverter:
8
+
9
+ @staticmethod
10
+ def convert_model_settings(model_running_config: ModelRunConfig) -> ModelSettings:
11
+
12
+ extra_body = {}
13
+ reasoning = {}
14
+ if model_running_config.get_reasoning_effort() is not None:
15
+ reasoning["effort"] = model_running_config.get_reasoning_effort()
16
+ if model_running_config.get_raw_thinking_tokens() is not None:
17
+ reasoning["max_tokens"] = model_running_config.get_raw_thinking_tokens()
18
+
19
+ if reasoning:
20
+ extra_body["reasoning"] = reasoning
21
+
22
+ model_settings = ModelSettings(
23
+ max_tokens=model_running_config.max_tokens,
24
+ extra_body=extra_body,
25
+ )
26
+
27
+ return model_settings
@@ -0,0 +1,121 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, List
3
+
4
+ @dataclass
5
+ class ModelBaseConfig:
6
+ """
7
+ Represents the configuration for a specific language model.
8
+ """
9
+ model_name: str
10
+ context_window: int
11
+ max_tokens: Optional[int] = None
12
+ supports_images: bool = False
13
+ supports_prompt_cache: bool = False
14
+
15
+ supports_extra_params: Optional[List[str]] = None
16
+
17
+ # Simple list of all model configurations
18
+ MODEL_SETTING: List[ModelBaseConfig] = [
19
+
20
+ # ModelBaseConfig(
21
+ # model_name="o3-pro",
22
+ # max_tokens=8192,
23
+ # context_window=200_000,
24
+ # supports_images=True,
25
+ # supports_extra_params=["reasoning_effort"],
26
+ # ),
27
+ ModelBaseConfig(
28
+ model_name="claude-opus-4.1",
29
+ max_tokens=8192,
30
+ context_window=200_000,
31
+ supports_extra_params=[],
32
+ ),
33
+
34
+ ModelBaseConfig(
35
+ model_name="gpt-5",
36
+ max_tokens=8192,
37
+ context_window=400_000,
38
+ supports_images=True,
39
+ supports_extra_params=["reasoning_effort"],
40
+ ),
41
+
42
+ ModelBaseConfig(
43
+ model_name="gpt-5-mini",
44
+ max_tokens=8192,
45
+ context_window=400_000,
46
+ supports_images=True,
47
+ supports_extra_params=["reasoning_effort"],
48
+ ),
49
+
50
+ ModelBaseConfig(
51
+ model_name="gpt-4.1",
52
+ max_tokens=8192,
53
+ context_window=1_047_576,
54
+ supports_images=True,
55
+ ),
56
+
57
+ ModelBaseConfig(
58
+ model_name="claude-opus-4",
59
+ max_tokens=8192,
60
+ context_window=200_000,
61
+ supports_images=True,
62
+ supports_extra_params=[],
63
+ ),
64
+ ModelBaseConfig(
65
+ model_name="claude-sonnet-4",
66
+ max_tokens=8192,
67
+ context_window=200_000,
68
+ supports_images=True,
69
+ supports_extra_params=[],
70
+ ),
71
+ ModelBaseConfig(
72
+ model_name="claude-3.7-sonnet",
73
+ max_tokens=8192,
74
+ context_window=200_000,
75
+ supports_images=True,
76
+ supports_extra_params=[],
77
+ ),
78
+
79
+ ModelBaseConfig(
80
+ model_name="gemini-2.5-pro",
81
+ max_tokens=8192,
82
+ context_window=1_048_576,
83
+ supports_extra_params=["thinking_tokens"],
84
+ ),
85
+
86
+ ModelBaseConfig(
87
+ model_name="deepseek-v3-0324",
88
+ max_tokens=8192,
89
+ context_window=128_000,
90
+ ),
91
+
92
+ ModelBaseConfig(
93
+ model_name="kimi-k2",
94
+ max_tokens=8192,
95
+ context_window=131_072,
96
+ ),
97
+ ]
98
+
99
+ def is_gemini_model(model_name: str) -> bool:
100
+ return model_name.startswith("gemini-")
101
+
102
+ def get_model_config(model_name: str) -> Optional[ModelBaseConfig]:
103
+ """
104
+ Retrieves the configuration for a given model name.
105
+
106
+ Args:
107
+ model_name: The name of the model to retrieve.
108
+
109
+ Returns:
110
+ A ModelSettings instance if the model is found, otherwise None.
111
+ """
112
+ # Check if model_name is None or empty
113
+ if not model_name:
114
+ raise ValueError("Model name cannot be None or empty")
115
+
116
+ # Only exact match
117
+ for model_config in MODEL_SETTING:
118
+ if model_config.model_name == model_name:
119
+ return model_config
120
+
121
+ return None
@@ -0,0 +1,153 @@
1
+ from dataclasses import dataclass, fields
2
+ from typing import Optional
3
+
4
+ from siada.models.model_base_config import ModelBaseConfig, get_model_config
5
+
6
+ import os
7
+ from pathlib import Path
8
+
9
+ import logging
10
+ import yaml
11
+ @dataclass()
12
+ class ModelRunConfig(ModelBaseConfig):
13
+
14
+ reasoning_effort : Optional[str] = None
15
+ thinking_tokens : Optional[int] = None
16
+ temperature : Optional[float] = None
17
+ extra_params : Optional[dict] = None
18
+ provider: Optional[str] = None
19
+
20
+
21
+
22
+ def __init__(self, model):
23
+ self.configure_model_settings(model)
24
+
25
+
26
+ def _copy_fields(self, source):
27
+ """Helper to copy fields from a ModelSettings instance to self"""
28
+ for field in fields(ModelBaseConfig):
29
+ val = getattr(source, field.name)
30
+ setattr(self, field.name, val)
31
+
32
+
33
+ def configure_model_settings(self, model):
34
+ # Look for exact model match
35
+ model_config = get_model_config(model)
36
+ if model_config:
37
+ self._copy_fields(model_config)
38
+ else:
39
+ raise ValueError(f"Model {model} not found in model settings")
40
+
41
+
42
+ def set_reasoning_effort(self, reasoning_effort):
43
+ self.reasoning_effort = reasoning_effort
44
+
45
+
46
+ def get_raw_thinking_tokens(self):
47
+ """Get formatted thinking token budget if available"""
48
+ return self.thinking_tokens
49
+
50
+ def get_thinking_tokens(self):
51
+ budget = self.get_raw_thinking_tokens()
52
+
53
+ if budget is not None:
54
+ # Format as xx.yK for thousands, xx.yM for millions
55
+ if budget >= 1024 * 1024:
56
+ value = budget / (1024 * 1024)
57
+ if value == int(value):
58
+ return f"{int(value)}M"
59
+ else:
60
+ return f"{value:.1f}M"
61
+ else:
62
+ value = budget / 1024
63
+ if value == int(value):
64
+ return f"{int(value)}k"
65
+ else:
66
+ return f"{value:.1f}k"
67
+ return None
68
+
69
+ def get_reasoning_effort(self):
70
+ """Get reasoning effort value if available"""
71
+ return self.reasoning_effort
72
+
73
+
74
+ def set_reasoning_effort(self, effort):
75
+ """Set the reasoning effort parameter for models that support it"""
76
+ if effort is not None:
77
+ self.reasoning_effort = effort
78
+
79
+
80
+ def set_thinking_tokens(self, value):
81
+ """
82
+ Set the thinking token budget for models that support it.
83
+ Accepts formats: 8096, "8k", "10.5k", "0.5M", "10K", etc.
84
+ Pass "0" to disable thinking tokens.
85
+ """
86
+ if value is not None:
87
+ num_tokens = self.parse_token_value(value)
88
+ self.temperature = None
89
+ if num_tokens > 0:
90
+ self.thinking_tokens = num_tokens
91
+ else:
92
+ self.thinking_tokens = None
93
+
94
+
95
+ def parse_token_value(self, value):
96
+ """
97
+ Parse a token value string into an integer.
98
+ Accepts formats: 8096, "8k", "10.5k", "0.5M", "10K", etc.
99
+
100
+ Args:
101
+ value: String or int token value
102
+
103
+ Returns:
104
+ Integer token value
105
+ """
106
+ if isinstance(value, int):
107
+ return value
108
+
109
+ if not isinstance(value, str):
110
+ return int(value) # Try to convert to int
111
+
112
+ value = value.strip().upper()
113
+
114
+ if value.endswith("K"):
115
+ multiplier = 1024
116
+ value = value[:-1]
117
+ elif value.endswith("M"):
118
+ multiplier = 1024 * 1024
119
+ value = value[:-1]
120
+ else:
121
+ multiplier = 1
122
+
123
+ # Convert to float first to handle decimal values like "10.5k"
124
+ return int(float(value) * multiplier)
125
+
126
+
127
+ @staticmethod
128
+ def get_default_config():
129
+ project_root = Path(__file__).parent.parent.parent
130
+ config_path = project_root / "agent_config.yaml"
131
+ llm_config = {}
132
+ if os.path.exists(config_path):
133
+ try:
134
+ with open(config_path, 'r', encoding='utf-8') as f:
135
+ config = yaml.safe_load(f)
136
+ llm_config = config.get('llm_config', {})
137
+ except Exception as e:
138
+ logging.warning(f"Failed to read agent config file for repo map instance: {str(e)}")
139
+ model_name = llm_config.get('model_name')
140
+ provider = llm_config.get('provider')
141
+
142
+ model_config = ModelRunConfig(model_name)
143
+
144
+ model_config.provider = provider
145
+ return model_config
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
File without changes