codeshield-runtime 0.1.0__tar.gz → 0.1.2__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 (20) hide show
  1. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/PKG-INFO +8 -6
  2. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/README.md +7 -5
  3. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/pyproject.toml +1 -1
  4. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/environment.py +5 -0
  5. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/loop.py +22 -1
  6. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/tools.py +8 -2
  7. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/tests/test_engine.py +48 -1
  8. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/.gitignore +0 -0
  9. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/LICENSE +0 -0
  10. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/__init__.py +0 -0
  11. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/__main__.py +0 -0
  12. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/analyzer.py +0 -0
  13. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/classifier.py +0 -0
  14. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/cli.py +0 -0
  15. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/runner.py +0 -0
  16. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/src/codeshield/schemas.py +0 -0
  17. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/tests/__init__.py +0 -0
  18. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/tests/test_cli.py +0 -0
  19. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/tests/test_coverage.py +0 -0
  20. {codeshield_runtime-0.1.0 → codeshield_runtime-0.1.2}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codeshield-runtime
3
- Version: 0.1.0
3
+ Version: 0.1.2
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
@@ -158,10 +158,10 @@ CodeShield is not locked into a single LLM. Pass any Python callable as the `pat
158
158
  from codeshield.loop import SelfHealingEngine
159
159
 
160
160
 
161
- def custom_openai_patcher(code: str, diagnosis) -> str:
162
- # Any LLM call (OpenAI, Anthropic, DeepSeek, Ollama, LiteLLM)
161
+ def custom_llm_patcher(code: str, diagnosis) -> str:
162
+ # Compatible with any frontier provider: GPT-5.4, Claude Sonnet 5, DeepSeek V4, Ollama
163
163
  response = client.chat.completions.create(
164
- model="gpt-4o-mini",
164
+ model="gpt-5.4-mini", # or "claude-sonnet-5", "deepseek-v4-flash"
165
165
  messages=[
166
166
  {
167
167
  "role": "user",
@@ -172,7 +172,7 @@ def custom_openai_patcher(code: str, diagnosis) -> str:
172
172
  return response.choices[0].message.content
173
173
 
174
174
 
175
- engine = SelfHealingEngine(patch_generator=custom_openai_patcher)
175
+ engine = SelfHealingEngine(patch_generator=custom_llm_patcher)
176
176
  ```
177
177
 
178
178
  ### Zero-Config Self-Healing with Gemini Flash
@@ -234,17 +234,19 @@ The `examples/` folder contains ready-to-run recipes that have been executed and
234
234
  - `02_security_gatekeeper.py`: AST rejection of unsafe code.
235
235
  - `03_llm_healing_workflow.py`: self-healing workflow with an LLM or local fallback.
236
236
  - `04_agent_tool_dropin.py`: end-to-end agentic tool-calling workflow with dynamic code generation.
237
+ - `05_custom_llm_openai_compatible.py`: model-agnostic, API-key-free self-healing with a custom `patch_generator`.
237
238
 
238
239
  ```bash
239
240
  python examples/01_basic_sandboxing.py
240
241
  python examples/02_security_gatekeeper.py
241
242
  python examples/03_llm_healing_workflow.py
242
243
  python examples/04_agent_tool_dropin.py
244
+ python examples/05_custom_llm_openai_compatible.py
243
245
  ```
244
246
 
245
247
  ## Running Tests & Lint
246
248
 
247
- The suite currently has **50 tests** with **>82% code coverage** on `src/codeshield`.
249
+ The suite currently has **53 tests** with **>82% code coverage** on `src/codeshield`.
248
250
 
249
251
  ```bash
250
252
  ruff check src tests examples
@@ -122,10 +122,10 @@ CodeShield is not locked into a single LLM. Pass any Python callable as the `pat
122
122
  from codeshield.loop import SelfHealingEngine
123
123
 
124
124
 
125
- def custom_openai_patcher(code: str, diagnosis) -> str:
126
- # Any LLM call (OpenAI, Anthropic, DeepSeek, Ollama, LiteLLM)
125
+ def custom_llm_patcher(code: str, diagnosis) -> str:
126
+ # Compatible with any frontier provider: GPT-5.4, Claude Sonnet 5, DeepSeek V4, Ollama
127
127
  response = client.chat.completions.create(
128
- model="gpt-4o-mini",
128
+ model="gpt-5.4-mini", # or "claude-sonnet-5", "deepseek-v4-flash"
129
129
  messages=[
130
130
  {
131
131
  "role": "user",
@@ -136,7 +136,7 @@ def custom_openai_patcher(code: str, diagnosis) -> str:
136
136
  return response.choices[0].message.content
137
137
 
138
138
 
139
- engine = SelfHealingEngine(patch_generator=custom_openai_patcher)
139
+ engine = SelfHealingEngine(patch_generator=custom_llm_patcher)
140
140
  ```
141
141
 
142
142
  ### Zero-Config Self-Healing with Gemini Flash
@@ -198,17 +198,19 @@ The `examples/` folder contains ready-to-run recipes that have been executed and
198
198
  - `02_security_gatekeeper.py`: AST rejection of unsafe code.
199
199
  - `03_llm_healing_workflow.py`: self-healing workflow with an LLM or local fallback.
200
200
  - `04_agent_tool_dropin.py`: end-to-end agentic tool-calling workflow with dynamic code generation.
201
+ - `05_custom_llm_openai_compatible.py`: model-agnostic, API-key-free self-healing with a custom `patch_generator`.
201
202
 
202
203
  ```bash
203
204
  python examples/01_basic_sandboxing.py
204
205
  python examples/02_security_gatekeeper.py
205
206
  python examples/03_llm_healing_workflow.py
206
207
  python examples/04_agent_tool_dropin.py
208
+ python examples/05_custom_llm_openai_compatible.py
207
209
  ```
208
210
 
209
211
  ## Running Tests & Lint
210
212
 
211
- The suite currently has **50 tests** with **>82% code coverage** on `src/codeshield`.
213
+ The suite currently has **53 tests** with **>82% code coverage** on `src/codeshield`.
212
214
 
213
215
  ```bash
214
216
  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.0"
7
+ version = "0.1.2"
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" }
@@ -63,6 +63,11 @@ class SandboxManager:
63
63
  """Return the root sandbox workspace directory."""
64
64
  return self._workspace
65
65
 
66
+ @property
67
+ def workspace_path(self) -> Path:
68
+ """Alias for the root sandbox workspace directory."""
69
+ return self._workspace
70
+
66
71
  @property
67
72
  def venv_path(self) -> Path:
68
73
  """Return the virtual environment directory."""
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: UP045
1
2
  """Self-healing execution loop."""
2
3
 
3
4
  from __future__ import annotations
@@ -7,6 +8,7 @@ import os
7
8
  import re
8
9
  from collections.abc import Callable
9
10
  from pathlib import Path
11
+ from typing import Optional
10
12
 
11
13
  from codeshield.analyzer import validate_syntax_and_safety
12
14
  from codeshield.classifier import TracebackClassifier
@@ -173,7 +175,7 @@ class SelfHealingEngine:
173
175
  sandbox: SandboxManager | None = None,
174
176
  runner: SubprocessRunner | None = None,
175
177
  classifier: TracebackClassifier | None = None,
176
- patch_generator: Callable[[str, ErrorDiagnosis], str | None] | None = None,
178
+ patch_generator: Optional[Callable[[str, ErrorDiagnosis], Optional[str]]] = None,
177
179
  gemini_api_key: str | None = None,
178
180
  gemini_model: str | None = None,
179
181
  use_llm: bool = True,
@@ -224,6 +226,9 @@ class SelfHealingEngine:
224
226
  ) -> tuple[ExecutionResult, ErrorDiagnosis | None]:
225
227
  """Run code through the AST -> sandbox -> heal loop.
226
228
 
229
+ The sandbox is created automatically if ``run`` is called outside a
230
+ ``with`` context manager and is cleaned up once execution finishes.
231
+
227
232
  Args:
228
233
  request: Either a ``CodeExecutionRequest`` or a raw source string.
229
234
  timeout: Optional execution timeout override.
@@ -238,6 +243,22 @@ class SelfHealingEngine:
238
243
  if isinstance(request, str):
239
244
  request = CodeExecutionRequest(code=request)
240
245
 
246
+ auto_created = not self._sandbox.python_executable.exists()
247
+ if auto_created:
248
+ self._sandbox.create()
249
+
250
+ try:
251
+ return self._run_loop(request, timeout)
252
+ finally:
253
+ if auto_created:
254
+ self._sandbox.cleanup()
255
+
256
+ def _run_loop(
257
+ self,
258
+ request: CodeExecutionRequest,
259
+ timeout: float | None,
260
+ ) -> tuple[ExecutionResult, ErrorDiagnosis | None]:
261
+ """Execute the AST -> sandbox -> heal loop assuming the sandbox exists."""
241
262
  diagnosis: ErrorDiagnosis | None = None
242
263
  for attempt in range(1, self._max_iterations + 1):
243
264
  logger.info("Self-healing iteration %d/%d", attempt, self._max_iterations)
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: UP045
1
2
  """Universal agent tool wrapper for CodeShield.
2
3
 
3
4
  The function returned by ``create_code_execution_tool`` can be registered as a
@@ -9,20 +10,25 @@ stdout or a structured error report.
9
10
  from __future__ import annotations
10
11
 
11
12
  from collections.abc import Callable
13
+ from typing import Optional
12
14
 
13
15
  from codeshield.environment import SandboxError
14
16
  from codeshield.loop import SelfHealingEngine, SelfHealingError
15
17
  from codeshield.runner import SubprocessRunnerError
18
+ from codeshield.schemas import ErrorDiagnosis
16
19
 
17
20
 
18
21
  def create_code_execution_tool(
19
- engine: SelfHealingEngine | None = None,
22
+ engine: Optional[SelfHealingEngine] = None,
23
+ patch_generator: Optional[Callable[[str, ErrorDiagnosis], Optional[str]]] = None,
20
24
  ) -> Callable[[str], str]:
21
25
  """Return a drop-in ``execute_python_code(code: str) -> str`` tool.
22
26
 
23
27
  Args:
24
28
  engine: Optional ``SelfHealingEngine`` instance. When ``None``, a fresh
25
29
  engine is created for each tool call.
30
+ patch_generator: Optional custom patcher ``(code, diagnosis) -> patched``
31
+ forwarded to ``SelfHealingEngine`` when ``engine`` is not provided.
26
32
 
27
33
  Returns:
28
34
  A callable ready to be registered as an agent tool.
@@ -41,7 +47,7 @@ def create_code_execution_tool(
41
47
  The stdout of the script if execution succeeds, or a structured
42
48
  error report if it fails after all self-healing attempts.
43
49
  """
44
- _engine = engine or SelfHealingEngine()
50
+ _engine = engine or SelfHealingEngine(patch_generator=patch_generator)
45
51
  with _engine:
46
52
  try:
47
53
  result, diagnosis = _engine.run(code)
@@ -7,7 +7,7 @@ import pytest
7
7
  from codeshield.analyzer import validate_syntax_and_safety
8
8
  from codeshield.classifier import TracebackClassifier
9
9
  from codeshield.environment import SandboxManager
10
- from codeshield.loop import SelfHealingEngine
10
+ from codeshield.loop import SelfHealingEngine, SelfHealingError
11
11
  from codeshield.runner import SubprocessRunner
12
12
  from codeshield.schemas import (
13
13
  ExecutionResult,
@@ -232,3 +232,50 @@ def test_self_healing_engine_fixes_name_error() -> None:
232
232
  assert result.exit_code == 0
233
233
  assert result.stdout.strip() == "4.0"
234
234
  assert diagnosis is None
235
+
236
+
237
+ def test_self_healing_engine_run_without_context_manager() -> None:
238
+ """The engine can be used without a ``with`` block and still auto-manages the sandbox."""
239
+ engine = SelfHealingEngine(use_llm=False)
240
+
241
+ result, diagnosis = engine.run("print('no context manager')")
242
+
243
+ assert result.exit_code == 0
244
+ assert result.stdout.strip() == "no context manager"
245
+ assert diagnosis is None
246
+ assert not engine._sandbox.workspace_path.exists()
247
+
248
+
249
+ def test_custom_patch_generator_success() -> None:
250
+ """A custom patch_generator can repair code that the local fallback cannot."""
251
+
252
+ def custom_patcher(code: str, diagnosis) -> str | None:
253
+ if diagnosis.error_type == "TypeError":
254
+ return code.replace('"Result: " + 42', '"Result: " + str(42)')
255
+ return None
256
+
257
+ engine = SelfHealingEngine(
258
+ patch_generator=custom_patcher,
259
+ use_llm=False,
260
+ )
261
+ with engine:
262
+ result, diagnosis = engine.run('print("Result: " + 42)')
263
+
264
+ assert result.exit_code == 0
265
+ assert result.stdout.strip() == "Result: 42"
266
+ assert diagnosis is None
267
+
268
+
269
+ def test_custom_patch_generator_ast_rejection() -> None:
270
+ """A custom patch that violates the AST security gate is rejected."""
271
+
272
+ def malicious_patcher(code: str, diagnosis) -> str | None:
273
+ return 'import os\nos.system("echo pwned")'
274
+
275
+ engine = SelfHealingEngine(
276
+ patch_generator=malicious_patcher,
277
+ use_llm=False,
278
+ )
279
+
280
+ with pytest.raises(SelfHealingError):
281
+ engine.run("print(undefined_value)")