-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat:Add support for containerized Code execution, and utilities ( upload / download fn ). #459
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
6ae3f20
Modified to comply with generator update, and so that no changes to t…
unaidedelf8777 54b2451
fixed my oopsies.
unaidedelf8777 03fb194
Fully Working. All functionality tested, but still havent done idiot …
unaidedelf8777 2e1f7bf
Merge remote-tracking branch 'upstream/main' into to-overwrite
unaidedelf8777 acb8dbc
left one merge header accidentaly. fixed that.
unaidedelf8777 1858ffb
I forgot a header apparently. lol
unaidedelf8777 e2716cd
Dev done (#4)
unaidedelf8777 30f8114
resolve merge conflicts
unaidedelf8777 e770bd0
Add container timeout for easier server integration of OI. controllab…
unaidedelf8777 df2a44f
Merge remote-tracking branch 'upstream/main' into devv
unaidedelf8777 3907b16
Update things, resolve merge conflicts.
unaidedelf8777 295ddfa
fixed the tests, since they imported and assumed that was a instance,…
unaidedelf8777 8884506
typo in interpreter/code_interpreters/languages/python.py
unaidedelf8777 2af31a9
fix html interpreter so that we can treat it like a subprocess code i…
unaidedelf8777 1288831
fix html interpreter so that we can treat it like a subprocess code i…
unaidedelf8777 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"python.analysis.typeCheckingMode": "basic" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import appdirs | ||
import shutil | ||
import atexit | ||
import os | ||
import re | ||
|
||
import docker | ||
from docker.tls import TLSConfig | ||
from docker.utils import kwargs_from_env | ||
|
||
|
||
def destroy(): # this fn is called when the entire program exits. registered with atexit in the __init__.py | ||
# Prepare the Docker client | ||
client_kwargs = kwargs_from_env() | ||
if client_kwargs.get('tls'): | ||
client_kwargs['tls'] = TLSConfig(**client_kwargs['tls']) | ||
client = docker.APIClient(**client_kwargs) | ||
|
||
# Get all containers | ||
all_containers = client.containers(all=True) | ||
|
||
# Filter containers based on the label | ||
for container in all_containers: | ||
labels = container['Labels'] | ||
if labels: | ||
session_id = labels.get('session_id') | ||
if session_id and re.match(r'^ses-', session_id): | ||
# Stop the container if it's running | ||
if container['State'] == 'running': | ||
client.stop(container=container['Id']) | ||
# Remove the container | ||
client.remove_container(container=container['Id']) | ||
session_path = os.path.join(appdirs.user_data_dir("Open Interpreter"), "sessions", session_id) | ||
if os.path.exists(session_path): | ||
shutil.rmtree(session_path) | ||
|
||
atexit.register(destroy) | ||
|
68 changes: 68 additions & 0 deletions
68
interpreter/code_interpreters/container_utils/auto_remove.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import threading | ||
import time | ||
from functools import wraps | ||
|
||
def access_aware(cls): | ||
class AccessAwareWrapper: | ||
def __init__(self, wrapped, auto_remove_timeout, close_callback=None): | ||
self._wrapped = wrapped | ||
self._last_accessed = time.time() | ||
self._auto_remove = auto_remove_timeout is not None | ||
self._timeout = auto_remove_timeout | ||
self.close_callback = close_callback # Store the callback | ||
if self._auto_remove: | ||
self._monitor_thread = threading.Thread(target=self._monitor_object, daemon=True) | ||
self._monitor_thread.start() | ||
|
||
def _monitor_object(self): | ||
while True: | ||
time.sleep(1) # Check every second | ||
if self._auto_remove and self.check_timeout(): | ||
# If a close_callback is defined, call it | ||
if self.close_callback: | ||
try: | ||
self.close_callback() # Call the callback | ||
except Exception as e: | ||
# Log or handle the exception as required | ||
return f"An error occurred during callback: {e}" | ||
|
||
try: | ||
self._wrapped.stop() | ||
except Exception: | ||
continue # why care? we are removing it anyway | ||
|
||
# If the wrapped object has a __del__ method, call it | ||
if self._wrapped and hasattr(self._wrapped, '__del__'): | ||
try: | ||
self._wrapped.__del__() | ||
except Exception as e: | ||
# Log or handle the exception as required | ||
return f"An error occurred during deletion: {e}" | ||
|
||
# Remove the strong reference to the wrapped object. this makes it go bye bye. | ||
self._wrapped = None | ||
break | ||
|
||
def touch(self): | ||
self._last_accessed = time.time() | ||
|
||
def check_timeout(self): | ||
return time.time() - self._last_accessed > self._timeout | ||
|
||
def __getattr__(self, attr): | ||
if self._wrapped is None: | ||
raise ValueError("Object has been removed due to inactivity.") | ||
self.touch() # Update last accessed time | ||
return getattr(self._wrapped, attr) # Use the actual object here | ||
|
||
def __del__(self): | ||
if self._auto_remove: | ||
self._monitor_thread.join() # Ensure the monitoring thread is cleaned up | ||
|
||
@wraps(cls) | ||
def wrapper(*args, **kwargs): | ||
auto_remove_timeout = kwargs.pop('auto_remove_timeout', None) # Extract the auto_remove_timeout argument | ||
close_callback = kwargs.pop('close_callback', None) # Extract the close_callback argument | ||
obj = cls(*args, **kwargs) # Create an instance of the original class | ||
return AccessAwareWrapper(obj, auto_remove_timeout, close_callback) # Wrap it | ||
return wrapper |
108 changes: 108 additions & 0 deletions
108
interpreter/code_interpreters/container_utils/build_image.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import os | ||
import json | ||
import hashlib | ||
import subprocess | ||
from docker import DockerClient | ||
from docker.errors import DockerException | ||
from rich import print as Print | ||
|
||
def get_files_hash(*file_paths): | ||
"""Return the SHA256 hash of multiple files.""" | ||
hasher = hashlib.sha256() | ||
for file_path in file_paths: | ||
with open(file_path, "rb") as f: | ||
while chunk := f.read(4096): | ||
hasher.update(chunk) | ||
return hasher.hexdigest() | ||
|
||
|
||
def build_docker_images( | ||
dockerfile_dir = os.path.join(os.path.abspath(os.path.dirname(os.path.dirname(__file__))), "dockerfiles") | ||
, | ||
): | ||
""" | ||
Builds a Docker image for the Open Interpreter runtime container if needed. | ||
|
||
Args: | ||
dockerfile_dir (str): The directory containing the Dockerfile and requirements.txt files. | ||
|
||
Returns: | ||
None | ||
""" | ||
try: | ||
client = DockerClient.from_env() | ||
except DockerException: | ||
Print("[bold red]ERROR[/bold red]: Could not connect to Docker daemon. Is Docker Engine installed and running?") | ||
Print( | ||
"\nFor information on Docker installation, visit: https://docs.docker.com/engine/install/ and follow the instructions for your system." | ||
) | ||
return | ||
|
||
image_name = "openinterpreter-runtime-container" | ||
hash_file_path = os.path.join(dockerfile_dir, "hash.json") | ||
|
||
dockerfile_name = "Dockerfile" | ||
requirements_name = "requirements.txt" | ||
dockerfile_path = os.path.join(dockerfile_dir, dockerfile_name) | ||
requirements_path = os.path.join(dockerfile_dir, requirements_name) | ||
|
||
if not os.path.exists(dockerfile_path) or not os.path.exists(requirements_path): | ||
Print("ERROR: Dockerfile or requirements.txt not found. Did you delete or rename them?") | ||
raise RuntimeError( | ||
"No container Dockerfiles or requirements.txt found. Make sure they are in the dockerfiles/ subdir of the module." | ||
) | ||
|
||
current_hash = get_files_hash(dockerfile_path, requirements_path) | ||
|
||
stored_hashes = {} | ||
if os.path.exists(hash_file_path): | ||
with open(hash_file_path, "rb") as f: | ||
stored_hashes = json.load(f) | ||
|
||
original_hash = stored_hashes.get("original_hash") | ||
previous_hash = stored_hashes.get("last_hash") | ||
|
||
if current_hash == original_hash: | ||
images = client.images.list(name=image_name, all=True) | ||
if not images: | ||
Print("Downloading default image from Docker Hub, please wait...") | ||
|
||
subprocess.run(["docker", "pull", "unaidedelf/openinterpreter-runtime-container:latest"]) | ||
subprocess.run(["docker", "tag", "unaidedelf/openinterpreter-runtime-container:latest", image_name ], | ||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | ||
elif current_hash != previous_hash: | ||
Print("Dockerfile or requirements.txt has changed. Building container...") | ||
|
||
try: | ||
# Run the subprocess without capturing stdout and stderr | ||
# This will allow Docker's output to be printed to the console in real-time | ||
subprocess.run( | ||
[ | ||
"docker", | ||
"build", | ||
"-t", | ||
f"{image_name}:latest", | ||
dockerfile_dir, | ||
], | ||
check=True, # This will raise a CalledProcessError if the command returns a non-zero exit code | ||
text=True, | ||
) | ||
|
||
# Update the stored current hash | ||
stored_hashes["last_hash"] = current_hash | ||
with open(hash_file_path, "w") as f: | ||
json.dump(stored_hashes, f) | ||
|
||
except subprocess.CalledProcessError: | ||
# Suppress Docker's error messages and display your own error message | ||
Print("Docker Build Error: Building Docker image failed. Please review the error message above and resolve the issue.") | ||
|
||
except FileNotFoundError: | ||
Print("ERROR: The 'docker' command was not found on your system.") | ||
Print( | ||
"Please ensure Docker Engine is installed and the 'docker' command is available in your PATH." | ||
) | ||
Print( | ||
"For information on Docker installation, visit: https://docs.docker.com/engine/install/" | ||
) | ||
Print("If Docker is installed, try starting a new terminal session.") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just putting a note here so that we don't forget to remove this when we switch to a different base container image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't the container sqlite be changed?