quantalogic 0.2.27__tar.gz → 0.2.30__tar.gz

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 (92) hide show
  1. {quantalogic-0.2.27 → quantalogic-0.2.30}/PKG-INFO +3 -1
  2. {quantalogic-0.2.27 → quantalogic-0.2.30}/README.md +2 -0
  3. {quantalogic-0.2.27 → quantalogic-0.2.30}/pyproject.toml +1 -1
  4. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/main.py +49 -2
  5. quantalogic-0.2.30/quantalogic/tools/execute_bash_command_tool.py +200 -0
  6. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/replace_in_file_tool.py +23 -13
  7. quantalogic-0.2.27/quantalogic/tools/execute_bash_command_tool.py +0 -116
  8. {quantalogic-0.2.27 → quantalogic-0.2.30}/LICENSE +0 -0
  9. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/__init__.py +0 -0
  10. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/agent.py +0 -0
  11. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/agent_config.py +0 -0
  12. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/agent_factory.py +0 -0
  13. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/coding_agent.py +0 -0
  14. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/console_print_events.py +0 -0
  15. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/console_print_token.py +0 -0
  16. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/docs_cli.py +0 -0
  17. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/event_emitter.py +0 -0
  18. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/generative_model.py +0 -0
  19. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/get_model_info.py +0 -0
  20. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/interactive_text_editor.py +0 -0
  21. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/memory.py +0 -0
  22. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/model_names.py +0 -0
  23. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/prompts.py +0 -0
  24. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/search_agent.py +0 -0
  25. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/__init__.py +0 -0
  26. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/agent_server.py +0 -0
  27. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/models.py +0 -0
  28. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/routes.py +0 -0
  29. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/state.py +0 -0
  30. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/static/js/event_visualizer.js +0 -0
  31. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/static/js/quantalogic.js +0 -0
  32. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/server/templates/index.html +0 -0
  33. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/task_file_reader.py +0 -0
  34. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/task_runner.py +0 -0
  35. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tool_manager.py +0 -0
  36. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/__init__.py +0 -0
  37. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/agent_tool.py +0 -0
  38. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/dalle_e.py +0 -0
  39. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/download_http_file_tool.py +0 -0
  40. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/duckduckgo_search_tool.py +0 -0
  41. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/edit_whole_content_tool.py +0 -0
  42. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/elixir_tool.py +0 -0
  43. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/generate_database_report_tool.py +0 -0
  44. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/grep_app_tool.py +0 -0
  45. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/input_question_tool.py +0 -0
  46. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/jinja_tool.py +0 -0
  47. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/__init__.py +0 -0
  48. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/c_handler.py +0 -0
  49. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/cpp_handler.py +0 -0
  50. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/go_handler.py +0 -0
  51. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/java_handler.py +0 -0
  52. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/javascript_handler.py +0 -0
  53. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/python_handler.py +0 -0
  54. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/rust_handler.py +0 -0
  55. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/scala_handler.py +0 -0
  56. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/language_handlers/typescript_handler.py +0 -0
  57. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/list_directory_tool.py +0 -0
  58. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/llm_tool.py +0 -0
  59. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/llm_vision_tool.py +0 -0
  60. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/markitdown_tool.py +0 -0
  61. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/nodejs_tool.py +0 -0
  62. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/python_tool.py +0 -0
  63. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/read_file_block_tool.py +0 -0
  64. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/read_file_tool.py +0 -0
  65. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/read_html_tool.py +0 -0
  66. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/ripgrep_tool.py +0 -0
  67. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/search_definition_names.py +0 -0
  68. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/serpapi_search_tool.py +0 -0
  69. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/sql_query_tool.py +0 -0
  70. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/task_complete_tool.py +0 -0
  71. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/tool.py +0 -0
  72. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/unified_diff_tool.py +0 -0
  73. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/utils/__init__.py +0 -0
  74. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/utils/create_sample_database.py +0 -0
  75. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/utils/generate_database_report.py +0 -0
  76. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/wikipedia_search_tool.py +0 -0
  77. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/tools/write_file_tool.py +0 -0
  78. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/__init__.py +0 -0
  79. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/ask_user_validation.py +0 -0
  80. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/check_version.py +0 -0
  81. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/download_http_file.py +0 -0
  82. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/get_coding_environment.py +0 -0
  83. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/get_environment.py +0 -0
  84. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/get_quantalogic_rules_content.py +0 -0
  85. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/git_ls.py +0 -0
  86. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/read_file.py +0 -0
  87. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/utils/read_http_text_content.py +0 -0
  88. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/version.py +0 -0
  89. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/version_check.py +0 -0
  90. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/welcome_message.py +0 -0
  91. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/xml_parser.py +0 -0
  92. {quantalogic-0.2.27 → quantalogic-0.2.30}/quantalogic/xml_tool_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: quantalogic
3
- Version: 0.2.27
3
+ Version: 0.2.30
4
4
  Summary: QuantaLogic ReAct Agents
5
5
  Author: Raphaël MANSUY
6
6
  Author-email: raphael.mansuy@gmail.com
@@ -63,8 +63,10 @@ Description-Content-Type: text/markdown
63
63
  [![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://quantalogic.github.io/quantalogic/)
64
64
 
65
65
 
66
+
66
67
  QuantaLogic is a ReAct (Reasoning & Action) framework for building advanced AI agents.
67
68
 
69
+
68
70
  It seamlessly integrates large language models (LLMs) with a robust tool system, enabling agents to understand, reason about, and execute complex tasks through natural language interaction.
69
71
 
70
72
  The `cli` version include coding capabilities comparable to Aider.
@@ -5,8 +5,10 @@
5
5
  [![Documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://quantalogic.github.io/quantalogic/)
6
6
 
7
7
 
8
+
8
9
  QuantaLogic is a ReAct (Reasoning & Action) framework for building advanced AI agents.
9
10
 
11
+
10
12
  It seamlessly integrates large language models (LLMs) with a robust tool system, enabling agents to understand, reason about, and execute complex tasks through natural language interaction.
11
13
 
12
14
  The `cli` version include coding capabilities comparable to Aider.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "quantalogic"
3
- version = "0.2.27"
3
+ version = "0.2.30"
4
4
  description = "QuantaLogic ReAct Agents"
5
5
  authors = ["Raphaël MANSUY <raphael.mansuy@gmail.com>"]
6
6
  readme = "README.md"
@@ -23,9 +23,52 @@ from quantalogic.agent_config import ( # noqa: E402
23
23
  )
24
24
  from quantalogic.task_runner import task_runner # noqa: E402
25
25
 
26
+ # Platform-specific imports
27
+ try:
28
+ if sys.platform == 'win32':
29
+ import msvcrt # Built-in Windows module
30
+ else:
31
+ import termios
32
+ import tty
33
+ except ImportError as e:
34
+ logger.warning(f"Could not import platform-specific module: {e}")
35
+ # Fall back to basic terminal handling if imports fail
36
+ msvcrt = None
37
+ termios = None
38
+ tty = None
39
+
26
40
  AGENT_MODES = ["code", "basic", "interpreter", "full", "code-basic", "search", "search-full"]
27
41
 
28
42
 
43
+ def setup_terminal():
44
+ """Configure terminal settings based on platform."""
45
+ if sys.platform == 'win32':
46
+ if msvcrt:
47
+ return None # Windows terminal is already configured
48
+ logger.warning("msvcrt module not available on Windows")
49
+ return None
50
+ else:
51
+ if termios and tty:
52
+ try:
53
+ fd = sys.stdin.fileno()
54
+ old_settings = termios.tcgetattr(fd)
55
+ tty.setraw(fd)
56
+ return old_settings
57
+ except (termios.error, AttributeError) as e:
58
+ logger.warning(f"Failed to configure terminal: {e}")
59
+ return None
60
+ return None
61
+
62
+
63
+ def restore_terminal(old_settings):
64
+ """Restore terminal settings based on platform."""
65
+ if sys.platform != 'win32' and termios and old_settings:
66
+ try:
67
+ termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings)
68
+ except (termios.error, AttributeError) as e:
69
+ logger.warning(f"Failed to restore terminal settings: {e}")
70
+
71
+
29
72
  @click.group(invoke_without_command=True)
30
73
  @click.option(
31
74
  "--compact-every-n-iteration",
@@ -181,8 +224,12 @@ def task(
181
224
 
182
225
 
183
226
  def main():
184
- """Main Entry point"""
185
- cli()
227
+ """Main entry point."""
228
+ old_settings = setup_terminal()
229
+ try:
230
+ cli() # type: ignore
231
+ finally:
232
+ restore_terminal(old_settings)
186
233
 
187
234
 
188
235
  if __name__ == "__main__":
@@ -0,0 +1,200 @@
1
+ """Tool for executing bash commands with interactive input support."""
2
+
3
+ import os
4
+ import select
5
+ import signal
6
+ import subprocess
7
+ import sys
8
+ from typing import Dict, Optional, Union
9
+
10
+ from loguru import logger
11
+
12
+ # Platform-specific imports
13
+ try:
14
+ if sys.platform != 'win32':
15
+ import pty
16
+ except ImportError as e:
17
+ logger.warning(f"Could not import platform-specific module: {e}")
18
+ pty = None
19
+
20
+ from quantalogic.tools.tool import Tool, ToolArgument
21
+
22
+
23
+ class ExecuteBashCommandTool(Tool):
24
+ """Tool for executing bash commands with real-time I/O handling."""
25
+
26
+ name: str = "execute_bash_tool"
27
+ description: str = "Executes a bash command and returns its output."
28
+ need_validation: bool = True
29
+ arguments: list = [
30
+ ToolArgument(
31
+ name="command",
32
+ arg_type="string",
33
+ description="The bash command to execute.",
34
+ required=True,
35
+ example="ls -la",
36
+ ),
37
+ ToolArgument(
38
+ name="working_dir",
39
+ arg_type="string",
40
+ description="The working directory where the command will be executed. Defaults to the current directory.",
41
+ required=False,
42
+ example="/path/to/directory",
43
+ ),
44
+ ToolArgument(
45
+ name="timeout",
46
+ arg_type="int",
47
+ description="Maximum time in seconds to wait for the command to complete. Defaults to 60 seconds.",
48
+ required=False,
49
+ example="60",
50
+ ),
51
+ ]
52
+
53
+ def _execute_windows(
54
+ self,
55
+ command: str,
56
+ cwd: str,
57
+ timeout_seconds: int,
58
+ env_vars: Dict[str, str],
59
+ ) -> str:
60
+ """Execute command on Windows platform."""
61
+ try:
62
+ # On Windows, use subprocess with pipes
63
+ process = subprocess.Popen(
64
+ command,
65
+ shell=True,
66
+ stdin=subprocess.PIPE,
67
+ stdout=subprocess.PIPE,
68
+ stderr=subprocess.PIPE,
69
+ cwd=cwd,
70
+ env=env_vars,
71
+ text=True,
72
+ encoding='utf-8'
73
+ )
74
+
75
+ try:
76
+ stdout, stderr = process.communicate(timeout=timeout_seconds)
77
+ return_code = process.returncode
78
+
79
+ if return_code != 0 and stderr:
80
+ logger.warning(f"Command failed with error: {stderr}")
81
+
82
+ formatted_result = (
83
+ "<command_output>"
84
+ f" <stdout>{stdout.strip()}</stdout>"
85
+ f" <returncode>{return_code}</returncode>"
86
+ f"</command_output>"
87
+ )
88
+ return formatted_result
89
+
90
+ except subprocess.TimeoutExpired:
91
+ process.kill()
92
+ return f"Command timed out after {timeout_seconds} seconds."
93
+
94
+ except Exception as e:
95
+ return f"Unexpected error executing command: {str(e)}"
96
+
97
+ def _execute_unix(
98
+ self,
99
+ command: str,
100
+ cwd: str,
101
+ timeout_seconds: int,
102
+ env_vars: Dict[str, str],
103
+ ) -> str:
104
+ """Execute command on Unix platform."""
105
+ try:
106
+ master, slave = pty.openpty()
107
+ proc = subprocess.Popen(
108
+ command,
109
+ shell=True,
110
+ stdin=slave,
111
+ stdout=slave,
112
+ stderr=subprocess.STDOUT,
113
+ cwd=cwd,
114
+ env=env_vars,
115
+ preexec_fn=os.setsid,
116
+ close_fds=True,
117
+ )
118
+ os.close(slave)
119
+
120
+ stdout_buffer = []
121
+ break_loop = False
122
+
123
+ try:
124
+ while True:
125
+ rlist, _, _ = select.select([master, sys.stdin], [], [], timeout_seconds)
126
+ if not rlist:
127
+ if proc.poll() is not None:
128
+ break # Process completed but select timed out
129
+ raise subprocess.TimeoutExpired(command, timeout_seconds)
130
+
131
+ for fd in rlist:
132
+ if fd == master:
133
+ data = os.read(master, 1024).decode()
134
+ if not data:
135
+ break_loop = True
136
+ break
137
+ stdout_buffer.append(data)
138
+ sys.stdout.write(data)
139
+ sys.stdout.flush()
140
+ elif fd == sys.stdin:
141
+ user_input = os.read(sys.stdin.fileno(), 1024)
142
+ os.write(master, user_input)
143
+
144
+ if break_loop or proc.poll() is not None:
145
+ while True:
146
+ data = os.read(master, 1024).decode()
147
+ if not data:
148
+ break
149
+ stdout_buffer.append(data)
150
+ sys.stdout.write(data)
151
+ sys.stdout.flush()
152
+ break
153
+
154
+ except subprocess.TimeoutExpired:
155
+ os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
156
+ return f"Command timed out after {timeout_seconds} seconds."
157
+ except EOFError:
158
+ pass # Process exited normally
159
+ finally:
160
+ os.close(master)
161
+ proc.wait()
162
+
163
+ stdout_content = ''.join(stdout_buffer)
164
+ return_code = proc.returncode
165
+ formatted_result = (
166
+ "<command_output>"
167
+ f" <stdout>{stdout_content.strip()}</stdout>"
168
+ f" <returncode>{return_code}</returncode>"
169
+ f"</command_output>"
170
+ )
171
+ return formatted_result
172
+ except Exception as e:
173
+ return f"Unexpected error executing command: {str(e)}"
174
+
175
+ def execute(
176
+ self,
177
+ command: str,
178
+ working_dir: Optional[str] = None,
179
+ timeout: Union[int, str, None] = 60,
180
+ env: Optional[Dict[str, str]] = None,
181
+ ) -> str:
182
+ """Executes a bash command with interactive input handling."""
183
+ timeout_seconds = int(timeout) if timeout else 60
184
+ cwd = working_dir or os.getcwd()
185
+ env_vars = os.environ.copy()
186
+ if env:
187
+ env_vars.update(env)
188
+
189
+ if sys.platform == 'win32':
190
+ return self._execute_windows(command, cwd, timeout_seconds, env_vars)
191
+ else:
192
+ if not pty:
193
+ logger.warning("PTY module not available, falling back to Windows-style execution")
194
+ return self._execute_windows(command, cwd, timeout_seconds, env_vars)
195
+ return self._execute_unix(command, cwd, timeout_seconds, env_vars)
196
+
197
+
198
+ if __name__ == "__main__":
199
+ tool = ExecuteBashCommandTool()
200
+ print(tool.to_markdown())
@@ -65,7 +65,6 @@ class ReplaceInFileTool(Tool):
65
65
  )
66
66
  need_validation: bool = True
67
67
 
68
- # Adjust this threshold to allow more or less approximate matching
69
68
  SIMILARITY_THRESHOLD: float = 0.85
70
69
 
71
70
  arguments: list[ToolArgument] = [
@@ -124,6 +123,15 @@ class ReplaceInFileTool(Tool):
124
123
  ),
125
124
  ]
126
125
 
126
+ def normalize_whitespace(self, text: str) -> str:
127
+ """Normalize leading whitespace by converting tabs to spaces."""
128
+ return '\n'.join([self._normalize_line(line) for line in text.split('\n')])
129
+
130
+ def _normalize_line(self, line: str) -> str:
131
+ """Normalize leading whitespace in a single line."""
132
+ leading_ws = len(line) - len(line.lstrip())
133
+ return line.replace('\t', ' ', leading_ws) # Convert tabs to 4 spaces only in leading whitespace
134
+
127
135
  def parse_diff(self, diff: str) -> list[SearchReplaceBlock]:
128
136
  """Parses the diff string into a list of SearchReplaceBlock instances."""
129
137
  if not diff or not diff.strip():
@@ -250,6 +258,7 @@ class ReplaceInFileTool(Tool):
250
258
  except Exception as e:
251
259
  return f"Error: Failed to write changes to '{path}': {str(e) or 'Unknown error'}"
252
260
 
261
+ # Maintain original success message format
253
262
  message = [f"Successfully modified '{path}'"]
254
263
  for idx, block in enumerate(blocks, 1):
255
264
  status = "Exact match" if block.similarity is None else f"Similar match ({block.similarity:.1%})"
@@ -267,26 +276,27 @@ class ReplaceInFileTool(Tool):
267
276
  return f"Error: Unexpected error occurred - {error_msg or 'Unknown error'}"
268
277
 
269
278
  def find_similar_match(self, search: str, content: str) -> Tuple[float, str]:
270
- """Finds the most similar substring in content compared to search."""
271
- if not search or not content:
272
- return 0.0, ""
273
-
274
- search_lines = search.splitlines()
275
- content_lines = content.splitlines()
279
+ """Finds the most similar substring in content compared to search with whitespace normalization."""
280
+ norm_search = self.normalize_whitespace(search)
281
+ content_lines = content.split('\n')
282
+ norm_content = self.normalize_whitespace(content)
283
+ norm_content_lines = norm_content.split('\n')
276
284
 
277
- if len(search_lines) > len(content_lines):
285
+ if len(norm_content_lines) < len(norm_search.split('\n')):
278
286
  return 0.0, ""
279
287
 
280
288
  max_similarity = 0.0
281
289
  best_match = ""
290
+ search_line_count = len(norm_search.split('\n'))
282
291
 
283
- for i in range(len(content_lines) - len(search_lines) + 1):
284
- candidate = "\n".join(content_lines[i : i + len(search_lines)])
285
- similarity = difflib.SequenceMatcher(None, search, candidate).ratio()
292
+ for i in range(len(norm_content_lines) - search_line_count + 1):
293
+ candidate_norm = '\n'.join(norm_content_lines[i:i+search_line_count])
294
+ similarity = difflib.SequenceMatcher(None, norm_search, candidate_norm).ratio()
286
295
 
287
296
  if similarity > max_similarity:
288
297
  max_similarity = similarity
289
- best_match = candidate
298
+ # Get original lines (non-normalized) for accurate replacement
299
+ best_match = '\n'.join(content_lines[i:i+search_line_count])
290
300
 
291
301
  return max_similarity, best_match
292
302
 
@@ -297,4 +307,4 @@ class ReplaceInFileTool(Tool):
297
307
 
298
308
  if __name__ == "__main__":
299
309
  tool = ReplaceInFileTool()
300
- print(tool.to_markdown())
310
+ print(tool.to_markdown())
@@ -1,116 +0,0 @@
1
- """Tool for executing bash commands and capturing their output."""
2
-
3
- import os
4
- import subprocess
5
- from typing import Dict, Optional, Union
6
-
7
- from quantalogic.tools.tool import Tool, ToolArgument
8
-
9
-
10
- class ExecuteBashCommandTool(Tool):
11
- """Tool for executing bash commands and capturing their output."""
12
-
13
- name: str = "execute_bash_tool"
14
- description: str = "Executes a bash command and returns its output."
15
- need_validation: bool = True
16
- arguments: list = [
17
- ToolArgument(
18
- name="command",
19
- arg_type="string",
20
- description="The bash command to execute.",
21
- required=True,
22
- example="ls -la",
23
- ),
24
- ToolArgument(
25
- name="working_dir",
26
- arg_type="string",
27
- description="The working directory where the command will be executed. Defaults to the current directory.",
28
- required=False,
29
- example="/path/to/directory",
30
- ),
31
- ToolArgument(
32
- name="timeout",
33
- arg_type="int",
34
- description="Maximum time in seconds to wait for the command to complete. Defaults to 60 seconds.",
35
- required=False,
36
- example="60",
37
- ),
38
- # Removed the `env` argument from ToolArgument since it doesn't support `dict` type
39
- ]
40
-
41
- def execute(
42
- self,
43
- command: str,
44
- working_dir: Optional[str] = None,
45
- timeout: Union[int, str, None] = 60,
46
- env: Optional[Dict[str, str]] = None,
47
- ) -> str:
48
- """Executes a bash command and returns its output.
49
-
50
- Args:
51
- command (str): The bash command to execute.
52
- working_dir (str, optional): Working directory for command execution. Defaults to the current directory.
53
- timeout (int or str, optional): Maximum execution time in seconds. Defaults to 60 seconds.
54
- env (dict, optional): Environment variables to set for the command execution. Defaults to the current environment.
55
-
56
- Returns:
57
- str: The command output or error message.
58
-
59
- Raises:
60
- subprocess.TimeoutExpired: If the command execution exceeds the timeout.
61
- subprocess.CalledProcessError: If the command returns a non-zero exit status.
62
- ValueError: If the timeout cannot be converted to an integer.
63
- """
64
- # Convert timeout to integer, defaulting to 60 if None or invalid
65
- try:
66
- timeout_seconds = int(timeout) if timeout else 60
67
- except (ValueError, TypeError):
68
- timeout_seconds = 60
69
-
70
- # Use the current working directory if no working directory is specified
71
- cwd = working_dir if working_dir else os.getcwd()
72
-
73
- # Use the current environment if no custom environment is specified
74
- env_vars = os.environ.copy()
75
- if env:
76
- env_vars.update(env)
77
-
78
- try:
79
- # Execute the command with specified timeout, working directory, and environment
80
- result = subprocess.run(
81
- command,
82
- shell=True,
83
- cwd=cwd,
84
- capture_output=True,
85
- text=True,
86
- timeout=timeout_seconds,
87
- env=env_vars,
88
- )
89
-
90
- formated_result = (
91
- "<command_output>"
92
- f" <stdout>"
93
- f"{result.stdout.strip()}"
94
- f" </stdout>"
95
- f" <stderr>"
96
- f"{result.stderr.strip()}"
97
- f" </stderr>"
98
- f" <returncode>"
99
- f" {result.returncode}"
100
- f" </returncode>"
101
- f"</command_output>"
102
- )
103
-
104
- return formated_result
105
-
106
- except subprocess.TimeoutExpired:
107
- return f"Command timed out after {timeout_seconds} seconds."
108
- except subprocess.CalledProcessError as e:
109
- return f"Command failed with error: {e.stderr.strip()}"
110
- except Exception as e:
111
- return f"Unexpected error executing command: {str(e)}"
112
-
113
-
114
- if __name__ == "__main__":
115
- tool = ExecuteBashCommandTool()
116
- print(tool.to_markdown())
File without changes