From f98c652e5604a6f531244533e25bed208bf2803e Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Mon, 20 Oct 2025 10:58:24 +0530 Subject: [PATCH 1/5] added isolated code executor --- .../code_executors/isolated_code_executor.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/google/adk/code_executors/isolated_code_executor.py diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py new file mode 100644 index 0000000000..1a256b9433 --- /dev/null +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from contextlib import redirect_stdout +import io +import re +from typing import Any + +from pydantic import Field +from typing_extensions import override + +from ..agents.invocation_context import InvocationContext +from .base_code_executor import BaseCodeExecutor +from .code_execution_utils import CodeExecutionInput +from .code_execution_utils import CodeExecutionResult + +import sys +import subprocess + +#Don't think this is needed anymore but keeping it around just in case. + +# def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: +# """Prepare globals for code execution, injecting __name__ if needed.""" +# if re.search(r"if\s+__name__\s*==\s*['\"]__main__['\"]", code): +# globals_['__name__'] = '__main__' + +class IsolatedCodeExecutor(BaseCodeExecutor): + """A code executor that safely executes code in an isolated environment through + the current local context.""" + + # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful. + stateful: bool = Field(default=False, frozen=True, exclude=True) + + # Overrides the BaseCodeExecutor attribute: this executor cannot + # optimize_data_file. + optimize_data_file: bool = Field(default=False, frozen=True, exclude=True) + + def __init__(self, **data): + """Initializes the IsolatedCodeExecutor.""" + if 'stateful' in data and data['stateful']: + raise ValueError('Cannot set `stateful=True` in IsolatedCodeExecutor.') + if 'optimize_data_file' in data and data['optimize_data_file']: + raise ValueError( + 'Cannot set `optimize_data_file=True` in IsolatedCodeExecutor.' + ) + super().__init__(**data) + + @override + def execute_code( + self, + invocation_context: InvocationContext, + code_execution_input: CodeExecutionInput, + ) -> CodeExecutionResult: + # Executes code by spawning a new python interpreter process. + code = code_execution_input.code + process_result = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True + ) + + # Collect the final result. + return CodeExecutionResult( + stdout=process_result.stdout, + stderr=process_result.stderr, + output_files=[], + ) From b3cdd32ba158609d03e1d8a6a42802404d466cea Mon Sep 17 00:00:00 2001 From: AlexFierro9 <95060707+AlexFierro9@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:40:01 +0530 Subject: [PATCH 2/5] Update src/google/adk/code_executors/isolated_code_executor.py Unused imports can be confusing Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/code_executors/isolated_code_executor.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index 1a256b9433..f79ae130c6 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -1,9 +1,7 @@ from __future__ import annotations -from contextlib import redirect_stdout -import io -import re -from typing import Any +import sys +import subprocess from pydantic import Field from typing_extensions import override @@ -13,9 +11,6 @@ from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult -import sys -import subprocess - #Don't think this is needed anymore but keeping it around just in case. # def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: From a77b0c200ec60da9fdb85bd03b3a6812c8fb229d Mon Sep 17 00:00:00 2001 From: AlexFierro9 <95060707+AlexFierro9@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:40:28 +0530 Subject: [PATCH 3/5] Update src/google/adk/code_executors/isolated_code_executor.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/google/adk/code_executors/isolated_code_executor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index f79ae130c6..e46af150ba 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -19,8 +19,12 @@ # globals_['__name__'] = '__main__' class IsolatedCodeExecutor(BaseCodeExecutor): - """A code executor that safely executes code in an isolated environment through - the current local context.""" + """A code executor that executes code in an isolated process. + + This provides memory isolation from the main application, but it is not a + full security sandbox. The executed code runs with the same permissions as the + main application and can access the filesystem, network, etc. + """ # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful. stateful: bool = Field(default=False, frozen=True, exclude=True) From 8fbd73bc80da3340de408129895a1c2a5dad5af3 Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Mon, 20 Oct 2025 13:29:46 +0530 Subject: [PATCH 4/5] added the units tests --- .../code_executors/isolated_code_executor.py | 8 +- .../test_isolated_code_executor.py | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 tests/unittests/code_executors/test_isolated_code_executor.py diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index e46af150ba..0e1b2c927d 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -11,12 +11,6 @@ from .code_execution_utils import CodeExecutionInput from .code_execution_utils import CodeExecutionResult -#Don't think this is needed anymore but keeping it around just in case. - -# def _prepare_globals(code: str, globals_: dict[str, Any]) -> None: -# """Prepare globals for code execution, injecting __name__ if needed.""" -# if re.search(r"if\s+__name__\s*==\s*['\"]__main__['\"]", code): -# globals_['__name__'] = '__main__' class IsolatedCodeExecutor(BaseCodeExecutor): """A code executor that executes code in an isolated process. @@ -57,7 +51,7 @@ def execute_code( text=True ) - # Collect the final result. + return CodeExecutionResult( stdout=process_result.stdout, stderr=process_result.stderr, diff --git a/tests/unittests/code_executors/test_isolated_code_executor.py b/tests/unittests/code_executors/test_isolated_code_executor.py new file mode 100644 index 0000000000..36feebc5b1 --- /dev/null +++ b/tests/unittests/code_executors/test_isolated_code_executor.py @@ -0,0 +1,112 @@ +from unittest.mock import MagicMock + +from google.adk.agents.base_agent import BaseAgent +from google.adk.agents.invocation_context import InvocationContext +from google.adk.code_executors.code_execution_utils import CodeExecutionInput +from google.adk.code_executors.code_execution_utils import CodeExecutionResult +from google.adk.code_executors.isolated_code_executor import IsolatedCodeExecutor +from google.adk.sessions.base_session_service import BaseSessionService +from google.adk.sessions.session import Session +import pytest +import os + + +@pytest.fixture +def mock_invocation_context() -> InvocationContext: + """Provides a mock InvocationContext.""" + mock_agent = MagicMock(spec=BaseAgent) + mock_session = MagicMock(spec=Session) + mock_session_service = MagicMock(spec=BaseSessionService) + return InvocationContext( + invocation_id="test_invocation", + agent=mock_agent, + session=mock_session, + session_service=mock_session_service, + ) + + +class TestIsolatedCodeExecutor: + + def test_init_default(self): + executor = IsolatedCodeExecutor() + assert not executor.stateful + assert not executor.optimize_data_file + + def test_init_stateful_raises_error(self): + with pytest.raises( + ValueError, + match="Cannot set `stateful=True` in IsolatedCodeExecutor.", + ): + IsolatedCodeExecutor(stateful=True) + + def test_init_optimize_data_file_raises_error(self): + with pytest.raises( + ValueError, + match=( + "Cannot set `optimize_data_file=True` in IsolatedCodeExecutor." + ), + ): + IsolatedCodeExecutor(optimize_data_file=True) + + def test_execute_code_simple_print( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code='print("hello world")') + result = executor.execute_code(mock_invocation_context, code_input) + + assert isinstance(result, CodeExecutionResult) + assert result.stdout == "hello world\n" + assert result.stderr == "" + assert result.output_files == [] + + def test_execute_code_with_error( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code='raise ValueError("Test error")') + result = executor.execute_code(mock_invocation_context, code_input) + + assert isinstance(result, CodeExecutionResult) + assert result.stdout == "" + assert "Test error" in result.stderr + assert result.output_files == [] + + def test_execute_code_variable_assignment( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code="x = 10\nprint(x * 2)") + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout == "20\n" + assert result.stderr == "" + + def test_execute_code_empty(self, mock_invocation_context: InvocationContext): + executor = IsolatedCodeExecutor() + code_input = CodeExecutionInput(code="") + result = executor.execute_code(mock_invocation_context, code_input) + assert result.stdout == "" + assert result.stderr == "" + + def test_execute_code_with_import( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code = "import os; print(os.linesep)" + code_input = CodeExecutionInput(code=code) + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout.strip() == os.linesep.strip() + assert result.stderr == "" + + def test_execute_code_multiline_output( + self, mock_invocation_context: InvocationContext + ): + executor = IsolatedCodeExecutor() + code = 'print("line 1")\nprint("line 2")' + code_input = CodeExecutionInput(code=code) + result = executor.execute_code(mock_invocation_context, code_input) + + assert result.stdout == "line 1\nline 2\n" + assert result.stderr == "" From b0f3f6ca06fce7ebeb2bd7d9de5457db0bf0912b Mon Sep 17 00:00:00 2001 From: AlexFierro9 Date: Fri, 24 Oct 2025 20:59:40 +0530 Subject: [PATCH 5/5] run autoformat.sh --- src/google/adk/code_executors/isolated_code_executor.py | 2 +- tests/unittests/code_executors/test_isolated_code_executor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/google/adk/code_executors/isolated_code_executor.py b/src/google/adk/code_executors/isolated_code_executor.py index 0e1b2c927d..7b74786c43 100644 --- a/src/google/adk/code_executors/isolated_code_executor.py +++ b/src/google/adk/code_executors/isolated_code_executor.py @@ -1,7 +1,7 @@ from __future__ import annotations -import sys import subprocess +import sys from pydantic import Field from typing_extensions import override diff --git a/tests/unittests/code_executors/test_isolated_code_executor.py b/tests/unittests/code_executors/test_isolated_code_executor.py index 36feebc5b1..70a1553b8a 100644 --- a/tests/unittests/code_executors/test_isolated_code_executor.py +++ b/tests/unittests/code_executors/test_isolated_code_executor.py @@ -1,3 +1,4 @@ +import os from unittest.mock import MagicMock from google.adk.agents.base_agent import BaseAgent @@ -8,7 +9,6 @@ from google.adk.sessions.base_session_service import BaseSessionService from google.adk.sessions.session import Session import pytest -import os @pytest.fixture