quantalogic 0.59.3__py3-none-any.whl → 0.61.0__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 (81) hide show
  1. quantalogic/agent.py +268 -24
  2. quantalogic/agent_config.py +5 -5
  3. quantalogic/agent_factory.py +2 -2
  4. quantalogic/codeact/__init__.py +0 -0
  5. quantalogic/codeact/agent.py +499 -0
  6. quantalogic/codeact/cli.py +232 -0
  7. quantalogic/codeact/constants.py +9 -0
  8. quantalogic/codeact/events.py +78 -0
  9. quantalogic/codeact/llm_util.py +76 -0
  10. quantalogic/codeact/prompts/error_format.j2 +11 -0
  11. quantalogic/codeact/prompts/generate_action.j2 +26 -0
  12. quantalogic/codeact/prompts/generate_program.j2 +39 -0
  13. quantalogic/codeact/prompts/response_format.j2 +11 -0
  14. quantalogic/codeact/tools_manager.py +135 -0
  15. quantalogic/codeact/utils.py +135 -0
  16. quantalogic/coding_agent.py +2 -2
  17. quantalogic/create_custom_agent.py +26 -78
  18. quantalogic/prompts/chat_system_prompt.j2 +10 -7
  19. quantalogic/prompts/code_2_system_prompt.j2 +190 -0
  20. quantalogic/prompts/code_system_prompt.j2 +142 -0
  21. quantalogic/prompts/doc_system_prompt.j2 +178 -0
  22. quantalogic/prompts/legal_2_system_prompt.j2 +218 -0
  23. quantalogic/prompts/legal_system_prompt.j2 +140 -0
  24. quantalogic/prompts/system_prompt.j2 +6 -2
  25. quantalogic/prompts/tools_prompt.j2 +2 -4
  26. quantalogic/prompts.py +23 -4
  27. quantalogic/python_interpreter/__init__.py +23 -0
  28. quantalogic/python_interpreter/assignment_visitors.py +63 -0
  29. quantalogic/python_interpreter/base_visitors.py +20 -0
  30. quantalogic/python_interpreter/class_visitors.py +22 -0
  31. quantalogic/python_interpreter/comprehension_visitors.py +172 -0
  32. quantalogic/python_interpreter/context_visitors.py +59 -0
  33. quantalogic/python_interpreter/control_flow_visitors.py +88 -0
  34. quantalogic/python_interpreter/exception_visitors.py +109 -0
  35. quantalogic/python_interpreter/exceptions.py +39 -0
  36. quantalogic/python_interpreter/execution.py +202 -0
  37. quantalogic/python_interpreter/function_utils.py +386 -0
  38. quantalogic/python_interpreter/function_visitors.py +209 -0
  39. quantalogic/python_interpreter/import_visitors.py +28 -0
  40. quantalogic/python_interpreter/interpreter_core.py +358 -0
  41. quantalogic/python_interpreter/literal_visitors.py +74 -0
  42. quantalogic/python_interpreter/misc_visitors.py +148 -0
  43. quantalogic/python_interpreter/operator_visitors.py +108 -0
  44. quantalogic/python_interpreter/scope.py +10 -0
  45. quantalogic/python_interpreter/visit_handlers.py +110 -0
  46. quantalogic/server/agent_server.py +1 -1
  47. quantalogic/tools/__init__.py +6 -3
  48. quantalogic/tools/action_gen.py +366 -0
  49. quantalogic/tools/duckduckgo_search_tool.py +1 -0
  50. quantalogic/tools/execute_bash_command_tool.py +114 -57
  51. quantalogic/tools/file_tracker_tool.py +49 -0
  52. quantalogic/tools/google_packages/google_news_tool.py +3 -0
  53. quantalogic/tools/image_generation/dalle_e.py +89 -137
  54. quantalogic/tools/python_tool.py +13 -0
  55. quantalogic/tools/rag_tool/__init__.py +2 -9
  56. quantalogic/tools/rag_tool/document_rag_sources_.py +728 -0
  57. quantalogic/tools/rag_tool/ocr_pdf_markdown.py +144 -0
  58. quantalogic/tools/replace_in_file_tool.py +1 -1
  59. quantalogic/tools/{search_definition_names.py → search_definition_names_tool.py} +2 -2
  60. quantalogic/tools/terminal_capture_tool.py +293 -0
  61. quantalogic/tools/tool.py +120 -22
  62. quantalogic/tools/utilities/__init__.py +2 -0
  63. quantalogic/tools/utilities/download_file_tool.py +3 -5
  64. quantalogic/tools/utilities/llm_tool.py +283 -0
  65. quantalogic/tools/utilities/selenium_tool.py +296 -0
  66. quantalogic/tools/utilities/vscode_tool.py +1 -1
  67. quantalogic/tools/web_navigation/__init__.py +5 -0
  68. quantalogic/tools/web_navigation/web_tool.py +145 -0
  69. quantalogic/tools/write_file_tool.py +72 -36
  70. quantalogic/utils/__init__.py +0 -1
  71. quantalogic/utils/test_python_interpreter.py +119 -0
  72. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/METADATA +7 -2
  73. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/RECORD +76 -35
  74. quantalogic/tools/rag_tool/document_metadata.py +0 -15
  75. quantalogic/tools/rag_tool/query_response.py +0 -20
  76. quantalogic/tools/rag_tool/rag_tool.py +0 -566
  77. quantalogic/tools/rag_tool/rag_tool_beta.py +0 -264
  78. quantalogic/utils/python_interpreter.py +0 -905
  79. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/LICENSE +0 -0
  80. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/WHEEL +0 -0
  81. {quantalogic-0.59.3.dist-info → quantalogic-0.61.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,296 @@
1
+ """Selenium Tool for web automation and testing.
2
+
3
+ This tool provides a high-level interface for web automation tasks using Selenium WebDriver.
4
+ It supports common web interactions like navigation, form filling, and element manipulation.
5
+ """
6
+
7
+ import asyncio
8
+ from typing import Optional, List, Dict, Any
9
+ from selenium import webdriver
10
+ from selenium.webdriver.common.by import By
11
+ from selenium.webdriver.support.ui import WebDriverWait
12
+ from selenium.webdriver.support import expected_conditions as EC
13
+ from selenium.webdriver.chrome.service import Service
14
+ from selenium.webdriver.chrome.options import Options
15
+ from webdriver_manager.chrome import ChromeDriverManager
16
+ from pydantic import Field, ConfigDict
17
+ from loguru import logger
18
+
19
+ from quantalogic.tools.tool import Tool, ToolArgument
20
+
21
+ class SeleniumTool(Tool):
22
+ """Tool for web automation using Selenium WebDriver."""
23
+
24
+ model_config = ConfigDict(arbitrary_types_allowed=True)
25
+
26
+ name: str = Field(default="selenium_tool")
27
+ description: str = Field(
28
+ default=(
29
+ "Automates web browser interactions using Selenium WebDriver. "
30
+ "Supports navigation, form filling, clicking, and extracting content. "
31
+ "Uses Chrome browser in headless mode by default."
32
+ )
33
+ )
34
+ arguments: list = Field(
35
+ default=[
36
+ ToolArgument(
37
+ name="action",
38
+ arg_type="string",
39
+ description=(
40
+ "The automation action to perform. Available actions: "
41
+ "navigate, click, type, extract_text, extract_attribute, wait_for_element"
42
+ ),
43
+ required=True,
44
+ example="navigate",
45
+ ),
46
+ ToolArgument(
47
+ name="url",
48
+ arg_type="string",
49
+ description="URL to navigate to (required for 'navigate' action)",
50
+ required=False,
51
+ example="https://example.com",
52
+ ),
53
+ ToolArgument(
54
+ name="selector",
55
+ arg_type="string",
56
+ description="CSS or XPath selector for target element",
57
+ required=False,
58
+ example="#login-button",
59
+ ),
60
+ ToolArgument(
61
+ name="selector_type",
62
+ arg_type="string",
63
+ description="Type of selector (css, xpath, id, name, class_name)",
64
+ required=False,
65
+ default="css",
66
+ example="css",
67
+ ),
68
+ ToolArgument(
69
+ name="value",
70
+ arg_type="string",
71
+ description="Value to type or attribute to extract",
72
+ required=False,
73
+ example="username123",
74
+ ),
75
+ ToolArgument(
76
+ name="timeout",
77
+ arg_type="int",
78
+ description="Maximum time to wait for element (seconds)",
79
+ required=False,
80
+ default="10",
81
+ example="10",
82
+ ),
83
+ ]
84
+ )
85
+
86
+ driver: Optional[webdriver.Chrome] = Field(default=None, exclude=True)
87
+ headless: bool = Field(default=True, description="Run browser in headless mode")
88
+ custom_options: List[str] = Field(default_factory=list, description="Custom Chrome options")
89
+
90
+ def __init__(
91
+ self,
92
+ headless: bool = True,
93
+ custom_options: Optional[List[str]] = None,
94
+ name: str = "selenium_tool"
95
+ ):
96
+ """Initialize SeleniumTool with browser configuration.
97
+
98
+ Args:
99
+ headless (bool): Run browser in headless mode. Defaults to True.
100
+ custom_options (List[str], optional): Custom Chrome options.
101
+ name (str): Name of the tool instance.
102
+ """
103
+ super().__init__(
104
+ **{
105
+ "headless": headless,
106
+ "custom_options": custom_options or [],
107
+ "name": name,
108
+ }
109
+ )
110
+ self._initialize_driver()
111
+
112
+ def _initialize_driver(self):
113
+ """Initialize Chrome WebDriver with configured options."""
114
+ try:
115
+ chrome_options = Options()
116
+ if self.headless:
117
+ chrome_options.add_argument("--headless")
118
+
119
+ # Add common options for stability
120
+ chrome_options.add_argument("--no-sandbox")
121
+ chrome_options.add_argument("--disable-dev-shm-usage")
122
+
123
+ # Add custom options
124
+ for option in self.custom_options:
125
+ chrome_options.add_argument(option)
126
+
127
+ # Initialize the driver
128
+ service = Service(ChromeDriverManager().install())
129
+ self.driver = webdriver.Chrome(service=service, options=chrome_options)
130
+ self.driver.implicitly_wait(5)
131
+ logger.info("Successfully initialized Chrome WebDriver")
132
+
133
+ except Exception as e:
134
+ logger.error(f"Failed to initialize WebDriver: {str(e)}")
135
+ raise
136
+
137
+ def _get_by_method(self, selector_type: str) -> By:
138
+ """Get the appropriate By method based on selector type.
139
+
140
+ Args:
141
+ selector_type: Type of selector (css, xpath, id, name, class_name)
142
+
143
+ Returns:
144
+ selenium.webdriver.common.by.By method
145
+ """
146
+ selector_types = {
147
+ "css": By.CSS_SELECTOR,
148
+ "xpath": By.XPATH,
149
+ "id": By.ID,
150
+ "name": By.NAME,
151
+ "class_name": By.CLASS_NAME,
152
+ }
153
+ return selector_types.get(selector_type.lower(), By.CSS_SELECTOR)
154
+
155
+ async def async_execute(
156
+ self,
157
+ action: str,
158
+ url: Optional[str] = None,
159
+ selector: Optional[str] = None,
160
+ selector_type: str = "css",
161
+ value: Optional[str] = None,
162
+ timeout: int = 10,
163
+ ) -> Dict[str, Any]:
164
+ """Execute a Selenium automation action asynchronously.
165
+
166
+ Args:
167
+ action: The automation action to perform
168
+ url: URL to navigate to (for navigate action)
169
+ selector: Element selector
170
+ selector_type: Type of selector (css, xpath, id, name, class_name)
171
+ value: Value to type or attribute to extract
172
+ timeout: Maximum time to wait for element (seconds)
173
+
174
+ Returns:
175
+ Dict containing action result and any extracted data
176
+ """
177
+ try:
178
+ if not self.driver:
179
+ self._initialize_driver()
180
+
181
+ result = {"success": False, "message": "", "data": None}
182
+
183
+ # Handle different actions
184
+ if action == "navigate":
185
+ if not url:
186
+ raise ValueError("URL is required for navigate action")
187
+ self.driver.get(url)
188
+ result["success"] = True
189
+ result["message"] = f"Successfully navigated to {url}"
190
+
191
+ elif action in ["click", "type", "extract_text", "extract_attribute", "wait_for_element"]:
192
+ if not selector:
193
+ raise ValueError("Selector is required for element actions")
194
+
195
+ # Wait for element
196
+ by_method = self._get_by_method(selector_type)
197
+ element = WebDriverWait(self.driver, timeout).until(
198
+ EC.presence_of_element_located((by_method, selector))
199
+ )
200
+
201
+ if action == "click":
202
+ element.click()
203
+ result["success"] = True
204
+ result["message"] = f"Successfully clicked element: {selector}"
205
+
206
+ elif action == "type":
207
+ if not value:
208
+ raise ValueError("Value is required for type action")
209
+ element.clear()
210
+ element.send_keys(value)
211
+ result["success"] = True
212
+ result["message"] = f"Successfully typed '{value}' into element: {selector}"
213
+
214
+ elif action == "extract_text":
215
+ text = element.text
216
+ result["success"] = True
217
+ result["message"] = "Successfully extracted text"
218
+ result["data"] = text
219
+
220
+ elif action == "extract_attribute":
221
+ if not value:
222
+ raise ValueError("Attribute name is required for extract_attribute action")
223
+ attr_value = element.get_attribute(value)
224
+ result["success"] = True
225
+ result["message"] = f"Successfully extracted attribute: {value}"
226
+ result["data"] = attr_value
227
+
228
+ elif action == "wait_for_element":
229
+ result["success"] = True
230
+ result["message"] = f"Element found: {selector}"
231
+
232
+ else:
233
+ raise ValueError(f"Unknown action: {action}")
234
+
235
+ return result
236
+
237
+ except Exception as e:
238
+ logger.error(f"Error in SeleniumTool.async_execute: {str(e)}")
239
+ return {
240
+ "success": False,
241
+ "message": f"Error: {str(e)}",
242
+ "data": None
243
+ }
244
+
245
+ def execute(
246
+ self,
247
+ action: str,
248
+ url: Optional[str] = None,
249
+ selector: Optional[str] = None,
250
+ selector_type: str = "css",
251
+ value: Optional[str] = None,
252
+ timeout: int = 10,
253
+ ) -> Dict[str, Any]:
254
+ """Synchronous wrapper for async_execute."""
255
+ return asyncio.run(
256
+ self.async_execute(
257
+ action=action,
258
+ url=url,
259
+ selector=selector,
260
+ selector_type=selector_type,
261
+ value=value,
262
+ timeout=timeout,
263
+ )
264
+ )
265
+
266
+ def __del__(self):
267
+ """Clean up WebDriver when the tool is destroyed."""
268
+ if self.driver:
269
+ try:
270
+ self.driver.quit()
271
+ logger.info("Successfully closed WebDriver")
272
+ except Exception as e:
273
+ logger.error(f"Error closing WebDriver: {str(e)}")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ # Example usage
278
+ tool = SeleniumTool(headless=True)
279
+
280
+ # Navigate to a website
281
+ result = tool.execute(
282
+ action="navigate",
283
+ url="https://example.com"
284
+ )
285
+ print("Navigation result:", result)
286
+
287
+ # Extract text from an element
288
+ result = tool.execute(
289
+ action="extract_text",
290
+ selector="h1",
291
+ selector_type="css"
292
+ )
293
+ print("Extracted text:", result)
294
+
295
+ # Clean up
296
+ del tool
@@ -15,7 +15,7 @@ class VSCodeServerTool(Tool):
15
15
 
16
16
  name: str = "vscode_tool"
17
17
  description: str = "Launches a VS Code Server instance for remote development."
18
- need_validation: bool = True
18
+ need_validation: bool = False
19
19
  arguments: list = [
20
20
  ToolArgument(
21
21
  name="workspace_path",
@@ -0,0 +1,5 @@
1
+ """Web navigation tools package."""
2
+
3
+ from quantalogic.tools.web_navigation.web_tool import WebNavigationTool
4
+
5
+ __all__ = ["WebNavigationTool"]
@@ -0,0 +1,145 @@
1
+ """Web Navigation Tool using browser-use for automated web interactions."""
2
+
3
+ import asyncio
4
+ from typing import Callable, Optional
5
+
6
+ from loguru import logger
7
+ from pydantic import ConfigDict, Field
8
+
9
+ from browser_use import Agent
10
+ from langchain_openai import ChatOpenAI
11
+ from quantalogic.tools.tool import Tool, ToolArgument
12
+
13
+
14
+ class WebNavigationTool(Tool):
15
+ """Tool for automated web navigation and interaction using browser-use."""
16
+
17
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
18
+
19
+ name: str = Field(default="web_navigation")
20
+ description: str = Field(
21
+ default=(
22
+ "Navigate and interact with web pages using natural language instructions. "
23
+ "This tool can perform tasks like searching, comparing prices, filling forms, "
24
+ "and extracting information from websites."
25
+ )
26
+ )
27
+ arguments: list = Field(
28
+ default=[
29
+ ToolArgument(
30
+ name="task",
31
+ arg_type="string",
32
+ description="The web navigation task to perform in natural language",
33
+ required=True,
34
+ example="Search Python documentation for asyncio examples",
35
+ ),
36
+ ToolArgument(
37
+ name="model_name",
38
+ arg_type="string",
39
+ description="The OpenAI model to use (e.g. gpt-3.5-turbo, gpt-4)",
40
+ required=True,
41
+ default="gpt-3.5-turbo",
42
+ example="gpt-3.5-turbo",
43
+ ),
44
+ ]
45
+ )
46
+
47
+ llm: Optional[ChatOpenAI] = Field(default=None, exclude=True)
48
+ agent: Optional[Agent] = Field(default=None, exclude=True)
49
+
50
+ def __init__(
51
+ self,
52
+ model_name: str = "gpt-3.5-turbo",
53
+ name: str = "web_navigation",
54
+ ) -> None:
55
+ """Initialize the WebNavigationTool.
56
+
57
+ Args:
58
+ model_name: OpenAI model to use. Defaults to "gpt-3.5-turbo".
59
+ name: Name of the tool instance. Defaults to "web_navigation".
60
+ """
61
+ super().__init__(
62
+ **{
63
+ "name": name,
64
+ "model_name": model_name,
65
+ }
66
+ )
67
+ self.model_post_init(None)
68
+
69
+ def model_post_init(self, __context: None) -> None:
70
+ """Initialize the LLM after model initialization.
71
+
72
+ Args:
73
+ __context: Unused context parameter.
74
+
75
+ Raises:
76
+ ValueError: If LLM initialization fails.
77
+ """
78
+ try:
79
+ self.llm = ChatOpenAI(model=self.model_name)
80
+ logger.debug(f"Initialized WebNavigationTool with model: {self.model_name}")
81
+ except Exception as e:
82
+ logger.error(f"Error initializing LLM: {e}")
83
+ raise ValueError(f"Failed to initialize LLM: {e}") from e
84
+
85
+ async def async_execute(self, task: str, model_name: Optional[str] = None) -> str:
86
+ """Execute the web navigation task asynchronously.
87
+
88
+ Args:
89
+ task: The web navigation task to perform.
90
+ model_name: Optional override for the LLM model.
91
+
92
+ Returns:
93
+ The result of the web navigation task.
94
+
95
+ Raises:
96
+ ValueError: If task is empty or LLM is not initialized.
97
+ RuntimeError: If web navigation fails.
98
+ """
99
+ if not task:
100
+ raise ValueError("Task cannot be empty")
101
+
102
+ if not self.llm:
103
+ raise ValueError("LLM not initialized")
104
+
105
+ try:
106
+ # Create a new Agent instance for each task
107
+ agent = Agent(
108
+ task=task,
109
+ llm=self.llm,
110
+ )
111
+
112
+ # Run the agent
113
+ result = await agent.run()
114
+ logger.debug(f"Completed web navigation task: {task}")
115
+ return result
116
+
117
+ except Exception as e:
118
+ logger.error(f"Error during web navigation: {e}")
119
+ raise RuntimeError(f"Web navigation failed: {e}") from e
120
+
121
+ def execute(self, task: str, model_name: Optional[str] = None) -> str:
122
+ """Execute the web navigation task synchronously.
123
+
124
+ Args:
125
+ task: The web navigation task to perform.
126
+ model_name: Optional override for the LLM model.
127
+
128
+ Returns:
129
+ The result of the web navigation task.
130
+ """
131
+ return asyncio.run(self.async_execute(task=task, model_name=model_name))
132
+
133
+
134
+ if __name__ == "__main__":
135
+ # Example usage
136
+ tool = WebNavigationTool()
137
+ task = "Search Python documentation for asyncio examples"
138
+
139
+ try:
140
+ # Synchronous execution
141
+ result = tool.execute(task=task)
142
+ print("Navigation Result:")
143
+ print(result)
144
+ except Exception as e:
145
+ logger.error(f"Example failed: {e}")
@@ -1,23 +1,25 @@
1
1
  """Tool for writing a file and returning its content."""
2
2
 
3
3
  import os
4
+ from pathlib import Path
4
5
 
6
+ from loguru import logger
5
7
  from quantalogic.tools.tool import Tool, ToolArgument
6
8
 
7
9
 
8
10
  class WriteFileTool(Tool):
9
- """Tool for writing a text file."""
11
+ """Tool for writing a text file in /tmp directory."""
10
12
 
11
13
  name: str = "write_file_tool"
12
- description: str = "Writes a file with the given content. The tool will fail if the file already exists when not used in append mode."
14
+ description: str = "Writes a file with the given content in /tmp directory. The tool will fail if the file already exists when not used in append mode."
13
15
  need_validation: bool = True
14
16
  arguments: list = [
15
17
  ToolArgument(
16
18
  name="file_path",
17
19
  arg_type="string",
18
- description="The path to the file to write. Using an absolute path is recommended.",
20
+ description="The name of the file to write in /tmp directory. Can include subdirectories within /tmp.",
19
21
  required=True,
20
- example="/path/to/file.txt",
22
+ example="/tmp/myfile.txt or myfile.txt",
21
23
  ),
22
24
  ToolArgument(
23
25
  name="content",
@@ -47,50 +49,84 @@ class WriteFileTool(Tool):
47
49
  ),
48
50
  ]
49
51
 
52
+ def _ensure_tmp_path(self, file_path: str) -> str:
53
+ """Ensures the file path is within /tmp directory.
54
+
55
+ Args:
56
+ file_path (str): The original file path
57
+
58
+ Returns:
59
+ str: Normalized path within /tmp
60
+
61
+ Raises:
62
+ ValueError: If the path attempts to escape /tmp
63
+ """
64
+ # Ensure /tmp exists and is writable
65
+ tmp_dir = Path("/tmp")
66
+ if not (tmp_dir.exists() and os.access(tmp_dir, os.W_OK)):
67
+ raise ValueError("Error: /tmp directory is not accessible")
68
+
69
+ # If path doesn't start with /tmp, prepend it
70
+ if not file_path.startswith("/tmp/"):
71
+ file_path = os.path.join("/tmp", file_path.lstrip("/"))
72
+
73
+ # Resolve the absolute path and check if it's really in /tmp
74
+ real_path = os.path.realpath(file_path)
75
+ if not real_path.startswith("/tmp/"):
76
+ raise ValueError("Error: Cannot write files outside of /tmp directory")
77
+
78
+ return real_path
79
+
50
80
  def execute(self, file_path: str, content: str, append_mode: str = "False", overwrite: str = "False") -> str:
51
- """Writes a file with the given content.
81
+ """Writes a file with the given content in /tmp directory.
52
82
 
53
83
  Args:
54
- file_path (str): The path to the file to write.
84
+ file_path (str): The path to the file to write (will be forced to /tmp).
55
85
  content (str): The content to write to the file.
56
86
  append_mode (str, optional): If true, append content to existing file. Defaults to "False".
57
87
  overwrite (str, optional): If true, overwrite existing file. Defaults to "False".
58
88
 
59
89
  Returns:
60
- str: The content of the file.
90
+ str: Status message with file path and size.
61
91
 
62
92
  Raises:
63
93
  FileExistsError: If the file already exists and append_mode is False and overwrite is False.
94
+ ValueError: If attempting to write outside /tmp or if /tmp is not accessible.
64
95
  """
65
- # Convert mode strings to booleans
66
- append_mode_bool = append_mode.lower() in ["true", "1", "yes"]
67
- overwrite_bool = overwrite.lower() in ["true", "1", "yes"]
68
-
69
- ## Handle tilde expansion
70
- if file_path.startswith("~"):
71
- # Expand the tilde to the user's home directory
72
- file_path = os.path.expanduser(file_path)
73
-
74
- # Convert relative paths to absolute paths using current working directory
75
- if not os.path.isabs(file_path):
76
- file_path = os.path.abspath(os.path.join(os.getcwd(), file_path))
77
-
78
- # Ensure parent directory exists
79
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
80
-
81
- # Determine file write mode based on append_mode
82
- mode = "a" if append_mode_bool else "w"
83
-
84
- # Check if file already exists and not in append mode and not in overwrite mode
85
- if os.path.exists(file_path) and not append_mode_bool and not overwrite_bool:
86
- raise FileExistsError(
87
- f"File {file_path} already exists. Set append_mode=True to append or overwrite=True to overwrite."
88
- )
89
-
90
- with open(file_path, mode, encoding="utf-8") as f:
91
- f.write(content)
92
- file_size = os.path.getsize(file_path)
93
- return f"File {file_path} {'appended to' if append_mode_bool else 'written'} successfully. Size: {file_size} bytes."
96
+ try:
97
+ # Convert mode strings to booleans
98
+ append_mode_bool = append_mode.lower() in ["true", "1", "yes"]
99
+ overwrite_bool = overwrite.lower() in ["true", "1", "yes"]
100
+
101
+ # Ensure path is in /tmp and normalize it
102
+ file_path = self._ensure_tmp_path(file_path)
103
+
104
+ # Ensure parent directory exists (only within /tmp)
105
+ parent_dir = os.path.dirname(file_path)
106
+ if parent_dir.startswith("/tmp/"):
107
+ os.makedirs(parent_dir, exist_ok=True)
108
+
109
+ # Determine file write mode based on append_mode
110
+ mode = "a" if append_mode_bool else "w"
111
+
112
+ # Check if file already exists and not in append mode and not in overwrite mode
113
+ if os.path.exists(file_path) and not append_mode_bool and not overwrite_bool:
114
+ raise FileExistsError(
115
+ f"File {file_path} already exists. Set append_mode=True to append or overwrite=True to overwrite."
116
+ )
117
+
118
+ with open(file_path, mode, encoding="utf-8") as f:
119
+ f.write(content)
120
+
121
+ file_size = os.path.getsize(file_path)
122
+ return f"File {file_path} {'appended to' if append_mode_bool else 'written'} successfully. Size: {file_size} bytes."
123
+
124
+ except (ValueError, FileExistsError) as e:
125
+ logger.error(f"Write file error: {str(e)}")
126
+ raise
127
+ except Exception as e:
128
+ logger.error(f"Unexpected error writing file: {str(e)}")
129
+ raise ValueError(f"Failed to write file: {str(e)}")
94
130
 
95
131
 
96
132
  if __name__ == "__main__":
@@ -6,7 +6,6 @@ from .get_environment import get_environment
6
6
  from .get_coding_environment import get_coding_environment
7
7
  from .get_quantalogic_rules_content import get_quantalogic_rules_file_content
8
8
  from .lm_studio_model_info import get_model_list
9
- from .python_interpreter import interpret_ast
10
9
 
11
10
  __all__ = [
12
11
  "download_http_file",