codeshield-runtime 0.1.2__tar.gz → 0.1.3__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.
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/PKG-INFO +55 -12
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/README.md +54 -11
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/pyproject.toml +1 -1
- codeshield_runtime-0.1.3/src/codeshield/__init__.py +46 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/environment.py +7 -1
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/loop.py +21 -3
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/runner.py +2 -1
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/tools.py +5 -5
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/tests/test_coverage.py +7 -5
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/tests/test_engine.py +8 -7
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/tests/test_tools.py +10 -2
- codeshield_runtime-0.1.2/src/codeshield/__init__.py +0 -17
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/.gitignore +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/LICENSE +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/__main__.py +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/analyzer.py +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/classifier.py +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/cli.py +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/src/codeshield/schemas.py +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/tests/__init__.py +0 -0
- {codeshield_runtime-0.1.2 → codeshield_runtime-0.1.3}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codeshield-runtime
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A secure, isolated, and self-healing Python code execution engine powered by uv and AST analysis.
|
|
5
5
|
Project-URL: Homepage, https://github.com/AlgorithmicMind/codeshield
|
|
6
6
|
Project-URL: Repository, https://github.com/AlgorithmicMind/codeshield
|
|
@@ -40,6 +40,8 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
[](https://www.python.org/)
|
|
41
41
|
[](LICENSE)
|
|
42
42
|
[](https://github.com/astral-sh/ruff)
|
|
43
|
+
[](https://pypi.org/project/codeshield-runtime/)
|
|
44
|
+
[](https://pypi.org/project/codeshield-runtime/)
|
|
43
45
|
|
|
44
46
|
> Deterministic, Isolated, and Self-Healing Python Execution Runtime for AI Agents.
|
|
45
47
|
|
|
@@ -142,12 +144,18 @@ pip install -e ".[test,lint,llm,dev]"
|
|
|
142
144
|
### Offline Usage (No API Key)
|
|
143
145
|
|
|
144
146
|
```python
|
|
145
|
-
from codeshield
|
|
147
|
+
from codeshield import SelfHealingEngine
|
|
146
148
|
|
|
149
|
+
# The sandbox is created and destroyed automatically on every ``run`` call.
|
|
147
150
|
engine = SelfHealingEngine(use_llm=False)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
result, diagnosis = engine.run("print('hello world')")
|
|
152
|
+
print(result.stdout)
|
|
153
|
+
|
|
154
|
+
# Use a ``with`` block to reuse a single sandbox across multiple runs.
|
|
155
|
+
with SelfHealingEngine(use_llm=False) as reusable_engine:
|
|
156
|
+
first, _ = reusable_engine.run("print(1 + 1)")
|
|
157
|
+
second, _ = reusable_engine.run("print(2 + 2)")
|
|
158
|
+
print(first.stdout, second.stdout)
|
|
151
159
|
```
|
|
152
160
|
|
|
153
161
|
### Model-Agnostic Self-Healing (Plug-and-Play)
|
|
@@ -155,13 +163,13 @@ with engine:
|
|
|
155
163
|
CodeShield is not locked into a single LLM. Pass any Python callable as the `patch_generator` to use OpenAI, Anthropic Claude, DeepSeek, Ollama, LiteLLM or your own service:
|
|
156
164
|
|
|
157
165
|
```python
|
|
158
|
-
from codeshield
|
|
166
|
+
from codeshield import SelfHealingEngine
|
|
159
167
|
|
|
160
168
|
|
|
161
169
|
def custom_llm_patcher(code: str, diagnosis) -> str:
|
|
162
|
-
# Compatible with any frontier provider: GPT-5.
|
|
170
|
+
# Compatible with any frontier provider: GPT-5.6, Claude Sonnet 5, DeepSeek V4, Ollama
|
|
163
171
|
response = client.chat.completions.create(
|
|
164
|
-
model="gpt-5.
|
|
172
|
+
model="gpt-5.6-luna", # or "claude-sonnet-5", "deepseek-v4-flash"
|
|
165
173
|
messages=[
|
|
166
174
|
{
|
|
167
175
|
"role": "user",
|
|
@@ -173,6 +181,11 @@ def custom_llm_patcher(code: str, diagnosis) -> str:
|
|
|
173
181
|
|
|
174
182
|
|
|
175
183
|
engine = SelfHealingEngine(patch_generator=custom_llm_patcher)
|
|
184
|
+
|
|
185
|
+
# Broken code -> AST gate -> sandbox execution -> traceback classification ->
|
|
186
|
+
# custom patch -> AST re-validation -> re-execution, all in a single call.
|
|
187
|
+
result, diagnosis = engine.run('print("Result: " + 42)')
|
|
188
|
+
print(result.stdout) # Result: 42
|
|
176
189
|
```
|
|
177
190
|
|
|
178
191
|
### Zero-Config Self-Healing with Gemini Flash
|
|
@@ -186,7 +199,8 @@ GEMINI_MODEL=gemini-3.7-flash
|
|
|
186
199
|
|
|
187
200
|
```python
|
|
188
201
|
from dotenv import load_dotenv
|
|
189
|
-
|
|
202
|
+
|
|
203
|
+
from codeshield import SelfHealingEngine
|
|
190
204
|
|
|
191
205
|
load_dotenv()
|
|
192
206
|
|
|
@@ -218,12 +232,41 @@ python -m codeshield run script.py --no-llm # force local fallback
|
|
|
218
232
|
```python
|
|
219
233
|
from codeshield import create_code_execution_tool
|
|
220
234
|
|
|
221
|
-
#
|
|
235
|
+
# Standard usage: built-in Gemini healing when configured, local heuristic otherwise
|
|
222
236
|
tools = [create_code_execution_tool()]
|
|
237
|
+
|
|
238
|
+
# Or bring your own model: the patcher is forwarded to the internal engine
|
|
239
|
+
tools = [create_code_execution_tool(patch_generator=custom_llm_patcher)]
|
|
223
240
|
```
|
|
224
241
|
|
|
225
242
|
`create_code_execution_tool()` returns a ready-to-register `execute_python_code(code: str) -> str` function. It runs the provided Python in a self-healing sandbox and returns either the stdout or a structured error report with `error_type` and `stderr`.
|
|
226
243
|
|
|
244
|
+
The callable exposes real type hints and a Google-style docstring, so any SDK that builds a function schema from a plain Python callable can register it directly.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Public API
|
|
249
|
+
|
|
250
|
+
Everything is re-exported at the package root, so imports never need internal submodules:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from codeshield import (
|
|
254
|
+
ASTSecurityError,
|
|
255
|
+
CodeExecutionRequest,
|
|
256
|
+
ErrorDiagnosis,
|
|
257
|
+
ExecutionResult,
|
|
258
|
+
SandboxManager,
|
|
259
|
+
SelfHealingEngine,
|
|
260
|
+
SelfHealingError,
|
|
261
|
+
SubprocessRunner,
|
|
262
|
+
TracebackClassifier,
|
|
263
|
+
create_code_execution_tool,
|
|
264
|
+
validate_syntax_and_safety,
|
|
265
|
+
)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`ASTSecurityError` subclasses `SelfHealingError` and is raised whenever the static AST gate blocks either the original snippet or a generated patch.
|
|
269
|
+
|
|
227
270
|
---
|
|
228
271
|
|
|
229
272
|
## Verified Examples
|
|
@@ -233,7 +276,7 @@ The `examples/` folder contains ready-to-run recipes that have been executed and
|
|
|
233
276
|
- `01_basic_sandboxing.py`: isolated execution with timing measurements.
|
|
234
277
|
- `02_security_gatekeeper.py`: AST rejection of unsafe code.
|
|
235
278
|
- `03_llm_healing_workflow.py`: self-healing workflow with an LLM or local fallback.
|
|
236
|
-
- `04_agent_tool_dropin.py`:
|
|
279
|
+
- `04_agent_tool_dropin.py`: dual-phase agentic trace, printing agent thought, tool call, sandbox runtime and final answer for both a legitimate analytics round and a security-defense round where the AST gate blocks a shell escape.
|
|
237
280
|
- `05_custom_llm_openai_compatible.py`: model-agnostic, API-key-free self-healing with a custom `patch_generator`.
|
|
238
281
|
|
|
239
282
|
```bash
|
|
@@ -246,7 +289,7 @@ python examples/05_custom_llm_openai_compatible.py
|
|
|
246
289
|
|
|
247
290
|
## Running Tests & Lint
|
|
248
291
|
|
|
249
|
-
The suite currently has **
|
|
292
|
+
The suite currently has **54 tests** with **>83% code coverage** on `src/codeshield`.
|
|
250
293
|
|
|
251
294
|
```bash
|
|
252
295
|
ruff check src tests examples
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[](https://www.python.org/)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://github.com/astral-sh/ruff)
|
|
7
|
+
[](https://pypi.org/project/codeshield-runtime/)
|
|
8
|
+
[](https://pypi.org/project/codeshield-runtime/)
|
|
7
9
|
|
|
8
10
|
> Deterministic, Isolated, and Self-Healing Python Execution Runtime for AI Agents.
|
|
9
11
|
|
|
@@ -106,12 +108,18 @@ pip install -e ".[test,lint,llm,dev]"
|
|
|
106
108
|
### Offline Usage (No API Key)
|
|
107
109
|
|
|
108
110
|
```python
|
|
109
|
-
from codeshield
|
|
111
|
+
from codeshield import SelfHealingEngine
|
|
110
112
|
|
|
113
|
+
# The sandbox is created and destroyed automatically on every ``run`` call.
|
|
111
114
|
engine = SelfHealingEngine(use_llm=False)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
result, diagnosis = engine.run("print('hello world')")
|
|
116
|
+
print(result.stdout)
|
|
117
|
+
|
|
118
|
+
# Use a ``with`` block to reuse a single sandbox across multiple runs.
|
|
119
|
+
with SelfHealingEngine(use_llm=False) as reusable_engine:
|
|
120
|
+
first, _ = reusable_engine.run("print(1 + 1)")
|
|
121
|
+
second, _ = reusable_engine.run("print(2 + 2)")
|
|
122
|
+
print(first.stdout, second.stdout)
|
|
115
123
|
```
|
|
116
124
|
|
|
117
125
|
### Model-Agnostic Self-Healing (Plug-and-Play)
|
|
@@ -119,13 +127,13 @@ with engine:
|
|
|
119
127
|
CodeShield is not locked into a single LLM. Pass any Python callable as the `patch_generator` to use OpenAI, Anthropic Claude, DeepSeek, Ollama, LiteLLM or your own service:
|
|
120
128
|
|
|
121
129
|
```python
|
|
122
|
-
from codeshield
|
|
130
|
+
from codeshield import SelfHealingEngine
|
|
123
131
|
|
|
124
132
|
|
|
125
133
|
def custom_llm_patcher(code: str, diagnosis) -> str:
|
|
126
|
-
# Compatible with any frontier provider: GPT-5.
|
|
134
|
+
# Compatible with any frontier provider: GPT-5.6, Claude Sonnet 5, DeepSeek V4, Ollama
|
|
127
135
|
response = client.chat.completions.create(
|
|
128
|
-
model="gpt-5.
|
|
136
|
+
model="gpt-5.6-luna", # or "claude-sonnet-5", "deepseek-v4-flash"
|
|
129
137
|
messages=[
|
|
130
138
|
{
|
|
131
139
|
"role": "user",
|
|
@@ -137,6 +145,11 @@ def custom_llm_patcher(code: str, diagnosis) -> str:
|
|
|
137
145
|
|
|
138
146
|
|
|
139
147
|
engine = SelfHealingEngine(patch_generator=custom_llm_patcher)
|
|
148
|
+
|
|
149
|
+
# Broken code -> AST gate -> sandbox execution -> traceback classification ->
|
|
150
|
+
# custom patch -> AST re-validation -> re-execution, all in a single call.
|
|
151
|
+
result, diagnosis = engine.run('print("Result: " + 42)')
|
|
152
|
+
print(result.stdout) # Result: 42
|
|
140
153
|
```
|
|
141
154
|
|
|
142
155
|
### Zero-Config Self-Healing with Gemini Flash
|
|
@@ -150,7 +163,8 @@ GEMINI_MODEL=gemini-3.7-flash
|
|
|
150
163
|
|
|
151
164
|
```python
|
|
152
165
|
from dotenv import load_dotenv
|
|
153
|
-
|
|
166
|
+
|
|
167
|
+
from codeshield import SelfHealingEngine
|
|
154
168
|
|
|
155
169
|
load_dotenv()
|
|
156
170
|
|
|
@@ -182,12 +196,41 @@ python -m codeshield run script.py --no-llm # force local fallback
|
|
|
182
196
|
```python
|
|
183
197
|
from codeshield import create_code_execution_tool
|
|
184
198
|
|
|
185
|
-
#
|
|
199
|
+
# Standard usage: built-in Gemini healing when configured, local heuristic otherwise
|
|
186
200
|
tools = [create_code_execution_tool()]
|
|
201
|
+
|
|
202
|
+
# Or bring your own model: the patcher is forwarded to the internal engine
|
|
203
|
+
tools = [create_code_execution_tool(patch_generator=custom_llm_patcher)]
|
|
187
204
|
```
|
|
188
205
|
|
|
189
206
|
`create_code_execution_tool()` returns a ready-to-register `execute_python_code(code: str) -> str` function. It runs the provided Python in a self-healing sandbox and returns either the stdout or a structured error report with `error_type` and `stderr`.
|
|
190
207
|
|
|
208
|
+
The callable exposes real type hints and a Google-style docstring, so any SDK that builds a function schema from a plain Python callable can register it directly.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Public API
|
|
213
|
+
|
|
214
|
+
Everything is re-exported at the package root, so imports never need internal submodules:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from codeshield import (
|
|
218
|
+
ASTSecurityError,
|
|
219
|
+
CodeExecutionRequest,
|
|
220
|
+
ErrorDiagnosis,
|
|
221
|
+
ExecutionResult,
|
|
222
|
+
SandboxManager,
|
|
223
|
+
SelfHealingEngine,
|
|
224
|
+
SelfHealingError,
|
|
225
|
+
SubprocessRunner,
|
|
226
|
+
TracebackClassifier,
|
|
227
|
+
create_code_execution_tool,
|
|
228
|
+
validate_syntax_and_safety,
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`ASTSecurityError` subclasses `SelfHealingError` and is raised whenever the static AST gate blocks either the original snippet or a generated patch.
|
|
233
|
+
|
|
191
234
|
---
|
|
192
235
|
|
|
193
236
|
## Verified Examples
|
|
@@ -197,7 +240,7 @@ The `examples/` folder contains ready-to-run recipes that have been executed and
|
|
|
197
240
|
- `01_basic_sandboxing.py`: isolated execution with timing measurements.
|
|
198
241
|
- `02_security_gatekeeper.py`: AST rejection of unsafe code.
|
|
199
242
|
- `03_llm_healing_workflow.py`: self-healing workflow with an LLM or local fallback.
|
|
200
|
-
- `04_agent_tool_dropin.py`:
|
|
243
|
+
- `04_agent_tool_dropin.py`: dual-phase agentic trace, printing agent thought, tool call, sandbox runtime and final answer for both a legitimate analytics round and a security-defense round where the AST gate blocks a shell escape.
|
|
201
244
|
- `05_custom_llm_openai_compatible.py`: model-agnostic, API-key-free self-healing with a custom `patch_generator`.
|
|
202
245
|
|
|
203
246
|
```bash
|
|
@@ -210,7 +253,7 @@ python examples/05_custom_llm_openai_compatible.py
|
|
|
210
253
|
|
|
211
254
|
## Running Tests & Lint
|
|
212
255
|
|
|
213
|
-
The suite currently has **
|
|
256
|
+
The suite currently has **54 tests** with **>83% code coverage** on `src/codeshield`.
|
|
214
257
|
|
|
215
258
|
```bash
|
|
216
259
|
ruff check src tests examples
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeshield-runtime"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "A secure, isolated, and self-healing Python code execution engine powered by uv and AST analysis."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""CodeShield.
|
|
2
|
+
|
|
3
|
+
A secure, isolated, and self-healing Python code execution engine.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.3"
|
|
7
|
+
|
|
8
|
+
from codeshield.analyzer import validate_syntax_and_safety
|
|
9
|
+
from codeshield.classifier import TracebackClassifier
|
|
10
|
+
from codeshield.environment import SandboxError, SandboxManager
|
|
11
|
+
from codeshield.loop import (
|
|
12
|
+
ASTSecurityError,
|
|
13
|
+
GeminiPatchGenerator,
|
|
14
|
+
LLMPatchError,
|
|
15
|
+
SelfHealingEngine,
|
|
16
|
+
SelfHealingError,
|
|
17
|
+
)
|
|
18
|
+
from codeshield.runner import SubprocessRunner, SubprocessRunnerError
|
|
19
|
+
from codeshield.schemas import (
|
|
20
|
+
CodeExecutionRequest,
|
|
21
|
+
ErrorDiagnosis,
|
|
22
|
+
ExecutionResult,
|
|
23
|
+
PatchProposal,
|
|
24
|
+
ValidationReport,
|
|
25
|
+
)
|
|
26
|
+
from codeshield.tools import create_code_execution_tool
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"ASTSecurityError",
|
|
30
|
+
"CodeExecutionRequest",
|
|
31
|
+
"ErrorDiagnosis",
|
|
32
|
+
"ExecutionResult",
|
|
33
|
+
"GeminiPatchGenerator",
|
|
34
|
+
"LLMPatchError",
|
|
35
|
+
"PatchProposal",
|
|
36
|
+
"SandboxError",
|
|
37
|
+
"SandboxManager",
|
|
38
|
+
"SelfHealingEngine",
|
|
39
|
+
"SelfHealingError",
|
|
40
|
+
"SubprocessRunner",
|
|
41
|
+
"SubprocessRunnerError",
|
|
42
|
+
"TracebackClassifier",
|
|
43
|
+
"ValidationReport",
|
|
44
|
+
"create_code_execution_tool",
|
|
45
|
+
"validate_syntax_and_safety",
|
|
46
|
+
]
|
|
@@ -65,7 +65,7 @@ class SandboxManager:
|
|
|
65
65
|
|
|
66
66
|
@property
|
|
67
67
|
def workspace_path(self) -> Path:
|
|
68
|
-
"""Alias
|
|
68
|
+
"""Alias of :attr:`workspace` used for explicit lifecycle checks."""
|
|
69
69
|
return self._workspace
|
|
70
70
|
|
|
71
71
|
@property
|
|
@@ -200,6 +200,10 @@ class SandboxManager:
|
|
|
200
200
|
) -> Path:
|
|
201
201
|
"""Write package specifiers to ``workspace/requirements.txt``.
|
|
202
202
|
|
|
203
|
+
Args:
|
|
204
|
+
packages: Package specifiers to write, one per line.
|
|
205
|
+
file_name: Name of the requirements file inside the workspace.
|
|
206
|
+
|
|
203
207
|
Returns:
|
|
204
208
|
Path of the written file.
|
|
205
209
|
"""
|
|
@@ -218,10 +222,12 @@ class SandboxManager:
|
|
|
218
222
|
logger.warning("Could not remove workspace %s: %s", self._workspace, exc)
|
|
219
223
|
|
|
220
224
|
def __enter__(self) -> SandboxManager:
|
|
225
|
+
"""Create the virtual environment and return the manager."""
|
|
221
226
|
self.create()
|
|
222
227
|
return self
|
|
223
228
|
|
|
224
229
|
def __exit__(self, *exc: object) -> None:
|
|
230
|
+
"""Remove the workspace when leaving the context."""
|
|
225
231
|
self.cleanup()
|
|
226
232
|
|
|
227
233
|
def _resolve_backend(self, backend: str | None) -> str:
|
|
@@ -34,6 +34,10 @@ class SelfHealingError(RuntimeError):
|
|
|
34
34
|
"""Raised when the self-healing loop cannot complete execution safely."""
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
class ASTSecurityError(SelfHealingError):
|
|
38
|
+
"""Raised when code or a generated patch is blocked by the static AST gate."""
|
|
39
|
+
|
|
40
|
+
|
|
37
41
|
class LLMPatchError(RuntimeError):
|
|
38
42
|
"""Raised when the LLM patch generator cannot produce a safe correction."""
|
|
39
43
|
|
|
@@ -239,6 +243,8 @@ class SelfHealingEngine:
|
|
|
239
243
|
Raises:
|
|
240
244
|
SelfHealingError: when the loop exhausts all iterations without a
|
|
241
245
|
clean result.
|
|
246
|
+
ASTSecurityError: when the code or a generated patch is blocked by
|
|
247
|
+
the static AST gate.
|
|
242
248
|
"""
|
|
243
249
|
if isinstance(request, str):
|
|
244
250
|
request = CodeExecutionRequest(code=request)
|
|
@@ -276,7 +282,7 @@ class SelfHealingEngine:
|
|
|
276
282
|
)
|
|
277
283
|
patched = self._generate_and_validate_patch(request.code, diagnosis)
|
|
278
284
|
if patched is None:
|
|
279
|
-
raise
|
|
285
|
+
raise ASTSecurityError(
|
|
280
286
|
f"Static safety violations cannot be auto-patched: {report.violations}"
|
|
281
287
|
)
|
|
282
288
|
request = self._apply_patch(request, patched, attempt)
|
|
@@ -337,9 +343,21 @@ class SelfHealingEngine:
|
|
|
337
343
|
proposal: PatchProposal,
|
|
338
344
|
attempt: int,
|
|
339
345
|
) -> CodeExecutionRequest:
|
|
340
|
-
"""Return a new request with the patched code and an updated file name.
|
|
346
|
+
"""Return a new request with the patched code and an updated file name.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
request: The request that produced the failing execution.
|
|
350
|
+
proposal: The AST-validated patch proposal to apply.
|
|
351
|
+
attempt: Current healing iteration, used to name the patched script.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
A new ``CodeExecutionRequest`` carrying the patched source.
|
|
355
|
+
|
|
356
|
+
Raises:
|
|
357
|
+
ASTSecurityError: when the proposal failed static AST validation.
|
|
358
|
+
"""
|
|
341
359
|
if not proposal.is_syntax_valid:
|
|
342
|
-
raise
|
|
360
|
+
raise ASTSecurityError(
|
|
343
361
|
f"Proposed patch failed AST validation: {proposal.patched_code[:200]}"
|
|
344
362
|
)
|
|
345
363
|
|
|
@@ -10,6 +10,7 @@ import subprocess
|
|
|
10
10
|
import threading
|
|
11
11
|
import time
|
|
12
12
|
from collections.abc import Iterable
|
|
13
|
+
from typing import IO
|
|
13
14
|
|
|
14
15
|
from codeshield.environment import SandboxError, SandboxManager
|
|
15
16
|
from codeshield.schemas import CodeExecutionRequest, ExecutionResult
|
|
@@ -146,7 +147,7 @@ class SubprocessRunner:
|
|
|
146
147
|
stdout_lines: list[str] = []
|
|
147
148
|
stderr_lines: list[str] = []
|
|
148
149
|
|
|
149
|
-
def reader(pipe, sink: list[str]) -> None:
|
|
150
|
+
def reader(pipe: IO[str], sink: list[str]) -> None:
|
|
150
151
|
"""Read lines from a pipe without blocking the main thread."""
|
|
151
152
|
try:
|
|
152
153
|
for line in pipe:
|
|
@@ -5,9 +5,11 @@ The function returned by ``create_code_execution_tool`` can be registered as a
|
|
|
5
5
|
tool in any agent framework (LangChain, CrewAI, Google Gen AI, etc.). It runs
|
|
6
6
|
the provided Python source inside a self-healing sandbox and returns either the
|
|
7
7
|
stdout or a structured error report.
|
|
8
|
-
"""
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
Annotations in this module are intentionally *not* postponed: agent SDKs such as
|
|
10
|
+
``google-genai`` introspect ``execute_python_code.__annotations__`` at runtime to
|
|
11
|
+
build the function-calling schema, and string annotations break that conversion.
|
|
12
|
+
"""
|
|
11
13
|
|
|
12
14
|
from collections.abc import Callable
|
|
13
15
|
from typing import Optional
|
|
@@ -51,9 +53,7 @@ def create_code_execution_tool(
|
|
|
51
53
|
with _engine:
|
|
52
54
|
try:
|
|
53
55
|
result, diagnosis = _engine.run(code)
|
|
54
|
-
except SelfHealingError as exc:
|
|
55
|
-
return f"error_type: SelfHealingError\nmessage: {exc}"
|
|
56
|
-
except (SandboxError, SubprocessRunnerError) as exc:
|
|
56
|
+
except (SelfHealingError, SandboxError, SubprocessRunnerError) as exc:
|
|
57
57
|
return f"error_type: {type(exc).__name__}\nmessage: {exc}"
|
|
58
58
|
|
|
59
59
|
if (
|
|
@@ -9,15 +9,17 @@ from unittest.mock import MagicMock
|
|
|
9
9
|
|
|
10
10
|
import pytest
|
|
11
11
|
|
|
12
|
-
from codeshield
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
from codeshield import (
|
|
13
|
+
CodeExecutionRequest,
|
|
14
|
+
ErrorDiagnosis,
|
|
15
15
|
GeminiPatchGenerator,
|
|
16
|
+
SandboxError,
|
|
17
|
+
SandboxManager,
|
|
16
18
|
SelfHealingEngine,
|
|
17
19
|
SelfHealingError,
|
|
20
|
+
SubprocessRunner,
|
|
21
|
+
TracebackClassifier,
|
|
18
22
|
)
|
|
19
|
-
from codeshield.runner import SubprocessRunner
|
|
20
|
-
from codeshield.schemas import CodeExecutionRequest, ErrorDiagnosis
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@pytest.fixture
|
|
@@ -4,13 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from codeshield
|
|
8
|
-
|
|
9
|
-
from codeshield.environment import SandboxManager
|
|
10
|
-
from codeshield.loop import SelfHealingEngine, SelfHealingError
|
|
11
|
-
from codeshield.runner import SubprocessRunner
|
|
12
|
-
from codeshield.schemas import (
|
|
7
|
+
from codeshield import (
|
|
8
|
+
ASTSecurityError,
|
|
13
9
|
ExecutionResult,
|
|
10
|
+
SandboxManager,
|
|
11
|
+
SelfHealingEngine,
|
|
12
|
+
SubprocessRunner,
|
|
13
|
+
TracebackClassifier,
|
|
14
|
+
validate_syntax_and_safety,
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
|
|
@@ -277,5 +278,5 @@ def test_custom_patch_generator_ast_rejection() -> None:
|
|
|
277
278
|
use_llm=False,
|
|
278
279
|
)
|
|
279
280
|
|
|
280
|
-
with pytest.raises(
|
|
281
|
+
with pytest.raises(ASTSecurityError):
|
|
281
282
|
engine.run("print(undefined_value)")
|
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from codeshield import create_code_execution_tool
|
|
6
|
-
|
|
5
|
+
from codeshield import SelfHealingEngine, create_code_execution_tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_tool_exposes_runtime_type_annotations() -> None:
|
|
9
|
+
"""Agent SDKs build the schema from ``__annotations__``, so they must be types."""
|
|
10
|
+
tool = create_code_execution_tool()
|
|
11
|
+
|
|
12
|
+
assert tool.__name__ == "execute_python_code"
|
|
13
|
+
assert tool.__annotations__["code"] is str
|
|
14
|
+
assert tool.__annotations__["return"] is str
|
|
7
15
|
|
|
8
16
|
|
|
9
17
|
def test_tool_returns_stdout_on_success() -> None:
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""CodeShield.
|
|
2
|
-
|
|
3
|
-
A secure, isolated, and self-healing Python code execution engine.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
__version__ = "0.1.0"
|
|
7
|
-
|
|
8
|
-
from codeshield.loop import SelfHealingEngine
|
|
9
|
-
from codeshield.schemas import CodeExecutionRequest, ExecutionResult
|
|
10
|
-
from codeshield.tools import create_code_execution_tool
|
|
11
|
-
|
|
12
|
-
__all__ = [
|
|
13
|
-
"SelfHealingEngine",
|
|
14
|
-
"CodeExecutionRequest",
|
|
15
|
-
"ExecutionResult",
|
|
16
|
-
"create_code_execution_tool",
|
|
17
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|