tinyagent-py 0.0.13__tar.gz → 0.0.16__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 (48) hide show
  1. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/PKG-INFO +25 -1
  2. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/README.md +24 -0
  3. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/pyproject.toml +1 -1
  4. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/helper.py +2 -2
  5. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/modal_sandbox.py +1 -1
  6. tinyagent_py-0.0.16/tinyagent/code_agent/providers/__init__.py +17 -0
  7. tinyagent_py-0.0.16/tinyagent/code_agent/providers/base.py +381 -0
  8. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/providers/modal_provider.py +150 -27
  9. tinyagent_py-0.0.16/tinyagent/code_agent/providers/seatbelt_provider.py +1065 -0
  10. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/safety.py +6 -2
  11. tinyagent_py-0.0.16/tinyagent/code_agent/tiny_code_agent.py +1639 -0
  12. tinyagent_py-0.0.16/tinyagent/code_agent/utils.py +454 -0
  13. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/hooks/__init__.py +3 -1
  14. tinyagent_py-0.0.16/tinyagent/hooks/jupyter_notebook_callback.py +1464 -0
  15. tinyagent_py-0.0.16/tinyagent/hooks/token_tracker.py +564 -0
  16. tinyagent_py-0.0.16/tinyagent/prompts/summarize.yaml +96 -0
  17. tinyagent_py-0.0.16/tinyagent/prompts/truncation.yaml +13 -0
  18. tinyagent_py-0.0.16/tinyagent/tiny_agent.py +1763 -0
  19. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent_py.egg-info/PKG-INFO +25 -1
  20. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent_py.egg-info/SOURCES.txt +5 -0
  21. tinyagent_py-0.0.13/tinyagent/code_agent/providers/__init__.py +0 -4
  22. tinyagent_py-0.0.13/tinyagent/code_agent/providers/base.py +0 -207
  23. tinyagent_py-0.0.13/tinyagent/code_agent/tiny_code_agent.py +0 -678
  24. tinyagent_py-0.0.13/tinyagent/code_agent/utils.py +0 -193
  25. tinyagent_py-0.0.13/tinyagent/tiny_agent.py +0 -1001
  26. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/LICENSE +0 -0
  27. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/setup.cfg +0 -0
  28. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/__init__.py +0 -0
  29. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/__init__.py +0 -0
  30. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/example.py +0 -0
  31. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/tools/__init__.py +0 -0
  32. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/code_agent/tools/example_tools.py +0 -0
  33. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/hooks/gradio_callback.py +0 -0
  34. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/hooks/logging_manager.py +0 -0
  35. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/hooks/rich_code_ui_callback.py +0 -0
  36. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/hooks/rich_ui_callback.py +0 -0
  37. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/mcp_client.py +0 -0
  38. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/memory_manager.py +0 -0
  39. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/prompts/code_agent.yaml +0 -0
  40. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/storage/__init__.py +0 -0
  41. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/storage/base.py +0 -0
  42. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/storage/json_file_storage.py +0 -0
  43. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/storage/postgres_storage.py +0 -0
  44. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/storage/redis_storage.py +0 -0
  45. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent/storage/sqlite_storage.py +0 -0
  46. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent_py.egg-info/dependency_links.txt +0 -0
  47. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent_py.egg-info/requires.txt +0 -0
  48. {tinyagent_py-0.0.13 → tinyagent_py-0.0.16}/tinyagent_py.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyagent-py
3
- Version: 0.0.13
3
+ Version: 0.0.16
4
4
  Summary: TinyAgent with MCP Client, Code Agent (Thinking, Planning, and Executing in Python), and Extendable Hooks, Tiny but powerful
5
5
  Author-email: Mahdi Golchin <golchin@askdev.ai>
6
6
  Project-URL: Homepage, https://github.com/askbudi/tinyagent
@@ -274,6 +274,30 @@ agent = TinyCodeAgent(
274
274
  )
275
275
  ```
276
276
 
277
+ ### Automatic Git Checkpoints
278
+
279
+ TinyCodeAgent can automatically create Git checkpoints after each successful shell command execution. This helps track changes made by the agent and provides a safety net for reverting changes if needed.
280
+
281
+ ```python
282
+ # Enable automatic Git checkpoints during initialization
283
+ agent = TinyCodeAgent(
284
+ model="gpt-4.1-mini",
285
+ auto_git_checkpoint=True # Enable automatic Git checkpoints
286
+ )
287
+
288
+ # Or enable/disable it later
289
+ agent.enable_auto_git_checkpoint(True) # Enable
290
+ agent.enable_auto_git_checkpoint(False) # Disable
291
+
292
+ # Check current status
293
+ is_enabled = agent.get_auto_git_checkpoint_status()
294
+ ```
295
+
296
+ Each checkpoint includes:
297
+ - Descriptive commit message with the command description
298
+ - Timestamp of when the command was executed
299
+ - The actual command that was run
300
+
277
301
  For detailed documentation, see the [TinyCodeAgent README](tinyagent/code_agent/README.md).
278
302
 
279
303
  ## How the TinyAgent Hook System Works
@@ -232,6 +232,30 @@ agent = TinyCodeAgent(
232
232
  )
233
233
  ```
234
234
 
235
+ ### Automatic Git Checkpoints
236
+
237
+ TinyCodeAgent can automatically create Git checkpoints after each successful shell command execution. This helps track changes made by the agent and provides a safety net for reverting changes if needed.
238
+
239
+ ```python
240
+ # Enable automatic Git checkpoints during initialization
241
+ agent = TinyCodeAgent(
242
+ model="gpt-4.1-mini",
243
+ auto_git_checkpoint=True # Enable automatic Git checkpoints
244
+ )
245
+
246
+ # Or enable/disable it later
247
+ agent.enable_auto_git_checkpoint(True) # Enable
248
+ agent.enable_auto_git_checkpoint(False) # Disable
249
+
250
+ # Check current status
251
+ is_enabled = agent.get_auto_git_checkpoint_status()
252
+ ```
253
+
254
+ Each checkpoint includes:
255
+ - Descriptive commit message with the command description
256
+ - Timestamp of when the command was executed
257
+ - The actual command that was run
258
+
235
259
  For detailed documentation, see the [TinyCodeAgent README](tinyagent/code_agent/README.md).
236
260
 
237
261
  ## How the TinyAgent Hook System Works
@@ -12,7 +12,7 @@ tinyagent = ["prompts/*.yaml"]
12
12
 
13
13
  [project]
14
14
  name = "tinyagent-py"
15
- version = "0.0.13"
15
+ version = "0.0.16"
16
16
  description = "TinyAgent with MCP Client, Code Agent (Thinking, Planning, and Executing in Python), and Extendable Hooks, Tiny but powerful"
17
17
  readme = "README.md"
18
18
  authors = [
@@ -47,13 +47,13 @@ You are an Agent, You need to solve the task, not suggesting user about how to s
47
47
 
48
48
  """)
49
49
 
50
- def load_template(path: str) -> str:
50
+ def load_template(path: str,key:str="system_prompt") -> str:
51
51
  """
52
52
  Load the YAML file and extract its 'system_prompt' field.
53
53
  """
54
54
  with open(path, "r") as f:
55
55
  data = yaml.safe_load(f)
56
- return data["system_prompt"]
56
+ return data[key]
57
57
 
58
58
  def render_system_prompt(template_str: str,
59
59
  tools: dict,
@@ -78,7 +78,7 @@ def create_sandbox(
78
78
 
79
79
  if apt_packages is None:
80
80
  # Always install the basics required for most workflows
81
- apt_packages = ("git", "curl", "nodejs", "npm")
81
+ apt_packages = ("git", "curl", "nodejs", "npm","ripgrep","tree")
82
82
 
83
83
  if default_packages is None:
84
84
  default_packages = (
@@ -0,0 +1,17 @@
1
+ from .base import CodeExecutionProvider
2
+ from .modal_provider import ModalProvider
3
+
4
+ # Import SeatbeltProvider conditionally to avoid errors on non-macOS systems
5
+ import platform
6
+ if platform.system() == "Darwin":
7
+ try:
8
+ from .seatbelt_provider import SeatbeltProvider
9
+ except ImportError:
10
+ # If there's an issue importing, just don't make it available
11
+ pass
12
+
13
+ __all__ = ["CodeExecutionProvider", "ModalProvider"]
14
+
15
+ # Add SeatbeltProvider to __all__ if it was successfully imported
16
+ if platform.system() == "Darwin" and "SeatbeltProvider" in globals():
17
+ __all__.append("SeatbeltProvider")
@@ -0,0 +1,381 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Dict, List, Any, Optional, Set
3
+ from tinyagent.hooks.logging_manager import LoggingManager
4
+ import cloudpickle
5
+
6
+
7
+ class CodeExecutionProvider(ABC):
8
+ """
9
+ Abstract base class for code execution providers.
10
+
11
+ This class defines the interface that all code execution providers must implement.
12
+ It allows for easy extension to support different execution environments
13
+ (Modal, Docker, local execution, cloud functions, etc.) with minimal code changes.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ log_manager: LoggingManager,
19
+ default_python_codes: Optional[List[str]] = None,
20
+ code_tools: List[Dict[str, Any]] = None,
21
+ pip_packages: List[str] = None,
22
+ secrets: Dict[str, Any] = None,
23
+ lazy_init: bool = True,
24
+ bypass_shell_safety: bool = False,
25
+ additional_safe_shell_commands: Optional[List[str]] = None,
26
+ additional_safe_control_operators: Optional[List[str]] = None,
27
+ **kwargs
28
+ ):
29
+ self.log_manager = log_manager
30
+ self.default_python_codes = default_python_codes or []
31
+ self.code_tools = code_tools or []
32
+ self.pip_packages = pip_packages or []
33
+ self.secrets = secrets or {}
34
+ self.lazy_init = lazy_init
35
+ self.kwargs = kwargs
36
+ self.executed_default_codes = False
37
+ self._globals_dict = kwargs.get("globals_dict", {})
38
+ self._locals_dict = kwargs.get("locals_dict", {})
39
+ self._user_variables = {}
40
+ self.code_tools_definitions = []
41
+
42
+ # Shell safety configuration
43
+ self.bypass_shell_safety = bypass_shell_safety
44
+
45
+ # Safe shell commands that don't modify the system or access sensitive data
46
+ self.safe_shell_commands: Set[str] = {
47
+ "ls", "cat", "grep", "find", "echo", "pwd", "whoami", "date",
48
+ "head", "tail", "wc", "sort", "uniq", "tr", "cut", "sed", "awk",
49
+ "ps", "df", "du", "uname", "which", "type", "file", "stat", "rg", "if",
50
+ "tree"
51
+ }
52
+
53
+ # Add additional safe shell commands if provided
54
+ if additional_safe_shell_commands:
55
+ if "*" in additional_safe_shell_commands:
56
+ # If wildcard is provided, allow all commands (effectively bypassing the check)
57
+ self.bypass_shell_safety = True
58
+ else:
59
+ self.safe_shell_commands.update(additional_safe_shell_commands)
60
+
61
+ # Safe control operators for shell commands
62
+ self.safe_control_operators: Set[str] = {"&&", "||", ";", "|"}
63
+
64
+ # Add additional safe control operators if provided
65
+ if additional_safe_control_operators:
66
+ if "*" in additional_safe_control_operators:
67
+ # If wildcard is provided, allow all operators
68
+ self.safe_control_operators = set("*")
69
+ else:
70
+ self.safe_control_operators.update(additional_safe_control_operators)
71
+
72
+ @abstractmethod
73
+ async def execute_python(
74
+ self,
75
+ code_lines: List[str],
76
+ timeout: int = 120
77
+ ) -> Dict[str, Any]:
78
+ """
79
+ Execute Python code and return the result.
80
+
81
+ Args:
82
+ code_lines: List of Python code lines to execute
83
+ timeout: Maximum execution time in seconds
84
+
85
+ Returns:
86
+ Dictionary containing execution results with keys:
87
+ - printed_output: stdout from the execution
88
+ - return_value: the return value if any
89
+ - stderr: stderr from the execution
90
+ - error_traceback: exception traceback if any error occurred
91
+ """
92
+ pass
93
+
94
+ @abstractmethod
95
+ async def execute_shell(
96
+ self,
97
+ command: List[str],
98
+ timeout: int = 10,
99
+ workdir: Optional[str] = None
100
+ ) -> Dict[str, Any]:
101
+ """
102
+ Execute a shell command securely and return the result.
103
+
104
+ Args:
105
+ command: List of command parts to execute
106
+ timeout: Maximum execution time in seconds
107
+ workdir: Working directory for command execution
108
+
109
+ Returns:
110
+ Dictionary containing execution results with keys:
111
+ - stdout: stdout from the execution
112
+ - stderr: stderr from the execution
113
+ - exit_code: exit code from the command
114
+ """
115
+ pass
116
+
117
+ def is_safe_command(self, command: List[str]) -> Dict[str, Any]:
118
+ """
119
+ Check if a shell command is safe to execute.
120
+
121
+ Args:
122
+ command: List of command parts to check
123
+
124
+ Returns:
125
+ Dictionary with:
126
+ - safe: Boolean indicating if command is safe
127
+ - reason: Reason why command is not safe (if applicable)
128
+ """
129
+ # If shell safety checks are bypassed, consider all commands safe
130
+ if self.bypass_shell_safety:
131
+ return {"safe": True}
132
+
133
+ if type(command) == str:
134
+ command = command.split(" ")
135
+ if not command or not isinstance(command, list) or len(command) == 0:
136
+ return {"safe": False, "reason": "Empty or invalid command"}
137
+
138
+ # Special handling for bash -c or bash -lc commands
139
+ if len(command) >= 3 and command[0] == "bash" and command[1] in ["-c", "-lc"]:
140
+ # For bash -c or bash -lc, we need to parse the command string that follows
141
+ # We'll extract commands from the bash command string and check them
142
+ bash_cmd_str = command[2]
143
+
144
+ # Simple parsing of the bash command to extract command names
145
+ # This is a basic implementation and might not cover all edge cases
146
+ import shlex
147
+ import re
148
+
149
+ try:
150
+ # Shell script keywords that should be allowed
151
+ shell_keywords = {
152
+ "if", "then", "else", "elif", "fi", "for", "do", "done",
153
+ "while", "until", "case", "esac", "in", "function", "select",
154
+ "time", "coproc", "true", "false"
155
+ }
156
+
157
+ # Split the command by common shell operators
158
+ cmd_parts = re.split(r'(\||;|&&|\|\||>|>>|<|<<)', bash_cmd_str)
159
+ commands_to_check = []
160
+
161
+ for part in cmd_parts:
162
+ part = part.strip()
163
+ if part and part not in ['|', ';', '&&', '||', '>', '>>', '<', '<<']:
164
+ # Get the first word which is typically the command
165
+ try:
166
+ words = shlex.split(part)
167
+ if words:
168
+ cmd_name = words[0].split('/')[-1] # Extract binary name
169
+
170
+ # Skip shell keywords
171
+ if cmd_name in shell_keywords:
172
+ continue
173
+
174
+ # Skip variable assignments (e.g., VAR=value)
175
+ if re.match(r'^[A-Za-z_][A-Za-z0-9_]*=', cmd_name):
176
+ continue
177
+
178
+ if cmd_name not in self.safe_shell_commands and '*' not in cmd_name and '?' not in cmd_name:
179
+ return {"safe": False, "reason": f"Unsafe command in bash script: {cmd_name}"}
180
+ except Exception:
181
+ # If parsing fails, be cautious and reject
182
+ return {"safe": False, "reason": "Could not parse bash command safely"}
183
+
184
+ # All commands in the bash script are safe
185
+ return {"safe": True}
186
+ except Exception as e:
187
+ return {"safe": False, "reason": f"Error parsing bash command: {str(e)}"}
188
+
189
+ # Normal command processing for non-bash -c commands
190
+ # Shell operators that might be passed as separate arguments
191
+ shell_operators = ['|', '>', '<', '>>', '<<', '&&', '||', ';']
192
+
193
+ # Extract actual commands from the command list, ignoring shell operators
194
+ commands_to_check = []
195
+ i = 0
196
+ while i < len(command):
197
+ if command[i] in shell_operators:
198
+ i += 1
199
+ continue
200
+
201
+ # Extract the binary name
202
+ bin_name = command[i].split("/")[-1]
203
+ commands_to_check.append(bin_name)
204
+
205
+ # Skip to next command after an operator
206
+ i += 1
207
+ while i < len(command) and command[i] not in shell_operators:
208
+ i += 1
209
+
210
+ # Check if all commands are in the safe list
211
+ for cmd in commands_to_check:
212
+ # Handle wildcards in command names (e.g., *.py)
213
+ if '*' in cmd or '?' in cmd:
214
+ continue
215
+
216
+ if cmd not in self.safe_shell_commands:
217
+ return {"safe": False, "reason": f"Unsafe command: {cmd}"}
218
+
219
+ return {"safe": True}
220
+
221
+ @abstractmethod
222
+ async def cleanup(self):
223
+ """Clean up any resources used by the provider."""
224
+ pass
225
+
226
+ def add_tools(self, tools: List[Any]) -> None:
227
+ """
228
+ Add tools to the execution environment.
229
+
230
+ Args:
231
+ tools: List of tool objects to add
232
+ """
233
+ tools_str_list = ["import cloudpickle"]
234
+ tools_str_list.append("###########<tools>###########\n")
235
+ for tool in tools:
236
+ tools_str_list.append(
237
+ f"globals()['{tool._tool_metadata['name']}'] = cloudpickle.loads({cloudpickle.dumps(tool)})"
238
+ )
239
+ tools_str_list.append("\n\n")
240
+ tools_str_list.append("###########</tools>###########\n")
241
+ tools_str_list.append("\n\n")
242
+ self.code_tools_definitions.extend(tools_str_list)
243
+
244
+ def set_code_tools(self, tools: List[Any]) -> None:
245
+ """
246
+ Set the code tools available in the execution environment.
247
+ Replaces any existing tools with the new list.
248
+
249
+ Args:
250
+ tools: List of tool objects to set
251
+ """
252
+ # Clear existing tools
253
+ self.code_tools = tools.copy()
254
+ self.code_tools_definitions = []
255
+
256
+ # Add the new tools
257
+ if tools:
258
+ self.add_tools(tools)
259
+
260
+ def set_user_variables(self, variables: Dict[str, Any]) -> None:
261
+ """
262
+ Set user variables that will be available in the Python environment.
263
+
264
+ Args:
265
+ variables: Dictionary of variable name -> value pairs
266
+ """
267
+ self._user_variables = variables.copy()
268
+
269
+ # Add variables to the execution environment by serializing them
270
+ # This ensures they are available when code is executed
271
+ variables_str_list = ["import cloudpickle"]
272
+ variables_str_list.append("###########<user_variables>###########\n")
273
+
274
+ for var_name, var_value in variables.items():
275
+ # Serialize the variable and add it to globals
276
+ serialized_var = cloudpickle.dumps(var_value)
277
+ variables_str_list.append(
278
+ f"globals()['{var_name}'] = cloudpickle.loads({serialized_var})"
279
+ )
280
+
281
+ variables_str_list.append("\n###########</user_variables>###########\n")
282
+ variables_str_list.append("\n")
283
+
284
+ # Remove any existing user variables from default codes
285
+ self._remove_existing_user_variables()
286
+
287
+ # Add new variables to default codes at the beginning (after tools if any)
288
+ # This ensures variables are available from the start
289
+ if variables_str_list:
290
+ # Find where to insert (after tools section if it exists)
291
+ insert_index = 0
292
+ for i, code in enumerate(self.code_tools_definitions):
293
+ if "###########</tools>###########" in code:
294
+ insert_index = i + 1
295
+ break
296
+
297
+ # Insert the variables code
298
+ for j, var_code in enumerate(variables_str_list):
299
+ self.code_tools_definitions.insert(insert_index + j, var_code)
300
+
301
+ def _remove_existing_user_variables(self) -> None:
302
+ """Remove existing user variables from default python codes."""
303
+ # Find and remove the user variables section
304
+ start_index = None
305
+ end_index = None
306
+
307
+ for i, code in enumerate(self.code_tools_definitions):
308
+ if "###########<user_variables>###########" in code:
309
+ start_index = i - 1 if i > 0 and "import cloudpickle" in self.code_tools_definitions[i-1] else i
310
+ elif "###########</user_variables>###########" in code:
311
+ end_index = i + 2 # Include the newline after
312
+ break
313
+
314
+ if start_index is not None and end_index is not None:
315
+ # Remove the old variables section
316
+ del self.code_tools_definitions[start_index:end_index]
317
+
318
+ def get_user_variables(self) -> Dict[str, Any]:
319
+ """
320
+ Get a copy of current user variables.
321
+
322
+ Returns:
323
+ Dictionary of current user variables
324
+ """
325
+ return self._user_variables.copy()
326
+
327
+ def update_user_variables_from_globals(self, globals_dict: Dict[str, Any]) -> None:
328
+ """
329
+ Extract and update user variables from the globals dictionary after code execution.
330
+ This ensures that any modifications to user variables during code execution are preserved.
331
+
332
+ Args:
333
+ globals_dict: The globals dictionary after code execution
334
+ """
335
+ if not globals_dict or not self._user_variables:
336
+ return
337
+
338
+ # Update user variables with values from globals
339
+ for var_name in list(self._user_variables.keys()):
340
+ if var_name in globals_dict:
341
+ try:
342
+ # Try to serialize the value to ensure it's valid
343
+ cloudpickle.dumps(globals_dict[var_name])
344
+ # Update the user variable with the new value
345
+ self._user_variables[var_name] = globals_dict[var_name]
346
+ except Exception:
347
+ # If serialization fails, keep the old value
348
+ pass
349
+
350
+ # Check for new variables that might have been created
351
+ # This handles cases where LLM creates new variables that should be preserved
352
+ for var_name, var_value in globals_dict.items():
353
+ # Skip special variables, modules, and functions
354
+ if (var_name.startswith('__') or
355
+ var_name in ['builtins', 'cloudpickle'] or
356
+ callable(var_value) or
357
+ var_name in self._user_variables):
358
+ continue
359
+
360
+ try:
361
+ # Try to serialize the value to ensure it's valid
362
+ cloudpickle.dumps(var_value)
363
+ # Add the new variable to user variables
364
+ self._user_variables[var_name] = var_value
365
+ except Exception:
366
+ # If serialization fails, skip this variable
367
+ pass
368
+
369
+ def shell_response_to_llm_understandable(self, response: Dict[str, Any]) -> str:
370
+ """
371
+ Convert a shell command response to a format that is understandable by the LLM.
372
+ """
373
+ if response.get('stderr',None) not in [None,""]:
374
+ error_message = "Bash Error: " + response['stderr']
375
+ if "No such file or directory" in response['stderr']:
376
+ error_message.replace("No such file or directory", "No such file or directory, Have you provided the correct absolute path? If you are unsure use ls first to make sure the path exists")
377
+ if "Command timed out after" in response['stderr']:
378
+ error_message += ", Make sure your command is specific enough. And only if it is the most specific and optimized command then try to increase the timeout parameter if you need to more time for this command."
379
+ return error_message
380
+ else:
381
+ return response['stdout']